NGUI 层级关系控制

NGUI元素的遮挡情况是不依赖空间关系,所以在NGUI上添加特效有时候特别蛋疼,特别是美术同学还要依赖空间关系来控制特效效果,那先看看看NGUI的层级是怎么处理的,不过下面的描述都是针对单个相机下的Panel,如果存在多个相机当然还要考虑相机的前后关系。在写之前,还是记录下这篇随笔参考的资源:《NGUI 渲染流程深入研究》) , 一篇不错的介绍,对理解整个流程很有帮助,对层级关系也做了很多描述;作为补充,《NGUI的渲染流程》 对 理解UIPanel、UIWidget、UIDrawCall的关系稍稍有点帮助

Render Queue

也就是渲染队列,默认情况下,Unity会基于对象距离摄像机的远近来排序对象。对象离摄像机越近就会优先绘制在其他更远的对象上面。对于大多数情况这是有效并合适的,但是在一些特殊情况下,你可能想要自己控制对象的绘制顺序。Unity提供给我们一些默认的渲染队列,每一个对应一个唯一的值,来指导Unity绘制对象到屏幕上的顺序。这些内置的渲染队列被称为Background, Geometry, AlphaTest, Transparent, Qverlay。具体描述如下,也就是说数值越小越先绘制。

渲染队列 渲染队列描述 渲染队列值
Background 通常被最先渲染 1000
Geometry 默认的渲染队列,它被用于绝大多数对象。不透明几何体使用该队。 2000
AlphaTest 通道检查的几何体使用该队列。它和Geometry队列不同,对于在所有立体物体绘制后渲染的通道检查的对象,它更有效。 2450
Transparent 该渲染队列在Geometry和AlphaTest队列后被渲染。任何通道混合的(也就是说,那些不写入深度缓存的Shaders)对象使用该队列,例如玻璃和粒子效果 3000
Overlay 该渲染队列是为覆盖物效果服务的。任何最后被渲染的对象使用该队列,例如镜头光晕。 4000
UI RenderQueue

NGUI 层级关系控制

对UI而言,一般是浮动在场景的上层,而且可能使用透明UI,所以RenderQueue一般从3000开始,通常情况下,Render Queue会在Shader的SubShader的Tag中明确描述渲染队列,如:

Tags { "Queue"="Transparent" }

如果查看NGUI的shader,应该可以看到这句话。

动态材质

上一篇文章介绍过,NGUI中所有元素最后都会在DrawCall中生成Mesh、MeshRender、Material,然后被绘制出来。NGUI使用Atlas来管理图片数据,也就是说不同层级的组件也会使用相同的Atlas,这就需要每个DrawCall在运行时动态修改材质的RenderQueue,NGUI通过材质参数来处理这个问题。具体可以可以参考Unity Material.renderQueue的定义, 理解这句话:

By default materials use render queue of the shader it uses. You can override the render queue used using this variable. Note that once render queue is set on the material, it stays at that value, even if shader is later changed to be different.

UIDrawCall实际会创建一个叫做mDynamicMat的Material用作后续的材质渲染顺序、贴图、Shader参数设置。

Widget绘制顺序

先看下DrallCall的生成,在UIPanel.FillllDawcall函数中,先对Widget进行排序,然后在对WIdget进行遍历过程中,如果相邻的Widget使用的材质、贴图或者Shader不相同则创建一个新的Drawcall, 也就说DrawCall列表中的顺序和Widget的顺序是一致的(对于存在DrawCall合并的情况,Drawcall中会记录widget上深度的起始和终止数值)。

在同一个Panel中,Widget会按照深度进行排序,而DrawCall的RenderQueue则根据从Panel的RenderQueue起始数值加上在DrawCall列表中的位置,而对单个drawCll而言,生成的顶点则也会根据Widget深度从小到到的顺序进行填充。也即是说深度越小的组件先绘制,会被后面深度大的组件遮挡住。

    // Widget 排序策略,深度相同情况下排序规则就会不明确
static public int PanelCompareFunc (UIWidget left, UIWidget right)
{
if (left.mDepth < right.mDepth) return -1;
if (left.mDepth > right.mDepth) return 1; Material leftMat = left.material;
Material rightMat = right.material; if (leftMat == rightMat) return 0;
if (leftMat == null) return 1;
if (rightMat == null) return -1; return (leftMat.GetInstanceID() < rightMat.GetInstanceID()) ? -1 : 1;
} // 更新Drawcall的绘制顺序
void UpdateDrawCalls ()
{ for (int i = 0; i < drawCalls.Count; ++i)
{
UIDrawCall dc = drawCalls[i]; dc.renderQueue = (renderQueue == RenderQueue.Explicit) ? startingRenderQueue : startingRenderQueue + i;
dc.alwaysOnScreen = alwaysOnScreen &&
(mClipping == UIDrawCall.Clipping.None || mClipping == UIDrawCall.Clipping.ConstrainButDontClip);
dc.sortingOrder = mSortingOrder;
dc.sortingLayerName = mSortingLayerName;
dc.clipTexture = mClipTexture;
}
}

Panel绘制顺序

对于不同的Panel而言,NGUI会根据Panel的深度值进行排序,然后依次计算其起始RenderQueue数值。这样的话 深度高的Panel,其内部组件的RenderQueue的数值也会相对较高

	/// <summary>
/// Function that can be used to depth-sort panels.
/// </summary> static public int CompareFunc (UIPanel a, UIPanel b)
{
if (a != b && a != null && b != null)
{
if (a.mDepth < b.mDepth) return -1;
if (a.mDepth > b.mDepth) return 1;
return (a.GetInstanceID() < b.GetInstanceID()) ? -1 : 1;
}
return 0;
} 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(); int rq = 3000; // 更新Panel的渲染顺序
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);
}
}
}
}

结论

A 一般情况下,UIPanel\Widget的层级使用Depth来控制其前后关系就可以满足需求,但是对于特效和U> I前后遮挡这种情况就比较难处理,不过可以通过三种方式解决:

  1. 相机深度
  2. SortingOrder(一直没弄明白这是什么鬼)
  3. RenderQueue

B. DrawCall的数量和组件的深度的也有关系,同样材质的组件使用连续的深度值就会合并为一个组件,OK ,实际上使用过程中,不合理使用似乎更多点,下图就是一种比较恶劣的使用情况,两张图片,但是深度设置不合理,却有10个DrawCall

NGUI 层级关系控制

上一篇:8款超酷体验的jQuery/CSS3应用插件


下一篇:asp.net学习之数据绑定控件、数据源控件概述