控制renderQueue解决NGUI与Unity3D物体渲染顺序问题

发表于2015-11-09
评论2 8.1k浏览

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的顺序叠加:

http://km.oa.com/files/post_photo/165/204165/6ab1f1e9a531a1fbbe4a1d8a1284f2f0.png

测试的工程地址:

\............3.客户端frankUISandwichWithParticle.zip

测试场景为AssetsTestScene.unity


另外这一工程中也包含了一个以邓老师的方式解决这一问题的示例,场景为AssetsTestScene_orig.unity

首先参考一下邓老师方法中实现这类叠加后的Draw Call细节:

http://km.oa.com/files/photos/captures/201406/1403786989_28.png

 

可以看到,因为所有元素最终都是以NGUI的元素进行管理的,draw call非常清晰,一层UI夹一层rtt纹理,render queue从3000开始依次排序下来,标准的多层三明治结构。

再来看另一种方式下UI的draw call细节:

http://km.oa.com/files/photos/captures/201406/1403787715_24.png

这种方式下,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物体上

http://km.oa.com/files/photos/captures/201406/1403788827_11.png

指定一个作为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、赵帆的耐心。:)

 

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引