自学内容网 自学内容网

UGUI优化篇--UGUI合批

UGUI合批规则概述

  1. 遍历所有UI元素

  2. 根据深度Depth(优先级最高,-1表示不用渲染)、材质ID、图片ID、渲染顺序对所有UI进行排序

  3. 进行合批处理,比如UI1和UI2(两者必须是紧挨着的)是相同材质、相同图片就可以进行合批,如果UI1和UI2之间有一个中间层UI3(根UI1和UI2材质不同),就会打断合批
    在这里插入图片描述

  • Text必须文字的网格覆盖到image上面才算相交,Rect Transform框框相交不算
  • 白色image的深度为0,最先渲染,然后判断Text是否相交,再判断材质ID和图片ID是否相同,如果相同则可以合批,因为不相同,所以Text深度为白色image+1为1(如果Text底下有很多image,则取最大的深度+1)
  • 红色image因为底下有Text和白色image,所以计算出两个深度值分别1和0,取最大值加1,就是2
  • 黄色和蓝色image会进行合批测试操作,深度值都是2(这里只是测试是否能合批,并还没有真正合批)
  • 最后会得到一个排序数组list并把所有深度为-1的值剔除掉,0、1、2、2、2,传给合批部分的程序进行合批操作,判断相邻的元素是否能进行合批,通过判断数组的值是否相等

UGUI性能查看工具

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

合批部分的特殊例子

一个白色image、蓝色image覆盖了Text,白色image和Text哪个先渲染

在这里插入图片描述

  1. 根据分析工具可以看到白色image先渲染,文本后渲染(白色图片ID小一点)
  2. 这样得到的数组list,会先是白色image、Text、蓝色image,导致白色和蓝色无法合批
  3. 解决办法是把白色image和蓝色image赋值同一个texture
    在这里插入图片描述
  4. 只要是满足合批条件,合批数组紧挨着,虽然没有覆盖,也可以合批
  5. 能不能合批,在最后合批数组上才能决定

Mask合批

  1. mask无法跟mask外的物体进行合批,但是mask之间可以合批

  2. mask本身会产生两个drawcall,性能损耗

Mask为什么会产生两个drawcall

  1. 第一个drawcall是mask在设置模板缓存产生的
  2. 第二个drawcall是mask在还原模板产生的
  3. mask之间可以进行合批,设置模板缓存和还原模板时候,因为图片ID和材质是一样的,所以可以合批
  4. 设置模板缓存时候,会把需要显示部分的缓存值设置为1,遮罩的部分设置为0,在渲染的时候会取出我存在当前像素当中的一个模板缓存值,然后判断它呢是否为一。如果为一的话,才进行渲染,如果不为一,就不不渲染了
  5. 每一个像素点上,创建了一个这样类似于一个数据缓存,通过它的模板缓存值来判断是否显示,都是像素级别的单位
  6. 模板还原也要产生一次drawcall,把每个像素点的模板缓存清除

Mask为什么不能合批

因为Mask会产生一个特殊的材质,材质不同就不能合批
在这里插入图片描述

    public virtual Material GetModifiedMaterial(Material baseMaterial)
    {
        if (!MaskEnabled())
            return baseMaterial;

        var rootSortCanvas = MaskUtilities.FindRootSortOverrideCanvas(transform);
        var stencilDepth = MaskUtilities.GetStencilDepth(transform, rootSortCanvas);
        if (stencilDepth >= 8)
        {
            Debug.LogWarning("Attempting to use a stencil mask with depth > 8", gameObject);
            return baseMaterial;
        }

        int desiredStencilBit = 1 << stencilDepth;

        // if we are at the first level...
        // we want to destroy what is there
        if (desiredStencilBit == 1)
        {
            // StencilMaterial.Add是最主要的方法,这里添加了特殊的材质,因为材质不同,所以不能和mask外的物体合批
            var maskMaterial = StencilMaterial.Add(baseMaterial, 1, StencilOp.Replace, CompareFunction.Always, m_ShowMaskGraphic ? ColorWriteMask.All : 0);
            StencilMaterial.Remove(m_MaskMaterial);
            m_MaskMaterial = maskMaterial;

            var unmaskMaterial = StencilMaterial.Add(baseMaterial, 1, StencilOp.Zero, CompareFunction.Always, 0);
            StencilMaterial.Remove(m_UnmaskMaterial);
            m_UnmaskMaterial = unmaskMaterial;
            graphic.canvasRenderer.popMaterialCount = 1;
            graphic.canvasRenderer.SetPopMaterial(m_UnmaskMaterial, 0);

            return m_MaskMaterial;
        }

        //otherwise we need to be a bit smarter and set some read / write masks
        var maskMaterial2 = StencilMaterial.Add(baseMaterial, desiredStencilBit | (desiredStencilBit - 1), StencilOp.Replace, CompareFunction.Equal, m_ShowMaskGraphic ? ColorWriteMask.All : 0, desiredStencilBit - 1, desiredStencilBit | (desiredStencilBit - 1));
        StencilMaterial.Remove(m_MaskMaterial);
        m_MaskMaterial = maskMaterial2;

        graphic.canvasRenderer.hasPopInstruction = true;
        var unmaskMaterial2 = StencilMaterial.Add(baseMaterial, desiredStencilBit - 1, StencilOp.Replace, CompareFunction.Equal, 0, desiredStencilBit - 1, desiredStencilBit | (desiredStencilBit - 1));
        StencilMaterial.Remove(m_UnmaskMaterial);
        m_UnmaskMaterial = unmaskMaterial2;
        graphic.canvasRenderer.popMaterialCount = 1;
        graphic.canvasRenderer.SetPopMaterial(m_UnmaskMaterial, 0);

        return m_MaskMaterial;
    }

