找回密码
 立即注册
查看: 378|回复: 0

ShaderLab: Stencil Buffer 的理解和应用

[复制链接]
发表于 2022-5-5 18:03 | 显示全部楼层 |阅读模式
综述:

在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 之后的执行参数可以通过这些特性,实现描边,镜面投影,物体交叉渲染,体积阴影等效果
参考文献:
    https://en.wikipedia.org/wiki/Stencil_bufferhttps://docs.unity3d.com/Manual/SL-Stencil.htmlhttps://github.com/mumuyu66/UnityStencilBufferUses

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-9-22 13:23 , Processed in 0.154149 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表