Mask实现原理(兼模板测试小结)

前言

虽然说网上已经有不少优秀的总结,但为了让知识停留在脑海里,我还是决定自己总结一份笔记。


大概思路

  1. Mask会修改Graph组件的材质为StencilMaterial,该材质的作用是给每个不透明的像素标记,将标记结果存入模板缓冲区中。
  2. 当子级UI进行模板测试时,如果通过就渲染,没通过就不渲染。

模板测试是怎么一回事

既然遮罩跟模板测试有关,我们就要了解下模板测试是怎么一回事。
模板缓冲区跟颜色缓冲区和深度缓冲区类似,模板缓冲区可以为“屏幕”上每个像素保存一个无符号整数(通常是8位)。在渲染的过程中,可以用一个程序设定的参考值和这个值进行比较,根据比较结果来决定是否更新相应像素的颜色值,这个比较过程就算模板测试。
image
从图中可以看到,模板测试通常发生在alpha测试之后,深度测试之前。模板测试可以指定应用场景,通常应用于Cull Back的几何体。除非Cull Front被指定,在这种情况下应用于正面消隐藏的几何体。当然也可以双面都指定。

模板测试语法
stencil
{
	Ref referenceValue
	ReadMask  readMask
	WriteMask writeMask
	Comp comparisonFunction
	Pass stencilOperation
	Fail stencilOperation
	ZFail stencilOperation
}
Ref

用来设定参考值,这个值用来与模板缓冲区中的值作比较,取值范围0-255。

ReadMask

ref值和stencilBufferValue值做比较前,ReadMask会分别和它们做次按位与(&)运算,ReadMask取值范围0-255,默认为11111111。

WriteMask

对写入模板缓冲区的值做次按位与(&)运算,WriteMask取值范围0-255,默认为11111111。

Comp

定义 参考值(RefValue)和缓冲值(stencilBufferValue) 比较的操作函数,默认:always。

Greater 相当于“>”操作,即仅当左边>右边,模板测试通过,渲染像素
GEqual 相当于“>=”操作,即仅当左边>=右边,模板测试通过,渲染像素
Less 相当于“<”操作,即仅当左边<右边,模板测试通过,渲染像素
LEqual 相当于“<=”操作,即仅当左边<=右边,模板测试通过,渲染像素
Equal 相当于“=”操作,即仅当左边=右边,模板测试通过,渲染像素
NotEqual 相当于“!=”操作,即仅当左边!=右边,模板测试通过,渲染像素
Always 不管公式两边为何值,模板测试总是通过,渲染像素
Never 不管公式两边为何值,模板测试总是失败 ,像素被抛弃
Pass

定义 当模板测试(和深度测试)通过时,则根据ref值(stencilOperation)对stencilBufferValue(模板缓冲区值)进行处理,默认:keep

Fail

定义 当模板测试(和深度测试)失败时,则根据ref值(stencilOperation)对stencilBufferValue(模板缓冲区值)进行处理,默认:keep

ZFail

定义 当模板测试通过,深度测试失败时,则根据ref值(stencilOperation)对stencilBufferValue值(模板缓冲区值)进行处理,默认:keep

Keep 保留当前缓冲中的内容,即stencilBufferValue不变
Zero 将0写入缓冲,即stencilBufferValue值变为0
Replace 将参考值写入缓冲,即将ref值赋给stencilBufferValue
IncrSat stencilBufferValue加1,如果stencilBufferValue超过255了,那么保留为255,即不大于255
DecrSat stencilBufferValue减1,如果stencilBufferValue超过为0,那么保留为0,即不小于0
Invert 将当前模板缓冲值(stencilBufferValue)按位取反
IncrWrap 当前缓冲的值加1,如果缓冲值超过255了,那么变成0
DecrWrap 当前缓冲的值减1,如果缓冲值已经为0,那么变成255
模板测试公式
if(referenceValue & readMask comparisonFunction stencilBufferValue & readMask)
{
	通过像素
}
else
{
	抛弃像素
}
例子
// 第一个Pass
ColorMask 0			// 不输出颜色到屏幕
ZWrite Off			// 关闭深度写入,避免将后面的像素剔除
stencil{
	Ref 1			// 将ref值定义为1
	Comp always		// 不管stencilBufferValue值为多少,模板测试都通过
	Pass replace		// 将参考值2写入模板缓冲区当中
}

