一、多例化技术概述
假设需要绘制有很多模型的场景,而大部分模型使用的是同一个模型,即使用同一组顶点数据在渲染时会给它们指定不同的世界坐标,绘制在不同位置上。比如草,可能由几个三角形构成,渲染一株草没有渲染压力,但是成千上万株就会调用相应次数绘制调用(draw call)就会极大影响性能。
1.1 不使用GPU多例化技术绘制多个相同模型
如果这样绘制同一模型大量实例(instance),很快就会因为绘制调用过多而达到性能瓶颈,与GPU执行绘制执行绘制顶点的操作本身相比,准备模型的顶点数据和世界变换等用到的着色器的操作会消耗更多性能,因为这些操作都是在相对缓慢的CPU到GPU总线(bus)上进行的。因此即便GPU执行渲染顶点非常快,命令GPU去准备渲染的操作却不一定快。
如果能将数据一次性发送给GPU,然后使用一个绘制函数让渲染流水线利用这些数据绘制多个相同的物体将会大大提升性能。这种技术就是GPU多例化(GPU Instancing)技术。
使用GPU多例化技术,能够在一个绘制调用中渲染多个相同的物体。Direct3D和OpenGL等渲染流水线实作目前已经实现了这个功能,Unity3D引擎在此基础上进行包装,使得在每个平台上都能用同一套代码使用GPU多例化技术。
GPU多例化的思想,就是把每个实例的不同信息存储在缓冲区(可能是顶点缓冲区,可能是存储着色器uniform变量的常量缓冲区)中, 然后直接操作缓冲区中的数据来设置。
以上一小节为例,仅仅因为每次模型的世界变换矩阵不同,就需要调用代价昂贵的绘制调用。而其实每次的调用都是很相似的,在设置世界矩阵时,无非就是更新着色器常量缓冲区中的某个uniform变量。这里又是一次CPU到GPU的传递数据操作。既然可以在定义顶点信息结构体时指定每个顶点的法线、切线和纹理映射坐标等信息,那完全也可以为每个顶点增加一个描述世界矩阵的属性,无非就是在顶点信息结构体中多加一个float4X4类型的属性变量而已。
假设需要渲染100个相同的模型,每个模型有256个三角形,那么需要两个缓冲区,一个是用来描述模型的顶点信息,因为待渲染的模型是相同的,所以这个缓冲区只存储了256个三角形(如果不存在任何的优化组织方式,则有768个顶点);另一个就是用来描述模型在世界坐标下的位置信息。例如不考虑旋转和缩放,100个模型即占用100个float3类型的存储空间。
以Direct3D 11平台为例,当准备好顶点数据、设置好顶点缓冲区之后,接下来进入输入组装阶段。输入组装阶段是使用硬件实现的。此阶段根据用户输入的顶点缓冲区信息、图元拓扑结构信息和描述顶点布局格式信息,把顶点组装成图元,然后发送给顶点缓冲区。设置好组装的相关设置后,对应的顶点着色器和片元着色器也要做好对应的设置才能使用多例化技术。
下面我们看在Unity3D中如何包装在各平台下此技术的实现。
二、如何在材质中启用多例化技术
要在Unity3D中启用GPU多例化技术,首先应在材质文件的Inspector面板中选中Enable Instancing复选框:
注意,只有材质文件使用的着色器代码文件中声明了支持GPU多例化技术,材质文件的Inspector面板中才会出现Enable GPU Instancing复选框。引擎提供的Standard着色器、StandardSpecular着色器及所有的外观着色器都支持GPU多例化技术。
三、添加逐实例数据
每一次多例化绘制调用时,默认的仅对同一网格材质,但是有着不同的位置变换信息的游戏对象进行批次化。为了能让GPU多例化技术不仅应用在只有不同的位置变换信息的游戏对象,如位置变换信息相同,但材质颜色不同的游戏对象,也可以使用GPU多例化技术,可以在自定义着色器代码中添加逐多例化属性。
3.1 在surface shader中给材质颜色增加GPU多例化支持的代码
在定义的属性的时候,要加上:
UNITY_INSTANCING_BUFFER_START(Props)
UNITY_DEFINE_INSTANCED_PROP(fixed4,_Color)
UNITY_INSTANCING_BUFFER_END(Props)
然后在surf函数中我对原本的_Color属性进行计算操作时,换成这种写法:
fixed4 c=tex2D(_MainTex,IN.uv_MainTex)*UNITY_ACCESS_INSTANCED_PROP(Props,_Color);
o.Albedo=c.rgb;
上面代码使用UNITY_INSTANCING_BUFFER_START宏宣告要使用GPU多例化技术的变量,使用UNITY_DEFINE_INSTANCED_PROP宏声明_Color变量使用技术,使用UNITY_INSTANCING_BUFFER_END宏结束使用技术的宣告。
3.2 在C#层改变game object中的多例化材质颜色属性
在C#代码段中,通过使用MaterialPropertyBlock类设置每一个多例化实例的不同颜色,在一个绘制调用中渲染不同颜色的多例化游戏对象,如下代码:
MaterialPropertyBlock props=new MaterialPropertyBlock();
MeshRenderer renderer;
foreach(GameObject obj in objects){
float r=Random.Range(0.0f,1.0f);