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)
- Lit Pass/Include 맵(정확 Pass 목록):
book/generated/urp-lit-map.md - Lit 핵심 심볼 xref:
book/generated/urp-17.3.0/xref/lit-key-symbols.md
20.1 ShaderLab Pass 선택 규칙(요약): URP는 ShaderTagId 리스트로 Pass를 고른다
URP의 “오브젝트 그리기 패스”는 내부적으로 ShaderTagId 리스트를 가지고 있고, 그 리스트에 맞는 ShaderLab Pass를 찾아 그립니다.
URP 17.3.0 예시(Forward Opaques/Transparents 기본 태그 리스트):
<URP>/Runtime/Passes/DrawObjectsPass.cs:91SRPDefaultUnlit,UniversalForward,UniversalForwardOnly
Deferred(=GBuffer) 렌더러를 켜면, “GBuffer + ForwardOnly” 전략이 섞입니다.
<URP>/Runtime/UniversalRenderer.cs:388주석 요약- Deferred 가능 머티리얼:
UniversalForward+UniversalGBuffer제공 권장 - Deferred 불가 머티리얼(언릿/특수):
UniversalForwardOnly권장 - (레거시) unnamed pass는
SRPDefaultUnlit로 간주되어 forward-only로 처리될 수 있음
- Deferred 가능 머티리얼:
즉, LightMode는 단순 태그가 아니라 파이프라인 소비 규칙과 1:1로 연결된 계약 키입니다.
20.1.1 Pass 선택 우선순위를 코드처럼 이해하기(개념)
URP의 오브젝트 드로우 패스는 내부 ShaderTagId 목록을 순회하면서 “첫 매칭 pass”를 선택하는 방식으로 이해하면 디버깅이 빨라집니다.
// 개념 의사코드(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 기준):
UniversalForwardUniversalGBufferShadowCasterDepthOnlyDepthNormalsMetaMotionVectorsXRMotionVectorsUniversal2D
정확 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:14k_MotionVectorsLightModeTag = "MotionVectors"
셰이더 쪽 핵심 포인트:
- Lit.shader는
ObjectMotionVectors.hlsl를#include_with_pragmas로 포함합니다.- pragma가 include 내부에 있는 경우, 일반
#include만 사용하면 누락될 수 있습니다.
- pragma가 include 내부에 있는 경우, 일반
- MotionVectors는 “색”이 아니라 “velocity”를 출력하는 단계이므로, 색/알파 로직과 분리해서 생각해야 합니다.
20.4.1 최소 MotionVectors pass 스니펫
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 누락
아래는 같은 파일을 포함해도 계약 안정성이 달라지는 대표 사례입니다.
// 위험: 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 스니펫
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 렌더러를 켠 프로젝트에서 호환성 체크는 이렇게 하면 빠릅니다.
- Frame Debugger에서 opaque draw가
GBuffer로 들어가는지 확인 - 머티리얼이
UniversalGBufferpass를 제공하는지 확인 - Deferred 불가 머티리얼은
UniversalForwardOnly로 분리되는지 확인
URP 소스 주석(핵심 요약, URP 17.3.0):
<URP>/Runtime/UniversalRenderer.cs:388- “Deferred 가능 머티리얼은 UniversalForward + UniversalGBuffer 제공”
- “Deferred 불가 머티리얼은 UniversalForwardOnly 제공”
20.6.1 Deferred용 최소 Pass 조합 스니펫
// 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 재질용)
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
- 드로우콜을 선택해 “Shader Pass”가 무엇인지 본다(ForwardLit/GBuffer/DepthNormals/MotionVectors 등)
- 기대한 LightMode로 그려지고 있는지 확인한다
B) RenderGraph Viewer
- Depth/Normals/History 리소스가 생성되는지, 어느 패스에서 쓰이는지 확인한다
- MotionVectors가 depth에 의존(리프로젝션)하는지 흐름을 확인한다
C) Generated로 빠르게 원인 좁히기
book/generated/urp-lit-map.md에서 해당 LightMode Pass의 include roots를 확인book/generated/urp-17.3.0/xref/lit-key-symbols.md에서 “정의 위치 + 대표 호출처”를 확인
D) 소스 검색으로 계약 누락 찾기(빠른 레시피)
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/모션블러/리프로젝션이 관여하면
MotionVectorspass가 실제로 소비되고 있는가? - SSAO/Outline이 있으면
DepthNormalspass가 실제로 소비되고 있는가? - Forward+ 환경이면
_CLUSTER_LIGHT_LOOPvariant와LIGHT_LOOP_BEGIN/END패턴이 존재하는가?
20.8.1 완료 기준(권장)
아래 4가지를 만족하면 “Pass 계약” 보강이 완료된 상태로 봐도 됩니다.
- Frame Debugger에서 기대한 LightMode가 실제 drawcall에 매칭된다.
- RenderGraph Viewer에서 DepthNormals/MotionVectors 리소스 생성-소비 흐름이 확인된다.
- Deferred/Forward+/XR ON/OFF 조합에서 동일 머티리얼이 기능 회귀 없이 동작한다.
samples/urp/URP_LitCompatibleTemplate.shader대비 필요한 pass만 남긴 최소 세트가 문서화되어 있다.