【Unity / URP14】ビルドログから理解するシェーダーストリッピング【シェーダーバリアント】
概要
シェーダーがコンパイルされるとき、Editor.logへシェーダーコンパイルのログが出力されます。
このログを見ることで、何個のシェーダーバリアントが作成され、ストリッピング後にどれくらいの数にまで減ったかを把握することができます。
Compiling shader "Unlit/NewUnlitShader" pass "" (vp)
Full variant space: 16
After settings filtering: 16
After built-in stripping: 8
After scriptable stripping: 8
Processed in 0.03 seconds
starting compilation...
今回の記事では、シェーダーコンパイルのログを見ながら、
シェーダーストリッピングについての理解を深めることをゴールとします。
環境
- Windows 10
- Unity2022.3.23f1
- UniversalRP 14.0.10
- CoreRP 14.0.10
UnityのTarget PlatformはStandalone (Windows) にしておきます。
(プラットフォームによってバリアントの数が変動します)
Chapter 0. シェーダーバリアント (Shader Variant)
シェーダーソースはコンパイル時、キーワードごとのシェーダープログラムの変異体(バリアント)を作成します。
参考 : [Unity]ShaderVariantについて~前編:ShaderVariantとは?~
例えば、_A
, _B
というシェーダーキーワードを定義した場合を考えます。
#pragma multi_compile _ _A
#pragma multi_compile _ _B
half4 frag (v2f i) : SV_Target
{
#if _A
half4 value1 = HeavyFuncA1(i.uv);
#else
half4 value1 = HeavyFuncA2(i.uv);
#endif
#if _B
half4 value2 = HeavyFuncB1(i.uv);
#else
half4 value2 = HeavyFuncB2(i.uv);
#endif
return value1 + value2;
}
アプリをビルドする時、
キーワード_A
を ON / OFF にした場合と キーワード_B
を ON/OFFにした場合の
すべての組み合わせについてのシェーダーバリアントが作成されます。
そして、マテリアル側で有効化されたキーワードを元にして、
実行されるシェーダーバリアントが決定されます。
シェーダーバリアントの数
以下のケースではシェーダーバリアントの数は
#pragma multi_compile _ _A // 2通り
#pragma multi_compile _ _B // 2通り
以下のケースではシェーダーバリアントの数は
#pragma multi_compile _ _A1 _A2 // 3通り
#pragma multi_compile _ _B1 _B2 _B3 // 4通り
#pragma multi_compile _ _C1 // 2通り
Chapter 1. シェーダーコンパイルのログを確認する
アプリをビルドした時などにシェーダーコンパイルが実行されます。
シェーダーコンパイルのログはEditor.logへ出力されます。
OS | パス |
---|---|
Windows | C:\Users{ユーザー名}\AppData\Local\Unity\Editor\Editor.log |
Mac OS | ~/Library/Logs/Unity/Editor.log |
Editor.logの中を見ると、以下のようなシェーダーコンパイルのログが確認できます。
Compiling shader "Hidden/Universal Render Pipeline/UberPost" pass "UberPost" (vp)
Full variant space: 2
After settings filtering: 2
After built-in stripping: 2
After scriptable stripping: 1
Processed in 0.00 seconds
starting compilation...
finished in 0.00 seconds. Local cache hits 1 (0.00s CPU time), remote cache hits 0 (0.00s CPU time), compiled 0 variants (0.00s CPU time), skipped 0 variants
Prepared data for serialisation in 0.00s
Compiling shader "Hidden/Universal Render Pipeline/UberPost" pass "UberPost" (fp)
Full variant space: 46080
After settings filtering: 1920
After built-in stripping: 1920
After scriptable stripping: 720
Processed in 0.01 seconds
starting compilation...
finished in 0.01 seconds. Local cache hits 720 (0.22s CPU time), remote cache hits 0 (0.00s CPU time), compiled 0 variants (0.00s CPU time), skipped 0 variants
Prepared data for serialisation in 0.01s
上記のログから以下のことが読み取れます。
- vp (vertex) のバリアントは2つ作成され、ストリッピングによって1つにまで減った
- fp (fragment) のバリアントは46080個作成され、ストリッピングによって720にまで減った
vp は vertex program , fp は fragment program を表していると考えられます。
Chapter 2. シェーダーバリアントの数を確認してみる
シェーダーコンパイルのログから、どれくらいの数のシェーダーバリアントが生成されるかを確認してみましょう。
検証1. シェーダーキーワードが無い場合
シェーダーキーワードを定義しないシンプルなシェーダーを作成します。
使用したシェーダー (NewUnlitShader.shader)
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
{
float4 vertex : POSITION;
};
struct v2f
{
float4 vertex : SV_POSITION;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
half4 frag (v2f i) : SV_Target
{
return half4(1, 0, 0, 1);
}
ENDCG
}
}
}
ビルド結果
シェーダーコンパイルすると、以下のようなログが出力されます。
Compiling shader "Unlit/NewUnlitShader" pass "" (vp)
Full variant space: 1
After settings filtering: 1
After built-in stripping: 1
After scriptable stripping: 1
Processed in 0.00 seconds
starting compilation...
finished in 0.00 seconds. Local cache hits 1 (0.00s CPU time), remote cache hits 0 (0.00s CPU time), compiled 0 variants (0.00s CPU time), skipped 0 variants
Compiling shader "Unlit/NewUnlitShader" pass "" (fp)
Full variant space: 1
After settings filtering: 1
After built-in stripping: 1
After scriptable stripping: 1
Processed in 0.00 seconds
starting compilation...
finished in 0.00 seconds. Local cache hits 1 (0.00s CPU time), remote cache hits 0 (0.00s CPU time), compiled 0 variants (0.00s CPU time), skipped 0 variants
Full variant space
は生成されたシェーダーバリアントの総数を表しています。
Full variant space: 1
After ~
の部分は、ストリッピングが行われた後のシェーダーバリアント数を表しています。
1のまま変化していないので、シェーダーバリアントは除去されていません。
After settings filtering: 1
After built-in stripping: 1
After scriptable stripping: 1
検証2. シェーダーキーワードを定義してみる
以下の1行を定義し、シェーダーバリアントを増やしてみます。
#pragma multi_compile _ _A
使用したシェーダー (NewUnlitShader.shader)
Shader "Unlit/NewUnlitShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fragment _ _A
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 vertex : SV_POSITION;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
half4 frag (v2f i) : SV_Target
{
#if _A
return half4(1, 0, 0, 1);
#else
return half4(0, 1, 0, 1);
#endif
}
ENDCG
}
}
}
ビルド結果
シェーダーバリアントの数が2つになっていることが確認できます。
Compiling shader "Unlit/NewUnlitShader" pass "" (vp)
Full variant space: 2
After settings filtering: 2
After built-in stripping: 2
After scriptable stripping: 2
Processed in 0.00 seconds
starting compilation...
Compiling shader "Unlit/NewUnlitShader" pass "" (fp)
Full variant space: 2
After settings filtering: 2
After built-in stripping: 2
After scriptable stripping: 2
Processed in 0.00 seconds
starting compilation...
ここで注目したいのはvpのバリアントの数です。
vertex 関数に キーワード _A
による分岐がなかったとしても、
コンパイルのログでは vertex のバリアントの数が2になります。
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
half4 frag (v2f i) : SV_Target
{
#if _A
return half4(1, 0, 0, 1);
#else
return half4(0, 1, 0, 1);
#endif
}
検証3 : fragmentのみシェーダーバリアントを増やす
fragmentだけシェーダーバリアントを作りたいときは、multi_compile_fragment
を使用します。
#pragma multi_compile_fragment _ _A
参考 : Indicate which shader keywords affect which shader stage
ビルド結果
fragmentのみシェーダーバリアントが増えていることが確認できます。
Compiling shader "Unlit/NewUnlitShader" pass "" (vp)
Full variant space: 1
After settings filtering: 1
After built-in stripping: 1
After scriptable stripping: 1
Processed in 0.00 seconds
starting compilation...
finished in 0.00 seconds. Local cache hits 1 (0.00s CPU time), remote cache hits 0 (0.00s CPU time), compiled 0 variants (0.00s CPU time), skipped 0 variants
Compiling shader "Unlit/NewUnlitShader" pass "" (fp)
Full variant space: 2
After settings filtering: 2
After built-in stripping: 2
After scriptable stripping: 2
Processed in 0.00 seconds
starting compilation...
finished in 0.01 seconds. Local cache hits 1 (0.00s CPU time), remote cache hits 0 (0.00s CPU time), compiled 1 variants (0.01s CPU time), skipped 0 variants
検証4. 複数のシェーダーキーワードを定義してみる
次に、以下のようなシェーダーキーワードを定義してみます。
シェーダーバリアントの数は
#pragma multi_compile _ _A1 // 2
#pragma multi_compile _ _B1 _B2 // 3
#pragma multi_compile _ _C1 _C2 _C3 // 4
#pragma multi_compile _ _D1 _D2 _D3 _D4 // 5
使用したシェーダー (NewUnlitShader.shader)
Shader "Unlit/NewUnlitShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile _ _A1
#pragma multi_compile _ _B1 _B2
#pragma multi_compile _ _C1 _C2 _C3
#pragma multi_compile _ _D1 _D2 _D3 _D4
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
return col;
}
ENDCG
}
}
}
ビルド結果
シェーダーをコンパイルしてみると、シェーダーバリアントの数が120になっていることが確認できした。
Compiling shader "Unlit/NewUnlitShader" pass "" (vp)
Full variant space: 120
After settings filtering: 120
After built-in stripping: 120
After scriptable stripping: 120
Processed in 0.00 seconds
starting compilation...
Compiling shader "Unlit/NewUnlitShader" pass "" (fp)
Full variant space: 120
After settings filtering: 120
After built-in stripping: 120
After scriptable stripping: 120
Processed in 0.00 seconds
starting compilation...
まとめ (Chapter 2)
- ビルドログ(Editor.log)を見ることで、生成されたシェーダーバリアントの数が確認できる
-
multi_compile
を使うと vertex と fragment の両方についてバリアントが生成される -
multi_compile_fragment
を使用すると、 fragment のみバリアントが生成される
Chapter 3. shader_feature
ゲーム内で、シェーダーキーワードが動的に切り替わらない場合、
使っていないシェーダーバリアントはビルドに含める必要がありません。
含めないほうがシェーダーのデータサイズを削減できます。
使わないシェーダーバリアントをビルドから除外するときに使用するのがshader_featureです。
shader_feature と multi_compile の違い
shader_featureとmulti_compileの違いは以下の通りです。
-
#pragma multi_compile
を使った場合、すべてのシェーダーキーワードの組み合わせについてのシェーダーバリアントがビルドに含まれます。 -
#pragma shader_feature
を使った場合、使われないバリアントはビルドから除外されます。- アプリに入るマテリアルで使用されるシェーダーバリアントはビルドに入ります。
#pragma shader_feature は、#pragma multi_compile と非常によく似ています。
唯一の違いは、shader_feature のシェーダーの未使用のバリアントがゲームのビルドに含まれないことです。
https://docs.unity3d.com/ja/2018.4/Manual/SL-MultipleProgramVariants.html
検証1. shader_featureを定義したときのバリアントを確認
以下のシェーダーキーワードをシェーダー内で定義してみましょう。
#pragma shader_feature _A
#pragma shader_feature _B
使用したシェーダー
Shader "Unlit/NewUnlitShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma shader_feature _A
#pragma shader_feature _B
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 vertex : SV_POSITION;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
half4 frag (v2f i) : SV_Target
{
#if _A
return half4(1, 0, 0, 1);
#else
return half4(0, 1, 0, 1);
#endif
}
ENDCG
}
}
}
ビルド結果
このShaderをResourcesフォルダに含めた状態でビルドを行うと、
シェーダーバリアントが1つだけビルドに含まれます。
Compiling shader "Unlit/NewUnlitShader" pass "" (vp)
Full variant space: 1
After settings filtering: 1
After built-in stripping: 1
After scriptable stripping: 1
Processed in 0.00 seconds
starting compilation...
finished in 0.00 seconds. Local cache hits 1 (0.00s CPU time), remote cache hits 0 (0.00s CPU time), compiled 0 variants (0.00s CPU time), skipped 0 variants
Prepared data for serialisation in 0.00s
Compiling shader "Unlit/NewUnlitShader" pass "" (fp)
Full variant space: 1
After settings filtering: 1
After built-in stripping: 1
After scriptable stripping: 1
Processed in 0.00 seconds
starting compilation...
finished in 0.00 seconds. Local cache hits 1 (0.00s CPU time), remote cache hits 0 (0.00s CPU time), compiled 0 variants (0.00s CPU time), skipped 0 variants
Prepared data for serialisation in 0.00s
検証2. ShaderVariantCollectionを使用した場合のバリアント数
ShaderVariantCollection
シェーダーキーワードをShaderVariantCollectionに登録することで、そのバリアントはビルドに含まれるようになります。
参考 : [Unity]ShaderVariantについて~中編:AssetBundleとShaderVariantの関係~
キーワードセットを4つ登録
すべてキーワードの組み合わせを登録した場合のバリアント数を確認してみます。
ビルド結果
これをビルドしてみると、バリアントの数は4となります。
コンパイルログ
Compiling shader "Unlit/NewUnlitShader" pass "" (vp)
Full variant space: 4
After settings filtering: 4
After built-in stripping: 4
After scriptable stripping: 4
Processed in 0.00 seconds
starting compilation...
finished in 0.00 seconds. Local cache hits 4 (0.00s CPU time), remote cache hits 0 (0.00s CPU time), compiled 0 variants (0.00s CPU time), skipped 0 variants
Prepared data for serialisation in 0.00s
Compiling shader "Unlit/NewUnlitShader" pass "" (fp)
Full variant space: 4
After settings filtering: 4
After built-in stripping: 4
After scriptable stripping: 4
Processed in 0.00 seconds
starting compilation...
finished in 0.00 seconds. Local cache hits 4 (0.00s CPU time), remote cache hits 0 (0.00s CPU time), compiled 0 variants (0.00s CPU time), skipped 0 variants
Prepared data for serialisation in 0.00s
キーワードセットを3つ登録
すべてキーワードの組み合わせを1つ除外し、3つにしてみます。
ビルド結果
バリアントの数は3になります。
コンパイルログ
Compiling shader "Unlit/NewUnlitShader" pass "" (vp)
Full variant space: 3
After settings filtering: 3
After built-in stripping: 3
After scriptable stripping: 3
Processed in 0.00 seconds
starting compilation...
finished in 0.00 seconds. Local cache hits 3 (0.00s CPU time), remote cache hits 0 (0.00s CPU time), compiled 0 variants (0.00s CPU time), skipped 0 variants
Prepared data for serialisation in 0.00s
Compiling shader "Unlit/NewUnlitShader" pass "" (fp)
Full variant space: 3
After settings filtering: 3
After built-in stripping: 3
After scriptable stripping: 3
Processed in 0.00 seconds
starting compilation...
finished in 0.00 seconds. Local cache hits 3 (0.00s CPU time), remote cache hits 0 (0.00s CPU time), compiled 0 variants (0.00s CPU time), skipped 0 variants
Prepared data for serialisation in 0.00s
キーワードセットを2つ登録 (no keyword を除外)
次に、no keywords を除外してみます。
ビルド結果
バリアントの数は3のままです、
キーワードが存在しないバリアントについては、ShaderVariantCollectionに登録しなくてもビルドに含まれるようです。
コンパイルログ
Compiling shader "Unlit/NewUnlitShader" pass "" (vp)
Full variant space: 3
After settings filtering: 3
After built-in stripping: 3
After scriptable stripping: 3
Processed in 0.00 seconds
starting compilation...
finished in 0.00 seconds. Local cache hits 3 (0.00s CPU time), remote cache hits 0 (0.00s CPU time), compiled 0 variants (0.00s CPU time), skipped 0 variants
Prepared data for serialisation in 0.00s
Compiling shader "Unlit/NewUnlitShader" pass "" (fp)
Full variant space: 3
After settings filtering: 3
After built-in stripping: 3
After scriptable stripping: 3
Processed in 0.00 seconds
starting compilation...
finished in 0.00 seconds. Local cache hits 3 (0.00s CPU time), remote cache hits 0 (0.00s CPU time), compiled 0 variants (0.00s CPU time), skipped 0 variants
Prepared data for serialisation in 0.00s
まとめ(Chapter 3)
- shader_featureを使用した場合、使われていないキーワードのシェーダーバリアントはビルドから除外される
- ShaderVariantCollectionにキーワードを登録することで、そのキーワードに対応したバリアントがビルドに含まれる
- キーワード無しのバリアントは自動的にビルドに含まれる
Chapter 4. multi_compile と shader_feature が混在する場合
次に、multi_compile
と shader_feature
が混在しているケースでのバリアントについて見てみようと思います。
ドキュメントを読む限り、
-
multi_compile
については、使用されていなかったとしても含まれる -
shader_feature
については、使われていない場合は除去される
と考えて良さそうですが、果たして本当にそうなのか気になります。
ビルドしてみてバリアントの数を確認してみたいと思います。
検証
以下のシェーダーキーワードを定義した場合、
バリアント数はShaderVariantCollectionに登録されたキーワードセットの数と一致します。
#pragma shader_feature _A
#pragma shader_feature _B
ここへ #pragma multi_compile _ _C
を追加し、その時のシェーダーバリアントの数がどうなるかを見てみます。
#pragma shader_feature _A
#pragma shader_feature _B
#pragma multi_compile _ _C // 追加
multi_compileを追加したことによるバリアント数の違い
ShaderVariantCollection
に登録したキーワードセットと、バリアント数をまとめてみました。
#pragma multi_compile _ _C
を追加したことで、バリアントの数が2倍になっています。
バリアント数(vp) ( multi_compile _ _C なし) |
バリアント数(vp) ( multi_compile _ _C あり) |
ShaderVariantCollection |
---|---|---|
4 | 8 | |
3 | 6 | |
2 | 4 | |
1 | 2 |
キーワードを変えたときのバリアント数の変化
さらに、ShaderVariantCollectionに登録するキーワードセットを変えたときのバリアント数も調べてみました。
multi_compile
で指定されているシェーダーキーワード_C
は、ShaderVariantCollectionに登録されていても無視されるようです
バリアント数(vp) ( multi_compile _ _C あり) |
_Aと_Bのみを登録 | _Aと_Bと_Cを登録 |
---|---|---|
8 | ||
6 | ||
4 | ||
2 |
まとめ (Chapter 4)
ShaderVariantCollectionに登録されるキーワードセットと、生成されるシェーダーバリアントは以下のような形になりそうです。
登録されているキーワード | 生成されるシェーダーバリアント |
---|---|
<no keyword> |
<no keyword> と _C
|
_A |
_A と _A _C
|
_B |
_B と _B _C
|
_A _B |
_A _B と _A _B _C
|
Chapter 5. ストリッピング(Built-in Stripping)を確認する
Unityには、シェーダーキーワードを定義するショートカットが用意されています。
参考 : Use shortcuts to create keyword sets
たとえば、multi_compile_fog
を使うとFog用のキーワード FOG_LINEAR
, FOG_EXP
, FOG_EXP2
が定義されます。
#pragma multi_compile_fog
サンプルシェーダー(NewUnlitShader.shader)
Shader "Unlit/NewUnlitShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
}
ビルド結果
multi_compile_fog
を使用したシェーダーをコンパイルしたときのログは以下のようになりました。
Compiling shader "Unlit/NewUnlitShader" pass "" (vp)
Full variant space: 4
After settings filtering: 4
After built-in stripping: 1
After scriptable stripping: 1
Processed in 0.00 seconds
starting compilation...
finished in 0.00 seconds. Local cache hits 1 (0.00s CPU time), remote cache hits 0 (0.00s CPU time), compiled 0 variants (0.00s CPU time), skipped 0 variants
Compiling shader "Unlit/NewUnlitShader" pass "" (fp)
Full variant space: 4
After settings filtering: 4
After built-in stripping: 1
After scriptable stripping: 1
Processed in 0.00 seconds
starting compilation...
finished in 0.00 seconds. Local cache hits 1 (0.00s CPU time), remote cache hits 0 (0.00s CPU time), compiled 0 variants (0.00s CPU time), skipped 0 variants
vertexとfragment両方についてFogのシェーダーバリアントが4個生成されていますが、
built-in stripping
によって1個にまで減っています。
使わないシェーダーバリアントはストリッピングされることが分かります。
フォグは使われていない場合は除外される (公式ドキュメント)
Unity公式ドキュメントには、使用されていないバリアントは除外される旨の記載があります。
いずれのシーンでも使用されていないフォグとライトマップモードを処理するシェーダーバリアントは、ゲームデータに含まれません。この動作をオーバーライドしたい場合は、Graphics ウィンドウを参照してください。
https://docs.unity3d.com/ja/2021.1/Manual/shader-compilation.html
Fogを有効化する
シーンの設定から、Fogを有効化してみましょう。
ビルド結果
コンパイル結果は以下のようになりました。
シェーダーバリアントの数はがストリッピングによって2にまで減っていることが確認できます。
Compiling shader "Unlit/NewUnlitShader" pass "" (vp)
Full variant space: 4
After settings filtering: 4
After built-in stripping: 2
After scriptable stripping: 2
Processed in 0.00 seconds
starting compilation...
finished in 0.03 seconds. Local cache hits 1 (0.00s CPU time), remote cache hits 0 (0.00s CPU time), compiled 1 variants (0.03s CPU time), skipped 0 variants
Prepared data for serialisation in 0.00s
Compiling shader "Unlit/NewUnlitShader" pass "" (fp)
Full variant space: 4
After settings filtering: 4
After built-in stripping: 2
After scriptable stripping: 2
Processed in 0.00 seconds
starting compilation...
finished in 0.01 seconds. Local cache hits 1 (0.00s CPU time), remote cache hits 0 (0.00s CPU time), compiled 1 variants (0.01s CPU time), skipped 0 variants
Prepared data for serialisation in 0.00s
Serialized binary data for shader Unlit/NewUnlitShader in 0.00s
d3d11 (total internal programs: 4, unique: 4)
Chapter 6. IPreprocessShadersによるストリッピング
IPreprocessShaders
を使用することで、ビルド時に不要なシェーダーバリアントを自分で除去(フィルタリング)することができます。
たとえば、デバッグ用のシェーダーバリアントを開発時には使用し、リリース用のビルドには含めない、といったことが実現できます。
参考 : スクリプタブルシェーダーバリアントの除去
デバッグキーワードを含むシェーダー
先ほどのシェーダーに、新しく DEBUG というシェーダーキーワードを定義してみます。
#pragma multi_compile_fog
#pragma multi_compile _ DEBUG
シェーダーサンプル (NewUnlitShader.shader)
Shader "Unlit/NewUnlitShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fog
#pragma multi_compile _ DEBUG
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
#if DEBUG
return col; // フォグを適用する前のカラーを出力 (デバッグ)
#endif
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
}
ビルド結果
DEBUG定義前と比べると、シェーダーバリアントの数が2倍になっていることが分かります。
Compiling shader "Unlit/NewUnlitShader" pass "" (vp)
Full variant space: 8
After settings filtering: 8
After built-in stripping: 4
After scriptable stripping: 4
Processed in 0.00 seconds
starting compilation...
finished in 0.04 seconds. Local cache hits 1 (0.00s CPU time), remote cache hits 0 (0.00s CPU time), compiled 3 variants (0.12s CPU time), skipped 0 variants
Prepared data for serialisation in 0.00s
Compiling shader "Unlit/NewUnlitShader" pass "" (fp)
Full variant space: 8
After settings filtering: 8
After built-in stripping: 4
After scriptable stripping: 4
Processed in 0.00 seconds
starting compilation...
finished in 0.01 seconds. Local cache hits 1 (0.00s CPU time), remote cache hits 0 (0.00s CPU time), compiled 3 variants (0.03s CPU time), skipped 0 variants
Prepared data for serialisation in 0.00s
シェーダーバリアントの除去
IPreprocessShaders
を使用して、シェーダーバリアントを除去してみます。
using System.Collections.Generic;
using UnityEditor.Build;
using UnityEditor.Rendering;
using UnityEngine;
using UnityEngine.Rendering;
// デバッグビルド設定の除去の簡単な例
class ShaderDebugBuildProcessor : IPreprocessShaders
{
ShaderKeyword m_KeywordDebug;
public ShaderDebugBuildProcessor()
{
m_KeywordDebug = new ShaderKeyword("DEBUG");
}
// 複数のコールバックを実装可能です。
// 最初に実行されるのは、callbackOrder が最も小さい数を戻すものです。
public int callbackOrder { get { return 0; } }
public void OnProcessShader(
Shader shader, ShaderSnippetData snippet, IList<ShaderCompilerData> shaderCompilerData)
{
for (int i = 0; i < shaderCompilerData.Count; ++i)
{
// DEBUGキーワードが有効だったら
if (shaderCompilerData[i].shaderKeywordSet.IsEnabled(m_KeywordDebug))
{
// シェーダーバリアントを除去
shaderCompilerData.RemoveAt(i);
--i;
}
}
}
}
ビルド結果
アプリケーションをビルドすると、以下のようなログが出力されます.
Compiling shader "Unlit/NewUnlitShader" pass "" (vp)
Full variant space: 8
After settings filtering: 8
After built-in stripping: 4
After scriptable stripping: 2
Processed in 0.00 seconds
starting compilation...
finished in 0.00 seconds. Local cache hits 2 (0.00s CPU time), remote cache hits 0 (0.00s CPU time), compiled 0 variants (0.00s CPU time), skipped 0 variants
Prepared data for serialisation in 0.00s
Compiling shader "Unlit/NewUnlitShader" pass "" (fp)
Full variant space: 8
After settings filtering: 8
After built-in stripping: 4
After scriptable stripping: 2
Processed in 0.00 seconds
starting compilation...
finished in 0.00 seconds. Local cache hits 2 (0.00s CPU time), remote cache hits 0 (0.00s CPU time), compiled 0 variants (0.00s CPU time), skipped 0 variants
Prepared data for serialisation in 0.00s
After built-in stripping
ではバリアントの数が4ですが、
After scriptable stripping
によってシェーダーバリアントの数が2にまで減ることができます。
まとめ (Chapter 6)
-
IPreprocessShaders
を利用することで、シェーバーバリアントを自分で除去できる - 自作したストリッピングは、ビルドログの
After scriptable stripping
の数値に現れる
Chapter 7. ストリッピングをカスタマイズできるIShaderVariantStripper
CoreRP14から、IShaderVariantStripperというインターフェースが生えました。
これを使用することで、URPのストリッピング処理(scriptable stripping)をカスタマイズすることができます。
ShadowCasterパスのストリッピングを確認してみる
URPの設定で、Main Light
が無効化されている場合、シェーダーのShadowCaster
パスは、ストリッピングされます。
_MAIN_LIGHT_SHADOWS
などのシャドウマッピング関連のバリアントも除外されます。
参考 : Shader Stripping | Universal RP | 14.0.11
Main Light無効
URPシェーダーのShaderCasterパスの実装例
Pass
{
Name "ShadowCaster"
Tags
{
"LightMode" = "ShadowCaster"
}
// -------------------------------------
// Render State Commands
ZWrite On
ZTest LEqual
ColorMask 0
Cull[_Cull]
HLSLPROGRAM
#pragma target 2.0
// -------------------------------------
// Shader Stages
#pragma vertex ShadowPassVertex
#pragma fragment ShadowPassFragment
#include "Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl"
#include "Packages/com.unity.render-pipelines.universal/Shaders/ShadowCasterPass.hlsl"
ENDHLSL
}
ビルド結果
アプリをビルドしたときに、バリアントの数がゼロになることが確認できます。
Compiling shader "Unlit/NewUnlitShader" pass "ShadowCaster" (vp)
Full variant space: 1
After settings filtering: 1
After built-in stripping: 1
After scriptable stripping: 0
Processed in 0.00 seconds
IShaderVariantStripperについて
CoreRPパッケージのShaderPreprocessor
クラスのCanRemoveVariant
メソッドで、バリアントをストリッピングするかどうかを判定しています。
bool CanRemoveVariant([DisallowNull] TShader shader, TShaderVariant shaderVariant, ShaderCompilerData shaderCompilerData)
{
return strippers
.Where(s => s is not IVariantStripperSkipper<TShader, TShaderVariant> skipper || !skipper.SkipShader(shader, shaderVariant))
.All(s => s.CanRemoveVariant(shader, shaderVariant, shaderCompilerData));
}
IShaderVariantStripperSkipper
を利用したクラスを定義することで、ストリッピングのふるまいを拡張できます。
ShadowCasterパスのストリッピングの実装箇所 (ShaderScriptableStripper.cs)
影が無効だった場合にShadowCasterPassを無効にするという判定は、ShaderScriptableStripper
クラスで実装されています。
このクラスは IShaderVariantStripper
を利用しています。
internal class ShaderScriptableStripper : IShaderVariantStripper, IShaderVariantStripperScope
ShadowCasterPassのストリッピング処理は以下になります。
MainLightShadows と AdditionalLightShadows 両方が無効だと ストリッピングが行われます。
internal bool StripUnusedPass_ShadowCaster(ref IShaderScriptableStrippingData strippingData)
{
if (strippingData.passType == PassType.ShadowCaster)
{
if ( !strippingData.IsShaderFeatureEnabled(ShaderFeatures.MainLightShadows)
&& !strippingData.IsShaderFeatureEnabled(ShaderFeatures.AdditionalLightShadows))
return true;
}
return false;
}
実装例 : ShadowCasterパスのストリッピングを無効にする
IShaderVariantStripper
の実装例を以下に示します。
URPのScripting Strippingが実行されるとき、ShadowCaster
パスのストリッピングがスキップされるようになります。
このクラスはEditorフォルダ内に格納しておきます。
using System.Linq;
using UnityEditor.Rendering;
using UnityEngine;
using UnityEngine.Rendering;
/// <summary>
/// カスタムのShaderVariantStripper
/// </summary>
public class CustomShaderVariantStripper : IShaderVariantStripper
{
public bool active => true; // trueを返すと、このStripperが実行される
/// <summary>
/// falseを返すと、バリアントが除去されなくなる
/// </summary>
public bool CanRemoveVariant(Shader shader, ShaderSnippetData shaderVariant, ShaderCompilerData shaderCompilerData)
{
if (shaderVariant.passType == PassType.ShadowCaster)
{
Debug.Log("スキップします");
return false;
}
return true;
}
}
ビルド結果
ShadowCasterパスがストリッピングされなくなります。
Compiling shader "Unlit/NewUnlitShader" pass "ShadowCaster" (vp)
Full variant space: 1
After settings filtering: 1
After built-in stripping: 1
After scriptable stripping: 1
Processed in 0.00 seconds
starting compilation...
finished in 0.00 seconds. Local cache hits 1 (0.00s CPU time), remote cache hits 0 (0.00s CPU time), compiled 0 variants (0.00s CPU time), skipped 0 variants
Prepared data for serialisation in 0.00s
Compiling shader "Unlit/NewUnlitShader" pass "ShadowCaster" (fp)
Full variant space: 1
After settings filtering: 1
After built-in stripping: 1
After scriptable stripping: 1
Processed in 0.00 seconds
starting compilation...
finished in 0.00 seconds. Local cache hits 1 (0.00s CPU time), remote cache hits 0 (0.00s CPU time), compiled 0 variants (0.00s CPU time), skipped 0 variants
Prepared data for serialisation in 0.00s
Chapter 8. RenderPipelineタグの指定が間違っているとストリッピングされる
Unityプロジェクトに登録されているのRenderPipelineAssetが持つタグと、
ShaderのRenderPipelineタグの指定が異なる場合、シェーダーはストリッピングされます。
Tags
{
"RenderPipeline"="UniversalRenderPipeline"
"RenderType"="Opaque"
}
Compiling shader "Unlit/NewUnlitShader" pass "" (vp)
Full variant space: 4
After settings filtering: 4
After built-in stripping: 4
After scriptable stripping: 0
Processed in 0.00 seconds
Prepared data for serialisation in 0.00s
使用したシェーダー
Shader "Unlit/NewUnlitShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags
{
"RenderPipeline"="UniversalRenderPipeline"
"RenderType"="Opaque"
}
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile _ _A
#pragma multi_compile _ _B
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 vertex : SV_POSITION;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
half4 frag (v2f i) : SV_Target
{
#if _A
return half4(1, 0, 0, 1);
#else
return half4(0, 1, 0, 1);
#endif
}
ENDCG
}
}
}
ストリッピング処理の実装箇所
このストリッピング処理は、CoreRPパッケージ内のShaderPreprocessor.csにて実装されています。
RenderPipelineタグの記述が間違っている場合にストリッピングされることになります。
string renderPipelineTag = string.Empty;
if (typeof(TShader) == typeof(Shader) && typeof(TShaderVariant) == typeof(ShaderSnippetData))
{
if (TryGetShaderVariantRenderPipelineTag(shader, shaderVariant, out renderPipelineTag))
{
if (!renderPipelineTag.Equals(globalRenderPipeline, StringComparison.OrdinalIgnoreCase))
afterStrippingShaderVariantCount = 0;
}
}
ストッピングの詳細を確認する (CoreRPのカスタマイズ)
CoreRPパッケージをカスタムパッケージ化し、ストリッピングが行われたときに警告ログを出すようにしてみます。
string renderPipelineTag = string.Empty;
if (typeof(TShader) == typeof(Shader) && typeof(TShaderVariant) == typeof(ShaderSnippetData))
{
if (TryGetShaderVariantRenderPipelineTag(shader, shaderVariant, out renderPipelineTag))
{
if (!renderPipelineTag.Equals(globalRenderPipeline, StringComparison.OrdinalIgnoreCase))
{
afterStrippingShaderVariantCount = 0;
Debug.LogWarning($"RenderPipelineタグが一致しないため、ストリッピングされました。shader={shader} renderPipelineTag={renderPipelineTag} globalRenderPipeline={globalRenderPipeline}");
}
}
}
ビルド結果
ビルドすると、ストリッピングが行われていることが確認できます。
RenderPipelineタグが一致しないため、ストリッピングされました。shader=Unlit/NewUnlitShader (UnityEngine.Shader) renderPipelineTag=UniversalRenderPipeline globalRenderPipeline=UniversalPipeline
URPを使用している場合、シェーダーのRenderPipelineタグは "UniversalPipeline" にしておく必要があるようです。
まとめ
ビルドログ について
- ビルドログ(Editor.log)を見ることで、生成されたシェーダーバリアントの数が確認できる
-
multi_compile
を使うと vertex と fragment の両方についてバリアントが生成される -
multi_compile_fragment
を使用すると、 fragment のみバリアントが生成される
ShaderVariantCollection について
-
#pragma shader_feature
を使った場合、使用されないキーワードのバリアントは除外される -
#pragma multi_compile
で指定されているシェーダーキーワードは、ShaderVariantCollectioに登録されていても無視される - ShaderVariantCollectionに記録したキーワードのバリアントはビルドに含まれる
- キーワードが無いバリアントについては、ShaderVariantCollectionへ登録してなくてもビルドに含まれる
Scriptable Stripping (スクリプタブルストリッピング) について
-
IPreprocessShaders
を利用することで、ストリッピング(バリアントの除外処理)を自作できる- 使用例 : デバッグ用のシェーダーバリアントはストリッピングする
-
IShaderVariantStripper
を利用すると、URPのシェーダーストリッピングをカスタマイズできる- 使用例 : ShadowCasterパスはストリッピングされないようにする
- 実装したストリッピングは、ビルドログの
After scriptable stripping
の数値に現れる
リンク
[Unity] ShaderVariantについて~前編:ShaderVariantとは?~
https://note.com/wotakuro/n/n48d40ec0f006
複数のシェーダープログラムのバリアントを作る
https://docs.unity3d.com/ja/2018.4/Manual/SL-MultipleProgramVariants.html
Indicate which shader keywords affect which shader stage
https://docs.unity3d.com/2022.3/Documentation/Manual/shader-variant-stripping.html
スクリプタブルシェーダーバリアントの除去
https://blog.unity.com/ja/engine-platform/stripping-scriptable-shader-variants
IShaderVariantStripper
https://docs.unity3d.com/Packages/com.unity.render-pipelines.core@14.0/api/UnityEditor.Rendering.IShaderVariantStripper.html
IShaderVariantStripperSkipper
https://docs.unity3d.com/Packages/com.unity.render-pipelines.core@14.0/api/UnityEditor.Rendering.IShaderVariantStripperSkipper.html
Unity 2021 LTS におけるシェーダーのビルド時間とメモリ使用量の改善
https://blog.unity.com/ja/engine-platform/2021-lts-improvements-to-shader-build-times-and-memory-usage
Discussion