drawcall优化
Unity(或者说基本全部图形引擎)生成一帧画面的处理过程大致能够这样简化描写叙述:引擎首先经过简单的可见性測试,确定摄像机能够看到的物体,然后把这些物体的顶点(包含本地位置、法线、UV等),索引(顶点怎样组成三角形),变换(就是物体的位置、旋转、缩放、以及摄像机位置等)。相关光源,纹理,渲染方式(由材质/Shader决定)等数据准备好。然后通知图形API——或者就简单地看作是通知GPU——開始绘制,GPU基于这些数据。经过一系列运算。在屏幕上画出成千上万的三角形。终于构成一幅图像。
在Unity中,每次引擎准备数据并通知GPU的过程称为一次Draw Call。
这一过程是逐个物体进行的。对于每一个物体,不仅仅GPU的渲染,引擎又一次设置材质/Shader也是一项很耗时的操作。
因此每帧的Draw Call次数是一项很重要的性能指标,对于iOS来说应尽量控制在20次以内。这个值能够在编辑器的Statistic窗体看到。
Unity内置了Draw Call Batching技术。从名字就能够看出,它的主要目标就是在一次Draw Call中批量处理多个物体。
仅仅要物体的变换和材质同样,GPU就能够按全然同样的方式进行处理,即能够把它们放在一个Draw Call中。
Draw Call Batching技术的核心就是在可见性測试之后,检查全部要绘制的物体的材质,把同样材质的分为一组(一个Batch),然后把它们组合成一个物体(统一变换),这样就能够在一个Draw Call中处理多个物体了(实际上是组合后的一个物体)。
但Draw Call Batching存在一个缺陷。就是它须要把一个Batch中的全部物体组合到一起。相当于创建了一个与这些物体加起来一样大的物体,与此同一时候就须要分配对应大小的内存。这不仅会消耗很多其它内存,还须要消耗CPU时间。
特别是对于移动的物体。每一帧都得又一次进行组合,这就须要进行一些权衡,否则得不偿失。
但对于精巧不动的物体来说,仅仅须要进行一次组合,之后就能够一直使用。效率要高得多。
Unity提供了Dynamic Batching和Static Batching两种方式。Dynamic Batching是全然自己主动进行的,不须要也无法进行不论什么干预。对于顶点数在300以内的可移动物体,仅仅要使用同样的材质,就会组成Batch。
Static Batching则须要把精巧的物体标记为Static,然后不管大小,都会组成Batch。如前文所说。Static Batching显然比Dynamic Batching要高效得多,于是。Static Batching功能是收费的……
要有效利用Draw Call Batching,首先是尽量降低场景中使用的材质数量,即尽量共享材质,对于仅纹理不同的材质能够把纹理组合到一张更大的纹理中(称为Texture Atlasing)。然后是把不会移动的物体标记为Static。此外还能够通过CombineChildren脚本(Standard Assets/Scripts/Unity Scripts/CombineChildren)手动把物体组合在一起,但这个脚本会影响可见性測试,由于组合在一起的物体始终会被看作一个物体,从而会添加GPU要处理的几何体数量。因此要小心使用。
对于复杂的静态场景,还能够考虑自行设计遮挡剔除算法,降低可见的物体数量同一时候也能够降低Draw Call。
总之。理解Draw Call和Draw Call Batching原理,依据场景特点设计对应的方案来尽量降低Draw Call次数才是王道,其他方面亦然。
U3D DrawCall优化手记
在近期。使用U3D开发的游戏核心部分功能即将完毕,中间因为各种历史原因,导致项目存在比較大的问题。这些问题在最后。恐怕仅仅能通过一次彻底的重构来解决
如今的游戏跑起来会有接近130-170个左右的DrawCall,游戏执行起来明显感觉到卡,而经过一天的优化,DrawCall成功缩减到30-70个。这个效果是很显著的,而且这个优化并没有通过将现有的资源打包图集来实现,图集都是原有的图集,假设从全局的角度对图集再进行一次优化。那么DrawCall还能够再降低十几个
本次优化的重点包含:层级关系和特效
对于U3D,我是一个菜鸟,对于U3D的一些东西是一知半解。比如DrawCall,我得到的是一些并不全然正确的信息,比如将N个纹理打包成一个图集,这个图集就仅仅会产生一个DrawCall,假设不打成图集。那么就会有N个DrawCall,这个观点在非常多人的认识里都是正确的,由于能够通过简单的操作来验证。但严格来说,这个观点是错误的,由于它还受层级关系影响!
渲染顺序
U3D的渲染是有顺序的。U3D的渲染顺序是由我们控制的,控制好U3D的渲染顺序,你才干控制好DrawCall
一个DrawCall。表示U3D使用这个材质/纹理,来进行一次渲染,那么这次渲染如果有3个对象,那么当3个对象都使用这一个材质/纹理的时候。就会产生一次DrawCall,能够理解为一次将纹理输送到屏幕上的过程。(实际上引擎大多会使用如双缓冲,缓存这类的手段来优化这个过程,但在这里我们仅仅须要这样子认识就能够了),如果3个对象使用不同的材质/纹理。那么无疑会产生3个DrawCall
接下来我们的3个对象使用2个材质,A和B使用材质1,C使用材质2,这时候来看,应该是有2个DrawCall,或者3个DrawCall。应该是2个DrawCall啊。为什么会有3个DrawCall???并且是有时候2个,有时候3个。
我们依照上面的DrawCall分析流程来分析一下:
1.渲染A,使用材质1
2.渲染B,使用材质1
3.渲染C,使用材质2
在这样的情况下是2个DrawCall。在以下这样的情况下,则是3个DrawCall
1.渲染A,使用材质1
2.渲染C,使用材质2
3.渲染B。使用材质1
由于我们没有控制好渲染顺序(或者说没有去特意控制)。所以导致了额外的DrawCall。由于A和B不是一次性渲染完的,而是被C打断了。所以导致材质1被分为两次渲染
那么是什么在控制这个渲染顺序呢?首先在多个相机的情况下,U3D会依据相机的深度顺序进行渲染,在每一个相机中,它会依据你距离相机的距离,由远到近进行渲染。在UI相机中,还会依据你UI对象的深度进行渲染
那么我们要做的就是,对要渲染的对象进行一次规划。正确地排列好它们,规则是,依照Z轴或者深度。对空间进行划分,然后确定好每一个对象的Z轴和深度,让使用同一个材质的东西,尽量保持在这个空间内。不要让其它材质的对象进入这个空间。否则就会打断这个空间的渲染顺序
在这个基础上。更细的规则有:
- 场景中的东西,我们使用Z轴来进行空间的划分,比如背景层,特效层1,人物层,特效层2
- NGUI中的东西。我们统一使用Depth来进行空间的划分
- 人物模型,当人物模型仅仅是用一个材质,DrawCall仅仅有1,可是用了2个以上的材质。DrawCall就会暴增(也许对材质的RenderQueue进行规划也能够使DrawCall仅仅有2个。但这个要拆分好才行),3D人物处于复杂3D场景中的时候,我们的空间规则难免被破坏,这仅仅能在设计的时候尽量去避免这样的情况了
- 使用了多个材质的特效。在动画的过程中,往往会引起DrawCall的波动,在视觉效果能够接受的范围内,能够将特效也进行空间划分。如果这个特效是2D显示,那么能够使用Z轴来划分空间
打包图集
每一个材质/纹理的渲染一定是会产生DrawCall的。这个DrawCall仅仅能通过打包图集来进行优化
制作图集一般遵循几个规则:
- 从功能角度进行划分,比如UI能够划分为公共部分,以及每一个详细的界面,功能上,显示上密切相关的图片打包到一起
- 不要一股脑把全部东西打包到一个图集里。特别是那些不可能同一时候出现的东西,它们就不应该在一个图集里。这种图集意义不大。降低不了DrawCall。而且一个你不须要显示的图片,会一直占用你的内存,这让我很不爽
- 注意控制图集的大小。不要让图集太大,一个超级大图集的DrawCall消耗也许顶的上十几个小图集的消耗
字符图集,在使用BMFont或者其它工具生成图片字的时候。我们往往是直接导入一大串文字,然后直接生成图片,但实际上这上面的操作也有优化空间,比如BMFont生成的图片大小。是能够设置的,有两个规则,一个规则是导出的图片尽量小,还有一个是导出的图片尽量少,默认的大小应该是512x512。如果你生成的图片256x256就能够容纳,那么多做一个操作你能够节省这么多空间,另外当你输入多几个字。就导致添加一张图片时,比如1024变成2048,那么你能够考虑使用3张512的图片,这样也会节省空间
经过精心划分的图集在加上精心规划的渲染顺序,DrawCall会有一个质的优化
特效清理
U3D提供了非常便捷的方法让我们非常轻易地使用美术给过来的特效,懒惰的U3D程序员会直接放入U3D。甚至不去看这是个什么特效,我们的特效一般都是一瞬间的事情,比如技能特效。或者其它什么特效,那么特效播放完,这个特效我们就看不到了,但如果这个特效在播放结束的时候,没有将自身的Active属性设置为false。那么它就会继续占用你的DrawCall,消耗你设备的计算能力,所以程序须要保证当一个特效播放完之后。可以被消耗,或者设置为非激活的状态,可以使用一些公共方法来完毕特效播放完之后的清理工作(自己实现2个静态函数。一个播放完销毁。一个播放完设置未激活)
完毕DrawCall的优化之后,接下来就是内存的优化了,(内存优化手记 待续)