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 등의 기술에 사용됩니다.
'Unity > Shader & VFX' 카테고리의 다른 글
Unity ShaderGraph Custom Function & Node 제작 가이드 (0) | 2024.10.02 |
---|---|
URP 14 - SimplePostBlur (0) | 2024.07.30 |
URP ScreenSpaceDecal Shader (0) | 2023.07.18 |
LinearEyeDepth와 Linear01Depth (0) | 2023.07.13 |
Unity Triplanar Shader 분석 & StaticWorldPosition 응용 (0) | 2023.07.10 |
WRITTEN BY
- CatDarkGame
Technical Artist dhwlgn12@gmail.com