news 2026/2/14 17:50:17

UGUI重建流程和优化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
UGUI重建流程和优化

UGUI重建流程和优化

参考文献
(五)UGUI源码分析之Rebuild(布局重建、图形重绘)_ugui rebuild-CSDN博客
(99+ 封私信 / 83 条消息) UGUI源码解析(二十一)LayoutRebuilder - 知乎
(99+ 封私信 / 83 条消息) UGUI源码解析(五) CanvasUpdateRegistry - 知乎
(99+ 封私信 / 85 条消息) UGUI UI重建二三事(一) - 知乎
(99+ 封私信 / 85 条消息) UGUI UI重建二三事(二) - 知乎

总体流程简述

首先,我们对UI进行修改时,如修改其大小,改材质等很多情况下,UI会将自己标记为脏,然后放进一个队列中。在相机即将渲染时会处理这个队列的元素,进行布局的重新计算,称作布局重建。然后重新生成graphic的网格,称为网格重建。

重建过程的主要接口

ICanvasElement接口

标记此组件需要参与重建。主要是Rebuild方法,会在此节点需要重建时调用,参数表面重建过程。
CanvasUpdate.// 标记了重建过程
Prelayout,
Layout,
PostLayout,
PreRender,
LatePreRender,
MaxUpdateValue,
LayoutComplete在布局完成时调用
GraphicUpdateComplete在网格重建完成时调用

实现情况:

1.Graphic实现,用来生成网格。即网格重建过程。
2.LayoutRebuilder实现,用来进行布局。即布局重建过程。
3.InputField、ScrollRect、Scrollbar、Slider、Toggle实现,主要是根据重建过程实现自己的功能。

ILayoutElement接口

标记此节点需要参与布局重建。给出布局重建时节点的宽高参数。
有宽高的min,preferred,flexible,用于布局。
此外layoutPriority标记布局优先级。
CalculateLayoutInputHorizontal
CalculateLayoutInputVertical
这两个方法计算自己的理想宽高。

实现情况:

Image、Text、InputField实现preferredWidth,preferredHeight会返回最合适的大小。
ScrollRect实现了所有参数,但都返回-1,仅供布局系统调用。
LayoutElement实现了所有参数,并开放到编辑器供配置。

ILayoutController接口

设置子节点的位置宽高。
实现两个方法SetLayoutHorizontal、SetLayoutVertical。作用是设置自己的子节点。

实现情况:

有三个类实现GridLayoutGroup,HorizontalLayoutGroup,VerticalLayoutGroup,

重建框架执行流程

CanvasUpdateRegistry处理重建的类。

待重建元素列表

m_LayoutRebuildQueue保存需要更新布局的队列
m_GraphicRebuildQueue保存需要更新图形的队列
队列元素的类型都是ICanvasElement,此接口为布局元素的基类。

元素如何加入到重建列表?

向布局重建队列添加元素的方法为
CanvasUpdateRegistry.MarkLayoutForRebuild //将需要重建的元素加入重建列表。实际调用下面的
CanvasUpdateRegistry.MarkLayoutRootForRebuild //将需要重建的布局根节点加入重建列表
一般情况下,各个组件在布局需要修改时调用MarkLayoutForRebuild将自己加入重建列表。
比如 OnEnable、OnDisable、OnRectTransformDimensionsChange等
向图形重建队列添加元素的方法为
CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild

重建总入口

CanvasUpdateRegistry实现单例模式,单例的构造函数就向Canvas.willRenderCanvases注册了PerformUpdate函数,因此PerformUpdate为重建过程的总入口。

PerformUpdate执行逻辑

首先调用CleanInvalidItems清理无效布局组件。
m_PerformingLayoutUpdate标记了正在进行布局重建。
使用SortLayoutList排序。排序依据为父物体层数少的在前。
依次对m_LayoutRebuildQueue列表中元素调用Rebuild,参数分别是Prelayout、Layout、PostLayout。
这就等于进行了重建的三个步骤。其中Layout进行了实际布局重建。
最后调用每个元素的LayoutComplete方法。
清理m_LayoutRebuildQueue列表。
布局重建完成,然后进行网格重建。此过程与上面布局重建基本一致。
先调用ClipperRegistry.instance.Cull(),不用排序
依次对m_LayoutRebuildQueue列表中元素调用Rebuild,参数分别是PreRender、LatePreRender。
最后调用每个元素的GraphicUpdateComplete方法。
清理m_GraphicRebuildQueue列表。
可以看到基本就是依次通知每个元素重建了。具体行为是让组件自己实现的,即调用Rebuild函数,每个组件都会重写来实现不同行为。

重要组件的具体重建行为

Graphic.Rebuild 网格重建

实现网格重建,即生成图片和文本网格的部分
Graphic会在PreRender时,检查网格刷新,检查材质刷新。
生成网格方法是OnPopulateMesh。
值得注意的是网格生成后,可通过IMeshModifier对网格进行调整,实现网格特效。如Shadow。
Graphic基类中网格生成是直接创建面片显示纯颜色。
Image重写了OnPopulateMesh方法,实现了一些特殊填充,就是Sliced,Tiled那些。
(这部分实现没有技巧,全是硬编码)
Text重写了OnPopulateMesh方法,改成文本的网格生成。其实现未开源。
不过可以通过m_TempVerts访问到每个字符生成完毕的网格数据。(可以用这个做超链接)
RawImage实现和Graphic基本一致,区别在没有主贴图时不会生成网格。

ILayoutElement.Rebuild布局重建

