TOOLS GAMES DEVLOG DOCS ABOUT CONTACT
HOME > DOCS > Shader / URP > URP 스텐실 버퍼(Stencil Buffer) 3D 포탈 셰이더
Shader / URP Intermediate Unity 2021.3+ / Unity 6 (URP)

URP 스텐실 버퍼(Stencil Buffer) 3D 포탈 셰이더

특정 포탈 게이트 평면을 통해서만 내부 3D 공간과 오브젝트가 보이도록 만드는 URP 스텐실 버퍼 마스킹 셰이더 기법과 렌더 큐 설정.

#Shader #URP #Stencil #HLSL #Rendering
핵심 개념스텐실 버퍼는 각 픽셀마다 8비트 정수(0~255) 값을 기록할 수 있는 버퍼입니다. 포탈 문(Mask)이 먼저 스텐실 버퍼에 특정 숫자(예: 1)를 기록하고, 포탈 안쪽 공간(Content)은 스텐실 버퍼가 1인 픽셀에만 자신을 그리도록 제한하면 별도의 보조 카메라나 렌더 텍스처 없이 완벽한 3D 포탈을 구현할 수 있습니다.

1. 동작 원리 및 렌더 큐 순서

이 기법의 성패는 렌더 큐(Render Queue) 순서에 달려 있습니다. 렌더 큐 순서가 맞지 않으면 마스크가 먼저 버퍼를 채우지 못해 공간이 가려지거나 뚫려 보이지 않습니다.

  1. 1단계 (마스크 오브젝트): 포탈 입구 평면입니다. 화면에 실제 색상을 칠하지 않지만(ColorMask 0), 스텐실 버퍼에 참조값(Ref 1)을 씁니다. 렌더 큐는 Geometry-1 (1999)로 설정해 일반 물체보다 먼저 그려지게 합니다.
  2. 2단계 (포탈 내부 오브젝트): 문 뒤에 숨겨진 방이나 오브젝트입니다. 스텐실 버퍼가 1인 곳에만 그려지도록 검사(Comp Equal)합니다. 렌더 큐는 기본 Geometry (2000)를 사용합니다.
  3. 3단계 (일반 월드 오브젝트): 스텐실 검사를 하지 않는 통상적인 물체들입니다.

2. 포탈 마스크 셰이더 (Portal Mask)

색상을 출력하지 않고 깊이 버퍼와 스텐실 버퍼만 조작하는 URP 언릿 셰이더입니다.

PortalMask.shader
Shader "Custom/URP/PortalMask"
{
    Properties
    {
        [IntRange] _StencilRef("Stencil ID", Range(0, 255)) = 1
    }
    SubShader
    {
        Tags 
        {
            "RenderType" = "Opaque"
            "RenderPipeline" = "UniversalPipeline"
            "Queue" = "Geometry-1"
        }

        Pass
        {
            Name "MaskPass"
            ZWrite On
            ColorMask 0 // 픽셀 색상 버퍼에는 아무것도 기록하지 않음

            Stencil
            {
                Ref [_StencilRef]
                Comp Always
                Pass Replace
            }

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            struct Attributes
            {
                float4 positionOS : POSITION;
            };

            struct Varyings
            {
                float4 positionCS : SV_POSITION;
            };

            Varyings vert(Attributes input)
            {
                Varyings output;
                output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
                return output;
            }

            half4 frag(Varyings input) : SV_Target
            {
                return half4(0, 0, 0, 0);
            }
            ENDHLSL
        }
    }
}

3. 포탈 내부 오브젝트 셰이더 (Portal Content)

스텐실 버퍼가 마스크의 ID와 일치할 때만 픽셀을 렌더링하는 셰이더입니다.

PortalContent.shader
Shader "Custom/URP/PortalContent"
{
    Properties
    {
        _BaseMap("Texture", 2D) = "white" {}
        _BaseColor("Color", Color) = (1, 1, 1, 1)
        [IntRange] _StencilRef("Stencil ID", Range(0, 255)) = 1
    }
    SubShader
    {
        Tags
        {
            "RenderType" = "Opaque"
            "RenderPipeline" = "UniversalPipeline"
            "Queue" = "Geometry"
        }

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

            Stencil
            {
                Ref [_StencilRef]
                Comp Equal    // 스텐실 버퍼 값이 _StencilRef와 같을 때만 통과
                Pass Keep
            }

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            struct Attributes
            {
                float4 positionOS : POSITION;
                float2 uv : TEXCOORD0;
            };

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

            TEXTURE2D(_BaseMap);
            SAMPLER(sampler_BaseMap);

            CBUFFER_START(UnityPerMaterial)
                float4 _BaseMap_ST;
                half4 _BaseColor;
            CBUFFER_END

            Varyings vert(Attributes input)
            {
                Varyings output;
                output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
                output.uv = TRANSFORM_TEX(input.uv, _BaseMap);
                return output;
            }

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

4. 파라미터 및 인스펙터 설정

프로퍼티권장값설명
_StencilRef (Mask)1포탈 문 평면에 부여할 스텐실 ID (0~255 사이 정수)
_StencilRef (Content)1내부 오브젝트가 검사할 스텐실 ID (Mask와 동일해야 보임)
Queue (Mask)Geometry-1 (1999)일반 불투명 오브젝트보다 먼저 그려져 스텐실 버퍼를 선점해야 함
Queue (Content)Geometry (2000)마스크가 기록된 직후 그려지도록 설정
ZWriteOn포탈 앞을 다른 물체가 가로막을 때 올바르게 가려지도록 깊이 기록 유지

5. 실무 주의점 및 트러블슈팅

  • Depth Prepass 문제: URP Forward+ 또는 Depth Priming이 켜져 있는 파이프라인 에셋의 경우 깊이 버퍼가 먼저 기록되어 렌더 큐가 의도와 다르게 동작할 수 있습니다. 마스크 패스가 Depth Prepass에 포함되지 않도록 DepthNormals / DepthOnly 패스를 생략하거나 큐 오프셋을 주의 깊게 지정해야 합니다.
  • 다중 포탈 구현 시 ID 분리: 포탈 A는 Stencil Ref 1, 포탈 B는 Stencil Ref 2로 지정하여 방마다 서로 다른 ID를 배정하면 하나의 씬에서 여러 포탈이 상호 간섭하지 않습니다.
  • 후면 컬링(Cull Back): 포탈 입구 뒤쪽에서 앞으로 바라볼 때는 마스크가 렌더링되지 않도록 평면(Quad)의 노멀 방향을 진입 방향으로 맞춰야 합니다.