IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> UGUI-Rebuild 网格重建(画布刷新)系统 -> 正文阅读

[游戏开发]UGUI-Rebuild 网格重建(画布刷新)系统

几个比较重要的类和接口:

Canvas、CanvasUpdateRegistry、ClipperRegistry、LayoutRebuilder、LayoutGroup、Graphics、MaskableGraphic。

ICanvasElement、ILayoutElement。

刷新的大致过程:由Canvas控制,通过 ICanvasElement 接口,使用脏标记方法SetDirty()来统一更新CanvasElement。

几个问题:脏标记法是什么?SetDirty()具体在哪些地方被调用?Rebuild的具体过程是什么?如何进行优化?这些问题将在下面解决。

目录

1 基础

1.1 脏标记方法

1.2 Canvas

1.3 子Canvas

1.4 Graphic

1.5 Layout

1.6 网格重建分为2部分:一个是Batch,一个是ReBuild

2 Canvas

2.1 Canvas.WillRenderCanvases事件

2.2 CanvasUpdateRegistry

2.3 PerformUpdate

3 Rebuild

3.1 Layout的Rebuild

3.2 Graphic的Rebuild

3.3 Rebatch和Rebuild的触发条件总结

4 优化

4.1 动静分离

4.2 其它


1 基础

1.1 脏标记方法

脏标识模式:脏标识模式 · Optimization Patterns · 游戏设计模式

将工作推迟到必要时进行,以免做没必要的工做,比如被销毁的物体的计算、父子关系的物体重复计算。

1.2 Canvas

Canvas是一个Native层实现的Unity组件,被Unity渲染系统用于在游戏世界空间中渲染分层几何体(layered geometry)。 Canvas负责把它们包含的Mesh合批,生成合适的渲染命令发送给Unity图形系统。以上行为都是在Native C++代码中完成,我们称之为Rebatch或者Batch Build,当一个Canvas中包含的几何体需要Rebacth时,这个Canvas就会被标记为Dirty状态。

几何图形由Canvas Renderer 组件提供给 Canvases 。

1.3 子Canvas

Canvas组件可以嵌套在另一个Canvas组件下,我们称为子Canvas,子Canvas可以把它的子物体与父Canvas分离,使得当子Canvas被标记为Dirty时,并不会强制让父Canvas也强制Rebuild,反之亦然。但在某些特殊情况下,使用子Canvas进行分离的方法可能会失效,例如当对父Canvas的更改导致子Canvas的大小发生变化时。

1.4 Graphic

Graphic是Image、RawImage、Text类的基类。大多数Unity内置的继承Graphic的类都是通过继承一个叫MaskableGraphic的子类来实现,这使得他们可以通过IMaskable接口来被隐藏。

1.5 Layout

Layout控制着RectTransform的大小和位置,通常用于创建复杂的布局,这些布局需要对其内容进行相对大小调整或相对位置调整。Layout仅依赖于RectTransforms,并且仅影响其关联RectTransforms的属性。这些Layout类不依赖于Graphic类,可以独立于UGUI的Graphic类之外使用。

1.6 网格重建分为2部分:一个是Batch,一个是ReBuild

Batch:就是Canvas 负责将其子节点的 UI 元素网格合并,并生成相应的渲染命令再发送到 Unity 的图形管道的过程。

通俗来讲,Canvas 就是渲染 UI 的组件,所以当 UI 变化了,它就要执行一次 Batch,给 GPU 进行渲染。

Rebuild:Layout和Graphic的更新称为Rebuild,指重新计算布局和网格的过程,这个过程在CanvasUpdateRegistry中执行。 在CanvasUpdateRegistry中,最重要的方法是PerformUpdate。每当Canvas组件调用WillRenderCanvases事件时,就会调用此方法。此事件每帧调用一次。

rebuild是batch的子操作,一次batch需要各组件执行自己的rebuild操作。

2 Canvas

2.1 Canvas.WillRenderCanvases事件

当Canvas需要重绘时会调用Canvas.SendWillRenderCanvases()方法。

 ?public sealed class Canvas : Behaviour
  {
 ? ?public delegate void WillRenderCanvases();
 ? ?public static event Canvas.WillRenderCanvases willRenderCanvases;
?
 ? ?public static void ForceUpdateCanvases() => Canvas.SendWillRenderCanvases();
?
 ?  [RequiredByNativeCode]
 ? ?private static void SendWillRenderCanvases()
 ?  {
 ? ? ?if (Canvas.willRenderCanvases == null)
 ? ? ? ?return;
 ? ? ?Canvas.willRenderCanvases();
 ?  }
  }