所有加入布局重建中元素,都是RectTransform,加入列表时会包一个LayoutRebuilder。
LayoutRebuilder在Rebuild的Layout阶段时计算了自己理想宽高。过程:
PerformLayoutCalculation递归后续遍历所有子节点。即先子节点,再自己。会对每个ILayoutElement节点执行一个委托CalculateLayoutInputHorizontal,作用是计算自己的最终宽高。
随后PerformLayoutControl递归后续遍历所有子节点。即先子节点,再自己。会对每个ILayoutController节点执行一个委托SetLayoutHorizontal,作用是设置自己的子节点。
特别的,有时需求会需要我们获取布局完成后的组件位置,可调用这个方法立即进行此元素的布局重建。之后可正确获取最佳宽高值。
LayoutRebuilder.ForceRebuildLayoutImmediate
其实现是,创建一个此节点的LayoutRebuilder,然后以CanvasUpdate.Layout为参数立即调用一次Rebuild。也就是立即触发一次布局重建。调用参数为布局根节点。

总结LayoutGroup的重建过程

包括GridLayoutGroup,HorizontalLayoutGroup,VerticalLayoutGroup
调用CalculateLayoutInputHorizontal时,LayoutGroup会收集所有子物体,保存在m_RectChildren中。有ILayoutIgnorer且ignoreLayout都是false的除外。
HorizontalLayoutGroup中重写CalculateLayoutInputHorizontal,计算自己宽高。
SetLayoutHorizontal执行过程
随后SetChildAlongAxisWithScale时设置子节点位置。

优化思路

可修改源码,检查上文提到的两个队列来查看重建情况。
重建优化思路基本就是减少重建的触发,以减少重建次数。
即减少UI元素位置大小图片材质等修改,减少mask矩形区域的变更。
少用布局组件,不会变化的布局组件删除或者关掉。不要频繁修改布局组件元素。

OnRectTransformDimensionsChange

可观察到此函数在网格需要变化时触发布局重建。
如修改Anchor,AnchoredPosition,Pivot,SizeDelta大概率导致网格变化产生重建。
而如果仅改变Scale,Rotation,Position,不会发生重建。
因此可考虑用scale改变代替enable避免重建。

摘抄大佬的笔记,总结触发rebuild的情况

https://zhuanlan.zhihu.com/p/448293298
  1. Text控件 文本的内容及颜色变化、设置是否支持富文本、更改换行模式、设置字体最大最小值、变更文本使用的对齐锚点、设置是否通过几何对齐、变更字体大小、变更是否支持水平及垂直溢出、修改行间距、变更字体样式(正常、斜体.....)。
  2. Image控件 颜色变化、变更显示类型(Simple、Sliced、Tiled、Filled)、变更是否应保留Sprite宽高比(Image.preserveAspect属性的变更),FillCenter属性变更(是否渲染平铺或切片图像的中心)、变更填充方式(Horizontal、Vertical、Radial360....)、变更图像填充率(fillAmount)、变更图像顺逆时针填充类型(Image.fillClockwise)、变更填充过程的原点(Image.FillOrigin)。
  3. RawImage控件 设置Texture、变更纹理使用的UVRcet、
  4. Shadow效果 改变效果的距离(effectDistance)及颜色(effectColor)、变更是否使用Graphic中的Alpha透明度(useGraphicAlpha)。
  5. Mask控件 设置是否展示与Mask渲染区域相关的图形(showMaskGraphic),enable发生变化
  6. 所有继承MaskableGraphic的控件(Image、RawImage、RectMask2D、Text) 设置此图形是否允许被遮盖、enable发生变化、父节点发生变化(TransFromParentChanged)、在Hierachy面板上发生改变(HierachyChanged)。
  7. 所有继承自BaseMeshEffect的效果类(目前只看到Shadow及PositionAsUV1)的enable变化及应用动画属性的操作。
  8. 所有继承自Graphic的UI控件材质(material)发生变化。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/7 18:25:30

幽冥大陆(五十)屏幕录像手机教程3D透镜主题——东方仙盟炼气期

地面颜色(半透明)、悬浮阴影颜色和镜面高光颜色时,如何选择搭配才能达到视觉上协调且符合场景需求的效果,我会结合视觉设计原则和实际场景来给你具体的配置思路。一、核心配置原则与思路首先要明确这三种颜色是关联且有层次的&…

作者头像 李华
网站建设 2026/2/13 12:56:23

Cox回归分析效率提升3倍,R语言高级建模技巧大公开

第一章:临床数据的 R 语言 Cox 回归优化在处理生存分析问题时,Cox 比例风险模型是临床研究中广泛应用的统计方法。它能够评估多个协变量对生存时间的影响,同时无需假设基础风险函数的具体形式。利用 R 语言中的 survival 包,可以高…

作者头像 李华
网站建设 2026/2/4 6:41:49

【渲染的抗锯齿终极指南】:揭秘图像锯齿根源与5种高效解决方案

第一章:渲染的抗锯齿在计算机图形学中,抗锯齿(Anti-Aliasing)是提升图像视觉质量的关键技术之一。由于数字图像由离散像素构成,在渲染斜线或曲线边缘时容易出现阶梯状的“锯齿”现象。抗锯齿通过平滑这些边缘&#xff…

作者头像 李华
网站建设 2026/2/11 16:11:40

【医疗行业数据防护】:满足HIPAA合规要求的5大技术控制措施

第一章:医疗数据的 HIPAA 合规概述在医疗信息化快速发展的背景下,保护患者隐私和确保数据安全成为核心议题。HIPAA(Health Insurance Portability and Accountability Act)作为美国医疗数据保护的基石性法规,为个人健康…

作者头像 李华