UGUI渲染分析
UGUI渲染分析
一句话总结
比较多,总结不了。
主要类分析
Graphic
这个抽象类代表所有可以在画布上显示出来的元素。
它有点类似3D渲染时的一个模型,它持有材质、贴图、并负责生成网格。因此UI的渲染仍是基于网格的。
它还提供了自身的Raycast检测。
CanvasRenderer
这个组件是实际参与渲染的组件,Graphic在Rebuild时会把材质、网格提交给它。这个类未公开部分源码,不多讨论了。
CanvasUpdateRegistry
CanvasUpdateRegistry是一个单例,它的功能是:
记录下当前帧所有请求重建布局和重建图像的画布元素,并在合适的时候调用它们相应的重建函数。
具体来说有如下几步:
- 它在构造函数中监听了
Canvas.willRenderCanvases委托,因此Unity内部会在合适的机时通知它进行重建过程。 - 它维护了两个
IndexedSet<ICanvasElement>(IndexedSet类似于有序的HashSet),分别记录当前帧请求重建布局和重建图像的画布元素(注意存储的是ICanvasElement而不是Graphic,Graphic是ICanvasElement但还有一些不可见对象也是ICanvasElement)。
当Canvas.willRenderCanvases触发时,它主要执行以下操作:
- 剔除两个列表中不可用的元素。
- 对重建布局列表按
ICanvasElement的transform父物体个数从少到多排序。 - 对重建布局列表每个元素调用
Rebuild方法。此遍历重复3次,分别是Prelayout、Layout、PostLayout。然后调用每个元素的LayoutComplete,最后清空列表。 - 对重建图像列表每个元素调用
Rebuild方法。此遍历重复2次,分别是PreRender、LatePreRender。然后调用每个元素的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的各类LayoutGroup、Fitter、ScrollRect、Text、Image都是在这个方法里处理。从接口上划分,主要分为三类:
| 接口 | 说明 | 举例 |
|---|---|---|
ILayoutController |
用于控制RectTransform布局的接口 |
|
ILayoutGroup |
继承ILayoutController,表示控制子物体的RectTransform布局 |
各类LayoutGroup、ScrollRect |
ILayoutSelfController |
继承ILayoutController,表示控制自身的RectTransform布局 |
各类Fitter |
ILayoutElement |
布局元素,负责计算和提供宽高方面的数据 | Image、Text、InputField、ScrollRect、LayoutElement、各类LayoutGroup |
重建布局
水平方向
对目标RectTransform的一次布局重建会先在【水平】方向上执行以下操作:
首先
以目标RectTransform为层级树的根,后序遍历该树,对每个节点:
- 如果既无
ILayoutElement也无LayoutGroup,不再向下计算。 - 调用
CalculateLayoutInputHorizontal以计算其水平方向数据。
然后
以目标RectTransform为层级树的根,先序遍历该树,对每个RectTransfom节点:
- 如果无
ILayoutController,不再向下计算。 - 对节点上的所有
ILayoutSelfController调用SetLayoutHorizontal(各类Fitter),这可能改变其自身的RectTransform。 - 对节点上的其余的
ILayoutController调用SetLayoutHorizontal(各类LayoutGroup、ScrollRect),将其自身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字体改变、字号改变、文字改变、文字设置改变

浙公网安备 33010602011771号