关于模板测试Stencil的基本使用
伊底_1D
2022年07月07日 12:36
收录于文集
共16篇

前言

        因为之前做的一个效果用到了以前没怎么用过的模板测试(stencil),所以系统性的了解了一下,并以此文留档记录。

        至少单从使用上来说,模板测试这个东西并不复杂,完整的代码也就是有六七个可配置项,常用的又只有两三个。所以轻松愉快的过掉这节吧- -


一、理解与应用

        先看一下模板测试能用来干什么- -本人身为一个原神玩家,当时渊下宫地图刚出的时候,那个入口的门的效果让我觉得很有意思。

1-1

        这个效果是,从那个红色标识的门洞范围内能看到对面山壁的入口,但从其他地方看(比如右图的青色区域)就只是山壁,而且在角色穿过门洞前,从旁边绕过去也是无法穿过山壁的。

        虽然不清楚mhy具体怎么实现的这个效果,但这种视觉效果利用模板测试是可以实现的。而入口能不能穿过则应该是以“角色从正面穿过门洞”为判断条件,在脚本里控制了山壁碰撞体的启用。

        我在引擎里还原了个最基本效果如下,本质上和原神这个效果是一码事。

1-2

        看网格就能发现,球的模型其实还完整的存在于那里,只不过在门框范围外的部分没有被渲染。而在这个例子中,决定是否渲染的便是模板值(默认是0)。

        对于屏幕上的每个像素,其保存的数据除了用于直接让我们看到效果的RGBA色值外,其他常见的还有深度值和模板值等。前者用于判断屏幕上一点距离相机的远近来处理遮挡剔除等,后者用于实现这种“部分渲染”的特殊效果。

1-3

        观察上图还可以发现,这个区域实际上利用了一个方片模型来实现。实际上要形容的话,这种效果的模板测试其实就是利用模型的剪影(外轮廓)做了一个图层蒙版区域。

        从渲染流程上来说,调整材质的Render Queue保证先渲染这个方片(写入指定模板值),然后渲染球。在渲染球的时候,如果模板值是0则不渲染,否则渲染,就是这么个简单的条件分支逻辑。

        这里先放上上面的剪影片和球的shader代码,关于Stencil的配置看不懂可以继续往下- -

剪影片

代码块
clike
自动换行
复制代码
//URP
Shader "StencilPlane"
{
    Properties
    {
        _Ref ("模板值", int) = 1
    }

    SubShader
    {
        Tags
        {
            "RenderPipeline" = "UniversalPipeline"
            "RenderType" = "Transparent"
            "Queue" = "Background"
        }

        Pass
        {
            Stencil
            {
                Ref [_Ref]
                Pass Replace 
            }
            
            Cull off
            ZWrite off

            HLSLPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            
            struct a2v
            {
                half4 posOS    : POSITION;
            };

            struct v2f
            {
                half4 posCS    : SV_POSITION;
            };

            v2f vert (a2v i)
            {
                v2f o;
                o.posCS = TransformObjectToHClip(i.posOS.xyz);
                return o;
            }

            half4 frag (v2f i) : SV_Target
            {
                return 0;
            }

            ENDHLSL
        }
    }
    FallBack "Hidden/Universal Render Pipeline/FallbackError"
}
复制成功

代码块
clike
自动换行
复制代码
//URP
Shader "StencilLambert"
{
    Properties
    {
        _Ref ("模板值", int) = 1
    }

    SubShader
    {
        Tags
        {
            "RenderPipeline" = "UniversalPipeline"
            "RenderType" = "Opaque"
            "Queue" = "Geometry"
        }

        Pass
        {
            Stencil
            {
                Ref [_Ref]
                Comp Equal
                Pass Replace 
            }
            
            Cull back
            ZWrite on

            HLSLPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
            
            struct a2v
            {
                half4 posOS    : POSITION;
                half3 nDirOS    : NORMAL;
            };

            struct v2f
            {
                half4 posCS    : SV_POSITION;
                half3 nDirWS    : TEXCOORD0;
            };

            v2f vert (a2v i)
            {
                v2f o;

                //坐标
                o.posCS = TransformObjectToHClip(i.posOS.xyz);

                //向量
                o.nDirWS = TransformObjectToWorldNormal(i.nDirOS);

                return o;
            }

            half4 frag (v2f i) : SV_Target
            {
                half3 nDirWS = normalize(i.nDirWS);
                half3 lDirWS = normalize(GetMainLight().direction);
                half lambert = saturate(dot(nDirWS, lDirWS));
                return half4(lambert.rrr, 1.0);
            }

            ENDHLSL
        }
    }
    FallBack "Hidden/Universal Render Pipeline/FallbackError"
}
复制成功

二、Stencil代码块

        U3Dshader中的模板测试实际上就是下面这么一个代码块,其位置放在shader的Pass或SubShader代码块中。

代码块
clike
自动换行
复制代码
Stencil
{
	Ref 参与比较的模板值(0-255)
   Comp 当前模板值与Ref的大小关系
	Pass 如果满足Comp条件要执行的操作
   Fail 如果不满足Comp条件要执行的操作
   ZFail 如果不满足Comp并且没通过深度测试要执行的操作
	ReadMask //一般用不上
   WriteMask //一般用不上
}
复制成功

        其中的各项只能填一些整数或现成的枚举值。也就是说,模板测试可配置但不可编程(你无法自定义判断模板值的方法以及分支中的方法)。其中,内置的各种枚举值与其含义对照如下。

Comp(实际上深度测试ZTest的可配置枚举值也是下面这些)

        Greater 当前模板值大于Ref

        GEqual 大于等于

        Less 小于

        LEqual 小于等于

        Equal 等于

        NotEqual 不等于

        Always 相当于布尔值true

        Never 相当于布尔值false

Pass / Fail / ZFail

        Keep 保持当前模板值不变

        Zero 改变当前模板值为0

        Replace 用Ref替换当前模板值

        IncrSat 当前模板值+1后写入,如果超过255则写入255(截断

        IncrWrap 当前模板值+1后写入,如果超过255则写入0(循环

        DecrSat 当前模板值-1后写入,如果小于0则写入0(截断

        DecrWarp 当前模板值-1后写入,如果小于0则写入255(循环

        Invert 当前模板值二进制按位取反后写入