UGUI优化(转)
原文地址:UGUI进阶知识[十]UGUI优化知识点和一些底层特性_张家二歌的博客-CSDN博客
UGUI组件绘制原理
UGUI的UI分为两类:Layout(布局,继承自LayoutGroup)和Graphics(显示,继承自Graphics)。Graphic类继承自ICanvasElement接口,这个接口限制了继承自其的子类的脚本所挂的物体必须作为挂载了Canvas组件的物体的子物体,下文要说到的关于显示类UI重建的方法声明在ICanvasElement接口里面,方法名字叫做Rebuild。
Graphic类有个比较重要的自类MaskableGraphic。其继承自IMaskable接口,继承自这个接口的子类能够被Mask组件的剔出效果影响。
Rebatch:Canvas收集所有子物体的CanvasRenderer组件的渲染绘制信息,并对其进行批处理和合并。
Canvas.WillRenderCanvases这个函数是canvas的一个代理,每一帧准备进行绘制的时候会调用,在profilter中可以看到,如果占用过高说明canvas的某个/某些UI界面元素的重建开销较大,可以通过动静分离UI来优化。
CanvasUpdateRegistry:这个类保存了更新的队列,所有下一帧需要被rebuild的UI都会加入这个队列中,这些UI包括显示类型的UI和排序类型的UI.PerformUpdate是Canvas的WillRenderCanvases代理的注册函数,在这里执行每个UI的rebuild方法和mask的裁剪操作。
对于一些自动排序的组件来说,他们继承的基类LayoutGroup会使用到用于重排序的类,名字叫做LayoutRebuilder.其下的重要方法是MarkLayoutRootForRebuild,这个方法对需要重绘的排序UI注册进CanvasUpdateRegistry的更新队列,所有能被排序的类都继承自ILayoutElement。
显示类型的UI组件,继承自Graphic类,生成顶点和三角面的时候会调用重写自Graphic的OnPopulateMesh(VertexHelper toFill)
方法,这个方法传入的VertexHelper参数用来收集绘制所需的顶点和三角面。
UGUI的两种Mask
RectMask2D只能以其所在的物体的Recttransform的矩形区域为边界进行剪切,子物体超出其 RectMask2D的边界部分的顶点直接舍弃掉。
Mask组件使用到了shader里面的模板缓冲(StencilBuffer)的原理。
UGUI合批
UGUI的合批以Canvas为单位,不同的Canvas之间的UI组件是不能够合批的。
合批的条件:排序后相邻的UI元素、UI组件相同、材质相同、texture相同。
UGUI首先会根据一定的排序规则将所有的UI组件以一定的顺序存储,这个顺序是深度的降序顺序存储,然后按照合批规则,依照存储顺序依次判断他们存储顺序中相邻的UI组件是否能进行合批,所以能合批的UI一定是在存储顺序中连续相邻的两个或几个UI。
排序:
深度排序:不显示的UI深度值为-1,不用管,接着判断是否该元素底部是否有物体,如果没有则赋值Depth为0,如果盖住物体(这块是通过Mesh进行判断,判断Mesh是否相交)则等于底部盖住的UI元素中Depth最大的值+1,如果两个相邻元素通过了合批测试,则这两个相邻元素的深度值相等。
深度排序之后,就会根据matID进行排序,如果材质相同则对ImgID进行排序,如果也相同,那会根据inspection面板上的RendererOrder,最后真正进行UI的合批。
Mask和RectMask2D对合批的影响
Mask:Mask组件本身因为其材质shader要做遮挡剔除和其他的不一样,本身的材质就占用两个drawcall,并且因为不一样而导致外部的image不能与mask本身的image合批,Mask下面的Image也不能和非Mask下的Image合批
结论:同深度、同材质、同tex的Mask之间能合批,不同Mask下的子物体也能合批。
RectMask2D:RectMask2D实现剪裁是在c#和c++代码中通过将子物体的在RectMask2D的矩形区域外的部分的网格裁剪掉从而达到遮挡剔除的效果的,区域外是没有任何的顶点或者三角面产生的,这种做法完全不会产生任何的drawcall(或者说batch)。
结论:同深度、同材质、同tex的RectMask2D之间可以合批,任意两个ReckMask2D下的子物体UI不能合批,但同一RectMask2D下的子物体可以合批。
优化策略
Overdraw:像素在一帧里被绘制多少次,理论上一次是最优的(没有遮挡关系),可以在查看。
重建:在UGUI中,一般没有更改的组件不会每帧都进行重新计算并绘制,而是每帧绘制的时候使用的是上一次计算好后缓存的计算数据。重建是以组件为单位的,不是canvas。
对于显示类型的组件来说UGUI机制里面有个名词叫做脏标记(Dirty),表示组件有了显示上的更改需要被重新计算并绘制,设置成脏标记的UI组件将在下一帧执行完批处理的时候被重新计算并绘制,一个组件如果被更改之后没有设置成脏标记,则其绘制使用的依然是上一次计算的数据,所以样子没有改变。脏标记包括顶点脏标记,材质脏标记(在更换材质的时候标记,只改material参数不标记),布局脏标记。
在OnEnable调用的时候,上述代码的SetAllDirty方法会被执行,也就是说当禁用或者启用某个显示类型的UI组件或者组件所在的物体的时候,它都会被重建一次。
优化
1.降低overdraw
像边框这类的图片可以用UnitySpriteEditor的九宫格去掉中间的像素
UGUI射线点击的判断过程用的是recttransform矩形区域而不是网格顶点,基于此那些不需要显示的(透明度为0)的btn可以去掉顶点信息。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class TransparentImage : Image
{
protected override void OnPopulateMesh(VertexHelper toFill)
{
toFill.Clear();
}
}
2.降低batch
在使用多个mask的时候可以使用mask组件,使用一个mask的时候可以考虑rectmask2d
在摆放UI或者生成UI的时候,尽量考虑是否符合UGUI合批规则,尽量多的减少UI的drawcall,可以通过改变Hierarchy层级,考虑更改覆盖关系,尽量使用相同材质,相同texture的手段。
同一层的image打成一个图集,可以合批。
3.降低重建
对于更改会设置脏标记的UI的属性,尽量考虑采用不会引起脏标记的方法进行设置,例如更改image颜色的时候,不采取直接更改Image的color属性,而是设置一个材质,更改shader的颜色。
UI动静分离,或者在常发生重建的UI上加canvas。
减少使用setactive,用CanvasRenderer组件的Cull Transparent Mesh代替,如果要控制一个物体以及其所有子物体的显隐,可以临时给这个物体添加Canvas Group组件,通过CanvasGroup的alpha值来控制显隐。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了