Unity 补完计划(四):UGUI-7:Graphic
Refs:
https://zhuanlan.zhihu.com/p/643275125
https://www.cnblogs.com/cancantrbl/p/16076748.html
https://docs.unity.cn/cn/2018.4/Manual/class-Canvas.html
前言
Graphic 算是 UGUI 中最重要的组件之一了,它是所有 UI 组件的基类与基础,负责实现 UI 的绘制功能。在本篇中,我们将详细讲解 Graphic 的实现。
前置知识
在学习 Graphic 源码之前,需要对 Canvas 及渲染流程有一定的了解。
Canvas
画布(canvas)是 UI 组件的容器,所有 UI 元素都必须放置在画布中。这就好比是电影幕布,所有的影像都在幕布上呈现。Canvas 组件通过渲染器将UI元素绘制到屏幕上。它使用层级结构来管理UI元素的显示顺序,可以通过设置UI元素的层级来控制它们的显示顺序。Canvas组件还可以设置渲染模式,包括屏幕空间、世界空间和摄像机空间等。
若场景中没有画布,在创建控件时会自动创建画布。一个默认的 Canvas 会包含以下组件:
Canvas 的存在,让我们可以将一些通用的 UI 配置放到 Canvas 上来配置,比如:
- Canvas 与游戏中其他物体是什么样的关系?(渲染模式)
- 对于不同分辨率的屏幕,Canvas 如何进行适配?(画布缩放器 Canvas Scaler)
对于这些问题,画布及其关联组件都提供了多种配置项来适配不同的需求。
渲染模式
渲染模式定义了画布与场景中其他物体在渲染上的关系。传统上,渲染 UI 的效果就好像是直接在屏幕上绘制的简单图形设计。也就是说,没有摄像机观察 3D 空间的概念。Unity 便支持这种屏幕空间渲染方式,但也允许 UI 在场景中渲染为对象,具体取决于 Render Mode 属性的值。可用的模式包括 Screen Space - Overlay、Screen Space - Camera 和 World Space。
-
Screen Space - Overlay
在此模式下,画布会进行缩放来适应屏幕,然后直接渲染而不参考场景或摄像机(即使场景中根本没有摄像机,也会渲染 UI)。如果更改屏幕的大小或分辨率,则 UI 将自动重新缩放进行适应。UI 将绘制在所有其他图形(例如摄像机视图)上。
注意:Screen Space - Overlay 画布需要存储在层级视图的顶级。如果未使用此设置,则 UI 可能会从视图中消失。这是一项内置的限制。请将 Screen Space - Overlay 画布保持在层级视图的顶级以便获得期望的结果。
-
Screen Space - Camera
在此模式下,画布的渲染效果就好像是在摄像机前面一定距离的平面对象上绘制的效果。UI 在屏幕上的大小不随距离而变化,因为 UI 始终会重新缩放来准确适应摄像机视锥体。如果更改屏幕的大小或分辨率或更改摄像机视锥体,则 UI 将自动重新缩放进行适应。场景中比 UI 平面更靠近摄像机的所有 3D 对象都将在 UI 前面渲染,而平面后的对象将被遮挡。
-
World Space
在 World Space 渲染模式下呈现的 UI 好像是 3D 场景中的一个 Plane 对象。与前两种渲染模式不同,其屏幕的大小将取决于拍摄的角度和相机的距离。
它是一个完全三维的 UI,也就是把 UI 也当成三维对象,例如摄像机离 UI 远了,其显示就会变小,近了就会变大。
此模式将 UI 视为场景中的平面对象进行渲染。但是,与 Screen Space - Camera 模式不同,该平面不需要面对摄像机,可以根据喜好任意定向。画布的大小可以使用 RectTransform 来设置,但画布在屏幕上的大小将取决于摄像机的视角和距离。其他场景对象可以位于画布后面、穿透画布或位于画布前面。
渲染模式的配置在 Canvas 组件的 Render Mode 选项中:
画布缩放器 Canvas Scaler
当画布在屏幕空间中渲染时,画布尺寸的大小会随着屏幕的大小和分辨率的变化而变化。这就是画布缩放器(Canvas Scaler)的作用,它可以根据屏幕的大小和分辨率来调整画布的大小,以确保 UI 元素在不同设备上具有一致的大小和布局。
画布缩放器(Canvas Scaler 组件),提供了三种缩放模式来适配不同的需求:
-
Constant Pixel Size:在此模式下,UI 元素的大小将不受 Canvas 的缩放影响,而是保持固定的像素大小。这种模式适用于需要确保 UI 元素在不同设备上的大小保持一致的情况。
-
Scale With Screen Size:在此模式下,UI 元素的大小将根据 Canvas 的缩放比例进行缩放,以适应不同分辨率的设备。这种模式适用于需要在不同分辨率的设备上呈现一致的 UI 布局和视觉效果的情况。
-
Constant Physical Size:在此模式下,UI 元素的大小将根据屏幕的物理大小进行缩放,以确保 UI 元素在不同设备上具有相同的物理大小。这种模式适用于需要确保 UI 元素在不同设备上具有相同的物理大小的情况,例如在使用触摸屏幕的设备上。
更详细的说明可以参考 Unity 官方文档:Canvas Scaler
Renderer 的对比
UGUI 的渲染器是 Canvas Render (CR), 同样渲染2D物体的是 Sprite Render (SR)
相同点:
- 都有一个渲染队列来处理透明物体,从后往前渲染
- 都可以通过图集并合并渲染批次,减少drawcall
不同点:
- Canvas Render 要与 Rect Transform 配合,必须在 Canvas 里使用,常用于 UI。Sprite Render 与 transform配合,常用于 gameplay
- Canvas Render 基于矩形分隔的三角形网络,一张网格里最少有两个三角形(不同的image type, 三角形的个数也会不同),透明部分也占空间。Sprite Render 的三角网络较为复杂,能剔除透明部分
Canvas Render 会老老实实地为一个矩形的Sprite生成两个三角形拼成的矩形几何体

