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

對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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM