Unityシェーダーの基礎知識。
[KeywordEnum]を使ったShaderVariantの実装、シェーダーのコード分岐。
関連ページ
参考URL
Unityでは
ShaderVariant
という静的分岐の機能があります。
この機能を使えば、インスペクターからVariantを切り替えて、1つシェーダーに複数の効果を持たせることが出来ます。
※コードが古くなっていたのでURP用に記事を改修しました(2026/03/16)

ShaderVariantとは
まず今回テストに使うShaderVariantの効果を紹介します。
インスペクターからVariantを切り替えることで、「シルエット化無し/青シルエット化/赤シルエット化」切り替えています。
ShaderVariantは、端的に言うとC#のプリプロセッサに近い機能になります。
例えばC#ではプリプロセッサを使うことで、iOS端末とAndroid端末で処理を分けることが可能になります。
下はその一例です。
UNITY_IOS
と
UNITY_ANDROID
は、Unity側で標準に用意されているシンボル名です。
using
UnityEngine;
public
class
TestPreprocessor
:
MonoBehaviour
{
private
void
Start()
{
#if
UNITY_IOS
Debug.Log("iOSの処理");
#elif
UNITY_ANDROID
Debug.Log("Androirの処理");
#endif
}
}
C#はプロジェクトビルド時に、必要なプリプロセッサのコードのみビルドに含めます。
なので上の例だと、BuildSettingsがiOSの場合は
private void Start()
{
Debug.Log("iOSの処理");
}
というコードのみがビルドに含まれ、Androidの場合は
private void Start()
{
Debug.Log("Androirの処理");
}
というコードのみがビルドに含まれます。
ShaderVariantの概念はこのプリプロセッサとほぼ同じになります。
下はShaderVariantの重要な処理部分のみ抜粋したものです。
half4
frag
(Varyings
input)
:
SV_Target
{
half4
col
=
SAMPLE_TEXTURE2D(_MainTex,
sampler_MainTex,
input.uv);
//_SILHOUETTE_NOのキーワードを選択している時は以下のコード
#if
defined(_SILHOUETTE_NO)
col.rgb
*=
input.color;
//_SILHOUETTE_BLUEのキーワードを選択している時は以下のコード
#elif
defined(_SILHOUETTE_BLUE)
col.rgb
=
half3(0,
0,
1);
//_SILHOUETTE_REDのキーワードを選択している時は以下のコード
#elif
defined(_SILHOUETTE_RED)
col.rgb
=
half3(1,
0,
0);
#endif
return
col;
}

コード全文と解説
下が今回のテストのコード全文です。

Shader
"UI/TestVariant"
{
Properties
{
//インスペクターからVarinatを選択させるため設定
[KeywordEnum(No,
Blue,
Red)]
_SILHOUETTE("Silhouette
Mode",
Float)
=
0
}
SubShader
{
Tags
{
"Queue"
=
"Transparent"
}
Cull
Off
ZWrite
Off
Blend
SrcAlpha
OneMinusSrcAlpha
Pass
{
HLSLPROGRAM
#pragma
vertex
vert
#pragma
fragment
frag
//Varinatのキーワードを定義
#pragma
shader_feature
_SILHOUETTE_NO
_SILHOUETTE_BLUE
_SILHOUETTE_RED
#include
"Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
struct
Attributes
{
float4
positionOS
:
POSITION;
float2
uv
:
TEXCOORD0;
half4
color
:
COLOR;
};
struct
Varyings
{
float4
positionCS
:
SV_POSITION;
float2
uv
:
TEXCOORD0;
half4
color
:
COLOR;
};
//uGUI Image > Source Imageにアタッチされた画像を参照する
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
Varyings
vert
(Attributes
input)
{
Varyings
output;
output.positionCS
=
TransformObjectToHClip(input.positionOS.xyz);
output.uv
=
input.uv;
output.color
=
input.color;
return
output;
}
half4
frag
(Varyings
input)
:
SV_Target
{
half4
col
=
SAMPLE_TEXTURE2D(_MainTex,
sampler_MainTex,
input.uv);
//_SILHOUETTE_NOのキーワードを選択している時は以下のコード
#if
defined(_SILHOUETTE_NO)
col.rgb
*=
input.color;
//_SILHOUETTE_BLUEのキーワードを選択している時は以下のコード
#elif
defined(_SILHOUETTE_BLUE)
col.rgb
=
half3(0,
0,
1);
//_SILHOUETTE_REDのキーワードを選択している時は以下のコード
#elif
defined(_SILHOUETTE_RED)
col.rgb
=
half3(1,
0,
0);
#endif
return
col;
}
ENDHLSL
}
}
}

