【UGUI源码分析】Mask的底层原理

Masking is implemented using the stencil buffer of the GPU.

Mask是通过GPU的模板缓冲实现的,关键代码只有一行,它的作用是为Mask对象生成一个特殊的材质,这个材质会将StencilBuffer的值置为1。

1
var maskMaterial = StencilMaterial.Add(baseMaterial, 1, StencilOp.Replace, CompareFunction.Always);

同样,在Image,Text和RawImage的基类 MaskableGraphic 中,有一行代码,它的作用是为MaskableGraphic生成一个特殊的材质,这个材质在渲染时会取出StencilBuffer的值,判断是否为1,如果是才进行渲染。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public virtual Material GetModifiedMaterial(Material baseMaterial){
    var toUse = baseMaterial;
 
    if (m_ShouldRecalculateStencil)
    {
        var rootCanvas = MaskUtilities.FindRootSortOverrideCanvas(transform);
        m_StencilValue = maskable ? MaskUtilities.GetStencilDepth(transform, rootCanvas) : 0;
        m_ShouldRecalculateStencil = false;
    }
 
    // if we have a enabled Mask component then it will
    // generate the mask material. This is an optimization
    // it adds some coupling between components though :(
    Mask maskComponent = GetComponent<Mask>();
    if (m_StencilValue > 0 && (maskComponent == null || !maskComponent.IsActive()))
    {
        // 核心代码是这一句
        var maskMat = StencilMaterial.Add(toUse, (1 << m_StencilValue) - 1, StencilOp.Keep, CompareFunction.Equal, ColorWriteMask.All, (1 << m_StencilValue) - 1, 0);
        StencilMaterial.Remove(m_MaskMaterial);
        m_MaskMaterial = maskMat;
        toUse = m_MaskMaterial;
    }
    return toUse;
}  

理解了原理,来看一下stencil test里的语法

1
2
3
4
5
6
7
8
9
Stencil
{
    Ref [Value] //表示要比较的值
    Comp [CompFunction] // 表示比较方法(等于/不等于/大于/小于等
    Pass [PassOp] // 表示当比较通过时对stencil buffer做什么操作(保留/替换/置0/增加/减少等
    Fail [FailOp] // 对buffer的操作,如果比较失败的话
    ReadMask [Value] // 表示取stencil buffer的值时用的mask
    WriteMask [Value]
}

简单的来说,是将stencil buffer的值与ReadMask与运算,然后与Ref值进行Comp比较,结果为true时进行Pass操作,否则进行Fail操作,操作值写入stencil buffer前先与WriteMask与运算。

我们再来看下前面的StencilMaterial.Add()的方法,从源代码里可以看出,它主要是帮助我们生成了一个材质并填充了Stencil相关的参数,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static Material Add(Material baseMat, int stencilID, StencilOp operation, CompareFunction compareFunction, ColorWriteMask colorWriteMask, int readMask, int writeMask)
{
    var newEnt = new MatEntry();
    newEnt.count = 1;
    newEnt.baseMat = baseMat;
    newEnt.customMat = new Material(baseMat);
 
    // 省略
 
    newEnt.customMat.SetInt("_Stencil", stencilID);
    newEnt.customMat.SetInt("_StencilOp", (int)operation);
    newEnt.customMat.SetInt("_StencilComp", (int)compareFunction);
    newEnt.customMat.SetInt("_StencilReadMask", readMask);
    newEnt.customMat.SetInt("_StencilWriteMask", writeMask);
    newEnt.customMat.SetInt("_ColorMask", (int)colorWriteMask);
    newEnt.customMat.SetInt("_UseAlphaClip", newEnt.useAlphaClip ? 1 : 0);
    m_List.Add(newEnt);
    return newEnt.customMat;
}

事实上我们可以用通过修改stencil buffer的属性来制造自定义材质,比如把上面例子中的CompareFunction.Equal 改为 CompareFunction.NotEqual 可实现遮罩的反效果,挖孔效果。

整个mask的实现流程可以看下这个flowchart,算是比较清楚了

 

 

这个博主总结的很不错,来自https://blog.csdn.net/NRatel/article/details/118252513

这里总结一下mask的实现原理:

  • mask会给image一个特殊的材质,这个材质会给image的每个像素点进行标记,将标记的结果放在一个缓存里(stencil buffer)
  • 当子级UI进行渲染的时候会去检查这个stencil buffer内的标记,如果当前覆盖的区域存在标记,就会进行渲染,otherwise不渲染

 

Reference:

  1. https://rugbbyli.gitlab.io/blog/post/2017-12-07-unity-stencil/
  2. https://www.daimajiaoliu.com/daima/479aedb9d900403
  3. https://blog.csdn.net/NRatel/article/details/118252513

 


__EOF__

本文作者cancantrbl
本文链接https://www.cnblogs.com/cancantrbl/p/16080175.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   cancantrbl  阅读(437)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示