Unity Shader之模板测试 原理与应用

一、认识模板测试

模板测试是在逐片元测试几个阶段中的第一个阶段,它是在深度测试和融混之前的;在平时,我们可能会接触深度测试比较多一点,所以接下来在认识模板测试的时候,经常会与深度测试做比较,以辅助大家来理解模板测试;

首先,想要进行模板测试,就需要有一个缓冲区,这个缓冲区就是模板缓冲;只有在建立一个窗口过程中预先请求模板缓冲,才能够进行模板测试,如果没有模板缓冲,则默认通过模板测试;很多窗口库以及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的区域可以被渲染,所以出现右图效果;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章