Sprite Render 会根据显示内容,裁剪掉元素中的大部分透明区域,最终生成的几何体可能会有比较复杂的顶点结构
一个DrawCall的渲染流程:
- CPU 发送 Draw Call 指令给 GPU;
- GPU 读取必要的数据到自己的显存;
- GPU 通过顶点着色器(vertex shader)等步骤将输入的几何体信息转化为像素点数据;
- 每个像素都通过片段着色器(fragment shader)处理后写入帧缓存;
- 当全部计算完成后,GPU 将帧缓存内容显示在屏幕上。
从上面的步骤可知,因为 SR 的顶点数据更复杂,在第一步和第二步的效率会比 CR 低,会有更多的vertex shader的计算;但 CR 会有更多的 fragment shader 的计算,因为是针对每个像素的计算,而 SR 会裁剪掉透明的部分,从而减少了大量的片段着色器运算,并降低了overdraw。
渲染层级
说完底层,我们再来看看UI渲染层级是怎么由哪些决定的。我们说的渲染层级高,意思就是会盖在物体上面,也是最后一个被渲染的那个。
渲染层级是由以下三个层级决定的,从高到低:
- 相机的 layer 和 depth:culling layer 可以决定相机能看到什么 layer,depth 越高的相机,其视野内能看到的所有物体渲染层级越高

