【Unity】SRP Batcherまとめ - Dynamic BachingやGPUインスタンシングとの違い~シェーダの書き方まで

UnityのSRP Batcherについてまとめました。

Unity2021.1.11f1

SRP Batcherとは?

SRP Batcherはシェーダ(正確にはシェーダキーワード)が同じもの多数描画する際に効率的に描画を行うことができる機能です。
メッシュやマテリアルが違っていても、シェーダキーワードが同一で描画順が連続していれば動作します。
GPUにマテリアルの情報をキャッシュすることで実現される仕組みになっていて、
Dynamic Bachingとは異なりドローコールは描画する数だけ発行されますが、セットパスコールがまとめられるという挙動になります。

SRP Batcherの動作要件は以下の通りです。

  • Scriptable Render Pipelineでのみ使える(ビルトインパイプラインでは使えない)
  • メッシュかスキンメッシュのみで、パーティクルには非対応
  • シェーダが対応している必要がある
  • ほとんどすべてのプラットフォームで動作する

Dynamic Baching・GPUインスタンシングとの違い

SRP Batcherと目的が似ている機能として、Dynamic BatchingとGPUインスタンシングが存在します。

Dynamic Batching

まずDynamic Batchingは同じマテリアルを適用する複数のメッシュをリアルタイムで結合する処理です。
これによりドローコールやセットパスコールが1回で済み、この部分については処理負荷の軽減につながります。

しかしながらメッシュの結合処理自体に安くはないCPU負荷がかかるというデメリットがあります。

また適用条件が厳しい(頂点属性900以下かつ頂点数300以下のメッシュ)ため、使えるケースが限られます。
現状のUnityでは、組み込みパイプラインとSRPのどちらもデフォルトでDynamic Batchingはオフになっています。

GPUインスタンシング

GPUインスタンシングもDynamic Batchingと同じく、マテリアルが一緒のものを一気に描画できる機能です。
ただしDynamic Batchingのようなメッシュの結合は不要で、GPUで効率的な処理を行います。

また色違いなど、プロパティが異なるケースにも対応可能です。
ただしシェーダはGPUインスタンシングに対応している必要があります。
またメッシュは同一のものである必要があります。

SRP Batcherの使い方

SRP Batcherを使うにはまずレンダリングパイプラインをSRPを使ったものにする必要があります。
これに関しては以下のUniversal RPについての記事を参考にしてください。

light11.hatenadiary.com

次にScriptable Render Pipeline Asset(Universal Render Pipeline Asset)のSRP BatcherのチェックボックスがONになっていることを確認します。
デフォルトではこの項目はONになっています。

f:id:halya_11:20210711230652p:plain
SRP Batcher

次にUniversal Render Pipeline/Litシェーダを使ったマテリアルを複数用意します。
そして異なるメッシュを持つレンダラにそれぞれをアサインします。

f:id:halya_11:20210711231219p:plain
オブジェクトを作成

この状態で再生し、FrameDebuggerでレンダリング順を確認します。

f:id:halya_11:20210711231451p:plain
Frame Debugger

3つのドローコールが一つのセットパスコールにまとめられていることが確認できました。

シェーダの書き方

さて次にSBP Batcherに対応したシェーダを書いてみます。
まずシンプルなSRPのUnlitシェーダから始めます。

Shader "Example"
{
    SubShader
    {
        Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalRenderPipeline" }

        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"            

            struct Attributes
            {
                float4 positionOS   : POSITION;                 
            };

            struct Varyings
            {
                float4 positionHCS  : SV_POSITION;
            };            

            Varyings vert(Attributes IN)
            {
                Varyings OUT;
                OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
                return OUT;
            }
     
            half4 frag() : SV_Target
            {
                return half4(1, 0, 0, 1);
            }
            ENDHLSL
        }
    }
}

上記のようにプロパティの存在しないシェーダはこの状態で普通にSRP Batchingされます。
このシェーダにプロパティを追加する際には、バッチングのために以下のコメント部分のようにCBUFFERの記述が必要になります。

Shader "Example"
{
    Properties
    { 
        _BaseColor("Base Color", Color) = (1, 1, 1, 1)
    }
    SubShader
    {
        Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalRenderPipeline" }

        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"            

            struct Attributes
            {
                float4 positionOS   : POSITION;                 
            };

            struct Varyings
            {
                float4 positionHCS  : SV_POSITION;
            };

            // SBP Batcherに対応するため、マテリアルに紐づくプロパティは全てこのブロック内に記述する必要がある
            CBUFFER_START(UnityPerMaterial)
                half4 _BaseColor;            
            CBUFFER_END

            Varyings vert(Attributes IN)
            {
                Varyings OUT;
                OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
                return OUT;
            }
     
            half4 frag() : SV_Target
            {
                // BaseColorを返す
                return _BaseColor;
            }
            ENDHLSL
        }
    }
}

このようにSRP Batcherに対応するには、マテリアルに紐づくプロパティを全てUnityPerMaterialという名前のCBUFFERで宣言する必要があります。

また、組み込みのプロパティをUnityPerDrawという名前のCBUFFERで宣言する必要もありますが、
これは基本的にIncludeされているファイル内で行われているので今回は対応する必要はありません。
CGINCLUDEを使っている場合には以下のように注意が必要そうですが、HLSLPROGRAMを使う分には問題ないかと思われます。

nyahoon.com

関連

light11.hatenadiary.com

light11.hatenadiary.com

参考

blog.unity.com

docs.unity3d.com

docs.unity3d.com