🐹

SRP Batcherについてまとめる

2023/11/19に公開

SRP Batcherとは

・DrawCall毎にCBuffer(Constant Buffer)を更新せずに更新が必要な値のみアップロードを行うことでアップロード済みのバッファを使用することができるという仕組み
・同じシェーダを使用している場合に別マテリアルに分けても同一SetPassで描画することができ、SetPassCallを減らすことが可能になる
※GPUへのアップロード頻度を減らせるだけであり、DrawCall自体を減らせるわけでない

似たような機能でGPUIntancingという同一マテリアルのメッシュをまとめて1回のDrawCallで描画できDrawCallを減らせる仕組みがあるが、SRP BatcherGPUInstancingと違いメッシュの結合を行わないためDrawCallは減らすことはできないがSetPassCallの頻度を減らすことをができる機能であるという違いがある

https://light11.hatenadiary.com/entry/2019/09/25/220517

SRP Batcher使用時の注意点

・シェーダーをSRPBatcherに対応させる必要がある
・MaterialPropertyBlocksを一緒に使用することはできない
・シェーダーバリアントをできるだけ少なくする必要がある
・GPUInstancingと併用することはできない
UnityCG.cgincをインクルードするとUnityPerDrawのCBUFFERに含まれるエンジン定数が5つのみになってしまい、SRP Batcherに対応させるために必要なエンジン定数が別のCBUFFERに含まれてしまうため扱いに注意する
https://nyahoon.com/blog/2288

SRP Batcherを使ってみる

環境

・Unity2022.3.13f
・URP14.0.9

1.シェーダーの用意

シンプルにテクスチャを貼り、色を加算合成するシェーダーを作成

Unlit_AddColor
Shader "Custom/UnlitAddColor"
{
	Properties
	{
		[MainTexture] _MainTex ("Texture", 2D) = "white" {}
		_AddColor ("Color", Color) = (0, 0, 0, 0)
	}

	SubShader
	{
		Tags
		{
			"RenderPipeline"="UniversalPipeline"
			"RenderType"="Opaque"
		}
		Pass
		{
			Name "Universal Forward"
			Tags
			{
				"LightMode" = "UniversalForward"
			}

			Cull Back
			ZTest LEqual
			ZWrite On

			HLSLPROGRAM
			#pragma target 4.5
			#pragma vertex vert
			#pragma fragment frag

			#pragma multi_compile_instancing
			#pragma multi_compile _ DOTS_INSTANCING_ON

			#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"


			struct appdata
			{
				float4 pos: POSITION;
				float2 uv : TEXCOORD0;
				UNITY_VERTEX_INPUT_INSTANCE_ID
			};

			struct v2f
			{
				float4 pos : SV_POSITION;
				float2 uv : TEXCOORD1;
				UNITY_VERTEX_INPUT_INSTANCE_ID
				UNITY_VERTEX_OUTPUT_STEREO
			};

			sampler2D _MainTex;
			float4 _MainTex_ST;
			half4 _AddColor;

			v2f vert(appdata i)
			{
				v2f o;
				UNITY_SETUP_INSTANCE_ID(i);
				UNITY_TRANSFER_INSTANCE_ID(i, o);
				VertexPositionInputs vInput = GetVertexPositionInputs(i.pos.xyz);
				o.pos = vInput.positionCS;
				o.uv = TRANSFORM_TEX(i.uv, _MainTex);
				return o;
			}

			half4 frag(v2f i) : SV_Target
			{
				UNITY_SETUP_INSTANCE_ID(i);
				half4 baseColor = tex2D(_MainTex, i.uv);
				baseColor.rgb += _AddColor.rgb;
				return baseColor;
			}
			ENDHLSL
		}
	}
}
**

作成したシェーダーを見てみると
SRP Batcherの項目にnot compatibleと表示されておりSRP Batcherが有効になっていないことが分かります

2.オブジェクトを配置・Materialを設定

オブジェクトを配置し、それぞれに1.で作成したシェーダーのマテリアルを設定する
・Cubeは青色
・Sphereは赤色
に加算合成させるようにしました

FrameDebuggerを見てみる

FrameDebuggerで見てみるとSRP Batcherによってバッチされていないことが分かります

3. シェーダーをSRP Batcherに対応させる

シェーダーのinspectorを見ると
Material property is found in another cbuffer than 'UnityPerMaterial' (_AddColor)
と表示されているので_AddColorのプロパティーをUnityPerMaterialというCBUFFERに入れればよいという事が分かります

