原图放在我的 Processon:UGUI RectMask2D+MaskableGraphic 工作原理 | ProcessOn免费在线作图,在线流程图,在线思维导图 |

一、RectMask2D 的实质

1、它的意图是:在渲染管线的应用阶段对其所有子物体进行粗粒度的裁剪和剔除。

2、它先利用 MaskUtilities 类的 GetRectMasksForClip() 方法获取到与当前 Rectmask2D 共同实际生效的所有父 RectMask2D 列表。

3、再利用 Clipping 类对这些父 RectMask2D 的矩形取交集,得到实际生效的裁剪矩形。

4、然后,它将这个裁剪矩形遍历地设给了其所有子 MaskableGraphic。

5、最后 MaskableGraphic 调用了 canvasRenderer.EnableRectClipping(clipRect); / canvasRenderer.DisableRectClipping(); 和 canvasRenderer.cull = cull。(注意,最终还是对CanvasRenderer的操作)。

二、注意

1、要注意,嵌套的 RectMask2D 的遮罩结果是取交集。

具体请看 Clipping 类中的实现。

2、要注意使用独立绘制顺序的 Canvas(overrideSorting==true)对 RectMask2D 遮罩结果的影响。

RectMask2D 的遮罩会被使用独立绘制顺序的 Canvas 打断。如:下面的情况,RectMask2D将不对 Image 生效。具体请看 MaskUtilities 类的 GetRectMasksForClip() 方法。

//---------------------------------------------------------
// --Canvas1
// ----RectMask2D
// ------Canvas2(使用独立绘制顺序)
// --------Image(clippable)
//---------------------------------------------------------

3、性能优化点

MaskUtilities 类的 GetRectMasksForClip() 方法中,两层循环都调用了 GetComponentsInParent(),还有 IsDescendantOrSelf() 方法,其性能都和父子物体嵌套的层数有关。

三、全注释

---------------------- NRatel 割 -------------------------------

更多 UGUI 注释已放入  GitHub - NRatel/uGUI: Source code for the Unity UI system.。

---------------------- NRatel 割 -------------------------------

1、RectMask2D:

