【小松教你手遊開發】【unity實用技能】控制renderQueue解決NGUI與Unity3D物體渲染順序問題

chrisfxs發表於2016-01-18

http://gad.qq.com/college/articledetail/7082053


NGUI與Unity3D物體渲染順序問題,做UI的同學應該都遇到過。主要指的是UI與Unity製作的特效、3D人物等一同顯示時的層次問題。

       之前鄧老師就這一問題,專門做了一次分享。鄧老師在分享時也指出了這類問題的根源:由於UI與特效等都是以transparent方式渲染,而Unity與NGUI在管理同是透明物體的render queue時實際上互相沒有感知,於是引出排序問題。鄧老師介紹了以Render To Texture方式解決這一問題的一種方法,文件見svn /wlgame_proj/trunk/Client/Doc/規範文件/UI上的特效顯示.docx


       然而使用rtt解決這類問題總感覺過重,且不夠靈活;而且rtt為了兼顧效率,紋理不可能設定太大,所以也導致了渲染精度降低、細節丟失的問題。

今天嘗試了另一種思路來解決這一問題:直接控制Unity中特效的render queue值,來達到使得UI、特效按照我們希望的順序進行渲染的目的。

先上效果圖,數字標註了不同的層次,按照0-7的順序疊加:

測試的工程地址:

\\......\......\03.客戶端\frank\UISandwichWithParticle.zip

測試場景為Assets\TestScene.unity


另外這一工程中也包含了一個以鄧老師的方式解決這一問題的示例,場景為Assets\TestScene_orig.unity

首先參考一下鄧老師方法中實現這類疊加後的Draw Call細節:

 

可以看到,因為所有元素最終都是以NGUI的元素進行管理的,draw call非常清晰,一層UI夾一層rtt紋理,render queue從3000開始依次排序下來,標準的多層三明治結構。

再來看另一種方式下UI的draw call細節:

這種方式下,UI和特效還是分開渲染的,所以NGUI的Draw Call統計裡只能看到UI的4個dc。注意,這裡手動設定了每個UI的render queue的值,分別是3000、3002、3004、3006,和上面的dc順序參照相對應的關係,可以發現是把用於特效渲染的render queue值(3001、3003、3005)給預留了出來。


這裡先插播一下關於設定UI的draw call的細節,也是這一方法中繁瑣的部分。我們知道NGUI會將採用同樣材質的widget合併到同一個draw call中進行渲染。然而在我們這個需求中,這一功能導致了無法在widget之間插入其它渲染佇列的問題,也就是原始的三明治問題。如果只是涉及到UI控制元件之間的穿插,NGUI可以通過depth設定來解決:

  1. 如果是使用同一材質的多個控制元件設定了不同的depth值,則NGUI還是將這些控制元件合併為同一個draw call,而在內部進行了排序;
  2. 如果設定了不同depth的多個控制元件,穿插使用了不同的材質,則NGUI會將其打散為不同的draw call,順序即按照depth指定。鄧老師的方式就用到的這一特性,其一共使用了2種材質而設定不同depth形成穿插關係,於是被打散成了7個dc。


我採用的方式,屬於上述第一種情況,無法簡單用depth設定來解決。為了將一個NGUI自動合併的dc打散,有多種hack的方式,這裡選擇的是手動給每個我們希望打散到不同dc的widget再新增一個panel的方式。增加panel原則上並不推薦,然而針對這一需求,實際上增加的空panel並不影響效能。其結果就是上圖中所示。


在為每個歸屬於不同層次的widget指定了所屬的render queue順序之後,剩下的就是為每個unity的特效指定應歸屬的render queue。

這裡引入了一個指令碼:RenderQueueModifier.cs(來源:http://www.tasharen.com/forum/index.php?topic=776.0)。使用時,需要將這一指令碼拖到對應的Unity的3D物體上

指定一個作為target的widget,以及排序方式即可。上圖的例子中,指定的target為card1,type為FRONT,含義是將這一特效指定為在Card1控制元件的前面

其原理也較為簡單,直接貼原始碼:

using UnityEngine;

using System.Collections;

 

public class RenderQueueModifier : MonoBehaviour

{

public enum RenderType

{

FRONT,

BACK

}

public UIWidget m_target = null;

public RenderType m_type = RenderType.FRONT;

Renderer[] _renderers;

int _lastQueue = 0;

void Start ()

{

_renderers = GetComponentsInChildren<Renderer>();

}

void FixedUpdate() {

if( m_target == null || m_target.drawCall == null )

return;

int queue = m_target.drawCall.renderQueue;

queue += m_type == RenderType.FRONT ? 1 : -1;

if( _lastQueue != queue )

{

_lastQueue = queue;

foreach( Renderer r in _renderers )

{

r.material.renderQueue = _lastQueue;

}

}

}

}


可以看到,原理上就是直接修改這一特效下所有renderer組建中的material的renderQueue值,來按照需要指定。

還是以上面截圖的示例為例,Card1位於NGUI指定的render queue位置3000,則這個特效所在的render queue為3001,而在它之後render queue為3002的控制元件正好是控制元件Mask1。

所以和鄧老師方案最終的render queue效果相比較,其實算是殊途同歸,可以在不使用Render To Texture的情況下,達到更好的效果。

TODO:

  • 偷懶所以手動新增的panel直接放在widget上,導致ngui會提示一條錯誤日誌。這個按照規範,將panel與widget設為父子結構即可
  • RenderQueueModifier.cs指令碼還有優化的餘地,特別是可以增強編輯器支援,來達到不啟動遊戲即可實時檢視疊加效果的功能。
  • 這個方案測試是在老版本的NGUI3.0.6中進行的。3.6版的新NGUI中,引入了手動修改render queue的功能,會更加方便為每個ui指定所屬的render queue。

最後,感謝在實驗過程中被我頻繁騷擾的鄧老師、ice、趙帆的耐心。:)


相關文章