Unity UI - MaskableGraphic 剖析
前言
MaskableGraphic 是一种可以用来被遮罩的 Graphic。在 Unity UI 中,Text、Image 和 RawImage 等组件类都继承自 MaskableGraphic。
MaskableGraphic 类
MaskableGraphic 是一个抽象类,它继承自 Graphic 类;同时还实现了如下多个接口用于实现遮罩效果:
IClippable,用于 RectMask2D 实现遮罩时裁剪遮罩区域;
IMaskable,用来在使用 Mask 实现遮罩效果时标记当前 MaskableGraphic 是可被绘制遮罩的;
IMaterialModifier,用来在使用 Mask 实现遮罩效果时更新渲染材质 Shader 中的模板测试参数。
成员属性
属性 | 描述 |
---|---|
m_MaskMaterial |
使用 Mask 实现遮罩效果时渲染所用材质 |
m_ParentMask |
使用 RectMask2D 实现遮罩效果时最适合的 RectMask2D |
m_Maskable |
当前 MaskableGraphic 是否允许被用来遮罩绘制 |
m_OnCullStateChanged |
当 culling state 发生变化时回调事件 |
m_StencilValue |
使用 Mask 实现遮罩效果时当前 MaskableGraphic 所在的模板测试深度 |
m_ShouldRecalculateStencil |
标记是否需要重新计算模板测试数据 |
方法
GetModifiedMaterial 方法
这个方法是 IMaterialModifier 接口中定义的一个方法,它能够更新当前渲染所用的材质 Shader,在下次 CanvasRender 渲染的时候就能使用被修改之后的材质 Shader。使用 Mask 实现遮罩效果时,需要使用这个方法来修改材质 Shader。下面就来看看具体的源码:
|
|
上面的代码主要分为两部分:
第一部分判断若需要计算模板测试深度
m_StencilValue
,则调用 MaskUtilities 类的GetStencilDepth
方法计算模板测试深度(MaskUtilities 类是遮罩相关的一个工具类,里面提供了很多遮罩绘制的辅助方法);第二部分通过判断模板测试深度
m_StencilValue
大于 0 且当前元素不是一个 Mask(或者是未激活的 Mask),那么就更新材质 Shader;这里更新主要是调用 StencilMaterial 类的Add
方法进行,主要设置了 Shader 中模板测试相关的参数,如模板测试参考值Ref
、模板测试参考值和模板缓冲值比较函数Comp
等(StencilMaterial 类是一个管理 Mask 渲染所用材质的类,它里面也提供了一系列用来更新/创建 Mask 所需材质或清除 Mask 材质缓存的方法)。
将 GetModifiedMaterial
方法串联到整个渲染中再来看看整个流程。当 Graphic 重新构建时,若需要更新材质则会在当前使用材质基础上,获取当前 UI 元素下所有的 IMaterialModifier,并调用 GetModifiedMaterial
方法修改渲染所用材质;通过这些流程,相关模板测试参数就能够被写入 Shader 中,最终参与到遮罩效果的实现过程中。
UpdateCull 方法
首先来看看代码,如下:
|
|
这个方法的作用主要就是更新当前 MaskableGraphic 所在的剔除 Cull
状态。当传入值为 true
,那么当前所在的 CanvasRender 的 cull
属性也会被设置为 true
,此时渲染时会忽略这个 CanvasRender 所在的 UI 元素(即当前 MaskableGraphic),同时相关事件回调被触发,最后调用 Graphic 类的 OnCullingChanged
方法标记当前 Graphic 重新构建(注意: 这里重新构建只有当剔除状态是 false
也就是不剔除是才会发生,因为只有不剔除才需要重新构建)。
RecalculateMasking 方法
这个方法是 IMaskable 接口中定义的,用来重置 Mask 渲染材质的一些值。首先它会从 StencilMaterial 缓存中清除先前渲染所用的材质,然后重置 m_ShouldRecalculateStencil
为 true
表明下次渲染前需要重新计算模板测试相关参数,然后调用父类 Graphic 的 SetMaterialDirty
方法标记材质需要被重新构建。
RecalculateMasking
方法在 MaskUtilities 类的 NotifyStencilStateChanged
方法中被回调,而 NotifyStencilStateChanged
方法会在 Mask/MaskableGraphic 类的 OnEnable
、OnDisable
等方法被调用,因此在 Unity 生命周期的一些回调方法中,若可以则当前 MaskableGraphic 的遮罩渲染材质相关数值会被重置,以用来在下次重新构建 Graphic 之前重新计算渲染材质。
RecalculateClipping 方法
这个方法是 IClippable 接口定义的一个方法,用来重新计算相关裁剪信息。在 MaskableGraphic 类中该方法最终调用了 UpdateClipParent
方法,下面就来看看这个方法的代码:
|
|
上面的代码中,首先调用 MaskUtilities
类的 GetRectMaskForClippable
方法获取到当前 MaskableGraphic 最合适的 RectMask2D;若需要更新当前 m_ParentMask
,则从之前的 RectMask2D 移除对自身的跟踪(调用了 RectMask2D 的 RemoveClippable
方法),紧接着调用自身的 UpdateCull
方法更新剔除 Cull
状态为 false
(重置当前 MaskableGraphic 为不需要被剔除状态);然后将自身添加到新的 RectMask2D 跟踪列表中。
RecalculateClipping
方法在 MaskUtilities 类的 Notify2DMaskStateChanged
方法中被调用,而 Notify2DMaskStateChanged
方法在 RectMask2D 的 OnEnable
、OnDisable
等方法中被调用,也就是说在 RectMask2D 的生命周期回调方法中,通过调用 Notify2DMaskStateChanged
方法通知其自身以及子元素所有的 IClippable 更新遮罩计算中裁剪需要的相关信息,这也是 RectMask2D 实现遮罩效果比较重要的一步,只有这样 RectMask2D 才能跟踪到需要实施遮罩的子元素以在后面为其计算遮罩区域。
Cull 方法
同样也是 IClippable 接口中定义的一个方法,在 MaskableGraphic 类中最终也调用 UpdateCull
方法。代码如下:
|
|
在分析上面的代码之前,来看看这个方法被执行的过程。在 Canvas 重新构建 Layout 之后,Graphic 重建之前,剔除 Cull 被至此那个(调用了 ClipperRegistry 类的 Cull
方法);剔除过程中所有的 IClipper 的 PerformClipping
方法被调用(包括 RectMask2D);在 RectMask2D 的 PerformClipping
方法中计算实现遮罩效果所要裁剪区域 clipRect 等信息后,对于 MaskableGraphic 其 Cull
方法会被调用,到这里就来到了上面的代码部分。
上面的代码中如果所要裁剪区域 clipRect 不是一个合法的矩形区域或者这个区域和当前 MaskableGraphic 所在的根 Canvas 没有重叠,那么剔除 Cull
状态为 true
,然后调用 UpdateCull
方法更新剔除状态。
SetClipRect 方法
仍然是 IClippable 接口中定义的方法,用来设置实现矩形遮罩效果时的遮罩区域。代码也很简单,就是调用当前所在 CanvasRender 的 EnableRectClipping
或 DisableRectClipping
启用或禁用矩形遮罩,如下:
|
|
这个方法的调用过程和上面分析的 Cull
方法类似,在 RectMask2D 的 PerformClipping
方法中计算实现遮罩效果所要裁剪区域 clipRect 等信息后,SetClipRect
方法就会被调用来设置裁剪区域信息;除此之外这个方法还会在 RectMask2D 的 RemoveClippable
方法中被调用,当从一个 RectMask2D 移除当前 MaskableGraphic 时,调用 SetClipRect
禁用自身裁剪。
OnEnable、OnDisable 等生命周期回调方法
对于 MaskableGraphic 会进行一些数据的重置(清空)工作。