UGUI性能优化之Draw Call详解
游戏开发圈里的人一定听过优化游戏要降低Draw Call,那么Draw Call到底是什么呢?要了解什么是Draw Call,就要从图形引擎渲染画面说起了。
一、图形引擎渲染画面的过程
Unity(或者说基本所有图形引擎)生成一帧画面的处理过程大致可以这样简化描述:
1. 可见性测试
引擎首先经过简单的可见性测试,确定摄像机可以看到的物体
2. 准备好需要渲染的数据
把这些物体的顶点(包括本地位置、法线、UV等),索引(顶点如何组成三角形),变换(就是物体的位置、旋转、缩放、以及摄像机位置等),相关光源,纹理,渲染方式(由材质/Shader决定)等数据准备好
3. 通知图形API开始绘制
通知图形API——或者就简单地看作是通知GPU——开始绘制,GPU基于这些数据,经过一系列运算,在屏幕上画出成千上万的三角形,最终构成一幅图像。
二、什么是Draw Call
Draw Call ,CPU准备好需要绘制的元素,对底层图形程序接口进行调用的过程。
优化Draw Call主要是CPU的处理速度的优化,CPU和GPU是并行工作的,处理的方式就是CPU和GPU之间有一个命令缓存区,CPU向其中添加命令,GPU从中取出命令并执行。一般来说,GPU的渲染速度是远远高于CPU的处理速度的,所以说性能瓶颈不在于GPU,而是CPU。
比方说,我有一张图片需要渲染,那么CPU就把这张图片的数据准备好(请注意,CPU准备这些数据的时候,是需要进行大量的计算的,比如法线、UV、顶点、位置、缩放、旋转、光照、纹理、渲染方式等等),放入缓存区,GPU检测到缓存区有命令了,就会执行,从而渲染这张图片。如果有十张、一百张图片呢?CPU需要准备并向缓存区添加十次、一百次的数据吗?显然不合理,因为从理论上来说,我们可以把一些相同的命令合在一起存入缓存区,这就是优化Draw Call了–合批。
三、如何优化Draw Call
上面讲了,优化Draw Call,其实就是把一些相同的数据,整合在一起,批量交给GPU去处理,也就是合批。那么问题来了,是所有元素都可以进行合批吗?CPU还做不到这种程度,元素之间的合批是有条件的:层级、纹理、渲染方式,当这三者都相同的时候,就会合批(Batch)。
先来说说纹理和渲染方式,因为它们是最好理解,也最好处理的。
· 纹理:其实就是贴图,如果使用的是同一张贴图,或者是同一张图集中的不同贴图,就可以认为它们的纹理是相同的;
· 渲染方式:渲染方式是由Shader计算的,在unity中,Shader是附着在材质球Material上,通过给元素添加Material来指定渲染方式。所以,可以简单的理解为,材质球Material相同,即可认为它们的渲染方式相同;这里需要注意的是,UI元素(Image、Raw Image、Text)的Material为None时,UGUI会给它指定默认的材质球UI-Default。
讲完了纹理和渲染方式,接下来说说层级。层级很好理解,但确是最难处理的,同时它也是合批的首要判定条件。
层级相同,则会判断纹理,纹理一致,再看渲染方式,都一致,则开始合批处理数据,处理完数据后将其添加进命令缓存区,GPU接收到命令就会开始渲染。
同一个Canvas内,UGUI的层级计算方式:
1、一个UI元素,在它所占的屏幕范围内(通常是矩形),如果没有任何UI在它的底下,那么它的层级号就是0(最底下);
2、如果一个UI在其底下且该UI可以和它合批,那它的层级号与底下的UI层级一样;
3、如果一个UI在其底下但是无法与它Batch,那它的层级号为底下的UI的层级+1;
4、如果有多个UI都在其下面,那么按前两种方式遍历计算所有的层级号,其中最大的那个作为自己的层级号。
为什么说层级是最难且最不好处理的呢?下面举个例子:
在上图中,rapidhit和5rapidhit在同一张图集中,bomb和wild在同一张图集中,一共两次batch,没毛病,符合我们的预期。再来看看下面这张图:
可以看到上图一共产生了4个batch,所以说,当两张同一图集的图片之间穿插了别的图集的图片,并且产生重叠时,这两张图片并不能合批。我们来调整一下层级,看看下图:
上图中,我们把bomb的层级调高了,使得rapidhit和5rapidhit的层级是连续的,bomb和wild的层级也是连续的,这时候同一图集的图片就合批了。因此,我们在制作UI和打图集的时候,应该–尽量使得相同图集的元素处于一个连续的层级内,以减少Draw Call。