美文网首页Unity3d 技术笔记
ShaderLab: Stencil Buffer 的理解和应用

ShaderLab: Stencil Buffer 的理解和应用

作者: 子夜书案 | 来源:发表于2022-05-04 19:49 被阅读0次

    综述:

    在GPU的架构中,有一块将每个像素以8-bit存储的区域,这片区域被称作为 stencil buffer
    在片段着色时(fragment shader),GPU 会将每个像素和 stencil buffer 中的值进行比较,这个比较过程称之为 stencil test。如果 stencil test 通过,再进行 depth test。如果 stencil test 测试不通过,GPU 便会跳过次像素点的后续处理。这也意味着可以通过读写 stencil buffer 来控制像素的呈现已否。
    Stencil buffer 通常运用在比较特殊的效果(Effects)上例如:传送门(portals),镜面(mirrors)等。

    渲染管线的兼容性:

    支持内置管线(built-in Render Pipeline),URP(Universal Render Pipeline),HDRP(High Definition Render Pipeline)

    执行时机:

    在 alpha test 之后,在depth test 之前。

    和Z-Buffer 的关系:

    Stencil buffer 通常是和 Z-buffer 共用一块内存,其占比通常为:24bits Z-buffer + 8bits Stencil buffer,在显存比较紧张的过去,其占比为:15 bits Z-buffer + 1bit Stencil buffer 。另外一种变体是:24 + 4 只使用 32 位中的 28 位,忽略剩下4位。

    用法:

    此指令放在 Pass 结构里,会单独对此 Pass 生效,或者放在 SubShader 中,将对其中的所有 Pass 生效。
    Stencil 指令(command)可以同时做两件事情:

    • 配置 Stencil test 内容
    • 配置 GPU 写往 stencil buffer 条件

    用来配置 stencil test 的参数有:

    • Ref : int(0-255), default is 0
      GPU 会使用 comparisonOperation 中定义的操作将 stencil buffer 中的当前内容和此值进行比较。
      此值使用 readMask 或 writeMask 过滤,如果 Pass、Fail 或 ZFail 的值为 Replace,GPU 也可以将此值写入模板缓冲区。

    • ReadMask int(0-255),default is 255
      读掩码,stencil test 时,此值为过滤掩码

    • Comp comparisonOperation ,default is Always 值可以为:

      • Never vaule=1 Never render pixels
        从不渲染像素
      • Less value = 2 Render pixels when their reference value is less than current value in the Stencil buffer
        当参考值小于 Stencil buffer 中的值时,像素将被渲染
      • Equal value=3 Render pixels when their refences value equal to the current value in the Stencil buffer
        当参考值等于 Stencil buffer 中的值时,像素将被渲染
      • LEqual value=4 Render pixels when their refences value less than or equal to the current value in the Stencil buffer.
        当参考值小于等于 Stencil buffer 中的值时,像素将被渲染
      • Greater value=5 Rander pixels when their refences value greater then current value in the stencil buffer.
        当参考值大于 Stencil buffer 中的值时,像素将被渲染
      • NotEqual value=6 Render pixels when their refences value differs from the current value in the stencil buffer.
        当参考值不等于 Stencil buffer 中的值时,像素将被渲染
      • GEqual value=7 Render pixels when their refences value greater then or equal to the current value in then stencil buffer.
        当参考值大于等于 Stencil buffer 中的值时,像素将被渲染
      • Alawys value=8 Always render pixels.
        一直渲染

    写往 stencil buffer 配置参数:

    • WriteMask int(0-255),default is 255
      GPU 在写入模板缓冲区时使用此值作为掩码。
      请注意,与其他掩码一样,它指定操作中包含哪些位。例如,值 0 意味着写入操作中不包含任何位,而不是模板缓冲区接收到值 0。

    • Pass: default Keep, The operation that the GPU performs on the stencil buffer when a pixels pass both the stencil test and the depth test.
      当像素通过 stencil test 和 深度测试后,GPU 在 stencil buffer 上的操作。可能的值为:

      • Keep value=0 Keep the current contents of the stencil buffer.
        保留 stencil buffer 中的当前值
      • Zero value=1 Write 0 into the stencil buffer.
        将 stencil buffer 中的当前值置为0.
      • Replace value=2 Write the refences value into the buffer.
        将参考值写入 stencil buffer
      • IncrSat value=3 Increment the current value in the stencil buffer.if the value is 255 already,it stays at 255.
        自增buffer中的当前值,直至它等于255
      • DecrSat value=4 Decrment the current value in the stencil buffer.if the value is 0 already,it stays at 0.
        渐减buffer中的值,直至它等于0
      • Invert value=5 Negate all the bits of the current value in the buffer.
        取反buffer中当前值的所有位
      • IncrWrap value=7 Increment the current value in the buffer.if the value is 255 already,it becomes 0.
        自增buffer中的当前值,如果等于255,重置为0
      • DecrWrap value=8 Decrement the current value in the buffer.if the value is 0 already,it becomes 255.
        自减buffer中的当前值,如果等于0,重置为255
    • Fail Stencil operation values,default = Keep ;The operation that the GPU performs on the stencil buffer when a pixel fails the stencil test.
      当像素 stencil test 失败时,GPU执行的操作变量,具体参考Pass。

    • ZFail Stencil operation values,default = Keep ;The operation that the GPU perfroms on the stencil buffer when a pixel passes the stencil test ,but fails the depth test.
      当像素通过stencil test 但是没有通过深度测试时,GPU执行是操作变量。具体值参考Pass。


      参考

    样例:

    {
         // The rest of the code that defines the SubShader goes here.
        Pass
        {    
             // All pixels in this Pass will pass the stencil test and write a value of 2 to the stencil buffer
             // You would typically do this if you wanted to prevent subsequent shaders from drawing to this area of the render target or restrict them to render to this area only
             Stencil
             {
                 Ref 2
                 Comp Always
                 Pass Replace
             }            
    
             // The rest of the code that defines the Pass goes here.
        }
    }
    }
    
    {
        SubShader
        {
                 // All pixels in this SubShader pass the stencil test only if the current value of the stencil buffer is less than 2
                 // You would typically do this if you wanted to only draw to areas of the render target that were not "masked out"
                 Stencil
                 {
                     Ref 2
                     Comp Less
                 }  
    
             // The rest of the code that defines the SubShader goes here.        
    
            Pass
            {    
                  // The rest of the code that defines the Pass goes here.
            }
        }
    }
    

    用 Stencil Buffer 实现的功能

    描边

    源码地址:https://github.com/mumuyu66/UnityStencilBufferUses
    Shader "Unlit/StentilOutline"
     {
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100
        Stencil {
             Ref 0          //0-255
             Comp Equal     //default:always
             Pass IncrSat   //default:keep
             Fail keep      //default:keep
             ZFail keep     //default:keep
        }
    
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog
            
            #include "UnityCG.cginc"
    
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };
    
            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };
    
            sampler2D _MainTex;
            float4 _MainTex_ST;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
            //  return fixed4(1,1,0,1);
                return col;
            }
            ENDCG
        }
    
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog
            
            #include "UnityCG.cginc"
    
            struct appdata
            {
                float4 vertex : POSITION;
                float4 normal: NORMAL;
                float2 uv : TEXCOORD0;
            };
    
            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };
    
            sampler2D _MainTex;
            float4 _MainTex_ST;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex=v.vertex+normalize(v.normal)*0.05f;
                o.vertex = UnityObjectToClipPos(o.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return fixed4(1,1,1,1);
            }
            ENDCG
        }
    }
    }
    

    说明:

    • stencil buffer 中的数据在帧结束前是不会清除的,因此它可以被不同的 shader ,pass 访问
     Stencil
     {
             Ref 0          //0-255
             Comp Equal     //default:always
             Pass IncrSat   //default:keep
             Fail keep      //default:keep
             ZFail keep     //default:keep
        }
    

    可以根据这原理,在第一 Pass 中运行过后,stencil将被设置为1,第二个Pass里将顶点扩大,由于原先的像素点的stencil全都等于1,除了扩大出来的顶点外,都不会被渲染。

    o.vertex=v.vertex+normalize(v.normal)*0.05f;
    

    效果

    描边效果

    反射:

    原理,在镜面shader中,将通过 stencil test 的像素点 stencil 设置为 1 ;在需要被镜像的shader 中的第二 pass 中将顶点翻转,渲染在stencil=1 的区域。

    Mirror.shader
       Shader "Unlit/Mirror"
      {
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "Queue"="Geometry-1" }
        LOD 100
    
        Stencil {
            Ref 0          //0-255
            Comp Equal     //default:always
            Pass IncrSat   //default:keep
            Fail keep      //default:keep
            ZFail keep     //default:keep
        }
    
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog
            
            #include "UnityCG.cginc"
    
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };
    
            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };
    
            sampler2D _MainTex;
            float4 _MainTex_ST;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return fixed4(0.2f,0.2f,0.2f,1.0f);
            }
            ENDCG
        }
    }
    }
    
    TwoPassReflection.shader
    Shader "Unlit/TwoPassReflection"
    {
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "Queue"="Geometry" }
        LOD 100
    
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog
            
            #include "UnityCG.cginc"
    
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };
    
            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };
    
            sampler2D _MainTex;
            float4 _MainTex_ST;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    
        Pass
        {
            Stencil {
                Ref 1          //0-255
                Comp Equal     //default:always
                Pass keep   //default:keep
                Fail keep      //default:keep
                ZFail keep     //default:keep
            }
            ZTest Always
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog
            
            #include "UnityCG.cginc"
    
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float4 normal: NORMAL;
            };
    
            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };
    
            sampler2D _MainTex;
            float4 _MainTex_ST;
            
            v2f vert (appdata v)
            {
                v2f o;
                v.vertex.xyz=reflect(v.vertex.xyz,float3(-1.0f,0.0f,0.0f));
                v.vertex.xyz=reflect(v.vertex.xyz,float3(0.0f,1.0f,0.0f));
                v.vertex.x+=1.5f;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv)*0.01f;
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
    }
    

    效果:

    镜面反射效果

    总结:

    • stencil buffer 存储在GPU,和屏幕像素点一一对应
    • stencil buffer 和 z-buffer 共用同一快数据
    • stencil buffer 内的数据是跨越shader ,pass 的,生命周期为一帧
    • 可以设置stencil test 成功条件,也可以设置 pass,fail 之后的执行参数
    • 可以通过这些特性,实现描边,镜面投影,物体交叉渲染,体积阴影等效果

    参考文献:

    相关文章

      网友评论

        本文标题:ShaderLab: Stencil Buffer 的理解和应用

        本文链接:https://www.haomeiwen.com/subject/pyswyrtx.html