Unity Draw call batching小结

DrawCalls 和 Batches

众所周知,优化场景需要优化draw call。但是多一些draw call真的就会慢很多吗?Unity早期在Statistics界面显示的是Draw calls,现在改成显示Batches。你知道这两者的区别吗?本文是对官方文档的一个小结,并在Unity上进行测试,为的是彻底搞清楚Unity的batch机制。

draw call就是你知道的那个draw call

首先,毋庸置疑的是draw call确实对应了图形API的一次绘制操作。draw call是资源敏感的,当draw call之间状态改变(例如切换不同的材质),会造成图形驱动重新确认和传输资源(resource-intensive validation and translation )。简单说就是需要重新设置GPU的状态,以及准备相应的资源,这也会造成CPU端的负载,比如重新传输顶点数据或索引(当然了,有了VBO和VAO后,实际的内存搬运就不需要重复进行了,但是设置GPU的渲染状态仍然需要从CPU发出指令,指定使用哪个VBO或VAO本身就是渲染状态的一种)。因此我们才说draw call多了,就会造成渲染效率降低,往往是GPU还很闲,CPU已经忙得不行。其实优化draw call是优化CPU,通过batch合并draw call后,CPU可以将原本多次draw call设置的指令,一次传输给GPU。好了,以上都是老生常谈,大路货。

draw call不一定意味着状态切换

draw call就是draw call,一次draw call并不意味着状态切换一次。实际上,有可能没有状态切换(什么都没改变),而仅仅简简单单执行一次draw call。这样的draw call并不是很费。后面会说到,Unity的静态batching就是这样的。静态batching的原理后面会细说,基本上执行静态batching时会对batch内部可见的物体执行多次draw call,但是并没有渲染状态的切换,虽然draw call数没有降,但是效率提升了。以前见到有人写博客,研究Unity的静态batching,发现draw call没降低,于是得出结论静态batching没有用。可能Unity也意识到了这个问题,在新版中(忘了从哪个版本开始),在Statistics中不再显示DrawCall数,而是改成显示Batch数,这样当执行静态batching时,会发现Batches降低了,这是符合实际的性能数据的,更有参考价值。这个Batches你可以理解为逻辑意义上的批次,对于静态batching,虽然draw call没降低,但是状态切换降低了,对于状态切换来说是进行了批次合并的,而状态切换才是真正影响draw call效率的瓶颈,至于draw call本身仅仅是个API调用而已并没有很费。

静态batching

静态batching的原理很简单,将可以合并(共享了相同的材质且都是静态)的物体的顶点变换到世界空间,并且对于每个批次创建一对共享的顶点和索引缓冲。实际绘制时,批次中通过了culling(如frustum culling)的物体分别执行draw call,但是会使用共享的顶点和索引缓冲中它们自己的偏移。至于渲染状态,是在本批次开始渲染时设置好的,批次中的多个物体执行draw call时并不会切换状态(当然了它们使用相同的渲染状态)

静态batching更费内存

原因是显然的,因为如果多个物体共享了相同的Mesh,进行静态batching时,这些物体的顶点会重复的拷贝到共享的缓冲中(因为他们的世界坐标并不一样)。某些情况,比如场景中大量的树,如果进行静态batching会造成很大的内存负担,此时就需要为了内存牺牲效率,对这些树禁用静态batching。

静态batching不限制物体顶点数且更有效率

这是相对于动态batching来说的(下面会说到),任意尺寸的几何体都可以进行静态batching。由于静态batching只需要进行一次合并操作,因此比动态batching更有效率。当然缺点是使用范围只限于静态物体。

静态batching的优化选项

在Unity的Player settings中,可以勾选“Optimize Mesh Data”,这样Unity会在进行静态batching时去除没有使用到的顶点属性。

静态batching的缓存区限制

虽然不限制物体的顶点数,但是batch本身的缓冲区大小是有限制的。在大多数平台下,一个batch的限制为64K的顶点缓冲和64K的索引缓冲。在OpenGLES平台,索引缓冲的限制为48K;对于MacOS,索引缓冲的限制为32K。