Unlit_AddColor
Shader "Custom/UnlitAddColor"
{
	Properties
	{
		[MainTexture] _MainTex ("Texture", 2D) = "white" {}
		_AddColor ("Color", Color) = (0, 0, 0, 0)
	}

	SubShader
	{
		Tags
		{
			"RenderPipeline"="UniversalPipeline"
			"RenderType"="Opaque"
		}
		Pass
		{
			Name "Universal Forward"
			Tags
			{
				"LightMode" = "UniversalForward"
			}

			Cull Back
			ZTest LEqual
			ZWrite On

			HLSLPROGRAM
			#pragma target 4.5
			#pragma vertex vert
			#pragma fragment frag

			#pragma multi_compile_instancing
			#pragma multi_compile _ DOTS_INSTANCING_ON

			#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"


			struct appdata
			{
				float4 pos: POSITION;
				float2 uv : TEXCOORD0;
				UNITY_VERTEX_INPUT_INSTANCE_ID
			};

			struct v2f
			{
				float4 pos : SV_POSITION;
				float2 uv : TEXCOORD1;
				UNITY_VERTEX_INPUT_INSTANCE_ID
				UNITY_VERTEX_OUTPUT_STEREO
			};

			sampler2D _MainTex;
			float4 _MainTex_ST;
			CBUFFER_START(UnityPerMaterial)
			half4 _AddColor;
			CBUFFER_END

			v2f vert(appdata i)
			{
				v2f o;
				UNITY_SETUP_INSTANCE_ID(i);
				UNITY_TRANSFER_INSTANCE_ID(i, o);
				VertexPositionInputs vInput = GetVertexPositionInputs(i.pos.xyz);
				o.pos = vInput.positionCS;
				o.uv = TRANSFORM_TEX(i.uv, _MainTex);
				return o;
			}

			half4 frag(v2f i) : SV_Target
			{
				UNITY_SETUP_INSTANCE_ID(i);
				half4 baseColor = tex2D(_MainTex, i.uv);
				baseColor.rgb += _AddColor.rgb;
				return baseColor;
			}
			ENDHLSL
		}
	}
}

sampler2D _MainTex;
float4 _MainTex_ST;
CBUFFER_START(UnityPerMaterial)
half4 _AddColor;
CBUFFER_END

UnityPerMaterialのCBUFFERに入れるために_AddColorCBUFFER_START(UnityPerMaterial)CBUFFER_ENDで囲むだけです

_AddColorUnityPerMaterialに入れると次は
Material property is found in another cbuffer than 'UnityPerMaterial'(_MainTex_ST)
と表示されたのでプロパティ全てをUnityPerMaterailのCBUFFERに含める必要があるそうです

Unlit_AddColor
Shader "Custom/UnlitAddColor"
{
	Properties
	{
		[MainTexture] _MainTex ("Texture", 2D) = "white" {}
		_AddColor ("Color", Color) = (0, 0, 0, 0)
	}

	SubShader
	{
		Tags
		{
			"RenderPipeline"="UniversalPipeline"
			"RenderType"="Opaque"
		}
		Pass
		{
			Name "Universal Forward"
			Tags
			{
				"LightMode" = "UniversalForward"
			}

			Cull Back
			ZTest LEqual
			ZWrite On

			HLSLPROGRAM
			#pragma target 4.5
			#pragma vertex vert
			#pragma fragment frag

			#pragma multi_compile_instancing
			#pragma multi_compile _ DOTS_INSTANCING_ON

			#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"


			struct appdata
			{
				float4 pos: POSITION;
				float2 uv : TEXCOORD0;
				UNITY_VERTEX_INPUT_INSTANCE_ID
			};

			struct v2f
			{
				float4 pos : SV_POSITION;
				float2 uv : TEXCOORD1;
				UNITY_VERTEX_INPUT_INSTANCE_ID
				UNITY_VERTEX_OUTPUT_STEREO
			};

			sampler2D _MainTex;
			CBUFFER_START(UnityPerMaterial)
			float4 _MainTex_ST;
			half4 _AddColor;
			CBUFFER_END

			v2f vert(appdata i)
			{
				v2f o;
				UNITY_SETUP_INSTANCE_ID(i);
				UNITY_TRANSFER_INSTANCE_ID(i, o);
				VertexPositionInputs vInput = GetVertexPositionInputs(i.pos.xyz);
				o.pos = vInput.positionCS;
				o.uv = TRANSFORM_TEX(i.uv, _MainTex);
				return o;
			}

			half4 frag(v2f i) : SV_Target
			{
				UNITY_SETUP_INSTANCE_ID(i);
				half4 baseColor = tex2D(_MainTex, i.uv);
				baseColor.rgb += _AddColor.rgb;
				return baseColor;
			}
			ENDHLSL
		}
	}
}

sampler2D _MainTex;
CBUFFER_START(UnityPerMaterial)
float4 _MainTex_ST;
half4 _AddColor;
CBUFFER_END

_MainTex_STUnityPerMaterialに含めるとcompatibleとなってSRP Batcherに対応できたことが分かります

4. FrameDebuggerを見てみる

SRP BatchSRP Batcherによってバッチされた描画になります

Detailsの項目を見ると、
Meshsの項目に「Sphere」と「Cube」の項目がありSRP Batcherによってバッチされたことが分かります

DrawCallsは"2"となっているためドローコールは減ってはいません

5. Planeを追加してみる

Planeを追加してみてどうなるか見てみます。
(デフォルトで適用されるシェーダーはUniversal Render Pipeline/Litのようですね)

ここで、FrameDebuggerを見てみるとPlane分のSRP Batchが追加されていますね

Planeには Cube,Sphereとは別のシェーダーが割り当てられているためバッチされていないことが分かります

Discussion