using System;
using System.Collections.Generic;
using UnityEngine.EventSystems;namespace UnityEngine.UI
{[AddComponentMenu("UI/Rect Mask 2D", 13)][ExecuteAlways][DisallowMultipleComponent][RequireComponent(typeof(RectTransform))]// A 2D rectangular mask that allows for clipping / masking of areas outside the mask.// The RectMask2D behaves in a similar way to a standard Mask component. It differs though in some of the restrictions that it has.// A RectMask2D:// *Only works in the 2D plane// *Requires elements on the mask to be coplanar.// *Does not require stencil buffer / extra draw calls// *Requires fewer draw calls// *Culls elements that are outside the mask area.// 一个允许裁剪 Mask 之外区域的2D矩形Mask。// * Only works in the 2D plane. 只在2D平面中工作。// * Requires elements on the mask to be coplanar. 要求元素与Mask在同一平面。// * Does not require stencil buffer / extra draw calls. 不需要模板缓冲区 / 额外的 draw calls。// * Requires fewer draw calls. 需要较少的绘制调用。// * 删除 Mask 区域之外的元素。public class RectMask2D : UIBehaviour, IClipper, ICanvasRaycastFilter{[NonSerialized]private readonly RectangularVertexClipper m_VertexClipper = new RectangularVertexClipper(); //矩形顶点剔除器[NonSerialized]private RectTransform m_RectTransform;  // 与本 RectMask2D 关联的 RectTransform。[NonSerialized]private HashSet<MaskableGraphic> m_MaskableTargets = new HashSet<MaskableGraphic>(); //所有受本 RectMask2D 裁剪的 MaskableGraphic 的集合。[NonSerialized]private HashSet<IClippable> m_ClipTargets = new HashSet<IClippable>(); //所有受本 RectMask2D 裁剪的 IClippable 的集合 (MaskableGraphic已除外。实际可能为空, 因为 目前实现了 IClippable 接口的只有 MaskableGraphic)。[NonSerialized]private bool m_ShouldRecalculateClipRects;  //是否需要重新计算 m_Clippers(脏标记)。[NonSerialized]private List<RectMask2D> m_Clippers = new List<RectMask2D>();   //对于当前节点,自身及实际生效的所有父 RectMask2D 的列表(中间未穿插“使用独立绘制顺序”的Canvas)。[NonSerialized]private Rect m_LastClipRectCanvasSpace; //保存上次生效的、Canvas空间下的裁剪矩形[NonSerialized]private bool m_ForceClip;// Returns a non-destroyed instance or a null reference.// 返回一个未销毁的 Canvas 或 null。// 当前所属的 Canvas(激活状态的)。[NonSerialized] private Canvas m_Canvas;private Canvas Canvas{get{if (m_Canvas == null){var list = ListPool<Canvas>.Get();gameObject.GetComponentsInParent(false, list);if (list.Count > 0)m_Canvas = list[list.Count - 1];elsem_Canvas = null;ListPool<Canvas>.Release(list);}return m_Canvas;}}// Get the Rect for the mask in canvas space.// 获取 Canvas空间下的 RectMask2D 的 Rect。public Rect canvasRect{get{return m_VertexClipper.GetCanvasRect(rectTransform, Canvas);}}// Helper function to get the RectTransform for the mask.// 获取 与本 RectMask2D 关联的 RectTransform。public RectTransform rectTransform{get { return m_RectTransform ?? (m_RectTransform = GetComponent<RectTransform>()); }}protected RectMask2D(){}// 1、调用父类 OnEnable。// 2、m_ShouldRecalculateClipRects 设为 true(需要重新计算 m_Clippers)。// 3、在 ClipperRegistry 注册。// 4、通知 2DMaskStateChanged(通知所有实现 IClippable 接口的子物体重新计算裁剪。protected override void OnEnable(){base.OnEnable();m_ShouldRecalculateClipRects = true;ClipperRegistry.Register(this);MaskUtilities.Notify2DMaskStateChanged(this);}// 1、调用父类 OnDisable。// 2、清空 m_MaskableTargets。// 3、清空 m_Clippers。// 4、移除 ClipperRegistry 中的注册。// 5、通知 2DMaskStateChanged。(通知所有实现 IClippable 接口的子物体重新计算裁剪。protected override void OnDisable(){// we call base OnDisable first here as we need to have the IsActive return the correct value when we notify the children that the mask state has changed.// 我们首先在这里调用 base.OnDisable,因为我们需要 在通知子物体Mask状态改变时,让 IsActive 返回正确的值。// 疑问 ??? 未理解,为什么调 base.OnDisable 会影响到 IsActive。// 实际测试,某 UIBehaviour 的子类的 OnDisable 中,调用 base.OnDisable 前后,其 activeInHierarchy 和 activeSelf 均为 false。base.OnDisable();m_ClipTargets.Clear();m_MaskableTargets.Clear();m_Clippers.Clear();ClipperRegistry.Unregister(this);MaskUtilities.Notify2DMaskStateChanged(this);   //会调用 GetComponentsInChildren,判断自身及子物体是否激活。}#if UNITY_EDITOR// 1、调用父类 OnValidate。// 2、m_ShouldRecalculateClipRects 设为 true(需要重新 m_Clippers)。// 4、若当前处于激活状态,通知 2DMaskStateChanged。protected override void OnValidate(){base.OnValidate();m_ShouldRecalculateClipRects = true;if (!IsActive())return;MaskUtilities.Notify2DMaskStateChanged(this);}#endif// 实现 ICanvasRaycastFilter 的接口// 射线投射位置是否有效public virtual bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera){if (!isActiveAndEnabled)   //若未激活或未启用,则有效(不过滤)return true;// 若激活且启用,则检查投射点是否在本 rectTransform 的矩形内。 在则有效。return RectTransformUtility.RectangleContainsScreenPoint(rectTransform, sp, eventCamera);}private Vector3[] m_Corners = new Vector3[4];// rectTransform 在其 Root Canvas 上的矩形private Rect rootCanvasRect{get{// 获取 rectTransform 四个转角的世界坐标// 4 个顶点的 返回数组是顺时针的。它从左下开始,然后到左上, 然后到右上,最后到右下。// GetWorldCorners: https://docs.unity3d.com/cn/2020.1/ScriptReference/RectTransform.GetWorldCorners.htmlrectTransform.GetWorldCorners(m_Corners);if (!ReferenceEquals(Canvas, null)) //Canvas不为null{Canvas rootCanvas = Canvas.rootCanvas;  //取根Canvasfor (int i = 0; i < 4; ++i)m_Corners[i] = rootCanvas.transform.InverseTransformPoint(m_Corners[i]); //将 rectTransform 的四个顶点,变换到 根Canvas 的坐标系下。}// 返回 rectTransform 在其 Root Canvas 上的矩形return new Rect(m_Corners[0].x, m_Corners[0].y, m_Corners[2].x - m_Corners[0].x, m_Corners[2].y - m_Corners[0].y);}}// 执行裁剪// 1、Canvas 为 null 时不执行裁剪。// 2、计算 m_Clippers(仅需要重新计算时)。// 3、判断是否应该剔除。// 4、为所有子 IClippable 和 所有子 MaskableGraphic 执行裁剪和剔除。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)//TODO 看看 IsActive() 在这里是否能正常工作,或者它是否会导致意外的副作用// if the parents are changed or something similar we do a recalculate here// 如果父物体发生了变化 或 类似的、导致裁剪矩形变化的情况,重新计算 m_Clippers。if (m_ShouldRecalculateClipRects){MaskUtilities.GetRectMasksForClip(this, m_Clippers);    //计算 m_Clippers。m_ShouldRecalculateClipRects = false;   //置回}// get the compound rects from the clippers that are valid// 用有效的 clippers 获取叠加/复合的矩形。bool validRect = true;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.// 如果 Canvas 的渲染模式为 ScreenSpaceOverlay 或 ScreenSpaceCamera,则它的内容只有在它的rect与根Canvas重叠时才会被渲染。// 即: ScreenSpaceOverlay 或 ScreenSpaceCamera 模式下,超过根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.// 只有在 Mask 内部才显示子元素。// 如果 Mask 被剔除,那么 Mask 内的子元素也会被筛选。// 在这种情况下,可以传递一个无效的 rect 来让被调用方避开一些处理。clipRect = Rect.zero;validRect = false;}//裁剪矩形变化了:if (clipRect != m_LastClipRectCanvasSpace){// 为所有子 IClippable 执行裁剪(启用裁剪并设置裁剪矩形/关闭裁剪)。foreach (IClippable clipTarget in m_ClipTargets){clipTarget.SetClipRect(clipRect, validRect);}// 为所有子 MaskableGraphic 执行裁剪(启用裁剪并设置裁剪矩形/关闭裁剪)。// 为所有子 MaskableGraphic 执行剔除(设置是否剔除)。foreach (MaskableGraphic maskableTarget in m_MaskableTargets){maskableTarget.SetClipRect(clipRect, validRect);maskableTarget.Cull(clipRect, validRect);}}//强制裁剪:else if (m_ForceClip){// 为所有子 IClippable 执行裁剪(启用裁剪并设置裁剪矩形/关闭裁剪)。foreach (IClippable clipTarget in m_ClipTargets){clipTarget.SetClipRect(clipRect, validRect);}// 为所有子 MaskableGraphic 执行裁剪(启用裁剪并设置裁剪矩形/关闭裁剪)。// 为所有子 MaskableGraphic 执行剔除(设置是否剔除)(仅 canvasRenderer.hasMoved 时)。foreach (MaskableGraphic maskableTarget in m_MaskableTargets){maskableTarget.SetClipRect(clipRect, validRect);if (maskableTarget.canvasRenderer.hasMoved)     //如果发生的任何更改会使生成的几何形状的位置无效,则为 true。maskableTarget.Cull(clipRect, validRect);}}//裁剪矩形未变化且未强制裁剪:else{// 为所有子 MaskableGraphic 执行剔除(设置是否剔除)(仅 canvasRenderer.hasMoved 时)。foreach (MaskableGraphic maskableTarget in m_MaskableTargets){if (maskableTarget.canvasRenderer.hasMoved)maskableTarget.Cull(clipRect, validRect);}}m_LastClipRectCanvasSpace = clipRect;   //保存裁剪矩形m_ForceClip = false;    //强制裁剪置回}// Add a IClippable to be tracked by the mask.// 添加一个被 RectMask2D 追踪的 IClippable。public void AddClippable(IClippable clippable){if (clippable == null)return;m_ShouldRecalculateClipRects = true;MaskableGraphic maskable = clippable as MaskableGraphic;if (maskable == null)m_ClipTargets.Add(clippable);   //若 IClippable 不是 MaskableGraphic,则加入 m_ClipTargets。elsem_MaskableTargets.Add(maskable); //若 IClippable 是 MaskableGraphic,则加入 m_MaskableTargets。m_ForceClip = true;}// Remove an IClippable from being tracked by the mask.// 移除一个被 RectMask2D 追踪的 IClippable。public void RemoveClippable(IClippable clippable){if (clippable == null)return;m_ShouldRecalculateClipRects = true;clippable.SetClipRect(new Rect(), false);MaskableGraphic maskable = clippable as MaskableGraphic;if (maskable == null)m_ClipTargets.Remove(clippable);    //若 IClippable 不是 MaskableGraphic,则从 m_ClipTargets 中移除。elsem_MaskableTargets.Remove(maskable);  //若 IClippable 是 MaskableGraphic,则从 m_MaskableTargets 中移除。m_ForceClip = true;}//重写 UIBehaviour 的方法//父物体改变后(具体看UIBehaviour里的注释),// 1、调用父类 OnTransformParentChanged。// 2、m_ShouldRecalculateClipRects 设为 true(需要重新计算 m_Clippers)。protected override void OnTransformParentChanged(){base.OnTransformParentChanged();m_ShouldRecalculateClipRects = true;}//重写 UIBehaviour 的方法//当关联的 Canvas 在 Hierarchy 上变化时(具体看UIBehaviour里的注释),// 1、清除对当前所属的 Canvas(激活状态的)的引用。// 2、调用父类 OnCanvasHierarchyChanged。// 3、m_ShouldRecalculateClipRects 设为 true(需要重新计算 m_Clippers)。protected override void OnCanvasHierarchyChanged(){m_Canvas = null;base.OnCanvasHierarchyChanged();m_ShouldRecalculateClipRects = true;}}
}

