NGUI与Unity3d物体交叉显示的一种解决方案


   在项目的开发过程中,很多做过UI的同学估计都会遇到NGUIunity3d物体的交叉显示问题,不知道如何处理,或者各种各样的界面穿插问题,界面层级混乱,对于界面来说,这些应该算是一个很严重的问题。在之前的一个预演项目在界面需求时,就曾遇到这样的问题,想把一个美术特效放在两个不同层级的Sprite中间显示,或者一个面板中显示了粒子效果后,再打开一个面板时,粒子效果不能穿透显示到新的面板,当时的解决方案只是解决了两个面板之间不会穿透显示,并没有很好的解决两个两个不同层级的Sprite之间显示。在这里,结合我们项目中实际使用的一种解决方式,来分析如何方便快捷的解决这个问题。

 

       为了深入理解这个问题,我们先来分析下NGUI的渲染流程和unity渲染顺序控制方式,然后介绍如何实现NGUI与模型和粒子特效穿插层级。

 

1: NGUI的渲染流程

UI制作的过程中,我们在UIPanel中将一个个UISpriteUILabel等组件拼装、放置好。UIPanel作为总体控制,将组件显示出来UIPanel,通过遍历自己子类下所有的UIWidget组件(已经按深度排序),先创建一个UIDrawCall,然后把该Widgetmaterialtextureshader对象以及Geometry的缓存传给UIDrawCall,如此反复循环搜索该UIPanel下的每一个Widget,只要是materialtextureshader都和上一个Widget一样的Widget,他们的缓存都传给同一个UIDrawCall,直到循环结束或者碰到一个材质球,贴图,shader对象任一不相同的Widget。当遇到这种Widget,循环会再创建一个新的UIDrawCall,然后传递materialtextureshader,缓存,如此这般,直到循环完全结束。

每次有新的UIDrawCall产生,UIPanel就会调用上一个UIDrawCallUpdateGeometry()函数,来创建渲染所需的对象。这些对象分别是MeshFilterMeshRender,和最重要的Mesh(Mesh的顶点,UVColor,法线,切线,还有三角面)。这些对象都会像我们正常在游戏中新建Cube一样,依附在创建UIDrawCall时生成的GameObject上以便可以渲染。我们在Editor中是看不到这个GameObject的,是因为创建的时候设置了HideFlags.HideAndDontSave所以,NGUI的实际渲染流程,就是一个把Widget上的视觉组件生成的缓存,做成UIDrawCall之后,生成mesh来渲染的过程。

 

了解了渲染流程之后,我们在来看看NGUI中,什么对渲染的层级有决定性的影响。

 

A:Depth NGUI中最正统的控制panel之间层级关系的就是它的 depth 属性。depth越大,越靠后渲染,越在前面显示。

NGUI与Unity3d物体交叉显示的一种解决方案

B:sortingOrder  ,一个render上的int属性值越大越靠前,和空间无关,可直接在UIPanel上设置。

NGUI与Unity3d物体交叉显示的一种解决方案

 

C: Render Queue ,一个materialshader都有的属性,一个int值,意思是渲染队列,一般从3000开始,如果直接修改materialrender queue,就会完全覆盖shader上的该属性。在之前的Widget遍历中,每次新生成UIDrawCall,就会把这个UIDrawCall对应的materialrender queue加上1,所以不同UIDrawCall之间的排序靠的就是这个,越晚生成的UIDrawCallrender queue越大,也就越靠前,这个前置效果也和空间无关NGUI与Unity3d物体交叉显示的一种解决方案

我们可以在UIPanelRender Q属性中修改这个值

D: 顶点缓存序列的先后 ,取决与每个组件的Depth属性值,

NGUI与Unity3d物体交叉显示的一种解决方案

UIGeometry里传递的顶点(vertex)序列,这是一组根据Widget上的视觉组件生成的vertex,这些vertex传入UIDrawCall之后,会计算出三角面,生成mesh。根据生成的三角面的顺序,也就是这些vertex传入的先后,NGUI的材质球会自绘制一种先后关系。后生成的面视觉上总是能在先生成的面前面,这种先后关系,在之前Widget遍历的时候就已经决定了,Widget深度越小,就会先被传递缓存,那么他提供的vertex就会排在生成列表的前面