SendWillRenderCanvas()方法中调用Canvas.willRenderCanvases()事件。

2.2 CanvasUpdateRegistry

CanvasUpdateRegistry(画布更新注册处)是一个单例,它是UGUI与Canvas之间的中介,继承了ICanvasElement接口的组件都可以注册到它,它监听了Canvas即将渲染的事件,并调用已注册组件的Rebuild等方法。

CanvasUpdateRegistry的构造函数:

 ? ?//CanvasUpdateRegistry 被初始化时向Canvas中注册了更新函数(PerformUpdate),以用来响应重建。
    protected CanvasUpdateRegistry()
 ?  {
 ? ? ? ?Canvas.willRenderCanvases += PerformUpdate;
 ?  }

willRenderCanvases是Canvas的静态事件,事件是一种特殊的委托,在渲染所有的Canvas之前,抛出willRenderCanvases事件,继而调用CanvasUpdateRegistry的PerformUpdate方法。

CanvasUpdateRegistry维护了两个索引集(不会存放相同的元素):

 ? ?//IndexedSet是Unity中吸取了List和Dictionary各自优点的一种容器
    private readonly IndexedSet<ICanvasElement> m_LayoutRebuildQueue = new IndexedSet<ICanvasElement>();
 ? ?private readonly IndexedSet<ICanvasElement> m_GraphicRebuildQueue = new IndexedSet<ICanvasElement>();

m_LayoutRebuildQueue:保存着需要重建的布局元素(一般是通过LayoutGroup布局改变的UI)

m_GraphicRebuildQueue:需要重建的Graphics元素(如Image,RawIamge,Text的贴图,材质,宽高发生变化)

m_LayoutRebuildQueue是通过RegisterCanvasElementForLayoutRebuild和TryRegisterCanvasElementForLayoutRebuild方法添加元素。

m_GraphicRebuildQueue是通过RegisterCanvasElementForGraphicRebuild和TryRegisterCanvasElementForGraphicRebuild方法添加元素。

二者通过UnRegisterCanvasElementForRebuild移除注册元素。

2.3 PerformUpdate

ICanvasElement接口:

public interface ICanvasElement
{
 ? ?void Rebuild(CanvasUpdate executing);//重建方法
 ? ?Transform transform{get;}
 ? ?void LayoutComplete();//布局重建完成
 ? ?void GraphicUpdateComplete();//图像重建完成
 ? ?bool IsDestroyed();//检查Element是否无效
}

CanvasUpdate枚举:

public enum CanvasUpdate
{
 ? ?Prelayout = 0,//预布局
 ? ?Layout = 1,//布局
 ? ?PostLayout = 2,//后期布局
 ? ?PreRender = 3,//预渲染
 ? ?LatePreRender = 4,//后期预渲染
 ? ?MaxUpdateValue = 5
}

除了最后一个枚举项,其他五个项分别代表了布局的三个阶段和渲染的两个阶段。

在PerformUpdate方法中

  1. 从两个序列中删除不可用的元素 CleanInvalidItems();

  2. 重建布局(Layout Rebuild)开始

  3. 对m_LayoutRebuildQueue(被标记为Dirty状态的Layout对象)依据父对象的数量进行排序,父transform少的在前

  4. 分别以PreLayout,Layout,PostLayout的参数顺序调用每一个元素的Rebuild方法

  5. 调用所有元素的LayoutComplete方法

  6. 清除布局重建序列中的所有元素

  7. 重建布局结束

  8. 完成布局后,调用组件的裁剪方法ClippingRegistry.Cull()

  9. 重建图形(Graphic Rebuild)开始

  10. 对m_GraphicRebuildQueue(被标记了Dirty状态的Graphic对象)以PreRender,LatePreRender的参数顺序调用每一个元素(无序)的Rebulid方法

  11. 调用所有元素的GraphicUpdateComplete方法

  12. 清除图形重建序列中的所有元素

  13. 重建图形结束

CanvasUpdateRegistry.cs中的PerformUpdate():

 ? 
