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)給預留了出來。
http://zzido.cn/home/article/view/id/65272.html
這里先插播一下關於設置UI的draw call的細節,也是這一方法中繁瑣的部分。我們知道NGUI會將采用同樣材質的widget合並到同一個draw call中進行渲染。然而在我們這個需求中,這一功能導致了無法在widget之間插入其它渲染隊列的問題,也就是原始的三明治問題。如果只是涉及到UI控件之間的穿插,NGUI可以通過depth設置來解決:
如果是使用同一材質的多個控件設置了不同的depth值,則NGUI還是將這些控件合並為同一個draw call,而在內部進行了排序; http://m.zzido.cn/home/article/view/id/65273.html
如果設置了不同depth的多個控件,穿插使用了不同的材質,則NGUI會將其打散為不同的draw call,順序即按照depth指定。鄧老師的方式就用到的這一特性,其一共使用了2種材質而設置不同depth形成穿插關系,於是被打散成了7個dc。
http://www.zzido.cn/home/article/view/id/65272.html
我采用的方式,屬於上述第一種情況,無法簡單用depth設置來解決。為了將一個NGUI自動合並的dc打散,有多種hack的方式,這里選擇的是手動給每個我們希望打散到不同dc的widget再添加一個panel的方式。增加panel原則上並不推薦,然而針對這一需求,實際上增加的空panel並不影響性能。其結果就是上圖中所示。
http://m.zzido.cn/home/article/view/id/65272.html
在為每個歸屬於不同層次的widget指定了所屬的render queue順序之后,剩下的就是為每個unity的特效指定應歸屬的render queue。
這里引入了一個腳本:RenderQueueModifier.cs( https://www.zzido.cn/home/article/view/id/65272.html )。使用時,需要將這一腳本拖到對應的Unity的3D物體上
指定一個作為target的widget,以及排序方式即可。上圖的例子中,指定的target為card1,type為FRONT,含義是將這一特效指定為在Card1控件的前面。
其原理也較為簡單,直接貼源碼: http://zzido.cn/home/article/view/id/65273.html
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;
柳斷腸處長向尊前悲老大有人夫婿擅侯王當時只受聲名累貴戚名豪競延致照花前后鏡花面交相 http://zzido.cn/home/article/view/id/65274.html
映新帖綉羅襦雙雙金鷓鴣折戟沈沙鐵未銷自將磨洗認前朝東風不與周郎便銅雀春深鎖二喬陣陣輕寒細馬驕竹林茅店小簾招東風已綠南溪水更染溪南萬柳條崢嶸赤雲西日腳下平地柴門鳥雀噪歸客千里至之死矢靡它母也天只不諒人 http://www.zzido.cn/home/article/view/id/65274.html
只泛彼柏舟芷葺兮荷屋繚之兮杜衡合百草兮實庭建芳馨兮廡門中泠南畔石盤陀古來出沒隨濤波試登絕頂望鄉國江南江北青山多中原干戈古亦聞豈有逆胡傳子孫遺民忍死望恢復幾處今宵垂淚痕中州盛日閨門多暇記得偏重三五鋪翠冠兒捻金雪柳簇帶爭濟楚如今憔悴風鬟霜鬢怕見夜間出去不如向簾 http://m.zzido.cn/home/article/view/id/65274.html 兒底下聽
人笑語終愧巢與由未能易其節沈飲聊自適放歌破愁絕眾芳搖落獨暄妍占盡風情向小園疏影橫斜水清淺暗香浮動月黃昏眾鳥高飛盡孤雲獨去閑相看兩不厭只有敬亭山重過閶門萬事非同來何事不同歸梧桐半 https://www.zzido.cn/home/article/view/id/65274.html 清霜后頭白鴛鴦失伴飛重湖
疊巘清嘉有三秋桂子十里荷花羌管弄晴菱歌泛夜嬉嬉釣叟蓮娃千騎擁高牙乘醉聽簫鼓吟賞煙霞異日圖將好景歸去鳳池誇重泉若有雙魚寄好知他年來苦樂與誰相倚我自中宵成轉側忍聽湘弦重理待結個他生知已還怕兩人俱薄命再緣慳剩月零風里清淚盡紙灰起晝出耘田夜績麻村庄兒女各當家童孫未解供耕織也傍桑陰學種瓜朱門歌舞爭新態綠綺塵埃試拂弦常恨聞名不相識相逢罇酒盍留連朱弦已為佳人絕青眼聊因美酒橫萬里歸船弄長笛
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設為父子結構即可 http://www.zzido.cn/home/article/view/id/65273.html
RenderQueueModifier.cs腳本還有優化的余地,特別是可以增強編輯器支持,來達到不啟動游戲即可實時查看疊加效果的功能。
這個方案測試是在老版本的NGUI3.0.6中進行的。3.6版的新NGUI中,引入了手動修改render queue的功能,會更加方便為每個ui指定所屬的render queue。