E空间深度 
   在摄像机坐标系下的Z轴,控制着该相机下的物体的深度,在fragment shader中进行深度测试,这样就控制了渲染到屏幕的顺序。

 

2:Unity3D对象的渲染顺序

   默认情况下,Unity会基于对象距离摄像机的远近来排序你的对象。因此,当一个对象离摄像机越近,它就会优先绘制在其他更远的对象上面。对于大多数情况这是有效并合适的,但是在一些特殊情况下,你可能想要自己控制对象的绘制顺序。

       Unity提供给我们一些默认的渲染队列,每一个对应一个唯一的值,来指导Unity绘制对象到屏幕上。这些内置的渲染队列被称为Background, Geometry, AlphaTest, Transparent, Qverlay。这些队列不是随便创建的,它们是为了让我们更容易地编写Shader并处理实时渲染的。下图的表格描述了这些渲染队列的用法:

NGUI与Unity3d物体交叉显示的一种解决方案

因此,一旦你知道你的对象属于哪一个渲染队列,就可以指定它的内置渲染队列标签,重写对象的深度排序。

但是有一点需要注意,就是不透明物体渲染时会进行深度检测(ZTest),深度缓存会记录距离摄像机最近的顶点,大于深度缓存的顶点会被抛弃,直接跳过渲染过程。如果顶点深度小于深度缓存中的数据,则更新深度缓存,将当前顶点的深度写入深度缓存(ZWrite)。

想在屏幕上渲染透明物体,需要在shader中将Queue设置为大于3000的值,并将ZWrite设置为Off,因为透明物体只会叠加在屏幕原有色值上而不是将其遮挡。因此透明物体的渲染结果只受其渲染顺序的影响,其渲染顺序为根据custom render queue从小到大,custom render queue相等时到摄像头距离由远及近依次渲染。对于透明物体虽然关闭了ZWrite,但ZTest依然有效,其还是会被Z值更小的不透明物体遮挡。

3:  NGUI中让Unity3d物体交叉显示

经过对NGUIunity3d物体的渲染顺序进行分析后,经明白了各个地方是可以怎么控制渲染顺序了,接下来就来解决一些项目中遇到的问题。

先从第一个简单问题说起,比如两个面板之间显示一个粒子效果,粒子特效本身是用“点精灵”渲染的,每个粒子就是一个点精灵,可以看做一个片模型,而片模型就可以通过设置Sorting Order属性来修改显示层级。sorting Order默认值为0,现有PanelAPanelB两个界面,把PanelA设置为0PanelA设置2

PanelA

粒子特效:
PanelB
2

粒子特效刚好插在AB之间,显示效果也是粒子特效穿插在AB之间,感觉很轻松的就解决了上面提到的问题,但是这样有一个严重的问题,就是当面板很多时,无法确保这个粒子效果不会穿透面板PanelC。但是你也可以通过统一控制面板的显示规则,来控制每个面板之间的层级间隔,来避免这种情况出现,在这里就不作讨论了。

 

然后我们另一种思路来解决这一问题:直接控制粒子特效的render queue值,来达到使得UI、特效按照我们希望的顺序进行渲染的目的,毕竟NGUI中也大量使用了RenderQueue来控制前后关系,比如在UIPanelLateUpdate方法中的具体实现:

NGUI与Unity3d物体交叉显示的一种解决方案

可以看到三种模式下,RenderQueue具体值加的方式,一般我们都是用Automatic模式,这种模式下是根据每个UIPanel中生成的DrawCall自动计算RenderQueue的值,而在一个面板中,可能存在一到多个DrawCall,在为每个归属于不同层次的widget指定了所属的render queue顺序之后,剩下的就是为特效指定应归属的render queue我们项目中目前的实现方式也是按照这种方式来实现的,下面来看下具体实现过程:NGUI与Unity3d物体交叉显示的一种解决方案

根据实际遇到的问题,一个特效有可能现在在整个面板的前或者后面、或者具体某一个组件的前面或者后面,这样我们引入UIEffectRenderQueue.cs脚本,来指定当前特效展示的方式:

    //指定一个作为targetwidget

    publicUIWidget mRenderQueueWidgetTarget = null;

    //指定一个作为targetPanel

    publicUIPanel mRenderQueuePanelTarget = null;

    //target的当前RenderQueue,如果在target的前面或者后面显示,该值会被修改

    publicint m_targetRenderQueue = -1;

    //是否要翻转Z

    publicbool m_reverseZOrder = false;

    //是否立即生效

    publicbool m_immeApply = false;

    //显示顺序

    publicRenderType m_type = RenderType.FRONT;