?private void PerformUpdate()
 ?  {
 ? ? ? ?UISystemProfilerApi.BeginSample(UISystemProfilerApi.SampleType.Layout);
 ? ? ? ?//从两个序列中删除不可用的元素
 ? ? ? ?CleanInvalidItems();
        
 ? ? ? ?//重建布局(Layout Rebuild)开始
 ? ? ? ?m_PerformingLayoutUpdate = true;//这个bool值用来锁住Rebuild期间的remove、SetDirty等操作,下同
 ? ? ? ?
        //依据父对象的数量进行排序,父transform少的在前,即从上到下进行Rebuild
 ? ? ? ?m_LayoutRebuildQueue.Sort(s_SortLayoutFunction);
 ? ? ? ?
 ? ? ? ?//分别以PreLayout,Layout,PostLayout的参数顺序调用每一个元素的Rebuild方法
 ? ? ? ?for (int i = 0; i <= (int)CanvasUpdate.PostLayout; i++)
 ? ? ?  {
 ? ? ? ? ? ?for (int j = 0; j < m_LayoutRebuildQueue.Count; j++)
 ? ? ? ? ?  {
 ? ? ? ? ? ? ? ?var rebuild = instance.m_LayoutRebuildQueue[j];
 ? ? ? ? ? ? ? ?try
 ? ? ? ? ? ? ?  {
 ? ? ? ? ? ? ? ? ? ?if (ObjectValidForUpdate(rebuild))//元素存在且为Object
 ? ? ? ? ? ? ? ? ? ? ? ?rebuild.Rebuild((CanvasUpdate)i);//调用元素各自的Rebuild方法
 ? ? ? ? ? ? ?  }
 ? ? ? ? ? ? ? ?catch (Exception e)
 ? ? ? ? ? ? ?  {
 ? ? ? ? ? ? ? ? ? ?Debug.LogException(e, rebuild.transform);
 ? ? ? ? ? ? ?  }
 ? ? ? ? ?  }
 ? ? ?  }
?
 ? ? ? ?//调用所有元素的LayoutComplete方法
 ? ? ? ?for (int i = 0; i < m_LayoutRebuildQueue.Count; ++i)
 ? ? ? ? ? ?m_LayoutRebuildQueue[i].LayoutComplete();
?
 ? ? ? ?instance.m_LayoutRebuildQueue.Clear();//清空队列
 ? ? ? ?m_PerformingLayoutUpdate = false;//解锁
?
 ? ? ? ?// now layout is complete do culling...
 ? ? ? ?//重建布局结束,完成布局后,调用组件的裁剪方法
 ? ? ? ?ClipperRegistry.instance.Cull();
?
 ? ? ? ?//重建图形(Graphic Rebuild)开始
 ? ? ? ?m_PerformingGraphicUpdate = true;//上锁
 ? ? ? ?
 ? ? ? ?//以PreRender,LatePreRender的参数顺序调用每一个元素的Rebulid方法,元素顺序无序
 ? ? ? ?for (var i = (int)CanvasUpdate.PreRender; i < (int)CanvasUpdate.MaxUpdateValue; i++)
 ? ? ?  {
 ? ? ? ? ? ?for (var k = 0; k < instance.m_GraphicRebuildQueue.Count; k++)
 ? ? ? ? ?  {
 ? ? ? ? ? ? ? ?try
 ? ? ? ? ? ? ?  {
 ? ? ? ? ? ? ? ? ? ?var element = instance.m_GraphicRebuildQueue[k];
 ? ? ? ? ? ? ? ? ? ?if (ObjectValidForUpdate(element))//元素存在且为Object
 ? ? ? ? ? ? ? ? ? ? ? ?element.Rebuild((CanvasUpdate)i);//调用元素各自的Rebuild方法
 ? ? ? ? ? ? ?  }
 ? ? ? ? ? ? ? ?catch (Exception e)
 ? ? ? ? ? ? ?  {
 ? ? ? ? ? ? ? ? ? ?Debug.LogException(e, instance.m_GraphicRebuildQueue[k].transform);
 ? ? ? ? ? ? ?  }
 ? ? ? ? ?  }
 ? ? ?  }
?
 ? ? ? ?//调用所有元素的LayoutComplete方法
 ? ? ? ?for (int i = 0; i < m_GraphicRebuildQueue.Count; ++i)
 ? ? ? ? ? ?m_GraphicRebuildQueue[i].GraphicUpdateComplete();
?
 ? ? ? ?instance.m_GraphicRebuildQueue.Clear();//清空队列
 ? ? ? ?m_PerformingGraphicUpdate = false;//解锁
 ? ? ? ?UISystemProfilerApi.EndSample(UISystemProfilerApi.SampleType.Layout);
 ?  }
