book/20-urp-pass-tags-and-lightmode-contract.md

20. URP Pass Tags & LightMode Contract

Unity 6.3(6000.3) / URP 17.3.0 기준으로 ShaderLab Pass의 LightMode가 URP 파이프라인에서 언제/왜 소비되는지(=계약)를 정리한다

20. URP Pass tags & LightMode contract (Unity 6.3 / URP 17.3.0)

이 챕터는 “URP 호환 셰이더”를 만들 때 가장 자주 깨지는 지점인 Pass 계약을 고정합니다.

질문을 이렇게 바꿔서 생각하면 빠릅니다.

  • “이 LightMode Pass가 없으면, URP의 어느 단계가 실패(또는 fallback)하나?”
  • “이 Pass는 어떤 출력(RenderTarget/Stencil/ColorMask)을 약속하나?”
  • “어떤 프로젝트 기능(Deferred/TAA/XR/2D)이 이 Pass를 소비하나?”

원칙

  • “정확 목록”은 로컬 소스 기반으로 고정합니다(Generated).
  • “언제/왜 소비되는가”는 URP Runtime(C#)과 셰이더(Pass states)를 함께 봅니다.

20.0 정확 레퍼런스(Generated)

20.1 ShaderLab Pass 선택 규칙(요약): URP는 ShaderTagId 리스트로 Pass를 고른다

URP의 “오브젝트 그리기 패스”는 내부적으로 ShaderTagId 리스트를 가지고 있고, 그 리스트에 맞는 ShaderLab Pass를 찾아 그립니다.

URP 17.3.0 예시(Forward Opaques/Transparents 기본 태그 리스트):

  • <URP>/Runtime/Passes/DrawObjectsPass.cs:91
    • SRPDefaultUnlit, UniversalForward, UniversalForwardOnly

Deferred(=GBuffer) 렌더러를 켜면, “GBuffer + ForwardOnly” 전략이 섞입니다.

  • <URP>/Runtime/UniversalRenderer.cs:388 주석 요약
    • Deferred 가능 머티리얼: UniversalForward + UniversalGBuffer 제공 권장
    • Deferred 불가 머티리얼(언릿/특수): UniversalForwardOnly 권장
    • (레거시) unnamed pass는 SRPDefaultUnlit로 간주되어 forward-only로 처리될 수 있음

즉, LightMode는 단순 태그가 아니라 파이프라인 소비 규칙과 1:1로 연결된 계약 키입니다.

20.1.1 Pass 선택 우선순위를 코드처럼 이해하기(개념)

URP의 오브젝트 드로우 패스는 내부 ShaderTagId 목록을 순회하면서 “첫 매칭 pass”를 선택하는 방식으로 이해하면 디버깅이 빨라집니다.

C#
// 개념 의사코드(URP DrawObjectsPass 동작 모델)
ShaderTagId[] tags =
{
    new ShaderTagId("SRPDefaultUnlit"),
    new ShaderTagId("UniversalForward"),
    new ShaderTagId("UniversalForwardOnly")
};

foreach (var tag in tags)
{
    if (material.HasPassWithLightMode(tag))
    {
        Draw(tag);
        break; // 첫 매칭 pass 사용
    }
}

실전에서는 최종 매칭 결과를 Frame Debugger의 Shader Pass에서 확인하는 것을 기준으로 삼으세요.

20.1.2 Name보다 LightMode가 계약의 기준

ShaderLab의 Pass Name은 디버깅 가독성에는 유용하지만, URP의 pass 선택 계약은 기본적으로 Tags { "LightMode"="..." }를 기준으로 동작합니다.

즉:

  • Name "ForwardLit"인데 LightMode가 없으면 URP가 기대 경로에서 못 찾을 수 있습니다.
  • 반대로 Name이 달라도 LightMode가 맞으면 소비 단계에서 정상 매칭될 수 있습니다.

실무 규칙으로는 “Name은 사람용, LightMode는 파이프라인 계약용”으로 구분해서 관리하는 것이 안전합니다.

20.2 URP 17.3.0 Lit.shader 기준 LightMode 9종(“실제 목록”)

Lit.shader가 실제로 포함하는 LightMode(Generated 기준):

  • UniversalForward
  • UniversalGBuffer
  • ShadowCaster
  • DepthOnly
  • DepthNormals
  • Meta
  • MotionVectors
  • XRMotionVectors
  • Universal2D

정확 include roots/개수는 여기서 확인:

20.2.1 왜 UniversalForwardOnly는 Lit 9종 목록에 없나?

UniversalForwardOnly는 “Deferred 경로를 사용할 수 없는 머티리얼”을 위해 준비하는 계약입니다.
URP Lit 기본 셰이더는 UniversalForward + UniversalGBuffer 조합으로 Deferred를 처리하기 때문에, 기본 Pass 목록에 UniversalForwardOnly가 없는 것이 정상입니다.

즉:

  • Lit 기본형: UniversalForward + UniversalGBuffer
  • 특수/언릿/Deferred 비호환 셰이더: UniversalForwardOnly를 별도 전략으로 사용

20.3 LightMode 계약 표(URP 17.3.0 Lit 기준)

아래 표는 “무슨 단계가 이 LightMode를 소비하는지”와 “셰이더가 무엇을 출력해야 하는지”를 한 번에 보기 위한 것입니다.

표를 읽는 법

  • 소비 단계: URP가 그 Pass를 호출하는 대표적인 파이프라인 단계(모드/기능에 따라 달라질 수 있음)
  • 핵심 출력: RenderTarget/Stencil/ColorMask 관점의 계약(학습/디버깅 핵심)
  • include 루트: Lit.shader가 해당 Pass에서 직접 include하는 핵심 파일(URP 17.3.0 기준)
LightMode 소비 단계(대표) 핵심 출력(요약) include 루트(대표) 빠졌을 때 대표 증상 필수/옵션 가이드
UniversalForward Forward 컬러(Forward+/투명 포함) 색(SV_Target0) + (옵션) Rendering Layers(SV_Target1) <URP>/Shaders/LitForwardPass.hlsl, <URP>/Shaders/LitInput.hlsl 오브젝트가 기본 컬러 경로에서 비정상(fallback/미출력) 거의 모든 프로젝트에서 필수급
UniversalGBuffer Deferred(GBuffer) MRT로 GBuffer 작성 <URP>/Shaders/LitGBufferPass.hlsl, <URP>/Shaders/LitInput.hlsl Deferred에서 조명/재질이 깨지거나 forward-only fallback 증가 Deferred면 필수급
ShadowCaster 그림자 맵 depth 기록(+알파 클립) <URP>/Shaders/ShadowCasterPass.hlsl, <URP>/Shaders/LitInput.hlsl 특정 재질 그림자가 사라짐/컷아웃 그림자 오류 그림자 쓰면 필수급
DepthOnly DepthTexture/Depth prepass depth 기록(색은 보통 마스킹) <URP>/Shaders/DepthOnlyPass.hlsl, <URP>/Shaders/LitInput.hlsl SceneDepth 기반 효과가 불안정/깊이 의존 경로 깨짐 DepthTexture/priming 사용 시 중요
DepthNormals DepthNormalsTexture depth+normal 기록 <URP>/Shaders/LitDepthNormalsPass.hlsl, <URP>/Shaders/LitInput.hlsl SSAO/Outline/SSR류가 즉시 깨짐 SSAO/Outline/SSR류면 사실상 필수
Meta 베이킹(Lightmap) meta(알베도/에미션) <URP>/Shaders/LitMetaPass.hlsl, <URP>/Shaders/LitInput.hlsl 베이크 결과가 비정상(어둡거나 에미션 누락) 베이킹이면 필수급
MotionVectors 모션 벡터(velocity) RG velocity 출력 <URP>/ShaderLibrary/ObjectMotionVectors.hlsl, <URP>/Shaders/LitInput.hlsl TAA/모션블러/리프로젝션 고스팅/번짐 TAA/모션블러/리프로젝션이면 중요
XRMotionVectors XR 모션 벡터 RGBA velocity(+stencil 계약 가능) <URP>/ShaderLibrary/ObjectMotionVectors.hlsl, <URP>/Shaders/LitInput.hlsl XR에서만 리프로젝션/모션 관련 아티팩트 XR에서 모션 벡터가 필요하면 중요
Universal2D 2D Renderer 2D 계약 컬러 출력 <URP>/Shaders/Utils/Universal2D.hlsl, <URP>/Shaders/LitInput.hlsl 2D Renderer에서 재질이 비정상 렌더 2D Renderer 호환 시 필요

20.3.1 기능별 최소 Pass 세트(빠른 의사결정용)

프로젝트 조건 최소 권장 LightMode 세트
기본 3D(Forward) UniversalForward, ShadowCaster, DepthOnly, Meta
+ 화면기반 노말 효과(SSAO/Outline/SSR) 기본 + DepthNormals
Deferred 사용 기본 + UniversalGBuffer
+ TAA/모션블러/리프로젝션 해당 세트 + MotionVectors
+ XR 모션 리프로젝션 해당 세트 + XRMotionVectors
2D Renderer 대응 Universal2D 추가(또는 전용 셰이더 분리)

빠르게 시작할 때는 samples/urp/URP_LitCompatibleTemplate.shader를 기준으로 필요한 pass만 남기는 방식이 안전합니다.

20.3.2 Pass 상태(State) 계약 퀵 레퍼런스

LightMode가 맞아도 pass 상태가 틀리면 결과가 깨질 수 있습니다. 최소한 아래 상태는 함께 점검하세요.

LightMode 상태 계약(대표) 점검 포인트
DepthOnly ZWrite On, ColorMask 0 깊이만 기록되고 컬러 쓰기 차단되는지
ShadowCaster ZWrite On, 적절한 ZTest 컷아웃 재질에서 알파클립과 컷오프 일치 여부
MotionVectors ColorMask RG velocity가 RG 채널에 기록되는지
XRMotionVectors ColorMask RGBA + (프로젝트별) stencil XR 리프로젝션 경로의 스텐실 계약 포함 여부
UniversalForward 보통 컬러 출력 + 필요 시 SV_Target1 Rendering Layers 사용 시 추가 타겟 출력 계약 유지

20.4 MotionVectors 계약(특히 자주 깨짐)

URP 17.3.0에서 Motion Vectors는 C#에서도 명시적으로 “MotionVectors” 태그를 소비합니다.

  • <URP>/Runtime/Passes/MotionVectorRenderPass.cs:14
    • k_MotionVectorsLightModeTag = "MotionVectors"

셰이더 쪽 핵심 포인트:

  • Lit.shader는 ObjectMotionVectors.hlsl#include_with_pragmas로 포함합니다.
    • pragma가 include 내부에 있는 경우, 일반 #include만 사용하면 누락될 수 있습니다.
  • MotionVectors는 “색”이 아니라 “velocity”를 출력하는 단계이므로, 색/알파 로직과 분리해서 생각해야 합니다.

20.4.1 최소 MotionVectors pass 스니펫

ShaderLab
Pass
{
    Name "MotionVectors"
    Tags { "LightMode" = "MotionVectors" }
    ColorMask RG

    HLSLPROGRAM
    #pragma shader_feature_local _ALPHATEST_ON
    #pragma multi_compile _ LOD_FADE_CROSSFADE
    #pragma shader_feature_local_vertex _ADD_PRECOMPUTED_VELOCITY

    #include "Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl"
    #include_with_pragmas "Packages/com.unity.render-pipelines.universal/ShaderLibrary/ObjectMotionVectors.hlsl"
    ENDHLSL
}

XR 대응이 필요하면 XRMotionVectors pass를 별도로 두고 stencil/define 계약까지 맞추는 방식이 안전합니다(샘플: samples/urp/URP_LitCompatibleTemplate.shader).

20.4.2 흔한 오구현: #include만 써서 pragma 누락

아래는 같은 파일을 포함해도 계약 안정성이 달라지는 대표 사례입니다.

HLSL
// 위험: include 내부 pragma가 반영되지 않을 수 있음
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/ObjectMotionVectors.hlsl"

// 권장: Lit.shader와 같은 방식
#include_with_pragmas "Packages/com.unity.render-pipelines.universal/ShaderLibrary/ObjectMotionVectors.hlsl"

MotionVectors 관련 이슈가 재현될 때는 include 형태부터 먼저 점검하면 원인 축소가 빠릅니다.

20.5 DepthNormals 계약(SSAO/Outline 기반)

DepthNormals는 URP에서 별도의 Pass로 소비됩니다.

  • <URP>/Runtime/Passes/DepthNormalOnlyPass.cs:19
    • 기본 태그 리스트: DepthNormals, DepthNormalsOnly

실무에서 깨지는 지점:

  • 노말 공간(WS/VS)과 인코딩 방식이 URP 구현과 다르면, SSAO/Outline이 즉시 깨집니다.
  • “내 셰이더가 DepthNormals에 참여하지 않는다”면, 포스트 효과에서 기대하는 텍스처가 빈 값이 됩니다.

20.5.1 최소 DepthNormals pass 스니펫

ShaderLab
Pass
{
    Name "DepthNormals"
    Tags { "LightMode"="DepthNormals" }
    ZWrite On

    HLSLPROGRAM
    #pragma target 4.5
    #pragma vertex DepthNormalsVertex
    #pragma fragment DepthNormalsFragment
    #pragma multi_compile_instancing
    #pragma shader_feature_local_fragment _ALPHATEST_ON

    #include "Packages/com.unity.render-pipelines.universal/Shaders/LitDepthNormalsPass.hlsl"
    ENDHLSL
}

직접 구현으로 바꿀 때는 normal 인코딩/공간 변환까지 소비 측(SSAO/Outline)과 함께 검증해야 합니다.

20.6 Deferred 계약: UniversalForward vs UniversalForwardOnly vs UniversalGBuffer

Deferred 렌더러를 켠 프로젝트에서 호환성 체크는 이렇게 하면 빠릅니다.

  1. Frame Debugger에서 opaque draw가 GBuffer로 들어가는지 확인
  2. 머티리얼이 UniversalGBuffer pass를 제공하는지 확인
  3. Deferred 불가 머티리얼은 UniversalForwardOnly로 분리되는지 확인

URP 소스 주석(핵심 요약, URP 17.3.0):

  • <URP>/Runtime/UniversalRenderer.cs:388
    • “Deferred 가능 머티리얼은 UniversalForward + UniversalGBuffer 제공”
    • “Deferred 불가 머티리얼은 UniversalForwardOnly 제공”

20.6.1 Deferred용 최소 Pass 조합 스니펫

ShaderLab
// Deferred 친화형 머티리얼
Pass { Tags { "LightMode"="UniversalForward" } }   // 투명/특수 경로 포함
Pass { Tags { "LightMode"="UniversalGBuffer" } }   // Opaque deferred 경로
Pass { Tags { "LightMode"="ShadowCaster" } }
Pass { Tags { "LightMode"="DepthOnly" } }
Pass { Tags { "LightMode"="DepthNormals" } }       // 화면기반 효과가 있으면 권장

Deferred 비호환 재질(특수 언릿/커스텀)이라면 UniversalForwardOnly 전략으로 별도 셰이더를 분리하는 것이 운영상 안전합니다.

20.6.2 UniversalForwardOnly 최소 스니펫(특수/비Deferred 재질용)

ShaderLab
Pass
{
    Name "ForwardOnly"
    Tags { "LightMode"="UniversalForwardOnly" }

    HLSLPROGRAM
    #pragma target 4.5
    #pragma vertex LitPassVertex
    #pragma fragment LitPassFragment
    #pragma multi_compile_instancing
    #pragma multi_compile_fog
    #pragma shader_feature_local_fragment _ALPHATEST_ON

    #include "Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl"
    #include "Packages/com.unity.render-pipelines.universal/Shaders/LitForwardPass.hlsl"
    ENDHLSL
}

Deferred renderer에서도 해당 재질을 forward-only로 안정적으로 분리하려면, 이 pass를 “분리 전략”으로 명시해 두는 편이 안전합니다.

20.7 디버깅 루틴: “URP가 내 Pass를 쓰고 있나?”

A) Frame Debugger

  1. 드로우콜을 선택해 “Shader Pass”가 무엇인지 본다(ForwardLit/GBuffer/DepthNormals/MotionVectors 등)
  2. 기대한 LightMode로 그려지고 있는지 확인한다

