【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:
- https://rugbbyli.gitlab.io/blog/post/2017-12-07-unity-stencil/
- https://www.daimajiaoliu.com/daima/479aedb9d900403
- https://blog.csdn.net/NRatel/article/details/118252513
__EOF__

本文链接:https://www.cnblogs.com/cancantrbl/p/16080175.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律