夜風のMixedReality

xRと出会って変わった人生と出会った技術を書き残すためのGeekなHoloRangerの居場所

MRTK StanderdShaderを読み解く その16 VertexExtrusion

本日はMRTKのShader調査枠です。

MRTKStandardShaderには様々な機能があります。そのほとんどがチェックボックスによって有効化でき、プリプロセッサによってコンパイル時に条件分岐されているので使いたい機能だけ処理が行われる仕組みになっています。

今回はVertexExtrusionに関して勉強していきます。

〇VertexExtrusionとは?

Vertex Extrusionとは頂点の押し出しという意味でその名のとおりメッシュの頂点を法線方向に押し出すことができます。

f:id:Holomoto-Sumire:20200817094715g:plain

一見スケールが変わっているだけのように見えますが、シェーダーでこの処理が行われています。

またオブジェクトのシェーディングによっては面として押し出されます。

f:id:Holomoto-Sumire:20200817095317g:plain

〇Shader

〇properties

   Properties
    {
        // Main maps.
        _Color("Color", Color) = (1.0, 1.0, 1.0, 1.0)
        _MainTex("Albedo", 2D) = "white" {}
  ...
        [Toggle(_VERTEX_EXTRUSION)] _VertexExtrusion("Vertex Extrusion", Float) = 0.0
        _VertexExtrusionValue("Vertex Extrusion Value", Float) = 0.0
        [Toggle(_VERTEX_EXTRUSION_SMOOTH_NORMALS)] _VertexExtrusionSmoothNormals("Vertex Extrusion Smooth Normals", Float) = 0.0
    }

propertiesでは[Toggle()]の属性でVertexExtrusionの機能を使用するか指定します。

 前述のとおりMRTKStandardShaderではこれによって必要な機能のみを処理します。

VertexExtrusionにはVertexExtrusionValueとvertexExtrusionSmoothNormalsの二つの項目があります。

〇頂点シェーダー

頂点シェーダーではモデルの頂点に関する処理を行います。

            v2f vert(appdata_t v)
            {
                v2f o;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
                float4 vertexPosition = v.vertex;
    ...
#if !defined(_VERTEX_EXTRUSION_SMOOTH_NORMALS)
                // uv3.y will contain a negative value when rendered by a UGUI and ScaleMeshEffect.
                if (v.uv3.y < 0.0)
                {
                    o.scale.x *= v.uv2.x;
                    o.scale.y *= v.uv2.y;
                    o.scale.z *= v.uv3.x;
                }
#endif

   ...

#if defined(_VERTEX_EXTRUSION)
#if defined(_VERTEX_EXTRUSION_SMOOTH_NORMALS)
                worldVertexPosition += UnityObjectToWorldNormal(v.uv2 * o.scale) * _VertexExtrusionValue;
#else
                worldVertexPosition += worldNormal * _VertexExtrusionValue;
#endif
                vertexPosition = mul(unity_WorldToObject, float4(worldVertexPosition, 1.0));
#endif
          ...
                return o;
            }

_VERTEX_EXTRUSION_SMOOTH_NORMALSが使用されていない場合(つまりVertexExtrusion Smooth Normalsを使用しない場合)次の処理が行われます。

  if (v.uv3.y < 0.0)
                {
                    o.scale.x *= v.uv2.x;
                    o.scale.y *= v.uv2.y;
                    o.scale.z *= v.uv3.x;
                }

if文はプログラミング言語において非常に多くの場で使用されますがShaderにおいては頂点やピクセルごとに条件分岐を行うため処理に負荷がかかることがあり、避けられる傾向にあります。

light11.hatenadiary.com

上のブログ記事によると使用しても処理が大きくならないケースとして次のような場合があるようです。

条件式に使われている値がstaticなものである

条件式に使われている値がuniform変数である

動的な判定であっても一回のレンダリングで必ずどちらかの分岐にしか入らない

今回の条件がv.uv3.y < 0.0(appdata_tのuv3のy成分が負)であり、v.uv3.yはif文の前のコメントアウトに以下の表記があります。

uv3.y will contain a negative value when rendered by a UGUI and ScaleMeshEffect.(UGUIとScaleMeshEffectでレンダリングすると、uv3.yには負の値が含まれます。)

つまりレンダリング時にすでにuv3.yが定まっており今回は一回のレンダリングで必ずどちらかの分岐にしか入らないという条件を満たすのでif文を使用しても負荷が大きくならないようです。

v2fのscaleのx成分にappdata_tのuv2のx成分が積算されます。y成分も同様です。

v2fのscaleのz成分にはappdata_tのx成分が積算されます。

#if defined(_VERTEX_EXTRUSION)
#if defined(_VERTEX_EXTRUSION_SMOOTH_NORMALS)
                worldVertexPosition += UnityObjectToWorldNormal(v.uv2 * o.scale) * _VertexExtrusionValue;
#else
                worldVertexPosition += worldNormal * _VertexExtrusionValue;
#endif
                vertexPosition = mul(unity_WorldToObject, float4(worldVertexPosition, 1.0));
#endif

  VertexExtrusionSmoothNormalsを使用している際は、worldVertexPositon(Unityのワールド座標での頂点)にappdata_tのuv2とv2fのscaleをUnityワールド座標の法線に変換して積算した値に_VertexExtrusionValueがさらに積算されています。

 VertexExtrusionSmoothNormalsを使用していない際は、そのままWorldNormal(頂点の持つ法線)に_VertexExtrusionValueが積算されます。

 法線方向に頂点座標を移動させています。これがVertexExtrusionの処理です。