Stencil Test 模板测试

在这里插入图片描述
在片元着色器结束,到帧缓存输出之间,有一个逐片元操作,有很多种测试,如图
Pixel Ownership Test :控制像素的使用权限,只能在game窗口和scene窗口显示,其他位置都会被剔除掉
Scissor Test :裁剪测试,在game和scene窗口内,自己可以再次定义渲染范围
Alpha Test :透明度测试
Stencil Test :模板测试
Depth Tset :深度测试
Blending:透明度混合

Stencil Test在Alpha Test之后Depth Test之前,模板测试和深度测试一样,它也可能会丢弃片元。被保留的片元会进入深度测试。模板测试是根据模板缓冲(Stencil Buffer)中的值来判断的,模板缓冲中为每个片元(像素)分配一个8位的数值,默认为0,取值范围[0, 255]

在这里插入图片描述

我们可以修改模板缓冲中的一些值为1,然后只渲染值为1对应的像素,就得到右图的结果

语法格式

Stencil
{
    
    
    Ref referenceValue        // 参考值 默认值为 0
    ReadMask readMask		  // 读掩码
    WriteMask writeMask		  // 写掩码
    Comp comparisonFunction   // 比较的方法 默认为 Always
    Pass stencilOperation     // 通过模板测试时的处理方法 默认为 keep
    Fail stencilOperation     // 没有通过模板测试时的处理方法 默认为 keep
    ZFail stencilOperation    // 通过模板测试却没有通过深度测试时的处理方法 默认为 keep
}

从逻辑上理解

//referenceValue是当前片元的参考值,stencilBufferValue是模板缓冲区的值
if(referenceValue & readMask comparisonFunction stencilBufferValue & readMask)
	通过像素
else
	抛弃像素

指定上面的比较方法 comparisonFunction

比较方法 描述
Greater 相当于“>”操作,即仅当左边>右边,模板测试通过,渲染像素
GEqual 相当于“>=”操作,即仅当左边>=右边,模板测试通过,渲染像素
Less 相当于“<”操作,即仅当左边<右边,模板测试通过,渲染像素
LEqual 相当于“<=”操作,即仅当左边<=右边,模板测试通过,渲染像素
Equal 相当于“=”操作,即仅当左边=右边,模板测试通过,渲染像素
NotEqual 相当于“!=”操作,即仅当左边!=右边,模板测试通过,渲染像素
Always 不管公式两边为何值,模板测试总是通过,渲染像素
Never 不管公式两边为何值,模板测试总是失败 ,像素被抛弃

指定上面的更新操作 stencilOperation

更新值 描述
Keep 保留当前缓冲中的内容,即stencilBufferValue不变
Zero 将0写入缓冲,即stencilBufferValue值变为0
Replace 将参考值写入缓冲,即将referenceValue赋值给stencilBufferValue
IncrSat stencilBufferValue加1,如果stencilBufferValue超过255了,那么保留为255,即不大于255
DecrSat stencilBufferValue减1,如果stencilBufferValue超过为0,那么保留为0,即不小于0
Invert 将当前模板缓冲值(stencilBufferValue)按位取反
IncrWrap 当前缓冲的值加1,如果缓冲值超过255了,那么变成0,然后继续自增
DecrWrap 当前缓冲的值减1,如果缓冲值已经为0,那么变成255,然后继续自减

实现3D卡牌效果

在这里插入图片描述
在这里插入图片描述

这里由两部分组成,一个是前面透明的Mask(蒙板),另一个是Mask后面的所有Object。为了先渲染Mask,我们需要控制Mask的Render Queue小于Object的Render Queue,然后在Mask的Shader中修改模板缓冲中的值,当渲染Object时,只有它的参考值Ref和模板缓冲值相同才能通过模板测试

