在优化DrawCall之前我们先来看一下GPU和Cpu之间的流水线是如何实现的

DrawCall简介

问题一:CPU和GPU是如何实现并进行工作的? 如果没有流水线化,那CPU就必须要等到GPU完成上一个渲染任务才能继续发送渲染命令。但这种方法显然效率低下。因此我们就需要GPU和CPU可以同时工作,而解决方案就是使用一个缓冲区。缓冲区里包含了一个命令队列,由CPU添加命令GPU从中读取命令,添加和读取的过程是相互独立的。命令缓冲区的命令有很多种,而DrawCall只是其中一种,其他命令还有改变渲染状态等 问题二:为什么DrawCall多了会影响帧率 我们先举一个例子,创建10000个文件,每个文件的大小为1kb,然后把它们从一个路径复制到另一个路径,你会发现这些文件总的大小不超过10mb但却要花费很长的时间,现在我们再创建一个单独的文件大小为10mb,然后也把他从一个路径复制到另一个路径你会发现这次所用的时间却是很少。这个原因在于每一个复制动作需要很多额外的操作,比如分配内存,创建各种元数据等。从某种层面上讲渲染的以上实验很类似。在每次调用DrawCall之前,CPU都要向GPU发送很多内容,包括数据状态和命令等。在这一阶段CPU需要完成很多工作,当CPU完成这些工作后GPU就可以开始本次的渲染,GPU的渲染能力是很强的,渲染200或者2000个三角玩网格通常是没有区别的,因此渲染速度往往快于CPU的提交命令的速度。如果DrawCall的数量太多,CPU就会话费大量的时间在提交DrawCall上,造成CPU过载 问题三:如何减少DrawCall 减少DrawCall的方法有很多,我们这里仅讨论使用批处理的方法。批处理的思想就是把很多小的DrawCall合并为一个大的DrawCall。需要注意的是由于我们是需要在CPU的内存中合并网格,而合并的过程是需要消耗时间的,因此批处理技术更加适合于那些静态的物体,例如不会移动的物体。当然我没也可以对动态的物体进行处理,但是由于这些物体是不断运动的,因此每一帧都要进行合并再发送给GPU,这对空间和时间都会造成一定的影响 降低Draw Call的方法则主要是减少所渲染物体的材质种类,并通过Draw Call Batching来减少其数量。 但是,需要注意的是,游戏性能并非Draw Call越小越好。这是因为,决定渲染模块性能的除了Draw Call之外,还有用于传输渲染数据的总线带宽。当我们使用Draw Call Batching将同种材质的网格模型拼合在一起时,可能会造成同一时间需要传输的数据(Texture、VB/IB等)大大增加,以至于造成带宽“堵塞”,在资源无法及时传输过去的情况下,GPU只能等待,从而反倒降低了游戏的运行帧率。 Draw Call和总线带宽是天平的两端,我们需要做的是尽可能维持天平的平衡,任何一边过高或过低,对性能来说都是无益的。

批处理(Batching)