图源:两水先木示 【Unity】UGUI优化_两水先木示的博客-CSDN博客

3 Rebuild

Rebuild 分为 Layout Rebuild 和 Graphic Rebuild

3.1 Layout的Rebuild

Layout元素:HorizontalLayoutGroup、VerticalLayoutGroup、GridLayoutGroup、ScrollRect等。

重新计算一个 Layout 组件子节点的位置或大小。

LayoutGroup.cs中的SetDirty() 函数:

 ? ?protected void SetDirty()
 ?  {
 ? ? ? ?if (!IsActive())
 ? ? ? ? ? ?return;
?
 ? ? ? ?if (!CanvasUpdateRegistry.IsRebuildingLayout())
 ? ? ? ? ? ?LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
 ? ? ? ?else
 ? ? ? ? ? ?StartCoroutine(DelayedSetDirty(rectTransform));
 ?  }
?
 ? ?IEnumerator DelayedSetDirty(RectTransform rectTransform)
 ?  {
 ? ? ? ?yield return null;
 ? ? ? ?LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
 ?  }

接下来:LayoutRebuilder.MarkLayoutForRebuild→LayoutRebuilder.MarkLayoutRootForRebuild→CanvasUpdateRegistry.TryRegisterCanvasElementForLayoutRebuild→CanvasUpdateRegistry.InternalTryRegisterCanvasElementForLayoutRebuild→m_LayoutRebuildQueue.AddUnique

LayoutGroup中SetDirty()调用的具体情况:

  • SetProperty

  • OnEnable

  • OnDidApplyAnimationProperties (动画修改属性时)

  • OnRectTransformDimensionsChange(RectTransform的Anchor,Width,Height,Anchor,Pivot改变时调用,注意改变Position,Rotation,Scale不会调用。)

  • OnTransformChildrenChanged(子物体改变时)

  • 当 LayoutGroup 的直接子节点,并且是 Graphic 类型的(Image、RawImage、Text),被 SetLayoutDirty 的时候,该 LayoutGroup 也会被加入到 Rebuild 的队列中。

  • 编辑器模式下OnValidate时。

Layout重建时过程:

先自下而上地执行Layout元素的CalculateLayoutInputHorizontal/ CalculateLayoutInputHorizontal方法进行计算布局大小、行数、列数等内容。

布局计算需要自下而上执行,子在父之前完成,因为父计算的大小依赖于子的大小。

然后自上而下地执行Layout元素的SetLayoutHorizontal/ SetLayoutVertical方法进行调整子物体的位置或调整自身大小等事情。

布局控制需要自上而下执行,父在子之前完成, 因为子依赖于父的大小。

LayoutRebuilder.cs中的Rebuild():

 ? ?public void Rebuild(CanvasUpdate executing)
 ?  {
 ? ? ? ?switch (executing)
 ? ? ?  {
 ? ? ? ? ? ?case CanvasUpdate.Layout:
 ? ? ? ? ? ? ? ?PerformLayoutCalculation(m_ToRebuild, e => (e as ILayoutElement).CalculateLayoutInputHorizontal());
 ? ? ? ? ? ? ? ?PerformLayoutControl(m_ToRebuild, e => (e as ILayoutController).SetLayoutHorizontal());
 ? ? ? ? ? ? ? ?PerformLayoutCalculation(m_ToRebuild, e => (e as ILayoutElement).CalculateLayoutInputVertical());
 ? ? ? ? ? ? ? ?PerformLayoutControl(m_ToRebuild, e => (e as ILayoutController).SetLayoutVertical());
 ? ? ? ? ? ? ? ?break;
 ? ? ?  }
 ?  }

LayoutRebuilder.cs中的PerformLayoutCalculation()和PerformLayoutControl():

