【Unity】【シェーダ】shader_featureやmulti_compileでシェーダのバリアントを作る

shader_featureやmulti_compileでシェーダのバリアントを作る方法です。

Unity2018.2

用途

shader_featureやmulti_compileを使うと「キーワード」を定義できます。

// キーワードRED, GREENを定義
#pragma shader_feature RED GREEN

キーワードを定義すると、キーワード毎に分岐した処理を書けます。

fixed4 frag () : SV_Target
{
    // キーワード毎に処理を分岐
   #ifdef RED
        return fixed4(1, 0, 0, 1);
   #elif GREEN
        return fixed4(0, 1, 0, 1);
   #endif
}

こうすると分岐毎に内部的に違うシェーダファイル(バリアント)が作られるので、
他の分岐の不要な処理をすることが無くシェーダを最適化できます。

// 内部的には下記の処理が書かれた別々のシェーダが生成されるイメージ

// 1個目
fixed4 frag () : SV_Target
{
    return fixed4(1, 0, 0, 1);
}

// 2個目
fixed4 frag () : SV_Target
{

    return fixed4(0, 1, 0, 1);
}

また、shader_featureは実際に使われているバリアントだけを生成します。
これに対してmulti_compileは定義されているキーワードすべての分のバリアントを生成します。

よってバリアントの数はshader_featureのほうが抑えられます。
ただしビルドには含まれないので動的にキーワードを変えるときには注意が必要です。

shader_featureの使い方

shader_featureは次のように使います。
(CGPROGRAM内のみ抜粋)

CGPROGRAM
            
#pragma vertex vert
#pragma fragment frag

// キーワードを4つ定義
// _ (何も定義されていない状態), RED, GREEN, BLUE
#pragma shader_feature _ RED GREEN BLUE

#include "UnityCG.cginc"
            
float4 vert (float4 vertex : POSITION) : SV_POSITION
{
    return UnityObjectToClipPos(vertex);
}
            
fixed4 frag () : SV_Target
{
    // それぞれのキーワードが定義されているかどうかで処理を分ける
   #ifdef RED
        return fixed4(1, 0, 0, 1);
   #elif GREEN
        return fixed4(0, 1, 0, 1);
   #elif BLUE
        return fixed4(0, 0, 1, 1);
   #else
        return fixed4(1, 1, 1, 1);
   #endif
}

ENDCG

multi_compileの使い方

multi_compileは前節のshader_featureの部分を書き換えるだけです。

CGPROGRAM
            
#pragma vertex vert
#pragma fragment frag

// キーワードを4つ定義
// _ (何も定義されていない状態), RED, GREEN, BLUE
#pragma multi_compile _ RED GREEN BLUE

#include "UnityCG.cginc"
            
float4 vert (float4 vertex : POSITION) : SV_POSITION
{
    return UnityObjectToClipPos(vertex);
}
            
fixed4 frag () : SV_Target
{
    // それぞれのキーワードが定義されているかどうかで処理を分ける
   #ifdef RED
        return fixed4(1, 0, 0, 1);
   #elif GREEN
        return fixed4(0, 1, 0, 1);
   #elif BLUE
        return fixed4(0, 0, 1, 1);
   #else
        return fixed4(1, 1, 1, 1);
   #endif
}

ENDCG

キーワードの切り換え方①

キーワードはMaterial.EnableKeyword()とMaterial.DisableKeyword()によって書き換えられます。

material.DisableKeyword("BLUE");
material.EnableKeyword("RED");

キーワードの切り換え方②

複数マテリアルに共通のキーワード(グローバルキーワード)を書き換えるにはShaderクラスを使います。

Shader.EnableKeyword("RED");
Shader.DisableKeyword("GREEN");

キーワードの切り換え方➂

KeywordEnumアトリビュートを使うとシェーダのプロパティから適用するキーワードを選べます。

Shader "Example"
{
    Properties {
        [KeywordEnum(RED, GREEN, BLUE)] _Color ("Color", Float) = 0
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            
           #pragma vertex vert
           #pragma fragment frag

            // 「プロパティ名_KeywordEnumの名前」を大文字にしたものがキーワードとなる
           #pragma shader_feature _COLOR_RED _COLOR_GREEN _COLOR_BLUE

           #include "UnityCG.cginc"
            
            float4 vert (float4 vertex : POSITION) : SV_POSITION
            {
                return UnityObjectToClipPos(vertex);
            }
            
            fixed4 frag () : SV_Target
            {
               #ifdef _COLOR_RED
                    return fixed4(1, 0, 0, 1);
               #elif _COLOR_GREEN
                    return fixed4(0, 1, 0, 1);
               #elif _COLOR_BLUE
                    return fixed4(0, 0, 1, 1);
               #else
                    return fixed4(1, 1, 1, 1);
               #endif
            }

            ENDCG
        }
    }
}

インスペクタからドロップダウンリストで選べます。

f:id:halya_11:20181106214453p:plain

キーワードの切り換え方④

Toggleアトリビュートを使うとシェーダのプロパティからキーワードをON/OFFできます。

Shader "Example"
{
    Properties {
        [Toggle] _Red ("Is Red?", Float) = 0
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            
           #pragma vertex vert
           #pragma fragment frag

            // 「プロパティ名_ON」という名前のキーワードが定義される
           #pragma shader_feature _ _RED_ON

           #include "UnityCG.cginc"
            
            float4 vert (float4 vertex : POSITION) : SV_POSITION
            {
                return UnityObjectToClipPos(vertex);
            }
            
            fixed4 frag () : SV_Target
            {
               #ifdef _RED_ON
                    return fixed4(1, 0, 0, 1);
               #else
                    return fixed4(1, 1, 1, 1);
               #endif
            }

            ENDCG
        }
    }
}

キーワードの名前を明示的に指定することもできます。

Shader "Example"
{
    Properties {
        [Toggle(IS_RED)] _Red ("Is Red?", Float) = 0
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            
           #pragma vertex vert
           #pragma fragment frag

            // 指定した名前のキーワードが定義される
           #pragma shader_feature _ IS_RED

           #include "UnityCG.cginc"
            
            float4 vert (float4 vertex : POSITION) : SV_POSITION
            {
                return UnityObjectToClipPos(vertex);
            }
            
            fixed4 frag () : SV_Target
            {
               #ifdef IS_RED
                    return fixed4(1, 0, 0, 1);
               #else
                    return fixed4(1, 1, 1, 1);
               #endif
            }

            ENDCG
        }
    }
}

インスペクタにはトグルが表示されます。

f:id:halya_11:20181106215008p:plain

参考

docs.unity3d.com