21. URP HLSL Structs & Dataflow (URP 17.3.0)
URP 셰이더를 “정확히” 공부할 때 가장 강력한 기준은 구조체(struct) 입니다.
- 함수는 왔다 갔다 하지만, 데이터 계약(구조체 필드와 의미)은 더 오래 유지됩니다.
- 특히 Lit 계열은
SurfaceData와InputData가 사실상 “파이프라인 인터페이스”입니다.
이 챕터는 다음을 목표로 합니다.
- 구조체 필드를 암기하는 게 아니라 의미/공간/생성자/소비자를 연결한다.
LitPassFragment → UniversalFragmentPBR데이터 흐름을 “필드 단위”로 설명한다.- 디버깅할 때 “어느 필드가 비었나”로 원인을 좁히는 습관을 만든다.
21.0 정확 레퍼런스(Generated)
구조체 정의/필드 목록의 1차 원본:
함수 시그니처(누가 채우는지 추적):
Lit 핵심 심볼 xref(정의 + 대표 호출처):
21.1 Lit 데이터 흐름(큰 그림)
Lit Forward를 데이터 흐름으로 보면 다음과 같습니다.
Parse error on line 1: flowchart TD A[Att ^ Expecting 'NEWLINE', 'SPACE', 'GRAPH', got 'ALPHA'
이 흐름의 “정확 레퍼런스”는 16/22장에서 더 좁혀 고정합니다.
21.2 VertexPositionInputs: 위치(WS/VS/CS/NDC) 계약
정의(URP 17.3.0):
<URP>/ShaderLibrary/Core.hlsl:259(book/generated/urp-17.3.0/symbols/structs.md)
생성 함수:
VertexPositionInputs GetVertexPositionInputs(float3 positionOS)<URP>/ShaderLibrary/ShaderVariablesFunctions.hlsl:8
필드:
| Field | Type | 의미 | 주 소비처 |
|---|---|---|---|
positionWS |
float3 |
월드 좌표 | 조명/그림자/클러스터(Forward+) |
positionVS |
float3 |
뷰(카메라) 좌표 | 깊이 기반 계산/특수 효과 |
positionCS |
float4 |
클립 좌표 | 래스터라이저 입력(최종) |
positionNDC |
float4 |
NDC(정규화 장치 좌표) | 스크린 UV 계산 등 |
실무 팁:
- “Forward+ 클러스터 루프”는
positionWS와normalizedScreenSpaceUV를 매우 자주 소비합니다. positionCS만 만들어서 끝내는 셰이더는 디버깅이 어렵습니다. 가능하면 URP의GetVertexPositionInputs를 출발점으로 두세요.
21.3 VertexNormalInputs: tangent basis 계약(WS tangent/bitangent/normal)
정의(URP 17.3.0):
<URP>/ShaderLibrary/Core.hlsl:267
생성 함수(대표):
VertexNormalInputs GetVertexNormalInputs(float3 normalOS)<URP>/ShaderLibrary/ShaderVariablesFunctions.hlsl:22
VertexNormalInputs GetVertexNormalInputs(float3 normalOS, float4 tangentOS)<URP>/ShaderLibrary/ShaderVariablesFunctions.hlsl:31
필드:
| Field | Type | 의미 | 주 소비처 |
|---|---|---|---|
tangentWS |
real3 |
월드 공간 탄젠트 | 노말맵/탄젠트 공간 변환 |
bitangentWS |
real3 |
월드 공간 바이탄젠트 | 노말맵/탄젠트 공간 변환 |
normalWS |
float3 |
월드 공간 노말 | 라이트/BRDF/그림자 |
실무 팁:
- “노말이 뒤집힌다/하이라이트가 이상하다”는 대부분 tangent basis 문제입니다.
normalWS는 라이트 루프/그림자/간접광에서 사실상 모든 곳이 소비합니다(가장 비싼 버그).
21.4 SurfaceData: 재질(표면) 입력 계약
정의(URP 17.3.0):
<URP>/ShaderLibrary/SurfaceData.hlsl:5
Lit에서 대표적으로 채우는 함수:
InitializeStandardLitSurfaceData(float2 uv, out SurfaceData outSurfaceData)<URP>/Shaders/LitInput.hlsl:252
필드:
| Field | Type | 의미(요약) | 주 소비처 |
|---|---|---|---|
albedo |
half3 |
기본 색(텍스처/컬러 곱 결과) | PBR |
specular |
half3 |
specular 컬러(스페큘러 워크플로) | PBR |
metallic |
half |
메탈릭(메탈릭 워크플로) | PBR |
smoothness |
half |
스무스니스(거칠기 역수 계열) | PBR |
normalTS |
half3 |
탄젠트 공간 노말(노말맵 결과) | InputData 구성 |
emission |
half3 |
자체 발광 | 최종 색 |
occlusion |
half |
오클루전 | 조명 감쇠 |
alpha |
half |
알파(투명/컷아웃) | OutputAlpha |
clearCoatMask |
half |
클리어코트 마스크 | PBR(옵션) |
clearCoatSmoothness |
half |
클리어코트 스무스니스 | PBR(옵션) |
실무 팁(안전한 커스터마이즈):
- “내 재질 모델을 바꾼다”의 1단계는
SurfaceData값을 조정하는 것입니다. - 이 단계는 Pass 계약/라이트 루프/Forward+ 분기와 비교적 독립적이어서, 호환성이 유지되기 쉽습니다.
21.5 InputData: 공간/카메라/조명 준비 계약(핵심)
정의(URP 17.3.0):
<URP>/ShaderLibrary/Input.hlsl:43
Lit Forward에서 대표적으로 채우는 함수:
InitializeInputData(Varyings input, half3 normalTS, out InputData inputData)<URP>/Shaders/LitForwardPass.hlsl:72
필드(총 23개, Generated 기준):
아래 표는 “필드 의미 + 보통 누가 채우고 어디서 쓰는지”를 한 번에 보기 위한 것입니다.
실제로 어떤 필드가 채워지는지는 프로젝트 기능(Decal/APV/Debug/ProbeVolumes 등)과 키워드에 따라 달라질 수 있습니다.
| Field | Type | 의미/공간 | 주 생산자(대표) | 주 소비처(대표) |
|---|---|---|---|---|
positionWS |
float3 |
월드 위치 | InitializeInputData |
라이트/그림자/클러스터 |
positionCS |
float4 |
클립 위치 | Varyings/패스 | LODFade/Decal 등 |
normalWS |
float3 |
월드 노말(정규화 필요) | InitializeInputData |
PBR/그림자 |
viewDirectionWS |
half3 |
월드 뷰 방향(보통 정규화) | InitializeInputData |
PBR |
shadowCoord |
float4 |
메인 라이트 shadow 좌표 | GetShadowCoord 등 |
Shadow 샘플링 |
fogCoord |
half |
포그 계수 | InitializeInputData |
MixFog |
vertexLighting |
half3 |
vertex 추가 조명 누적 | vertex path | 최종 색(옵션) |
bakedGI |
half3 |
baked GI(LM/APV/SH) | InitializeBakedGIData |
PBR |
normalizedScreenSpaceUV |
float2 |
0..1 스크린 UV | GetNormalizedScreenSpaceUV 등 |
Forward+(클러스터), SSR류 |
shadowMask |
half4 |
shadowmask/probe occlusion | GI/Lightmap 경로 | shadow mixing |
tangentToWorld |
half3x3 |
TS→WS basis | InitializeInputData |
normalTS 변환 |
dynamicLightmapUV |
half2 |
동적 라이트맵 UV | vertex path | GI |
staticLightmapUV |
half2 |
정적 라이트맵 UV | vertex path | GI |
vertexSH |
float3 |
SH(정점) | vertex path | GI |
brdfDiffuse |
half3 |
BRDF diffuse 캐시(옵션) | PBR 준비 | PBR |
brdfSpecular |
half3 |
BRDF specular 캐시(옵션) | PBR 준비 | PBR |
uv |
float2 |
원본/추적용 UV(옵션) | 디버그/기능 | 디버그/VT 등 |
mipCount |
uint |
mip 수(옵션) | VT/스트리밍 | 디버그 |
texelSize |
float4 |
texel size(옵션) | VT/스트리밍 | 디버그 |
mipInfo |
float4 |
mip 정보(옵션) | VT/스트리밍 | 디버그 |
streamInfo |
float4 |
스트리밍 정보(옵션) | VT/스트리밍 | 디버그 |
originalColor |
float3 |
원본 컬러(옵션) | 디버그/후처리 | 디버그 |
probeOcclusion |
float4 |
프로브 오클루전(옵션) | probe/APV | shadow mixing |
실무 팁:
- Forward+에서
normalizedScreenSpaceUV가 비어 있으면 클러스터 라이트 루프가 제대로 동작하지 않습니다. shadowCoord는 “메인 라이트 그림자”의 핵심 입력입니다. 방향/공간이 어긋나면 그림자가 통째로 깨집니다.
21.6 Light: 라이트 요약형(조명 소비 계약)
정의(URP 17.3.0):
<URP>/ShaderLibrary/RealtimeLights.hlsl:12
필드:
| Field | Type | 의미(요약) |
|---|---|---|
direction |
half3 |
라이트 방향 |
color |
half3 |
라이트 색/강도 |
distanceAttenuation |
float |
거리 감쇠 |
shadowAttenuation |
half |
그림자 감쇠 |
layerMask |
uint |
light layer 매칭 |
Forward+ 루프와 결합되는 핵심 API:
GetMainLight/GetAdditionalLight/LIGHT_LOOP_BEGIN/END- 자세한 내용: 07. Forward/Forward+/Lights
21.7 흔한 실수(데이터 흐름 관점)
- 공간(space) 혼동: normalTS/normalWS, positionCS/positionNDC를 섞어서 사용
- 정규화 누락:
normalWS,viewDirectionWS가 정규화되지 않아 스펙큘러가 깨짐 - Forward+ 입력 누락:
normalizedScreenSpaceUV가 비어 클러스터 루프가 실패 - DepthNormals 계약 미참여: SSAO/Outline이 빈 텍스처를 읽음
실무 디버깅 루틴은 다음 챕터와 같이 보면 빠릅니다.
- Pass 계약:
book/20-urp-pass-tags-and-lightmode-contract.md - Lit callflow/편집 지점:
book/22-urp-lit-callflow-and-edit-points.md - 문제 해결 플레이북:
book/24-debugging-playbook-unity6-urp.md