本文是学习 https://blog.csdn.net/lzhq1982/article/details/88119283 这篇文章,再自己重新记录印象深刻
创建一个空场景,添加一个gameObject挂上脚本
public class TestInstancing : MonoBehaviour
{
public Transform prefab;
public int instances = 5000;
public float radius = 50f;
void Start()
{
for (int i = 0; i < instances; i++)
{
Transform t = Instantiate(prefab);
t.localPosition = Random.insideUnitSphere * radius;
t.SetParent(transform);
}
}
}
再放一个sphere进来,把spherer赋值给脚本的prefab成员。
然后创建一个有颜色参数的shader,作为材质传给prefab球
Shader "Unlit/NewUnlitShader"
{
Properties
{
_Color("Color", Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType" = "Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
float4 _Color;
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return _Color;
}
ENDCG
}
}
}
运行如下
可以看到DC很高,unity几乎无法为我们自动合批(因为球体的顶点数太高,换成cube才有可能合批)
然后我们在shader中加上instancing开关再试下。(通过这种方式,不用Graphics.DrawMeshInstanced也可以)
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_instancing
此时材质的属性面板会多一个勾选
勾上之后,场景中的球马上变少了
DC也变成了只有15次,可以看出来引擎帮我们合批了
所有5000个球体仍在渲染,只是同一渲染批次中的所有球体都在同一位置。他们都使用渲染批次中第一个球体的转换矩阵。 这是因为一个渲染批次中所有球体的矩阵现在作为数组发送到图形处理器。 而且没有告诉着色器使用哪个数组索引,所以着色器总是使用第一个。
因此我们必须告诉着色器instanceId
struct appdata
{
UNITY_VERTEX_INPUT_INSTANCE_ID
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
UNITY_VERTEX_INPUT_INSTANCE_ID
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
v2f vert (appdata v)
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_TRANSFER_INSTANCE_ID(v, o);
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
UNITY_VERTEX_INPUT_INSTANCE_ID宏的定义如下
#define UNITY_VERTEX_INPUT_INSTANCE_ID uint instanceID : SV_InstanceID;
UNITY_SETUP_INSTANCE_ID宏是做一些初始化的操作,可以查阅内置shader
UNITY_TRANSFER_INSTANCE_ID是赋值给v2f结构
加上这几句后,又变成了几千个球但DC降低了,效果如下:
按理说这里顶点变换函数应该写成如下这个样子
o.vertex = UnityObjectToClipPos(v.vertex, v.instanceId)
这样才能取得正确的矩阵,但Unity为了兼容老代码。偷偷地把UnityObjectToClipPos用宏替换给换了
代码如下:
inline float4 UnityObjectToClipPosInstanced(in float3 pos)
{
return mul(UNITY_MATRIX_VP, mul(unity_ObjectToWorldArray[unity_InstanceID], float4(pos, 1.0)));
}
这是很丑的方式。
然后我们把每个球赋值成不同的颜色,因为不能每个球都创建一个材质,所以我们用MaterialPropertyBlock来设置代码如下
void Start()
{
MaterialPropertyBlock block = new MaterialPropertyBlock();
for (int i = 0; i < instances; i++)
{
Transform t = Instantiate(prefab);
t.localPosition = Random.insideUnitSphere * radius;
t.SetParent(transform);
block.SetColor("_Color", new Color(Random.Range(0f, 1.0f), Random.Range(0f, 1.0f), Random.Range(0f, 1.0f)));
MeshRenderer renderer = t.GetComponent<MeshRenderer>();
renderer.SetPropertyBlock(block);
}
}
可发现drawCall又回去了
因为渲染器发现用instanceId并不能区分MaterialPropertyBlock中的颜色,因此它就当有5000个不同的材质了,又不合批了。
所以我们需要手动的把_Color也数组化。可以用instanceId来访问
加入
UNITY_INSTANCING_CBUFFER_START(InstanceProperties)
UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
UNITY_INSTANCING_CBUFFER_END
其中InstanceProperties没有什么意义
这三行定义了一个大小为500的color数组,
最后改frag
fixed4 frag (v2f i) : SV_Target
{
return UNITY_ACCESS_INSTANCED_PROP(_Color);
}