09. URP 기준 “완전 호환” 셰이더 작성 가이드 (HLSL)
이 챕터의 목표는 URP의 기존 셰이더/파이프라인과 호환되는 커스텀 HLSL 셰이더를 작성하는 체크리스트를 제공하는 것입니다.
호환성은 크게 4가지로 나뉩니다.
- 키워드/Variant 호환: URP가 기대하는
#pragma multi_compile/shader_feature - 패스 태그/라이트모드: URP가 패스를 찾는 규칙
- 상수버퍼 레이아웃(SRP Batcher): CBUFFER 구조/정렬 규칙
- Forward+/Deferred 등 경로 호환: 라이트 루프 분기
이 4가지를 동시에 만족시키면, “URP 내장 Lit 계열과 같은 환경에서” 커스텀 셰이더가 안정적으로 동작합니다.
9.0 정확 레퍼런스(Generated, URP 17.3.0)
이 챕터는 “개념 체크리스트”로 끝나지 않도록, 로컬 URP/Core 소스에서 자동 추출한 레퍼런스와 함께 읽는 것을 전제로 합니다.
- Lit Pass/Include 맵:
book/generated/urp-lit-map.md - 함수/구조체/매크로 인덱스:
book/generated/urp-17.3.0/symbols/* - Lit 핵심 심볼 xref:
book/generated/urp-17.3.0/xref/lit-key-symbols.md
특히 Pass 계약(어떤 LightMode가 언제 소비되나)은 별도 챕터로 고정합니다.
- Pass tags/LightMode 계약 표:
book/20-urp-pass-tags-and-lightmode-contract.md
9.1 Pass 태그: URP가 패스를 인식하는 방식
URP는 “어떤 패스가 어떤 용도인지”를 LightMode 등의 태그로 구분합니다.
커스텀 셰이더가 특정 렌더링 단계(예: DepthOnly, ShadowCaster)에 참여하려면, URP가 사용하는 LightMode 태그와 패스 구성을 맞춰야 합니다.
실무 팁: 가장 안전한 방법은 URP의 기존 셰이더(Lit/SimpleLit/Unlit)의 Pass 섹션을 복제(구조만) 하고, 내부 HLSL만 교체하는 방식으로 시작하는 것입니다.
9.1.1 “패스”는 두 종류가 있다: (A) 셰이더 패스 vs (B) 렌더 패스
혼동 포인트를 먼저 분리합니다.
- (A) ShaderLab Pass: 한 셰이더 안에 정의된 Pass 블록(ForwardLit, ShadowCaster 등)
- (B) URP Render Pass(ScriptableRenderPass): URP 파이프라인에 삽입되는 렌더 단계
URP의 Render Pass는 특정 시점에 특정 큐를 그릴 때, 셰이더의 특정 ShaderLab Pass(=LightMode)를 선택해 사용합니다.
즉, “호환”이란:
- URP가 기대하는 LightMode Pass가 존재하고
- 그 Pass가 URP가 기대하는 입력/키워드/버퍼 레이아웃을 만족
하는 상태를 의미합니다.
9.1.2 LightMode 태그는 “계약”이다(URP 17.3.0 Lit 기준)
URP는 Render Pass(파이프라인 단계)에서 LightMode를 기준으로 ShaderLab Pass를 선택합니다.
따라서 “완전 호환”이라는 말은:
- 프로젝트가 소비하는 LightMode Pass를 제공하고,
- 그 Pass가 요구하는 출력/키워드/입력 구조를 만족한다
는 뜻입니다.
URP 17.3.0의 Lit.shader가 실제로 포함하는 LightMode 9종(Generated 기준):
| LightMode | 권장 분류 | 주 소비 단계(요약) | 비고 |
|---|---|---|---|
UniversalForward |
필수급 | Forward 컬러 | Forward+ 분기 포함 |
ShadowCaster |
필수급 | 그림자 맵 | 알파 클립/바이어스 |
DepthOnly |
보통 필수 | DepthTexture/Depth prepass | ColorMask 설정 주의 |
DepthNormals |
기능 의존 | DepthNormalsTexture | SSAO/Outline/SSR류 기반 |
Meta |
기능 의존 | 라이트맵 베이킹 | 베이킹을 쓰면 사실상 필수 |
UniversalGBuffer |
기능 의존 | Deferred(GBuffer) | Deferred 프로젝트면 필수급 |
MotionVectors |
기능 의존 | velocity | TAA/모션블러/리프로젝션 |
XRMotionVectors |
XR 의존 | XR velocity | 스텐실 계약 포함 |
Universal2D |
2D 의존 | 2D Renderer | 2D 파이프라인 계약 |
정확 include/엔트리/정의 위치:
계약(언제/왜 소비되나) 상세:
9.2 URP 셰이더의 기본 골격(ShaderLab)
URP 호환 셰이더를 만들 때 “Pass 계약까지 포함한” 기본 골격은 다음 형태를 기준으로 잡는 것이 안전합니다.
Shader "Custom/URP/ExampleLit"
{
Properties
{
_BaseMap ("Base Map", 2D) = "white" {}
_BaseColor ("Base Color", Color) = (1,1,1,1)
}
SubShader
{
Tags
{
"RenderPipeline"="UniversalPipeline"
"RenderType"="Opaque"
"Queue"="Geometry"
}
// Pass: Forward(컬러)
Pass
{
Name "ForwardLit"
Tags { "LightMode"="UniversalForward" }
HLSLPROGRAM
#pragma vertex Vert
#pragma fragment Frag
// (A) URP 키워드/variant
// (B) include/CBUFFER
// (C) 엔트리 함수
ENDHLSL
}
// Pass: ShadowCaster(그림자)
Pass
{
Name "ShadowCaster"
Tags { "LightMode"="ShadowCaster" }
HLSLPROGRAM
#pragma vertex VertShadow
#pragma fragment FragShadow
ENDHLSL
}
// Pass: DepthOnly(깊이)
Pass { Name "DepthOnly" Tags { "LightMode"="DepthOnly" } }
// Pass: DepthNormals(깊이+노말)
Pass { Name "DepthNormals" Tags { "LightMode"="DepthNormals" } }
// Pass: Meta(베이킹)
Pass { Name "Meta" Tags { "LightMode"="Meta" } }
// (옵션) Deferred를 목표로 한다면:
Pass { Name "GBuffer" Tags { "LightMode"="UniversalGBuffer" } }
// (옵션) TAA/모션블러/리프로젝션을 목표로 한다면:
Pass { Name "MotionVectors" Tags { "LightMode"="MotionVectors" } }
// (옵션) XR에서 모션 벡터를 쓰면:
Pass { Name "XRMotionVectors" Tags { "LightMode"="XRMotionVectors" } }
// (옵션) 2D Renderer 호환이 필요하면:
Pass { Name "Universal2D" Tags { "LightMode"="Universal2D" } }
}
}
위 코드는 “Pass 계약 목록”을 보여주기 위한 골격입니다.
실제 구현은 URP Lit Pass 구성을 출발점으로 하는 것이 가장 안전합니다:
- 템플릿(샘플):
samples/urp/URP_LitCompatibleTemplate.shader- 설명 챕터: 18. URP 완전 호환 Pass 세트 템플릿
9.3 URP include 구성(실전 최소 세트)
URP 셰이더는 보통 다음 include를 기반으로 시작합니다.
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
URP 17.3.0에서 Core.hlsl은 사실상 “루트 include”입니다.
- Forward+ 분기 스위치:
_CLUSTER_LIGHT_LOOP→USE_CLUSTER_LIGHT_LOOP - SRP Core(공통) include를 끌어옴(
Common.hlsl,Packing.hlsl등) - URP
Input.hlsl을 include(전역 상수/구조체/유틸 기반)
관련(정확 정의 위치는 generated 기준):
USE_CLUSTER_LIGHT_LOOP:<URP>/ShaderLibrary/Core.hlsl_CLUSTER_LIGHT_LOOP:<URP>/ShaderLibrary/ForwardPlusKeyword.deprecated.hlsl
여기에 목적에 따라:
- 라이팅이 필요하면 Lighting/RealtimesLights 계열 include
- 뎁스 샘플링이 필요하면 DeclareDepthTexture 계열 include
- 표면 텍스처 샘플링을 URP 스타일로 하고 싶으면 SurfaceInput 계열 include
를 추가합니다. 구체적인 “어떤 파일을 include해야 하는가”는 URP 버전별로 달라질 수 있으니, URP Lit가 include하는 목록을 추적하는 방식을 권장합니다. (관련: 08. URP HLSL 라이브러리 지도)
참고:
#include_with_pragmas가 필요한 경우
URP는 일부 파일(DOTS, ObjectMotionVectors 등)을#include_with_pragmas로 포함합니다.
해당 파일이#pragma를 내부에 포함하는 경우(예: MotionVectors pass), 일반#include만으로는 “필요한 pragma가 누락”될 수 있습니다.
자세한 Pass 계약/예시는book/20-urp-pass-tags-and-lightmode-contract.md참고.
9.4 키워드/Variants: “맞춰야 하는 것”과 “줄여야 하는 것”
URP 호환을 위해 키워드를 맞추다 보면 variant가 폭발하기 쉽습니다.
- 장점: URP 기능(추가 라이트, 그림자, 소프트 쉐도우, 라이트맵, 포그…)과 자연스럽게 결합
- 단점: 빌드 시간/메모리/로딩 시간이 늘어남
9.4.1 라이팅/그림자 관련 키워드(대표)
Forward(+)/그림자를 포함하는 셰이더에서 흔히 등장:
- Additional Lights:
_ADDITIONAL_LIGHTS,_ADDITIONAL_LIGHTS_VERTEX - Additional Light Shadows:
_ADDITIONAL_LIGHT_SHADOWS - Forward+ loop:
_CLUSTER_LIGHT_LOOP - Soft Shadows:
_SHADOWS_SOFT(또는 유사 키워드) - Main Light Shadows/캐스케이드: URP 버전/셰이더에 따라 키워드 구성이 상이
권장: 첫 구현은 “정말 필요한 것만” 켜고, 기능을 추가할 때마다 키워드를 늘리며 검증하세요.
9.5 SRP Batcher 호환: CBUFFER 규칙(매우 중요)
SRP Batcher는 머티리얼 상수 업로드 비용을 줄이기 위해 머티리얼 상수 버퍼 레이아웃을 전제로 최적화합니다.
따라서, 커스텀 셰이더에서 SRP Batcher를 살리려면:
- 머티리얼 프로퍼티를
CBUFFER_START(UnityPerMaterial) ... CBUFFER_END에 넣고 - 레이아웃(타입/순서)이 조건부 컴파일로 바뀌지 않게 하고
- 불필요하게 전역(비-CBUFFER) 변수로 머티리얼 값을 두지 않는 것
이 핵심입니다.
CBUFFER_START(UnityPerMaterial)
float4 _BaseColor;
float4 _BaseMap_ST;
CBUFFER_END
9.5.1 자주 SRP Batcher를 깨는 패턴
#if로 프로퍼티를 빼거나 타입을 바꿔 CBUFFER 레이아웃이 달라짐- pass마다 CBUFFER 내용/순서가 달라짐
- 머티리얼 값인데 전역 변수/상수버퍼 밖에 흩어져 있음
관련 공식 문서:
- SRP Batcher 개요: https://docs.unity3d.com/Manual/SRPBatcher.html
9.6 Forward+ 호환: 라이트 루프 분기
Forward+를 지원하려면 07. Forward/Forward+/Lights에서 설명한 키워드와 분기 패턴이 필요합니다.
Forward+는 셰이더 측에서 “추가 라이트를 기존 방식으로 단순 루프”하면 안 되는 경우가 있으므로, URP 제공 매크로/함수를 사용해 클러스터 루프 경로를 반드시 점검하세요.
핵심 체크:
_CLUSTER_LIGHT_LOOPvariant가 존재하는가?- Forward+에서
GetAdditionalLightsCount()가 0을 반환할 수 있음을 알고 있는가?- 해결:
LIGHT_LOOP_BEGIN/END패턴 사용(URP 내부에서 Forward/Forward+ 루프를 매크로로 통일)
- 해결:
정확 정의/참조 위치는 generated xref로 고정:
관련 공식 문서:
- Forward+ 추가 라이트/키워드: https://docs.unity3d.com/6000.3/Documentation/Manual/urp/rendering/additional-lights-fplus.html
9.7 “완전 호환” 체크리스트(요약)
A) 파이프라인/패스
Tags { "RenderPipeline"="UniversalPipeline" }가 있는가- 필요한 LightMode Pass(Forward/ShadowCaster/DepthOnly/DepthNormals/Meta)가 존재하는가
B) 키워드/Variant
- 프로젝트에서 사용하는 URP 기능(Additional Lights, Shadows, Soft Shadows, Fog…)에 필요한 키워드가 포함되어 있는가
- Forward+ 사용 시
_CLUSTER_LIGHT_LOOP가 포함되어 있는가
C) 상수버퍼(SRP Batcher)
- 머티리얼 프로퍼티가
UnityPerMaterialCBUFFER에 모여 있는가 - pass 간 CBUFFER 레이아웃이 일관적인가
D) 리소스 입력(Depth/Normals 등)
- Depth/Normal/History가 필요한 경우, 파이프라인에서 실제로 생성되도록 요구/설정했는가
- 셰이더에서 올바른 include/샘플링/선형화를 했는가
추가 읽을거리(공식/권위 자료)
- Forward+ 추가 라이트 키워드/동작: https://docs.unity3d.com/6000.3/Documentation/Manual/urp/rendering/additional-lights-fplus.html
- SRP Batcher: https://docs.unity3d.com/Manual/SRPBatcher.html