private void PerformLayoutCalculation(RectTransform rect, UnityAction<Component> action)
    {
        if (rect == null)
            return;

        var components = ListPool<Component>.Get();
        rect.GetComponents(typeof(ILayoutElement), components);
        StripDisabledBehavioursFromList(components);

        // If there are no controllers on this rect we can skip this entire sub-tree
        // We don't need to consider controllers on children deeper in the sub-tree either,
        // since they will be their own roots.
        if (components.Count > 0  || rect.GetComponent(typeof(ILayoutGroup)))
        {
            // Layout calculations needs to executed bottom up with children being done before their parents,
            // because the parent calculated sizes rely on the sizes of the children.

            for (int i = 0; i < rect.childCount; i++)
                PerformLayoutCalculation(rect.GetChild(i) as RectTransform, action);

            for (int i = 0; i < components.Count; i++)
                action(components[i]);
        }

        ListPool<Component>.Release(components);
    } 
    
    
    private void PerformLayoutControl(RectTransform rect, UnityAction<Component> action)
    {
        if (rect == null)
            return;

        var components = ListPool<Component>.Get();
        rect.GetComponents(typeof(ILayoutController), components);
        StripDisabledBehavioursFromList(components);

        // If there are no controllers on this rect we can skip this entire sub-tree
        // We don't need to consider controllers on children deeper in the sub-tree either,
        // since they will be their own roots.
        if (components.Count > 0)
        {
            // Layout control needs to executed top down with parents being done before their children,
            // because the children rely on the sizes of the parents.

            // First call layout controllers that may change their own RectTransform
            for (int i = 0; i < components.Count; i++)
                if (components[i] is ILayoutSelfController)
                    action(components[i]);

            // Then call the remaining, such as layout groups that change their children
            //taking their own RectTransform size into account.
            for (int i = 0; i < components.Count; i++)
                if (!(components[i] is ILayoutSelfController))
                    action(components[i]);

            for (int i = 0; i < rect.childCount; i++)
                PerformLayoutControl(rect.GetChild(i) as RectTransform, action);
        }

        ListPool<Component>.Release(components);
    }

3.2 Graphic的Rebuild

Graphic元素:RawImage、Text、Image。

Graphic.cs中的SetDirty():

  • SetAllDirty()

  • SetVerticesDirty ()

  • SetMaterialDirty()

  • SetLayoutDirty()

Graphic.cs中的Rebuild()函数:

public virtual void Rebuild(CanvasUpdate update)
    {
        if (canvasRenderer == null || canvasRenderer.cull)
            return;

        switch (update)
        {
            case CanvasUpdate.PreRender:
                if (m_VertsDirty)
                {
                    UpdateGeometry();
                    m_VertsDirty = false;
                }
                if (m_MaterialDirty)
                {
                    UpdateMaterial();
                    m_MaterialDirty = false;
                }
                break;
        }
    }

当Graphic进行Rebuild时,UGUI将控制权转交给ICanvasElement接口的Rebuild方法。Graphic类实现了这个接口。

如果顶点数据已标记为Dirty状态(如组件的矩形变换更改大小时),则重建网格。 如果材质数据已标记为Dirty状态(如组件的材质或纹理发生改变时),则将更新附着的画布渲染器的材质。 Graphic的Rebuild不需要按特定顺序遍历Graphic组件列表,也不需要任何排序操作。

接下来:CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild()→CanvasUpdateRegistry.InternalRegisterCanvasElementForGraphicRebuild→m_GraphicRebuildQueue.AddUnique

无论是 Layout,还是 Graphic 的改变,都会把本次的改变分别存储在对应的队列中,即m_LayoutRebuildQueue.AddUnique 和 m_GraphicRebuildQueue.AddUnique,殊途同归。

Graphic中SetDirty()调用的具体情况: Graphic的SetAllDirty(简称A)、SetVerticesDirty(简称V)、SetMaterialDirty(简称M)、SetLayoutDirty(简称L)

Graphic(Image、RawImage、Text的基类):OnEnable、Reset、OnDidApplyAnimationProperties、编辑器下OnValidate时调用A,OnRectTransformDimensionsChange调用V(如果不在重建layout则还会调用L),OnTransformParentChanged时调用A,设置Material 时调用M,设置Color 时调用V。

Image:Sprite改变时调用A,type、preserveAspect、fillCenter、fillMethod、fillAmount、fillClockwise、fillOrigin、useSpriteMesh改变时调用V,SetNativeSize时调用A,OnCanvasHierarchyChanged时调用V和L,OnDidApplyAnimationProperties时调用M和V。

RawImage: texture改变时调用V和M,uvRect改变时调用V,OnDidApplyAnimationProperties时调用V和M。

Text: FontTextureChanged、Font改变时调用A,text第一次写入时调用V(文本改变时还调用L)supportRichText、resizeTextForBestFit、resizeTextMinSize、resizeTextMaxSize、alignment、fontSize、horizontalOverflow、verticalOverflow、lineSpacing、fontStyle改变时调用V和L ,alignByGeometry改变时调用V。

