Unity优化之UI篇(UGUI)

文章探讨了Unity中UI优化的关键问题,包括如何平衡Canvas的重绘和批次处理,减少DrawCall,优化片段着色器复杂度以降低GPU填充率,以及解决Text组件导致的顶点过多问题。建议通过动静分离、合理分配Canvas、减少不必要的重绘和合批打断来提高性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

        在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

Unity UI优化总结_Don里个冬的博客-优快云博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值