type
status
date
summary
tags
category
icon
一、概述二、基础组件2.1 CanvasRender Mode2.2 GraphicRaycaster2.3 CanvasScaler2.4 CanvasGroup2.5 CanvasRender三、源码剖析3.1 ICanvasElement3.2 CanvasUpdateRegistry四、引用
一、概述
Canvas是UI的最上层,负责管理下面子节点UI元素的布局、渲染、排序。在UGUI中得到实现的类是CanvasScaler和GraphicRaycaster,而Canvas, CanvasGroup, CanvasRenderer并不在UGUI中实现。
本文记录了Canvas相关组件的作用,通过对CanvasUpdateRegistry类的代码进行剖析来介绍Canvas渲染前会经历哪些步骤,介绍展示的Unity源码版本基于2019.1,链接地址如下:
二、基础组件
2.1 Canvas
Unity官方文档对Canvas组件的描述如下:
画布 (Canvas) 组件表示进行 UI 布局和渲染的抽象空间。所有 UI 元素都必须是附加了画布组件的游戏对象的子对象
可以看出,Canvas就相当于画画时的画板,UI元素可以理解为画板上的内容,当我们将各类元素画好后,Canvas要做的事情就是合并这些元素。
合并的规则为:同一个Canvas里,相同层级,相同材质球的元素进行合并,从而减少Drawcall。不过相同层级的概念并不是gameobject上的节点层级,而是覆盖层级。如果两个元素重叠,则可以认为它们是上下层关系,把所有重叠的层级数计算完毕后,第0层的所有元素统一合并,第1层的元素也统一合并,以此类推。
Render Mode
Overlay
Overlay模式并不与空间上排序有任何关系,空间上的前后位置不再对元素起作用,它常用在纯UI的区域内。在此模式下,画布被缩放以适合屏幕,然后直接渲染而不参考场景或相机(即使场景中根本没有相机,也会呈现UI)
Sort order参数在排序时被着重使用到,Sort order参数的值越大,越靠前渲染。在这个模式下没有Camera的渲染机制因此很难加入普通的3D模型物体来增加效果。
该模式下,Canvas组件需要在UI层次结构的顶层,不然UI会在视图中消失。
Screen Camera
Screen Camera模式相对比较通用一点,它依赖于Camera的平面透视,渲染时的布局依赖于它绑定的Camera。场景中比UI平面更靠近相机的任何3D对象都将在UI前面渲染,而平面后面的对象将被遮挡。
这种模式是实际项目中制作UI最常用的模式,不过UGUI底层有对排序做些规则,如对元素的z轴不为0的元素,会单独提取出来渲染,不参与合并。
World Space
World Space模式主要用于当UI物体放在3D世界中的场景下。比如,一个大的场景中,需要将一张标志图放在一个石块头上,这时就需要World Space模式。
它与 Screen Camera 的区别是,它常在世界空间中与普通3D物体一同展示,依赖于截锥体透视(Perspective)Camera。画布的大小可以使用其矩形变换进行设置,但其在屏幕上的大小将取决于摄像机的视角和距离。它的原理挺简单的,与普通物体一样,当UI物体在这个Camera视野中时,就相当于渲染了一个普通的3D面片,只不过除了普通的渲染Canvas还对这些场景里的UI进行合并处理。
2.2 GraphicRaycaster
输入系统的图形碰撞测试组件,它并不会检测Canvas以外的内容,检测的都是画布下的元素。当图元素上存在有效的碰撞体时,Graphic Raycaster 组件会统一使用射线碰撞测试来检测碰撞的元素。
参数 | 含义 |
Ignore Reversed Graphics | 忽略反转背对相机的UI元素的射线检测 |
Blocking Objects | 可以阻断射线检测的物体类型,2D\3D\所有,要求这类物体有Collider组件。在Camera Space 和World Space能体现出来,UI射线检测是否可以穿透3D物体 |
Blocking Mask | 可以阻断射线检测的物体的layer |
2.3 CanvasScaler
这是个缩放比例组件,用来指定画布中元素的比例大小。
有简单指定比例大小的Constant Pixel Size模式,也有Scale With Screen Size以屏幕为基准的自动适配比例大小,或者Constant Physical Size以物理大小为基准的适配规则。
在实际手游项目里,设备的屏幕分辨率变化比较大,通常使用以屏幕为基准的自动适配比例大小的Scale With Screen Size选项
2.4 CanvasGroup
CanvasGroup用于从一个位置控制它自己和所有子项对象。Canvas Group参数改动时会给下面的UI元素(继承了UIBehaviour)发送
OnCanvasGroupChanged
消息。参数 | 含义 |
Alpha | group下的UI元素透明度基数,会和UI元素本身的alpha值相乘 |
Interactable | group下的UI元素是否可交互(射线检测),取消勾选意味着是单纯的HUD |
Block Raycasts | group下面的UI是否会阻断射线检测,在Camera Space或World Space的canvas可以阻断射线穿透UI到后面的3D物体 |
Ignore Parent Groups | 忽略父canvas group的设置,用这个canvas group的设置覆盖 |
2.5 CanvasRender
CanvasRenderer负责渲染继承自Graphic类的UI元素(Image、RawImage、Text),每个元素都需要一个CanvasRenderer,其他UI元素可以不添加CanvasRenderer组件。
CanvasRenderer会做UI基于canvas的剔除、网格提交、材质绑定、渲染,本质上和MeshRenderer做的内容类似。
三、源码剖析
Canvas是UI的最上层,管理下面UI元素的布局和渲染。涉及Canvas类、CanvasUpdateRegistry类、ICanvasElement接口和CanvasUpdate枚举。核心是CanvasUpdateRegistry
图片引用
3.1 ICanvasElement
在Unity中,几乎所有的UI组件都继承自ICanvasElement接口,接口内提供了重建UI组件的
Rebuild
方法,继承自该接口的组件都会对该方法进行重写。ICanvasElement接口包含的方法如下:
可以看到,
Rebuild
方法中需要提供的参数类型为CanvasUpdate,这是一个枚举类型。在UI元素实现的Rebuild
方法中,方法会将传入的枚举类型通过switch
来对不同阶段做不同的处理。之所以要分阶段,是因为UI无论布局还是网格材质的更新,都需要按照一定顺序才能保证结果正确。3.2 CanvasUpdateRegistry
- Canvas向外提供了事件
willRenderCanvases
,该事件会在渲染前每帧都执行。CanvasUpdateRegistry在构造函数中向该事件注册了PerformUpdate
方法,该方法是整个UGUI布局和渲染更新前的入口,需要注意的是,该方法并不会对UGUI中的元素进行渲染操作
- CanvasUpdateRegistry中使用两个索引集IndexedSet(内部使用List和Dictionary实现)来保存需要的UI元素。可能引起好奇的是,UI元素在什么条件会被添加进这些队列中呢?这里以m_GraphicRebuildQueue队列举例,继承自Graphic类的UI元素(Text, Image, RawImage)在下面的情况下都会被添加进队列(大部分回调接口都是继承自UIBehaviour):
- OnRectTransformDimensionsChange
- OnTransformParentChanged
- OnDidApplyAnimationProperties
- OnCanvasHierarchyChanged
- OnCullingChanged
- …
- 需要更新的UI元素将会在
PerformUpdate
方法中,通过ICanvasElement被重写的Rebuild
方法进行更新。
四、引用
- 作者:Felix
- 链接:felix1125.com/article/a97f1120-f1c4-47e4-9587-113b63f3f252
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章