Unity - DrawCall优化相关 Batch 动态合批 静态合批
什么是DrawCall
CPU呼叫GPU进行绘制是一次DrawCall
以 OpenGL 为例,就是调用带有绘制功能的 API 的次数
如:DrawCall : 10 次,那就意味着调用了 glDrawXXXX 的 API 10 次
啥叫:Batch
Batch 直译:批量,的意思
在 实时渲染 中,已动态合批为例(Dynamic Batch)一般理解为:为了减少 DrawCall,或是减少 SetPassCall 而将绘制时材质一样(或是说 shader + shader 参数 + 绘制前状态,都一样)的 VBO,IBO,等数据打包到一个大的 VBO,或是 IBO 中,然后在调用一次 DrawCall,从而提升性能:SetPass 的 State 时,或是多次 Draw API 调用产生过多的 CPU 消耗的性能的问题
(另外还有:静态合批(Static Batch)、Instanced 批量(GPU Instancing Batch)绘制,都算是 Batch 的方式)
所以 Batch 的目的是:将原本需要 多次 SetDrawState + 多次 DrawCall,优化为:1次 SetDrawState + 1次 DrawCall
可以简单的理解为:批量渲染是通过减少CPU向GPU发送渲染命令(DrawCall)的次数,以及减少GPU切换渲染状态的次数,尽量让GPU一次多做一些事情,来提升逻辑线和渲染线的整体效率。但这是建立在GPU相对空闲,而CPU把更多的时间都耗费在渲染命令的提交上时,才有意义。
合批最重要的前提:材质必须相同!!!
Batch ≠ DrawCall
优化方式
相同材质的物体一同绘制
那么如何相同材质的物体一同绘制
就是把相同材质的物体的网格合并一同绘制
Why?
- 底层的图形api有一个绘制三角形的接口,这个就是CPU调用gpu绘制的接口
- 在调用这个接口前会准备顶点数据,即网格数据
- 把网格合并后只要进行一次绘制三角形的接口调用,减少了DrawCa
为啥要优化DrawCall
上面已经说了,Cpu叫GPU执行一次绘制渲染就是DrawCall。
传输数据消耗比较大,GPU的计算能力也相对较强,就可能存在CPU传递的指令只让渲染一小部分,GPU执行完毕以后,但是下一条的指令还没有过来的时候,
这就好比这条路车多了,堵车了,那么如何梳理交通,让一次通过的红绿灯让更多的车子过去,提供更高效的通行效率和时间呢?关键就是在于,这次红绿灯,我让多少车过去了。
所以,提高DrawCall的效率非常的重要,就是保证尽量的让每次的DrawCall能够渲染更多的内容(让更多的车子过去)。这样,即便是红绿灯通行次数少了(DrawCall的提交次数),但是
通行效率提高了,一次性过去的车子多了,那么也能有效的减少拥堵的情况。
然后如何去提升DrawCall的效率呢,主要的方法就是合批(Batch),,也就是上面解释的名词。
Unity中的合批
静态合批 Static Batching
- Unity中把物体标记为Static,然后开启静态合批
- 限制
- 需要保持static,不能改变transform
- 使用相同材质的物体才能合批
- 一个批次上限为~15k个顶点
动态合批 Dynamic Batching
Unity自带动态合批,需要在Unity中开启动态合批选项
会导致cpu消耗,如果不是gpu有瓶颈,最好关闭动态合批
前提
使用顶点位置、法线、UV0、UV1和切线为一个着色器提供180个顶点
使用顶点位置、法线和单一UV的着色器的300个顶点
动态合批的限制条件呢?
●材质球相同;
●Mesh顶点数量不能超过300以及顶点属性不能超过900;
●缩放不能为负值(x、y、z向量的乘积不能为负)等。
静态合批的利弊
静态合批采用了以空间换时间的策略来提升渲染效率。
其优势在于:网格通常在预处理阶段(打包)时合并,运行时顶点、索引信息也不会发生变化,所以无需CPU消耗算力维护;若采用相同的材质,则以一次渲染命令,便可以同时渲染出多个本来相对独立的物体,减少了DrawCall的次数。
在渲染前,可以先进行视锥体剔除,减少了顶点着色器对不可见顶点的处理次数,提高了GPU的效率。
其弊端在于:合批后的网格会常驻内存,在有些场景下可能并不适用。比如森林中的每一棵树的网格都相同,如果对它采用静态合批策略,合批后的网格基本等同于:单颗树网格 x 树的数量,这对内存的消耗可能就十分巨大了。
总而言之,静态合批在解决场景中材质基本相同、网格不同、且自始至终都保持静止的物体上时,很适用。
动态合批与静态合批的区别
1、动态合批不会创建常驻内存的“合并后网格”,也就是说它不会在运行时造成内存的显著增长,也不会影响打包时的包体大小;
2、动态合批在绘制前会先将顶点转换到世界坐标系下,然后再填充进顶点、索引缓冲区;静态合批后子网格不接受任何变换操作,仅手动合批后的Root节点可被操作,因此静态合批的顶点、索引缓冲区中的信息不会被修改(Root的变换信息则会通过Constant Buffer传入);
3、因为2的原因,动态合批的主要开销在于遍历顶点进行空间变换时的对CPU性能的开销;静态合批没有这个操作,所以也没有这个开销;
4、动态合批使用根据渲染器类型分配的公共缓冲区,而静态合批使用自己专用的缓冲区。
开启方法
Player Setting > Other Settings