Unity3d 技术笔记

ShaderLab: Stencil Buffer 的理解和应用

2022-05-04  本文已影响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 的参数有:

写往 stencil buffer 配置参数:

样例:

{
     // 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
    }
}
}

效果:

镜面反射效果

总结:

参考文献:

上一篇 下一篇

猜你喜欢

热点阅读