- canvas 的 layer 和 order
-
Screen Space - Overlay: sort order 越大显示越前面
-
Screen Spacce - Camera: order layer越大显示越前面;sorting layer越在下方的层显示越前面。
-
World Space: 当UI为场景的一部分,即UI为场景的一部分,需要以3D形式展示。变量和camera screen space一样
- 物体的 hierarchy 关系:物体越在下面,显示越在前面
Graphic
Graphic
是一个抽象类,继承自 UIBehaviour
并实现了 ICanvasElement
接口。这意味着由 Graphic
派生出的类也可以通过 CanvasUpdateRegistry
来将自己注册进 m_GraphicRebuildQueue
中。
本节将要介绍的逻辑如上图所示。
成员变量
首先还是看看 Graphic
的成员变量,对于 Graphic
而言,最重要的还是用于渲染的数据成员,如材质、纹理、颜色、网格等。我们首先来看一下对于这些数据,Graphic
是如何处理的。
- Material
1 | static protected Material s_DefaultUI = null; |
首先,Graphic
中定义了一个静态的 Material
类型的变量 s_DefaultUI
,用于存储默认的 UI 材质,并利用 Canvas.GetDefaultCanvasMaterial()
方法来获取它。也就是我们在创建一个 Image
等 UI 组件时,指定的默认材质了。
1 | [ ] |
其次,提供了用户可以设置的材质 m_Material
,如果用户没有设置材质,则使用默认材质 defaultMaterial
。此外,在设置材质时,会调用 SetMaterialDirty()
方法对当前 Graphic
做脏标记,以进行重新渲染,这个方法会在下文分析。
1 | public virtual Material materialForRendering |
最后,materialForRendering
方法用于获取当前用于渲染的材质,它会遍历当前 Graphic
上的所有 IMaterialModifier
组件,对当前材质进行修改,最后返回修改后的材质。
值得注意的是:materialForRendering
是真正提交给 CanvasRenderer
的材质。因此,针对同一个 material
,我们也可以通过设置不同的 IMaterialModifier
组件来实现不同的渲染效果。这是一种良好的解耦设计,使得 Graphic
的渲染效果可以通过不同的 IMaterialModifier
组件来定制。
- Texture
接着是 Texture
,Graphic
中定义了一个静态的 Texture2D
类型的变量 s_WhiteTexture
,用于储存默认的 UI 纹理,即白色纹理。
1 | static protected Texture2D s_WhiteTexture = null; |
mainTexture
是真正提交给 CanvasRenderer
的纹理,这里我们可以看到,默认返回的就是白色纹理。且会在 OnEnable
方法中初始化 s_WhiteTexture
。不过 mainTexture
本身是一个虚方法,因此我们可以在自定义的 Graphic
类中重写这个方法,来控制纹理的渲染。
此外,从注释中也可以看到关于合批的一些考量,Unity 会尝试将 UI 元素进行合批,以提高性能,因此在实现自定义 Graphic
时,最好使用图集来减少绘制调用。
- Color
Graphic
中对于颜色的处理相较于材质和纹理要简单一些,只是定义了一个 Color
类型的变量 m_Color
,用于存储颜色信息。
1 | [private Color m_Color = Color.white; ] |
color
属性也是最终交付给 CanvasRenderer
的颜色信息,当颜色发生变化时,会调用 SetVerticesDirty()
方法对当前 Graphic
做脏标记,以进行重新渲染。
- Mesh
最后是 Mesh
,Mesh
用于储存 Graphic
的顶点信息,包括顶点的位置以及三角形的索引等。Graphic
使用 VertexHelper
类用于辅助生成 Mesh
信息。
1 | [protected static Mesh s_Mesh; ] |
Graphic
真正交给 CanvasRenderer
用于渲染的是 workerMesh
,在默认的的渲染流程中,会通过 s_VertexHelper
生成网格信息并赋予 workerMesh
,然后交给 CanvasRenderer
进行渲染。
小结
到这里我们就基本了解了 Graphic
是如何处理渲染所需的数据的,Graphic
分别将 materialForRendering
、mainTexture
、color
与 workerMesh
交给 CanvasRenderer
进行渲染。虽然 Unity 的渲染管线只开放到将数据提交给 CanvasRenderer
这一步,但 Graphic
中大部分与渲染相关的数据都是可以通过重写方法来定制的,还是赋予了开发者很大的自定义空间。
渲染流程
虽然 CanvasRenderer
的底层我们无法查看,但 UGUI 本身的渲染流程是可以通过 Graphic
的源码来了解的。上文提到,Graphic
也实现了 ICanvasElement
接口,因此也必然实现了相关的方法,通过这些方法我们就可以了解 Graphic
的渲染流程。
ICanvasElement 相关方法
Graphic
中共实现了以下的 ICanvasElement
接口方法:
1 | public virtual void Rebuild(CanvasUpdate update) |
其中,Rebuild
方法是最重要的一个方法,我们都知道 CanvasUpdateRegistry
会在每一帧中逐阶段地调用 m_LayoutRebuildQueue
以及 m_GraphicRebuildQueue
中的 ICanvasElement
对象的 Rebuild
方法。因此,最重要的更新逻辑一定在 Rebuild
方法中。
Graphic
中,Rebuild
方法只在 CanvasUpdate.PreRender
阶段起作用,主要进行了两步操作:1. 更新几何信息;2. 更新材质信息。这两步操作分别由 UpdateGeometry
和 UpdateMaterial
方法完成。
至于其他接口方法,LayoutComplete
和 GraphicUpdateComplete
方法都是空实现,而 IsDestroyed
方法则没有实现。毕竟 Graphic
只是一个抽象类,确实不应该实现这个方法。
UpdateGeometry
UpdateGeometry
方法用于更新 Graphic
的几何信息,即将 VertexHelper
生成的网格信息填充到 workerMesh
中,再交由 CanvasRenderer
处理。这里涉及到了许多辅助类,都会在下文进行单独讲解,目前还是仅梳理渲染逻辑。
1 | protected virtual void UpdateGeometry() |
在 UpdateGeometry
方法中,首先会根据 useLegacyMeshGeneration
的值来决定使用新的还是旧的网格生成方法,看样子是有一些兼容性的考虑。这里我们只分析 DoMeshGeneration
方法。
1 | private void DoMeshGeneration() |
DoMeshGeneration
首先会调用 OnPopulateMesh
方法将顶点信息更新至 s_VertexHelper
中,然后遍历当前 Graphic
上的所有 IMeshModifier
组件,对 s_VertexHelper
中的顶点信息进行修改。最后,将修改完成的顶点信息填充至 workerMesh
中,并交由 CanvasRenderer
处理。
1 | protected virtual void OnPopulateMesh(VertexHelper vh) |
OnPopulateMesh
方法是一个虚方法,用于生成 Graphic
的顶点信息。默认的实现非常简单,如同上面对于 CanvasRenderer
的分析一样,这里仅仅是根据 RectTransfrom
的大小生成了一个矩形的网格并分割为两个三角形,效果如下图所示:

