Unity Shader - ShaderLab: Stencil 模板(缓存)

目录:Unity Shader - 知识点目录(先占位,后续持续更新)
原文:ShaderLab: Stencil
版本:2019.1


2020.3 版本的文档,讲得很详细,可以参考:ShaderLab command: Stencil


ShaderLab: Stencil

模板(缓存)

Stencil buffer(模板缓存)可用于一般的像素遮罩或是剔除像素功能的地方使用。

模板缓存通常是每个像素位占8位的整型数据。缓存可以被写入数据,自增,或是自减。然后draw call时可以检测该像素对应的模板值,来决定在运行像素着色器前是否丢弃该像素。

注意:上面是官方的说明,但是与实际的渲染管线有些不符,因为Stencil Test是在Fragment Shader之后的,那么有可能是与Early-Z类似的Early-Stencil

Syntax

语法

Ref

参考值设置

    Ref referenceValue

该值用于比较时参考用的(除了 Comp 设置为 always时,不会考虑比较,直接算是通过),该值也可以控制写入到模板缓存的,具体可以在pass/zfail/fail的StencilOp的枚举中来设置为replace。(这段我重新翻译了,因为官方的写的乱七八糟,我回头看了一下,我自己也没懂Unity的文档说的是啥,所以重新翻译该段)

ReadMask

读入的掩码

    ReadMask readMask

一个占8位的掩码,和 0~255的整型数值一样,用于在比较参考值(referenceValue & readkMask)与模板缓存的值(stencilBufferValue & readMask)。默认该ReadMask值为:255(8位bits全是1,意味所有位都可以通过掩码)。

WriteMask

写入的掩码

    WriteMask writeMask

一个占8位的掩码,和 0~255的整型数值一样,用于在写数据到模板缓存时的位掩码过滤。注意,就像其他的写入数据的掩码一样,指定哪些位是需要写入缓存中的(例如,WriteMask 0 意味着没有数据位的影响,且写入的将会是0)。默认是:255。

Comp

设置参考值与模板值的比较方式

    Comp comparisonFunction

这个function函数用于将参考值reference value与当前模板缓存的值比较时用的。默认是:always。(always的话,就是不同再比,直接pass过了的意思)

Pass

设置通过后的操作

    Pass stencilOperation

当参考值与模板值得比较测试通过后(且深度测试也通过后)应该怎么操作模板缓存中的值。默认:keep。(keep就是保持缓存的值不变,意思不会缓存值有任何操作)

Fail

    Fail stencilOperation

当参考值与模板值的比较测试失败后,应该怎么操作模板缓存中的值。默认:keep。(keep就是保持缓存的值不变,意思不会缓存值有任何操作)

ZFail

    ZFail stencilOperation

当参考值与模板值的比较通过后,但深度测试没有通过,应该怎么操作模板缓存中的值。默认:keep。(keep就是保持缓存的值不变,意思不会缓存值有任何操作)

Comp,Pass,Fail 和 ZFail 都将应用于几何体的正面测试,除非正面给剔除了,这种情况将用于背面测试。你也可以直接指定两种面向的模板测试的应用,使用这些来定义正面的:CompFront,PassFront,FailFont,ZFailFont(对几何体正面的),和CompBack,PassBack,FailBack,ZFailBack(对于几何体背面的)。

Comparison Function

参考值与缓存值的比较功能

下面有那么都是其一的比较方式:

枚举描述
Greater仅当参考值比缓存值大时才渲染该像素。
GEqual参考值大于或等于缓存值,才渲染像素。
Less小于缓存值
LEqaul小于或等于缓存值
Eqaul等于缓存值
NotEqaul不等于缓存值
Always不用测试,总是通过。(注意这个是默认值)
Never不用测试,反正都不通过

至于枚举对应的数值是那些,官方文档都没给出,但可以在 FrameDebugger 查看:数值对应的枚举是那些:查看:Unity ShaderLab Stencil Comp 枚举的对应数值

Stencil Operation

模板操作

以下都是操作枚举:

枚举描述
Keep保持当前缓存值。(默认值)
Zero将0写入缓存值。
Replace将参考值写入缓存值。
IncrSat将缓存值自增1.如果缓存值本身255的数值了,那么保持255数据不变。
DecrSat将缓存值自减1.如果缓存值本身0的数值了,那么保持0数据不变。
Invert缓存值位取反。(相当于这样子吧:~(Value in buffer)
IncrWrap将缓存值自增1.如果缓存值本身255的数值了,那么将数据倒回0。
DecrWrap将缓存值自减1.如果缓存值本身0的数值了,那么将数据倒回255。

Deferred rendering path

延迟渲染路径

渲染对象在延迟选中的模板测试功能会有些限制,在base pass和光照的pass的执行期间,模板缓存用于其他目的使用。在这两个阶段中,着色器中定义的模板状态将被忽略,只在最终pass的处理过程中有用。因此模板测试将会这些对象失效,但模板缓存值仍然会被后面渲染的对象修改。对象在正向渲染路径处理后,接着延迟渲染路径处理也将会再次设置这些模板状态。
(什么鬼?原文我真没看懂,还是去看Vulkan得Stencil文档,已去了解过,Vulkan上没看到有对延迟渲染与模板缓存的限制描述:https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html/vkspec.html#fragops-stencil)

延迟渲染路径中使用三个高位来存储模板缓存数据,根据在场景中多少光照使用了模板遮罩来决定再加上4个高位。可以使用模板读写掩码来过滤位操作,也可以使用相机在光照pass后强制清除模板缓冲区,使用Camera.clearStencilAfterLightingPass。(原文能懂,但为何写得这么难看)

Example

第一个例子,不论深度测试是否通过,将会向模板缓存写入"2"的值。模板测试比较时:always,不同比较总是通过。

Shader "Red" {
    SubShader {
        Tags { "RenderType"="Opaque" "Queue"="Geometry"}
        Pass {
            Stencil {
                Ref 2
                Comp always
                Pass replace
            }
        
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            struct appdata {
                float4 vertex : POSITION;
            };
            struct v2f {
                float4 pos : SV_POSITION;
            };
            v2f vert(appdata v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                return o;
            }
            half4 frag(v2f i) : SV_Target {
                return half4(1,0,0,1);
            }
            ENDCG
        }
    } 
}

第二个将会在上面第一个shader的pass(红色的着色)处理后,才能模板测试通过,因为它对模板缓存值检测等会2的值。在深度测试失败后它也会将缓存值自减1。

Shader "Green" {
    SubShader {
        Tags { "RenderType"="Opaque" "Queue"="Geometry+1"}
        Pass {
            Stencil {
                Ref 2
                Comp equal
                Pass keep 
                ZFail decrWrap
            }
        
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            struct appdata {
                float4 vertex : POSITION;
            };
            struct v2f {
                float4 pos : SV_POSITION;
            };
            v2f vert(appdata v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                return o;
            }
            half4 frag(v2f i) : SV_Target {
                return half4(0,1,0,1);
            }
            ENDCG
        }
    } 
}

第三个仅在缓存值为1才通过,所以只有红球和绿球相交的像素,红球是设置缓存为2,绿球是当深度测试失败(这里是深度被遮挡)时才将缓存值自减1,才能变为1值(所以这里只要用红球遮挡住绿球的部分,就是缓存值为1的地方,也就是蓝球可以显示的地方)。

Shader "Blue" {
    SubShader {
        Tags { "RenderType"="Opaque" "Queue"="Geometry+2"}
        Pass {
            Stencil {
                Ref 1
                Comp equal
            }
        
            CGPROGRAM
            #include "UnityCG.cginc"
            #pragma vertex vert
            #pragma fragment frag
            struct appdata {
                float4 vertex : POSITION;
            };
            struct v2f {
                float4 pos : SV_POSITION;
            };
            v2f vert(appdata v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                return o;
            }
            half4 frag(v2f i) : SV_Target {
                return half4(0,0,1,1);
            }
            ENDCG
        }
    }
}

结果:
在这里插入图片描述
另一个例子更直接的效果。第一个使用这个shader渲染的球让适当的模板缓存区域标记上。

Shader "HolePrepare" {
    SubShader {
        Tags { "RenderType"="Opaque" "Queue"="Geometry+1"}
        ColorMask 0
        ZWrite off
        Stencil {
            Ref 1
            Comp always
            Pass replace
        }

        CGINCLUDE
            struct appdata {
                float4 vertex : POSITION;
            };
            struct v2f {
                float4 pos : SV_POSITION;
            };
            v2f vert(appdata v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                return o;
            }
            half4 frag(v2f i) : SV_Target {
                return half4(1,1,0,1);
            }
        ENDCG

        Pass {
            Cull Front
            ZTest Less
        
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            ENDCG
        }
        Pass {
            Cull Back
            ZTest Greater
        
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            ENDCG
        }
    } 
}

然后再一个使用standard的surface shader渲染的,带有特殊的剔除正面的,禁用深度测试和模板测试来丢弃之前标记的像素。

Shader "Hole" {
    Properties {
        _Color ("Main Color", Color) = (1,1,1,0)
    }
    SubShader {
        Tags { "RenderType"="Opaque" "Queue"="Geometry+2"}

        ColorMask RGB
        Cull Front
        ZTest Always
        Stencil {
            Ref 1
            Comp notequal 
        }

        CGPROGRAM
        #pragma surface surf Lambert
        float4 _Color;
        struct Input {
            float4 color : COLOR;
        };
        void surf (Input IN, inout SurfaceOutput o) {
            o.Albedo = _Color.rgb;
            o.Normal = half3(0,0,-1);
            o.Alpha = 1;
        }
        ENDCG
    } 
}

结果:
在这里插入图片描述
(最后这个例子没有啥参考意义,可以不用太去了解)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值