动态batching

针对Mesh的动态batching

当在设置中开启动态batching后,Unity会自动合并动态物体。除了材质相同,动态batching还有很多条件。

模型顶点属性限制

由于执行动态batch是需要每帧使用CPU进行合并顶点,因此这个操作本身是比较费CPU的,很可能得不偿失,因此Unity对可batch的模型的顶点属性数量进行了限制(而静态bacthing则无此限制)。具体的限制为模型不能超过900个顶点属性且不能超过300个顶点。例如,一个模型使用了顶点坐标,法线和一组UV,这样每个顶点就有3个属性,由于总的属性数不能超过900,那么这个模型不能超过300个顶点否则就不可以被动态batching。另一个例子,如果模型使用了顶点坐标,法线,两组UV以及切线,那么每个顶点使用5个属性,所以该模型如果超过180个顶点就不能被动态batching了。并且这个属性的限制,Unity以后还可能更改。

物体包含镜像的变换不能被动态batching

例如物体A的缩放为+1,而B为-1,则A和B不能合并。

材质实例不同则不能合并

比如你在脚本里面设置了Mesh的Renderer.material,就会创建材质的拷贝。这样就不能合并了。我们需要设置Renderer.sharedMaterial,这样材质是共享的就可以合并。当然这个有个例外,就是在进行shadow caster渲染时,一般是会使用默认的shader,投影的draw call还是可以动态合并的。

关于使用lightmap的物体的动态合并

物体使用Lightmap时要指定lightmap的索引以及在某个lightmap texture上的偏移和缩放。只有当物体定位到同一个lightmap的同一个位置时,才可以被合并到一起。这个很好理解吧,相当于使用的贴图必须一样。

多Pass的shader会破坏合并

因为多pass的shader可能对某个pass执行多次,例如前向渲染中,对每个逐像素光源都执行一个forward add pass。由于这种多pass shader相当于切换了多次材质,所以是无法合并的。

动态batching默认是关闭的

因为动态batching存在CPU的消耗,很可能得不偿失,且draw call需要的资源取决于很多因素,例如在主机平台或者Apple的Metal,draw call的消耗已经很低了,动态batching不一定比draw call更省。估计是这个原因,因此Unity默认关闭了动态batching。

针对粒子系统,Line Renderers和Trail Renderers的动态batching

这类对象会动态生成几何体,它们的动态batching和模型的动态batching不一样。

  • 对于每种兼容的renderer类型,Unity将所有可合并的内容build到一个大的顶点缓冲中。
  • 对每个batch,renderer设置材质状态。
  • Unity将缓冲区绑定到显卡。。
  • 对batch中的每个renderer,Unity更新缓冲区的偏移,然后执行一次新的draw call。
  • 设置渲染状态的消耗比使用一个不同的offset执行draw call要高很多,这种方式和静态batching很像。

哪些不能batching

  • 目前,只有Mesh Renderers, Trail Renderers, Line Renderers, Particle Systems和Sprite Renderers可以被batch。而Skinned Meshes, Cloth和其他渲染组件不能被batch。
  • 只有相同类型的Renderers可以被合并。

半透明材质呢?

半透明物体渲染时需要从后向前排序后渲染,然后Unity再试着合并。由于是先按Z值排序的,所以邻接绘制的物体不一定共享了材质,因此合并的机会降低很多。而对于不透明物体,不需要按Z值排序,而是按材质排序的,这样被合并的机会高了很多。

手动合并

对于放在一起的一些小物体进行手动合并(由美术导出模型时合并或者通过脚本中执行Mesh.CombineMeshes)也是一个很好的方案。但是这个方案的局限性有两个,首先需要人为控制,不利于修改。另外合并后就不能分开参与culling了。当然物体很靠近且很小,例如一张桌子和放在桌子上的物体,手动合并还是很合适的。

参考资料

Unity文档

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值