【Unity / SRP Batcher】CBUFFER外パラメータによる描画崩れ
はじめに
SRP Batcherを利用している場合、
CBUFFER_START(UnityPerMaterial)
~ CBUFFER_END
で囲った変数は、
マテリアル固有のパラメータとして扱われます。
CBUFFERの外にあるパラメータはグローバルなものとして扱われます。
half4 _Color; // グローバルなパラメータ
CBUFFER_START(UnityPerMaterial)
float4 _MainTex_ST; // マテリアル固有のパラメータ
CBUFFER_END
この時、マテリアルに対して上記のグローバルパラメータをセットすると、
他のマテリアルの描画に影響を与えてしまいます。
material.SetColor("_Color", color); // 他のマテリアルにも影響を与える
今回の記事では、こちらについて掘り下げていきたいと思います。
環境
【UnityEditor】
Unity6000.1.6f1
Universal RP 17.1.0 (RenderGraphは有効化)
【iOS】
XCode 16.3 (16E140)
iPhoneX (iOS)
【Android】
RenderDoc v1.38
Samsung Galaxy S9 (SM-G960F/DS)
GraphicsAPI : Vulkan
Unityプロジェクト
今回の検証に使用したUnityプロジェクトは、以下のGitHubに置いています。
Chapter1. 不具合を確認する
まず、material.SetColor
によって他のマテリアルの描画に影響を与えてしまう状況を再現してみたいと思います。
シェーダーの作成
以下のシェーダーを使用します。
Shader "Unlit/NewUnlitShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
half4 _Color;
CBUFFER_START(UnityPerMaterial)
float4 _MainTex_ST;
CBUFFER_END
v2f vert (appdata v)
{
v2f o;
o.vertex = TransformObjectToHClip(float4(v.vertex.xyz, 1.0));
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
half4 frag (v2f i) : SV_Target
{
half4 col = tex2D(_MainTex, i.uv) * _Color;
return col;
}
ENDHLSL
}
}
}
マテリアルの作成
2つのマテリアルA, Bを作成します。
A -> Bという順番で描画されたいので、RenderQueueは
A = 2000
B = 2001
としてみました。
マテリアルの適用
オブジェクトを2つ用意し、マテリアル Aと Bを適用します。
コンポーネントの作成
マテリアルに対して、色を設定するようなコンポーネントを作成します。
using UnityEngine;
public class NewMonoBehaviourScript : MonoBehaviour
{
[SerializeField] private Color _color = new Color(1, 1, 1, 1);
private Material _duplicatedMaterial;
private void Start()
{
_duplicatedMaterial = GetComponent<Renderer>().material; // マテリアルが複製される
}
private void Update()
{
_duplicatedMaterial.SetColor("_Color", _color);
}
}
コンポーネントの追加
先ほど作成したオブジェクトに、上記のコンポーネントをアタッチします。
片方はColorを赤に、もう片方は青に設定してみました。
Unityを再生
Unityを再生すると、両方のSphereが赤くなりました。
material.SetColorで設定した色が、他のマテリアルの描画でも利用されてしまっています。
RenderQueueを変えてみる
マテリアルのRenderQueueを変更してみましょう。
A = 2002
B = 2001
とします。
B -> Aの順番で描画されるようになります。
すると、Bの色(青)がAの描画にも利用されるようになりました。
最初に描画されたマテリアルの_Color
が、他のマテリアルの描画にも利用されていることが確認できました。
Chapter1 まとめ
-
SRP Batcherを使用する際は、マテリアル固有のパラメータは必ずCBUFFER_START(UnityPerMaterial)内で定義する必要がある
-
CBUFFER外で定義したパラメータは、すべてのマテリアルで共有されてしまう
Chapter2. Xcodeで描画の中身を見てみる (iOS)
シーンをiOS向けにビルドし、iPhoneXで動かし、
GPU System Traceをキャプチャしてドローコールを確認してみる事にします。
RenderLoop.DrawSRPBatcherというドローコールが確認できます。
ここで3Dオブジェクトの描画を行なっているようです。
drawIndexedPrimitivesによって描画が実行されます。
リソースの確認 (UnityPerMaterial)
ここで、drawIndexedPrimitivesにバインドされているリソースを確認してみましょう。
Compute_8
というリソースが、VertexのUnityPerMaterial
としてバインドされていることがわかります。
Compute_8
の中を見ると、_MainTex_STが入っていることが確認できます。
UnityPerMaterial
というCBUFFERはCompute_***
というリソースでメモリ上に確保され、
これがFragmentのUnityPerMaterial
パラメータとしてバインドされるようです。
CBUFFER_START(UnityPerMaterial)
float4 _MainTex_ST;
CBUFFER_END
リソースの確認 (FGlobals)
また、ScratchBuffer0_2
というリソースがあり、これがFGlobals
パラメータとして、Fragmentのリソースにバインドされていることもわかります。
ScratchBuffer0_2
の中を見ると、_Color
が入っていることが確認できます。
_Color
はCBUFFERの中に定義していないため、グローバルなバッファの中に入ります。
half4 _Color; // ScratchBuffer0_2に格納される
CBUFFER_START(UnityPerMaterial)
float4 _MainTex_ST;
CBUFFER_END
コマンドの確認
コマンド一覧ビューに戻ると、Compute_8
やCompute_9
を設定しているAPIコール
setVertexBuffersが確認できます。
プリミティブの描画を呼ぶたびに、UnityPerMaterialを更新しているようです
それに対して、ScratchBuffer0_2 (グローバルなバッファ)の更新処理は1度しか呼ばれていません。
Chapter2. まとめ
描画コマンド
-
RenderLoop.DrawSRPBatcher
というドローコールで3Dオブジェクトの描画を実行 -
drawIndexedPrimitives
を使用してプリミティブの描画を実施
リソース
-
UnityPerMaterial:
-
Compute_8
のようなリソースとしてメモリ上に確保される - シェーダー内の
UnityPerMaterial
CBUFFERに対応 - 例:
_MainTex_ST
などのマテリアル固有のパラメータを格納
-
-
FGlobals:
-
ScratchBuffer0_2
のようなリソースとしてメモリ上に確保される - Fragmentsからアクセスするグローバルなシェーダーのパラメータは、この中に入る
- CBUFFERに定義されていないパラメータは、この中に格納される
-
half4 _Color; // ScratchBuffer0_2 のようなバッファに格納される
CBUFFER_START(UnityPerMaterial)
float4 _MainTex_ST; // Compute_8 のようなバッファ に格納される
CBUFFER_END
Chapter3. RenderDocで描画の中身を見てみる (Android)
UnityをAndroid向けにビルドし、RenderDocでキャプチャしてみます。
端末は Galaxy S9 を使用しました。
Event Browser
Event Browserを確認してみます。
RenderLoop.DrawSRPBatcherというイベントがあります。
ここで3Dオブジェクトの描画を実行しています。
Pipeline State
オブジェクト描画時のパイプラインステートを確認してみます。
FS (Fragment Stage)
FS を確認してみましょう。
1つ目の球と2つ目の球のどちらを描画する時も、Scratchbuffer page というバッファがバインドされており、
Byte Rangeが同じになっています。
Scratchbuffer pageの中身のデータを見ると、_Color が入っていることも確認できます。
half4 _Color; // Scratchbuffer page のようなバッファに格納される
CBUFFER_START(UnityPerMaterial)
float4 _MainTex_ST; // Buffer1620 のようなバッファ に格納される
CBUFFER_END
VS (Vertex Stage)
Pipeline State の VS を確認してみましょう。
1つ目と2つ目のオブジェクト描画時にバインドされているバッファが異なるものになっています。
Bufferの中を見ると、CBUFFER内部のパラメータが格納されていることが確認できます。
half4 _Color;
CBUFFER_START(UnityPerMaterial)
float4 _MainTex_ST; // Buffer1620 や Buffer1621 に格納されている
CBUFFER_END
Chapter3. まとめ
-
UnityPerMaterial:
-
Buffer1620
のようなリソースとしてメモリ上に確保される - シェーダー内の
UnityPerMaterial
CBUFFERに対応
-
- CBUFFER外パラメータ:
-
Scratchbuffers page
のようなリソースとしてメモリ上に確保される - CBUFFER内に定義されていないパラメータは、この中に格納される
-
half4 _Color; // `Scratchbuffers page`のようなリソースとしてメモリ上に確保される
CBUFFER_START(UnityPerMaterial)
float4 _MainTex_ST; // `Buffer1620` のようなリソースとしてメモリ上に確保される
CBUFFER_END
補足 : CBUFFER外パラメータは、異なるシェーダーバリアント間では共有されない
CBUFFER外で定義したシェーダーパラメータは、異なるシェーダーバリアントを利用するマテリアルでは共有されません。
シェーダーキーワードによる分岐を追加することで確認できます。
Properties
{
_MainTex ("Texture", 2D) = "white" {}
+ [Toggle(_A)] _IsA("A", Int) = 0
}
...
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
+ #pragma shader_feature _A
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
記事まとめ
SRP Batcherを利用している場合、
- マテリアル固有のパラメータは必ずCBUFFER_START(UnityPerMaterial)内で定義する必要がある
- CBUFFER外で定義したパラメータは、同一のシェーダーバリアントを利用する他のマテリアルと共有される
関連記事
Discussion