type
status
date
summary
tags
category
icon

一、概述

本文记录的是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),当遮罩下的子元素渲染时,逐像素判断模板值是否为特定值,如果是特定值,就表示在遮罩范围内,可以显示。如果不是,则表示不在遮罩范围内不显示
notion image
绿色矩形是遮罩区域,模板值都被写入为1。当渲染横着的红色矩形时,只有模板值为1的区域才会显示,非1的会被丢弃不会显示,从而实现了裁剪效果。

三、RectMask2D

Unity官方文档中对RectMask2D的描述如下:
🔹
Unity官方文档 RectMask2D 是类似于Mask的遮罩控件,遮罩将子元素限制为父元素的矩形。与标准的Mask控件不同,这种控件有一些限制,但也有许多性能优势
RectMask2D 控件的局限性包括:
  • 仅在 2D 空间中有效
  • 不能正确掩盖不共面的元素
RectMask2D 的优势包括:
  • 不使用模板缓冲区
  • 无需额外的绘制调用
  • 无需更改材质
  • 高速性能

3.1 原理概述

RectMask2D的实现原理,可以简单分为以下几个步骤:
  1. 找出自身和父物体中所有RectMask2D覆盖区域的交集,计算这些RectMask2D所表示的矩形的交集,求出一个最终裁剪区域的重叠矩形clipRect
  1. 遍历所有的目标对象,为它们设置裁剪矩形clipRect
  1. 使用Shader实现的矩形裁剪,判断片元是否在矩形内。不在矩形内的片元透明度将被设置为0。然后通过clip将透明度小于0.001的片元丢弃掉

3.2 源码剖析

3.2.1 注册和更新UI

  1. 在裁剪模块中,UGUI定义了两个接口IClipperIClippable,分别表示裁剪对象和被裁剪目标对象。从源码中可以看到:
      • 继承了IClipper接口的只有RectMask2D
      • 继承了IClippable接口的只有MaskableGraphic(TextImageRawImage)
  1. 我们先将目光放在PerformClipping方法的调用时机上。在CanvasUpdateRegistry中,更新UI的PerformUpdate方法会在每帧执行,方法执行的流程如下:
    1. m_LayoutRebuildQueue中的元素进行排序,依据是父节点的多少。接下来依次将Prelayout、LayoutPostLayout作为参数传递给Rebuild进行布局重建,完成后通知布局队列中的元素重建完成(执行回调函数)
    2. 调用ClipperRegistry的Cull函数进行裁剪
    3. 进行图形重建,遍历m_GraphicRebuildQueue的值,分别将参数PreRender、LatePreRender作为参数传递给Rebuild函数进行图形重建,通知图形重建完成(执行回调函数)
  1. ClipperRegistry中Cull内容非常简单,对所有IClipper对象(RectMask2D)调用接口中的PerformClipping方法
    1. RectMask2D会在OnEnable中将自己注册到ClipperRegistry中

      3.2.2 裁剪

      1. 然后我们来看一下RectMask2D中PerformClipping方法的具体实现,方法执行流程分为如下:
        1. 获取自身以及父节点中的所有RectMask2D,将其添加到m_Clippers
        2. 通过m_Clippers计算矩形的交集,得到裁剪和剔除的矩形clipRect
        3. 遍历所有被裁减/被遮掩对象,通过SetClipRect设置裁剪矩形
      1. 实现裁剪的关键就在于SetClipRect了,对于MaskableGraphic,裁剪和剔除的方法是通过改变CanvasRender状态来实现,默认实现的SetClipRect方法如下所示
        1. 其中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也有可能破坏合批,在复杂的情况下,并没有确切的结论来判断哪个更优,只能利用工具实际测试找到最优者
       

      五、引用(好文推荐)

       
      如图片加载失败,可以刷新多试几次
      Unity UGUI模块 CanvasUnity UGUI模块 ObejectPool,ListPool,IndexedSet
      • Twikoo
      • Cusdis