文章目录
CPU
一、减少draw call数目
我们最常看到的优化技术大概就是批处理(batching)
了。批处理的实现原理就是为了减少每一帧需要的draw call数目。为了把一个对象渲染到屏幕上,CPU需要检查哪些光源影响了该物体,绑定shader并设置它的参数,再把渲染命令发送给GPU。当场景中包含了大量对象时,这些操作就会非常耗时。一个极端的例子是,如果我们需要渲染一千个三角形,把它们按一千个单独的网格进行渲染所花费的时间要远远大于渲染一个包含了一千个三角形的网格。在这两种情况下,GPU的性能消耗其实并没有多大的区别,但CPU的draw call数目就会成为性能瓶颈。因此,批处理的思想很简单,就是在每次面对draw call时尽可能多地处理多个物体
。
使用同一个材质的物体可以一起处理。这是因为,对于使用同一个材质的物体,它们之间的不同仅仅在于顶点数据的差别。我们可以把这些顶点数据合并在一起,再一起发送给GPU,就可以完成一次批处理。Unity中支持两种批处理方式:
1.动态批处理
- 实现原理:每一帧把可以进行批处理的模型网格进行合并,再把合并后模型数据传递给GPU,然后使用同一个材质对其渲染。
- 优点:
- 一切处理都是Unity自动完成的,不需要我们自己做任何操作(场景中有一些模型共享了同一个材质并满足一些条件,Unity就可以自动把它们进行批处理)。
- 物体可以移动(处理每帧时Unity都会重新合并一次网格)。
- 缺点:
- 限制很多,可能一不小心就会破坏了这种机制,导致Unity无法动态批处理一些使用了相同材质的物体。这些条件有:能够进行动态批处理的网格的顶点属性规模要小于900(如果shader中需要使用顶点位置、法线和纹理坐标这3个顶点属性,它的顶点数目不能超过300)。多Pass的shader会中断批处理等。
2.静态批处理
-
实现原理:
- 静态批处理的实现非常简单,只需要把物体面板上的
Static
复选框勾选上即可(实际上我们只需要勾选Batching Static
即可)。在运行时查看每个模型使用的网格,会发现它们都变成了一个名为Combined Mesh (roo:scene)的东西。 - 只在运行开始阶段,把需要进行静态批处理的模型合并到一个新的网格结构中,这意味着这些模型不可以在运行时刻被移动。但由于它只需要进行一次合并操作,因此,比动态批处理更加高效。
- 在内部实现上,Unity首先把这些静态物体
变换到世界空间
下,然后为它们构建一个更大的顶点和索引缓存。对于使用了同一材质的物体,Unity只需要调用一个draw call就可以绘制全部物体。而对于使用了不同材质的物体,尽管仍然需要调用多个draw call,但可以减少这些draw call之间费时的状态切换。 - 如果场景中包含了除了平行光以外的其他光源,并且在shader中定义了额外的Pass来处理它们,这些额外的Pass部分是不会被批处理的。
- 静态批处理的实现非常简单,只需要把物体面板上的
-
优点:
- 自由度很高,限制很少(适用于任何大小的几何模型)。
-
缺点:
- 可能会占用更多的内存(如果在静态批处理前一些物体共享了相同的网格,那么在内存中每一个物体都会对应一个该网格的复制品,VBO(Vertex BufferObject,顶点缓冲对象)的数目会变大)。
- 经过静态批处理后的所有物体都不可以再移动了(即便在脚本中尝试改变物体的位置也是无效的)。
3.关于共享材质
无论是动态批处理还是静态批处理,都要求模型之间需要共享同一个材质。但不同的模型之间总会需要有不同的渲染属性,例如,使用不同的纹理、颜色等。这时,我们需要一些策略来尽可能地合并材质
(只要我们调整了参数,就会影响到所有使用这个材质的对象):
- 材质的纹理不同:我们可以把这些纹理合并到一张更大的纹理中,这张更大的纹理被称为是一张图集(atlas)。一旦使用了同一张纹理,我们就可以使用同一个材质,再使用不同的采样坐标对纹理采样即可。
- 颜色不同、某些浮点属性不同:使用网格的顶点数据(最常见的就是顶点颜色数据)来存储这些参数。
如果我们需要在脚本中访问共享材质,应该使用Renderer.sharedMaterial
来保证修改的是和其他物体共享的材质,但这意味着修改会应用到所有使用该材质的物体上。另一个类似的API是Renderer.material
,如果使用Renderer.material
来修改材质,Unity会创建一个该材质的复制品,从而破坏批处理在该物体上的应用,这可能并不是我们希望看到的。
4.批处理的注意事项
- 尽可能选择静态批处理,但得时刻小心对内存的消耗,并且记住经过静态批处理的物体不可以再被移动。