夜風のMixedReality

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

MRTK HandTrianglesShaderの中身を読み解く その③ 頂点シェーダーを読み解く

昨日はHandTrianglesShaderのPropertiesとShaderLab部分を見ていきました。

redhologerbera.hatenablog.com

redhologerbera.hatenablog.com

さて、いよいよ今日からShaderの処理を読み解きます。

すべてのソースコードは以下のMRTKのGitHubで閲覧することができます。

github.com

まずは長いのでPassの内部の関数や構造体だけを見ていきます。

Pass{
CGPROGRAM

    #pragma vertex vertex_main
    #pragma fragment fragment_main
    #pragma geometry geometry_main
    #pragma multi_compile _ USE_ALBEDO_TEXTURE

    #include "UnityCG.cginc"

 struct VertexInput{
}
 struct VertexOutput{
}
 struct FragmentInput{
}
 VertexOutput vertex_main(VertexInput v) {
}
   void emitVertex_Bid177(){
}
    void Emit_Triangle_B177(){
}
 float3 Rotate_Bid164(float A, float3 Center, float3 Axis, float3 XYZ){
}
    void Fly_B164( ){
}
  void Find_Nearest_B163(){
}
void Average_B153(){
}
 float ramp_Bid160(){
}
 void Pulse_B160(){
}
  void Flip_V_For_Hydrogen_B154(){
}
 float2 mod289_Bid150(){
}
 float2 permute_Bid150(){
}
 float2 permuteB_Bid150(){
}
   void Cell_Noise_2D_B150(){
}
    void Pt_Sample_Texture_B157(){
}
    void AutoPulse_B149(){
}
   float2 mod289_Bid151(){
}
    float2 permute_Bid151(){
}
    float2 permuteB_Bid151(){
}
    void Cell_Noise_2D_B151(){
}
    [maxvertexcount(Geo_Max_Out_Vertices)]
    void geometry_main(){
}
  void Transition_B171(){
}
   void Edges_B168(){
}
   void Split_Color_Alpha_B172(){
}
  fixed4 fragment_main(FragmentInput fragInput) : SV_Target{
}
ENDCG
}

長い… 長いです。

●#pragma
    #pragma vertex vertex_main
    #pragma fragment fragment_main
    #pragma geometry geometry_main
    #pragma multi_compile _ USE_ALBEDO_TEXTURE

    #include "UnityCG.cginc"
    #pragma vertex vertex_main

vertex_mainという関数名で頂点シェーダーを使用するという記述です。

    #pragma fragment fragment_main

fragment_mainという関数名でフラグメントシェーダーを使用するという記述です。

    #pragma geometry geometry_main

geometry_mainという関数名でジオメトリシェーダーを使用するという記述です。

    #pragma multi_compile _ USE_ALBEDO_TEXTURE

ここで#pragma multi_compile はシェーダーバリアントを指します。シェーダーバリアントとは異なるプリプロセッサー指令でシェーダーコード複数回コンパイルする処理になります。

といっても理解できなかったため下のQiita記事を参照させていただきました。

qiita.com

これによると

            #pragma multi_compile _ USE_ALBEDO_TEXTURE

の場合_、とUSE_ALBEDO_TEXTUREの2つのキーワードが指定されています。 USE_ALBEDO_TEXTUREはプリプロセッサディレクティブを用いて処理を分岐できるようになるようです。

 これは661行目にて

   #if defined(USE_ALBEDO_TEXTURE)
          Pt_Sample_Texture_B157(Average_Q153,Result_Q151,_Color_Map_,_Vary_UV_,1,Color_Q157);
        #else
          Color_Q157 = float4(1,1,1,1);
        #endif

という処理でUSE_ALBEDO_TEXTUREがプロパティでonになっている場合とOffの場合で処理を分けています。

最後に

    #include "UnityCG.cginc"

でUnityCG.cgincをインクルードすることを宣言しています。

●頂点シェーダー
  VertexOutput vertex_main(VertexInput v)
    {
        VertexOutput o;
        UNITY_SETUP_INSTANCE_ID(v);
        UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
        UNITY_TRANSFER_INSTANCE_ID(v, o);


        o.pos = UnityObjectToClipPos(v.vertex);
        float4 posWorld = mul(unity_ObjectToWorld, v.vertex);
        o.posWorld = posWorld.xyz;

        o.uv = v.uv0;

        return o;
    }
    #pragma vertex vertex_main