2、Clipping

using System.Collections.Generic;namespace UnityEngine.UI
{// Utility class to help when clipping using IClipper.// 使用 IClipper 进行裁剪时的工具类。public static class Clipping{// Find the Rect to use for clipping.// Given the input RectMask2ds find a rectangle that is the overlap of all the inputs.// 找到用于剪切的矩形。// 输入一个 RectMask2d 列表,找到与所有输入矩形都重叠的矩形。(所有输入矩形的总交集)// 参数"rectMaskParents":RectMasks to build the overlap rect from. //// 参数"validRect":Was there a valid Rect found.// 返回值:The final compounded overlapping rect.//---------------------------------------------------------------// 这个方法决定了,有多个 RectMask2d 嵌套时,是怎么处理的!//---------------------------------------------------------------public static Rect FindCullAndClipWorldRect(List<RectMask2D> rectMaskParents, out bool validRect){//列表为空,返回无效和默认Rectif (rectMaskParents.Count == 0){validRect = false;  return new Rect();}Rect current = rectMaskParents[0].canvasRect; //取第一个 RectMask2D 在 Canvas空间下的 Rect。float xMin = current.xMin;float xMax = current.xMax;float yMin = current.yMin;float yMax = current.yMax;for (var i = 1; i < rectMaskParents.Count; ++i)   //遍历取交集{current = rectMaskParents[i].canvasRect;     //取其他 RectMask2D 在 Canvas空间下的 Rect。//取交集:取所有RectMask2D 的 xMin 和 yMin 的最大值 和 xMax 和 yMax 的最小值。if (xMin < current.xMin)xMin = current.xMin;if (yMin < current.yMin)yMin = current.yMin;if (xMax > current.xMax)xMax = current.xMax;if (yMax > current.yMax)yMax = current.yMax;}validRect = xMax > xMin && yMax > yMin; //总交集是否有效if (validRect)return new Rect(xMin, yMin, xMax - xMin, yMax - yMin);  //返回总交集elsereturn new Rect();}}
}