BaseMeshEffect(Shadow的基类): OnEnable、OnDisable、OnDidApplyAnimationProperties、编辑器下OnValidate时调用V, (都是间接调用身上的Graphic的V,本身并不继承Graphic)

Shadow(Outline的基类): useGraphicAlpha、effectDistance、effectColor改变时调用V (都是间接调用Graphic的V,本身并不继承Graphic)

总结:OnEnable、OnDisable、OnTransformParentChanged、OnDidApplyAnimationProperties、OnRectTransformDimensionsChange、OnCanvasHierarchyChanged、图集加载完成时,Text、Image、RawImage、Shadow属性改变时。

3.3 Rebatch和Rebuild的触发条件总结

触发Rebatch的条件:

当Canvas下有Mesh发生改变时,如:

  • SetActive

  • Transform属性变化

  • Graphic的Color属性变化(改Mesh顶点色)

  • Text文本内容变化

  • Depth发生变化

触发Rebuild的条件:

  • Layout修改RectTransform部分影响布局的属性

  • Graphic的Mesh或Material发生变化

  • Mask裁剪内容变化

图源:Unity高锦锦 Unity UGUI优化与原理【unity官方】_gaojinjingg的专栏-CSDN博客_unityugui优化

4 优化

转自UGUI性能优化总结 | 无境

4.1 动静分离

基于Rebatch是以Canvas为单位,当Canvas下UI元素发生变化时,会引起整个Canvas的重构,其中会包括网格合并,网格重叠检测,层级排序等操作。对于同一个界面,我们可以再细分Canvas,把相对静态的、不会变动的UI放在一个Canvas里,而相对变化比较频繁的UI就放在另一个Canvas里,使得频繁变化的Canvas里只对自己的Canvas下的元素进行Rebatch,而节省掉另一个Canvas中不需要变化的元素的Rebatch计算。

只有同一个Canvas下的UI元素才有可能合批,在中间新增Canvas会打断合批,动静分离优化本质是DrawCall换重构耗时的权衡。

Rebatch是在Canvas.BuildBatch函数中进行,而在Unity 5.2版本后,已经对Canvas.BuildBatch做了优化,优化后使用子线程进行计算,已经很大程度缓解了主线程的压力,目前来说动静分离并没有那么需要关注了。

4.2 其它

  • 慎用自带组件Outlien和Shaow,都是通过重复绘制多个Mesh实现的,其中Showdow绘制为原文本Mesh的2倍,而Outline为5倍,对渲染面数、顶点数,BuildBatch和SendWillRenderCanvases的耗时,Overdraw都有影响。若对于某种字体每次出现都需要这两种效果,可以让美术同学直接把阴影和描边做到字体里。

  • Text组件的Best Fit属性若非必要不要使用,它会使字号随着文本框大小而自动适配,一方面是适配本身在调整文本框大小时有CPU耗时开销,另一方面每个字号下新生成的字都会在Font Texture上占用一个字的大小,容易导致Font Texture过大(这个类似图集,当Font Texture当前大小放不下时才会占用更多内存)。这个特定问题已在 Unity 5.4 中得到纠正,Best Fit 不会不必要地扩展字体的纹理图集,但仍然比静态大小的文本慢得多。

  • 尽量少使用Layout组件,会增加Canvas.SendWillRenderCanvases函数耗时,利用好RectTransform同样可实现简单布局。

  • 对于血条、飘字、小地图标记等频繁更新位置的UI,可尽量减低更新频率,如隔帧更新,并设定更新阈值,当位移大于一定数值时再赋值(一方面是位移小时可能表现上看不出位移,另一方面是就算是没有实际位移,重复赋相同的值,也会SetDirty触发重建),可减少BuildBatch耗时。

参考:

[Unity官方]Optimizing Unity UI - Unity Learn

[UWA 学堂]影响性能更大的元凶Rebuild

Unity UGUI优化与原理【unity官方】_gaojinjingg的专栏-CSDN博客_unityugui优化

(五)UGUI源码分析之Rebuild(布局重建、图形重绘)_两水先木示的博客-CSDN博客_ugui重建

UGUI性能优化总结 | 无境

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2022-02-26 12:03:55  更:2022-02-26 12:05:08 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/27 16:38:45-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码