前言
虽然说网上已经有不少优秀的总结,但为了让知识停留在脑海里,我还是决定自己总结一份笔记。
大概思路
- Mask会修改Graph组件的材质为StencilMaterial,该材质的作用是给每个不透明的像素标记,将标记结果存入模板缓冲区中。
- 当子级UI进行模板测试时,如果通过就渲染,没通过就不渲染。
模板测试是怎么一回事
既然遮罩跟模板测试有关,我们就要了解下模板测试是怎么一回事。
模板缓冲区跟颜色缓冲区和深度缓冲区类似,模板缓冲区可以为“屏幕”上每个像素保存一个无符号整数(通常是8位)。在渲染的过程中,可以用一个程序设定的参考值和这个值进行比较,根据比较结果来决定是否更新相应像素的颜色值,这个比较过程就算模板测试。
从图中可以看到,模板测试通常发生在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值相同,才能通过测试,渲染到屏幕上
}
屏幕中所有像素,绿色方框内的模板缓冲区值都是2,后面人物模型用第二个Pass的Shader,只有模板缓冲区为1的像素点才能渲染到屏幕上。
Mask是怎么做到遮挡的
了解完模板测试原理,我们来看下Unity内Mask是怎么做到遮挡的,在场景中添加2个Image,Image-1是Image-2的父节点,其中Image-1添加Mask组件。
我们可以看到给Image-1添加了Mask组件,同时修改了Image-1和Image-2的材质为。
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