一、什么是模板测试
Unity官方文档:Unity - Manual: ShaderLab command: Stencil
一个很经典的模板测试例子就是 UI Mask:
二、Unity 中的模板测试
SubShader
{
Tags { …… }
Pass
{
Stencil
{
//可选
Ref referenceValue
ReadMask readMask
WriteMask writeMask
Comp comparisonFunction
Pass stencilOperation
Fail stencilOperation
ZFail stencilOperation
}
CGPROGRAM
//……
CGEND
}
}
1):Ref
- 对应 OpenGL 中 glStencilFunc(GLenum func, GLint ref, GLuint mask) 第二个参数 GLint ref
- 范围 [0, 255]
指定模板测试的引用值,模板缓冲的内容会与这个值对比,模板缓冲默认值当然为0
2):ReadMask
- 对应 OpenGL 中 glStencilFunc(GLenum func, GLint ref, GLuint mask) 第三个参数 GLuint mask
- 范围 [0, 255],默认值 255
读遮罩,在模板测试对比引用值和储存的模板值前,对它们进行按位与操作
3):WriteMask
- 对应 OpenGL 中 glStencilMask(GLint 0~255)
- 范围 [0, 255],默认值 255
写遮罩,再写入模板缓冲时,对它们进行按位与操作(如果对应位为1,则对应位可写入缓存,如果对应位为0,则对应位不可写入缓存)
4):Comp
- 对应 OpenGL 中 glStencilFunc(GLenum func, GLint ref, GLuint mask) 第一个参数 GLenum func
- 枚举,默认值 Always
模板测试规则,也就是参考值和缓冲值比较规则
- Always:永远通过测试
- Never:永远不通过测试
- Less:在片段模板值小于缓冲的模板值时通过测试
- Equal:在片段模板值等于缓冲区的模板值时通过测试
- LEqual:在片段模板值小于等于缓冲区的模板值时通过测试
- Greater:在片段模板值大于缓冲区的模板值时通过测试
- NotEqual:在片段模板值不等于缓冲区的模板值时通过测试
- GEqual:在片段模板值大于等于缓冲区的模板值时通过测试
5):Pass
- 对应 OpenGL 中 glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass) 第三个参数 GLenum dppass
- 枚举,默认值 Keep
当模板测试和深度测试都通过时,模板缓冲的值处理方式
- Keep:保持现有的模板值
- Zero:将模板值置为0
- Replace:替换,将模板值设置为 Ref 值
- IncrSat:如果模板值不是最大值就将模板值+1
- IncrWrap:与 IncrSat 一样将模板值+1,如果模板值已经是最大值则设为0
- DecrSat:如果模板值不是最小值就将模板值-1
- DecrWrap:与 DecrSat 一样将模板值-1,如果模板值已经是最小值则设为最大值
- Invert:将模板值按位反转
6):Fail
- 对应 OpenGL 中 glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass) 第三个参数 GLenum sfail
- 枚举,默认值 Keep
当模板测试不通过时,模板缓冲的值处理方式
7):ZFail
- 对应 OpenGL 中 glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass) 第二个参数 GLenum dpfail
- 枚举,默认值 Keep
当模板测试通过、深度测试未通过时,模板缓冲的值处理方式
三、关于模板缓冲区
若 Shader 中应用了模板测试,那么对于每个像素,测试的过程如下:
- StencilBufferValue:模板缓冲区当前模板值
(Ref & ReadMask) Comp (StencilBufferValue & ReadMask)
模板缓冲区的大小为固定的8位,这也是为什么 Ref 的值范围为 [0, 255],除此之外,若纹理缓冲设置为 Depth24Stencil8,那么8位模板值就会存在于 Zbuffer 中
在 Unity 屏幕后处理中使用 Stencil:
出乎意料之外:直接调用 Blit 无法实现模板测试效果,因为其无法识别深度模板缓冲,但是还好,如果是 Unity 默认的 Build-in 管线,可以使用下面的代码解决:
void OnRenderImage(RenderTexture src, RenderTexture dest)
{
//…… material为你自己的材质
RenderTexture buffer = RenderTexture.GetTemporary(src.width, src.height, 24);
Graphics.SetRenderTarget(buffer.colorBuffer, src.depthBuffer);
Graphics.Blit(src, buffer, material);
Graphics.Blit(buffer, dest);
RenderTexture.ReleaseTemporary(buffer);
}
原理是通过设置 SetRenderTarget() 让 Graphics.Blit 调用的时候能够获取到深度图信息,注意中间纹理的 depthBuffer 必须要为 24,这样才可以从中拿到模板信息
搞定之后就可以进行测试:
Subshader
{
ZTest Always Cull Off ZWrite Off
Pass
{
Stencil
{
Ref 1
Comp Equal
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
如果黑屏了那就是没问题!所有的像素都没有通过模板测试
参考资料: