반응형

Stencil Test란?

Stencil Buffer는 8bit 사이즈의 화면 버퍼이며 Fragment(픽셀)에 0 ~ 255 값을 기록합니다.

Stencil Test는 Stencil Buffer 정보를 기반으로 Fragment Shader를 동작시킬지 검사하는 작업이며 Rasterization 이후, Fragment Shader 전에 동작합니다.

Stencil Test에 통과 못한 Fragment는 Discard(폐기)되어 Fragment Shader 호출되지 않습니다.

(Stencil Test에 통과되지 못하면 Fragment 연산 자체가 안된다는 뜻)

 

GPU 렌더링 파이프라인

  • Vertex Stage
  • Rasterization(Fragment 생성)
  • Stencil Test
  • Depth(Z) Test
  • Fragment Stage
  • Blending & Color Write (프레임 버퍼 기록)
  • Depth Write & Stencil Write

 

Unity URP HLSL 기반 Stencil Shader Sample

Shader "CatDarkGame/StencilSample"
{
   Properties
    { 
        [MainTexture] _BaseMap("Texture", 2D) = "white" {}
        [MainColor] _BaseColor("Color", Color) = (1, 1, 1, 1)
        
        [Header(Cull Face)][Space(5)]
        [Enum(UnityEngine.Rendering.CullMode)] _CullMode("Cull Mode", Float) = 2
        
        [Header(Blend)][Space(5)]
        [Enum(UnityEngine.Rendering.BlendMode)] _SrcFactor("Src Factor", Float) = 1
        [Enum(UnityEngine.Rendering.BlendMode)] _DstFactor("Dst Factor", Float) = 0
        
        [Header(Depth)][Space(5)]
        [Enum(UnityEngine.Rendering.CompareFunction)] _ZTest("Z Test", Float) = 4
        [Enum(Off, 0, On, 1)] _ZWrite("ZWrite", Float) = 1
        
        [Header(Stencil)][Space(5)]
        [IntRange] _StencilRef("Reference", Range(0,255)) = 0
        [IntRange] _StencilReadMask("Read Mask", Range(0,255)) = 255
        [IntRange] _StencilWriteMask("Write Mask", Range(0,255)) = 255
        [Space(5)]
        [Enum(UnityEngine.Rendering.CompareFunction)] _StencilComp("Comparison", Int) = 8
        [Enum(UnityEngine.Rendering.StencilOp)] _StencilPass("OP-Pass", Int) = 0
        [Enum(UnityEngine.Rendering.StencilOp)] _StencilFail("OP-Fail", Int) = 0
        [Enum(UnityEngine.Rendering.StencilOp)] _ZFail("OP-ZFail", Int) = 0
        /*
        [Enum(UnityEngine.Rendering.CompareFunction)] _StencilCompBack("ComparisonBack", Int) = 8
        [Enum(UnityEngine.Rendering.StencilOp)] _StencilPassBack("OP-PassBack", Int) = 0
        [Enum(UnityEngine.Rendering.StencilOp)] _StencilFailBack("OP-FailBack", Int) = 0
        [Enum(UnityEngine.Rendering.StencilOp)] _ZFailBack("OP-ZFailBack", Int) = 0
        [Space(1)]
        [Enum(UnityEngine.Rendering.CompareFunction)] _StencilCompFront("ComparisonFront", Int) = 8
        [Enum(UnityEngine.Rendering.StencilOp)] _StencilPassFront("OP-PassFront", Int) = 0
        [Enum(UnityEngine.Rendering.StencilOp)] _StencilFailFront("OP-FailFront", Int) = 0
        [Enum(UnityEngine.Rendering.StencilOp)] _ZFailFront("OP-ZFailFront", Int) = 0
        
        */
    }

    SubShader
    {
         Tags
        {
            "RenderType" = "Opaque"
            "RenderPipeline" = "UniversalPipeline"
            "UniversalMaterialType" = "Lit"
            "IgnoreProjector" = "True"
        }

        Pass
        {
            Name  "Forward"
            Tags {"LightMode" = "UniversalForward"}

            Cull [_CullMode]
            ZWrite [_ZWrite]
            ZTest [_ZTest]
            Blend [_SrcFactor] [_DstFactor]
            
            Stencil
            {
                Ref [_StencilRef]
                ReadMask [_StencilReadMask]
                WriteMask [_StencilWriteMask]

                Comp [_StencilComp]
                Pass [_StencilPass]
                Fail [_StencilFail]
                ZFail [_ZFail]
                /*                             
                CompFront [_StencilCompBack]
                PassFront [_StencilPassBack]
                FailFront [_StencilFailBack]
                ZFailFront [_ZFailBack]

                CompBack [_StencilCompFront]
                PassBack [_StencilPassFront]
                FailBack [_StencilFailFront]
                ZFailBack [_ZFailFront]
                */
            }

            HLSLPROGRAM
            #pragma target 4.5
            #pragma exclude_renderers gles d3d9
            
            #pragma vertex vert
            #pragma fragment frag

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            
            CBUFFER_START(UnityPerMaterial)
                float4 _BaseMap_ST;
                half4 _BaseColor;
            CBUFFER_END

            TEXTURE2D(_BaseMap);    SAMPLER(sampler_BaseMap);
            
            struct Attributes
            {
                float4 positionOS    : POSITION;
                float2 uv            : TEXCOORD0;
            };

            struct Varyings
            {
                float4 positionCS    : SV_POSITION;
                float2 uv            : TEXCOORD0;
            }; 
            

            Varyings vert(Attributes input)
            {
                Varyings output = (Varyings)0;

                float4 positionOS = input.positionOS;
                float3 positionWS = TransformObjectToWorld(positionOS.xyz);
                float4 positionCS = TransformWorldToHClip(positionWS);

                output.positionCS = positionCS;
                output.uv = input.uv * _BaseMap_ST.xy + _BaseMap_ST.zw;
                return output;
            }

            half4 frag(Varyings input) : SV_Target
            {
                float2 baseMapUV = input.uv.xy;
                half4 col = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, baseMapUV);
              
                half4 finalColor = col * _BaseColor;
                return finalColor;
            }
            
            ENDHLSL
        }
    }
}

 

 

Stencil 커맨드 설명

Stencil {
    Ref 1         // Stencil 비교 기준값 (Reference Value)
    Comp Equal    // Stencil Buffer 값 == Ref(1)일 때 통과
    Pass Keep     // Stencil Test 통과 시, Stencil Buffer 값 변경 없음
    Fail Zero     // Stencil Test 실패 시, Stencil Buffer 값을 0으로 설정
}
Stencil OP 옵션
Keep 기존 Stencil Buffer 값을 유지
Zero Stencil Buffer 값을 0으로 설정
Replace Ref 값으로 Stencil Buffer 값을 변
IncrSat Stencil Buffer 값을 1 증가 (최대 255, 초과 시 255 유지)
DecrSat Stencil Buffer 값을 1 감소 (최소 0, 이하 시 0 유지)
Invert Stencil Buffer 비트를 반전
IncrWrap Stencil Buffer 값을 1 증가 (255 초과 시 0으로 Wrap)
DecrWrap Stencil Buffer 값을 1 감소 (0 이하 시 255로 Wrap)

 

 

Stencil Test 응용 예제 1 - Ref & Comp

  Red Green Blue
Render Queue 2000 2001 2002
ZTest LessEqual Disable Disable
Stencil Ref 128 128 128
Stencil Comp Always Equal Equal
StencilOp Pass Replace Zero Keep

 

해석

  • Stencil Test는 Pass 별로 동작하기 때문에 Render Queue(렌더 순서)에 영향 받습니다.
  • Red의 StencilOp Pass는 Replace이기 때문에 Stencil Test에 통과된 픽셀에 Stencil Buffer에 Stencil Ref 값 128을 기록합니다.
  • Red의 Stencil Comp가 Always이기 때문에 Stencil Test를 항상 통과합니다. 하지만 ZTest가 LessEqual이기 때문에 Red보다 앞에 그려진 픽셀은 렌더링되지 않습니다.
  • Green과 Blue는 Red 뒤에 위치하기 때문에 ZTest를 비활성화해야 렌더링될 수 있습니다.
  • Green은 Stencil Comp가 Equal이기 때문에 Green이 렌더링되는 픽셀의 Stencil Buffer가 Stencil Ref 128과 동일해야 Stencil Test 통과됩니다.
  • Green의 StencilOp Pass가 Zero이기 때문에 Green의 Stencil Test가 통과된 픽셀의 Stencil Buffer 값은 0이됩니다.
  • Blue는 Green과 Stencil Ref & Comp 값이 동일하지만 Green이 렌더링되면서 Stencil Buffer 값을 0으로 만들었기 때문에 Green이 렌더링되지 않은 픽셀에만 렌더링됩니다.

 

Stencil Test 응용 예제 2 - Read & Write Mask

  Red Green 캐릭터 Blue
Render Queue 2000 2001 2002 3000
ZTest LessEqual LessEqual LessEqual Always
Stencil Ref 128 129 131 15
Stencil Read Mask 255 255 255 1
Stencil Write Mask 255 1 1 255
Stencil Comp Always Always Always Equal
StencilOp Pass Replace Zero Zero Keep
StnecilOp ZFail Replace Replace Replace Keep

 

Stencil Read & Write Mask 개념

Stencil Comp(비교)는 Integer(정수) 기준으로 연산합니다.

Stencil Read & Write Mask를 Stencil Buffer & Ref 값을 8비트 이진수 Mask 기준으로 처리됩니다.

기본 값이 255이기 때문에 8비트 전부를 기록하지만, 만약 Mask 값을 128로 세팅한다면 0번 비트만 Stencil Buffer에 기록합니다. 

위 예시로 캐릭터의 Ref 값은 131이지만 Stencil Write가 1이기 때문에 7번 비트만 Stencil Buffer에 기록하게 되어 Red와 캐릭터가 겹쳐서 렌더링된 픽셀의 Stencil Buffer 값은 131이 아닌 129가 됩니다.

 

Stencil Test 응용 예제 3 - Stencil Volume Mask

  Yellow Gray Red Green
Render Queue 2000 2001 3000 3001
Cull Back Back Back Front
ZTest LessEqual LessEqual GreaterEqual GreaterEqual
Stencil Ref 0 224 1 128
Stencil Read Mask 255 255 255 129
Stencil Write Mask 255 255 1 255
Stencil Comp Always Always Always Less
StencilOp Pass Replace Replace Keep Keep
StnecilOp ZFail Keep Keep Replace Keep

 

Stencil Volume Mask는 Stencil & Depth Test를 통해 Volume과 겹친 픽셀을 검출하는 기법입니다.

Stencil Deferred Additional Light, Per Object Shadow 등의 기술에 사용됩니다.

 

반응형

WRITTEN BY
CatDarkGame
Technical Artist dhwlgn12@gmail.com

,