目录
2022年8月27日更新
模板测试在深度测试之后进行,所以如果想做一个物体背后的物体能够显示在前面,那必须让背后的物体能够通过深度测试,即ZTest Always,但又不是整个物体显示出来,只显示出被遮挡部分,就需用到模板测试,用遮挡物写入模板,被遮挡物进行模板测试,还得注意先后顺序,必须是遮挡物先进行渲染,然后再进行渲染被遮挡物。
一、介绍模板测试
1、模板测试是在片元着色器之后进行的,同时也在深度测试之后进行,它的目的是过滤片元,当参考值和模板缓冲区数值比较后达成条件的才会显示,当然也可以不进行模板测试。
2、在Pass块内定义,以Stencil{}来实现一个Pass它对应的模板测试操作
3、Stencil内有如下指令:
① Ref referenceValue
Ref指令 + referenceValue参考值(0~255整数值)
当Comp指令指明为非Always时,以此referenceValue整数值与模板缓冲区的数值进行比较,比较方式根据Comp指令指明的条件来进行,当referenceValue数值满足条件时,则算作通过模板测试;当通过模板测试后,且Stencil的Pass指令指明为Replace时则用此值替换掉缓冲区的数值。
② ReadMask readMask
ReadMask指令 + readMask读(Ref指令指定的数值时的 和 当前模板缓冲区)掩码【需测试】
readMask(0~255整数值)即8bit
③ WriteMask writeMask
写入(Ref指令指定的数值时的 和 当前模板缓冲区)掩码 【需测试验证】
writeMask (0~255整数值) 即8bit
④ Comp comparisonFunction
Comp指令 + comparisonFunction比较函数(如:Always【直接允许通过模板测试】)
Comp是指明模板测试是如何测试的,有如下比较函数:
Greater | Only render pixels whose reference value is greater than the value in the buffer. |
GEqual | Only render pixels whose reference value is greater than or equal to the value in the buffer. |
Less | Only render pixels whose reference value is less than the value in the buffer. |
LEqual | Only render pixels whose reference value is less than or equal to the value in the buffer. |
Equal | Only render pixels whose reference value equals the value in the buffer. |
NotEqual | Only render pixels whose reference value differs from the value in the buffer. |
Always | Make the stencil test always pass. |
Never | Make the stencil test always fail. |
下面才是按顺序解释,Comp的0-8数字代表意义。
Disabled | 禁用深度或模板测试。 |
Never | 从不通过深度或模板测试。 |
Less | 当新值小于旧值时通过深度或模板测试。 |
Equal | 当值相等时通过深度或模板测试。 |
LessEqual | 当新值小于或等于旧值时通过深度或模板测试。 |
Greater | 当新值大于旧值时通过深度或模板测试。 |
NotEqual | 当值不同时通过深度或模板测试。 |
GreaterEqual | 当新值大于或等于旧值时通过深度或模板测试。 |
Always | 始终通过深度或模板测试。 |
⑤ Pass stencilOperation
Pass指令 + stencilOperation回调函数(操作)
Pass指令是指明模板测试通过后应执行什么样的操作的,有如下stencilOperation操作
Keep | Keep the current contents of the buffer. |
Zero | Write 0 into the buffer. |
Replace | Write the reference value into the buffer. |
IncrSat | Increment the current value in the buffer. If the value is 255 already, it stays at 255. |
DecrSat | Decrement the current value in the buffer. If the value is 0 already, it stays at 0. |
Invert | Negate all the bits. |
IncrWrap | Increment the current value in the buffer. If the value is 255 already, it becomes 0. |
DecrWrap | Decrement the current value in the buffer. If the value is 0 already, it becomes 255. |
⑥ Fail stencilOperation
指明模板测试失败后的操作
⑦ ZFail stencilOperation
指明深度测试失败后的操作
二、实战测试
2.1 官方实战
1、制作一个最简单的顶点着色器/片元着色器的Shader,仅进行写入模板值(即2),然后输出一个红色值。
Shader "Unlit/StencilTestRed"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" "Queue"="Geometry"}
LOD 100
Pass
{
Stencil
{
//Ref参考值: 当Comp非Always时会用于比较(虽然还不清楚是和什么比较,可能是和模板缓冲区的值进行比较)
//若Pass、Fail、ZFail为Replace时,该值用于写入模板缓冲区,[0,255]
Ref 2
Comp Always //总是通过模板测试
Pass Replace //通过模板测试后进行替换操作(将Ref的值写入模板缓冲区)
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return fixed4(1, 0, 0, 1);
}
ENDCG
}
}
}
2、制作一个最简单的Shader,模板测试比较函数为Equal(等于),Ref参考值为2,Pass通过模板测试后Keep(保持原值,即不进行任何操作),ZFail深度测试失败后对模板缓冲区的数值减1(若是0则会减为255),输出颜色值为绿色。
Shader "Unlit/StencilTestGreen"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" "Queue"="Geometry+1"}
LOD 100
Pass
{
Stencil
{
Ref 2
Comp equal
Pass keep
ZFail DecrWrap
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 vertex : SV_POSITION;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return fixed4(0, 1, 0, 1);
}
ENDCG
}
}
}
3、制作一个最简单的Shader,仅进行了模板测试Comp等于操作,Ref值为1,输出颜色值为蓝色
Shader "Unlit/StencilTestBlue"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" "Queue"="Geometry+2"}
LOD 100
Pass
{
Stencil
{
Ref 1
Comp equal
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return fixed4(0, 0, 1, 1);
}
ENDCG
}
}
}
*注意:上方三个Shader对应红、绿、蓝球,并且Queue渲染队列是先红、然后绿、最后蓝进行渲染的,不然可能会有问题。
上面三张图分别是红球、绿球、篮球的渲染过程,红球渲染时将红球所在的模板缓冲区的数值改为2,绿球渲染时会用2与缓冲区的数值进行对比,结果只有与红球相交的部分通过模板测试(即2==2),所以只有那部分渲染成了绿色,而其他部分无法通过测试抛弃,并且绿球渲染时,当深度测试不通过时,会将那深度测试不通过的部分的模板缓值减1(2-1=1),注意!深度测试不通过的部分,此时是只有红球和绿球渲染时的深度测试不通过部分,而非三个物体都渲染出来后的深度测试不通过部分,这一点要好好地理解!所以最后蓝色球渲染时,会将蓝色球所在区域模板值为1的渲染出来,其他的抛弃。
各位请看上图,右图是我画出来的深紫色区域,这个区域在红球已渲染,绿球渲染时,蓝球还未渲染的情况下,这个区域不是绿球模板测试时的深度测试不通过区域。但当三个球体都被渲染出来后,这个区域才是深度测试不通过区域哟。
2.2 关于ReadMask和WriteMask的测试
1、制作一个Shader,模板参考值为9,总是通过模板测试,Pass后替换模板缓冲区的值,写入掩码为1,所以经过掩码后写入的是1
Shader "Unlit/StencilReadMaskTestShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" "Queue"="Geometry" }
LOD 100
Pass
{
Stencil
{
Ref 9
WriteMask 1
Comp Always
Pass Replace
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return fixed4(1,0,0,1);
}
ENDCG
}
}
}
2、制作一个Shader,模板参考值为5,模板比较操作是等于Equal,读入掩码为3,所以经过读取掩码后的模板参考值为1。
Shader "Unlit/StencilReadMaskTestShaderReadMask"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" "Queue"="Geometry+1" }
LOD 100
Pass
{
Stencil
{
Ref 5
ReadMask 3
Comp Equal
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return fixed4(0,0,1,1);
}
ENDCG
}
}
}
所以读取掩码和写入掩码都是针对模板参考值Ref而言的,并不是针对模板缓冲区的数值。我当初是以为2个会针对缓冲区的值。
详细解释:
红球渲染时写入参考值9时,使用写入掩码1对9进行掩码操作,即&操作,1001&0001=0001,所以写入的是1;
蓝球渲染时在进行模板测试等于操作时,读取模板参考值5,使用读取掩码3进行掩码操作,即&操作,101&011=001,所以读取到的是1,最终篮球区域上模板缓存值为1的区域被渲染出来。