DrawCall Batching
DrawCall Batching
Unityは描画効率のために、複数のRendererの描画を一つに束ねることがあります。 これをバッチングといい、 どのRendererの描画をまとめるかを事前に指定する方式をstatic batching Materialの状況をみてUnityがよしなに描画を束ねるかどうかを自動で判断するのをdynamic batchingといいます。 バッチングのON/OFFはPlayer Settingsで指定でき、デフォルトはONです。
dyanmic batchingの条件
dynamic batchingの効く条件は、以下2点を満たすことです
- 同一のマテリアルを使用していること
- Material Property Blockの値が一致していること
あ、もちろん同一のCameraで映してくださいね。
実験
どシンプルなSprite風シェーダで実験です。 このシェーダは画像と乗算カラーのみを指定できます。
Shader "Hoge/OreOreSprite" { Properties { _MainTex ("Texture", 2D) = "white" {} _Color("Tint", Color) = (1,1,1,1) } SubShader { Cull Off ZWrite Off ZTest Always Blend One OneMinusSrcAlpha Tags { "Queue" = "Transparent" "PreviewType" = "Plane" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" float4 _Color; struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; float4 color : COLOR; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; float4 color : COLOR; }; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; o.color = v.color * _Color; return o; } sampler2D _MainTex; fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv) * i.color; return col; } ENDCG } } }
作ったShaderにunity-chanの画像でもぶっ挿して乗算カラーも指定しておきます。
このMaterialを使用して適当にQuadメッシュを二つ描画してみます。 FrameDebuggerでみると、ちゃんとバッチが効いて1Drawで2つの描画が束ねられています。
ここで、2体目のUnity-ChanのMaterialを差し替えてみましょう。 元のMaterialをコピーして、同じシェーダ、同じテクスチャ、同じ乗算カラーのMaterialを作成します。 中身は一緒だけどガワが違うとでもいいましょうか。
描画は分かれてしまいました。 中身がどんなに等しくても、Materialが別だとバッチングは効かないことがわかります。
Material Property Block
Unityには、同一のMaterialを使用しつつも、Renderer単位でパラメータを変える仕組みとして
Material Property Block
が存在します。
すんごい雑にStartでMaterialPropertyBlockをつっこむコンポーネントを書いてみます。
using UnityEngine; [RequireComponent(typeof(MeshRenderer))] public class OreOreSprite : MonoBehaviour { [SerializeField] private Texture _texture = null; [SerializeField] private Color _color; private MeshRenderer _renderer = null; public MeshRenderer Renderer { get { return _renderer != null ? _renderer : (_renderer = GetComponent<MeshRenderer>()); } } private MaterialPropertyBlock _block = null; void Start () { _block = new MaterialPropertyBlock(); _block.SetTexture("_MainTex", _texture); _block.SetColor("_Color", _color); this.Renderer.SetPropertyBlock(_block); } }
インスペクタでテクスチャとカラーを指定可能
これで、同一のMaterialを使いながらもパラメータを分けることができるようになりました。 同一のマテリアルを使用していてもパラメータが異なることによりバッチングは効いていません。
この状態で、Inspectorから同じ画像、同じ色を指定してみましょう。(Materialは同一のものを使用するとします)
無事描画が一つにまとまりました。
まとめ
dynamic batchingの効く条件は、以下2点を満たすことです
- 同一のマテリアルを使用していること
- Material Property Blockの値が一致していること
今回はMeshRendererを使用しましたが、SpriteRendererやCanvasあたりも基本的には同じです。 (Canvasはdyanmic batchingというよりはCanvas独自バッチングになるのでむしろdynamic batchingからははずれますが、Materialが分かれたりパラメータがばらけるとバッチされないという点では一緒) MaterialPropertyBlockによって、同一Materialを使いまわしながらも、パラメータを変更できるようにし、 MaterialPropertyBlockのパラメータが(たまたまor狙って)一致した場合は描画が束ねられる、という形をとっています。 なおuGUIの場合はCanvasRendererがMaterialPropertyBlockに近い役割を担っています。
テクスチャなんて一致するのかよ、という点については、 複数の画像を一つにパッキングしておけば束ねられます。
こうやってパックされたテクスチャを使って、Spriteによる範囲指定で抜けば、画像内のキャラを何体書いても同一Materialを使う限り1DrawCallで済みます。 キャラクターは画像が大きいことが多いので例として良くないですが、 小さい画像の多いUIではパッキングは効果があります。
余談:PerRendererDataについて
MaterialPropertyBlockやCanvasRendererによって、後から画像を挿す場合Materialに挿してある画像は無駄です。 消し忘れると、MaterialをLoadしただけで使う予定のない画像までついてくる。許せん、となります。 そのような想定の画像はshader側で [PerRendererData] 指定をしておくと良いです。
Shader "Hoge/OreOreSprite" { Properties { [PerRendererData] _MainTex ("Texture", 2D) = "white" {} _Color("Tint", Color) = (1,1,1,1) }
これを書くと、Materialのインスペクタから _MainTexを指す箇所が消えるので事故防止になります。
© UTJ/UCL