はじめに
Unity でシェーダをテンプレートファイルから生成できるアセットを作ったので、作った動機および使い方の紹介をしたいと思います。
以前 uRaymarching というアセットの一部の機能として作成したものを分離したものになります。
ダウンロード
外観
テンプレートファイルを用意すると、中に書かれた分岐や変数に応じて、こんな感じのシェーダ生成 UI が作成されます:
UI のコンポーネントとしては、チェックボックスによるフラグの ON/OFF や、変数のプルダウンリストからの選択、コード編集ブロックなどになります。
背景・動機
Unity でシェーダの一部分を変更したいときはシェーダバリアントを利用します。
具体的には以下のように #ifdef
の分岐の変数を #pragma multi_compile
文や #pragma shader_feature
でキーワードとして指定しておき、それをスクリプト等から切り替えられる、という形です。
CGINCLUDE ... fixed4 getColor() { #ifdef HOGE fixed4 col = fixed4(1, 0, 0, 1); #elif FUGA fixed4 col = fixed4(0, 1, 0, 1); #elif PIYO fixed4 col = fixed4(0, 0, 1, 1); #else fixed4 col = fixed4(0, 0, 0, 1); #endif return col; } ... ENDCG ... Pass { ... #pragma vertex Vert #pragma fragment Frag #pragma multi_compile HOGE FUGA PIYO ___ ... }
var mat = GetComponent<Renderer>().material;
mat.EnableKeyword("HOGE");
ただ、この方法では CGPROGRAM
/CGINCLUDE
~ ENDCG
ブロック内での分岐しか出来ません。ちょっとだけプロパティを追加したり、ごっそりパスを追加 / 削除したり、そういったブロックでの外側の分岐や変数を使いたいときもあると思います。この話は以下のロジカルビートさんの「いけにえと雪のセツナ」のエントリでも言及されています:
「いけにえと雪のセツナ」グラフィック解説(第3回・シェーダ編) – 株式会社ロジカルビート
また、コードの一部だけ変えたシェーダを量産したいケースもあります。この際、量産したい数だけシェーダをコピーして用意したり、全ての分岐を単一のシェーダの分岐として突っ込んだりする必要がありますが、これは作業量(元ファイルに変更が入ると全部修正)やリソースの無駄遣い(multi_compile
の場合、分岐 x 他の分岐文のシェーダのコンパイルが走る)といった観点からも良くありません。テンプレートファイルで編集したい箇所だけ記述しておき、そこを UI からちょこちょこいじって、生成したシェーダを一括管理出来たら良いな、と思いました。
こういった背景から色々と機能を作成してみました。
使い方
使い方は、以下のような流れになります。
- シェーダテンプレートの用意(
*/Resources/ShaderTemplates
以下に .txt ファイルを置く) - プロジェクトウィンドウから「Create > Shader > uShaderTemplate > Generator」でジェネレータを生成
- シェーダの名前やシェーダテンプレートを生成した Generator のインスペクタから選択
- 変数やコードブロックを編集
- 「Export」ボタンを押してシェーダを生成
- 「Create Material」ボタンを押してマテリアルを生成(or プロジェクトビューの「Create > Material」)
ジェネレータ
ジェネレータは生成されたシェーダのパラメタを保存したりテンプレートに記述された情報から UI を生成したりといった役割を果たすアセット(ScriptableObject
)です。プロジェクトウィンドウで作成することが出来ます:
アセットファイルをクリックすると以下のようなインスペクタが表示され、テンプレートで設定した変数が編集できるようになっています:
ビルドに含む意味は無いので、Editor ディレクトリ以下に置いておくのが良いと思います。
テンプレートファイル
さて、試しにサンプルとして追加した「uShaderTemplate > Examples > Editor > Resources > ShaderTemplates > 1. VertFrag.txt」を見てみましょう:
Shader "Custom/<Name>" { Properties { @block Properties _MainTex("Texture", 2D) = "white" {} @endblock } SubShader { Tags { "Queue"="<Queue=Geometry|Transparent>" "RenderType"="<RenderType=Opaque|Transparent>" } LOD <LOD=100> CGINCLUDE #include "UnityCG.cginc" struct v2f { float2 uv : TEXCOORD0; @if UseFog : true UNITY_FOG_COORDS(1) @endif float4 vertex : SV_POSITION; }; @block VertexShader sampler2D _MainTex; float4 _MainTex_ST; v2f vert(appdata_full v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.texcoord, _MainTex); UNITY_TRANSFER_FOG(o,o.vertex); return o; } @endblock @block FragmentShader fixed4 frag(v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); UNITY_APPLY_FOG(i.fogCoord, col); return col; } @endblock ENDCG Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag @if UseFog : true #pragma multi_compile_fog @endif ENDCG } } CustomEditor "uShaderTemplate.MaterialEditor" }
コード中に @if hoge
、<Fuga>
、@block Piyo
といった記述が見つかると思います。これらがテンプレートファイルで使用できる文法になります。#pragma multi_compile
や #pragma shader_feature
と異なり、シェーダ内の任意の場所で使用することが出来ます。
任意の */Resources/ShaderTemplates
ディレクトリ下に置くと自動で検出され、「Basic > Shader Template」に表示されるようになります。
テンプレートファイルで使える文法
使える文法は以下になります:
分岐
@if HogeHoge #define HOGEHOGE @endif
"Hoge Hoge" とラベル付けられたチェックボックスが Condition
セクションに表示されるようになります。チェックしたときのみ IF 文の中身が展開されます。
また、@else
を追加してチェックされてないときの展開される文も指定できます:
@if HogeHoge2 : false #define HOGEHOGE2 @else #define HOGEHOGE3 @endif
なお、@elsif
は未実装です。
変数
変数は <>
で囲んだものが Variables セクションに表示されるようになります。3 つの書き方があります:
#define Hoge <Hoge> // 空のテキストフィールド #define Fuga <Fuga=Hoge> // デフォルト値のあるテキストフィールド #define Piyo <Piyo=Hoge|Fuga|Piyo> // プルダウンリスト
コードブロック
@block
~ @endblock
で囲んだ場所はシンタックスハイライト付きのコードエディタとして新しいセクションが生成されます。直接インスペクタからコードを編集しても良いですが、生成後のシェーダでも @block
~ @endblock
の記述があり、この内部を外部エディタで編集しても、保存した際に自動で反映されるようになっています(FileWatcher
で監視)。
@block Moge float Moge2() { return _Move * _Moge; } @endblock
ボタン
ボタンの各機能は以下になります:
- Export(Ctrl+R)
- シェーダをジェネレータの情報を元にエクスポートします。Ctrl + R でも可能です。
- Create Material
- 生成されたシェーダを元にマテリアルを生成します。
- Reset to Default
- テンプレートに書かれたデフォルト値へと全てのパラメタを変更します。
- Update Template
- テンプレートファイルを編集した場合、このボタンを押すと変更が反映されます。
- Reconvert All
- 生成されたシェーダを一括変換します。テンプレートの変更を全適用したいときに便利です。
定数
変数をそれぞれのシェーダで入れる代わりに、定数を格納したアセットを作成し、それを複数のジェネレータで共有することが出来ます。
「Create > Shader > uShaderTemplate > Constants」をプロジェクトビューから選択し、定数ファイルを生成します。アセットには Name
と対応する Value
を入力することが出来ます。これをジェネレータの Constants フィールドにドラッグ・ドロップすることで適用することが出来ます。注意点として、定数ファイルを修正した場合、その変更を適用したい場合は「Reconvert All」で全てのシェーダを再コンバートする必要があります。
その他
コードエディタについて
本当はコードエディタ部も分離したかったのですが、シェーダの編集に特化した作りになっているので、取り敢えず今はこのままで…。
シンタックスハイライトの仕組みは以下の記事にまとめてあります。
Tips
- 生成されたシェーダやジェネレータは場所を移動しても管理されます(シェーダは参照を保持しているため)
- マテリアルやゲームオブジェクトのインスペクタからも編集できます
- テンプレートにエラーが有ると表示されなくなるので、テンプレートをいじる時はジェネレータのインスペクタで行うのをオススメします'
- 生成されたシェーダを利用したマテリアルの参照は「Materials」セクションから参照できます
- ジェネレータは
ScriptableObject
のアセットになっています。
おわりに
ずっと前に作成したものだったのですが、まとめていなかったので書きました。ご要望あれば機能追加していきます。