こんにちは、エンジニアのオオバです。
シェーダーにはマクロと呼ばれる機能が実装されています。マクロを使うとシェーダー内のコードの置き換えや条件分岐を簡単に実装することが可能です。
またシェーダーには「multi_compile
」と「shader_feature
」と呼ばれる 異なるバリエーション(分岐)シェーダーを実行時に切り替える機能 です。この分岐したシェーダーの事をシェーダーバリアントと呼びます。
マクロ、multi_compile
、shader_feature
は密接な関係です。これらを使いこなすことでシェーダーを書く際の選択肢が大きく広がります。まだ使っていない方はこの記事を通してぜひ覚えてしまいましょう。
👉DOTweenの教科書を読んでUnityアニメーションをプログラミングしてみよう!
シェーダーのマクロとは?
Unityのシェーダーで使える「マクロ」とは何でしょうか。マクロとはコードの一部を置き換えられるプリプロセッサです。プリプロセッサマクロとも呼ばれますが、本記事では単に「マクロ」と呼称します。
ちなみに「プリプロセッサ」とはコンパイル前に処理挟める仕組みのこと。つまりマクロは シェーダーのコンパイル前にコードの一部を置き換えることができる機能 ということです。
言葉だけでは分かりづらいためソースコードでマクロを確認してみましょう。
#define HOGE(a) fixed4(a, 0, 0, 1);
fixed4 frag (v2f i) : SV_Target
{
return HOGE(1);
}
上記はシェーダーコードの一部で、マクロを適用したフラグメントシェーダーです。 #define マクロ名(引数) 処理
という文法でマクロを記述できます。
マクロ「HOGE」はfixed4型を返却する関数のような処理になっていることが分かります。
HOGE(1)
と記述したということはコンパイル時には fixed4(1, 0, 0, 1)
と展開されるということです。
このシェーダーを当てると上図のように赤色に描画されます。
このようにマクロを使うことで簡単にコードの置き換えられます。
またマクロ名は一般的にアッパースネークケース(大文字 + アンダースコア)で記述するので覚えておきましょう。
複数行に渡るマクロの記述方法
マクロの処理が長くなると改行したくなります。以下のように記述するとコンパイルエラーです。
#define TEST_MACRO(abc)
abc.x = 0;
こちらのように\ (バックスラッシュ)
をお尻に付けるとコンパイルが通ります。
#define TEST_MACRO(abc) \
abc.x = 0;
書きなれないかもしれませんが、バックスラッシュを使って改行処理を記述していきましょう。
multi_compileとshader_featureとは?
次に「multi_compile
」と「shader_feature
」について解説します。
繰り返しになりますが、multi_compile
とshader_feature
はビルド時にコンパイルされたシェーダーバリエーション(シェーダーバリアント)を 実行時に切り替える機能 です。具体的にどのようにシェーダーバリアントを作るのでしょうか。
次のコードを見てみましょう。
fixed4 frag (v2f i) : SV_Target
{
// デフォルト色(緑)
fixed4 color = fixed4(0, 1, 0, 1);
#ifdef YELLOW
color = fixed4(1, 1, 0, 1);
#elif defined(BLUE)
color = fixed4(0, 0, 1, 1);
#endif
return color;
}
#ifdef〜#elif〜#endifを使って処理を分岐しました。マクロ「YELLOW」が有効なら黄色、「BLUE」が有効なら青色に描画されます。
現状、YELLOWもBLUEも有効化されていないため、デフォルト色の緑が描画されます。
では次にマクロを有効化してみましょう。マクロを有効化する方法は2種類あります。
1.defineを使ったマクロ切り替え
1つ目は #define
を使用したマクロ切り替えです。次のコードを見てみましょう。
#define YELLOW
fixed4 frag (v2f i) : SV_Target
{
// デフォルト色(緑)
fixed4 color = fixed4(0, 1, 0, 1);
#ifdef YELLOW
color = fixed4(1, 1, 0, 1);
#elif defined(BLUE)
color = fixed4(0, 0, 1, 1);
#endif
return color;
}
マクロ「YELLOW」を有効化しました。文法は #define マクロ名
で有効化します。
YELLOWを有効化したことで有効化したことでシェーダーは黄色を描画しました。
2.C#からマクロ切り替え
もう1つはC#コードからマクロの切り替えです。Materialに対して EnableKeyword 関数を実行することでマクロを有効化します。
[SerializeField] private Renderer _renderer;
void Awake()
{
_renderer.material.EnableKeyword("YELLOW");
}
上記のように EnableKeyword
でマクロYELLOWを有効化できます。
しかしまだ準備不足で上記の コードは正しく動作しません。 ここでmulti_compile
とshader_feature
が登場します。
multi_compileとshader_featureでコンパイル指定
multi_compile
とshader_feature
を使用することでシェーダーコードのコンパイル指示ができます。
つまり EnableKeyword("YELLOW"); が動作しない理由は マクロ「YELLOW」が有効なバリエーションがコンパイルされなかったため です。
そこで multi_compile
とshader_feature
の登場です。まずはmulti_compile
を使った例を見ていきましょう。
#pragma multi_compile _ YELLOW
fixed4 frag (v2f i) : SV_Target
{
fixed4 color = fixed4(0, 1, 0, 1);
#ifdef YELLOW
color = fixed4(1, 1, 0, 1);
#elif defined(BLUE)
color = fixed4(0, 0, 1, 1);
#endif
return color;
}
ポイントはここ#pragma multi_compile _ YELLOW
です。
文法はこちら 「 #pragma multi_compile マクロ名
」 。今回の場合は「_(アンダースコア)」と「YELLOW」をコンパイルするように指示しています。
YELLOWがコンパイルされたことで黄色が描画されるようになりました。
multi_compile
に「_(アンダースコア)」を指定している理由は、YELLOWが存在しない場合用です。もし下記のように「_」を除いてYELLOWだけを指定したらどうなるでしょうか。
#pragma multi_compile YELLOW
YELLOWの存在しないシェーダーがコンパイルされないためマクロ「YELLOW」が常に有効化されてしまうということです。 指定したマクロが有効ではない場合も存在するなら「_」をコンパイル対象に含めましょう。
次にshader_feature
の例を見ていきましょう。基本的にmulti_compile
と記述方法は同じです。
#pragma shader_feature _ YELLOW
fixed4 frag (v2f i) : SV_Target
{
fixed4 color = fixed4(0, 1, 0, 1);
#ifdef YELLOW
color = fixed4(1, 1, 0, 1);
#elif defined(BLUE)
color = fixed4(0, 0, 1, 1);
#endif
return color;
}
#pragma shader_feature _ YELLOW
と記述することでYELLOマクロのある場合とない場合をコンパイルします。
multi_compileとshader_featureの違いって何?
multi_compile
とshader_feature
の違いはシェーダーバリアントのコンパイル対象です。multi_compile
は指示されたシェーダーバリアントを全てコンパイル対象にします。
一方shader_feature
は未使用のマクロをコンパイルから外します。ここで言う「マクロの使用」とは #define や Material.EnableKeyword を指します。
つまりmulti_compile
は全部入り、shader_feature
は使用した分だけシェーダーバリアントが作られるということです。shader_feature
の方が無駄を省く分ビルドサイズが節約できるかもしれません。
まとめ
本記事ではマクロ、multi_compile
、shader_feature
の入門コンテンツとして解説してきました。
簡単に記事の内容をまとめます。
①マクロとはコンパイル前のコード置き換え
②multi_compile、shader_featureはシェーダーバリアントのコンパイル指示
③shader_featureは未使用マクロを除外してコンパイル
マクロ・multi_compile
、shader_feature
を使うことでシェーダー内の分岐を簡単に実装することができます。
シェーダーってどうしてもいろんなパターンを網羅する必要が出てくるため、適切にバリアント化する必要が出てきます。
これからシェーダーを始める方はぜひともマクロ・multi_compile
、shader_feature
を正しく理解して使っていただければと思います。

筆者のXをフォローしよう