OnPopulateMesh
方法是可以被重写的,因此我们可以通过重写这个方法来实现自定义的顶点信息生成逻辑。
UpdateMaterial
UpdateMaterial
方法用于更新 Graphic
的材质信息,包括了材质与纹理两种数据。
1 | protected virtual void UpdateMaterial() |
UpdateMaterial
中是直接将 materialForRendering
与 mainTexture
交由 CanvasRenderer
处理,这里没有涉及到其他的逻辑。值得一提的是,网格与材质的修改时机并不相同,针对材质也有相应的 IMaterialModifier
接口,但已经在获得 materialForRendering
时处理过了,因此在 UpdateMaterial
中不再处理。
脏标记
在 Graphic
的渲染过程中也大量使用了脏标记。在上述的 Rebuild
方法中,会先判断 m_VertsDirty
是否为 true
,如果是则调用 UpdateGeometry
方法,并且更新之后会重新将 m_VertsDirty
置为 false
。m_MaterialDirty
同理。
这里我们再来分析一下脏标记的设置流程
1 | public virtual void SetVerticesDirty() |
分别由 SetVerticesDirty
和 SetMaterialDirty
方法来设置顶点及材质的脏标记,这两个方法都会将当前 Graphic
重新注册进 CanvasUpdateRegistry
的 m_GraphicRebuildQueue
中,以在下一帧的 CanvasUpdate.PreRender
阶段重新渲染。
此外还定义了 m_OnDirtyVertsCallback
和 m_OnDirtyMaterialCallback
两个回调,这两个回调可以在外部设置,用于在设置脏标记时执行一些额外的逻辑。
1 | public virtual void SetLayoutDirty() |
除了渲染流程的脏标记外,Graphic
还提供了 SetLayoutDirty
方法用于设置布局的脏标记。单独的 Graphic
一般不会涉及到布局的问题,但如果物体上还挂载了其他的布局相关组件,那么就可以通过这个方法来更新布局。
1 | public virtual void SetAllDirty() |
SetAllDirty
方法则是用于一次性设置所有的脏标记,表示整个 Graphic
的布局、材质以及顶点信息都需要重新渲染。由于分别调用了 SetxxxDirty
方法,因此所有注册的回调都会被执行。
下面就来看看什么地方会调用这些脏标记的方法。
1 | public virtual Material material |
首先,在材质以及颜色发生变化时,会调用 SetMaterialDirty
和 SetVerticesDirty
方法,分别对材质和顶点信息进行脏标记。
1 | protected override void OnRectTransformDimensionsChange() |
此外就是 UIBehaviour
中定义的一些回调方法,在层级结构发生变化、应用动画属性等情况下,也会调用 SetAllDirty
方法,表示整个 Graphic
都需要重新渲染。
1 | protected override void OnEnable() |
最后就是在一些生命周期方法中的调用,这里就不再赘述。
小结
至此,我们已经了解了 Graphic
的渲染流程,Graphic
在 ICanvasElement.Rebuild
方法中针对 CanvasUpdate.PreRender
阶段进行了几何信息和材质信息的更新,分别由 UpdateGeometry
和 UpdateMaterial
方法完成。UpdateGeometry
方法通过 VertexHelper
生成网格信息,并通过 CanvasRenderer
进行渲染;UpdateMaterial
方法则直接将材质及纹理信息交由 CanvasRenderer
处理。此外,Graphic
在渲染过程中也大量使用了脏标记,通过 SetVerticesDirty
、SetMaterialDirty
、SetLayoutDirty
等方法来标记需要重新渲染的信息。
杂项
除主要的渲染流程外,Graphic
中还有一些其他的方法用于处理其他逻辑,这里一并分析。
GraphicRegistry
在 Graphic
的许多生命周期函数中,都涉及到了 GraphicRegistry
的调用,这是一个单例类,用于管理 Canvas
中的 Graphic
对象。
1 | private readonly Dictionary<Canvas, IndexedSet<Graphic>> m_Graphics = new Dictionary<Canvas, IndexedSet<Graphic>>(); |
GraphicRegistry
在类中维护了一个 Dictionary
,用于存储 Canvas
与 Graphic
的映射关系。当一个 Graphic
对象被一个 Canvas
所接管时,就会调用 RegisterGraphicForCanvas
方法,将 Graphic
对象注册进 m_Graphics
中。
1 | /// <summary> |
在 Graphic
的 OnEnable
、OnTransformParentChanged
等方法中,都会调用 RegisterGraphicForCanvas
方法,建立起当前 Graphic
与 Canvas
的关系。
1 | /// <summary> |
同理,UnregisterGraphicForCanvas
方法则是用于解除 Graphic
与 Canvas
的关系。
1 | private static readonly List<Graphic> s_EmptyList = new List<Graphic>(); |
此外,还提供了 GetGraphicsForCanvas
方法,用于获取一个 Canvas
下的所有 Graphic
对象。
Canvas & CanvasRenderer 相关
Graphic
中定义了一些方法用于维护当前物体所属的 Canvas
对象。
1 | [private Canvas m_Canvas; ] |
CacheCanvas
方法用于获取当前 Graphic
所属的 Canvas
对象,通过 GetComponentsInParent
方法获取父物体中的所有 Canvas
组件,然后遍历找到第一个激活且启用的 Canvas
对象。
1 | [private CanvasRenderer m_CanvasRenderer; ] |
canvasRenderer
方法用于获取当前 Graphic
的 CanvasRenderer
组件,这里直接通过 GetComponent
方法获取,因为 Graphic
模块必然要依赖于一个 CanvasRenderer
组件。
Raycast
Raycast
是一个虚方法,用于判断一个给定的点是否能够被射线检测到,这部分的内容涉及到 GraphicRaycaster
,会在后续章节更详细讲解。
1 | public virtual bool Raycast(Vector2 sp, Camera eventCamera) |
这里的主要逻辑就是遍历当前物体上的所有 ICanvasRaycastFilter
组件,再调用 filter.IsRaycastLocationValid
方法来判断当前点是否可以被射线检测到。ICanvasRaycastFilter
本身也是不开放的,因此没法继续深入分析。
除了 ICanvasRaycastFilter
的处理外,函数还处理了 CanvasGroup
的情况。如果 CanvasGroup
本身设置了 ignoreParentGroups
,则会忽略父级的 CanvasGroup
的设置。
CanvasGroup
CanvasGroup
可以影响该组 UI 元素的部分性质,而不需要费力的对该组 UI 下的每个元素进行逐一地调整。CanvasGroup
同时作用于该组件 UI 下的全部元素。
-
Alpha
: 该组UI元素的透明度。注:每个UI最终的透明度是由此值和自身的alpha数值相乘得到。 -
Interactable
: 是否需要交互(勾选的则是可交互),同时作用于该组全部UI元素。 -
Blcok Raycasts
: 是否可以接收图形射线的检测(勾选则接受检测)。注:不适用于Physics.Raycast.。 -
Ignore Parent Group
: 是否需要忽略父级对象中的CanvasGroup的设置。(勾选则忽略)
辅助类
Graphic
的整个渲染流程中,还涉及到了一些辅助类,如 VertexHelper
、IMeshModifier
、IMaterialModifier
等。
VertexHelper
VertexHelper
是一个用于辅助生成 Mesh
数据的类,Graphic
中多处使用了 VertexHelper
来处理顶点信息。
UIVertex
在介绍 VertexHelper
之前,还是先来看一下 Unity 中为 UI 的一个顶点定义的结构体 UIVertex
。
1 | public struct UIVertex |
可以看出,一个 UI 的顶点信息包括了位置、法线、切线、颜色以及四组 UV 坐标。UV 坐标一般用于纹理映射,而法线和切线则用于光照计算。虽然在 UIVertex
中定义了四组 UV 坐标,但一般在渲染时只需要用第一个 UV 数据,其他的 UV 数据一般用于特殊的效果。
VertexHelper 的成员变量
回到 VertexHelper
中,首先还是看一下 VertexHelper
的成员变量。
1 | public class VertexHelper : IDisposable |
可以看出,VertexHelper
中的储存的顶点数据基本就可以理解为 List<UIVertex>
,多出的 m_Indices
则是用于储存三角形的索引信息。利用这两部分数据就可以生成一个完整的 Mesh
了。
VertexHelper 的内存管理
1 | public VertexHelper() |
VertexHelper
提供了两个构造函数,一个是默认构造函数,一个是通过 Mesh
对象来初始化的构造函数。在初始化时,会调用 InitializeListIfRequired
方法来初始化顶点数据。这里所分配的列标均通过 ListPool
来获取。
1 | public class VertexHelper : IDisposable |
细心的读者可能会发现,VertexHelper
实现了 IDisposable
接口,这是因为 VertexHelper
中使用了 ListPool
来获取内存,因此需要在使用完毕后释放内存。Dispose
方法中就是释放内存的逻辑。关于 C# 中的 IDisposable
接口,可以参考 C# 的 GC。
由于 VertexHelper
实现了 Dispose
方法,因此在使用时必须显式调用 Dispose
方法或使用 using
语句来释放内存。
1 | /// <summary> |
Clear
方法用于清空所有的顶点数据,可以用于提交完数据后的重置。
VertexHelper 的顶点操作
VertexHelper
中提供了一系列的方法用于操作顶点数据。
1 | public void AddVert(Vector3 position, Color32 color, Vector2 uv0, Vector2 uv1, Vector2 uv2, Vector2 uv3, Vector3 normal, Vector4 tangent) |
AddVert
方法用于向 VertexHelper
中添加一个顶点,有多种版本的重载可以选择。这里的顶点信息就是 UIVertex
结构体中的信息。
1 | public void AddTriangle(int idx0, int idx1, int idx2) |
AddTriangle
方法用于向 VertexHelper
中添加一个三角形,传入的参数是三个顶点的索引。
1 | /// <summary> |
FillMesh
方法用于将 VertexHelper
中的顶点数据填充至 Mesh
对象中。这里实际的实现也非常简单,就是调用 Mesh
对象的相应方法来填充数据。需要注意的是,Mesh
对象的顶点数量不能超过 65000,因此在填充数据前会进行检查。此外, Mesh
中这些方法的实现也是不开放的,因此我们只能分析到这里。
实际上,利用上述的三个方法,我们就可以生成一个完整的 Mesh
了。首先利用 AddVert
方法添加顶点,然后利用 AddTriangle
将顶点连接成三角形,最后调用 FillMesh
方法将数据填充至 Mesh
对象中。
而 Graphic
中也正是这么做的,如 OnPopulateMesh
方法:
1 | protected virtual void OnPopulateMesh(VertexHelper vh) |
就是这么的朴实无华。
其他函数
VertexHelper
中还有一些其他的函数提供给外界使用。
1 | public void PopulateUIVertex(ref UIVertex vertex, int i) |
PopulateUIVertex
方法用于将 VertexHelper
中的第 i
个顶点数据填充至 UIVertex
对象中,而 SetUIVertex
方法则是将 UIVertex
对象填充至 VertexHelper
中的第 i
个顶点。
1 | /// <summary> |
AddUIVertexQuad
方法用于直接添加一个四边形,传入的参数是四个 UIVertex
对象。函数中会自动将顶点连接成两个三角形。
1 | /// <summary> |
剩余的三个函数均与 CanvasRenderer
有关,由于没有开放源码。因此这里只能结合官方文档大致理解这些函数的功能。
-
AddUIVertexStream
:用于向CanvasRenderer
中添加一组UIVertex
数据以用于后续的渲染。 -
AddUIVertexTriangleStream
:用于向CanvasRenderer
中添加一组三角形数据,这里的UIVertex
数据是按照三角形的顺序排列的。 -
GetUIVertexStream
:用于从CanvasRenderer
中获取UIVertex
数据,填充至stream
中。
IMaterialModifier & IMeshModifier
IMaterialModifier
和 IMeshModifier
是两个接口,用于在 Graphic
的渲染过程中对材质和网格进行修改。
1 | public interface IMaterialModifier |
其中,IMaterialModifier
在 UGUI 中主要是完成一些 Mask 的操作,而 IMeshModifier
则主要用于实现阴影和描边等效果。实现这些接口的具体类我们放到下一章分析。
总结
至此,我们就几乎分析完了 UGUI 中 Graphic
类的实现。作为所有 UI 组件的基类,Graphic
主要实现的功能便是对 UI 的渲染。本篇也着重分析了 Graphic
的渲染流程。结合下图能够更清楚地理解 Graphic
的工作流程。
值得注意的是,Graphic
中还涉及到一些内容本文并没有展开分析,比如GraphicRaycaster
、IMaterialModifier
、IMeshModifier
等内容,这些会在后续的文章中继续分析。此外,Graphic
还涉及到了一些与动画相关的功能,即 TweenRunner
。由于这部分内容对于理解 UGUI 的原理来说并不重要,因此本系列文章不会分析。