こんにちは、トイロジックでツール業務を担当しているプログラマーのIです。主にプロジェクトで使用するGUI/CUIツールやDCCツールの作成・整備を行っています。
本記事では、社内のShaderEditorでノードからどのようにHLSLコードを生成しているのかという部分を簡単にご紹介しようと思います。今回は複雑な部分は省き、基本的な考え方をお伝えできればと思います。
ShaderEditorとは?
まずはじめに、社内のShaderEditorがどういったものなのかを簡単に説明したいと思います。
トイロジックでは元々、プログラマーによってマテリアルの作成を行っていましたが、イテレーションの速さなどからデザイナー(TA)が自身でマテリアルを作成できるようにしたいという思いから作成されました。
基本的には出力ノードに対して値や関数ノードを繋げていき、一つのマテリアルを作成します。
WPFでこういったノードを表示するのにとても便利なOSSですので、ご興味がある方はぜひ使ってみてはいかがでしょうか。
ノードからどのようにHLSLを生成しているの?
ShaderEditorでは、ノードで組んだマテリアルをHLSLに自動変換します。
ShaderEditorで使用できるノードには一つ一つ役割があります。値(定数バッファやテクスチャなどのリソースも含む)を生成するノードや、HLSL組み込みの関数を呼び出すノード、社内で作成している特定の関数を呼び出すノードなどそれぞれですが、基本的に一つのノードで一つの処理が行われます。
皆さんが知りたいのはこれをどのようにしてHLSLにしているのかというところかと思います。今回は構造体や定数バッファの定義などの部分は省き、main関数内のコード生成に焦点を置いて説明しようと思います。
結論から言うと、出力ノードの入力ピンに繋がっているノードを順に巡っていきスタックに積んでいき、上から順番に処理していくだけです。ただ、既に積まれているノードが再度積まれる場合は、既に積まれているものを削除して先頭に積みなおします。これにより、スタックの先頭に近いノードほどコード生成の優先度の高いノードになります。
今回の例
では今回の例で見ていきましょう。
一番下の「PixelDepthOffset」から見ていき、最初にノードがつながっているのは「Opacity」ですね。
では、「Opacity」に繋がっているノードを順に巡っていきます。
これを見ると「MaskComp - float」
→ 「Texture Sampler」
→ 「Texture2D」
→ 「PSIn Receiver」
という順にスタックに積まれることになりますね。 (基本的にノード内の入力ピンは下から処理していきます)
コード生成時はこのスタックされたノードを上から一つ一つ処理していけばいいわけです。※ 上二つの「PSIn Receiver」と「Texture2D」は値ノードでmain関数のコード生成前にそれぞれ事前に「VSOutput構造体」と「Texture2Dリソース」が生成されていることにします
この部分だけで実際に生成されるコードは以下のようになりますね。
PSResult result;
float4 LocalVal_v0 = texBaseColor.Sample(texBaseColor_s, input.UV); // TextureSampleノード : texBaseColor, texBaseColor_s はTexture2Dノードから、input.UV はPSIn Receiverノードから
float LocalVal_f0 = LocalVal_v0.a; // MaskComp - floatノード
result.Opacity = LocalVal_f0; // MeshOutBlockノード
このように出力ノードに繋がっているすべてのノードを順に巡りスタックに積んでいき、先頭から処理することで一つのHLSLコードを生成することができます。今回は説明を省いていますが、定数バッファやテクスチャリソースなどの値ノード系はmain関数内のコード生成前に事前にスタック内から該当するノードを探し出し、コードの先頭に生成しています。
最後に
本記事ではコード生成の処理の中でも基本的な部分の考え方のみご紹介させていただきました。実際にはこのほかにも自動生成された変数の管理やスコープの管理など考えないといけないことは多いですが、基本の考え方は今回紹介した出力ノードから繋がっているノードを巡り、スタックに積んで上から処理していくというものです。
今回の内容が少しでもお役に立てると嬉しいです。ぜひ挑戦してみてください。