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를 필드 단위로 해부하고 “누가 채우고 어디서 쓰는지”를 데이터 흐름으로 고정한다

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

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

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

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

21.0 정확 레퍼런스(IDE)

구조체 정의/필드 목록의 1차 원본:

함수 시그니처(누가 채우는지 추적):

Lit 핵심 심볼 xref(정의 + 대표 호출처):

21.1 Lit 데이터 흐름(큰 그림)

Lit Forward를 데이터 흐름으로 보면 다음과 같습니다.

flowchart TD A[Attributes\n(vertex input)] --> B[GetVertexPositionInputs\nGetVertexNormalInputs] B --> C[Varyings\n(interpolators)] C --> D[InitializeStandardLitSurfaceData\n=> SurfaceData] C --> E[InitializeInputData\n=> InputData] D --> F[UniversalFragmentPBR] E --> F F --> G[SV_Target0 outColor]

이 흐름의 “정확 레퍼런스”는 16/22장에서 더 좁혀 고정합니다.

21.2 VertexPositionInputs: 위치(WS/VS/CS/NDC) 계약

정의(URP 17.3.0):

생성 함수:

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

필드:

FieldType의미주 소비처
positionWSfloat3월드 좌표조명/그림자/클러스터(Forward+)
positionVSfloat3뷰(카메라) 좌표깊이 기반 계산/특수 효과
positionCSfloat4클립 좌표래스터라이저 입력(최종)
positionNDCfloat4NDC(정규화 장치 좌표)스크린 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

필드:

FieldType의미주 소비처
tangentWSreal3월드 공간 탄젠트노말맵/탄젠트 공간 변환
bitangentWSreal3월드 공간 바이탄젠트노말맵/탄젠트 공간 변환
normalWSfloat3월드 공간 노말라이트/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

필드:

FieldType의미(요약)주 소비처
albedohalf3기본 색(텍스처/컬러 곱 결과)PBR
specularhalf3specular 컬러(스페큘러 워크플로)PBR
metallichalf메탈릭(메탈릭 워크플로)PBR
smoothnesshalf스무스니스(거칠기 역수 계열)PBR
normalTShalf3탄젠트 공간 노말(노말맵 결과)InputData 구성
emissionhalf3자체 발광최종 색
occlusionhalf오클루전조명 감쇠
alphahalf알파(투명/컷아웃)OutputAlpha
clearCoatMaskhalf클리어코트 마스크PBR(옵션)
clearCoatSmoothnesshalf클리어코트 스무스니스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개, IDE 기준):

아래 표는 “필드 의미 + 보통 누가 채우고 어디서 쓰는지”를 한 번에 보기 위한 것입니다.
실제로 어떤 필드가 채워지는지는 프로젝트 기능(Decal/APV/Debug/ProbeVolumes 등)과 키워드에 따라 달라질 수 있습니다.

FieldType의미/공간주 생산자(대표)주 소비처(대표)
positionWSfloat3월드 위치InitializeInputData라이트/그림자/클러스터
positionCSfloat4클립 위치Varyings/패스LODFade/Decal 등
normalWSfloat3월드 노말(정규화 필요)InitializeInputDataPBR/그림자
viewDirectionWShalf3월드 뷰 방향(보통 정규화)InitializeInputDataPBR
shadowCoordfloat4메인 라이트 shadow 좌표GetShadowCoordShadow 샘플링
fogCoordhalf포그 계수InitializeInputDataMixFog
vertexLightinghalf3vertex 추가 조명 누적vertex path최종 색(옵션)
bakedGIhalf3baked GI(LM/APV/SH)InitializeBakedGIDataPBR
normalizedScreenSpaceUVfloat20..1 스크린 UVGetNormalizedScreenSpaceUVForward+(클러스터), SSR류
shadowMaskhalf4shadowmask/probe occlusionGI/Lightmap 경로shadow mixing
tangentToWorldhalf3x3TS→WS basisInitializeInputDatanormalTS 변환
dynamicLightmapUVhalf2동적 라이트맵 UVvertex pathGI
staticLightmapUVhalf2정적 라이트맵 UVvertex pathGI
vertexSHfloat3SH(정점)vertex pathGI
brdfDiffusehalf3BRDF diffuse 캐시(옵션)PBR 준비PBR
brdfSpecularhalf3BRDF specular 캐시(옵션)PBR 준비PBR
uvfloat2원본/추적용 UV(옵션)디버그/기능디버그/VT 등
mipCountuintmip 수(옵션)VT/스트리밍디버그
texelSizefloat4texel size(옵션)VT/스트리밍디버그
mipInfofloat4mip 정보(옵션)VT/스트리밍디버그
streamInfofloat4스트리밍 정보(옵션)VT/스트리밍디버그
originalColorfloat3원본 컬러(옵션)디버그/후처리디버그
probeOcclusionfloat4프로브 오클루전(옵션)probe/APVshadow mixing

실무 팁:

  • Forward+에서 normalizedScreenSpaceUV가 비어 있으면 클러스터 라이트 루프가 제대로 동작하지 않습니다.
  • shadowCoord는 “메인 라이트 그림자”의 핵심 입력입니다. 방향/공간이 어긋나면 그림자가 통째로 깨집니다.

21.6 Light: 라이트 요약형(조명 소비 계약)

정의(URP 17.3.0):

  • <URP>/ShaderLibrary/RealtimeLights.hlsl:12

필드:

FieldType의미(요약)
directionhalf3라이트 방향
colorhalf3라이트 색/강도
distanceAttenuationfloat거리 감쇠
shadowAttenuationhalf그림자 감쇠
layerMaskuintlight layer 매칭

Forward+ 루프와 결합되는 핵심 API:

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

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

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