B) RenderGraph Viewer

  1. Depth/Normals/History 리소스가 생성되는지, 어느 패스에서 쓰이는지 확인한다
  2. MotionVectors가 depth에 의존(리프로젝션)하는지 흐름을 확인한다

C) Generated로 빠르게 원인 좁히기

D) 소스 검색으로 계약 누락 찾기(빠른 레시피)

POWERSHELL
rg -n 'LightMode"\\s*=\\s*"(UniversalGBuffer|DepthNormals|MotionVectors|XRMotionVectors)"' samples Assets
rg -n 'include_with_pragmas\\s+".*ObjectMotionVectors.hlsl"' samples Assets

첫 번째 명령으로 pass 존재 여부를, 두 번째 명령으로 MotionVectors pragma 누락 여부를 빠르게 점검할 수 있습니다.

E) 증상 기반 1차 확인 매트릭스

증상 먼저 볼 항목 다음 확인
Deferred에서만 재질 깨짐 UniversalGBuffer pass 존재/매칭 UniversalForwardOnly 분리 필요성
SSAO/Outline 불량 DepthNormals pass 매칭 노말 공간/인코딩 일치 여부
TAA/모션블러 고스팅 MotionVectors pass 매칭 include_with_pragmas 사용 여부
XR에서만 리프로젝션 이상 XRMotionVectors pass 매칭 stencil/define 계약 포함 여부
그림자 누락/컷아웃 오류 ShadowCaster pass 매칭 _ALPHATEST_ON + _Cutoff 일치 여부

20.8 체크리스트(수용 기준)

  • 프로젝트가 Deferred면 UniversalGBuffer가 실제로 소비되고 있는가?
  • TAA/모션블러/리프로젝션이 관여하면 MotionVectors pass가 실제로 소비되고 있는가?
  • SSAO/Outline이 있으면 DepthNormals pass가 실제로 소비되고 있는가?
  • Forward+ 환경이면 _CLUSTER_LIGHT_LOOP variant와 LIGHT_LOOP_BEGIN/END 패턴이 존재하는가?

20.8.1 완료 기준(권장)

아래 4가지를 만족하면 “Pass 계약” 보강이 완료된 상태로 봐도 됩니다.

  1. Frame Debugger에서 기대한 LightMode가 실제 drawcall에 매칭된다.
  2. RenderGraph Viewer에서 DepthNormals/MotionVectors 리소스 생성-소비 흐름이 확인된다.
  3. Deferred/Forward+/XR ON/OFF 조합에서 동일 머티리얼이 기능 회귀 없이 동작한다.
  4. samples/urp/URP_LitCompatibleTemplate.shader 대비 필요한 pass만 남긴 최소 세트가 문서화되어 있다.