ShaderLab: Stencil Buffer 的理解和应用

综述:

在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 之后的执行参数
  • 可以通过这些特性,实现描边,镜面投影,物体交叉渲染,体积阴影等效果

参考文献:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 193,968评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,682评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,254评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,074评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,964评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,055评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,484评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,170评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,433评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,512评论 2 308
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,296评论 1 325
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,184评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,545评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,150评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,437评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,630评论 2 335

推荐阅读更多精彩内容