经常性会被问到如何优化DrawCall,或者DrawCall太多怎么办。
那么读完这篇文章你就能明白几种drawcall优化的方式。
通常引擎一般会提供3种合批的方式: 静态合并,动态合并,实例化
那么3种合批的方法到底有什么区别呢,我们需要在何时使用对应的方法?
这里我们首选从底层驱动来说起,以OpenGL为例,绘制元素的API一般为:
glDrawElements 或者 glDrawArrays
调用这些方法后,cpu会将顶点数据传入显存,之后由显卡进行绘制。
每次调用这些方法,我们称之为一个DrawCall
那么当多次调用这些方法时,会明显导致draw增多。
所以在优化游戏时,我们很多情况首先要做的就是进行DrawCall的优化。
为了解决DrawCall太多的问题,我们很容易想到的就是把相同材质的网格进行合并。
既然你的的材质是一样的,那么我合并网格后只要调用一次 glDrawElements
就只产生一个DrawCall,岂不美哉。
所以就有第一种合批的方法:
dynamic batching
动态合批是指在引擎的运行期,将同材质的模型合并为一个大的网格数据传输给显卡。
这种情况,需要耗费CPU来进行缓存的合并,对CPU是有一定影响的。而且由于数据较大,对带宽的要求也更好。
这就是为什么有的小伙伴合批和了半天,运行效率更低的原因。
因为一方面,CPU被拿去做合批运算耗费时间,另一方面带宽占用也变大了。
那么既然运行时合批会消耗CPU,那完全可以将在运行时不需要移动的物体进行离线合批嘛。
(离线合批:在非运行时进行合批。)
于是就引申出了第二种合批方式:
static batching
静态合批下,引擎会帮我们把选择好的网格(Mesh)合并成一个大的网格。
同理会帮我们将网格信息内的uv坐标,物体的纹理等进行重新计算和合并。
这种合并显而易见的好处是不占用运行时效率。
唯一的问题是,如果顶点过多,导致网格太大,那么传输效率就会降低,是一种比较影响带宽的做法。
Instancing
最后呢就是Instancing,既然我们怎么合批都会耗费cpu和带宽,那么我什么不把这些麻烦的事情交给gpu做呢?
毕竟gpu是擅长并行计算的,而绘制多个同样材质的物体本来就是一个并行的过程嘛。
因此驱动组织都提供了可以一次传输,多次绘制的方法,这种我们称为实例化(instancing)
instancing是底层驱动支持的一种特性,在OpenGl内方法一般为在绘制函数后面增加 Instanced
比如:
glDrawArrayInstanced
glDrawElementsInstanced
以glDrawElementsInstanced
为例可以明显看到他和glDrawElements
的区别
void glDrawElements( GLenum mode,
GLsizei count,
GLenum type,
const void * indices);
void glDrawElementsInstanced( GLenum mode,
GLsizei count,
GLenum type,
const void * indices,
GLsizei primcount);
可以看到instanced的函数比普通的函数多出一个叫做 primcount
参数,这个参数的作用是通知显卡在绘制这些顶点时,重复绘制的次数。
在绘制的shader内,我们可以通过访问 gl_InstanceID
来查看到底绘制到第几个实例了,因为我们每个实例的位置旋转缩放和顶点颜色可能不一样。
当然intancing也不是没有限制的,毕竟驱动商是在迭代的过程中才发现这样的需求的,因此对于低于OpenGL3.0的设备是不支持实例化的。
当然现在低于OpenGL3.x的设备比较少见了,可以比较放心的去实例化方式绘制。
废话不多说我们来看看各种合批的结果吧
(软件为Render Doc)
![没有合并批次](
没有合并批次的情况下,DrawCall达到了230个
![动态合并批次](
而在我们进动态合批以后,DC减少到了179个,可以说效果显著。
![实例化](
当我们可以实例化以后,Instancing减少到了160了! 可喜可贺。
所以从上面的例子中我们可以看出,在渲染同样材质的物体时,我们应该尽量开启实例化,而由于静态合批可以将不同材质的物体合并到一起,因此静态合批实际上是优先级最高的一种合批方式。
如果你的场景中有大量同类物品确不能静态合批,那么可以试试动态合批。
最后我们可以得到如下结论:
静态合批 > Instancing > 动态合批
如何在Cocos Creator中开启batching和合批。
我们只需要在材质内进行勾选就可以了。
共同学习,写下你的评论
评论加载中...
作者其他优质文章