3、RectangularVertexClipper

namespace UnityEngine.UI
{internal class RectangularVertexClipper{readonly Vector3[] m_WorldCorners = new Vector3[4];readonly Vector3[] m_CanvasCorners = new Vector3[4];//获取 t在Canvas下的 rect (包括位置和大小)public Rect GetCanvasRect(RectTransform t, Canvas c){if (c == null)return new Rect();t.GetWorldCorners(m_WorldCorners);  //取t的世界坐标var canvasTransform = c.GetComponent<Transform>();for (int i = 0; i < 4; ++i)m_CanvasCorners[i] = canvasTransform.InverseTransformPoint(m_WorldCorners[i]);  //将世界坐标转为相对Canvas的local坐标//返回t在Canvas下的 rect (包括位置和大小)return new Rect(m_CanvasCorners[0].x, m_CanvasCorners[0].y, m_CanvasCorners[2].x - m_CanvasCorners[0].x, m_CanvasCorners[2].y - m_CanvasCorners[0].y);}}
}

UGUI 源码之 RectMask2D、Clipping、RectangularVertexClipper相关推荐

  1. ugui源码_UGUI 源码笔记(一)文件结构和部分组件使用

    这是我阅读 UGUI 源码记录的相关笔记,共三部分.文件结构和部分组件使用.输入事件.核心部分 ZeroyiQ:UGUI 源码笔记(一)文件结构和部分组件使用 ZeroyiQ:UGUI 源码笔记(二) ...