Shader "MyCustom/StencilMask"
{
    
    
    Properties
    {
    
    
        _StencilRef ("StencilRef", Int) = 0
    }
    SubShader
    {
    
    
        Tags {
    
     "RenderType"="Opaque" "Queue"="Geometry-1"}
        ZWrite Off
        ColorMask 0
        
        Stencil
        {
    
    
            Ref [_StencilRef]  // [0, 255]
            Comp always        // 模板测试总是通过
            Pass replace       // Ref值替换掉stencil buffer中的值
        }

        Pass
        {
    
    
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
    
    
                float4 vertex : POSITION;
            };

            struct v2f
            {
    
    
                float4 vertex : SV_POSITION;
            };
            
            
            v2f vert (appdata v)
            {
    
    
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }

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

注意这里需要关闭深度写入,不能让Mask影响后面物体的渲染,使用ColorMask 0,不写入颜色,将Mask变成透明的

扫描二维码关注公众号,回复: 14603903 查看本文章

在这里插入图片描述

这里的渲染队列设置为Geometry-1(1999),是为了先渲染Mask,因为物体默认的渲染队列是Geometry(2000),在面板上修改参考值Ref为1,这样Mask覆盖的区域模板测试总是通过,并且会修改模板值为1,其他部分还是0,如图

在这里插入图片描述

Shader "MyCustom/StencilObject"
{
    
    
    Properties
    {
    
    
        _Color("Color",Color) = (1, 1, 1, 1)
        _StencilRef("StencilRef", Int) = 0
    }
    SubShader
    {
    
    
        Tags {
    
     "RenderType"="Opaque" "Queue"="Geometry"}
        
        Stencil
        {
    
    
            Ref [_StencilRef]  // [0, 255]
            Comp Equal         // Ref值和stencil buff值相等时通过模板测试
        }

        Pass
        {
    
    
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
    
    
                float4 vertex : POSITION;
            };

            struct v2f
            {
    
    
                float4 vertex : SV_POSITION;
            };
            

            v2f vert (appdata v)
            {
    
    
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }

            float4 _Color;

            fixed4 frag (v2f i) : SV_Target
            {
    
    
                return _Color;
            }
            ENDCG
        }
    }
}

在这里插入图片描述
对于Object,修改面板上的Ref为1,这样当模板缓冲区中的值为1(即处于Mask覆盖区域)才能通过模板测试,渲染出来
最终效果
在这里插入图片描述
在这里插入图片描述

笼中窥梦效果

在这里插入图片描述
立方体不同面显示不同场景,实现原理和上面的3D卡牌是一样的,每个面都有一个Mask,赋予不同的Ref值,场景中的物体只要Ref值和Mask一样,就能在这个Mask上显示,详情可以参考这个项目,地址

透视效果

在这里插入图片描述
这里要实现透过Mask,看到墙后面的物体,原理和上面一样的,只是中间加了个墙作为遮挡,上面用到的两个Shader也不用修改
Mask
在这里插入图片描述

Wall,Wall需要新建一个材质,和Object使用相同的Shader,Ref值改成0
在这里插入图片描述

Object
在这里插入图片描述
Mask覆盖的区域模板值改为1,其他区域还是0
渲染Wall的时候,在Mask覆盖区域 0 != 1,模板测试未通过,其他区域,0 == 0,模板测试通过
渲染Object时,只有在Mask覆盖区域 1 == 1,模板测试通过
在这里插入图片描述

描边

在这里插入图片描述
使用模板测试实现的描边,当物体有重叠时,在物体相连处并没有描边 ,它不是基于模型做的描边

Shader "MyCustom/StencilOutline"
{
    
    
    Properties
    {
    
    
        _StencilRef ("StencilRef", Int) = 0
        [Enum(UnityEngine.Rendering.CompareFunction)]_StencilComp ("StencilComp", Int) = 8
        [Enum(UnityEngine.Rendering.StencilOp)]_StencilOp ("StencilOp", Int) = 2
        _EdgeColor("Edge Color", Color) = (1, 1, 1, 1)
        _EdgeWidth("Edge Width", Float) = 0.1
    }
    SubShader
    {
    
    
        Tags {
    
     "RenderType"="Opaque"}
        
        Stencil
        {
    
    
            Ref [_StencilRef]
            Comp [_StencilComp]
            Pass [_StencilOp]
        }

        Pass
        {
    
    
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
    
    
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };

            struct v2f
            {
    
    
                float4 vertex : SV_POSITION;
                float3 worldNormal  : TEXCOORD0;
            };
            
            
            v2f vert (appdata v)
            {
    
    
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
    
    
                float3 worldNormal = normalize(i.worldNormal);
                float3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
                float lambert = max(dot(worldNormal, worldLight), 0.0);
                float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                return float4(lambert + ambient, 1);
            }
            ENDCG
        }
        
        Pass
        {
    
    
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            struct appdata
            {
    
    
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };

            struct v2f
            {
    
    
                float4 vertex       : SV_POSITION;
            };

            float _EdgeWidth;
            float4 _EdgeColor;

            v2f vert (appdata v)
            {
    
    
                v2f o;
                o.vertex = v.vertex + normalize(float4(v.normal, 0)) * _EdgeWidth;
                o.vertex = UnityObjectToClipPos(o.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
    
    
                return _EdgeColor;
            }
            
            ENDCG
        }
    }
}

在这里插入图片描述
这里比较方法和更新操作使用了枚举,比较函数Equal,比较操作是模板值加1,Stencil在Pass外面,两个Pass都会执行模板测试
第一个Pass使用兰伯特渲染物体,模板缓冲中默认值是0,与0相等,通过模板测试,并将物体覆盖区域的模板值加1
第二个Pass顶点沿法线方向外扩,描边的部分Ref值与0相等,通过模板测试,描边内的物体模板值已经变成1了,与0不相等,没通过模板测试,所以只渲染描边的部分

显示几何体相交部分

在这里插入图片描述

左边是红球,右边是绿球,灰色是一个平面,只在平面上方它们相交的部分显示绿色

红球的设置:先渲染红球,并把红球覆盖区域的模板值改为1,平面下方没有通过深度测试,所以不显示

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

        Stencil
        {
    
    
            Ref 1            // 0-255
            Comp always      // 总是通过测试
            Pass replace     // 用1替换stencil buffer的值
        }

绿球的设置:后渲染绿球,只有在相交的部分,Ref值和模板值相同,模板测试通过

    SubShader
    {
    
    
        Tags {
    
     "RenderType"="Opaque" "Queue"="Geometry+1"}

        Stencil
        {
    
    
            Ref 1           // 0-255
            Comp Equal      // Ref和stencil buffer值相等时通过
            Pass keep       // stencil 和 z test都通过,保持模板值不变
        }

在这里插入图片描述

左边是红球,右边是绿球,灰色是一个平面,只在平面下方它们相交的部分显示绿色

红球的设置:先渲染红球,并把红球覆盖区域的模板值改为1,平面下方没有通过深度测试的部分,模板值减1,即为255

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

        Stencil
        {
    
    
            Ref 1            // 0-255
            Comp always      // 总是通过测试
            Pass replace     // 用1替换stencil buffer的值
            ZFail decrWrap   // z test 没有通过, stencil buff值减1,0减1变成255
        }

绿球的设置:后渲染绿球,只有在相交的部分,Ref值和模板值相同,模板测试通过,为了让绿球在平面下方的部分也能通过深度测试,所以设置了ZTest Always,深度测试的内容参考另一篇文章:Depth Test 深度测试

    SubShader
    {
    
    
        Tags {
    
     "RenderType"="Opaque" "Queue"="Geometry+1"}
		ZTest Always
        Stencil
        {
    
    
            Ref 255         // 0-255
            Comp Equal      // Ref和stencil buffer值相等时通过
            Pass keep       // stencil 和 z test都通过,保持模板值不变
        }

参考

【技术美术百人计划】图形 3.1 深度与模板测试 传送门效果示例

猜你喜欢

转载自blog.csdn.net/sinat_34014668/article/details/128730507
今日推荐