type
status
date
summary
tags
category
icon

一、概述

无论是在商业还是个人项目中,Image和Text组件都是我们最常接触的组件之一,它们间接继承自Graphic类。Graphic类中实现了构建网格,更新材质等重要的方法,这篇文章想要对Graphic类和Graphic类组件的重建流程进行讨论。
文章内的Unity源码版本基于2019.1,链接地址如下:

二、Graphic类

2.1 定义与继承关系

Graphic类是所有可视化UI组件(即有带材质的)的基类,当我们想要创建一个可视化的UI组件,我们应该让组件继承Graphic。需要注意的是,一个对象上只能绑定一个Graphic组件
在源码中,官方例举了一个简单的示例来展示如何自定义一个可视化的组件,感兴趣可以点击上方的源码链接查看。
notion image

2.2 数据成员

Graphic类的主要数据包括贴图材质颜色网格数据和所属的Canvas以及CanvasRenderer
===
组件所属的CanvasCanvasRenderer
===
贴图(Texture),包括一张公共的白色贴图和此UI元素的图片
===
材质(Material),包括一个公共的默认材质和当前材质
===
颜色(Color),内置UI组件使用该颜色作为顶点颜色,我们能够通过改变该颜色来控制Graphic组件(Text,Image,RawImage)的颜色
===
网格数据(Mesh)s_Mesh用来设置给canvasRenderer,s_VertexHelper是辅助设置mesh数据的中间结构

三、UI重建流程

在上一篇文章对Canvas进行分析后,我们知道Canvas在渲染前的每帧会调用PerformUpdate方法,对重建队列中的UI元素进行遍历重构,那么一个值得探究的问题是:UI元素会在什么时机,通过什么方式被添加进队列中呢?
===
继承自Graphic类的UI元素在初始化关键属性被改变后,元素会调用对应的回调方法,然后在方法内部将自身注册进CanvasUpdateRegistry的重建队列中。我们首先将目光放在这些特定方法的调用时机上。
🔹
将UI元素添加进重建队列的方法的调用时机
  1. OnRectTransformDimensionsChange:当UI元素的RectTransform尺寸被改变时调用,需要注意的是,当任一子对象的RectTransform尺寸改变时也会调用(判断依赖于锚点)
  1. OnTransformParentChanged:当关联的Transform的父物体变化后方法被调用,指Hierarchy上的父子层级关系变化。由面板上拖拽调整父子关系或调用transform.SetParent()触发。
  1. OnDidApplyAnimationProperties:当通过animation clip属性改变时调用
  1. OnCullingChanged:当CanvasRenderer的cull属性被改变时调用
  1. SetMaterialDirty:当设置元素的材质时调用
  1. OnEnable
  1. OnValidate:编辑器下,脚本被加载或 Inspector 中的任何值被修改时,方法被调用
  1. Reset:编辑器下,脚本被加载或 Inspector 上的Reset被点击时,方法被调用
===
在上述方法的内部,程序会调用不同类型SetXXXDirty()方法,将UI元素添加进CanvasUpdateRegistry对应的重建队列中,我们这里以OnRectTransformDimensionsChange为例进行查看
===
通过代码可以看到,SetXXXDirty()一共有三种类型,分别是布局顶点材质
===
CanvasUpdateRegistry管线会在每帧的willRenderCanvases事件中对重建队列中的ICanvasElement对象执行更新操作,调用重建队列中对象的Rebuild方法(Graphic类继承了ICanvasElement接口并实现了Rebuild方法),执行流程可以参阅
Unity UGUI模块 Canvas
===
继承自Graphic的对象在Rebuild方法中通过UpdateMaterial接口中更新CanvasRenderer中的材质和贴图,通过UpdateGeometry接口将网格数据设置到CanvasRenderer中
在上面的代码中,有两个接口起到了重要的作用,一个是IMeshModifier,另一个则是IMaterialModifier
IMeshModifier接口中定义两个重载的ModifyMesh方法,通过重写该方法,我们能够自定义的修改UI元素的网格,去实现一些我们想要的UI特效,例如常见的Outline描边、Shadow阴影和PositionAsUV1镂空。这三个效果都派生自BaseMeshEffect类,BaseMeshEffect就继承了IMeshModifier接口。
IMaterialModifier接口则提供了一个可以修改材质的GetModifiedMaterial方法,Mask组件则是通过实现该方法实现遮挡效果
从上面的代码可以看出,CanvasRenderer 和 Canvas 才是合并和渲染网格的关键,但可惜的是CanvasRenderer 和 Canvas 并没有在官网的UGUI代码中开源出来,于是我们只能猜测其内部做了什么。一个合理的猜想是,每次重构时获取 Canvas 下面所有的 CanvasRenderer 实例,将它们的 Mesh 合并起来然后进行渲染。
 

四、引用

CLR via C#(第四版) 笔记 反射原理Unity UGUI模块 Canvas
  • Twikoo
  • Cusdis