type
status
date
summary
tags
category
icon
一、概述二、Mask2.1 原理概述模板缓冲(Stencil Buffer)三、RectMask2D3.1 原理概述3.2 源码剖析3.2.1 注册和更新UI3.2.2 裁剪四、区别和对比4.1 使用场景对比4.2 性能比较五、引用(好文推荐)
一、概述
本文记录的是UGUI模块中的裁剪模块:Mask和RectMask2D的一些实现细节以及区别,展示的Unity源码版本基于2019.1,链接地址如下:
二、Mask
Unity官方文档中对Mask的描述如下:
Unity官方文档
遮罩(Mask)不是可见的 UI 控件,而是一种修改控件子元素外观的方法。遮罩将子元素限制(即“掩盖”)为父元素的形状。因此,如果子项比父项大,则子项仅包含在父项以内的部分才可见
2.1 原理概述
Mask是利用Graphic渲染时修改对应片元的模板值来确定遮罩的大小与形状,Graphic的形状决定了Mask遮罩的形状。因此缺少Graphic组件,Mask遮罩将会失效。
Graphic类是UGUI中所有可显示图形的基类,例如Image和Text就是间接继承于Graphic。
模板缓冲(Stencil Buffer)
Mask实现遮挡效果是通过使用GPU的模板缓冲区来实现的。在渲染管线最后的逐片元操作阶段,会有一个模板测试,决定了特定位置的像素是否需要被渲染,这个过程是通过模板缓冲实现的。
简单来说,Mask在渲染时会将其大小对应位置的像素的模板值都设置为特定值(不一定是1),当遮罩下的子元素渲染时,逐像素判断模板值是否为特定值,如果是特定值,就表示在遮罩范围内,可以显示。如果不是,则表示不在遮罩范围内不显示
绿色矩形是遮罩区域,模板值都被写入为1。当渲染横着的红色矩形时,只有模板值为1的区域才会显示,非1的会被丢弃不会显示,从而实现了裁剪效果。
三、RectMask2D
Unity官方文档中对RectMask2D的描述如下:
Unity官方文档
RectMask2D 是类似于Mask的遮罩控件,遮罩将子元素限制为父元素的矩形。与标准的Mask控件不同,这种控件有一些限制,但也有许多性能优势
RectMask2D 控件的局限性包括:
- 仅在 2D 空间中有效
- 不能正确掩盖不共面的元素
RectMask2D 的优势包括:
- 不使用模板缓冲区
- 无需额外的绘制调用
- 无需更改材质
- 高速性能
3.1 原理概述
RectMask2D的实现原理,可以简单分为以下几个步骤:
- 找出自身和父物体中所有RectMask2D覆盖区域的交集,计算这些RectMask2D所表示的矩形的交集,求出一个最终裁剪区域的重叠矩形clipRect
- 遍历所有的目标对象,为它们设置裁剪矩形clipRect
- 使用Shader实现的矩形裁剪,判断片元是否在矩形内。不在矩形内的片元透明度将被设置为0。然后通过clip将透明度小于0.001的片元丢弃掉
3.2 源码剖析
3.2.1 注册和更新UI
- 在裁剪模块中,UGUI定义了两个接口
IClipper
和IClippable
,分别表示裁剪对象和被裁剪目标对象。从源码中可以看到: - 继承了IClipper接口的只有RectMask2D
- 继承了IClippable接口的只有MaskableGraphic(Text,Image,RawImage)
- 我们先将目光放在
PerformClipping
方法的调用时机上。在CanvasUpdateRegistry中,更新UI的PerformUpdate
方法会在每帧执行,方法执行的流程如下: - 对m_LayoutRebuildQueue中的元素进行排序,依据是父节点的多少。接下来依次将Prelayout、Layout和PostLayout作为参数传递给
Rebuild
进行布局重建,完成后通知布局队列中的元素重建完成(执行回调函数) - 调用ClipperRegistry的
Cull
函数进行裁剪 - 进行图形重建,遍历m_GraphicRebuildQueue的值,分别将参数PreRender、LatePreRender作为参数传递给
Rebuild
函数进行图形重建,通知图形重建完成(执行回调函数)
- ClipperRegistry中
Cull
内容非常简单,对所有IClipper对象(RectMask2D)调用接口中的PerformClipping
方法
- RectMask2D会在
OnEnable
中将自己注册到ClipperRegistry中
3.2.2 裁剪
- 然后我们来看一下RectMask2D中
PerformClipping
方法的具体实现,方法执行流程分为如下: - 获取自身以及父节点中的所有RectMask2D,将其添加到m_Clippers中
- 通过m_Clippers计算矩形的交集,得到裁剪和剔除的矩形clipRect
- 遍历所有被裁减/被遮掩对象,通过
SetClipRect
设置裁剪矩形
- 实现裁剪的关键就在于
SetClipRect
了,对于MaskableGraphic,裁剪和剔除的方法是通过改变CanvasRender状态来实现,默认实现的SetClipRect
方法如下所示
其中canvasRender是挂在被裁减对象上的CanvasRender组件。根据UnityAPI文档可知,
EnableRectClipping
的作用是启用矩形裁剪。将对位于指定矩形外的几何形状进行裁剪(不渲染),DisableRectClipping
对应的就是禁用该裁剪。但由于Unity并未将CanvasRender开源,所以其内部实现无从知晓。通过查阅资料,得知
SetClipRect
是通过Shader实现的矩形裁剪。查看UI默认使用的Shader是UI/Default,这是Unity的内置Shader。在Shader代码中可以看到,clipRect矩形会被作为参数传入,不在矩形内的图元透明度会被设为0,并在clip
裁剪方法中被丢弃,这样便达到了裁剪的目的。四、区别和对比
4.1 使用场景对比
- Mask遮罩的大小与形状依赖于Graphic,而RectMask2D只需要依赖RectTransform
- Mask支持圆形或其他形状遮罩, 而RectMask2D只支持矩形
4.2 性能比较
Mask的实现利用了模板缓冲区,会增加2个drawcall(一个用来在绘制元素前获取一个材质修改模板缓冲的值,另一个用来在所有UI绘制完后将模板缓冲的值恢复原样)性能会受到一定影响。
简单的UGUI界面,还是建议使用RectMask2D,相对来说性能更强,也无需额外的绘制调用。但由于RectMask2D也有可能破坏合批,在复杂的情况下,并没有确切的结论来判断哪个更优,只能利用工具实际测试找到最优者
五、引用(好文推荐)
如图片加载失败,可以刷新多试几次
- 作者:Felix
- 链接:felix1125.com/article/450c2365-edb3-4387-b0e0-139f24fc1979
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章