UGUI优化相关

1.打断合批的操作:

1.材质和图片的不同(相同的图集不算图片不同,本质上都是用的那个图集)。

2.不可合批的UI的Mesh重叠:会打断排序(让后面的UI的深度都+1),这会导致其前面的UI和后面的UI分成两个批次

比如:四个UI:image1,image2,text1,image3,image4;

如果text1和image3不重叠:那image1、image2、image3,image4还是同一深度,还是可以合批的,最后就是两个DC。

如果text1和image3重叠:那image1、image2、image4和image3的深度就不相等了,最后image1、image2、image4一个DC,text1一个DC,image3一个DC。

 

 

3.Mask:下面说

4.UI层次结构发生变化:新增、删除都会引起整个CanvasUI顺序更新。

2.Mask

Mask的实现是使用模板缓冲,要额外增加两个dc,一个用来在绘制元素前修改模板缓冲的值,另一个用来在所有UI绘制完后将模板缓冲的值回复原样。

Mask的dc数是两个额外drawcall再加上mask下面的UI的drawcall,多个Mask之间可以合批(多个mask的第一个dc、第二个dc、下面的子物体的dc可以合批),Mask和外面的UI不能合批。重叠的Mask(mask的子物体有重叠也不能合批,哪怕重叠的部分没显示出来)不能合批。

RectMask2D是在C#层先将UI不在其父物体矩形范围内的顶点透明度设置为0,然后通过Shader丢弃掉透明度小于0.001的元素,不需要创建新的材质所以也不需要增加额外的dc,但会打断合批。

RectMask的dc数就是子物体的dc数(子物体可以合批),但多个RectMask不可以合批,RectMask和其他UI也不可以合批。

使用环境:

当需要使用不规则遮罩的话那就只能使用Mask,其他情况看需要遮罩的数量,如果多就使用Mask(可以合批,额外的dc可以忽略),少的话就RectMask2D。

3.Rebuild和Rebatch

在Unity的UGUI系统中,Rebuild和Rebatch是两个关键的性能消耗点。

1.rebuild

Rebuild发生在C#层面,是指UGUI库中layout组件调整RectTransform尺寸、Graphic组件更新Material,以及Mask执行Cull的过程。

这个profiler的监测函数是Canvas.SendWillRendererCanvses。

主要包括两个部分:

1.布局重建(LayoutRebuild)

CanvasUpdateRegistry.TryRegisterCanvasElementForLayoutRebuild(rebuilder)

当UI元素的布局需要更新时,例如SetActive、transform的改变(有layout组件并影响到了布局,比如父物体有layout,改了子物体的scale(rotation测下来是不影响))、更改父物体、颜色改变、文本内容改变等会触发SetLayoutDirty的操作。

2.图形重建(GraphicRebuild)

CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this)

当UI元素的图像需要更新时,例如RectTransform属性的更改(不是Transform的属性,width、pivot等,不包括posXYZ、Rotation、Scale等)以及文字的变化、图片的修改、颜色的改变等等。

rebuild的操作就是将发生改变的UI放到CanvasUpdateRegistry.m_LayoutRebuildQueue和CanvasUpdateRegistry.m_GraphicRebuildQueue中,在Canvas.willRenderCanvases(会在渲染前进行每帧调用)中遍历并调用其Rebuild()方法(重建网格、更新材质、重新layout)

3.UI重建的具体方法Rebuild()(网格重建和图形重建都是走到这里)

以Graphc的Rebuid方法为例,分两个部分:

UpdateGeometry();//更新网格顶点信息,就是确定每一个UI元素Mesh的信息,包括顶点数据、三角形数据、UV数据、顶点色数据。这一步是在c++实现的,所以效率强于在C#实现的NGUI。

UpdateMaterial();//更新渲染材质信息。

优化:

1.降低UI的更新频率,采取分帧策略

2.简化UI,使用更简单的方法实现效果,比如用固定的美术字替代OutLine或者ShadowMap(这两个都是做文本美化的,而且很费)。

3.关注Font.CacheFontForText,由于一次性写入很多新的字符,导致FontTexture纹理扩容。(这个可以用TextMeshPro来代替)

2.Rebatch

Rebatch发生在C++层面,是指Canvas分析UI节点生成最优批次的过程。Batch以Canvas为单位,同一个Canvas下的UI元素最终都会被Batch到一个Mesh中。Batch前,UGUI根据UI材质以及渲染顺序重排,在不改变渲染结果的前提下,尽可能将相同材质的UI元素合并在同一个SubMesh中,以减少DC。Batch只在UI元素发生变化时进行,合成的Mesh越大,耗时越大。重建对Canvas下所有ui元素生效,不论是否修改过。

这个profiler的监测函数是Canvas.BuildBatch(这个过程是在主线程发起,子线程中执行的,当子线程压力过大或者合并的UI网格过于复杂的时候,会在主线程产生等待,等待的耗时会被统计到EmitWorldScreenspaceCameraGeometry中。)。

触发条件:

1.Mesh发生改变:例如SetActive、transform的改变、颜色改变、文本内容改变等。

2.Canvas数据更新,这个就是Canvas自己掌控了,我们无法干涉。

3.UI层次结构发生改变,新增、删除UI或UI子节点。

优化

针对Rebatch的优化主要是针对这个过程的速度进行优化(毕竟基本只要改UI就会触发这个)。

1.动静分离,缩小更新的canvas的复杂度,将频繁变动的UI和不变的UI放到两个Canvas中。

2.减少节点层次和数量:合批计算量小。

3.使用相同材质的Ui尽量保持深度相同:这样对合批算法友好,速度快。

4.尽量不要打断合批。

3.SyncTransform

在Unity 2018版本及以后的版本中,Canvas下某个UI元素调用SetActive(false改成true)会导致该Canvas下的其他UI元素触发SyncTransform,导致UI更新的整体开销上升,在Unity 2017的版本中只会导致该UI元素本身触发SyncTransform。

所以,针对UI元素(如Image、Text)特别多的Canvas,需要注意是否存在一些UI元素在频繁地SetActive,对于这种情况建议使用SetScale(0或者1)来代替SetActive(false或者true)。或者,也可以将Canvas适当拆分,让需要进行SetActive(true)操作的元素和其他元素不在一个Canvas下,就不会频繁调用SyncTransform了。

 4.图集相关

1.图集的尺寸越小越好,毕竟越小的图集加载越快。

2.同一个界面的图集打到一起,这样界面的图片都可以合批并且只需要加载这一张图集。

3.上限设为1024或者2048,有图片宽高超过的话就单独拿出来或者分成两张做成两个UI。

4.图集设置:间隔、压缩格式、mipmap(UI基本不需要,贴图可能需要)等

5.一个界面过大导致一个图集不够的话可以分成两个或者加大图集尺寸(权衡)。

6.当图集设置和图集内图片的设置冲突的情况以图片为准(这个最好用工具弄,导入图片和打图集都自动化,防止出错)。

7.图集中图片发生粘连可以调整图片的meshtype或者增加图片精度。

5.SetActive优化

SetActive虽然使用起来很省事,也不容易出bug,但是过多的使用这个会导致一些性能问题:

1.会触发当前对象及子物体的OnEnable和OnDisable方法,这两个方法都会产生GC。

2.会造成当前UI和其子UI的Rebuild(在SetActive(true)的时候,SetActive(false)不会触发)ReBatch。

在说优化方法之前,说一下OnEnable和OnDisable为什么会有GC:

1.Graphic脚本的OnEnable中写了很多GetComponent的逻辑,这个会有GC。

2.Imge和Text等脚本也在OnEnable和OnDisable中写了一些额外逻辑(将网格信息在内存中存储、移除),会产生GC,而且texture和文本越大,网格信息越大,gc越多。

优化方法