Variantに関係する部分だけ解説します。
インスペクター上にVariantの選択タブを表示させるために、まず下のコードを打ち込んでいます。
Properties
{
//インスペクターからVarinatを選択させるため設定
[KeywordEnum(No,
Blue,
Red)]
_SILHOUETTE("Silhouette
Mode",
Float)
=
0
}
ここの部分は結構重要なので3つに分割して解説します。
[KeywordEnum(No, Blue, Red)]
ここに記述した文字列が実際に選択タブで表示されます。
文字列の指定に制限はなく何でも良いです。
ちなみにこの
[KeywordEnum]
を
マテリアルプロパティドロワー
、プロパティの中の()を
ドロワー引数
と呼びます。
この部分は制限がかなり厳しいです。
まず一番最初に_(アンダーバー)を付ける必要があります。
更に、後ろに出てくるVarinatのキーワードとリンクしている必要があります。
//Varinatのキーワードを定義
#pragma
shader_feature
_SILHOUETTE_NO
_SILHOUETTE_BLUE
_SILHOUETTE_RED
今回は
_SILHOUETTE_NO
と
__SILHOUETTE_BLUE
、
_SILHOUETTE_RED
の3つのキーワードを定義しています。
なのでその共通する前半部分の
_SILHOUETTE
をプロパティにも入力しています。
もしこれが
_SILHOUETTE
ではなく
_SILHOUETTES
にした場合、キーワードと一致していないのでVariantが機能しません。
文字列が一致している必要があるものの、実はプロパティの方は小文字にするだけの変更なら問題ないです。
つまり下のコードでもOKです。
Properties
{
//インスペクターからVarinatを選択させるため設定
[KeywordEnum(No,
Blue,
Red)]
_silhouette("Silhouette
Mode",
Float)
=
0
}
ただ問題なのは、実際のキーワードの定義の方は小文字が許されておらず、必ず全て大文字にする必要があります。
下の例ではVariantが動きません。
//Varinatのキーワードを定義
#pragma
shader_feature
_silhouette_NO
_silhouette_BLUE
_silhouette_RED
わざわざプロパティだけ小文字にする意味もないので、今回のテストでは両方大文字を使っています。
("Silhouette Mode", Float) = 0
"Silhouette Mode"
はインスペクターに表示される文字列で、何を入力しても問題ないです。
第二引数に関しては、[KeywordEnum]で指定した変数には、0, 1, 2...という整数しか代入されないため、一見FloatよりIntの方が適切に思えます。
しかし
PropertiesにIntと入力しても結局処理的にはFloatとして扱われる
ため、わざわざIntと入力しても意味はないです。
= 0
がVariantの初期値の設定です。
もしこれが= 1であった場合、MaterialからTest/TetstVariantを指定した時の初期設定はBlueになります。
ShaderVarinatのキーワードを定義する時、必ず
#pragma shader_feature
という記述が先に必要になります。
//Varinatのキーワードを定義
#pragma
shader_feature
_SILHOUETTE_NO
_SILHOUETTE_BLUE
_SILHOUETTE_RED
今回の場合3つしか定義していないですが、別に4つでも5つでも良いです。
増やした分は、ちゃんとプロパティのKeywordEnumにも追記します。
実際のシェーダー処理をしているフラグメントシェーダー部分は、今回の場合あまり特別なことをしていないです。
プリプロセッサの知識があればすっと入ってくるコードだと思います。
インスペクターから_SILHOUETTE_NOを選択していればシルエット化なし。
_SILHOUETTE_BLUEを選択していれば青シルエット化、_SILHOUETTE_REDを選択していれば赤シルエット化しています。
half4
frag
(Varyings
input)
:
SV_Target
{
half4
col
=
SAMPLE_TEXTURE2D(_MainTex,
sampler_MainTex,
input.uv);
//_SILHOUETTE_NOのキーワードを選択している時は以下のコード
#if
defined(_SILHOUETTE_NO)
col.rgb
*=
input.color;
//_SILHOUETTE_BLUEのキーワードを選択している時は以下のコード
#elif
defined(_SILHOUETTE_BLUE)
col.rgb
=
half3(0,
0,
1);
//_SILHOUETTE_REDのキーワードを選択している時は以下のコード
#elif
defined(_SILHOUETTE_RED)
col.rgb
=
half3(1,
0,
0);
#endif
return
col;
}

shader_featureとmulti_compile
本シェーダーではキーワードの定義にshader_featureを使っていますが、これをmulti_compileに置き換えても全く同様に動きます。
//Varinatのキーワードを定義
//#pragma shader_feature _SILHOUETTE_NO _SILHOUETTE_BLUE _SILHOUETTE_RED
#pragma
multi_compile
_SILHOUETTE_NO
_SILHOUETTE_BLUE
_SILHOUETTE_RED
2つの処理の違いは、アプリビルド時にコードを軽量化するか否かです。
shader_featureはプロジェクト内のマテリアルで一つも指定されていないキーワードのコードを削ります。
対してmulti_compileは必ず全てビルドに含めます。
例えばプロジェクト内で
_SILHOUETTE_NO
のVariantのみ使っている場合は、
_SILHOUETTE_NO
の範囲のみがビルドに含まれます。
反対に
_SILHOUETTE_NO
のVariantのみ使っていない場合は、
_SILHOUETTE_BLUE
と
_SILHOUETTE_RED
の範囲のみがビルドに含まれます。
全て使っていれば全ての範囲が含まれます。
これだけ聞くとshader_featureの方が優秀に見えますが、一つ落とし穴があります。
マテリアルからそのキーワードを指定していなくても、C#のコードから動的に変更してそのキーワードに変更し使っている場合です。
この場合shader_featureを使うとエラーが出てしまいます。
0
0