book/35-tech-ramp-and-material-id-segmentation.md

N/A

35. 기술 심화: Ramp + Material ID Segmentation

Ramp 음영과 부위 ID 분할을 결합하는 NPR 핵심 패턴

35. 기술 심화: Ramp + Material ID Segmentation

[B] Ramp + Material ID는 서브컬쳐 캐릭터 렌더링에서 “가장 먼저 고정해야 하는 계약”에 가깝습니다. 얼굴 SDF나 외곽선을 붙이기 전에, 부위별로 ‘빛에 어떻게 반응할지’ 를 데이터로 고정하지 않으면 스타일이 계속 흔들립니다.

  • [B] Ramp: 연속 조명(NdotL)을 “아트가 제어 가능한 구간”으로 재매핑하는 전달함수
  • [B] Material ID: 같은 광원에서도 피부/천/금속/부츠가 다른 반응을 하게 만드는 분기 키
  • [B] 결론: NdotL(또는 Half-Lambert)Material ID를 2D 룩업으로 묶으면, 아트 피드백과 디버깅이 빨라진다

목적

  • [B] 램프 음영과 부위 ID 분할 계약을 표준화한다.
  • [A/B] 아트 텍스처 규약과 셰이더 수식을 함께 고정한다.

증거 등급 요약(A/B/C)

  • A: NPR/PBR 혼합 철학(공식 발표)
  • B: NdotL-x / ID-y 샘플 패턴(역분석)
  • C: LUT 전처리/보정 차이

핵심 개념

서론: “램프만”으로는 운영이 어려운 이유

  • [B] 1D 램프만 쓰면 캐릭터 전체가 같은 전달함수를 공유한다. 그 결과, 피부를 고치면 금속이 깨지고 금속을 고치면 천이 깨지는 식의 회귀가 생긴다.
  • [B] Material ID를 같이 쓰면 “부위별 전달함수”가 되므로, 수정 범위를 데이터(행/슬롯)로 제한할 수 있다.

개론: 데이터 계약(팀 규약으로 고정할 것)

  • [B] ID 소스: LightMap/Mask 텍스처의 특정 채널(예: A)을 matId로 사용한다.
  • [B] ID 의미: 0.0~1.0 범위를 N개 슬롯으로 나누고, 각 슬롯이 의미하는 “재질군”을 문서로 고정한다.
  • [B] 램프 레이아웃:
  • [B] 1D 램프: x = light (단일 재질만 쓸 때)
  • [B] 2D 램프(권장): x = light, y = matId slot (부위별로 다른 ramp)

이론: 왜 Half-Lambert(0.5*NdotL+0.5)를 자주 쓰나

  • [C] NdotL은 0 아래에서 급격히 꺼져 그림자 경계가 과도하게 날카로워질 수 있다.
  • [C] Half-Lambert는 음영을 0..1로 매끈하게 펼쳐 램프 구간화를 더 안정적으로 만든다.
  • [B] 중요한 건 “어떤 리매핑을 썼는지”를 팀 규약으로 고정하고, 모든 캐릭터가 같은 x축을 공유하게 만드는 것이다.

심화: 샘플링/필터링에서 자주 터지는 문제

  • [B] Ramp에 mipmap이 켜져 있으면 구간 경계가 흐려져 “색띠 깨짐/번짐”이 발생한다.
  • [B] Ramp에 bilinear filtering을 쓰면 인접 슬롯(y)이 섞여 다른 재질 색이 새어 나온다.
  • [B] 그래서 흔히 아래를 같이 고정한다.
  • [B] 1) Ramp: mipmap off, Clamp, (필요시) Point 또는 “경계가 넓은 램프 텍스처”
  • [B] 2) Mask/ID: sRGB off(정수에 가까운 값은 색공간 변환에 취약)

코드 해설(2D ramp: x=light, y=slot)

  • [B] floor(id * (N - eps)) 패턴은 id=1.0 같은 경계값이 다음 슬롯으로 넘어가는 오프바이원 문제를 줄인다.
  • [B] y = (row + 0.5)/N은 슬롯 중심을 샘플해 bilinear 혼합을 최소화한다.
HLSL
// Input contracts
// - _MaskTex.a: 0..1 material id (sRGB off recommended)
// - _RampTex: 2D (x=light, y=row), clamp, no mipmap recommended
half matId = SAMPLE_TEXTURE2D(_MaskTex, sampler_MaskTex, uv).a;

// Slotting (N=8 example)
const half kRows = 8.0h;
half row = floor(saturate(matId) * (kRows - 0.001h)); // 0..7
half y = (row + 0.5h) / kRows;

// Light -> 0..1 domain
half ndl = dot(N, L);
half x = saturate(ndl * 0.5h + 0.5h); // Half-Lambert

half3 ramp = SAMPLE_TEXTURE2D(_RampTex, sampler_RampTex, half2(x, y)).rgb;

URP 매핑 포인트

설계 해석

  • [B] Ramp 텍스처를 mipmap off + clamp로 고정하는 이유는 색띠 경계를 의도적으로 보존하기 위해서다.

  • [B] ID 채널은 아트 툴과 엔진 import가 동일한 색공간(sRGB off) 계약을 공유해야 값 드리프트를 막을 수 있다.

  • [B] 적용 위치: 캐릭터 Lit 파생 셰이더의 diffuse 전달함수(“NdotL -> diffuse”) 구간에서 ramp로 치환한다.

  • [B] URP에서 흔한 분리:

  • [B] 1) SubcultureRamp.hlsl 같은 include에 “샘플/디코드”를 고정

  • [B] 2) face/hair/body 셰이더는 “샘플 결과”만 사용해 drift를 막는다

  • [B] import 권장:

  • [B] Ramp: mipmap off, clamp, (옵션) point

  • [B] Mask/ID: sRGB off, 압축 포맷 변경 시 경계 튐 재검증

실패 패턴/오해

  • [B] ID 채널 의미를 캐릭터마다 바꾸는 운영 오류
  • [B] ramp 경계 필터링으로 색띠 깨짐
  • [B] id=1.0 경계값을 고려하지 않아 마지막 슬롯이 튄다(오프바이원)

실무 체크리스트

Sources (섹션 단위 인용)