以下内容基于 NGUI v.3.11.2 。示例是在 Unity 5.5 中测试。
渲染结构
NGUI 用 Panel 管理渲染。一个 UIPanel
实例表示一个 Panel 。UIPanel.widgets
表示 Panel 中需要渲染的物体,UIPanel.drawCalls
表示 Panel 中的渲染指令,而 static UIPanel.list
存放所有的 Panel 。默认情况下用于渲染的 GameObject (DrawCall 所在的)是隐藏的,可以定义宏 SHOW_HIDDEN_OBJECTS
来显示 DrawCall GameObject 。于是 NGUI 需要控制的渲染顺序包括以下。
- 不同的 Panel 的渲染顺序。
- 同一 Panel 中所有 Widget 的渲染顺序。
- 同一 DrawCall 中绘制的三角形顺序。
渲染顺序
UI 是 2D 的,不需要在渲染时开启深度测试(depth test)。因此,若渲染位置相同时,后渲染的图形覆盖先渲染的图形。既然 NGUI 通过创建 DrawCall GameObject 来渲染,那么先创建的 DrawCall GameObject 就表示先渲染,后创建的 DrawCall GameObject 在相同位置渲染时就会覆盖之前渲染的图形。
在使用 NGUI 时,都知道可以设置 Panel 和 Widget 的 Depth 属性。Depth 值越低,越容易被覆盖。根据上面的渲染机制分析可以得到:Depth 值低的 Panel 或 Widget 会先被处理。下面进入具体的代码中来验证。
- 根据 Depth 排序 Panel 。在 UIPanel.cs 文件查找
list.Sort(CompareFunc);
,再看UIPanel.CompareFunc
函数刚好发现 Depth 越小,在list
中越靠前。 - 根据 Depth 排序 Widget 。在 UIPanel.cs 文件查找
widgets.Sort(UIWidget.PanelCompareFunc);
再看比较函数,发现和 Panel 一样,Depth 越小在列表中越靠前。注:NGUI 并不是每次都重新排序所有的 Widget ,新加的 Widget 根据 Depth 插入到指定位置就避免了完全重排,但是排序规则都一样的。 - 合并 DrawCall 。查看函数
UIPanel.FillAllDrawCalls
可知,若UIPanel.widgets
相邻的 Widget 满足 Material 、Texture 、Shader 相同,则会把此 Widget 的绘制合并到前一个 Widget 的 DrawCall 中。其实合并或者不合并,后一个 Widget 都是后绘制,因此不影响最终的结果。
我们明白了应用层面 Depth 的设置,但是实际的渲染是在 Shader 中进行的,如何根据应用层的 Depth 控制 Shader 的渲染呢?其实很简单。排序后设置相应的 renderQueue
即可。具体实现搜索 UIDrawCall.cs 中 mDynamicMat.renderQueue = mRenderQueue;
。
经过上面分析,Depth 小的 Panel 先绘制,同一 Panel 中 Depth 小的 Widget 先绘制。这就是 NGUI 渲染顺序。由于需要控制 Panel 绘制顺序,所以 NGUI 绘制时在单个 LateUpdate
中根据列表处理所有的 Panel ,为了避免所有 Panel 的 LateUpdate
都处理一遍,因此引入了 static UIPanel.mUpdateFrame
控制调用频率。如下代码(为了方便显示,删除了一些代码)。
void LateUpdate ()
{
if (mUpdateFrame != Time.frameCount)
{
mUpdateFrame = Time.frameCount;
// Update each panel in order
for (int i = 0, imax = list.Count; i < imax; ++i)
list[i].UpdateSelf();
}
}
模拟渲染
既然写了那么多字,下面来模拟一下渲染。先看运行截图。
一共绘制左右两组三角形,每组包含 2 个 DrawCall ,每个 DrawCall 绘制了 2 个三角形。
每个 DrawCall 先绘制亮色三角形,再绘制暗色三角形,所以暗色挡住亮色区域。
查看 Hierarchy 视图中以 __dc 开头的 GameObject ,它们就是用于渲染的 DrawCall GameObject 。
看下面的渲染调用。左边绿色三角形的
renderQueue
更大,因此后绘制而挡住了红色三角形。右边则相反。DrawCallMgr.CreateDrawCall(Vector3.zero, Color.green, "green", 3001); DrawCallMgr.CreateDrawCall(new Vector3(Vector3.zero.x, Vector3.zero.y - 0.5f, Vector3.zero.z), Color.red, "red", 3000);
DrawCallMgr.CreateDrawCall(new Vector3(Vector3.zero.x + 3.0f, Vector3.zero.y, Vector3.zero.z), Color.green, "green", 3000); DrawCallMgr.CreateDrawCall(new Vector3(Vector3.zero.x + 3.0f, Vector3.zero.y - 0.5f, Vector3.zero.z), Color.red, "red", 3001);
如何运行示例代码呢。下载 DrawCallTest.cs 和 SimpleColor.shader 然后把文件导入 Unity 工程中(我是在 Unity5.5 中测试的,5.x 应该都可以)。导入文件后,新建场景,把 DrawCallTest.cs 拖动到 Main Camera 上,然后运行游戏即可。