UGUI MaskableGraphic源码分析,RectMask2D与Mask的原理

系列

UGUI源码分析系列总览

MaskableGraphic

BaseClass: Graphic

Interface: IClippable、IMaskable、IMaterialModifier

简介

前置:Graphic源码剖析

MaskableGraphicGraphic的基础上实现了裁剪与遮罩功能

这主要是由 IClippableIMaskable 两个接口来实现的。

在这里插入图片描述

​ 在Graphic更新材质的流程中有提及Mask。Graphic 可以理解成由骨头和皮肤所组成,骨头即顶点信息所构建的网格(Mesh),皮肤则是依附于Mesh的材质和纹理。实际上Mesh是不可见的,对于可见物的处理(例如Mask遮罩剔除)都是针对于Material。

理解清楚IClippable与IMaskable相关的组件原理便是理解MaskableGraphic的关键

RectMask2D 矩形裁剪

BaseClass: UIBehaviour

Interface: IClipper、ICanvasRaycastFilter

Intro: 这是UGUI提供的不依赖于Graphic的裁剪组件,它的原理在于设置IClippable组件中canvasRenderer.EnableRectClipping 来实现矩形裁剪效果

说到 IClipper(裁剪者)与 IClippable(可裁剪对象),那必须以RectMask2D组件为例来讲解

RectMask2D的工作原理

  • RectMask2DIClipper,当启动时(Enable)先向ClipperRegistry中注册自己,然后会调用其所有子节点下IClippable 组件的RecalculateClipping方法,将其添加进最近父节点中的RectMask2D中(这是为了避免各种嵌套带来的浪费)
// MaskableGraphic 中更新裁剪者的方法
private void UpdateClipParent()
{
    var newParent = (maskable && IsActive()) ? MaskUtilities.GetRectMaskForClippable(this) : null;

    // if the new parent is different OR is now inactive
    if (m_ParentMask != null && (newParent != m_ParentMask || !newParent.IsActive()))
    {
        m_ParentMask.RemoveClippable(this);
        UpdateCull(false);
    }

    // don't re-add it if the newparent is inactive
    if (newParent != null && newParent.IsActive())
        newParent.AddClippable(this);

    m_ParentMask = newParent;
}
  • Canvas进行刷新的时候(CanvasUpdateSystem),会调用所有启用中的IClipper,执行Cull 操作,遍历执行 IClipper.PerformClipping

    ClipperRegistry.instance.Cull();

    m_Clippers[i].PerformClipping();

  • PerformClipping : 目的在于更新IClippable中用于裁剪的Rect

    首先会借助MaskUtilities、Clipping 寻找的最小的裁剪框clipRect

    接着会遍历自身下所有的IClippable组件(由IClippable.RecalculateClipping 添加)设置clipRect

    clipTarget.SetClipRect(clipRect, validRect) validRect:用于判断裁剪框是否可用(长宽>0)

    canvasRenderer.EnableRectClipping(clipRect) MaskableGraphic 中设置裁剪框

    最后会判断是否改变IClippable中cull的状态

    canvasRenderer.cull = cull;

    在此流程期间RectMask2D会优化处理过程:

    ​ 1.记录上次的clipRect来判断裁剪矩形是否发生变化,从而省略没必要的重新裁剪。

    m_LastClipRectCanvasSpace = clipRect;

    ​ 2.裁剪层的子集合会因为父级的裁剪而被裁剪,因此可以传递无效的rect来避免重复的处理。

    clipTarget.Cull(maskIsCulled ? Rect.zero : clipRect,maskIsCulled ? false : validRect)

IMaskable基于Material的遮罩

MaskableGraphic 中IMaskable实现:

  • Enable时,若该物体自身含有Mask组件则会调用其子节点路径下所有IMaskable组件方法
protected override void OnEnable()
{
    base.OnEnable();
    m_ShouldRecalculateStencil = true; //控制是否从新计算遮罩深度->改变遮罩材质
    UpdateClipParent();
    SetMaterialDirty();

    if (GetComponent<Mask>() != null)
    {
        // 设置Mask遮罩状态
        MaskUtilities.NotifyStencilStateChanged(this);
    }
}

//IMaskable 接口方法
public virtual void RecalculateMasking()
{
    m_ShouldRecalculateStencil = true;
    SetMaterialDirty();
}
  • Grahpic材质重建的过程中会调用其上所有IMaterialModifier组件方法来处理最终的渲染材质materialForRendering
//MaskableGraphic 中IMaterialModifier 组件方法
public virtual Material GetModifiedMaterial(Material baseMaterial)
{
    var toUse = baseMaterial;//来自Graphic的基础材质
    if (m_ShouldRecalculateStencil)
    {
        var rootCanvas = MaskUtilities.FindRootSortOverrideCanvas(transform);
        m_StencilValue = maskable ? MaskUtilities.GetStencilDepth(transform, rootCanvas) : 0;
        m_ShouldRecalculateStencil = false;
    }
    // 优化了遮罩处理,如果已经启用了Mask组件,则不必再次做重复的事情
    Mask maskComponent = GetComponent<Mask>();
    if (m_StencilValue > 0 && (maskComponent == null || !maskComponent.IsActive()))
    {
        //借助StencilMaterial生产一个新的遮罩材质,这里是使用list存储避免重复生成一样的材质
        var maskMat = StencilMaterial.Add(toUse, (1 << m_StencilValue) - 1, StencilOp.Keep, CompareFunction.Equal, ColorWriteMask.All, (1 << m_StencilValue) - 1, 0);
        StencilMaterial.Remove(m_MaskMaterial);
        m_MaskMaterial = maskMat;
        toUse = m_MaskMaterial;
    }
    return toUse;//返回新生成的遮罩材质
}

原创文章 28 获赞 30 访问量 2419

猜你喜欢

转载自blog.csdn.net/qq_28820675/article/details/105849111
今日推荐