模板测试

除了混合透明和透明测试,Untiy提供了另外一种实现透明效果的方法-模板测试,达到逐像素保留或丢弃像素的目的。

alt

如上图,通过0,1比较逐像素扣除不想要的部分。

与透明测试给一个阈值全局比较不同,模板测试可以指定区域设置比较,可以理解为自定义的局部透明测试(本质都是丢弃像素),并且支持多种比较方法(透明度测试只是透明度大于阈值)。

模板测试再透明测试之后,深度测试之前。先根据透明度阈值进性全局的像素剔除,再有针对性的根据参照值进行模板测试剔除,最后在实际场景里根据远近关系进行深度测试后剔除 alt

模板测试的计算流程

现在我们要对一个物体B进行渲染并模板测试,首先要设定模板值,通过为物体A指定一个参照值,直接通过模板测试,再把A的参照值写入模板缓存中就成为了模板值,此时再把B的参照值跟A比较(有多种方法),通过后进行模板操作(有多种),不通过舍弃像素(也可以对模板值进行操作)。 alt

模板测试的实用语法

模板测试的指令可以写在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挖空了一部分。

alt