上期我们学习了简单的Scene优化,接下来我们继续编辑器创建资源的UGUI优化
UI篇(UGUI)
优化UGUI应从哪些方面入手?
可以从CPU和GPU两方面考虑,CPU方面,避免触发或减少Canvas的Rebuild和Rebatch,减少Drawcall,减少CPU处理顶点的时间;GPU方面,降低Overdraw,缩小纹理大小。
Canvas的Batch构建过程(The Batch building process),简称Rebatch
Batch 构建过程是指Canvas通过结合网格绘制它所承载的UI元素,生成适当的渲染命令发送给Unity图形流水线。Batch的结果被缓存复用,直到这个Canvas被标为dirty,当Canvas中某一个构成的网格改变的时候就会标记为dirty,这个Dirty就会触发Rebatch。
Rebatch不仅有批处理排序,还有网格合并之类的。Canvas的网格从那些Canvas下的CnavasRenderer组件中获取,但不包含任何子Canvas。
所以对于UGUI的性能分析,要分开两点
Canvas的批处理过程 Rebatch
Graphic 和Layout的 Rebuild
这两点,都会影响性能。但是Rebatch是有多线程的加持的,而Rebuild是在主线程的。
Rebuild自身会有性能消耗,同时Rebuild会触发Rebatch。
Rebatch除了被Rebuild触发,还会被其他情况触发。
Rebatch的性能上的问题,在电脑上比较难看的出来,因为有多线程加持。
Rebuild过程
Rebuild 过程是指Layout和Graphic组件的网格被重新计算,这是在CanvasUpdateRegistry类中执行的。这是一个C#类,打开UI的源码,它里面的一个函数,叫做PerformUpdate,当一个Canvas组件触发它的WillRenderCanvases事件时,这个方法就会被执行。这个事件每帧调用一次,这也是为什么我们看Profile的时候,出现性能高峰的总是会看到WillRenderCanvases的原因了。
PerformUpdate的运行过程分3步:
按顺序遍历调用Dirty的Layout组件的Rebuild函数
要求任何已注册的裁剪组件(例如Mask),剔除所有裁剪的组件。这是通过ClippingRegistry.Cull完成的。
按顺序遍历调用Dirty的Graphic组件的Rebuild函数
Rebuild分为 Layout Rebuild 和 Graphic Rebuild
Layout Rebuild
要重新计算一个或多个Layout组件中包含的组件的适当位置(和可能的大小),必须按其适当的层次结构顺序应用Layouts。在GameObject层次结构中靠近根部的布局可能会更改嵌套在其中的任何布局的位置和大小,因此必须首先进行计算。
为此,UGUI根据层次结构中的深度对dirty的Layout组件列表进行排序。层次结构中较高的Layout(即,父节点较少)将被移到列表的前面。
然后,排序好的Layout组件的列表将被rebuild,在这个步骤Layout组件控制的UI元素的位置和大小将被实际改变。
Graphic Rebuild
当Graphic组件被rebuild的时候,UGUI会将控制权传递给ICanvasElement接口的Rebuild方法。Graphic执行了这一步,并在rebuild过程中的PreRender阶段运行了两个不同的rebuild步骤:
如果顶点数据已标记为Dirty(例如,当组件的RectTransform的大小更改时),则将重新构建网格。
如果将材质数据标记为Dirty(例如,当更改组件的材质或纹理时),则将更新附加的Canvas Renderer的材质。
Graphic的Rebuild不会按照Graphic组件的特殊顺序进行,并且不需要任何排序操作。
Rebatch和Rebuild的触发条件总结
触发Rebatch的条件:
当Canvas下有Mesh发生改变时,如:
SetActive
Transform属性变化
Graphic的Color属性变化(改Mesh顶点色)
Text文本内容变化
Depth发生变化
触发Rebuild的条件:
Layout修改RectTransform部分影响布局的属性
Graphic的Mesh或Material发生变化
Mask裁剪内容变化
Unity UI性能的四类问题
1. Canvas Re-batch 时间过长
2. Canvas Over-dirty, Re-batch次数过多
3. 生成网格顶点时间过长
4. Fill-rate overutilization
Canvas画布
Canvas负责管理UGUI元素,负责UI渲染网格的生成与更新,并向GPU发送DrawCall指令。
Canvas Re-batch过程(合批)
1. 根据UI元素深度关系进行排序
2. 检查UI元素的覆盖关系
3. 检查UI元素材质并进行合批
合批对比
UGUI渲染细节
- UGUI中渲染是在Transparent半透明渲染队列中完成的,半透明队列的绘制顺序是从后往前画,由于UI元素做Alpha Blend,我们在做UI时很难保障每一个像素不被重画,UI的Overdraw太高,这会造成片元着色器利用率过高,造成GPU负担。
- UI SpriteAtlas图集利用率不高的情况下,大量完全透明的像素被采样也会导致像素被重绘,造成片元着色器利用率过高;同时纹理采样器浪费了大量采样在无效的像素上,导致需要采样的图集像素不能尽快的被采样,造成纹理采样器的填充率过低,同样也会带来性能问题。如下图
Re-Build过程(重构)
1.在WillRenderCanvases事件调用PerformUpdate::CanvasUpdateRegistry接口
通过ICanvasElement.Rebuild方法重新构建Dirty的Layout组件
通过ClippingRegistry.Cullf方法,任何已注册的裁剪组件Clipping Compnents(Such as Masks)的对象进行裁剪剔除操作
任何Dirty的 Graphics Compnents都会被要求重新生成图形元素
2.Layout Rebuild
UI元素位置、大小、颜色发生变化
优先计算靠近Root节点,并根据层级深度排序
3.Graphic Rebuild
顶点数据被标记成Dirty
材质或贴图数据被标记成Dirty
使用Canvas的基本准则
- 将所有可能打断合批的层移到最下边的图层,尽量避免UI元素出现重叠区域,尤其是一些很小的ui元素,比如字体,是一个很小的矩形,容易被忽略,但是每一帧都会导致重新合批
- 可以拆分使用多个同级或嵌套的Canvas来减少Canvas的Rebatch复杂度
- 拆分动态和静态对象放到不同Canvas下,来避免动态UI元素导致每帧都要做太复杂的canvas Rebatch操作
- 不使用Layout组件,这样就不会有太多的rebuild 的过程了,同样可以减少Rebatch的时间消耗
- Canvas的RenderMode尽量Overlay模式,减少Camera调用的开销
UGUI射线(Raycaster)优化
- 必要的需要交互UI组件才开启“Raycast Target”
- 开启“Raycast Targets”的UI组件越少,层级越浅,性能越好
- 对于复杂的控件,尽量在根节点开启“Raycast Target”
- 对于嵌套的Canvas,OverrideSorting属性会打断射线,可以降低层级遍历的成本
UGUI优化思路
1.Canvas 子节点动静分离
在UI Canvas 中进行子节点动静分离,主要是将频繁变动的UI元素,如动态文本动画图标与相对静态的元素,如背景图,固定按钮分开管理。这样可以减少不必要的重绘,提高性能。通常将动态节点放在一个单独的Canvas或者层级下。在需要更新时只重绘该部分,避免影响影响静态元素。
2.Sprite Packer
是将多个小的UI精灵纹理合并成一个大纹理图集的工具,这有助于减少纹理加载次数和内存占用,提高渲染性能
3.运行时动态合并图集
当然UI运行时动态合并图集性能,
1.减少合并频率
2.合并前筛选出真正需要合并的元素,避免不必要的操作
3.尽量采用多线程技术来处理合并任务,防止阻塞主线程
4.对图集大小合理限制,防止内存占用过大
4.自动布局组件
不要使用Layout组件,Layout组件性能消耗相对昂贵,会大大地增加Canvas.SendWillRenderCanvases函数耗时,利用好RectTransform同样可实现简单布局。
5.动态加载和裁剪
对于UI动态加载,可以采用对象池来管理UI元素,避免频繁的实例化和销毁,在裁剪方面,设置合理的裁剪区域,减少不必要的计算,同时利用层级结构和遮挡关系,减少渲染压力。
6.组件中自定义材质球打断合批
在UI组件中,自定义的材质球打断合批主要是因为其改变了渲染状态,当材质球的shader,纹理等属性与其他UI组件不同时,就无法合批渲染,可以通过统一材质属性或者使用相同的材质模板来减少这种打断,提高渲染效率
7. Raycat Target
1.只需要在交互的UI元素上勾选,如按钮等。
2.使用射线检测范围更小的组件替换
3.将不需要检测的UI元素放在不勾选Raycat Target的父物体下,利用层级关系减少不必要的检测
8.不可见元素 Cull Transparent Mesh
对于UI中不可见元素,使用Cull Transparent Mesh(剔除透明网格)可以提高性能,它通过不渲染这些不可见的透明元素,减少了渲染计算量和内存占用,一般在合适的渲染管线设置中开启此功能即可
9.UI 字体(TTF和OTF)
1.选择简洁的字体,减少复杂笔画
2.使用字体图集,合并常用字符纹理
3.根据需要动态加载字体资源
4.避免字体框重叠,造成合批打断
5.字体网格重建,UIText组件发生变化时,父级对象发生变化时,UI组件或其父对象enable和disable时
谨慎使用Text的Best Fit选项,虽然这个选项可以动态的调整字体大小以适应UI布局而不会超框,但其代价是很高的,一方面是适配本身在调整文本框大小时有CPU耗时开销,另一方面每个字号下新生成的字都会在Font Texture上占用一个字的大小,容易导致Font Texture过大(这个类似图集,当Font Texture当前大小放不下时才会占用更多内存)。这个特定问题已在 Unity 5.4 中得到纠正,Best Fit 不会不必要地扩展字体的纹理图集,但仍然比静态大小的文本慢得多。
10.计时器和倒计时,触发UI重绘
11.Sprite 九宫格设置
如果是较大的背景图的UI元素建议也要使用Sprite的九宫格拉伸处理,充分减小UI Sprite大小,提高UI Atlas图集利用率
12.GameObject Active/Deactive -> UI CanvasGroup Alpha
13.Canvas Pixel Perfect
谨慎使用Canvas的Pixel Perfect选项,该选项会使得ui元素在发生位置变化时,造成layout Rebuild。(比如ScrollRect滚动时,如果开启了Canvas的pixel Perfect,会使得Canvas.SendWillRenderCanvas消耗较高)
14.shadow 顶点和面数翻倍
15.outline 顶点和面数翻倍
慎用自带组件Outline和Shadow,都是通过重复绘制多个Mesh实现的,其中Shadow绘制为原文本Mesh的2倍,而Outline为5倍,对渲染面数、顶点数,BuildBatch和SendWillRenderCanvases的耗时,Overdraw都有影响。如经常用,可考虑其它方式,如TextMeshPro,或把阴影和描边做到字体里。
16.元素 position Z值不为 0 打断合批
17.Mask vs RectMask2D
Mask依赖Image组件,占用两个Batch,多一倍Overdraw,可以裁剪任意形状。
RectMask2D不依赖Image组件,不占用Batch,没有Overdraw,只能裁剪规则形状。
因此,一般情况下,规则的裁剪尽量用RectMask2D代替Mask,特别是在使用ScrollRect时。
RectMask2D一定比Mask好吗?并不是,Mask间是可以合批的,而RectMask2D间不行,因此当要使用多Mask时,如背包界面中的道具格子,每个格子有裁剪需求时,尽量用Mask,Mask可合批,而RectMask2D会导致合批被打断。
因此:
- 当一个界面只有一个Mask,那么RectMask2D优于Mask;
- 当有两个Mask,那么两者差不多;
- 当大于两个Mask,那么Mask优于RectMask2D。
18.避免多层 激活UI元素堆叠
19.加载页/活动图等几乎充满屏幕的UI,关闭场景相机渲染,或渲染一张RT模糊后作背景
20.文本组件建议使用Text Mesh Pro
21.界面操作一般会触发UI的开关或者隐藏显示,Active 和DeActive,必然会造成UI重建。而采用控制Canvas组件的激活与关闭。
22.不需要参与点击事件的Canvas取消激活Graphic Raycaster 脚本。
射线检测遍历所有将'Raycast Target'设置为true的Graphic组件。每一个Raycast Target都会被进行测试。如果一个Raycast Target通过了所有的测试,那么它就会被添加到“被命中”列表中。
每个Graphic Raycaster都将遍历 Transform层次结构一直到根,此操作的成本与层次结构的深度成比例线性增长。因此进行射线检测的元素越多,层级越深,消耗越高。
鉴于所有射线检测目标都必须由Graphic Raycaster进行测试,因此最好的做法是仅在必须接收点击事件的UI组件上启用'Raycast Target'设置。检测目标列表越少,遍历的层级越浅,每次射线检测的速度越快。
23.持续性的UI动态效果特效,最好采用特效的方式制作,脱离UI系统。
24.动态合静态的UI 要分开,分别挂上canvas。
25. 在使用非全屏但模态对话框时,建议使用**OnDemandRendering**接口,对渲染进行降频。
26.优化裁剪UI Shader,根据实际使用需求移除多余特性关键字。
27.滚动视图Scroll View优化
- 使用RectMask2d组件裁剪
- 使用基于位置的对象池作为实例化缓存
这篇文章是摘取自多个大神的文章,所以会有知识点重复的情况,但是我主张的是可以重复,但是知识点一定要全!!!
今天是2024年11月30日
重复一段毒鸡汤来勉励我和你
你的对手在看书
你的仇人在磨刀
你的闺蜜在减肥
隔壁的老王在练腰
而你在干嘛?