夜風のMixedReality

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

ShaderのCGPROGRAM~ENDCGとCGINCLUDE~ENDCGの違い

本日はUnity Shader学習枠です。

Untiyのシェーダーでは基本的にShaderLab言語の中でCGPROGOAM~ENDCGと記述されている間にCG/HLSL言語を使用してコードを記述します。

今回は構文に関するTipsです。

〇CGPROGAM~ENDCGとCGINCLUDE~ENDCG

オーソドックスなシェーダーでは次のようにPassブロックの中でCGPROGRAM~ENDCGが定義され、この間で冒頭で述べた通りHLSL文で記述されています。

Shader "Unlit/NewUnlitShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {

            };

            struct v2f
            {

            };
            v2f vert (appdata v)
            {

            }

            fixed4 frag (v2f i) : SV_Target
            {

            }
            ENDCG
        }
    }
}

URPなどの最新の環境ではHLSLPROGRAM~ENDHLSLが使用されることが増えていますが基本的には同一の使われ方です。

これとは別にSubShader内にCGINCLUDE~ENDCGと呼ばれる記述がみられることがあります。

Shader "Test"
{
    Properties{
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        CGINCLUDE
        #pragma vertex vert
        #pragma fragment frag
            
        #include "UnityCG.cginc" 
  ・・・

        ENDCG
        Cull Off
        ZTest Always
        ZWrite Off
        Tags { "RenderType"="Opaque" }

        Pass
        {
            CGPROGRAM

            ENDCG
        }

    }
}

CGINCLUDEとCGPROGRAMは、両方Unityでシェーダーを記述するための記述子の一種で、シェーダーのコードの中に含まれる、異なる種類のコードブロックを定義するために使用されます。

例えば重ねての説明になりますがCGPROGRAMでは内部をCG/HLSL文のコードを定義するために使用しています。

ではCGINCLUDEについてですが、CGINCLUDEブロックは、一般的に複数のパスを持つシェーダーで使用され再利用可能なコードを定義するために使用されます。

CGINCLUDEブロック内に定義されたコードは、CGPROGRAMブロック内のシェーダーに直接挿入されます。

 HLSL文としてIncludeするのと同様にこの方法を使用することで、多数のシェーダーで同じコードを繰り返し記述する必要がなくなり、コードの再利用性が向上します。

例えば次のコードでは頂点シェーダーを2つのパスで共通化して使い、フラグメントシェーダーのみそれぞれのパス独特の処理を行うようにしています。

Shader "Test"
{
    Properties{
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        CGINCLUDE
        #pragma vertex vert
        #pragma fragment frag
            
        #include "UnityCG.cginc" 

        struct appdata
        {
            float4 vertex : POSITION;
            float2 uv : TEXCOORD0;
        };

        struct v2f
        {
            float2 uv : TEXCOORD0;
            float4 vertex : SV_POSITION;
        };

        v2f vert (appdata v)
        {
            v2f o;
            o.vertex = UnityObjectToClipPos(v.vertex);
            o.uv = TRANSFORM_TEX(v.uv, _MainTex);
            return o;
        }

        ENDCG
        Cull Off
        ZTest Always
        ZWrite Off
        Tags { "RenderType"="Opaque" }

        Pass
        {
            CGPROGRAM
           fixed4 frag (v2f i) : SV_Target
            {
     1パス目特有の処理
                return カラー1;
            }
            ENDCG
        }

        Pass
        {
            CGPROGRAM
            fixed4 frag (v2f i) : SV_Target
            {
     2パス目特有の処理
                return カラー2;
            }
            ENDCG
        }
    }
}

以前より気になっていた手法ですが、多くのシェーダーで共有化する機能を抽出して外部のHLSLファイルで定義し、Includeするのに対してこちらの方法は1つのシェーダーで複数のパスで共通化している機能をまとめる書き方となっており、目的は似ているものの考え方や使われ方は異なるようです。

筆者自身がマルチパスのシェーダーをあまり書いていなかったのでこれを機会に書いていきたいです。