模板测试
除了混合透明和透明测试,Untiy提供了另外一种实现透明效果的方法-模板测试,达到逐像素保留或丢弃像素的目的。
如上图,通过0,1比较逐像素扣除不想要的部分。
与透明测试给一个阈值全局比较不同,模板测试可以指定区域设置比较,可以理解为自定义的局部透明测试(本质都是丢弃像素),并且支持多种比较方法(透明度测试只是透明度大于阈值)。
模板测试再透明测试之后,深度测试之前。先根据透明度阈值进性全局的像素剔除,再有针对性的根据参照值进行模板测试剔除,最后在实际场景里根据远近关系进行深度测试后剔除
模板测试的计算流程
现在我们要对一个物体B进行渲染并模板测试,首先要设定模板值,通过为物体A指定一个参照值,直接通过模板测试,再把A的参照值写入模板缓存中就成为了模板值,此时再把B的参照值跟A比较(有多种方法),通过后进行模板操作(有多种),不通过舍弃像素(也可以对模板值进行操作)。
模板测试的实用语法
模板测试的指令可以写在SubShader里也可以写在Pass中,作用范围多大的区别,包含在Stencil{}代码块中。
Stencil
{
Ref referenceValue
ReadMask readMask
WriteMask writeMask
Comp comparisonFuction
Pass stencilOperation
Fail stencilOperation
Zfail stencilOperation
}
指令 | 说明 |
---|---|
Ref referenceValue | 与缓存中的模板值进行比较的值,成为参照值,如果比较符合条件,可以被写入缓存 |
ReadMask readMask | 0-255的一个整数对应2进制0000000-11111111,指定参照值和模板值的那些位可以读取 |
WriteMask writeMask | 同上,8位二进制11111111,指定哪些位可以写入 |
Comp comparisonFuction | 参照值与模板测试的比较方法,默认为always |
Pass stencilOperation | 模板测试和深度都测试时对缓存中的模板值的操作,默认为keep |
Fail stencilOperation | 模板测试未通过时对缓存中的模板值的操作,默认为keep |
Zfail stencilOperation | 模板测试通过,深度都测试未通过时对缓存中的模板值的操作,默认为keep |
模板测试的比较方法
指定了模板测试通过时参照值和缓存值的比较方法,同样这些方法也可以用于深度测试。
方法 | 说明 |
---|---|
Greater | 当前渲染像素的参照值大于缓存的时候才会通过测试 |
GEqual | 当前渲染像素的参照值大于等于缓存的时候才会通过测试 |
Less | 当前渲染像素的参照值小于缓存的时候才会通过测试 |
LEqual | 当前渲染像素的参照值小于或等于缓存的时候才会测试 |
Equal | 当前渲染像素的参照值等于缓存的时候才会测试 |
NotEqual | 当前渲染像素的参照值不等于缓存的时候才会测试 |
Always | 当前渲染像素总是通过测试 |
Never | 当前渲染像素总是不通过测试 |
模板操作
不管是否通过测试,都可以对缓存中模板值进行操作(只不过不通过会舍弃像素)。
名称 | 缩写 |
---|---|
Keep | 继续保持缓存中的模板值 |
Zero | 把0写入缓存 |
Replace | 把当前的参照值写入缓存 |
IncrSat | 递增缓存中的模板值,如果数值已经为255,则停止递增 |
DecrSat | 递减缓存中的模板值,如果数值已经为0,则停止递减 |
Invert | 将模板值按位取反 |
IncrWrap | 递增缓存中的模板值,如果数值已经为255.则变为0 |
DecrWrap | 递减缓存中的模板值,如果数值已经为0则变为255 |
模板测试透明效果
完成的效果为,通过A物体在B物体上挖一个洞,并可以通过这个动看到后面的其他物体,类似布尔运算。 A物体Shader:
Shader "Custom/StencilTestA"
{
Properties
{
_MainColor("Main Color",Color) = (1, 1, 1, 1)
_MainTex("Main Tex",2D) = "White"{}
}
SubShader
{
Tags { "Queue" = "Geometry-1" }
Pass
{
//设置模板测试的状态
Stencil
{
Ref 1
Comp Always
Pass Replace
}
//禁止绘制任何色彩
ColorMask 0
ZWrite off
CGPROGRAM
# pragma vertex vert
# pragma fragment frag
float4 vert(in float4 vertex: POSITION):SV_POSITION
{
float4 pos = UnityObjectToClipPos(vertex);
return pos;
}
void frag(out fixed4 color : SV_Target)
{
color = fixed4(0,0,0,0);
}
ENDCG
}
}
}
通过指定A物体的渲染队列为Geometry-1使其最先渲染,设置其参照值为1,并always通过模板测试,使用Pass Replace将参照值写入模板值缓存中,A不进行颜色绘制也不进行深度写入避免盖住其他物体。
B物体Shader:
Shader "Custom/StencilTestB"
{
Properties
{
_MainColor("Main Color",Color) = (1, 1, 1, 1)
_MainTex("Main Tex",2D) = "White"{}
}
SubShader
{
Tags { "Queue"="Geometry" }
Pass
{
Tags{"LightMode" = "ForwardBase"}
//设置模板测试的状态
Stencil
{
Ref 1
Comp NotEqual
Pass Keep
}
CGPROGRAM
# pragma vertex vert
# pragma fragment frag
# include "UnityCG.cginc"
# include "UnityLightingCommon.cginc"
struct v2f
{
float4 pos : SV_POSITION;
float4 worldPos : TEXCOORD0;
float3 worldNormal : TEXCOORD1;
float2 texcoord : TEXCOORD2;
};
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _MainColor;
v2f vert(appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldNormal = normalize(worldNormal);
o.texcoord = TRANSFORM_TEX(v.texcoord,_MainTex);
return o;
}
fixed4 frag(v2f i): SV_Target
{
float3 worldLight = UnityWorldSpaceLightDir(i.worldPos.xyz);
worldLight = normalize(worldLight);
fixed NdotL = saturate(dot(i.worldNormal, worldLight));
fixed4 color = tex2D(_MainTex,i.texcoord);
color.rgb *= _MainColor * NdotL * _LightColor0.rgb;
color.rgb += unity_AmbientSky.rgb;
return color;
}
ENDCG
}
}
}
B物体正常渲染顺序,参照值为1,与缓存值不相等时才通过模板测试,通过测试后保持缓存中的模板值。
这样模板缓存中存着的相当于是A物体的像素位置,B物体会跟A物体逐像素比较,相同代表这个像素点已经有A了,B的像素剔除,同时A不进行绘制,实现的效果就是B被A挖空了一部分。