Unity在5.4 Beta版本中引入了一种新的Draw Call Batching方式——GPU Instancing。当场景中有大量使用相同材质和网格的物体时,通过GPU Instancing可以大幅降低Draw Call数量。本文将由Unity官方开发工程师蔡元星,为大家简单介绍GPU Instancing的原理并引导读者修改已有的Shader来开启Instancing。
什么是GPU InstancingGPU Instancing是指由GPU和图形API支持的,用一个Draw Call同时绘制多个Geometry相同的物体的技术。
Instancing的应用场景 Instancing并不是总能提高性能,所以有必要明白Instancing技术可以做什么、不能做什么。
Instancing能做什么:
-
通过减少Draw Call数量来降低CPU开销。
Instancing不能做什么:
-
减少GPU的负载。实际上,Instancing还会在GPU上带来一些额外的开销。
-
有大量使用相同材质和相同网格的物体
-
性能受制于过多的Draw Call (图形驱动在CPU上负载过大)
在Unity 5.4中使用Instancing 在Unity 5.4中使用Instancing需要注意:
-
类似于Static / Dynamic Batching,Instancing是一种新的合并Draw Call的方式
-
适用于MeshRenderer组件和Graphics.DrawMesh()
-
需要使用相同的Material和Mesh
-
需要把Shader改成Instanced的版本
-
当所有前提条件都满足时,Instancing是自动进行的,并且比Static/Dynamic Batching有更高的优先级
Instancing的实现步骤如下:
-
将Per-Instance Data(世界矩阵、颜色等自定义属性)打包成Uniform Array,存储在Instance Constant Buffers中
-
对于可以使用Instancing的Batch,调用各平台图形API的Instanced Draw Call,这样会为每一个Instance生成一个不同的SV_InstanceID
-
在Shader中使用SV_InstanceID作为Uniform Array的索引获取当前Instance的Per-Instance Data
“multi_compile_instancing”会使你的Shader生成两个Variant,其中一个定义了Shader关键字INSTANCING_ON,另外一个没有定义此关键字。 除了这个#pragma指令,下面所列其他的修改都是使用了在UnityInstancing.cginc里定义的宏(此cginc文件位于Unity_Install_Dir\Editor\Data\CGIncludes)。取决于关键字INSTANCING_ON是否被定义,这些宏将展开为不同的代码。 UNITY_INSTANCE_ID
用于在Vertex Shader输入 / 输出结构中定义一个语义为SV_InstanceID的元素。 UNITY_INSTANCING_CBUFFER_START(name) / UNITY_INSTANCING_CBUFFER_END
每个Instance独有的属性必须定义在一个遵循特殊命名规则的Constant Buffer中。使用这对宏来定义这些Constant Buffer。“name”参数可以是任意字符串。 UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
定义一个具有特定类型和名字的每个Instance独有的Shader属性。这个宏实际会定义一个Uniform数组。 UNITY_SETUP_INSTANCE_ID(v)
这个宏必须在Vertex Shader的最开始调用,如果你需要在Fragment Shader里访问Instanced属性,则需要在Fragment Shader的开始也用一下。这个宏的目的在于让Instance ID在Shader函数里也能够被访问到。 UNITY_TRANSFER_INSTANCE_ID(v, o)
在Vertex Shader中把Instance ID从输入结构拷贝至输出结构中。只有当你需要在Fragment Shader中访问每个Instance独有的属性时才需要写这个宏。 UNITY_ACCESS_INSTANCED_PROP(_Color)
访问每个Instance独有的属性。这个宏会使用Instance ID作为索引到Uniform数组中去取当前Instance对应的数据。 最后我们需要提一下UnityObjectToClipPos:
在写Instanced Shader时,通常情况下你并不用在意顶点空间转换,因为所有内建的矩阵名字在Instanced Shader中都是被重定义过的。比如unity_ObjectToWorld实际上会变成unity_ObjectToWorldArray[unity_InstanceID];UNITY_MATRIX_MVP会变成mul(UNITY_MATRIX_VP, unity_ObjectToWorldArray[unity_InstanceID])。注意到如果直接使用UNITY_MATRIX_MVP,我们会引入一个额外的矩阵乘法运算,所以推荐使用UnityObjectToClipPos / UnityObjectToViewPos函数,它们会把这一次额外的矩阵乘法优化为向量-矩阵乘法。 2 Surface Shader 如果想把一个Surface Shader改写成支持Instancing的版本,你只需要加上“#pragma multi_compile_instancing” 就可以了。设置Instance ID的代码会自动生成。定义或访问每个Instance独有属性的方法同Custom Vertex / Fragment Shader。 另外,你可以在Project窗口右键单击,选择Create->Shader->Standard Surface Shader (Instanced)来创建一个示例Shader。
使用Instancing的限制 下列情况不能使用Instancing:
-
使用Lightmap的物体
-
受不同Light Probe / Reflection Probe影响的物体
-
使用包含多个Pass的Shader的物体,只有第一个Pass可以Instancing
-
前向渲染时,受多个光源影响的物体只有Base Pass可以instancing,Add Passes不行
最后需要再次强调的是,Instancing在Shader上有额外的开销,并不是总能提高帧率。永远要以实际Profiling的结果为准!
摘自:http://mp.weixin.qq.com/s?__biz=MjM5NjE1MTkwMg==&mid=2651036819&idx=1&sn=f47686dba250bdf708c2e1d0b1ac8162&scene=23&srcid=0513pnXKHTE7zvzfeTV31cUo#rd