で宣言されていた頂点シェーダーです。

ここもこれまで見た頂点関数同様

  VertexOutput vertex_main(VertexInput v)
    {
        VertexOutput o;

        return o;
    }

が定型でVertexInputから受け取り、処理をVertexOutputへと返すという意味になります。

        UNITY_SETUP_INSTANCE_ID(v);
        UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
        UNITY_TRANSFER_INSTANCE_ID(v, o);

UNITY_SETUP_INSTANCE_ID(v)はv=VertexInputの現在どちらの目を GPUレンダリングしているかの情報に基づいて、unity_StereoEyeIndex と unity_InstanceID の Unity のビルトインシェーダー変数を正しい値に計算し、設定します。

UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO()は unity_StereoEyeIndexの値に基づいてレンダリング先になるテクスチャの情報を GPU に伝えます。

 このunity_StereoEyeIndexというものはxRにおいて使用されるシングルレンダリングパスという描画の処理にかかわってくるものでHMDのディスプレイで左目のレンダリングでは 0、右目のレンダリングでは 1 の値を取ります。

HoloLensをはじめとするxRHMDでは右目と左目用の二つのディスプレイがあり、それぞれの映像を出力します。UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o)ではo=VertexOutputへ右目、左目それぞれの描画処理を行えるようにする分岐器のような意味を持っています。

シングルパスインスタンシングレンダリング - Unity マニュアル

https://forum.unity.com/threads/unity_vertex_output_stereo.449118/

        UNITY_TRANSFER_INSTANCE_ID(v, o);

 これはv=vertexInputからo=VertexOutputへインスタンスIDをコピーして渡す処理です。 これはフラグメントシェーダ内のインスタンスごとのデータにアクセスする必要がある場合にのみ必要はのようです。

docs.unity3d.com

o.pos = UnityObjectToClipPos(v.vertex);

は頂点の位置をカメラから見た座標へ変換しています。UnityObjectToClipPos()がその処理に当たります。これがo.posに当たります。

    float4 posWorld = mul(unity_ObjectToWorld, v.vertex);

頂点をワールド座標へ変換しています。これがposWorldに当たります。

       o.posWorld = posWorld.xyz;

o.posWorldに先ほどのposWorldを代入しています。

これをo=VertexOutputに代入します。

これがHandTrianglesShaderの頂点シェーダーの処理に当たります。

ここではVertexInputから値を受け取っています。次にVertexInputを見ていきます。

●VertexInput構造体
    struct VertexInput {
        float4 vertex : POSITION;
        float2 uv0 : TEXCOORD0;
        UNITY_VERTEX_INPUT_INSTANCE_ID
    };

頂点シェーダーの前提条件が記述されています。

 POSITION、TEXCOORD0はセマンティクスです。これはシェーダープログラムの入力や出力の値が、何を意味するかを表すためのものです。

 ここではvertexが頂点の位置、uv0が一つ目のテクスチャ座標を指します。

UNITY_VERTEX_INPUT_INSTANCE_IDは頂点シェーダーのInput/Output構造体で使用してインスタンス ID を定義します。

GPU インスタンシング - Unity マニュアル

インスタンスIDは、現在処理されているジオメトリのインスタンスを識別するために使われます。

docs.microsoft.com

 次に頂点シェーダーでの処理が渡されるVertexOutput構造体を見ていきます。

●VertexOutput構造体
 struct VertexOutput{
        float4 pos : SV_POSITION;
        float3 posWorld : TEXCOORD8;
        float2 uv : TEXCOORD0;
        UNITY_VERTEX_INPUT_INSTANCE_ID
        UNITY_VERTEX_OUTPUT_STEREO
    };
        float4 pos : SV_POSITION;

 システム上の座標として使用されることを指します。これは頂点シェーダーの関数内で 

o.pos = UnityObjectToClipPos(v.vertex);

と処理されているため頂点の位置をカメラから見た座標に当たります。

        float3 posWorld : TEXCOORD8;

 テクスチャ座標として使用されることを指します。これは頂点のワールド座標になります。

        float2 uv : TEXCOORD0;

 uvはテクスチャ座標として使用されることを指します。これは一つ目のテクスチャ画像になります。

これらの処理がHandTrianglesShaderの頂点シェーダーになります。

意外とシンプルですが、理解できたようなできないような…