皆さんこんにちは、SGE技術本部(コアテク)所属のグラフィックスエンジニア チャンユービンです。
今回はUnity 6.3で追加された新機能「ShaderBuildSettings」を紹介します。この機能を使えば、Shaderのソースコードを変更せず、Shaderバリアントストリップ用のスクリプトを書かなくても、Shaderバリアントを最適化できます。
概要
執筆時のUnityバージョン: Unity 6000.3.2f1
ShaderBuildSettingsでは、以下の操作ができます:
- キーワードタイプの変換:
multi_compile、shader_feature、dynamic_branch間で相互変換 - キーワードの除外: 不要なキーワードバリアントを選択的に削除
これらの操作により、Shaderバリアント数を効率的に最適化し、ビルド時間とパッケージサイズを削減できます。

設定場所
| 設定範囲 | パス | 優先度 |
|---|---|---|
| プロジェクト全体 | Project Settings → Graphics → Shader Build Settings | 低 |
| BuildProfile | Add Settings → Graphics Settings → Shader Build Settings | 高 |
前提知識:キーワードタイプ
ShaderBuildSettingsを使う前に、3種類のキーワードタイプの違いを理解しておきましょう。
multi_compile と shader_feature
| 特性 | multi_compile | shader_feature |
|---|---|---|
| バリアント生成 | すべてのキーワード組み合わせをコンパイル | マテリアルで使用されているバリアントのみコンパイル |
| 未使用バリアント | ビルドに含まれる | ビルドから除外される |
| 適用シーン | コードでグローバル制御するキーワード | マテリアル設定で使うキーワード |
dynamic_branch
dynamic_branchはUnity 2022.1で導入されたキーワードタイプで、上記2つとは本質的に異なります:
| 特性 | multi_compile / shader_feature | dynamic_branch |
|---|---|---|
| コンパイル結果 | 複数のShaderバリアントを生成 | 単一のShaderのみ生成 |
| 分岐方式 | コンパイル時の静的分岐 | 実行時の動的分岐(GPU実行) |
| キーワード実装 | プリプロセッサマクロ | Uniformブール変数 |
宣言方法は他のキーワードタイプと同じです:
#pragma dynamic_branch _ _DY_KEYWORD1 _DY_KEYWORD2 #pragma dynamic_branch_local _ _DY_KEYWORD1 _DY_KEYWORD2 // local版
注意:
dynamic_branchには_vertexや_fragmentなどステージ特定版は存在しません。
キーワードの条件分岐方法
プリプロセッサディレクティブ(#if / #ifdef)
#ifdef _KEYWORD // コンパイル時にこのコードを含めるか決定 #endif
通常のif文
if (_KEYWORD) { // 実行時にこのコードを実行するか判断 }
3種類のキーワードタイプの分岐方法サポート状況:
| キーワードタイプ | #if / #ifdef | if文 |
|---|---|---|
| multi_compile | ✓ | ✓ |
| shader_feature | ✓ | ✓ |
| dynamic_branch | ✗ | ✓ |
注意:
multi_compileとshader_featureで_vertexや_fragmentなどステージ特定版を使う場合、通常のif文は使えません。推奨: Unity公式はif文の使用を推奨しています。キーワードタイプ間の切り替えが容易になるためです。
使い方
設定手順
- 設定画面を開く(
Project SettingsまたはBuildProfile)Project Settingsの場合: Edit → Project Settings → Graphics → Shader Build SettingsBuildProfileの場合: File → Build Profile → 任意のBuildProfileを選択 → Add Settings → Graphics Settings → Shader Build Settings
- + ボタンで項目を追加
- Keyword set にキーワードセットを入力
- Shaderで定義されたキーワードと完全一致させる(
_プレースホルダーも含む) - 例:
_ _KEYWORD1 _KEYWORD2 - キーワードの記述順序は異なっていてもOK
- Shaderで定義されたキーワードと完全一致させる(
- 左の矢印をクリックして展開し、保持するキーワードを選択
- Type override で目標のキーワードタイプを選択

Type Overrideオプション
| オプション | 説明 |
|---|---|
| Default | 元のキーワードタイプを維持 |
| shader_feature | shader_featureタイプに変換 |
| multi_compile | multi_compileタイプに変換 |
| dynamic_branch | dynamic_branchタイプに変換(制限あり) |
反映タイミング
- 設定をApplyすると、Unity保存時に反映
- 関連するすべてのShaderが再インポート・コンパイルされる
- Shaderソースコードは変更されない、コンパイル結果のみ影響
- 選択されなかったキーワードは強制的に除外される(dynamic_branch以外)
- Asset Importフェーズで反映(Buildフェーズではない)ので、Editor上で結果を確認できる
注意事項と制限
設定粒度の制限
この機能はBuildProfile単位でしか設定できず、特定のマテリアルだけに適用することはできません。
グローバル影響
設定したキーワードは、そのキーワードを使用するすべてのShaderに影響します。特にURPなどレンダリングパイプライン内蔵のキーワードを変更する場合、予期しないShaderに影響する可能性があるので注意が必要です。
Defaultオプションの不具合(Unity 6000.3.2f1の時点)
Type overrideでDefaultを選択した場合、キーワード除外機能に以下の問題があります:
| Shader内の元タイプ | キーワード除外 |
|---|---|
| multi_compile | ✓ 正常動作 |
| shader_feature | ✗ 機能しない |
回避策: この不具合は
Defaultを選択した時のみ発生します。明示的にType overrideを選択すれば(元と同じshader_feature→shader_featureでも)、キーワード除外機能は正常に動作します。
dynamic_branchに変換できる条件
dynamic_branchはプリプロセッサディレクティブをサポートしないため、以下の条件を満たす場合のみ安全に変換できます:
- if文でキーワードを判断している(
#if/#ifdefではなく)- 注:
_vertexや_fragmentなどステージ特定版を使用している場合はif文が使えないため、変換不可
- 注:
- キーワードが変数宣言に影響しない
変換できない例
以下のコードはキーワードが変数やテクスチャの宣言に影響しているため、dynamic_branchに変換できません:
#pragma multi_compile _ _CUSTOM_KEYWORD struct Varyings { // ... #if defined(_CUSTOM_KEYWORD) float4 customParams : TEXCOORD1; // 変数宣言がキーワードに依存 #endif }; #if defined(_CUSTOM_KEYWORD) TEXTURE2D(_CustomMap); // テクスチャ宣言がキーワードに依存 SAMPLER(sampler_CustomMap); #endif half4 frag(Varyings IN) : SV_Target { if (_CUSTOM_KEYWORD) { // コンパイルエラー:_CUSTOM_KEYWORD未定義時にcustomParamsと_CustomMapが存在しない half4 customTex = SAMPLE_TEXTURE2D(_CustomMap, sampler_CustomMap, IN.uv); color *= customTex * IN.customParams; } }
変換できる例
キーワードがコードロジックの分岐のみを制御し、変数宣言に影響しない場合は安全に変換できます:
#pragma multi_compile _ _EFFECT_ON // すべての変数は常に宣言 TEXTURE2D(_EffectMap); SAMPLER(sampler_EffectMap); half4 frag(Varyings IN) : SV_Target { half4 color = baseColor; if (_EFFECT_ON) { // このエフェクトを実行するかどうかのみ制御、変数は常に存在 half4 effect = SAMPLE_TEXTURE2D(_EffectMap, sampler_EffectMap, IN.uv); color *= effect; } return color; }
ユースケース
以下は代表的なユースケースの一例です。プロジェクトの要件に応じて、他にも様々な活用方法が考えられます。
開発時
開発中は、ビルド時間の短縮やデバッグ効率の向上に活用できます:
- 素早くビルドして機能確認:
dynamic_branchに変換し、単一Shaderのみコンパイル。バリアント数とコンパイル時間を大幅に削減 - 特定機能のデバッグ、不要機能を除外:
shader_featureやmulti_compileに変換し、不要なキーワードを除外
リリース時
ShaderBuildSettingsはShaderバリアント数を抑える手段としても使えます。以下のケースに適しています:
- UberShaderバリアント最適化: 大量のバリアントを持つ汎用Shaderを使っているが、実際には一部の機能しか使わない場合
- マルチプラットフォーム対応:
BuildProfileで各プラットフォームごとにキーワードタイプを調整したり、不要なキーワードを除外- タイプ調整:メモリ制限のあるプラットフォームは
dynamic_branch、パフォーマンス重視のプラットフォームはmulti_compileやshader_feature - キーワード除外:ローエンドプラットフォームは一部のハイエンドエフェクトキーワードを除外、ハイエンドプラットフォームはフル機能を維持
- タイプ調整:メモリ制限のあるプラットフォームは
使用例
最後に、具体的な例でShaderBuildSettingsの効果を見てみましょう。
クリックしてShaderソースコードを表示
Shader "Custom/CustomUnlit" { Properties { [MainColor] _BaseColor("Base Color", Color) = (1, 1, 1, 1) [MainTexture] _BaseMap("Base Map", 2D) = "white" [Toggle] _UseTexture("Use Texture", Float) = 0 [KeywordEnum(None, Red, Green, Blue, Negative)] _Mode("Mode", Float) = 0 } SubShader { Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" } Pass { HLSLPROGRAM #pragma vertex vert #pragma fragment frag // 自前のキーワード定義 #pragma multi_compile _ _USETEXTURE_ON #pragma multi_compile _ _MODE_RED _MODE_GREEN _MODE_BLUE _MODE_NEGATIVE #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" struct Attributes { float4 positionOS : POSITION; #if defined(_USETEXTURE_ON) float2 uv : TEXCOORD0; #endif }; struct Varyings { float4 positionHCS : SV_POSITION; #if defined(_USETEXTURE_ON) float2 uv : TEXCOORD0; #endif }; #if defined(_USETEXTURE_ON) TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap); #endif CBUFFER_START(UnityPerMaterial) half4 _BaseColor; float4 _BaseMap_ST; CBUFFER_END Varyings vert(Attributes IN) { Varyings OUT; OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz); #if defined(_USETEXTURE_ON) OUT.uv = TRANSFORM_TEX(IN.uv, _BaseMap); #endif return OUT; } half4 frag(Varyings IN) : SV_Target { half4 color = _BaseColor; // #ifでの条件分岐 #if defined(_USETEXTURE_ON) half4 texColor = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv); color *= texColor; #endif // 通常ifでの条件分岐 if (_MODE_RED) color.rgb *= half3(1, 0, 0); else if (_MODE_GREEN) color.rgb *= half3(0, 1, 0); else if (_MODE_BLUE) color.rgb *= half3(0, 0, 1); else if (_MODE_NEGATIVE) color.rgb = 1 - color.rgb; return color; } ENDHLSL } } }
初期状態
まず、このShaderのデフォルト状態でのバリアント数を確認します。このShaderは合計10個のバリアントを生成します(2 × 5 = 10)。
クリックしてスクリーンショットと詳細を表示
Shaderファイルを選択すると、InspectorでShaderに含まれるキーワードを確認できます:

Compile and show codeの右にある矢印をクリックすると、実際のバリアント数が表示されます。Showをクリックすると詳細が見られます:

Keywords stripped away when not used: STEREO_CUBEMAP_RENDER_ON STEREO_INSTANCING_ON STEREO_MULTIVIEW_ON UNITY_SINGLE_PASS_STEREO Keywords always included into build: _USETEXTURE_ON _MODE_RED _MODE_GREEN _MODE_BLUE _MODE_NEGATIVE 10 keyword variants used in scene: <no keywords defined> _MODE_RED _MODE_GREEN _MODE_BLUE _MODE_NEGATIVE _USETEXTURE_ON _MODE_RED _USETEXTURE_ON _MODE_GREEN _USETEXTURE_ON _MODE_BLUE _USETEXTURE_ON _MODE_NEGATIVE _USETEXTURE_ON
キーワードタイプをshader_featureに変換
両方のキーワードグループをshader_featureに変換すると、バリアント数が10個から2個に減少します。これはshader_featureの特性どおり、デフォルトバリアントと実際に使用されているバリアントのみが保持されます。
クリックしてスクリーンショットと詳細を表示
補足:シーンにこのShaderを使用するマテリアルを配置し、
_MODE_GREENと_USETEXTURE_ONキーワードを有効にしています。

保持されたバリアント:
- デフォルトバリアント(キーワードなし)
- 実際に使用されているバリアント(MODE_GREEN USETEXTURE_ON)
Keywords stripped away when not used: STEREO_CUBEMAP_RENDER_ON STEREO_INSTANCING_ON STEREO_MULTIVIEW_ON UNITY_SINGLE_PASS_STEREO _MODE_BLUE _MODE_GREEN _MODE_NEGATIVE _MODE_RED _USETEXTURE_ON 2 keyword variants used in scene: <no keywords defined> _MODE_GREEN _USETEXTURE_ON
一部のキーワードのみ保持
_MODEキーワードグループのタイプをmulti_compileのまま、_MODE_REDのみ保持します。保持されなかったキーワードは完全に除外され、バリアント数は1個に減少します。キーワード自体が除外されるため、マテリアルでその機能を有効にすることもできなくなります。
クリックしてスクリーンショットと詳細を表示

Keywords stripped away when not used: STEREO_CUBEMAP_RENDER_ON STEREO_INSTANCING_ON STEREO_MULTIVIEW_ON UNITY_SINGLE_PASS_STEREO Keywords always included into build: _MODE_RED 1 keyword variants used in scene: _MODE_RED
dynamic_branchに変換
_MODEキーワードグループをdynamic_branchに変換します(_USETEXTURE_ONは#ifプリプロセッサディレクティブを使用し変数宣言に影響するため、変換条件を満たしません)。変換後、_MODE関連のバリアントは消えますが、キーワード自体は保持され、バリアント数は2個に減少します。dynamic_branchはバリアントを生成しないため、キーワード除外機能は効きません。
クリックしてスクリーンショットと詳細を表示

Keywords stripped away when not used: STEREO_CUBEMAP_RENDER_ON STEREO_INSTANCING_ON STEREO_MULTIVIEW_ON UNITY_SINGLE_PASS_STEREO Keywords always included into build: _USETEXTURE_ON _MODE_RED _MODE_GREEN _MODE_BLUE _MODE_NEGATIVE 2 keyword variants used in scene: <no keywords defined> _USETEXTURE_ON
コンパイル後のShaderコードを見ると、dynamic_branchの仕組みがよく分かります。_MODEキーワードがConstant Buffer内の整数変数になり、フラグメントシェーダー内でif文で判定しています。
クリックしてコンパイル後のコードを表示
// ... // _MODE関連キーワードがConstant Buffer変数になった Constant Buffer "UnityDynamicKeywords" (16 bytes) on slot 0 { ScalarInt _MODE_RED at 0 ScalarInt _MODE_GREEN at 4 ScalarInt _MODE_BLUE at 8 ScalarInt _MODE_NEGATIVE at 12 } // ... struct UnityDynamicKeywords_Type { int _MODE_RED; int _MODE_GREEN; int _MODE_BLUE; int _MODE_NEGATIVE; }; // ... // フラグメントシェーダー内でif文による_MODEキーワードの判定 fragment Mtl_FragmentOut xlatMtlMain( constant UnityDynamicKeywords_Type& UnityDynamicKeywords [[ buffer(0) ]], constant UnityPerMaterial_Type& UnityPerMaterial [[ buffer(1) ]]) { Mtl_FragmentOut output; float3 u_xlat0; float3 u_xlat1; u_xlat0.xyz = (-UnityPerMaterial._BaseColor.xyz) + float3(1.0, 1.0, 1.0); u_xlat0.xyz = (UnityDynamicKeywords._MODE_NEGATIVE != 0) ? u_xlat0.xyz : UnityPerMaterial._BaseColor.xyz; u_xlat1.xyz = UnityPerMaterial._BaseColor.xyz * float3(0.0, 0.0, 1.0); u_xlat0.xyz = (UnityDynamicKeywords._MODE_BLUE != 0) ? u_xlat1.xyz : u_xlat0.xyz; u_xlat1.xyz = UnityPerMaterial._BaseColor.xyz * float3(0.0, 1.0, 0.0); u_xlat0.xyz = (UnityDynamicKeywords._MODE_GREEN != 0) ? u_xlat1.xyz : u_xlat0.xyz; u_xlat1.xyz = UnityPerMaterial._BaseColor.xyz * float3(1.0, 0.0, 0.0); output.SV_Target0.xyz = (UnityDynamicKeywords._MODE_RED != 0) ? u_xlat1.xyz : u_xlat0.xyz; output.SV_Target0.w = UnityPerMaterial._BaseColor.w; return output; }
最後に
以上、Unity 6.3で追加されたShaderBuildSettings機能の紹介でした。
Unity 6.3以前は、Shaderバリアント数を制御するためにストリップスクリプトを手動で書く必要がありました。また、ストリップスクリプトはBuild時にしか効果を発揮しないため、Editor上で結果を確認することが困難でした。ShaderBuildSettingsというUnityエンジンレベルでサポートされた機能により、プロジェクト全体でShaderバリアント数を簡単に制御でき、Editor上で即座に結果を確認できるようになりました。
最後まで読んでいただき、ありがとうございました。皆さんのお役に立てれば幸いです!