先日 Projector For LWRP のユーザーから、シェーダーを SRP Batcher に対応して欲しいという要望をいただきました。恥ずかしながら SRP Batcher というものを知らなかったのですが、調べてみたらシェーダー定数を UnityPerMaterial という名前の CBUFFER にぶっこむだけでいいみたいだったので、安請け合いしたところ・・・

いざやってみると全然うまくいかない。

これは僕の環境が Mac だったからというのもあるんですが、逆に Windows だったらこの問題に気付かずにリリースしてしまったかもしれないので、気付けてラッキーといえばラッキーだったかも。

で、うまくいかなかった根本的な原因は、Mac (Metal) の場合に CBUFFER_START(name) マクロがきちんと定義されてなかったということ。
このマクロは HLSLSupport.cginc の中で

と定義されていて、見てのとおり Mac (Metal) の場合は空になっています。

ところが、Universal Render Pipeline の場合、Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl の中で

と API 毎に違うファイルがインクルードされていて、それぞれのファイルで CBUFFER_START が定義されています。
それぞれのファイルを見てみると、GLES2.hlsl だけが CBUFFER_START を空で定義していて、それ以外は

となっています。

つまり Mac の場合でも CBUFFER_START がきちんと定義されてなければいけないのに、UnityCG.cginc をインクルードしてしまうと定義が空になり、
Builtin property found in another cbuffer than "UnityPerDraw"
という理由で SRP Batcher が not compatible になっていしまいます。

なので Packages/com.unity.render-pipelines.core/Common.hlsl をインクルードすればいいわけですが、実はここにも罠があります。

というのは、Unity でシェーダーを書くときは、HLSLのコードを

のように、CGPROGRAM 〜 ENDCG の間に書くと思うんですが、CGPROGRAM を使うと自動的に HLSLSupport.cginc がインクルードされてしまうのでした。
このため、例えば

と書くと、redefinition of '_Time' とか 'CBUFFER_START': macro redefinition とかいったエラーが出てしまいます。

これを回避するため、CGPROGRAM 〜 ENDCG の代わりに、HLSLPROGRAM 〜 ENDHLSL を使わないといけません。

でもこれだと UnityCG.cginc で定義されている便利なフォグ関連のマクロ (UNITY_FOG_COORDS, UNITY_TRANSFER_FOG, UNITY_APPLY_FOG など) が使えません。また、UnityObjectToClipPos(v)TransformObjectToHClip(v) に変更する必要もあります。

それに、Packages/com.unity.render-pipelines.universal/ShaderLibrary/Input.hlsl をインクルードしてしまうと、そのシェーダーは Universal RP を使っているプロジェクトじゃないとコンパイルできなくなってしまいます。

なので、できれば UnityCG.cginc を使いたい。で、マニュアルを見てみると、Unity 2019.3 から新しい enable_cbuffer というプラグマが追加されています。さっそく試してみると、

と書いて SRP Batcher compatible になりました。CGPROGRAM を使っても問題ありません。

でもこの方法だと Unity 2019.2 以前では
Unrecognized #pragma directive: enable_cbuffer
という警告が出てしまいます。

Unity 2019.2 以前でも同じソースファイルを使えるようにするとしたら、

と書いても良さそうです。ただし、UNITY_ENABLE_CBUFFER マクロが使われているのは Unity 2019.3 以降の HLSLSupport.cginc なので、Unity 2019.2 以前では SRP Batcher compatible ではなくなります。

Unity 2019.2 の HLSLSupport.cginc では

となっていて、SHADER_API_PSSL が定義されているときの CBUFFER_START が変わっています。なので、

としておくのが良さそうです。

しかし・・・、これで問題なく動くかなと思ってたんですが、Unity 2018.4 で Player settings の Metal Editor Support のチェックを外したところ、
GLSL: Built-in matrices are not in any uniform block.
というエラーが出てしまいました。Metal の場合は問題なかったんですが、OpenGLの場合に CBUFFER を定義しなおしてしまうと問題があるようです。

Unity のブログによると、OpenGL で SRP Batcher がサポートされるのは Unity 2019.2 からとなっています。なのでそれよりも前のバージョンでは CBUFFER は空の定義のままにしておいた方がいいみたいです。

そこで最終的には次のようなコードを EnableCbuffer.cginc として HLSLPROGRAM 〜 ENDHLSL の間でインクルードすることにしました。

紆余曲折あって最終的にこの形になり、これでほとんどのシェーダーを SRP Batcher compatible にできたんですが、ライトマップを使うシェーダーは not compatible のままでした。これは unity_LightmapST というビルトインのシェーダー定数が UnityPerDraw という名前の CBUFFER に入っていないといけないのに、UnityCG.cginc (UnityShaderVariables.cginc) をインクルードしてしまうと、UnityLightmaps という CBUFFER の中に入ってしまうためです。

素直に Packages/com.unity.render-pipelines.universal/ShaderLibrary/Input.hlsl をインクルードすればいいんですが、なんとか UnityCG.cginc を使いたいので、この解決方法も説明したいと思いますが、ここまででだいぶ長くなったので、また別の記事で書くことにします。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

Anti Spam Code *