0. 概述
本文将从整体类图出发,先对NGUI渲染涉及到几个重点的类的关系有一个整体的了解,接着再讲下各个类的作用,然后通过源码将下整个渲染的流程,最后尝试解答几个问题。本文使用的NGUI版本是3.8.2。
1. 整体类图
我们从图中可以看到涉及到NGUI渲染流程的类主要有UIRect、UIWidget、UIPanel、UIDrawcall和UIGeometry。
2. 各个类的作用
2.1 UIRect
UIRect作为UIWidget和UIPanel的抽象基类,主要作用是维护4个锚点(左/上/右/下),并根据锚定更新类型在适当的时候更新4个锚点,并提供OnAnchor抽象方法让子类根据锚点更新自己的尺寸。
2.2 UIGeometry
- UIGeometry由UIWidget生成,UIWidget都有一个UIGeometry
- UIGeometry包含Widget的顶点verts,几何顶点的纹理坐标uvs,几何顶点的颜色cols,并提供接口将顶点数据转换为所属Panel局部坐标的顶点mRtpVerts,法线mRtpNormal和切线数据mRtpTan。
- UIGeometry将几何数据生成分开为3个步骤,保证widget只有在自己发生变化的时候重build
- 1.清空缓存-对应方法Clear
-
- 根据世界坐标到panel本地坐标的矩阵将转换Widget的顶点数据- 对应方法ApplyTransform
-
- 向特定传入的buffer填入转换后的顶点等数据-对应方法WriteToBuffers
2.3 UIWidget
- UIWidget是每个UI组件如我们常见的UITexture、UISprite、UILabel的基类,每个UI元素对应一个UIWidget。
- UIWidget包含UI元素信息如width、height、depth、pivot、alpha、color,并提供几个抽象属性material、mainTexture和shader由子类
去实现。 - UIWidget都有一个UIGeometry,并提供***OnFill***抽象方法,由子类如UILabel和UISprite将顶点、UV、Color、法线和切线数据填入UIGeometry缓存中。
- UIWidget都有一个UIDrawcall,但一个UIDrawcall对应1个或者多个的UIWidget,UI元素的渲染最终是通过UIDrawcall实现的。
2.4 UIDrawCall
- UIDrawcall是渲染UI元素的载体,由UIPanel生成,其中包含渲染使用的MeshRender、Mesh、MeshFilter和Material组件。
- 其核心函数是UpdateGeometry,其分为以下几个步骤进行几何信息更新:1. 生成Mesh,2.赋值缓存的顶点、UV、颜色、法线和切线等数据给Mesh,3.更新MeshRender使用的材质
2.5 UIPanel
- UIPanel作为UIWidget列表和UIDrawcall列表的管理者,监测UIPanel下的Widget的变化,一旦Widget发生变化就需要更新widget对应的drawcall。在必要的时候需要重新生成全部的drawcall。
- UIPanel的UIWidget列表是按照widget的深度排序的
- UIPanel维护一个静态的panel列表,是按照panel的深度排序的
3. 渲染流程
[图片上传中…(NGUI_UIPanel.png-f91a26-1571404641111-0)]
3.1 UIPanel的LateUpdate
先遍历根据深度进行排序的静态的Panel列表,并调用UIPanel的UpdateSelf函数更新每个Panel.
再遍历Panel列表根据Panel的RenderQueue类型不同(Automatic/StartAt/Explicit),设置相应的RenderQueue数据并调用Panel的UpdateDrawCalls更新Drawcall。
void LateUpdate ()
{
#if UNITY_EDITOR
if (mUpdateFrame != Time.frameCount || !Application.isPlaying)
#else
if (mUpdateFrame != Time.frameCount)
#endif
{
mUpdateFrame = Time.frameCount;
// Update each panel in order
for (int i = 0, imax = list.Count; i < imax; ++i)
list[i].UpdateSelf();
int rq = 3000;
// Update all draw calls, making them draw in the right order
for (int i = 0, imax = list.Count; i < imax; ++i)
{
UIPanel p = list[i];
if (p.renderQueue == RenderQueue.Automatic)
{
p.startingRenderQueue = rq;
p.UpdateDrawCalls();
rq += p.drawCalls.Count;
}
else if (p.renderQueue == RenderQueue.StartAt)
{
p.UpdateDrawCalls();
if (p.drawCalls.Count != 0)
rq = Mathf.Max(rq, p.startingRenderQueue + p.drawCalls.Count);
}
else // Explicit
{
p.UpdateDrawCalls();
if (p.drawCalls.Count != 0)
rq = Mathf.Max(rq, p.startingRenderQueue + 1);
}
}
}
}
3.2 UIPanel的UpdateSelf
3.2.1 流程
- UpdateTransformMatrix
更新世界坐标到Panel本地坐标的矩阵和裁剪区域 - UpdateLayers
更新widget layer,保持widget的layer与panel一致 - UpdateWidgets
更新属于Panel下的所有widget- 判断是否重Build
- 是 调用UIPanel的FillAllDrawCalls重新生成Drawcall
- 否 遍历Drawcall列表如果Drawcall.isDirty就调用FillDrawCall更新对应Drawcall
- 判断是否重Build
3.2.2 代码
void UpdateSelf ()
{
mUpdateTime = RealTime.time;
UpdateTransformMatrix();
UpdateLayers();
UpdateWidgets();
if (mRebuild)
{
mRebuild = false;
FillAllDrawCalls();
}
else
{
for (int i = 0; i < drawCalls.Count; )
{
UIDrawCall dc = drawCalls[i];
if (dc.isDirty && !FillDrawCall(dc))
{
UIDrawCall.Destroy(dc);
drawCalls.RemoveAt(i);
continue;
}
++i;