// 第二个Pass

Stencil {
      Ref 1			// 将ref值定义为1
      Comp Equal		// ref值需要和stencilBufferValue值相同,才能通过测试,渲染到屏幕上
}

image
屏幕中所有像素,绿色方框内的模板缓冲区值都是2,后面人物模型用第二个Pass的Shader,只有模板缓冲区为1的像素点才能渲染到屏幕上。

Mask是怎么做到遮挡的

了解完模板测试原理,我们来看下Unity内Mask是怎么做到遮挡的,在场景中添加2个Image,Image-1是Image-2的父节点,其中Image-1添加Mask组件。
image
我们可以看到给Image-1添加了Mask组件,同时修改了Image-1和Image-2的材质为。
image
Image1开启了模板测试,Op为Always,Comp为Replace,意思是不管怎样都将1写入到模板缓冲区当中。
Image2同样开启了模板测试,但它没有开启写入,Op为Keep,保持原状;Comp为Equeal,WriteMask为1,ref值为1,意思是仅有模板缓冲区当中值为1的像素点才能被显示在屏幕上。

代码添加

Mask.GetModifiedMaterial

        /// Stencil calculation time!
        public virtual Material GetModifiedMaterial(Material baseMaterial)
        {
            if (!MaskEnabled())
                return baseMaterial;

            var rootSortCanvas = MaskUtilities.FindRootSortOverrideCanvas(transform);
            var stencilDepth = MaskUtilities.GetStencilDepth(transform, rootSortCanvas);
            if (stencilDepth >= 8)
            {
                Debug.LogWarning("Attempting to use a stencil mask with depth > 8", gameObject);
                return baseMaterial;
            }

            int desiredStencilBit = 1 << stencilDepth;

            // if we are at the first level...
            // we want to destroy what is there
            if (desiredStencilBit == 1)
            {
                var maskMaterial = StencilMaterial.Add(baseMaterial, 1, StencilOp.Replace, CompareFunction.Always, m_ShowMaskGraphic ? ColorWriteMask.All : 0);
                StencilMaterial.Remove(m_MaskMaterial);
                m_MaskMaterial = maskMaterial;

                var unmaskMaterial = StencilMaterial.Add(baseMaterial, 1, StencilOp.Zero, CompareFunction.Always, 0);
                StencilMaterial.Remove(m_UnmaskMaterial);
                m_UnmaskMaterial = unmaskMaterial;
                graphic.canvasRenderer.popMaterialCount = 1;
                graphic.canvasRenderer.SetPopMaterial(m_UnmaskMaterial, 0);

                return m_MaskMaterial;
            }

            //otherwise we need to be a bit smarter and set some read / write masks
            var maskMaterial2 = StencilMaterial.Add(baseMaterial, desiredStencilBit | (desiredStencilBit - 1), StencilOp.Replace, CompareFunction.Equal, m_ShowMaskGraphic ? ColorWriteMask.All : 0, desiredStencilBit - 1, desiredStencilBit | (desiredStencilBit - 1));
            StencilMaterial.Remove(m_MaskMaterial);
            m_MaskMaterial = maskMaterial2;

            graphic.canvasRenderer.hasPopInstruction = true;
            var unmaskMaterial2 = StencilMaterial.Add(baseMaterial, desiredStencilBit - 1, StencilOp.Replace, CompareFunction.Equal, 0, desiredStencilBit - 1, desiredStencilBit | (desiredStencilBit - 1));
            StencilMaterial.Remove(m_UnmaskMaterial);
            m_UnmaskMaterial = unmaskMaterial2;
            graphic.canvasRenderer.popMaterialCount = 1;
            graphic.canvasRenderer.SetPopMaterial(m_UnmaskMaterial, 0);

            return m_MaskMaterial;
        }

其中

参考资料

https://blog.csdn.net/u011047171/article/details/46928463
https://blog.csdn.net/u013477973/article/details/89343777
https://www.cnblogs.com/j349900963/p/8340571.html

posted @ 2024-04-12 21:29  陈侠云  阅读(414)  评论(0)    收藏  举报
//雪花飘落效果