显示顺序的定义:

    publicenumRenderType

    {

        FRONT,//显示在目标组件的前面

        BACK, //显示在目标组件的后面

        EQUAL, //显示在目标组件的同级

     }

 

脚本生效后的核心方法如下:

privatevoid Apply()

    {

        float currZOrder = 0f;

        int currAttachType = -1;

        int queue = GetDestRenderQueue();

        if (queue <= 0)

            return;

        if (m_lastRenderQueue != queue)

        {

            Renderer renderer;

            OrderMaterial mat;

            int sortingOrder = GetDestSortingOrder();

            for (int i = 0; i < materials.Count; ++i)

            {

                mat = materials[i];

                renderer = mat.render;

                if (renderer)

                {

                    if (renderer.sortingOrder != sortingOrder)

                        renderer.sortingOrder = sortingOrder;

                }

                materials[i] = SetZOrderOrderMaterial(ref mat);

            }

            SortZOrder();

 

            queue = GetDestRenderQueue();

            m_lastRenderQueue = queue;

 

            if (m_type == RenderType.FRONT)

            {

                for (int i = 0; i < materials.Count; ++i)

                {

                    mat = materials[i];

                    if (currAttachType != materials[i].attachType || currZOrder != materials[i].zOrder)

                    {

                        queue += 1;

                        currZOrder = materials[i].zOrder;

                        currAttachType = materials[i].attachType;

                    }

                    materials[i] = ApplyOrderMaterial(ref mat, queue);

                }

            }

            elseif (m_type == RenderType.BACK)

            {

                for (int i = materials.Count - 1; i >= 0; --i)

                {

                    mat = materials[i];

                    if (currAttachType != materials[i].attachType || currZOrder != materials[i].zOrder)

                    {

                        queue -= 1;

                        currZOrder = materials[i].zOrder;

                        currAttachType = materials[i].attachType;

                    }

                    materials[i] = ApplyOrderMaterial(ref mat, queue);

                }

            }

           

        }

}

获取目标TargetRenderQueue的具体实现如下:

privateint GetDestRenderQueue()

{

        int queue = m_targetRenderQueue;

        if (m_type == RenderType.FRONT || m_type == RenderType.BACK)

        {

            if (mRenderQueuePanelTarget != null)

            {

                if (m_type == RenderType.FRONT)

                {

                    queue = mRenderQueuePanelTarget.startingRenderQueue +

                        mRenderQueuePanelTarget.drawCalls.Count +

                        mRenderQueuePanelTarget.mAdditionalDrawCallCounts;

                }

                elseif (m_type == RenderType.BACK)

                {

                    queue = mRenderQueuePanelTarget.startingRenderQueue;

                }

            }

            if (mRenderQueueWidgetTarget != null)

            {

                if (mRenderQueueWidgetTarget.drawCall != null)

                {

                    queue = mRenderQueueWidgetTarget.drawCall.renderQueue;

                }

                else

                {

                    queue = 2000;

                }

            }

            if (queue>0)

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

        }

        return queue;

}

获取目标TargetSortingOrder的具体实现如下:

    privateint GetDestSortingOrder()

    {

        int sortingOrder = 0;

        if (mRenderQueuePanelTarget != null)

        {

            sortingOrder = mRenderQueuePanelTarget.sortingOrder;

        }

        if (mRenderQueueWidgetTarget != null && mRenderQueueWidgetTarget.drawCall != null)

        {

            sortingOrder = mRenderQueueWidgetTarget.drawCall.sortingOrder;

        }

        return sortingOrder;

}


   原理上就是直接修改这一特效下所有renderer组建中的materialrenderQueue值,来按照需要指定该特效需要显示在哪一个层级。在具体的实现中,有一个小的细节,就是在修改renderQueue的同时,也修改了sorting order,是因为UIPaneldepth控制着UIDrawCall的生成顺序,影响了RenderQueue的顺序,而sorting orderRenderQueue优先级更高,所以为了渲染效果的准确性,在设置renderQueue的同时,也需要把sorting order设置为Targetorder值。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值