book/21-urp-hlsl-structs-dataflow.md

21. URP HLSL Structs & Dataflow

Unity 6.3(6000.3) / URP 17.3.0에서 VertexPositionInputs/VertexNormalInputs/SurfaceData/InputData/Light를 필드 단위로 해부하고 “누가 채우고 어디서 쓰는지”를 데이터 흐름으로 고정한다

21. URP HLSL Structs & Dataflow (URP 17.3.0)

URP 셰이더를 “정확히” 공부할 때 가장 강력한 기준은 구조체(struct) 입니다.

  • 함수는 왔다 갔다 하지만, 데이터 계약(구조체 필드와 의미)은 더 오래 유지됩니다.
  • 특히 Lit 계열은 SurfaceDataInputData가 사실상 “파이프라인 인터페이스”입니다.

이 챕터는 다음을 목표로 합니다.

  1. 구조체 필드를 암기하는 게 아니라 의미/공간/생성자/소비자를 연결한다.
  2. LitPassFragment → UniversalFragmentPBR 데이터 흐름을 “필드 단위”로 설명한다.
  3. 디버깅할 때 “어느 필드가 비었나”로 원인을 좁히는 습관을 만든다.

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):

생성 함수:

  • VertexPositionInputs GetVertexPositionInputs(float3 positionOS)
    • <URP>/ShaderLibrary/ShaderVariablesFunctions.hlsl:8

필드:

Field Type 의미 주 소비처
positionWS float3 월드 좌표 조명/그림자/클러스터(Forward+)
positionVS float3 뷰(카메라) 좌표 깊이 기반 계산/특수 효과
positionCS float4 클립 좌표 래스터라이저 입력(최종)
positionNDC float4 NDC(정규화 장치 좌표) 스크린 UV 계산 등

실무 팁:

  • “Forward+ 클러스터 루프”는 positionWSnormalizedScreenSpaceUV를 매우 자주 소비합니다.
  • 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:

21.7 흔한 실수(데이터 흐름 관점)

  1. 공간(space) 혼동: normalTS/normalWS, positionCS/positionNDC를 섞어서 사용
  2. 정규화 누락: normalWS, viewDirectionWS가 정규화되지 않아 스펙큘러가 깨짐
  3. Forward+ 입력 누락: normalizedScreenSpaceUV가 비어 클러스터 루프가 실패
  4. DepthNormals 계약 미참여: SSAO/Outline이 빈 텍스처를 읽음

실무 디버깅 루틴은 다음 챕터와 같이 보면 빠릅니다.