UnityShader 模板测试(Stencil)
写在开头
最近因为项目上的需要,作为一个小白只能硬着头皮研究了unityshader模板测试的相关资料。出于记录和分享的目的,也为了对这些天的研究做个总结方便后面需要的时候查看便写了本篇文章。本人技术粗浅,眼界有限如果有说的不对的地方还是希望能得到大家的指正和帮助。
什么是模板测试
对于我们的三维场景显示在电脑屏幕上的过程,我们可以想象为是一个画家在画布上画画,画家按照顺序在画布上一层层的画上新的图案,最后组合成一幅画。如果我们想要在一张白纸上画出黑夜和月亮,那我们是不是可以找一个瓶盖放在画布上然后在涂黑整个画布后取走它,这样我们就初步的得到一个画面。那个瓶盖子就可以认为是我们说的模板。回归到unity中,电脑屏幕就是我们的画布,unity的 Stencil buffer 就是我们的模板,我们可以对一个shader设置它的模板测试方法,以此来决定一个电脑屏幕上的一个像素点的颜色是不是该被新的颜色所覆盖。
Stencil buffer是个啥
我理解的模板缓冲区(Stencil buffer)类似于是一个byte型的二维数组,数组中的每个值都对应着屏幕上的一个像素。在默认的情况下这个数组中的每个值都是0。
如何使用模板测试的方式实现一个遮罩效果
如果我们想要一个物体A只有在被另一个特定物体B遮挡住时才能显示出来的效果。上文我们有提到模板缓冲区,默认的模板缓冲区值都为0,如果我们把可以显示B的区域所对应的值都设为2,然后在绘制A的时候对每个像素点对于的模板缓冲区的值进行判断如果等于2,就显示该像素点的颜色否则就不绘制该像素点,这样是不是就实现了我们想要的效果了。按照我们的思路接下来我们只需要解决两个问题。
1.创建一个模板
unity给我们做了很好的封装,模板缓冲区是一直存在的,我只需要在shader中加入特定的语句就可以开启模板测试并对模板缓冲区的数据进行修改。加入下面的代码段就表示对于使用本shader的片元函数渲染出的像素点对应的模板值为2。
Stencil
{
Ref 2
Comp Always
Pass Replace
}
2.和模板进行比较
上一步在物体B渲染结束后,B所在的像素点对应的模板值为2,我们需要在渲染A的时候比较A所在的某个像素点的模板值是不是等于2,如果等于2shader会执行相应的渲染操作,将A渲染在屏幕上。
Stencil
{
Ref 2
Comp Equal
Pass Kepp
}
示例代码
第一步绘制出B的遮罩模板
Shader "Mask" {
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry"}
Pass {
ZWrite off
ColorMask 0 //不显示用于遮罩的物体B
Stencil {
Ref 2
Comp always //模板测试总是通过
Pass replace //将对应位置的模板值替换为 ref的值
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
struct appdata {
float4 vertex : POSITION;
};
struct v2f {
float4 pos : SV_POSITION;
};
v2f vert(appdata v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
half4 frag(v2f i) : SV_Target {
return half4(1,0,0,1);
}
ENDCG
}
}
}
第二步,和模板进行比较当模板中的值和当前值一致时,渲染物体A。
Shader "Green" {
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry+1"} //控制渲染队列 保证在模板绘制后才绘制物体A
Pass {
Stencil {
Ref 2
Comp equal //模板测试 当ref的值和buffer中的值相等时通过模板测试
Pass keep //不修改模板值
ZFail decrWrap
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
struct appdata {
float4 vertex : POSITION;
};
struct v2f {
float4 pos : SV_POSITION;
};
v2f vert(appdata v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
half4 frag(v2f i) : SV_Target {
return half4(0,1,0,1);
}
ENDCG
}
}
}
一定要注意的是,需要确保模板是先绘制出来的。后面的模板测试需要现有模板才能保证效果和预期的一致。
效果图
Stencil 配置说明
Ref
Ref 2
要比较的值或要写入缓冲区的值。取值范围是 0 - 255整数。
ReadMask
ReadMask 255
一个0-255整数的8位掩码,用于在比较引用值和缓冲区内容时先使用该掩码对 引用值(ref)和模板中的值进行 按位& 的操作后再进行比较。 默认值:255。
WriteMask
WriteMask 255
一个0-255整数的8位掩码,在写入缓冲区时使用。它指定了模板缓冲区的哪些位将受到写操作的影响(例如,WriteMask 0表示没有位将受到影响,而不是0将被写入)。默认值:255。
Comp
Comp always
用于将引用值与缓冲区的当前内容进行比较的函数。默认值:always。比较函数是以下之一:
value | 说明 |
---|---|
Greater | 仅渲染参考值大于缓冲区中的值的像素。 |
GEqual | 仅呈现参考值大于或等于缓冲区中的值的像素。 |
Less | 仅渲染参考值小于缓冲区中的值的像素。 |
LEqual | 仅呈现参考值小于或等于缓冲区中的值的像素。 |
Equal | 仅呈现参考值等于缓冲区中的值的像素。 |
NotEqual | 仅渲染参考值不等于缓冲区中的值的像素。 |
Always | 模板测试始终通过。 |
Never | 模板测试始终不通过。 |
Pass ,Fail , ZFail
Pass keep
定义当模板测试(和深度测试)通过时,该如何处理缓冲区的内容。默认值:keep。
Fail keep
定义当模板测试失败时,该如何处理缓冲区的内容。默认值:keep。
ZFail keep
定义当模板测试通过,但深度测试失败时,该如何处理缓冲区的内容。默认值:keep。
Comp, Pass, Fail和ZFail将应用到正面的几何图形,除非Cull Front被指定,在这种情况下,它是背面的几何图形。您还可以通过定义CompFront、PassFront、FailFront、ZFailFront(用于正面几何图形)和CompBack、PassBack、FailBack、ZFailBack(用于背面几何图形)来显式地指定双面显示。
value | 说明 |
---|---|
Keep | 保持缓冲区的当前内容。 |
Zero | 将0写入缓冲区。 |
Replace | 将引用值(ref)写入缓冲区。 |
IncrSat | 递增缓冲区中的当前值。如果值已经是255,它将保持255。 |
DecrSat | 缓冲中的当前值递减。如果值已经是0,它将保持为0。 |
Invert | 缓冲中的值按位取反。 |
IncrWrap | 递增缓冲区中的当前值。如果值已经是255,它就变成0。 |
DecrWrap | 缓冲中的当前值递减。如果值已经是0,就变成255。 |
结语
脑子里有很多想法,但是又说不出来。冒着被媳妇锤死的风险终于在12点之前写完了。
官方文档 Stencil