驱动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源码阅读