前言
在Unity中UI优化的核心问题就是重绘和批处理之间的平衡。虽然说可以通过一些简单的技巧单方面地减少批次或者减少重绘,但进行过一波优化之后,最终还是要面临批次和重绘的平衡问题。
合理分配Canvas
Canvas是UGUI的基本组件,它生成表示放置在其上的 UI 元素的网格,也就是说它会把一个Canvas下的所有元素合并在一个Mesh里。如果Canvas下的元素很多,任意一个元素发生位置、大小的改变,就需要重新合并所有元素的Mesh。如果元素非常多的话,可能就会造成卡顿。因此我们可以合理的划分Canvas,将需要频繁更改的动态UI元素与静态UI元素划分成多个Canvas从而减少重绘,但需要注意的是不要太过细分Canvas,否则会导致DrawCall上升。
情景一、CPU处理批次时间过长。(批次过多)
什么原因?
UGUI中Canvas负责将几何图形合并成批,并且生成对应的渲染命令发送给Unity图形管线。而一个批次中所处理的事,就是Canvas合并网格并且生成合适的渲染命令发送给Unity图形管线。这个处理的结果会被缓存并且一直被重用,直到Canvas脏了。批次过多,DC也就过多,GPU渲染压力也就会过大。所以说,我们要尽可能地合并批次。
对Unity来说,渲染UI的顺序影响因素包括UI的深度 > 材质id > 贴图id > 渲染层级(相同条件下比较下一个因素),按照渲染UI的顺序,相邻且相同材质的UI可以合批。
优化方案
对于减少批次的优化方案,按顺序来,首先就是尽可能保证UI的材质都一样;其次就是尽可能保证UI的贴图都一样或者都来自同一图集;再次就是减少合批打断,可以通过调整UI的Hierarchy或者是调整UI的重叠来减少合批打断。
除此之外,如果Canvas上存在大量大型Graphic元素,在合并网络的时候,排序和分析将会花费大量时间。
情景二、片段着色器利用率过高(或者说GPU fill-rate填充率过高),即每个片段处理的时间过长。(可能是片段着色器过于复杂,也可能是每个片段要处理的东西太多/采样数量过多)
什么原因?
一部分原因可能是由于片段着色器的实在是过于复杂了。
优化方案
针对这一点的优化方案,无非就是降低片段着色器的复杂度。但一般情况下,UI渲染不会过多地修改片段着色器。所以说大部分情况下的原因还是每个片段下要处理(重叠)的UI数量过多,可能是相机过多导致一个片段下要处理很多UI,也可能是单纯重叠大量的UI所导致的。
那为啥过量重叠UI就会产生这样的问题呢?这个问题实际上就是常说的UI Overdraw(过度绘制),并且在移动端上对性能的影响格外明显。与正常的物体不同,Unity对于UI的渲染策略是当作半透明物体来进行渲染的,也就是说无论UI是不是包含透明度,都是后往前渲染的。
优化方案
针对这一点的优化方案,如:主动消除完全透明的UI;简化UI减少重叠区域;尽可能地关闭不使用的相机;美术层面上合并贴图等。
情景三、频繁重绘Canvas花费时间过长。(Canvas过脏)
重绘的原因?
何为重绘?或者说重绘干啥了?
Canvas的重绘把Graphic组件的布局、顶点、网格等全部重新计算了一次,或者说合批就是重绘中的一部分。当Canvas不需要重绘/合批的时候,Unity图形管线会直接用上次缓存的命令来进行后续的图形绘制。具体开销或者细节可以从CanvasUpdateRegistry中看出来。
至于什么情况下会导致Canvas重绘,这里列出了Unity中导致Canvas变脏的地方:
· 设置顶点脏——SetVerticesDirty,如RectTransform、Image中各种参数修改等;
· 设置材质脏——SetMaterialDirty,如Material、Texture相关修改等;
· 设置布局脏——SetLayoutDirty,如Layout系列组件进行的修改等;
· 设置上面三个都脏——SetAllDirty。
所以说,对于UI,基本上只要动了就会触发Canvas重绘。
优化方案:UI动静分离
动指的是元素移动,或放大缩小频率比较高的UI,静就是静止不动的,或者说动的比较少的UI,将动和静的UI元素分别放在不同的Canvas上即可。理论基础是子Canvas不会导致父Canvas重绘。
但是有一点需要注意,上面提到了Canvas负责UI合批,不同Canvas不能合批,也就是说采取动静分离的话,虽然减少了Canvas重绘,但是增加了批次。所以最早的时候说,在Unity中UI优化的核心问题就是重绘和批处理之间的平衡,原因就在于此。
上面也提到了,Canvas重绘的过程中就包括合并批次的操作,所以说,除了触发Canvas重绘的频率,导致合批开销过大的原因也会导致Canvas重绘的开销过大。
情景四、CPU生成顶点时间过长。(对UI来说,很多情况下是Text过多从而导致顶点过多)
Unity的Text组件实际上有很多坑.
先看Text是如何实现的。
Text在渲染的时候是根据字体将每一个字渲染成一个quad(两个三角形),所以很多情况下,每个字都存在着浪费的透明渲染区域。并且由于字形不同,每一个字的位置和字与字之间的空隙都不一样。这样就导致了下图出现的Overdraw的情况,并且很有可能在不经意之间就打断了合批。
当Text内容修改、enable或是disable的时候,每个字的mesh都将会被重新计算。
除此之外,如果使用Best Fit,那么就需要花费大量时间计算合适字体大小,并且在Unity5.4之前还会出现字体图集被大量填充的问题。
如果使用动态字体,那么就需要面临字体图集的重计算和大小扩展。
如果存在备用字体,那么就需要面临所有备用字体被提前加载完成的内存压力。
补充内容
CPU与GPU瓶颈
对于不同大小的Batch,CPU处理的时间都是一样的。也就是说,假设一个CPU一秒能处理100个Batch,当GPU未达到瓶颈时,Batch越大效率越高;但是当GPU达到瓶颈时,适当地增加Batch数量,减小Batch大小,也许是更好的选择。
这就好比是坐公交车,如果每辆车只坐1-2个人,那么适当增加每辆车的人数,可以有效地提升效率;但如果每辆车坐了1000个人,那么适当地增加公交车数量,将人数平摊一些,才是比较好的做法。
一、Canvas模式设置
非UI对象只要移出了视口,就不会进行批处理了。
而UI对象如果想要移出视口后不进行批处理,则需要将Canvas设置成WorldSpace模式或者Camera模式,而不是Overlay模式。否则一旦使用了Overlay模式,即使移出了视口也仍然会进行批处理。
一旦某个Canvas上存在一个UI在视口内(需要被渲染),则整个Canvas上的所有UI都需要被批处理。所以说要么把Canvas移出去很远(保证Canvas上的所有内容不渲染),要么不移出去(渲染Canvas上的所有内容)。
二、为无交互的元素禁用Raycast Target
UI元素具有Raycast Target选项,允许该元素通过单击、触摸和其他用户行为进行交互。当以上任何一个动作发生时,GraphicsRaycaster 组件将执行像素到边界框检查,以确定与之交互的是哪个元素,这是一个简单的迭代for 循环。对非交互元素禁用此选项,就减少了GraphicsRaycaster需要迭代的元素数量,提高了性能。
参考文章
Optimizing Unity UI - Unity Learn文章来源:https://www.toymoban.com/news/detail-761959.html
Unity UI优化总结_Don里个冬的博客-CSDN博客文章来源地址https://www.toymoban.com/news/detail-761959.html
到了这里,关于Unity优化之UI篇(UGUI)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!