UGUI源码解析——Graphic

Posted Hello Bug.

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UGUI源码解析——Graphic相关的知识,希望对你有一定的参考价值。

一:前言

Graphic是UGUI的核心组件,负责图像的显示与更新,是MaskableGraphic的基类,而MaskableGraphic是Text、RawImage、Image的基类


二:源码解析——类头


Graphic继承自UIBehaviour和ICanvasElement
UIBehaviour是所有UGUI组件的最基类,负责接收来自UnityEngine或者UnityEditor的事件:UGUI源码解析——UIBehaviour
ICanvasElement负责接收Canvas重新渲染的事件:UGUI源码解析——CanvasUpdateRegistry

Graphic添加了三个特性
——DisallowMultipleComponent:不允许一个对象添加多个相同的组件
——RequireComponent:依赖于CanvasRenderer画布渲染组件和RectTransform组件
——ExecuteAlways:编辑器模式下可以运行
另外Graphic是一个抽象类,意味着不能被实例化,但是它提供了很多可重写的方法可被继承并重写


三:源码解析——继承自UIBehaviour的方法

——OnEnable

首先调用CacheCanvas方法找到渲染当前元素的Canvas,之后调用GraphicRegistry.RegisterGraphicForCanvas方法将此元素注册到m_Graphics字典中(每个Canvas对应了一个Graphic序列),接着设置s_WhiteTexture(mainTexture的默认贴图),最后调用SetAllDirty将元素添加到布局和图像重建序列中(这样说有些不严谨,因为布局重建是通过LayoutRebuilder类管理的,如果父物体身上没有继承ILayoutGroup的组件是不会添加到布局重建序列中的),SetAllDirty下面会重点说

——OnDisable

首先从GraphicRegistry和CanvasUpdateRegistry中将当前元素移除注册并清理canvasRenderer,最后通过LayoutRebuilder.MarkLayoutForRebuild方法将元素添加到布局重建序列中(因为Graphic已经Disable所以不需要将元素添加到图像重建序列中)

——OnRectTransformDimensionsChange

将元素添加到图像和布局重建序列中,如果正在进行布局重建则只将元素添加到图像重建序列中

——OnBeforeTransformParentChanged

首先将渲染当前元素的canvas从GraphicRegistry移除注册,并将元素添加到布局重建序列中

——OnTransformParentChanged

首先调用CacheCanvas方法找到渲染当前元素的Canvas,之后调用GraphicRegistry.RegisterGraphicForCanvas方法将此元素注册到m_Graphics字典中(每个Canvas对应了一个Graphic序列),最后使用SetAllDirty将元素添加到图像和布局重建序列中

——OnCanvasHierarchyChanged

首先调用CacheCanvas方法找到渲染当前元素的Canvas,如果此canvas与缓存的canvas不同则移除之前注册的canvas并调用GraphicRegistry.RegisterGraphicForCanvas方法重新将此元素注册到m_Graphics字典中(每个Canvas对应了一个Graphic序列)


四:源码解析——继承自ICanvasElement的方法

——Rebuild

在CanvasUpdateRegistry类中给委托Canvas.willRenderCanvases注册了PerformUpdate方法,PerformUpdate会在CanvasRender渲染之前会遍历布局和图像重建序列调用每个元素的Rebuild方法,在Rebuild方法里调用UpdateGeometry和UpdateMaterial更新网格和材质,布局不在Graphic类中管理而是单独通过LayoutRebuilder去管理的​​​​​

UpdateGeometry方法中首先调用了OnPopulateMesh方法将元素的顶点、颜色、UV等信息暂存到m_VertexHelper中,Graphic类中的OnPopulateMesh绘制了一个矩形,我们可以在自己的UI类中,重写OnPopulateMesh方法,实现自定义的UI
接着遍历身上继承了IMeshModifier的组件去调用ModifyMesh方法(一般是实现网格的一些特效,例如Shadow、Outline),更新m_VertexHelper数据
然后调用s_VertexHelper.FillMesh(workerMesh)将暂存的数据填入workMesh中
最后调用canvasRenderer.SetMesh(workerMesh)将workerMesh赋值给canvasRenderer,这样下一帧canvasRenderer渲染时就会用workerMesh的数据进行绘制了(SetMesh方法最终在C++中实现,这也是UGUI的效率比NGUI高一些的原因,因为NGUI的Mesh合并是在C#中完成)

UI是如何绘制出来的?
首先要生成显示UI用的Mesh,例如一个矩形的Mesh,由4个顶点,2个三角形组成,每个顶点都包含UV坐标、顶点色等信息,调节Image或者Text的颜色,其实就是改变它们的顶点色
然后将网格和纹理信息发送给CanvasRender,CanvasRender再将渲染命令发送给GPU进行渲染,这样一个简单的UI元素就显示出来了,这个过程叫做一次批处理
其实这个流程与渲染一个普通的Cube是类似的
可以简单的理解为,所谓的UI其实就是用一个正交的Camera看着若干的平面网格,只不过单单的显示出来还远远不够,还有一些其他操作,比如DrawCall需要合并,Button需要点击等等


UpdateMaterial方法中首先将自身材质赋值给canvasRenderer(遍历身上继承了IMaterialModifier的组件(例如Mask),然后将贴图赋值给canvasRenderer,这样下一帧canvasRenderer渲染时就会用materialForRendering和mainTexture的数据进行绘制了


五:SetDirty


SetLayoutDirty调用了LayoutRebuilder.MarkLayoutForRebuild方法,此方法内部其实是调用了CanvasUpdateRegistry类中的InternalRegisterCanvasElementForLayoutRebuild方法将元素添加到了布局重建的序列中
SetMaterialDirty和SetVerticesDirty调用了CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild方法将元素添加到了图像重建的序列中


六:UGUI优化技巧

以上是关于UGUI源码解析——Graphic的主要内容,如果未能解决你的问题,请参考以下文章

UGUI源码解析——Mask

UGUI源码解析——Mask

UGUI源码解析——Mask

UGUI源码解析——MaskableGraphic

UGUI源码解析——MaskableGraphic

UGUI源码解析——MaskableGraphic