理解组成Unity UI系统的不同部分是非常重要的,有一些基础类和组件共同组成了Unity UI系统。这个章节首先会介绍经常会用到的一些术语,然后会讨论Unity UI关键系统的底层细节。
术语
Canvas:画布
Unity渲染系统用来提供层次化几何布局的类,是用原生的C++代码编写,会在游戏世界坐标系之中或者之上进行显示。
画布负责将其包含的几何图元组合成批,生成合适的渲染命令,然后把这些发送给Unity的图形系统。以上部分都在原生的C++代码中完成,这个过程称为重建或者批处理。当某个画布中的几何图元被认为需要重新批处理,这个画布就被认为已经是脏画布。
Geometry:几何结构
通过Canvas Renderer组件提供给画布
Sub-canvas:子画布或者嵌入画布
子画布指的是嵌入到其他的画布控件之中的画布。子画布会将其包含的控件和其父画布包含的控件进行分离;如果子画布已脏,则只会对自己进行重新批处理的过程,不会影响父画布,反之亦然。(某些情况下,这个说法也不正确,例如当父画布导致子画布重绘大小的时候)
Graphic:图形类
图形类是Unity UI的C#代码库中提供的基类,它是所有向Canvas系统提供绘制的几何结构的Unity UI类的基类。大部分内置的Unity UI图形是派生自MaskableGraphic
这个子类,这个子类实现了IMaskable
的接口,从而可以实现被遮罩的效果。Drawable
的子类主要有Image
和Text
,这两个类组成了对应的Image
和Text
组件。
Layout components:布局组件
布局组件控制RectTransform
的尺寸和位置,通常用来构建复杂的布局,比如说根据内容来适配大小和位置。布局组件只依赖与RectTransform
而且只会改变关联的RectTranform的属性。布局组件不依赖于Graphic类,可以独立于Unity UI的Graphic组件使用。
Graphic和布局组件都依赖于CanvasUpdateRegistry
类,这个类并没有在Unity编辑器中暴露出来。这个类会跟踪需要更新的布局组件和Graphic组件,并且当关联的Canvas触发了willRenderCanvases
事件的时候触发更新操作。
布局组件和Graphic组件的更新被称为重建。重建的过程细节将在之后详细讨论。
渲染的细节
当使用Unity UI构建用户界面的时候,需要牢牢记住Canvas绘制几何图元的顺序是按照透明队列进行的。也就是说,Unity UI产生的几何图元都是按照从后向前使用alpha混合进行绘制。从性能角度解释就是,多边形栅格化之后的每个像素都会被采样,即使他会被其他的不透明像素完全挡住。在手机设备上,很高程度的重绘会超出GPU的填充率能力。
画布的批处理过程
画布的批处理过程是将UI元素的网格合并,产生合适的渲染命令发送给Unity的图形管道。处理的结果会被缓存和重用,直到Canvas被标记为dirty,这种情况会在画布包含的某个Mesh改变的时候出现。
画布使用的mesh来自于画布的CanvasRenderer组件,但是不包含任何子画布。
计算批处理需要将Mesh按照深度进行排序并且检查它们的覆盖情况和是否共享了材质等等。这个操作是多线程的,所以性能差异在不同的CPU架构上面比较大,尤其是移动设备和桌面计算机环境。
图形的重建过程
图形的重建过程指的是Unity UI的C#库中的图形组件的布局和mesh被重新计算的过程。这些操作是在CanvasUpdateRegistry
类中完成的。相关代码可以在Unity UI的源代码中找到。
在CanvasUpdateRegistry
中,值得关注的方法是PerformUpdate
方法。只要画布组件调用WillRenderCanvases
事件的时候,这个方法就会被调用。这个事件每帧只会被调用一次。
PerformUpdate
分三步进行:
- 脏布局组件需要重建布局,通过
ICanvasElement.Rebuild
方法 - 任何注册的需要剪切组件(如Mask组件)需要剔除被剪切掉的部分。这个操作是通过
ClippingRegistry.Cull
完成的。 - 脏Graphic组件需要重建图形元素。
对于布局重建和图形重建而言,都需要分成多个部分完成。布局重建分成三步完成(PreLayout,Layout和PostLayout),图形的重建则是分成两部分完成(PreRender和LatePreRender)
布局重建
为了重新计算在一个或者多个布局组件下的组件的位置(和大小),需要将不同的布局组件按照合适的层次进行排序。在GameObject的层级中,越靠近根节点的布局组件可以改变其包含的Layout组件的大小和位置,所以需要首先倍计算。
为了完成这个任务,Unity UI 会将标记为dirty的布局组件按照在层级中的深度进行排序。层次越高(父Transform越少的元素)会排到列表的前面。
排好序的Layout组件需要重建布局,这时候Layout组件绑定的UI元素的位置和大小才会实际改变。对于Layout布局如何改变单个元素的位置和大小,参见Unity手册的UI Auto Layouts章节。
图形重建
当Graphic组件重建的时候,Unity会通过ICanvasElement接口的Rebuild方法控制。Graphic实现这个方法并且在PreRender阶段执行两种不同的重建步骤。
- 如果顶点数据被标记成dirty(例如组件的RectTransform改变大小),mesh会被重建;
- 如果材质数据被标记为dirty(例如组件的材质或者纹理改变),那么绑定的CanvasRenderer的材质信息会被更新;
Graphic重建的过程不需要对Graphic组件进行排序。