一、认识模板测试
模板测试是在逐片元测试几个阶段中的第一个阶段,它是在深度测试和融混之前的;在平时,我们可能会接触深度测试比较多一点,所以接下来在认识模板测试的时候,经常会与深度测试做比较,以辅助大家来理解模板测试;
首先,想要进行模板测试,就需要有一个缓冲区,这个缓冲区就是模板缓冲;只有在建立一个窗口过程中预先请求模板缓冲,才能够进行模板测试,如果没有模板缓冲,则默认通过模板测试;很多窗口库以及Unity都会帮我们做好这件事;
回顾深度缓冲相关
我们先回顾一下深度缓冲的逻辑,我们做深度测试,那么就需要比较深度值,用于比较的两个值分别是什么呢,一个是通过几何阶段计算得到的当前像素的z值,另一个就是当前深度缓冲区内的z值,两者做一个比较;而深度缓冲的默认值,也就是清除深度缓冲后,它的所有值都为1,因为我们知道深度缓冲的值的范围是[0,1],越小的值即为越靠近相机的;
理解模板缓冲
模板缓冲和深度缓冲的原理类似,它也是需要做模板测试,那用于比较的两个值是什么呢,一个是模板缓冲中的值,一个是设置的参考值;而模板缓冲的默认值,就是255(因为模板缓冲的数据是8位的);和深度缓冲不同的是,如果要进行深度写入,写入值是每个像素计算出的深度值,而在模板缓冲中如果要进行写入,写入值就是当前的参考值;
一些关于模板缓冲的其它概念
掩码mask,它允许我们设置一个掩码mask,当模板缓冲中的值与模板值比较前,其实模板缓冲的值是需要与掩码mask进行and操作的;当然这里可以不用关心,一般情况下都是所有位置1或置0;
模板比较函数 ,用于测试的函数逻辑;
Pass/ZFail/Fail,一般在做完测试后都会有这三种情况,通过模板测试、通过模板测试但未通过深度测试、未通过模板测试;
操作函数,上述三种情况,都需要有一个处理模板缓冲值的方式,这一点和深度缓冲一样;
与深度缓冲相关的概念都在这里,下面进入到Unity Stencil Test的学习;
二、Unity ShaderLab Stencil的语法
这一部分主要参考了Unity的官方文档:Unity Manual ShaderLab-Stencil
这里先介绍语法部分,这些语法和模板缓冲原理中的概念是几乎一致的;
Ref,Ref refVal,指定一个用于比较的参考值;
ReadMask maskVal,指定读取掩码,这个就相当于上面说的遮罩掩码,在读取模板缓冲的值来进行比较的时候,会与掩码进行and运算,默认值为255;
WriteMask maskVal,指定写入掩码,这个上面没有提到,是unity独自提供的,和ReadMask类似,在写入模板缓冲值的时候,会与掩码进行与操作,然后将结果写入模板缓冲,默认值为255;
Comp comparisonFunction,比较函数,unity提供了8中枚举,Greater,GEqual,Less,LEqual,Equal,NotEqual,Always,Never;通过字面意思理解即可;
Pass/ZFail//Fail stencilOp,为每一种结果指定一个操作,一共有八种枚举,Keep,Zero,Replace,IncrSat,IncrWrap,DecrSat,DecrWrap,Invert;
三、实际应用
这里直接借用官方案例做一个简单的分析;
第一个shader,指定渲染队列为Geometry2000,然后设置Pass为replace,这样就把该shader渲染的物体区域的深度缓冲值都设为了2;将该shader赋值给一个sphere;
Shader "Stencil/Red" {
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry"}
Pass {
Stencil {
Ref 2
Comp always
Pass replace
}
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
}
}
}
第二个shader,渲染队列为2001, 意味着在红球之后渲染,然后设置Ref为2,比较函数为equal,这样的话,如果这个用于渲染绿球,绿球就显示和红球重合的一部分了;
Shader "Green" {
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry+1"}
Pass {
Stencil {
Ref 2
Comp equal
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
}
}
}
如果我们不走模板测试,应该是左图,现在做了模板测试,就只有模板缓冲为2的区域可以被渲染,所以出现右图效果;