UGUI渲染分析

UGUI渲染分析

一句话总结

比较多,总结不了。

主要类分析

Graphic

这个抽象类代表所有可以在画布上显示出来的元素。
它有点类似3D渲染时的一个模型,它持有材质、贴图、并负责生成网格。因此UI的渲染仍是基于网格的。
它还提供了自身的Raycast检测。

CanvasRenderer

这个组件是实际参与渲染的组件,GraphicRebuild时会把材质、网格提交给它。这个类未公开部分源码,不多讨论了。

CanvasUpdateRegistry

CanvasUpdateRegistry是一个单例,它的功能是:
记录下当前帧所有请求重建布局和重建图像的画布元素,并在合适的时候调用它们相应的重建函数。
具体来说有如下几步:

  • 它在构造函数中监听了Canvas.willRenderCanvases委托,因此Unity内部会在合适的机时通知它进行重建过程。
  • 它维护了两个IndexedSet<ICanvasElement>IndexedSet类似于有序的HashSet),分别记录当前帧请求重建布局和重建图像的画布元素(注意存储的是ICanvasElement而不是GraphicGraphicICanvasElement但还有一些不可见对象也是ICanvasElement)。

Canvas.willRenderCanvases触发时,它主要执行以下操作:

  • 剔除两个列表中不可用的元素。
  • 对重建布局列表按ICanvasElementtransform父物体个数从少到多排序。
  • 对重建布局列表每个元素调用Rebuild方法。此遍历重复3次,分别是PrelayoutLayoutPostLayout。然后调用每个元素的LayoutComplete,最后清空列表。
  • 对重建图像列表每个元素调用Rebuild方法。此遍历重复2次,分别是PreRenderLatePreRender。然后调用每个元素的GraphicUpdateComplete,最后清空列表。

LayoutRebuilder

LayoutRebuilder实质上是Graphic布局部分的逻辑,单独提成了一个类。
可能是因为它不关心Graphic的显示内容,只关心其RectTransform,所以给单独出来了。
注意它也是ICanvasElement

与Graphic的关系

那么,既然它是Graphic的布局逻辑而又分离在了一个单独的类,那它俩是如何联系起来的呢?答案就是它的静态方法:MarkLayoutForRebuild(RectTransform rect)
当一个Graphic认为自己需要重建布局时(transform变化,字体变化等)它会调用这个方法,方法内部创建一个LayoutRebuilder实例并把自己注册进CanvasUpdateRegistry中,因此将会在合适时机执行它的Rebuild方法。

MarkLayoutForRebuild方法的细节

它接受一个RectTransform,然后它从这个RectTransform向其根方向寻找【布局根节点】,最后对布局根节点生成一个LayoutRebuilder实例注册到CanvasUpdateRegistry中。
何谓布局根节点?
它是传入节点本身或其父节点,并且满足如下条件:

  • 布局根节点上有活动的ILayoutGroup组件。
  • 布局根节点到传入节点之间的每个节点上都有活动的ILayoutGroup组件。
  • 布局根节点的父节点上没有活动的ILayoutGroup组件。

因此可能存在找不到布局根节点的情况,这种情况就说明传入节点不会参与自动布局。因此这种情况下MarkLayoutForRebuild函数不会执行实际操作。

Rebuild方法

参与布局的各类实体概念

画布元素的布局重构是比较复杂的,UGUI的各类LayoutGroupFitterScrollRectTextImage都是在这个方法里处理。从接口上划分,主要分为三类:

接口 说明 举例
ILayoutController 用于控制RectTransform布局的接口
ILayoutGroup 继承ILayoutController,表示控制子物体的RectTransform布局 各类LayoutGroupScrollRect
ILayoutSelfController 继承ILayoutController,表示控制自身的RectTransform布局 各类Fitter
ILayoutElement 布局元素,负责计算和提供宽高方面的数据 ImageTextInputFieldScrollRectLayoutElement、各类LayoutGroup
重建布局
水平方向

对目标RectTransform的一次布局重建会先在【水平】方向上执行以下操作:

首先
以目标RectTransform为层级树的根,后序遍历该树,对每个节点:

  • 如果既无ILayoutElement也无LayoutGroup,不再向下计算。
  • 调用CalculateLayoutInputHorizontal以计算其水平方向数据。

然后
以目标RectTransform为层级树的根,先序遍历该树,对每个RectTransfom节点:

  • 如果无ILayoutController,不再向下计算。
  • 对节点上的所有ILayoutSelfController调用SetLayoutHorizontal(各类Fitter),这可能改变其自身的RectTransform
  • 对节点上的其余的ILayoutController调用SetLayoutHorizontal(各类LayoutGroupScrollRect),将其自身RectTransform数据代入去改变它的子物体。
竖直方向

然后会在【竖直】方向上执行相同操作,只是
CalculateLayoutInputHorizontal改为CalculateLayoutInputVertical
SetLayoutHorizontal改为SetLayoutVertical
计算和修改的是竖直方向的数据。

大体上说就是计算宽高时先计算子物体,设置布局时先设置父物体。

Graphic的Rebuild

Graphic的重建就是图像重建,它会在某些情况下请求重建自身的图像、将自身注册到CanvasUpdateRegistry、并内部标识是需要重建材质还是重建网格或两者兼有。当CanvasUpdateRegistry触发其Rebuild方法时,它会相应的提交材质或网格数据到CanvasRenderer中。
Graphic的布局重建只负责请求,由LayoutRebuilder完成。

触发图像重建的情形:

  • 父物体变更
  • 启用
  • 颜色改变
  • RectTransfom范围改变
  • 被Animation修改了属性
  • Image、RawImage设置图片
  • Image图片设置改变
  • Text字体改变、字号改变、文字改变、文字设置改变
  • Mask启禁用、设置改变
  • 修改材质

触发布局重建的情形:

  • 父物体变更
  • 启用
  • RectTransfom范围改变
  • 被Animation修改了属性
  • Image、RawImage设置图片
  • Text字体改变、字号改变、文字改变、文字设置改变
posted @ 2022-03-16 18:58  啊循  阅读(223)  评论(0)    收藏  举报