- Home /
Setting MaterialPropertyBlocks breaks batching
I have a prefab with the CircleCollider and following script attached:
using UnityEngine;
using System.Collections;
public class ShakeMaterialOnTrigger : MonoBehaviour {
public float shakeTime = 0.2f;
public float amplitude = 0.1f;
public int oscillations = 3;
private static int offsetID = -1;
void Awake () {
if (offsetID == -1) offsetID = Shader.PropertyToID("_Offset");
}
void OnTriggerEnter2D (Collider2D collider) {
StartCoroutine("Shake");
}
IEnumerator Shake () {
float t = 0;
while (t < shakeTime) {
t += Time.deltaTime;
float frac = Mathf.Clamp01(t / shakeTime);
float x = Mathf.Sin(frac * oscillations * 2 * Mathf.PI) * amplitude * (1-frac);
SetOffset(new Vector4(x,0,0,1));
yield return new WaitForEndOfFrame();
}
SetOffset(new Vector4(0,0,0,1));
}
public void SetOffset (Vector4 offset) {
MaterialPropertyBlock props = new MaterialPropertyBlock();
renderer.GetPropertyBlock(props);
props.AddVector(offsetID, offset);
renderer.SetPropertyBlock(props);
}
}
It also has a material using the following shader:
Shader "Custom/Offset Verts" {
Properties {
[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
_Offset ("Vertex Offset (X, Y, Angle, Size)", Vector) = (0,0,0,1)
}
SubShader {
Tags {
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
"PreviewType"="Plane"
"CanUseSpriteAtlas"="True"
}
Cull Off
Lighting Off
ZWrite Off
Fog { Mode Off }
Blend SrcAlpha OneMinusSrcAlpha
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata_t {
float4 vertex : POSITION;
float4 color : COLOR;
float2 texcoord : TEXCOORD0;
};
struct v2f {
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
half2 texcoord : TEXCOORD0;
};
float4 _Offset;
v2f vert(appdata_t IN) {
v2f OUT;
OUT.vertex = mul(UNITY_MATRIX_MVP, IN.vertex);
float ang = radians(_Offset.z);
float2 pos = OUT.vertex.xy * _Offset.w;
pos = float2(pos.x*cos(ang) - pos.y*sin(ang), pos.x*sin(ang) + pos.y*cos(ang));
pos += _Offset.xy;
OUT.vertex.xy = pos;
OUT.texcoord = IN.texcoord;
OUT.color = IN.color;
return OUT;
}
sampler2D _MainTex;
fixed4 frag(v2f IN) : SV_Target {
fixed4 c = tex2D(_MainTex, IN.texcoord) * IN.color;
return c;
}
ENDCG
}
}
}
There are a few hundred of these in the scene. They're all marked as static. When I hit play, the draw call sits at 2 (one for the character, one for all of the grass), saved by batching is at a few hundred. The effect I'm looking for is there (grass shakes as you walk through it), but each time another object gets triggered, draw calls go up, batch count goes down. Am I misunderstanding something about how MaterialPropertyBlocks work? For completeness, I've also tried clearing the block before setting it.
I am also attemping to use $$anonymous$$PB to achieve lower draw calls while using different colors.
Same issue here, am trying to render multiple objects with the same $$anonymous$$aterial- and $$anonymous$$esh-Instance, and also with the same $$anonymous$$aterialPropertyBlock (but different values with the $$anonymous$$aterialPropertyBlock), but it seems to break batching.
But my understand of the $$anonymous$$aterialPropertyBlock is that is exactly made for that use, so changing material properties without breaking the batching.
Wonder if I miss something?!
Answer by Bunny83 · May 26, 2016 at 11:11 AM
Whenever you change any global shader properties the objects can't batch. See this forum thread for more details.
Batching means to combined the geometry of multiple objects before it's send to the GPU and render it as one object. Of course all shader globals need to be the same as they are not part of the streaming vertex data. So whenever you change any material property, no matter how, the objects won't batch unless they have the same properties.
The MaterialPropertyBlock is ment for situations where you have a lot different instances with parameters that change a lot. So you don't have to duplicate the materials but you dynamically set the shader globals with the same MaterialPropertyBlock. It's not ment to save drawcalls but to save overhead.
The way you use the MaterialPropertyBlock however is extremely counter productive since you create a new MaterialPropertyBlock each time and let it be GCed each frame during your shake.
Thank you Bunny83 for the clarification. Seems I was wrong about the $$anonymous$$aterialPropertyBlock's intention.
Looks like I need to wait for the GPU Instancing feature of Unity 5.4 in my case and hope that it is flexible enough for my usecase - I want to draw the same mesh but with different textures and computebuffers assigned (which right now results in thousands of draw calls).
Thank you for the clarification.
Answer by Shadowphax · Nov 05, 2018 at 10:17 AM
Quite old but I think the problem is that you don't have [PerRendererData] on the offset property, so Unity will just create a new instance anyway.
This is a good blog post on the material property block.