  2. [UGUI源码剖析]—Rebuild 网格重建(画布刷新)系统

    几个比较重要的类和接口: Canvas.CanvasUpdateRegistry.ClipperRegistry.LayoutRebuilder.LayoutGroup.Graphics.Maskab ...

  3. UGUI源码分析:开关组件Toggle与ToggleGroup

    系列 UGUI源码分析系列总览 相关前置: UGUI EventSystem源码分析 UGUI源码分析:Selectable交互组件的基类 文章目录 系列 Toggle Toggle组件属性介绍 初始 ...

  4. UGUI源码分析:GridLayoutGroup网格布局组件与ContentSizeFitter尺寸调节组件

    系列 UGUI源码分析系列总览 相关前置: UGUI CanvasUpdateSystem源码分析 UGUI源码分析:LayoutSystem布局系统 UGUI源码分析:LayoutGroup中的纵横 ...

  5. UGUI源码分析:LayoutGroup中的纵横布局组件(HorizontalOrVerticalLayoutGroup)

    系列 UGUI源码分析系列总览 相关前置: UGUI CanvasUpdateSystem源码分析 UGUI源码分析:LayoutSystem布局系统 文章目录 系列 UML图一览 LayoutGro ...

  6. 如何把UGUI当做一个插件使用(删除Unity中的UGUI,导入UGUI源码进入项目)

    最近闲着没事,一直也都知道UGUI是开源的,所以就想着把UGUI的源代码放到Unity里面,看一看能不能用,经过一番调试,终于弄好了,有兴趣的同学可以看一下,欢迎交流沟通. 欲练神功,必先自宫.第一步 ...

  7. Unity中的UGUI源码解析之事件系统(2)-EventSystem组件

    Unity中的UGUI源码解析之事件系统(2)-EventSystem组件 今天介绍我们的第一个主角: EventSystem. EventSystem在整个事件系统中处于中心, 相当于事件系统的管理 ...

  8. Unity中的UGUI源码解析之事件系统(8)-输入模块(中)

    Unity中的UGUI源码解析之事件系统(8)-输入模块(中) 接上一篇文章, 继续介绍输入模块. Unity中主要处理的是指针事件, 也就是在2d平面上跟踪指针设备输入坐标的的事件, 这一类事件有鼠 ...

  9. Unity中的UGUI源码解析之事件系统(9)-输入模块(下)

    Unity中的UGUI源码解析之事件系统(9)-输入模块(下) 接上一篇文章, 继续介绍输入模块. StandaloneInputModule类是上一篇文章介绍的抽象类PointerInputModu ...

最新文章

  1. Android巩固之事件分发机制
  2. php pdo 时间,php – 使用PDO执行时间记录查询 – 自动完成功能无效
  3. got github
  4. 字节 位 比特的关系
  5. python需要电脑配置-python3批量统计用户电脑配置
  6. Java 线程的 wait 和 notify 的神坑
  7. 【Python】青少年蓝桥杯_每日一题_12.11_开关灯问题
  8. Apache Flink 1.10.0 发布 | 云原生生态周报 Vol. 38
  9. 【USACO Feb 2014】Cow Decathlon
  10. 【numpy求和】numpy.sum()求和
  11. 单片机串口通信电平不匹配的解决电路,5V 3.3V串口通讯
  12. mysql 5.6 cmake_mysql5.6如何使用cmake编译
  13. 一定要多反思复盘和整理
  14. 星环inceptor建表公式以及各个表的区别联系
  15. 【AutoCAD 卸载工具,完全彻底删除清理干净AutoCAD各种残留注册表和文件】
  16. 【C语言】c语言练习题【2】(适合初学者)
  17. redis设置零点过期,网站浏览量
  18. Create React App无eject配置(react-app-rewired 和 customize-cra)
  19. ui设计现状与意义_UI设计师的前景与现状?
  20. 对物联网的感悟_物联网心得体会总结

热门文章

  1. 火车头不能用mysql_火车头采集器发布失败常见问题汇总
  2. 建网站之前要先做好SEO布局工作
  3. Deepin20.6直接运行exe文件
  4. 用小白鼠试验毒水问题
  5. P1618 三连击(升级版)【全排列next_permutation】
  6. 还有人在质疑数据挖掘是泡沫吗?千万不要叶公好龙
  7. 【爬虫】每天定时爬取网页小故事并发送至指定邮箱
  8. 什么是adsl动态拨号服务器?
  9. java ip 白名单_Java代码中对IP进行白名单验证
  10. linux firawll防火墙设置白名单/指定ip访问指定端口