先日 Projector For LWRP のユーザーから、シェーダーを SRP Batcher に対応して欲しいという要望をいただきました。恥ずかしながら SRP Batcher というものを知らなかったのですが、調べてみたらシェーダー定数を UnityPerMaterial という名前の CBUFFER にぶっこむだけでいいみたいだったので、安請け合いしたところ・・・
いざやってみると全然うまくいかない。
これは僕の環境が Mac だったからというのもあるんですが、逆に Windows だったらこの問題に気付かずにリリースしてしまったかもしれないので、気付けてラッキーといえばラッキーだったかも。
で、うまくいかなかった根本的な原因は、Mac (Metal) の場合に CBUFFER_START(name)
マクロがきちんと定義されてなかったということ。
このマクロは HLSLSupport.cginc の中で
1 2 3 4 5 6 7 8 |
#if defined(SHADER_API_D3D11) || defined(UNITY_ENABLE_CBUFFER) || defined(SHADER_API_PSSL) #define CBUFFER_START(name) cbuffer name { #define CBUFFER_END }; #else // On specific platforms, like OpenGL, GLES3 and Metal, constant buffers may still be used for instancing #define CBUFFER_START(name) #define CBUFFER_END #endif |
と定義されていて、見てのとおり Mac (Metal) の場合は空になっています。
ところが、Universal Render Pipeline の場合、Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl の中で
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// Include language header #if defined(SHADER_API_XBOXONE) #include "Packages/com.unity.render-pipelines.xboxone/ShaderLibrary/API/XBoxOne.hlsl" #elif defined(SHADER_API_PSSL) #include "Packages/com.unity.render-pipelines.ps4/ShaderLibrary/API/PSSL.hlsl" #elif defined(SHADER_API_D3D11) #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/API/D3D11.hlsl" #elif defined(SHADER_API_METAL) #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/API/Metal.hlsl" #elif defined(SHADER_API_VULKAN) #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/API/Vulkan.hlsl" #elif defined(SHADER_API_SWITCH) #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/API/Switch.hlsl" #elif defined(SHADER_API_GLCORE) #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/API/GLCore.hlsl" #elif defined(SHADER_API_GLES3) #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/API/GLES3.hlsl" #elif defined(SHADER_API_GLES) #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/API/GLES2.hlsl" #else #error unsupported shader api #endif |
と API 毎に違うファイルがインクルードされていて、それぞれのファイルで CBUFFER_START
が定義されています。
それぞれのファイルを見てみると、GLES2.hlsl だけが CBUFFER_START
を空で定義していて、それ以外は
1 2 |
#define CBUFFER_START(name) cbuffer name { #define CBUFFER_END }; |
となっています。
つまり 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のコードを
1 2 3 4 |
CGPROGRAM // ここにHLSLのコードを書く. : ENDCG |
のように、CGPROGRAM 〜 ENDCG
の間に書くと思うんですが、CGPROGRAM
を使うと自動的に HLSLSupport.cginc がインクルードされてしまうのでした。
このため、例えば
1 2 3 4 5 |
CGPROGRAM #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Input.hlsl" : ENDCG |
と書くと、redefinition of '_Time'
とか 'CBUFFER_START': macro redefinition
とかいったエラーが出てしまいます。
これを回避するため、CGPROGRAM 〜 ENDCG
の代わりに、HLSLPROGRAM 〜 ENDHLSL
を使わないといけません。
1 2 3 4 5 |
HLSLPROGRAM #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Input.hlsl" : 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
というプラグマが追加されています。さっそく試してみると、
1 2 3 4 5 |
CGPROGRAM #pragma enable_cbuffer #include "UnityCG.cginc" : ENDCG |
と書いて SRP Batcher compatible になりました。CGPROGRAM
を使っても問題ありません。
でもこの方法だと Unity 2019.2 以前では
Unrecognized #pragma directive: enable_cbuffer
という警告が出てしまいます。
Unity 2019.2 以前でも同じソースファイルを使えるようにするとしたら、
1 2 3 4 5 |
HLSLPROGRAM #define UNITY_ENABLE_CBUFFER #include "UnityCG.cginc" : ENDHLSL |
と書いても良さそうです。ただし、UNITY_ENABLE_CBUFFER
マクロが使われているのは Unity 2019.3 以降の HLSLSupport.cginc なので、Unity 2019.2 以前では SRP Batcher compatible ではなくなります。
Unity 2019.2 の HLSLSupport.cginc では
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#if defined(SHADER_API_PSSL) // variable modifiers #define nointerpolation nointerp #define noperspective nopersp #define CBUFFER_START(name) ConstantBuffer name { #define CBUFFER_END }; #elif defined(SHADER_API_D3D11) #define CBUFFER_START(name) cbuffer name { #define CBUFFER_END }; #else // On specific platforms, like OpenGL, GLES3 and Metal, constant buffers may still be used for instancing #define CBUFFER_START(name) #define CBUFFER_END #endif |
となっていて、SHADER_API_PSSL
が定義されているときの CBUFFER_START
が変わっています。なので、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
HLSLPROGRAM #if 201930 <= UNITY_VERSION // SRP Batcher 対応のため、Unity 2019.3 以降では UNITY_ENABLE_CBUFFER を定義しておく. #define UNITY_ENABLE_CBUFFER #elif 201820 <= UNITY_VERSION // Unity 2018.2 より前のバージョンはそもそも SRP Batcher を使えないので、CBUFFER_START を定義しなおす必要はない. #include "HLSLSupport.cginc" #if !(defined(SHADER_API_D3D11) || defined(SHADER_API_PSSL)) #undef CBUFFER_START #undef CBUFFER_END #define CBUFFER_START(name) cbuffer name { #define CBUFFER_END }; #endif #endif #include "UnityCG.cginc" : ENDHLSL |
としておくのが良さそうです。
しかし・・・、これで問題なく動くかなと思ってたんですが、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
の間でインクルードすることにしました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
// // EnableCbuffer.cginc // // Projector For LWRP // // Copyright 2020 NYAHOON GAMES PTE. LTD. All Rights Reserved. // #if !defined(P4LWRP_ENABLECBUFFER_CGINC_DEFINED) #define P4LWRP_ENABLECBUFFER_CGINC_DEFINED // Min version required for SRP Batcher #if defined(SHADER_API_PSSL) || defined(SHADER_API_D3D11) #define SRP_BATCHER_COMPATIBLE_VERSION 201820 #elif defined(SHADER_API_METAL) || defined(SHADER_API_VULKAN) || defined(SHADER_API_SWITCH) #define SRP_BATCHER_COMPATIBLE_VERSION 201830 #elif defined(SHADER_API_GLES) // not supported #define SRP_BATCHER_COMPATIBLE_VERSION 999999 #else #define SRP_BATCHER_COMPATIBLE_VERSION 201920 #endif // enable CBUFFER macros for SRP Batcher #if 201930 <= UNITY_VERSION #define UNITY_ENABLE_CBUFFER #elif SRP_BATCHER_COMPATIBLE_VERSION <= UNITY_VERSION #include "HLSLSupport.cginc" #if !(defined(SHADER_API_D3D11) || defined(SHADER_API_PSSL)) #undef CBUFFER_START #undef CBUFFER_END #define CBUFFER_START(name) cbuffer name { #define CBUFFER_END }; #endif #endif #endif // !defined(P4LWRP_ENABLECBUFFER_CGINC_DEFINED) |
紆余曲折あって最終的にこの形になり、これでほとんどのシェーダーを 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 を使いたいので、この解決方法も説明したいと思いますが、ここまででだいぶ長くなったので、また別の記事で書くことにします。