UGUI源码阅读(四)遮罩之RectMask2D

驱动RectMask2D实现在CanvasUpdateRegistry组件:

private void PerformUpdate()
{
    ...
    // now layout is complete do culling...
    UnityEngine.Profiling.Profiler.BeginSample(m_CullingUpdateProfilerString);
    ClipperRegistry.instance.Cull();
    UnityEngine.Profiling.Profiler.EndSample();
}

ClipperRegistry:

/// <summary>
/// Perform the clipping on all registered IClipper
/// </summary>
public void Cull()
{
    var clippersCount = m_Clippers.Count;
    for (var i = 0; i < clippersCount; ++i)
    {
        m_Clippers[i].PerformClipping();
    }
}

RectMask2D:

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
        MaskUtilities.GetRectMasksForClip(this, m_Clippers);
        m_ShouldRecalculateClipRects = false;
    }

    // get the compound rects from
    // the clippers that are valid
    bool validRect = true;
    //计算所有RectMask2D的交集Rect
    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 =     //rootCanvasRect是否包含这个clipRect
        (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;
    }

    //驱动该RectMask2D影响的MaskableGraphic进行处理
    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();
}

MaskableGraphic组件:

/// <summary>
/// See IClippable.SetClipRect
/// 设置矩形裁剪区域
/// </summary>
public virtual void SetClipRect(Rect clipRect, bool validRect)
{
    if (validRect)
        canvasRenderer.EnableRectClipping(clipRect);
    else
        canvasRenderer.DisableRectClipping();
}

//设置边沿柔和
public virtual void SetClipSoftness(Vector2 clipSoftness)
{
    canvasRenderer.clippingSoftness = clipSoftness;
}

/// <summary>
/// See IClippable.Cull
/// 执行剔除
/// </summary>
public virtual void Cull(Rect clipRect, bool validRect)
{
    //该Rect是否覆盖clipRect
    var cull = !validRect || !clipRect.Overlaps(rootCanvasRect, true);
    UpdateCull(cull);
}

//更新剔除状态
private void UpdateCull(bool cull)
{
    if (canvasRenderer.cull != cull)
    {
        canvasRenderer.cull = cull;
        UISystemProfilerApi.AddMarker("MaskableGraphic.cullingChanged", this);
        m_OnCullStateChanged.Invoke(cull);
        OnCullingChanged();
    }
}

//父节点RectMask2D变化时
private void UpdateClipParent()
{
    //获取直接受控制的RectMask2D
    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);
    }
    
    //将该组件放入RectMask2D中受其管理
    // don't re-add it if the newparent is inactive
    if (newParent != null && newParent.IsActive())
        newParent.AddClippable(this);

    m_ParentMask = newParent;
}

MaskUtilities:

/// <summary>
/// Find the correct RectMask2D for a given IClippable.
/// </summary>
/// <param name="clippable">Clippable to search from.</param>
/// <returns>The Correct RectMask2D</returns>
public static RectMask2D GetRectMaskForClippable(IClippable clippable)
{
    List<RectMask2D> rectMaskComponents = ListPool<RectMask2D>.Get();
    List<Canvas> canvasComponents = ListPool<Canvas>.Get();
    RectMask2D componentToReturn = null;

    //获取父节点的所有RectMask2D
    clippable.gameObject.GetComponentsInParent(false, rectMaskComponents);

    if (rectMaskComponents.Count > 0)
    {
        for (int rmi = 0; rmi < rectMaskComponents.Count; rmi++)
        {
            componentToReturn = rectMaskComponents[rmi];
            if (componentToReturn.gameObject == clippable.gameObject)
            {
                //自身上的RectMask2D不影响自身
                componentToReturn = null;
                continue;
            }
            if (!componentToReturn.isActiveAndEnabled)
            {
                componentToReturn = null;
                continue;
            }
            //这行代码应放在循环外面
            clippable.gameObject.GetComponentsInParent(false, canvasComponents);
            for (int i = canvasComponents.Count - 1; i >= 0; i--)
            {
                //当前RectMask2D不是overrideSorting Canvas下的组件
                if (!IsDescendantOrSelf(canvasComponents[i].transform, componentToReturn.transform) && canvasComponents[i].overrideSorting)
                {
                    componentToReturn = null;
                    break;
                }
            }
            break;
        }
    }

    ListPool<RectMask2D>.Release(rectMaskComponents);
    ListPool<Canvas>.Release(canvasComponents);

    return componentToReturn;
}

至于RectMask2D Shader实现裁剪,可参阅笔者另一篇文章Unity UI-Default.shader源码阅读