静态批处理Static batching: 静态批处理允许游戏引擎尽可能多的去降低绘制任意大小的物体所产生的DrawCall,它会占用更多的内存资源和更少的CPU资源,因为它需要额外的内存资源来存储合并后的几何结构,如果在静态批处理之前,如果有几个对象共享相同的几何结构,那么将为每个对象创建一个几何图形,无论是在编辑器还是在运行时。这看起来是个艰难的选择,你需要在内存性能和渲染性能间做出最为正确的选择。在内部,静态批处理是通过将静态对象转换为世界空间,并为它们构建一个大的顶点+索引缓冲区。然后,在同一批中,一系列的“便宜”画调用,一系列的“便宜”,几乎没有任何状态变化之间的。所以在技术上它并不保存“三维的调用”,但它可以节省它们之间的状态变化(这是昂贵的部分)。使用静态批处理非常简单啦,只要勾选物体的Static选项即可! [

](http://www.wjgbaby.com/wp-content/uploads/2018/04/18040802-300x259.jpg)
](http://www.wjgbaby.com/wp-content/uploads/2018/04/18040802-300x259.jpg)
动态批处理Dynamic batching:

如果动态物体共用着相同的材质,那么Unity会自动对这些物体进行批处理。

动态批处理操作是自动完成的,并不需要你进行额外的操作。

1.批处理动态物体需要在每个顶点上进行一定的开销,所以动态批处理仅支持小于900顶点的网格物体。

2.如果你的着色器使用顶点位置,法线和UV值三种属性,那么你只能批处理300顶点以下的物体;如果你的着色器需要使用顶点位置,法线,UV0,UV1和切向量,那你只能批处理180顶点以下的物体。

3.请注意:属性数量的限制可能会在将来进行改变。

4.不要使用缩放尺度(scale)。分别拥有缩放尺度(1,1,1)和(2,2,2)的两个物体将不会进行批处理。

5.统一缩放尺度的物体不会与非统一缩放尺度的物体进行批处理。

使用缩放尺度(1,1,1)和 (1,2,1)的两个物体将不会进行批处理,但是使用缩放尺度(1,2,1)和(1,3,1)的两个物体将可以进行批处理。

6.使用不同材质的实例化物体(instance)将会导致批处理失败。

7.拥有lightmap的物体含有额外(隐藏)的材质属性,比如:lightmap的偏移和缩放系数等。所以,拥有lightmap的物体将不会进行批处理(除非他们指向lightmap的同一部分)。

8.多通道的shader会妨碍批处理操作。比如,几乎unity中所有的着色器在前向渲染中都支持多个光源,并为它们有效地开辟多个通道。

9.预设体的实例会自动地使用相同的网格模型和材质。

UGUI批处理: 在 UGUI 中,Batch是以Canvas为单位的,即在同一个Canvas下的UI元素最终都会被Batch到同一个Mesh中。而在Batch前,UGUI会根据这些UI元素的材质(通常就是Atlas)以及渲染顺序进行重排,在不改变渲染结果的前提下,尽可能将相同材质的UI元素合并在同一个SubMesh中,从而把DrawCall降到最低。而Batch的操作只会在UI元素发生变化时才进行,且合成的Mesh越大,操作的耗时也就越大。 因此,建议尽可能把频繁变化(位置,颜色,长宽等)的UI元素从复杂的Canvas中分离出来,从而避免复杂的Canvas频繁重建。 粒子系统批处理: 关于粒子系统拼合,是指引擎会将若干个材质相同且深度相同的粒子系统在渲染前进行合批(Batch),从而通过一个Draw Call来对其粒子系统进行渲染,进而降低粒子系统的渲染开销。 补充:粒子系统的Draw Call动态拼合与半透明物体的动态拼合机制相当(粒子基本都是半透明材质)。而对半透明物体,由于其渲染顺序的限制(必须从后向前渲染,以保证渲染结果的正确性),动态拼合只能对渲染顺序相邻且材质相同的物体有效。而在决定半透明物体的渲染顺序时,Unity首先会按Shader中的RenderQueue进行排序;其次(相同RenderQueue时),会根据每个半透明物件到屏幕的距离,距离大的优先渲染。 因此,需要尽可能地将相同材质的粒子系统放在比较接近的深度下,才能更多地使动态拼合生效。但通常由于相机的运动、粒子系统的分散分布等原因造成粒子系统之间的穿插,能够动态拼合的数量往往都是很少的,所以我们在粒子系统模块看到的开销分布通常类似该图,主要都是未拼合粒子系统造成。 经过验证,Unity 5.3.5 版本中恢复了对粒子系统的动态合批功能(可尝试添加多个默认的粒子系统,观察Batches的数量变化)。 Mesh批处理: 默认情况下,带蒙皮的Mesh(Skinned Mesh Renderer)是不支持动态合批的。如果场景中相同材质的蒙皮网格数量很多,可以考虑通过插件MeshBaker来进行合并,具体方法可以参考这个链接好插件让你事半功倍!