Mask注意要点

  1. Mask剔除的部分还是会影响深度计算的,从而影响合批,增加drawcall次数
  2. Mask剔除的部分还是会drawcall,只不过mask把绘制的像素剔除了
  3. Mask下的子物体可以正常进行合批
  4. mask之间只要满足合批条件,那么他们之间的元素也是能够进行合批的
    在这里插入图片描述

RectMask2D

  1. RectMask2D本身不占用drawcall
  2. 因为实际的具体逻辑是在canvas render里进行的,涉及到渲染具体的操作都是内部用c++类做的,性能更好,这里通过性能分析渲染的面和点判断遮盖的部分是否被渲染出来
    在这里插入图片描述

为什么RecMask2D比Mask性能更好

  1. RectMask2D本身不占用drawcall,Mask本身有两次drawcall
  2. RectMask2D对于被遮罩的部分并不会绘制,Mask是把遮罩的部分剔除了

主要代码

public virtual void PerformClipping()
{
    if (ReferenceEquals(Canvas, null))
    {
        return;
    }

    //TODO See if an IsActive() test would work well here or whether it might cause unexpected side effects (re case 776771)

    // if the parents are changed
    // or something similar we
    // do a recalculate here
    if (m_ShouldRecalculateClipRects)
    {
        MaskUtilities.GetRectMasksForClip(this, m_Clippers);
        m_ShouldRecalculateClipRects = false;
    }

    // get the compound rects from
    // the clippers that are valid
    bool validRect = true;
    // 获取需要剪切的区域
    Rect clipRect = Clipping.FindCullAndClipWorldRect(m_Clippers, out validRect);

    // If the mask is in ScreenSpaceOverlay/Camera render mode, its content is only rendered when its rect
    // overlaps that of the root canvas.
    RenderMode renderMode = Canvas.rootCanvas.renderMode;
    bool maskIsCulled =
        (renderMode == RenderMode.ScreenSpaceCamera || renderMode == RenderMode.ScreenSpaceOverlay) &&
        !clipRect.Overlaps(rootCanvasRect, true);

    if (maskIsCulled)
    {
        // Children are only displayed when inside the mask. If the mask is culled, then the children
        // inside the mask are also culled. In that situation, we pass an invalid rect to allow callees
        // to avoid some processing.
        clipRect = Rect.zero;
        validRect = false;
    }

    if (clipRect != m_LastClipRectCanvasSpace)
    {
        foreach (IClippable clipTarget in m_ClipTargets)
        {
            //设置剪切效果
            clipTarget.SetClipRect(clipRect, validRect);
        }

        foreach (MaskableGraphic maskableTarget in m_MaskableTargets)
        {
            maskableTarget.SetClipRect(clipRect, validRect);
            maskableTarget.Cull(clipRect, validRect);
        }
    }
    else if (m_ForceClip)
    {
        foreach (IClippable clipTarget in m_ClipTargets)
        {
            clipTarget.SetClipRect(clipRect, validRect);
        }

        foreach (MaskableGraphic maskableTarget in m_MaskableTargets)
        {
            maskableTarget.SetClipRect(clipRect, validRect);

            if (maskableTarget.canvasRenderer.hasMoved)
                maskableTarget.Cull(clipRect, validRect);
        }
    }
    else
    {
        foreach (MaskableGraphic maskableTarget in m_MaskableTargets)
        {
            //Case 1170399 - hasMoved is not a valid check when animating on pivot of the object
            maskableTarget.Cull(clipRect, validRect);
        }
    }

    m_LastClipRectCanvasSpace = clipRect;
    m_ForceClip = false;

    UpdateClipSoftness();
}

RectMask2D注意要点

  1. 遮罩的部分因为没有绘制,所以不影响深度计算,不影响合批
  2. 会打断合批,一个RectMask2D下的子物体不可以跟另一个RectMask2D下的子物体进行合批,但RectMask2D下的子物体可以进行合批
  3. RectMask2D组件的Image之间可以进行合批

根据应用场景选择使用哪个(Mask、RectMask2D)

只需要一个遮罩

选择用RectMask2D性能更好

需要多个遮罩,遮罩下有多个子物体

选择用Mask可以对不同Mask之间的子物体进行合批,这种情况就会比RectMask2D性能更好


原文地址:https://blog.csdn.net/2301_76618920/article/details/140635709

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!