1.使用Canvas更改layer + 非Canvas改CanvasRender.cull 的方式,让摄像机不渲染UI,缺点:只支持Canvas是screenspacecamera模式而且会出现新加入的子节点不会自己改layer(需要加额外逻辑)并且与RectMask2D冲突、并且隐藏的UI要继承自Graphic。

2.加canvasgroup,改alpha和Interactable。测下来是GC基本没了,速度快了很多。缺点:依然有DrawCall,但不会Rebatch。改CanvasGroup.Alpha的操作不是依次改子物体的color,而是子物体的颜色值要和上层的canvasgroup的alpha相乘。

但上面两种方法都或多或少有点问题,需要用的自己处理,而且关掉的UI本质上还是活跃在场景中的(只是不渲染,但不会Rebatch),unity的生命周期函数还是走的(update),如果在update或者OnPointerClick等函数中有写逻辑记得屏蔽

还可以将位置改到摄像机外或者改scale为0,这两种方式都不会触发Rebuild,但会触发Rebatch,而且问题也很多,这边就不细说了。

代码:原文:聊一聊UGUI优化SetActive - 知乎 (zhihu.com)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
using System.Collections.Generic;
using UnityEngine;
 
[XLua.LuaCallCSharp]
public static class UnityUtil
{
    static int UILayer = LayerMask.NameToLayer("UI");
    static int HiddenLayer = LayerMask.NameToLayer("Hidden");
    static List<Canvas> s_CanvasCacheList = new List<Canvas>();
    static List<CanvasRenderer> s_RendersList = new List<CanvasRenderer>();
    static List<MaskableGraphic> s_tempGraphics = new List<MaskableGraphic>();
 
    public static T GetOrAddComponent<T>(this GameObject go) where T : Component
    {
        T comp = go.GetComponent<T>();
        if (!comp)
        {
            comp = go.AddComponent<T>();
        }
        return comp;
    }
    public static T GetOrAddComponent<T>(this Transform t) where T : Component
    {
        T comp = t.GetComponent<T>();
        if (!comp)
        {
            comp = t.gameObject.AddComponent<T>();
        }
        return comp;
    }
     
    /// <summary>
    /// the fast version set uGUI active
    /// </summary>
    /// <param name="go"></param>
    /// <param name="value"></param>
    public static void SetUIActiveFast(this GameObject go, bool value)
    {
        ///if active false, must set active.
        if (!go.activeSelf)
        {
            go.SetActive(value);
            return;
        }
 
        ///if game object is canvas, set layer fastest.
        if (go.GetComponent<Canvas>())
        {
            go.GetComponentsInChildren(false, s_CanvasCacheList);
            foreach (var canvas in s_CanvasCacheList)
            {
                canvas.gameObject.layer = value ? UILayer : HiddenLayer;
            }
            s_CanvasCacheList.Clear();
            return;
        }
 
        ///otherwise, set cull. unknown bug.
        go.GetComponentsInChildren(false, s_RendersList);
        foreach(var render in s_RendersList)
        {
            render.cull = !value;
        }
        s_RendersList.Clear();
 
        ///Refresh the material's Cliprect
        go.GetComponentsInChildren(false, s_tempGraphics);
        foreach (var render in s_tempGraphics)
        {
            render.SetMaterialDirty();
        }
        s_tempGraphics.Clear();
    }
 
    /// <summary>
    /// the slow version set uGUI active
    /// </summary>
    /// <param name="go"></param>
    /// <param name="value"></param>
    public static void SetUIActiveSlow(this GameObject go, bool value)
    {
        ///if active false, must set active.
        if (!go.activeSelf)
        {
            go.SetActive(value);
            return;
        }
 
        ///last way, set alpha.
        ///the performance of the scheme setting alpha was one third of that of direct GameObject.SetActive
        var group = go.GetOrAddComponent<CanvasGroup>();
        group.alpha = value ? 1f : 0f;
        group.blocksRaycasts = value;
    }
}

  

posted @   mc宇少  阅读(588)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示