UGUI源码阅读(五)遮罩之Mask

Mask使用了Gpu的模板测试功能,具体参阅 模板测试Unity官方文档

模板测试公式为:

(ref & readMask) comparisonFunction (stencilBufferValue & readMask)

对应Shader渲染设置:

Stencil
{
    Ref [_Stencil]
    Comp [_StencilComp]
    Pass [_StencilOp]
    ReadMask [_StencilReadMask]
    WriteMask [_StencilWriteMask]
}

为实现遮罩效果,需要Image组件 + Mask组件;

Mask组件主要用来修改同gameObject上Image组件使用的材质球,使Image组件向模板缓冲写入缓冲值:

/// Stencil calculation time!
/// 用作Mask的Graphic,修改其材质球,支持写入模板测试值
public virtual Material GetModifiedMaterial(Material baseMaterial)
{
    if (!MaskEnabled())
        return baseMaterial;

    var rootSortCanvas = MaskUtilities.FindRootSortOverrideCanvas(transform);
    //stencilDepth表示父节点有多少个Mask,不包括自身
    var stencilDepth = MaskUtilities.GetStencilDepth(transform, rootSortCanvas);
    if (stencilDepth >= 8)
    {
        Debug.LogWarning("Attempting to use a stencil mask with depth > 8", gameObject);
        return baseMaterial;
    }

    //每个mask对应一个bit位
    int desiredStencilBit = 1 << stencilDepth;

    // if we are at the first level...
    // we want to destroy what is there
    if (desiredStencilBit == 1)
    {
        //渲染当前Graphic,模板值1,替换模板值,模板测试通过
        var maskMaterial = StencilMaterial.Add(baseMaterial, 1, StencilOp.Replace, CompareFunction.Always, m_ShowMaskGraphic ? ColorWriteMask.All : 0);
        StencilMaterial.Remove(m_MaskMaterial);
        m_MaskMaterial = maskMaterial;
        //Mask子节点的元素渲染完后,清空模板值,渲染时不写入颜色
        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
    //当前渲染的Graphic,受父节点Mask影响时
    //模板值 = 当前bit位对应值 + 所有低位bit对应的值
    //测试通过直接替换
    //比较函数为Equal,读取掩码为“所有低位对应的值”,以实现父节点Mask功能
    //写入掩码为当前模板值
    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;
    //Mask里面的元素渲染完后,复位到先前的模板值,不渲染颜色
    //模板值 = 所有低位对应的值
    //由于要清空当前Mask对应的bit位,写入掩码为desiredStencilBit | (desiredStencilBit - 1)
    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;
}

其中 StencilMaterial 类:

public static Material Add(Material baseMat, int stencilID, StencilOp operation, CompareFunction compareFunction, ColorWriteMask colorWriteMask, int readMask, int writeMask)
{
    ...

    var listCount = m_List.Count;
    for (int i = 0; i < listCount; ++i)
    {
        MatEntry ent = m_List[i];

        if (ent.baseMat == baseMat
            && ent.stencilId == stencilID
            && ent.operation == operation
            && ent.compareFunction == compareFunction
            && ent.readMask == readMask
            && ent.writeMask == writeMask
            && ent.colorMask == colorWriteMask)
        {
            ++ent.count;
            return ent.customMat;
        }
    }

    var newEnt = new MatEntry();
    newEnt.count = 1;
    newEnt.baseMat = baseMat;
    newEnt.customMat = new Material(baseMat);
    newEnt.customMat.hideFlags = HideFlags.HideAndDontSave;
    newEnt.stencilId = stencilID;
    newEnt.operation = operation;
    newEnt.compareFunction = compareFunction;
    newEnt.readMask = readMask;
    newEnt.writeMask = writeMask;
    newEnt.colorMask = colorWriteMask;
    //为了避免透明区域写入模板值,需要AlphaClip
    newEnt.useAlphaClip = operation != StencilOp.Keep && writeMask > 0;

    newEnt.customMat.name = string.Format("Stencil Id:{0}, Op:{1}, Comp:{2}, WriteMask:{3}, ReadMask:{4}, ColorMask:{5} AlphaClip:{6} ({7})", stencilID, operation, compareFunction, writeMask, readMask, colorWriteMask, newEnt.useAlphaClip, baseMat.name);

    newEnt.customMat.SetFloat("_Stencil", (float)stencilID);
    newEnt.customMat.SetFloat("_StencilOp", (float)operation);
    newEnt.customMat.SetFloat("_StencilComp", (float)compareFunction);
    newEnt.customMat.SetFloat("_StencilReadMask", (float)readMask);
    newEnt.customMat.SetFloat("_StencilWriteMask", (float)writeMask);
    newEnt.customMat.SetFloat("_ColorMask", (float)colorWriteMask);
    newEnt.customMat.SetFloat("_UseUIAlphaClip", newEnt.useAlphaClip ? 1.0f : 0.0f);
    //应该多次一举了,设置了_UseUIAlphaClip会自动启用UNITY_UI_ALPHACLIP
    if (newEnt.useAlphaClip)
        newEnt.customMat.EnableKeyword("UNITY_UI_ALPHACLIP");
    else
        newEnt.customMat.DisableKeyword("UNITY_UI_ALPHACLIP");

    m_List.Add(newEnt);
    return newEnt.customMat;
}

对于Mask子节点的Image组件,其渲染需要进行模板测试,实现在MaskableGraphic中:

public abstract class MaskableGraphic : Graphic, IClippable, IMaskable, IMaterialModifier
{
    ...
    
    //该组件是否支持被遮罩
    [SerializeField]
    private bool m_Maskable = true;

    //该组件是用于遮罩的Graphic
    private bool m_IsMaskingGraphic = false;

   [NonSerialized]
   protected int m_StencilValue;

   /// <summary>
   /// See IMaterialModifier.GetModifiedMaterial
   /// </summary>
   public virtual Material GetModifiedMaterial(Material baseMaterial)
   {
       var toUse = baseMaterial;

       if (m_ShouldRecalculateStencil)
       {
           if (maskable)
           {
               var rootCanvas = MaskUtilities.FindRootSortOverrideCanvas(transform);
               //受多少个Mask影响
               m_StencilValue = MaskUtilities.GetStencilDepth(transform, rootCanvas);
           }
           else
               m_StencilValue = 0;

           m_ShouldRecalculateStencil = false;
       }

       // if we have a enabled Mask component then it will
       // generate the mask material. This is an optimization
       // it adds some coupling between components though :(
       if (m_StencilValue > 0 && !isMaskingGraphic)    //如果Graphic用于Mask,则在Mask组件里面修改材质球
       {
           //模板测试设置
           //Ref值 = (1 << m_StencilValue) - 1,每个Mask占一个bit位
           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;
   }

    ...

    /// <summary>
    /// See IMaskable.RecalculateMasking
    /// 父节点Mask变化时,重新获取渲染的材质球
    /// </summary>
    public virtual void RecalculateMasking()
    {
        // Remove the material reference as either the graphic of the mask has been enable/ disabled.
        // This will cause the material to be repopulated from the original if need be. (case 994413)
        StencilMaterial.Remove(m_MaskMaterial);
        m_MaskMaterial = null;
        m_ShouldRecalculateStencil = true;
        SetMaterialDirty();
    }
}

猜你喜欢

转载自blog.csdn.net/qilin598866753/article/details/140319444