栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 游戏开发 > 其他

Unity UGUI 源码MaskableGraphic,RectMask2D,Mask

其他 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Unity UGUI 源码MaskableGraphic,RectMask2D,Mask

MaskableGraphic

MaskableGraphic继承自Graphic,分别实现了IClippable, IMaskable, IMaterialModifier这三个接口。从其字面意思也可以看的出来它是可遮罩的Graphic,可以结合着ugui中的两种遮罩组件Mask和RectMask2D一起看。

protected override void OnEnable()
{
    base.OnEnable();
    m_ShouldRecalculateStencil = true;
    //处理RectMask2D
    UpdateClipParent();
    SetMaterialDirty();
    //处理Mask
    if (isMaskingGraphic)
    {
        MaskUtilities.NotifyStencilStateChanged(this);
    }
}
RectMask2D

首先是RectMask2D的OnEnable

// RectMask2D.cs
protected override void OnEnable()
 {
     base.OnEnable();
     m_ShouldRecalculateClipRects = true;
     // ClipperRegistry的m_Clippers储存着所有的RectMask2D
     ClipperRegistry.Register(this);
     // 通知子节点中所有实现了IClippable接口的去执行RecalculateClipping
     MaskUtilities.Notify2DMaskStateChanged(this);
 }

此时,RectMask2D下的MaskableGraphic就会执行RecalculateClipping -> UpdateClipParent

// MaskableGraphic.cs
 private void UpdateClipParent()
 {
 	 // 首先先找到Parent中离自己最近的那个RectMask2D
 	 // 这里RectMask2D是未激活状态的不符合条件,除此以外,被Canvas隔开的也不符合条件(Canvas的overridesorting是enable的状态的)
     var newParent = (maskable && IsActive()) ? MaskUtilities.GetRectMaskForClippable(this) : null;
     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())
     	 // 将MaskableGraphic加入到m_MaskableTargets列表里
         newParent.AddClippable(this);

     m_ParentMask = newParent;
 }

RectMask2D会受到Canvas的overridesorting的影响导致遮罩不生效

这里主要是负责将MaskableGraphic绑定到符合条件的RectMask2D上。
在CanvasUpdateRegistry的PerformUpdate函数中,会执行Cull操作,它为m_Clippers即所有的RectMask2D执行PerformClipping(Graphic中有解释)

// CanvasUpdateRegistry.cs
private void PerformUpdate()
{
    ......
    // now layout is complete do culling...
    ClipperRegistry.instance.Cull();
    ......
}

PerformClipping

// RectMask2D.cs
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)
    {
    	// 首先先找到所有的生效的RectMask2D的列表m_Clippers
        MaskUtilities.GetRectMasksForClip(this, m_Clippers);
        m_ShouldRecalculateClipRects = false;
    }

    // get the compound rects from
    // the clippers that are valid
    bool validRect = true;
    // 计算m_Clippers中的重叠部分clipRect,是否有重叠的部分validRect
    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;
    }
	// 遍历m_ClipTargets,m_MaskableTargets列表执行SetClipRect
    if (clipRect != m_LastClipRectCanvasSpace)
    {
        foreach (IClippable clipTarget in m_ClipTargets)
        {
            clipTarget.SetClipRect(clipRect, validRect);
        }

        foreach (MaskableGraphic maskableTarget in m_MaskableTargets)
        {
            // 执行canvasRenderer.EnableRectClipping(clipRect);
            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();
}

PerformClipping首先先获取Parent中所有生效的RectMask2D,然后通过他们计算出重叠部分,最后在MaskableGraphic上执行SetClipRect,也就是canvasRenderer.EnableRectClipping(clipRect)进行裁剪。
canvasRender的裁剪是通过修改Material中的_ClipRect实现的。_ClipRect的alpha值都为0。

color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect);
Mask

首先是OnEnable

// Mask.cs
protected override void OnEnable()
{
    base.OnEnable();
    // 这个graphic是与Mask的在同一个GameObject上的Graphic组件
    if (graphic != null)
    {
        graphic.canvasRenderer.hasPopInstruction = true;
        graphic.SetMaterialDirty();

        // Default the graphic to being the maskable graphic if its found.
        if (graphic is MaskableGraphic)
            (graphic as MaskableGraphic).isMaskingGraphic = true;
    }
	//  通知子节点中所有实现了IMaskable接口的去执行RecalculateMasking
    MaskUtilities.NotifyStencilStateChanged(this);
}
// MaskableGraphic.cs
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();
}

此时Mask上的Graphic和其子节点的Graphic都是MaterialDirty状态。在Graphic.cs中
Rebuild -> UpdateMaterial -> materialForRendering

// Graphic.cs
public virtual Material materialForRendering
{
    get
    {
        var components = ListPool.Get();
        GetComponents(typeof(IMaterialModifier), components);

        var currentMat = material;
        for (var i = 0; i < components.Count; i++)
            currentMat = (components[i] as IMaterialModifier).GetModifiedMaterial(currentMat);
        ListPool.Release(components);
        return currentMat;
    }
}

会获取自身上所有实现了IMaterialModifier接口然后执行GetModifiedMaterial函数,此时在Mask上绑定的Graphic就会调用Mask上的GetModifiedMaterial,GetModifiedMaterial返回一个Material供Graphic使用。

// Mask.cs
/// Stencil calculation time!
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)
    {
    	// 这部分只负责生成一个stencil ID是1 Replace Always的StencilMaterial 作为返回值,后续也是Mask上的Graphic会SetMaterial
        var maskMaterial = StencilMaterial.Add(baseMaterial, 1, StencilOp.Replace, CompareFunction.Always, m_ShowMaskGraphic ? ColorWriteMask.All : 0);
        StencilMaterial.Remove(m_MaskMaterial);
        m_MaskMaterial = maskMaterial;

    	// 这个是给Mask上的Graphic使用的Material,先全部置0
        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子节点的MaskableGraphic也是同理,执行GetModifiedMaterial

public virtual Material GetModifiedMaterial(Material baseMaterial)
{
    var toUse = baseMaterial;

    if (m_ShouldRecalculateStencil)
    {
    	// 通过maskable控制stencil ID,false时候直接为0
        if (maskable)
        {
            var rootCanvas = MaskUtilities.FindRootSortOverrideCanvas(transform);
            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)
    {
    	// 等于1的时候可以显示出来
        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;
}

可以看到Mask是通过模板测试进行遮罩的

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/902261.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号