폴리곤 수를 늘리지 않고 법선 벡터를 조작해 표면 디테일을 표현하는 기법
핵심 개념 : 빛 반응을 속이는 원리
폴리곤이 적은 메시 (Low Poly)
실제 지오메트리가 평평함 -> 빛이 균열하게 반사 -> 밋밋해 보이는 효과 발생
노멀 맵 적용
지오메트리는 그대로 (폴리곤 추가 없음) -> 픽셀마다 법선 벡터(Normal Vector)를 다르게 조작 -> 빛이 마치 울퉁불퉁한 표면에서 반사된 것처럼 계산 -> 디테일이 있어보이는 효과
즉. 실제로 튀어나온 것은 없이 빛 계산만 속인다. 폴리고 비용 없이 디테일 표현이 가능하다
법선 벡터
- 표면에 수직인 방향 벡터
- 평평한 표면 : 모든 법선이 동일하여 빛 반응이 균일하고 평평해 보인다.
- 울툴불퉁한 표면 : 법선이 각각 다르기에 빛 반응이 다양하고 굴곡이 있어 보인다.
조명 계산
color = albedo * dot(Normal, LightDir)
dot(Normal, LightDir)의 값이 달리지면 빛 방향이 달라진다. 노멀 맵은 이 Normal을 픽셀마다 바꾸는 것이다.
노멀 맵이 파란색인 이유
- 노멀 맵 = 법선 벡터를 RGB 텍스처로 저장한 것
- 법선 벡터 범위 : -1 ~ +1
- 텍스처 색상 범위 : 0 ~ 1
변환 공식
색상 = (법선 + 1) / 2
법선 = 색상 * 2 - 1
평평한 표면의 법선 = (0, 0, 1)
- R = (0 + 1) / 2 = 0.5 -> 128 -> 회색
- G = (0 + 1) / 2 = 0.5 -> 128 -> 회색
- B = (1 + 1) / 2 = 1.0 -> 255 -> 파란색
-> 완전히 평평한 곳은 RGB(128, 128, 255) = 파란색. 그렇기에 노멀 맵이 파란빛이다.
Z축이 파란색인 이유
- 노멀 맵은 탄젠트 공간 기준
- 탄젠트 공간에서 표면 정면 = Z축 = (0, 0, 1)
- Z가 가장 큰 값(1.0 -> 파란색 255)이 기본값
좌표 공간
노멀 맵이 저장하는 법선의 좌표 공간
월드 공간 노멀 맵 (World Space)
- 글로벌 XYZ 기준으로 거장
- 오브젝트가 회전하면 잘못된 방향 가리킴 -> 움직이는 오브젝트에 부적합하다
탄젠트 공간 노멀 맵 (Tangent Space) <- 일반적으로 이것으 가리킨다
- 메시 표면 기준 좌표계로 저장
- T(탄젠트) + B(바이탄젠트) + N(노멀)로 구성
- 오브젝트가 어떻게 회전해도 정상 동작 -> 캐릭터, 애니메이션 메시에 적합
픽셀 셰이더에서 변환
- 텍스처에서 읽은 법선 ( 탄젠트 공간 )
- TBN 행렬로 월드/뷰 공간으로 변환
- 조명 계산에 사용
TBN 행렬 (셰이더 구현)
// 노멀 맵 픽셀 셰이더(HLSL)
Texture2D NoramlMap : register(t1);
SamplerState Sampler : register(s0);
cbuffer LightBuffer : register(b1)
{
float3 lightDir;
float3 lightColor;
}
float4 PS(PixelInput input) : SV_TARGET
{
// 노멀 맵에서 법선 일기
float3 normalSample = NormalMap.Sample(Sampler, input.texCoord).rgb;
// 0 ~ 1 범위를 -1 ~ 1 범위로 변환
float3 tangentNormal = normalSample * 2.0 - 1.0;
// TBN 행렬 구성. T, B, N은 버텍스 셰이더에서 전달받은 값
float3 T = normalize(input.tangent);
float3 B = normalize(input.bitangent);
float3 N = normalize(input.normal);
float3x3 TBN = float3x3(T, B, N);
// 탄젠트 공간 -> 월드 공간으로 변환
float3 worldNormal = normalize(mul(tangentNormal, TBN));
// 변환된 법선으로 조명 계산
float NdotL = max(dot(worldNormal, normalize(-lightDir)), 0.0);
float4 albedo = AlbedoMap.Sample(Sampler, input.texCoord);
return float4(albedo.rgb * lightColor * NdotL, albedo.a);
}
노멀 맵 종류
노멀 맵 (Normal Map)
- 법선 방향만 저장
- 빛 반응 조작
- 가장 기본적이고 가장 많이 쓰인다.
- 파일 : _N, _Normal 접미사
범프 맵 (Bump Map)
- 높이값(흑백)으로 법선을 유추
- 노멀 맵보다 구형 기술
- GPU가 높이 -> 법선 변환 필요가 필요하다
- 파일 : _B, _Bump 접미사
하이트 맵 (Height Map)
- 높이 정보를 흑백으로 저장
- 노멀 맵 생성에 쓰거나 테셀레이션으로 실제 지오메트리 변위에 사용
디스플레이스먼트 맵 (Displacement Map)
- 하이트 맵 기반으로 버텍스를 실제로 이동
- 테셀레이션이 필요하다
- 실제 실루엣이 변하기에 노멀 맵보다 비용이 크다
- UE5에서 World Displacement 노드가 있다.
노멀 맵 vs 디스플레이스먼트 맵
노멀 맵
- 폴리곤 수 변화가 없다
- 실루엣에도 변화가 없다. 여전히 가장자리는 평평하다
- 텍스처 샘플링만 쓰기에 비용이 낮다
- 가장자리에서 깨진다(silhouette problem)는 한계점이 존재하다
디스플레이스먼트 맵
- 폴리곤 수가 테셀레이션으로 인해 증가한다
- 실루엣이 실제로 변형된다
- 지오메트리 생성으로 인해 비용이 높다
- 실루엣까지 디테일하게 표현되는 장점이 있다.
평평한 바닥, 벽 -> 노멀 맵으로 충분하다
바위, 지형 실루엣 -> 디스플레이스먼트 맵
캐릭터 피부 -> 노멀 맵 + 약간의 테셀레이션
UE5에서 노멀 맵 작업 흐름 (제작 파이프라인)
하이폴리 메시(High Poly) : Blender / ZBrush에서 수백만 폴리곤으로 디테일 조각한다
↓ 베이킹 (Baking)
로우폴리 메시 + 노멀맵 : 게임에서 실제로 쓰는 메시 + 하이폴리의 디테일을 구운 노멀 맵
UE5 임포트
- 텍스처 설정 : Normal Map 타입 선택
- sRGB 체크 해제 ( 노멀 맵은 색상 데이터가 아니다 )
- Compression : BC5 / DXT5nm (채널 최적화)
머티리얼 연결
- TextureSample -> Normal 핀
- Flat Normal 노드 : (0, 0, 1) 기본값
- Normal Intensity 조절 : Lerp로 강도 제어
노멀 맵 최적화
BC5 압축 (두 채널 압축)
노멀 맵은 XY만 저장해도 된다. Z = sqrt(1 - X^2 - Y^2)으로 재계산 가능
-> BC5로 RG만 저장한다 -> 메모리 절약 + 품질 유지
셰이더에서 Z 재계산
float3 normal;
normal.xy = normalSample.rg * 2.0 - 1.0;
normal.z = sqrt(1.0 - saturate(dot(normal.xy, normal.xy)));
normal = normalize(normal);
sRGB 설정 주의
- 노멀 맵은 색상이 아닌 벡터 데이터
- sRGB 감마 보정을 적용하면 잘못된 법선이다
- UE5에서 반드지 sRGB 체크를 해제해야한다
밉맵과 노멀 맵
- 일반 텍스처 : 밉맵 = 평균값으로 다운샘플
- 노멀 맵 : 평균 -> 법선이 정규화되지 않는다. -> 각 밉맵 레벨에서 법선 재정규화가 필요하다. 근데 UE5에서는 자동으로 처리.
연결되는 단어들
패럴렉스 매핑 (Parallax Mapping) : 노멀 맵의 실루엣 한계를 보완으로 하이트 맵으로 UV 좌표를 시선 방향에 따라 이동시켜 깊이감을 포현하며 자기 그림자까지 구현 가능하다
앰비언트 오클루전 맵 (AO Map) : 노멀 맵과 함께 쓰이는 텍스처. 구석진 곳에 그림자를 추가해 입체감을 강화한다. 노멀 맵이 빛 방향 반응을 담당하면 AO는 전역 그림자를 담당한다.
베이킹 (Baking) : 하이폴리 메시의 디테일을 로우폴리 메시의 노멀 맵으로 굽는 과정. Marmoset Toolbag, Blender, Substance Painter에서 수행한다.
탄젠트 (Tangent) : 노멀과 수직이면서 UV의 U 방향을 따르는 벡터. 노멀 맵의 TBN 행렬 구성에 필수이다. 버텍스 데이터에
포함되어 GPU로 전달한다.
'Word' 카테고리의 다른 글
| 오버드로우 (Overdraw) (0) | 2026.07.27 |
|---|---|
| UV 매핑 (UV Mapping) (0) | 2026.07.22 |
| 컴퓨트 셰이더 ( Compute Shader ) (0) | 2026.07.20 |
| HLSL/GLSL (0) | 2026.07.16 |
| WebGL / WebGPU (0) | 2026.07.14 |
