Shader / URP
Intermediate
Unity 2022.3+ / Unity 6 (URP 2D)
2D 픽셀 스프라이트 노멀맵(Normal Map)과 URP 2D 동적 광원 입체 라이팅
Dead Cells 스타일: 2D 도트 스프라이트에 Secondary Texture _NormalMap을 연결하고 URP 2D Point/Spot Light로 실시간 입체 명암과 림라이트를 연출하는 기법.
#Shader #URP #2D #NormalMap #DeadCells #Lighting
핵심 개념*Dead Cells*의 그래픽이 경이로운 이유는 평면적인 2D 픽셀 캐릭터가 횃불이나 마법 광원을 지나갈 때 몸체 굴곡에 따라 빛이 실시간으로 흐르기 때문입니다. 이는 2D 스프라이트에 노멀맵(Normal Map)을 바인딩하고 URP의 2D 라이팅 파이프라인(
Sprite-Lit-Default)을 활용해 3D 벡터 내적($N \cdot L$) 연산을 수행한 결과입니다.1. 스프라이트 에디터 Secondary Texture 설정
| 단계 | 작업 경로 및 옵션 | 설명 |
|---|---|---|
| 1단계 | Texture Import > Texture Type = Sprite (2D and UI) | 플레이어 스프라이트 시트 기본 임포트 |
| 2단계 | Sprite Editor 열기 > Secondary Textures 선택 | 스프라이트 메타데이터에 추가 텍스처 슬롯 개방 |
| 3단계 | Name: _NormalMap, Texture: 캐릭터_Normal 텍스처 등록 | 픽셀별 법선 벡터 텍스처를 1:1 좌표로 바인딩 |
| 4단계 | 캐릭터 Material = Universal Render Pipeline/2D/Sprite-Lit-Default | 노멀 연산을 지원하는 URP 2D 기본 셰이더 머티리얼 적용 |
2. 커스텀 2D 도트 계단식(Cel-stepped) 노멀 라이트 셰이더
// 2D 픽셀 스프라이트의 동적 광원을 도트 계단식으로 양자화하는 연산 블록
half3 CalculateCel2DLighting(half3 baseColor, half3 normalTS, float3 worldPos, float3 lightPos, half3 lightColor, float lightRange)
{
float3 lightDir = lightPos - worldPos;
float dist = length(lightDir);
if (dist > lightRange) return baseColor;
lightDir = normalize(lightDir);
// 2D 스프라이트 접선 공간 노멀 언패킹
half3 N = normalTS;
half3 L = half3(lightDir.xy, lightDir.z);
// 램버트 도트곱
float NdotL = max(0.0, dot(N, L));
// 부드러운 3D 조명을 3단계 도트 명암으로 양자화 (Dead Cells 룩)
float celFactor = floor(NdotL * 3.0) / 3.0;
// 감쇠 적용
float atten = saturate(1.0 - (dist / lightRange));
half3 finalLight = lightColor * celFactor * atten;
return baseColor * (half3(0.2, 0.2, 0.3) + finalLight); // 환경광 + 동적광
}
3. 실전 제작 팁
- 노멀맵 자동 생성 도구: 스프라이트를 한 장씩 수작업으로 노멀을 그릴 필요 없이,
SpriteIlluminator나Laigter(무료 오픈소스)를 사용하면 2D 도트 그림의 외곽선과 음영을 분석해 입체 노멀맵을 1초 만에 자동 추출할 수 있습니다. - Z축 깊이(Z-Depth) 배치: 2D 씬에서 조명이 정상적으로 림라이트를 비추려면 광원(
Light 2D)의 Transform Z 좌표가 스프라이트보다 살짝 앞(카메라 쪽, 예:z = -1.0f)에 위치해야 입체적인 각도가 형성됩니다.