UIGU source code analysis 13: MaskGraphic

Source code 13: MaskGraphic

public abstract class MaskableGraphic : Graphic, IClippable, IMaskable, IMaterialModifier
{
	...    
}

Image Text RawImage inherits MaskGraphic. Basically, all UI components that can be displayed inherit MaskGraphic.

In addition to inheriting Graphic, MaskableGraphic also inherits three interfaces


IClippable

IClippable The clipper is usually used together with the clipper IClipper. See the source code comments of the IClippable interface.

   public interface IClippable
    {
        /// <summary>
        ///可被裁剪的对象
        /// GameObject of the IClippable object
        /// </summary>
        GameObject gameObject { get; }

        /// <summary>
        ///可被裁剪的父对象状态发生变化的时候使用
        /// Will be called when the state of a parent IClippable changed.
        /// </summary>
        void RecalculateClipping();

        /// <summary>
        /// 可被裁剪的对象RectTransform
        /// The RectTransform of the clippable.
        /// </summary>
        RectTransform rectTransform { get; }

        /// <summary>
        /// 在给给定的裁剪范围中进行裁剪计算
        /// Clip and cull the IClippable given a specific clipping rect
        /// </summary>
        /// <param name="clipRect">The Rectangle in which to clip against.</param>
        /// <param name="validRect">Is the Rect valid. If not then the rect has 0 size.</param>
        void Cull(Rect clipRect, bool validRect);

        /// <summary>
        /// 设置可被裁剪的对象的裁剪范围
        /// Set the clip rect for the IClippable.
        /// </summary>
        /// <param name="value">The Rectangle for the clipping</param>
        /// <param name="validRect">Is the rect valid.</param>
        void SetClipRect(Rect value, bool validRect);

        /// <summary>
        /// Set the clip softness for the IClippable.
        ///
        /// The softness is a linear alpha falloff over clipSoftness pixels.
        /// </summary>
        /// <param name="clipSoftness">The number of pixels to apply the softness to </param>
        void SetClipSoftness(Vector2 clipSoftness);
        

IClipper currently only has RectMask2D component inheritance implementation

That is to say, usually when cropping, the cutter RectMask2D sets a cutting rectangle to the cutter MaskableGraphic.


Here we first analyze how MaskGraphic sets up the application cropping related content and how to crop it in RectMask2D.

SetClipRect MaskableGraphic sends the clipping rectangle to the canvasRenderer renderer

public virtual void SetClipRect(Rect clipRect, bool validRect)
{
    if (validRect)
        canvasRenderer.EnableRectClipping(clipRect);
    else
        canvasRenderer.DisableRectClipping();
}

RecalculateClipping recalculates the RectMask2D component (m_ParentMask) in the parent object

/// <summary>
    /// See IClippable.RecalculateClipping
    /// </summary>
    public virtual void RecalculateClipping()
    {
        UpdateClipParent();
    }
    
    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;
	}

Cull culling method If validRect is false, or the input clipRect does not coincide with the rectangular area of ​​the Canvas, call the UpdateCull method, set cull to true, and assign cull to canvasRenderer.cull. If canvasRenderer.cull changes, send the event m_OnCullStateChanged, m_OnCullStateChanged.Invoke(cull), and call SetVerticesDirty to set the Dirty of the vertex and wait for redrawing.

    public virtual void Cull(Rect clipRect, bool validRect)
    {
        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();
        }
    }

IMaskable

IMaskable The masked person is similar to IClippable and also has a masker Mask component (must have a MaskGraphic component)

Here we only analyze it as IMaskable Mask and analyze it later.

IMaskable has only one implementation method RecalculateMasking

        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();
        }

Set m_ShouldRecalculateStencil to true and call SetMaterialDirty, that is, when Mask is enabled, the StencilValue of all IMaskable sub-objects will change and mark reconstruction.

IMaterialModifier

MaskGraphic also implements the IMaterialModifier interface, which is mainly used to achieve the masking effect, because the masking effect is achieved through materials.

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

            if (m_ShouldRecalculateStencil)
            {
                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)
            {
                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;
        }

The GetModifiedMaterial method is called in Rebulid after being marked and reconstructed.

If you need to recalculate the template, get the Canvas with overrideSorting as true from the parent RectTransform, assign it to the rootCanvas, and then get the template depth from the rootCanvas through MaskUtilities.GetStencilDepth.

If the template depth is greater than 0, and there is no Mask component or the Mask component is not activated, add baseMaterial, stencilID, operation and other parameters to StencilMaterial, remove the old m_MaskMaterial from StencilMaterial, replace m_MaskMaterial with the new baseMaterial, and return


Other supplements

  protected override void OnEnable()
    {
        base.OnEnable();
        m_ShouldRecalculateStencil = true;
        UpdateClipParent();
        SetMaterialDirty();

        if (isMaskingGraphic)
        {
            MaskUtilities.NotifyStencilStateChanged(this);
        }
    }

When the component is first activated, set the recalculation template flag to True, update the clipped parent object, set Material to Dirty, and SetMaterialDirty. Removed m_MaskMaterial from StencilMaterial and set m_MaskMaterial to empty. If the Makk component is not empty, call MaskUtilities.NotifyStencilStateChanged to recalculate the Mask.

   protected override void OnDisable()
    {
        base.OnDisable();
        m_ShouldRecalculateStencil = true;
        SetMaterialDirty();
        UpdateClipParent();
        StencilMaterial.Remove(m_MaskMaterial);
        m_MaskMaterial = null;

        if (isMaskingGraphic)
        {
            MaskUtilities.NotifyStencilStateChanged(this);
        }
    }

Set the recalculation template m_ShouldRecalculateStencil to true, update the clipped parent object UpdateClipParent, set Material to Dirty, SetMaterialDirty. Removed m_MaskMaterial from StencilMaterial and set m_MaskMaterial to empty. If the Mesh component is not empty, call MaskUtilities.NotifyStencilStateChanged to recalculate the Mask.


StencilMaterial class

    /// <summary>
    /// Dynamic material class makes it possible to create custom materials on the fly on a per-Graphic basis,
    /// and still have them get cleaned up correctly.
    /// </summary>
    public static class StencilMaterial
    {
       	private static List<MatEntry> m_List = new List<MatEntry>();
    	...
    }

StencilMaterial is a static class responsible for managing template materials. Maintains a list of MatEntry types:

private static List m_List = new List();

The Add, Remove and ClearAll methods can be called externally to operate on this List.

The Add method will create a MatEntry, assign the input baseMat and other parameters to the MatEntry, create a customMat that assigns the baseMat, and assign parameters such as stencilID and operation to the customMat. In fact, the shader parameters of the customMat are assigned.

Guess you like

Origin blog.csdn.net/NippyLi/article/details/123514436