浅谈UGUI优化-网格合并
文章目录
之前说到优化DrawCall可以降低CPU在渲染上的开销,那么是不是DrawCall越少就一定越好呢?单纯讨论DrawCall的话,确实是,但是如果为了合并DrawCall需要付出的代价很大,就会得不偿失。网格合并(Rebatch)就是UGUI解决DrawCall问题的代价之一。
什么是网格合并
我们知道UGUI绘制UI的过程需要创建Mesh信息,例如一个Image元素在Simple模式下会创建四个顶点,如果我们有100个相同的这样的Image,UGUI底层(Native Code部分)会将这100个Image的Mesh信息合并成一个大的Mesh,用同一个DrawCall发送给CPU,这就是网格合并,是UGUI行之有效的降低DrawCall提高效率的方法。
一切看起来很美好,但是,UI的Mesh信息相对于普通的Mesh来说有一个很大的不同,就是UI的Mesh信息是会变化的,而一旦某个UI元素的Mesh信息发生修改,那么和这个元素合并在一批的整个Mesh都会变成不可用,此时就只能重新合并,颇有种“一块臭肉坏了满锅汤”的意味。
可以根据一个例子来体会下这个过程
|
|
我们创建了5000个静止的Image和一个跳舞的Image,跳舞是指我们可以通过按下空格键来切换跳舞状态,当处于跳舞状态时,它会随机出现在一个位置。通过Unity的Profiler可以体会下这种变化:
通过搜索可以发现Canvas.BuildBatch这一项的Call是1,即被调用一次,而在跳舞之前这一项的Call为0。
可以通过这种方式测试下常见的对UI元素的修改方式是否导致Rebatch:
修改内容 | 是否导致Rebatch |
---|---|
anchorPosition | √ |
sizeDelta | √ |
localPosition | √ |
localRotation | √ |
localScale | √ |
SetActive | √ |
color | √ |
SourceImage | √ |
raycastTarget | × |
text | √ |
可以看到大部分对于UI的操作都会引起Rebatch,这可能也是很多人将Rebuild和Rebatch混为一谈的原因,毕竟一旦对UI元素进行了操作,Rebuild和Rebatch几乎都会发生。但是他们本质上是不同的东西,Rebuild发生在C#层面,是某个具体的元素重新生成Mesh信息,性能消耗受发生Rebuild的元素多少影响,而Rebatch发生在Native Code层面,是合并后的Mesh信息需要重组,其性能消耗受发生次数和涉及Mesh复杂程度影响。
建议
可以说只要UI元素需要动1,Rebatch就是不可避免的,我们能做的只是将他的开销降低,也就是降低需要动的UI元素经过Batch后的Mesh的复杂程度,这就是老生常谈的“动静分离”的本质。以我们的RebatchSimulation
为例,这里把5000个不动的元素和1个动的元素放在一个Canvas下,也就是UGUI会将这5001个UI元素的Mesh信息合并成一个大的Mesh处理,这时其中一个元素动了,这个大的Mesh就需要重新组建,造成严重的性能开销。优化的方法也很明显,就是将它们分离到不同的Canvas下,由于UGUI渲染是以Canvas为单位,动的元素就不会影响到其他Canvas里面静的元素。
注意
Rebatch组建Mesh是以Canvas为单位而非DrawCall,也就是说一个UI元素动了,是以Canvas为单位去Rebatch,把动的元素分隔到单独DrawCall并不能达到动静分离的目的。
-
这里的动并非字面意义的动,而是指对UI元素会导致Rebatch的操作。 ↩︎