作者:蓝向芸_443 | 来源:互联网 | 2023-10-12 09:53
原图放在我的Processon:https:www.processon.comviewlink60d71f12637689326ce58b20一、Mask的实质1
原图放在我的 Processon:https://www.processon.com/view/link/60d71f12637689326ce58b20
一、Mask 的实质
1、Mask 是通过模板测试进行遮罩的。
Unity模板测试简略文档。
需要知道:模板测试发生在光栅化阶段/逐片元操作时。如图:
2、Mask 通过创建遮罩材质使UI系统完成模板测试。
⑴、Mask 组件在 OnEnable() 时将同级关联的 CanvasRenderer 的 hasPopInstruction 字段置为 true。使这个节点的所有子节点绘制完之后多一次绘制,用于遮罩。
⑵、Mask 组件会在 GetModifiedMaterial 方法(实现自 IMaterialModifier)中为同级关联的 CanvasRenderer 生成一个 unmaskMaterial。并设置为 popMaterial,用于遮罩。
⑶、Mask 组件会在 GetModifiedMaterial 方法(实现自 IMaterialModifier)中为同级的 Graphic 组件生成一个 maskMaterial。(最终赋给 CanvasRenderer)(见 Graphic类中的 canvasRenderer.SetMaterial(materialForRendering, 0);)
⑷、Mask 的子 MaskableGraphic 会在GetModifiedMaterial 方法(实现自 IMaterialModifier)中为自身生成一个maskMaterial。(最终赋给 CanvasRenderer)(见 Graphic类中的 canvasRenderer.SetMaterial(materialForRendering, 0);)
⑸、从 StencilMaterial 类中可以看到,生成的材质都是基于 UI-Default.shader 创建的,然后修改了其模板测试相关的参数。可以继续查看 UI-Default.shader 了解详情。(Unity 官网下载 Built-in-Shaders:https://unity.cn/releases/lts。)
⑹、为使多层 Mask 可以嵌套工作(和 RectMask2D 一样取交集),在生成这些材质时会根据自身深度(自身所在的、嵌套Mask下的位置)提供不同的模板测试参数。
例如一个三层嵌套的例子。
(具体进行模板测试时的参数比较、操作待研究。。)
原图在我的 processon: https://www.processon.com/view/link/60db2583637689326ced8271
3、从性能上简单比较 Mask 和 RectMask2D。
⑴、Mask 的模板测试发生在光栅化阶段/逐片元操作时,而 RectMask2D 的粗裁剪发生在应用阶段/设置渲染状态时。可以看出 RectMask2D 的粗裁剪执行得很早,可以大幅略过被裁剪部分的渲染过程。
⑵、Mask 会将 CanvasRenderer的 hasPopInstruction 设为 true,这会增加一次 drawcall。
⑶、Mask 基于 UI-Default.shader 为自身及子Graphic 创建了新材质,所以会打断与其他UI的合批。
二、全注释:
---------------------- NRatel 割 -------------------------------
更多 UGUI 注释已放入 https://github.com/NRatel/uGUI。
---------------------- NRatel 割 -------------------------------
1、IMaterialModifier
namespace UnityEngine.UI
{// Interface which allows for the modification of the Material used to render a Graphic before they are passed to the CanvasRenderer.// When a Graphic sets a material is is passed (in order) to any components on the GameObject that implement IMaterialModifier.// This component can modify the material to be used for rendering.// 这个接口允许渲染一个图形的材质在传递到 CanvasRenderer 之前被修改。public interface IMaterialModifier{// Perform material modification in this function.// 在此方法中执行材质的修改。// "baseMaterial":The material that is to be modified. //将被修改的材质// 返回值:The modified material. //被修改后的材质Material GetModifiedMaterial(Material baseMaterial);}
}
2、Mask
using System;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Rendering;
using UnityEngine.Serialization;namespace UnityEngine.UI
{[AddComponentMenu("UI/Mask", 13)][ExecuteAlways][RequireComponent(typeof(RectTransform))][DisallowMultipleComponent]// A component for masking children elements.// By using this element any children elements that have masking enabled will mask where a sibling Graphic would write 0 to the stencil buffer.// 用于遮罩子元素的组件。// 通过使用这个元素&#xff0c;任何启用了 Mask&#xff08;MaskableGraphic 的 maskable&#xff09; 的子元素都会被遮罩。与本组件同级关联的 Graphic 将在模板测试缓冲区上写入0。public class Mask : UIBehaviour, ICanvasRaycastFilter, IMaterialModifier{[NonSerialized]private RectTransform m_RectTransform; //与本组件关联的 RectTransform。public RectTransform rectTransform{get { return m_RectTransform ?? (m_RectTransform &#61; GetComponent()); }}[SerializeField]private bool m_ShowMaskGraphic &#61; true;// Show the graphic that is associated with the Mask render area.// 是否显示与 Mask 关联的图形的渲染区域。// 设置时&#xff0c;若关联的 Graphic 不为 null&#xff0c;则标记 Graphic 的材质脏标记 为脏。public bool showMaskGraphic{get { return m_ShowMaskGraphic; }set{if (m_ShowMaskGraphic &#61;&#61; value)return;m_ShowMaskGraphic &#61; value;if (graphic !&#61; null)graphic.SetMaterialDirty();}}[NonSerialized]private Graphic m_Graphic;// The graphic associated with the Mask.// 与 Mask 关联的 graphic。public Graphic graphic{get { return m_Graphic ?? (m_Graphic &#61; GetComponent()); }}[NonSerialized]private Material m_MaskMaterial; //Mask材质[NonSerialized]private Material m_UnmaskMaterial;protected Mask(){}//Mask是否启用&#xff08;生效&#xff09;&#xff1a;激活且关联的 Graphic 不为 null。public virtual bool MaskEnabled() { return IsActive() && graphic !&#61; null; }[Obsolete("Not used anymore.")]public virtual void OnSiblingGraphicEnabledDisabled() {}// 1、调用父类 OnEnable。// 2、若关联的 Graphic 不为null// ⑴、启用与 Graphic 关联的 CanvasRenderer 组件的 hasPopInstruction。// ⑵、标记 Graphic 的材质脏标记 为脏。// 3、通知 StencilStateChanged。&#xff08;通知所有实现 IMaskable 接口的子物体重新计算遮罩。protected override void OnEnable(){base.OnEnable();if (graphic !&#61; null){// hasPopInstruction&#xff1a;// Enable“render stack”pop draw call。// 当使用 hierarchy 渲染时&#xff0c;canvasRenderer 可以插入一个"pop"指令。// 这个"pop"指令将在所有子元素被渲染后执行。// CanvasRenderer 组件将使用配置的 pop 材质渲染。graphic.canvasRenderer.hasPopInstruction &#61; true; graphic.SetMaterialDirty();}MaskUtilities.NotifyStencilStateChanged(this);}// 1、调用父类 OnDisable。// 2、若关联的 Graphic 不为null// ⑴、标记 Graphic 的材质脏标记 为脏。// ⑵、关闭与 Graphic 关联的 CanvasRenderer 组件的 hasPopInstruction。// ⑶、设置与 Graphic 关联的 CanvasRenderer 组件的 popMaterialCount 为 0。// 3、将 m_MaskMaterial 从 StencilMaterial 中移除&#xff0c;并设置 m_MaskMaterial 为 null。// 4、将 m_UnmaskMaterial 从 StencilMaterial 中移除&#xff0c;并设置 m_UnmaskMaterial 为 null。// 5、通知 StencilStateChanged。&#xff08;通知所有实现 IMaskable 接口的子物体重新计算遮罩。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&#xff0c;因为我们需要 在通知子物体Mask状态改变时&#xff0c;让 IsActive 返回正确的值。// 疑问 ??? 未理解&#xff0c;为什么调 base.OnDisable 会影响到 IsActive。// 实际测试&#xff0c;某 UIBehaviour 的子类的 OnDisable 中&#xff0c;调用 base.OnDisable 前后&#xff0c;其 activeInHierarchy 和 activeSelf 均为 false。base.OnDisable();if (graphic !&#61; null){graphic.SetMaterialDirty();graphic.canvasRenderer.hasPopInstruction &#61; false;graphic.canvasRenderer.popMaterialCount &#61; 0; // popMaterialCount&#xff1a;CanvasRenderer组件可用的材质数量&#xff0c;用于内部遮罩。}StencilMaterial.Remove(m_MaskMaterial);m_MaskMaterial &#61; null;StencilMaterial.Remove(m_UnmaskMaterial);m_UnmaskMaterial &#61; null;MaskUtilities.NotifyStencilStateChanged(this);}#if UNITY_EDITORprotected override void OnValidate(){base.OnValidate();if (!IsActive())return;if (graphic !&#61; null)graphic.SetMaterialDirty();MaskUtilities.NotifyStencilStateChanged(this);}#endif// 实现 ICanvasRaycastFilter 的接口// 射线投射位置是否有效public virtual bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera){if (!isActiveAndEnabled) //若未激活或未启用&#xff0c;则有效&#xff08;不过滤&#xff09;return true;// 若激活且启用&#xff0c;则检查投射点是否在本 rectTransform 的矩形内。 在则有效。return RectTransformUtility.RectangleContainsScreenPoint(rectTransform, sp, eventCamera);}// Stencil calculation time!// 实际的模板测试在这里进行&#xff01;// 实现 IMaterialModifier 的接口。// 1、检查 Mask 是否启用&#xff0c;若未启用&#xff0c;直接返回 baseMaterial。public virtual Material GetModifiedMaterial(Material baseMaterial){if (!MaskEnabled())return baseMaterial;var rootSortCanvas &#61; MaskUtilities.FindRootSortOverrideCanvas(transform); // 获取最深根的 Canvas&#xff0c;或第一个“使用独立绘制顺序”的 Canvas。var stencilDepth &#61; MaskUtilities.GetStencilDepth(transform, rootSortCanvas); // 计算模板测试深度。if (stencilDepth >&#61; 8){Debug.LogWarning("Attempting to use a stencil mask with depth > 8", gameObject);return baseMaterial; //如果深度>&#61;8&#xff0c;抛出警告&#xff0c;直接返回 baseMaterial。}int desiredStencilBit &#61; 1 <}
3、IMaskable
在 Mask 状态改变时&#xff0c;MaskUtilities 利用这个接口配合对遮罩情况进行更新。
using System;namespace UnityEngine.UI
{// This element is capable of being masked out.// 这个元素可以被遮罩。&#xff08;目前只有MaskableGraphic实现它&#xff09;public interface IMaskable{// Recalculate masking for this element and all children elements.// Use this to update the internal state (recreate materials etc).// 重新计算此元素和所有子元素的遮罩。// 更新内部状态(重新创建材质等)。void RecalculateMasking();}
}