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是通过模板测试进行遮罩的



