美文网首页
NGUI渲染流程

NGUI渲染流程

作者: 白桦叶 | 来源:发表于2019-10-17 20:11 被阅读0次

    0. 概述

    本文将从整体类图出发,先对NGUI渲染涉及到几个重点的类的关系有一个整体的了解,接着再讲下各个类的作用,然后通过源码将下整个渲染的流程,最后尝试解答几个问题。本文使用的NGUI版本是3.8.2。

    1. 整体类图

    NGUI_Class.png

    我们从图中可以看到涉及到NGUI渲染流程的类主要有UIRect、UIWidget、UIPanel、UIDrawcall和UIGeometry。

    2. 各个类的作用

    2.1 UIRect

    UIRect作为UIWidget和UIPanel的抽象基类,主要作用是维护4个锚点(左/上/右/下),并根据锚定更新类型在适当的时候更新4个锚点,并提供OnAnchor抽象方法让子类根据锚点更新自己的尺寸。

    2.2 UIGeometry

    • UIGeometry由UIWidget生成,UIWidget都有一个UIGeometry
    • UIGeometry包含Widget的顶点verts,几何顶点的纹理坐标uvs,几何顶点的颜色cols,并提供接口将顶点数据转换为所属Panel局部坐标的顶点mRtpVerts,法线mRtpNormal和切线数据mRtpTan。
    • UIGeometry将几何数据生成分开为3个步骤,保证widget只有在自己发生变化的时候重build
      • 1.清空缓存-对应方法Clear
        1. 根据世界坐标到panel本地坐标的矩阵将转换Widget的顶点数据- 对应方法ApplyTransform
        1. 向特定传入的buffer填入转换后的顶点等数据-对应方法WriteToBuffers

    2.3 UIWidget

    • UIWidget是每个UI组件如我们常见的UITexture、UISprite、UILabel的基类,每个UI元素对应一个UIWidget。
    • UIWidget包含UI元素信息如width、height、depth、pivot、alpha、color,并提供几个抽象属性material、mainTexture和shader由子类
      去实现。
    • UIWidget都有一个UIGeometry,并提供OnFill抽象方法,由子类如UILabel和UISprite将顶点、UV、Color、法线和切线数据填入UIGeometry缓存中。
    • UIWidget都有一个UIDrawcall,但一个UIDrawcall对应1个或者多个的UIWidget,UI元素的渲染最终是通过UIDrawcall实现的。

    2.4 UIDrawCall

    • UIDrawcall是渲染UI元素的载体,由UIPanel生成,其中包含渲染使用的MeshRender、Mesh、MeshFilter和Material组件。
    • 其核心函数是UpdateGeometry,其分为以下几个步骤进行几何信息更新:1. 生成Mesh,2.赋值缓存的顶点、UV、颜色、法线和切线等数据给Mesh,3.更新MeshRender使用的材质

    2.5 UIPanel

    • UIPanel作为UIWidget列表和UIDrawcall列表的管理者,监测UIPanel下的Widget的变化,一旦Widget发生变化就需要更新widget对应的drawcall。在必要的时候需要重新生成全部的drawcall。
    • UIPanel的UIWidget列表是按照widget的深度排序的
    • UIPanel维护一个静态的panel列表,是按照panel的深度排序的

    3. 渲染流程

    [图片上传中...(NGUI_UIPanel.png-f91a26-1571404641111-0)]

    3.1 UIPanel的LateUpdate

    先遍历根据深度进行排序的静态的Panel列表,并调用UIPanel的UpdateSelf函数更新每个Panel.
    再遍历Panel列表根据Panel的RenderQueue类型不同(Automatic/StartAt/Explicit),设置相应的RenderQueue数据并调用Panel的UpdateDrawCalls更新Drawcall。

    void LateUpdate ()
        {
    #if UNITY_EDITOR
            if (mUpdateFrame != Time.frameCount || !Application.isPlaying)
    #else
            if (mUpdateFrame != Time.frameCount)
    #endif
            {
                mUpdateFrame = Time.frameCount;
    
                // Update each panel in order
                for (int i = 0, imax = list.Count; i < imax; ++i)
                    list[i].UpdateSelf();
    
                int rq = 3000;
    
                // Update all draw calls, making them draw in the right order
                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);
                    }
                }
            }
        }
    

    3.2 UIPanel的UpdateSelf

    3.2.1 流程

    • UpdateTransformMatrix
      更新世界坐标到Panel本地坐标的矩阵和裁剪区域
    • UpdateLayers
      更新widget layer,保持widget的layer与panel一致
    • UpdateWidgets
      更新属于Panel下的所有widget
      • 判断是否重Build
        • 是 调用UIPanel的FillAllDrawCalls重新生成Drawcall
        • 否 遍历Drawcall列表如果Drawcall.isDirty就调用FillDrawCall更新对应Drawcall

    3.2.2 代码

    void UpdateSelf ()
        {
            mUpdateTime = RealTime.time;
    
            UpdateTransformMatrix();
            UpdateLayers();
            UpdateWidgets();
    
            if (mRebuild)
            {
                mRebuild = false;
                FillAllDrawCalls();
            }
            else
            {
                for (int i = 0; i < drawCalls.Count; )
                {
                    UIDrawCall dc = drawCalls[i];
    
                    if (dc.isDirty && !FillDrawCall(dc))
                    {
                        UIDrawCall.Destroy(dc);
                        drawCalls.RemoveAt(i);
                        continue;
                    }
                    ++i;
                }
            }
    
            if (mUpdateScroll)
            {
                mUpdateScroll = false;
                UIScrollView sv = GetComponent<UIScrollView>();
                if (sv != null) sv.UpdateScrollbars();
            }
        }
    

    3.3 UIPanel的UpdateWidgets

    3.3.1 流程

    • 遍历调用Widget列表调用UIWidget的UpdateTransform判断widget的位置是否发生变化
    • 调用widget的UpdateVisibility函数更新其可见性
    • 调用widget的UpdateGeometry函数更新其几何数据,返回widget的几何数据是否发生改变
      如果几何信息发生改变
      • drawcall存在,设置drawcall dirty
      • drawcall没有则调用FindDrawCall寻找widget对应的drawcall

    3.3.2 代码

    void UpdateWidgets()
        {
    #if UNITY_EDITOR
            bool forceVisible = cullWhileDragging ? false : (Application.isPlaying && mCullTime > mUpdateTime);
    #else
            bool forceVisible = cullWhileDragging ? false : (mCullTime > mUpdateTime);
    #endif
            bool changed = false;
    
            if (mForced != forceVisible)
            {
                mForced = forceVisible;
                mResized = true;
            }
    
            bool clipped = hasCumulativeClipping;
    
            // Update all widgets
            for (int i = 0, imax = widgets.Count; i < imax; ++i)
            {
                UIWidget w = widgets[i];
    
                // If the widget is visible, update it
                if (w.panel == this && w.enabled)
                {
    #if UNITY_EDITOR
                    // When an object is dragged from Project view to Scene view, its Z is...
                    // odd, to say the least. Force it if possible.
                    if (!Application.isPlaying)
                    {
                        Transform t = w.cachedTransform;
    
                        if (t.hideFlags != HideFlags.HideInHierarchy)
                        {
                            t = (t.parent != null && t.parent.hideFlags == HideFlags.HideInHierarchy) ?
                                t.parent : null;
                        }
    
                        if (t != null)
                        {
                            for (; ; )
                            {
                                if (t.parent == null) break;
                                if (t.parent.hideFlags == HideFlags.HideInHierarchy) t = t.parent;
                                else break;
                            }
    
                            if (t != null)
                            {
                                Vector3 pos = t.localPosition;
                                pos.x = Mathf.Round(pos.x);
                                pos.y = Mathf.Round(pos.y);
                                pos.z = 0f;
    
                                if (Vector3.SqrMagnitude(t.localPosition - pos) > 0.0001f)
                                    t.localPosition = pos;
                            }
                        }
                    }
    #endif
                    int frame = Time.frameCount;
    
                    // First update the widget's transform
                    if (w.UpdateTransform(frame) || mResized)
                    {
                        // Only proceed to checking the widget's visibility if it actually moved
                        bool vis = forceVisible || (w.CalculateCumulativeAlpha(frame) > 0.001f);
                        w.UpdateVisibility(vis, forceVisible || ((clipped || w.hideIfOffScreen) ? IsVisible(w) : true));
                    }
                    
                    // Update the widget's geometry if necessary
                    if (w.UpdateGeometry(frame))
                    {
                        changed = true;
    
                        if (!mRebuild)
                        {
                            if (w.drawCall != null)
                            {
                                w.drawCall.isDirty = true;
                            }
                            else
                            {
                                // Find an existing draw call, if possible
                                FindDrawCall(w);
                            }
                        }
                    }
                }
            }
    
            // Inform the changed event listeners
            if (changed && onGeometryUpdated != null) onGeometryUpdated();
            mResized = false;
        }
    

    3.4 UIWidget的UpdateGeometry

    3.4.1 流程

    • 当widget信息改变的时候,调用UIGeometry.Clear清除缓存,并传入UIGeometry的缓存数据到OnFill函数填入顶点,UV和颜色数据,UIGeometry.ApplyTransform传入widget本地坐标到panel本地坐标的矩阵进行数据转化
    • 如果只是widget位置变化,只需要调用UIGeometry.ApplyTransform更新其转化数据

    3.4.2 代码

    public bool UpdateGeometry (int frame)
        {
            // Has the alpha changed?
            float finalAlpha = CalculateFinalAlpha(frame);
            if (mIsVisibleByAlpha && mLastAlpha != finalAlpha) mChanged = true;
            mLastAlpha = finalAlpha;
    
            if (mChanged)
            {
                mChanged = false;
    
                if (mIsVisibleByAlpha && finalAlpha > 0.001f && shader != null)
                {
                    bool hadVertices = geometry.hasVertices;
    
                    if (fillGeometry)
                    {
                        geometry.Clear();
                        OnFill(geometry.verts, geometry.uvs, geometry.cols);
                    }
    
                    if (geometry.hasVertices)
                    {
                        // Want to see what's being filled? Uncomment this line.
                        //Debug.Log("Fill " + name + " (" + Time.frameCount + ")");
    
                        if (mMatrixFrame != frame)
                        {
                            mLocalToPanel = panel.worldToLocal * cachedTransform.localToWorldMatrix;
                            mMatrixFrame = frame;
                        }
                        geometry.ApplyTransform(mLocalToPanel);
                        mMoved = false;
                        return true;
                    }
                    return hadVertices;
                }
                else if (geometry.hasVertices)
                {
                    if (fillGeometry) geometry.Clear();
                    mMoved = false;
                    return true;
                }
            }
            else if (mMoved && geometry.hasVertices)
            {
                if (mMatrixFrame != frame)
                {
                    mLocalToPanel = panel.worldToLocal * cachedTransform.localToWorldMatrix;
                    mMatrixFrame = frame;
                }
                geometry.ApplyTransform(mLocalToPanel);
                mMoved = false;
                return true;
            }
            mMoved = false;
            return false;
        }
    

    3.5 UIPanel的FillAllDrawCalls

    3.5.1 流程

    • 清空Drawcall列表
    • 遍历已经深度排序好的Widget列表,先创建一个Drawcall,将widget的顶点、UV、Color数据都调用widget的WriteToBuffers函数写入Drawcall的缓存,不断循环,将material,texture,shader都相同的widget数据写入同一个Drawcall里面,并更新Drawcall的初始深度、终点深度,直到遇到不满足条件的widget,则创建新的Drawcall。
    • 最终调用Drawcall的UpdateGeometry函数更新其几何信息

    3.5.2 代码

    void FillAllDrawCalls ()
        {
            for (int i = 0; i < drawCalls.Count; ++i)
                UIDrawCall.Destroy(drawCalls[i]);
            drawCalls.Clear();
    
            Material mat = null;
            Texture tex = null;
            Shader sdr = null;
            UIDrawCall dc = null;
            int count = 0;
    
            if (mSortWidgets) SortWidgets();
    
            for (int i = 0; i < widgets.Count; ++i)
            {
                UIWidget w = widgets[i];
    
                if (w.isVisible && w.hasVertices)
                {
                    Material mt = w.material;
                    Texture tx = w.mainTexture;
                    Shader sd = w.shader;
    
                    if (mat != mt || tex != tx || sdr != sd)
                    {
                        if (dc != null && dc.verts.size != 0)
                        {
                            drawCalls.Add(dc);
                            dc.UpdateGeometry(count);
                            dc.onRender = mOnRender;
                            mOnRender = null;
                            count = 0;
                            dc = null;
                        }
    
                        mat = mt;
                        tex = tx;
                        sdr = sd;
                    }
    
                    if (mat != null || sdr != null || tex != null)
                    {
                        if (dc == null)
                        {
                            dc = UIDrawCall.Create(this, mat, tex, sdr);
                            dc.depthStart = w.depth;
                            dc.depthEnd = dc.depthStart;
                            dc.panel = this;
                        }
                        else
                        {
                            int rd = w.depth;
                            if (rd < dc.depthStart) dc.depthStart = rd;
                            if (rd > dc.depthEnd) dc.depthEnd = rd;
                        }
    
                        w.drawCall = dc;
    
                        ++count;
                        if (generateNormals) w.WriteToBuffers(dc.verts, dc.uvs, dc.cols, dc.norms, dc.tans);
                        else w.WriteToBuffers(dc.verts, dc.uvs, dc.cols, null, null);
    
                        if (w.mOnRender != null)
                        {
                            if (mOnRender == null) mOnRender = w.mOnRender;
                            else mOnRender += w.mOnRender;
                        }
                    }
                }
                else w.drawCall = null;
            }
    
            if (dc != null && dc.verts.size != 0)
            {
                drawCalls.Add(dc);
                dc.UpdateGeometry(count);
                dc.onRender = mOnRender;
                mOnRender = null;
            }
        }
    

    3.6 UIPanel的FillDrawCall

    3.6.1 流程

    • 遍历widget,寻找widget的drawcall为目标drawcall
    • 调用该widget的WriteToBuffers函数将几何信息写入Drawcall的缓存
    • 最终调用Drawcall的UpdateGeometry函数更新其几何信息

    3.7.2 代码

    bool FillDrawCall (UIDrawCall dc)
        {
            if (dc != null)
            {
                dc.isDirty = false;
                int count = 0;
    
                for (int i = 0; i < widgets.Count; )
                {
                    UIWidget w = widgets[i];
    
                    if (w == null)
                    {
    #if UNITY_EDITOR
                        Debug.LogError("This should never happen");
    #endif
                        widgets.RemoveAt(i);
                        continue;
                    }
    
                    if (w.drawCall == dc)
                    {
                        if (w.isVisible && w.hasVertices)
                        {
                            ++count;
                            
                            if (generateNormals) w.WriteToBuffers(dc.verts, dc.uvs, dc.cols, dc.norms, dc.tans);
                            else w.WriteToBuffers(dc.verts, dc.uvs, dc.cols, null, null);
    
                            if (w.mOnRender != null)
                            {
                                if (mOnRender == null) mOnRender = w.mOnRender;
                                else mOnRender += w.mOnRender;
                            }
                        }
                        else w.drawCall = null;
                    }
                    ++i;
                }
    
                if (dc.verts.size != 0)
                {
                    dc.UpdateGeometry(count);
                    dc.onRender = mOnRender;
                    mOnRender = null;
                    return true;
                }
            }
            return false;
        }
    

    3.7 UIDrawcall的UpdateGeometry

    3.7.1 流程

    • 创建Mesh
    • 将drawcall缓存的顶点、UV、Color等数据赋值给Mesh
    • 更新MeshRenderer使用的材质
    • 清空drawcall缓存的顶点、UV、Color等数据

    3.7.2 代码

    public void UpdateGeometry (int widgetCount)
        {
            this.widgetCount = widgetCount;
            int count = verts.size;
    
            // Safety check to ensure we get valid values
            if (count > 0 && (count == uvs.size && count == cols.size) && (count % 4) == 0)
            {
                // Cache all components
                if (mFilter == null) mFilter = gameObject.GetComponent<MeshFilter>();
                if (mFilter == null) mFilter = gameObject.AddComponent<MeshFilter>();
    
                if (verts.size < 65000)
                {
                    // Populate the index buffer
                    int indexCount = (count >> 1) * 3;
                    bool setIndices = (mIndices == null || mIndices.Length != indexCount);
    
                    // Create the mesh
                    if (mMesh == null)
                    {
                        mMesh = new Mesh();
                        mMesh.hideFlags = HideFlags.DontSave;
                        mMesh.name = (mMaterial != null) ? "[NGUI] " + mMaterial.name : "[NGUI] Mesh";
                        mMesh.MarkDynamic();
                        setIndices = true;
                    }
    #if !UNITY_FLASH
                    // If the buffer length doesn't match, we need to trim all buffers
                    bool trim = (uvs.buffer.Length != verts.buffer.Length) ||
                        (cols.buffer.Length != verts.buffer.Length) ||
                        (norms.buffer != null && norms.buffer.Length != verts.buffer.Length) ||
                        (tans.buffer != null && tans.buffer.Length != verts.buffer.Length);
    
                    // Non-automatic render queues rely on Z position, so it's a good idea to trim everything
                    if (!trim && panel.renderQueue != UIPanel.RenderQueue.Automatic)
                        trim = (mMesh == null || mMesh.vertexCount != verts.buffer.Length);
    
                    // NOTE: Apparently there is a bug with Adreno devices:
                    // http://www.tasharen.com/forum/index.php?topic=8415.0
    #if !UNITY_ANDROID
                    // If the number of vertices in the buffer is less than half of the full buffer, trim it
                    if (!trim && (verts.size << 1) < verts.buffer.Length) trim = true;
    #endif
                    mTriangles = (verts.size >> 1);
    
                    if (trim || verts.buffer.Length > 65000)
                    {
                        if (trim || mMesh.vertexCount != verts.size)
                        {
                            mMesh.Clear();
                            setIndices = true;
                        }
    
                        mMesh.vertices = verts.ToArray();
                        mMesh.uv = uvs.ToArray();
                        mMesh.colors32 = cols.ToArray();
    
                        if (norms != null) mMesh.normals = norms.ToArray();
                        if (tans != null) mMesh.tangents = tans.ToArray();
                    }
                    else
                    {
                        if (mMesh.vertexCount != verts.buffer.Length)
                        {
                            mMesh.Clear();
                            setIndices = true;
                        }
    
                        mMesh.vertices = verts.buffer;
                        mMesh.uv = uvs.buffer;
                        mMesh.colors32 = cols.buffer;
    
                        if (norms != null) mMesh.normals = norms.buffer;
                        if (tans != null) mMesh.tangents = tans.buffer;
                    }
    #else
                    mTriangles = (verts.size >> 1);
    
                    if (mMesh.vertexCount != verts.size)
                    {
                        mMesh.Clear();
                        setIndices = true;
                    }
    
                    mMesh.vertices = verts.ToArray();
                    mMesh.uv = uvs.ToArray();
                    mMesh.colors32 = cols.ToArray();
    
                    if (norms != null) mMesh.normals = norms.ToArray();
                    if (tans != null) mMesh.tangents = tans.ToArray();
    #endif
                    if (setIndices)
                    {
                        mIndices = GenerateCachedIndexBuffer(count, indexCount);
                        mMesh.triangles = mIndices;
                    }
    
    #if !UNITY_FLASH
                    if (trim || !alwaysOnScreen)
    #endif
                        mMesh.RecalculateBounds();
    
                    mFilter.mesh = mMesh;
                }
                else
                {
                    mTriangles = 0;
                    if (mFilter.mesh != null) mFilter.mesh.Clear();
                    Debug.LogError("Too many vertices on one panel: " + verts.size);
                }
    
                if (mRenderer == null) mRenderer = gameObject.GetComponent<MeshRenderer>();
    
                if (mRenderer == null)
                {
                    mRenderer = gameObject.AddComponent<MeshRenderer>();
    #if UNITY_EDITOR
                    mRenderer.enabled = isActive;
    #endif
                }
                UpdateMaterials();
            }
            else
            {
                if (mFilter.mesh != null) mFilter.mesh.Clear();
                Debug.LogError("UIWidgets must fill the buffer with 4 vertices per quad. Found " + count);
            }
    
            verts.Clear();
            uvs.Clear();
            cols.Clear();
            norms.Clear();
            tans.Clear();
        }
    

    3.8 UIPanel的UpdateDrawCalls

    3.8.1 作用

    更新Panel的裁剪区域和Drawcall的位置,旋转角度,sortingorder,renderQueue等信息。

    3.8.2 代码

    void UpdateDrawCalls ()
        {
            Transform trans = cachedTransform;
            bool isUI = usedForUI;
    
            if (clipping != UIDrawCall.Clipping.None)
            {
                drawCallClipRange = finalClipRegion;
                drawCallClipRange.z *= 0.5f;
                drawCallClipRange.w *= 0.5f;
            }
            else drawCallClipRange = Vector4.zero;
    
            int w = Screen.width;
            int h = Screen.height;
    
            // Legacy functionality
            if (drawCallClipRange.z == 0f) drawCallClipRange.z = w * 0.5f;
            if (drawCallClipRange.w == 0f) drawCallClipRange.w = h * 0.5f;
    
            // DirectX 9 half-pixel offset
            if (halfPixelOffset)
            {
                drawCallClipRange.x -= 0.5f;
                drawCallClipRange.y += 0.5f;
            }
    
            Vector3 pos;
    
            if (isUI)
            {
                Transform parent = cachedTransform.parent;
                pos = cachedTransform.localPosition;
    
                if (clipping != UIDrawCall.Clipping.None)
                {
                    pos.x = Mathf.RoundToInt(pos.x);
                    pos.y = Mathf.RoundToInt(pos.y);
                }
    
                if (parent != null) pos = parent.TransformPoint(pos);
                pos += drawCallOffset;
            }
            else pos = trans.position;
    
            Quaternion rot = trans.rotation;
            Vector3 scale = trans.lossyScale;
    
            for (int i = 0; i < drawCalls.Count; ++i)
            {
                UIDrawCall dc = drawCalls[i];
    
                Transform t = dc.cachedTransform;
                t.position = pos;
                t.rotation = rot;
                t.localScale = scale;
    
                dc.renderQueue = (renderQueue == RenderQueue.Explicit) ? startingRenderQueue : startingRenderQueue + i;
                dc.alwaysOnScreen = alwaysOnScreen &&
                    (mClipping == UIDrawCall.Clipping.None || mClipping == UIDrawCall.Clipping.ConstrainButDontClip);
                dc.sortingOrder = mSortingOrder;
                dc.clipTexture = mClipTexture;
            }
        }
    

    4. 解答问题

    4.1 UIWidget的depth如何生效

    1. 我们知道在同一个Panel不同的UIWidget可以通过设置深度depth值,depth越大越显示在前面
    2. UIPanel下widget组件列表按照在widget的depth进行排序,在FillAllDrawCalls遍历widget组件列表将material,texture,shader相同的widget的顶点,UV,Color等几何数据填入同一个Drawcall缓存中。
    3. 上面讲到UIDrawcall是渲染UI元素的载体,UIPanel生成UIDrawcall,UIDrawcall是一个组件,挂载在一个GameObject,这个GameObject上再挂载MeshRender、Mesh、MeshFilter、材质等Unity组件,通过这些组件将UI元素渲染出来。
    4. 最终UI元素渲染顺序是通过MeshRender的sortingOrder和其材质的renderQueue共同决定的。
    5. 下方为RenderQueue,Sortingorder,Sorting Layer如何共同决定物体的渲染顺序

    1.RenderQueue 2500以下
    1. Sorting Layer/Order in Layer
    1. 按照Sorting Layer/Order in Layer 设置的值,越小越优先
    2. 无此属性,等同于 Sorting Layer=default ,Order in Layer=0 参与排序
    2.RenderQueue 越小越优先
    3.RenderQueue 相等,由近到远排序优先
    2.RenderQueue 2500以上
    1. Sorting Layer/Order in Layer
    1. 按照Sorting Layer/Order in Layer 设置的值,越小越优先
    2. 无此属性,等同于 Sorting Layer=default ,Order in Layer=0 参与排序
    2.RenderQueue 越小越优先
    3.RenderQueue 相等,由远到近排序优先

    参考链接https://www.jianshu.com/p/0341f0ab9020

    1. UIPanel有sortingorder和RenderQueue类型,sortingorder用来设置对应UIPanel下每个UIDrawcall上MeshRender的sortingorder。所以同一个Panel下的UIdrawcall的MeshRender的sortingorder是一样的。而RenderQueue有3个类型,分别是
      RenderQueue.Automatic,自动。起始值3000,Panel下第一个UIDrawcall起始为之前处理Panel中最大的的rendqueue ,每一个drawcall的RenderQueue 不断+1。
      RenderQueue.StartAt 指定Panel下UIDrawcall的RenderQueue 初始值,每一个drawcall的RenderQueue 不断+1
      RenderQueue.Explicit,指定特定值,Panel下的每个drawcall都是这个值
    2. 代码

    4.2 UIPanel什么时候重新绘制

    1. 第一次LateUpdate
    2. 移除Widget的时候Widget的depth等于其Drawcall的起始或者结束深度
    3. 添加Widget的时候,Panel无法为其从现有的Drawcall列表找到复合条件的Drawcall(Widget的material、shader、texture与Drawcall相等且Widget的depth在Drawcall的深度范围内)。

    4.3 UIWidget什么时候重新绘制

    1. Widget的最终alpha发生变化
    2. Widget的锚点发生变化
    3. 当调用Widget的MarkAsChanged标记其发生变化的时候
    4. Widget位置或者大小发生变化

    4.4 项目如何减少Drawcall

    1. 由UIPanel生成Drawcall的函数FillAllDrawCalls可以知道,按照遍历已经按照深度排序的Widget列表,当碰到Widget的Shader、Texture或者Material不想等的时候,需要重新生成一个Drawcall。
    2. 了解到这机制,减少drawcall的思路就是尽可能将Shader、Texture、Material相同的Widget安排在同一个深度范围内,并且中间尽可能少的穿插与其不同的Widget。
    3. 公司项目减少Drawcall的方法
      3.1 公司项目的界面一般对应一个UIPanel(没有ScrollView或者ListView),静态UI界面主要由图片或者文字,我们定义图片或者文字的层级区间类型,其中文字最高为1000,并且我们界面都是同一个字体,一个界面的所UILabel的深度值都为1000,这样一个界面的所有文字只会产生一个Drawcall。
      3.2 另外使用图集的Widget(UISprite)和使用单一图片的Widget(UITexture)的深度值范围是区分开的,因为UISprite和UITexture一般来说使用的Shader、Texture、Material无法完全一样,通过划分深度范围能有效减少因两者穿插产生多余Drawcall。
      3.3 图片或者文字的层级区间
      • PageMask, //页面遮罩 depth:0
      • BgIcon, //背景Icon depth:100-200
      • AtlasBg, //图集-背景 depth:200-300
      • AtlasBasicComponent, //图集-基础控件 depth:300-400
      • AtalsMainScene, //图集-主界面 depth:200-500
      • DynIcon, //动态图标 depth:500-700
      • UpperAtlas, //图标或者图集上的图集 depth:700-800
      • UpperIcon, //图标之上的图标 depth:800-900
      • Label //字体为1000

    相关文章

      网友评论

          本文标题:NGUI渲染流程

          本文链接:https://www.haomeiwen.com/subject/rodkmctx.html