首页 > UI, 其他 > 【UGUI源码分析】六、MaskableGraphic、遮罩、裁剪和剔除

【UGUI源码分析】六、MaskableGraphic、遮罩、裁剪和剔除

2019年1月11日
目录
[隐藏]

类: MaskableGraphic, Mask, RectMask2D, ClipperRegistry, CanvasRenderer
辅助类:Clipping, MaskUtilities, StencilMaterial
接口: IMaskable, IClippable, IClipper

本文总结UGUI的遮罩(mask)、裁剪(clipping)和剔除(culling),对应的组件分别是Mask和RectMask2D,原理不一样,前者是用模版测试,后者是用裁剪和剔除。

遮罩、剔除和裁剪UML图

遮罩通过设置材质实现,剔除和裁剪通过设置canvasRenderer实现。

1. 剔除和裁剪

之前Canvas的文章里写了CanvasUpdateRegistry.PerformUpdate()时,会先进行布局,然后会进行剔除和裁剪,最后更新网格和材质数据。通过调用 ClipperRegistry.instance.Cull(); 来进行剔除和裁剪。

ClipperRegistry是一个注册表,保存了场景内所有的IClipper对象(具体就是RectMask2D),在Cull方法里会遍历所有的IClipper, 并调用IClipper的PerformClipping方法

RectMask2D实现了IClipper接口,会处理剔除和裁剪。RectMask2D的数据主要包括

MaskableGraphic在合适的时机注册到 m_ClipTargets 里,而且只会注册到最近的包含RectMask2D祖先节点里。做裁剪和剔除前会收集 m_Clippers

PerformClipping()方法里面主要做的内容是

  1. 调用MaskUtilities.GetRectMasksForClip收集这个RectMask2D以及父节点中满足一定条件(rectMask2D最近的没有OverrideSorting的父canvas节点的RectMask2D子节点)的RectMask2D
  2. 计算这些RectMask2D矩形的交集,得到裁剪和剔除的矩形 clipRect
  3. 调用IClippable的SetClipRect方法裁剪
  4. 调用IClippable的Cull方法剔除

IClippable即MaskableGraphic具体的裁剪和剔除方法是通过设置canvasRenderer状态。

剔除通过设置canvasRenderer.cull = true ,剔除后的Graphic将没有网格,不会进入渲染管线,而遮罩还是有网格,会进行渲染,只是FS之后、写入FB之前进行一次模板测试。

裁剪是通过设置canvasRenderer.EnableRectClipping(clipRect) ,这个矩形最后会传到shader里作为参数_ClipRect,矩形外面的部分透明度是0

如果开启了UNITY_UI_ALPHACLIP关键字,会做一次alpha测试。

红色-RectMask2D 黑色-网格

所以说UGUI的裁剪(clipping)并不是生成新的网格,而是做alpha test也就是调用clip()函数。

2. 遮罩

遮罩(Mask)的实现和裁剪剔除不同,是通过模板测试实现。

一个mask节点的设置

遮罩的思路是

  1. 先渲染Mask,写入模板值
  2. 渲染Graphic,设置合适的参数做模板测试
  3. 再渲染一次Mask,重置模板值

例子

1) Mask

Mask的主要成员和方法

在GetModifiedMaterial方法中,会构建m_MaskMaterial和m_UnMaskMaterial(设置模板测试参数),其中m_MaskMaterial会作为返回值用来设置canvasRenderer.SetMaterail,而m_UnMaskMaterail会通过canvasRenderer.SetPopMaterial设置进mask graphic的canvasRenderer。

Unity在渲染canvasRenderer的时候会维护一个渲染栈(render stack)的概念,因为canvas下graphic的渲染是按照hierachy顺序,父节点压入栈渲染,如果有子节点将子节点压入栈渲染,否则弹出栈,子节点同样。SetPopMaterail就是在弹出栈的时候进行一次(可以多次)额外的渲染,在Mask这里是为了重置stencil buffer。

When rendering using the hierarchy the renderer can insert a 'pop'. The pop instruction is executed after all children have been rendered. The canvas renderer is rerendered using the configured pop materials.

一个渲染栈的例子

与设置渲染栈相关的方法有

在Mask的GetModifiedMaterial方法里,会先计算出一个stencilDepth,这个mask graphic在同一个canvas的mask中的层级深度,最上层的mask stencilDepth是0,最多可以嵌套到7,即8层,当超过8层时会报一个错。对应模板的8个位(每个像素的模板缓冲值是1个字节存储),UGUI用模板值的每个位标记每一层。

8 bits stencil buffer

模板测试原理:

if Func(Ref & ReadMask, STENCIL_BUFFER & ReadMask) then
 Op(Ref & WriteMask)

Mask push时材质设置的模板参数

Mask pop时材质的模板参数,

其中desiredStencilBit = 1 << stencilDepth,desiredStencilBit-1即这一位之前所有位为1。在渲染mask的时候会依次从低到高将模板值置为1,pop时会从高到低依次置为0。具体的算法参见Mask.GetModifierMaterail()方法。

渲染Mask Graphic时UI-Default Shader会开启UNITY_UI_ALPHACLIP做一次alpha测试,丢弃透明像素,不透明的像素会更新模板值

2) MaskableGraphic

上面是Mask遮罩的渲染,MaskableGraphic本身也实现了IMaterailModifier接口,用来针对遮罩做材质替换,如果检测到父节点中有Mask,就替换为包含模板测试的材质。MaskableGraphic渲染时的模板参数设置是

其中stencilValue是这个Graphic在masks下的深度,(1<<stencilValue)-1就是它的所有父mask标志位都为1,只有这些mask的交集最终在模板缓冲里的位才会全部是1。这样就实现了嵌套,graphic的每个父mask都是一次模板位写入。

3) StencilMaterial

UGUI使用一个StencilMaterail静态类作为Mask材质的缓存池。因为并不是每次IMaterailModifier调用的时候都意味着要更新模板值,回想Graphic的SetXXXDirty()触发时机。所以需要对材质做一个缓存

如果缓存池中有符合模板参数的材质,直接从池子里拿,用一个引用计数维护材质的销毁

3. 自定义和扩展

1) 派生MaskableGraphic

继承MaskableGraphic能让graphic支持剔除、裁剪和遮罩。Image、RawImage、Text都是派生自MaskableGraphic。

2) 实现IMaterailModifier接口

MaskableGraphic实现了IMaterailModifier接口,这是一个graphic本身去实现IMaterailModifier接口的粒子,而不一定要用一个单独的UIEffect组件去实现这个接口。

3) 自定义Pop Materail

Mask提供了一个设置pop materail的范例,通过实现IMaterailModifier接口,在GetModifiedMaterail方法内调用canvasRenderer.SetPopMaterial方法设置pop materail。Pop materail的本质是父节点在子节点渲染完后增加一次额外渲染,可以用来实现一种UI层面的“后处理”效果,而不局限于Mask这里的应用。比如在子节点渲染完后做一次color shift、tone mapping等等,非常有用。

 

分类: UI, 其他 标签: