Notice
Recent Posts
Recent Comments
Link
«   2026/02   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
Tags
more
Archives
Today
Total
관리 메뉴

MOONSUN

[D3D] 노멀 매핑(Normal Mapping) 과 접선 공간 (Tangent Space) 본문

D3D

[D3D] 노멀 매핑(Normal Mapping) 과 접선 공간 (Tangent Space)

MoonSun_v 2025. 10. 2. 14:03

 

 

0. High Polygon

삼각형 수가 늘어날수록 Vertex 와 Pixel Shader의 실행 횟수도 늘어난다.

하지만 그렇다고 삼각형을 줄이면(Low Poly) Vertex Nomal이 줄어들어 음영의 디테일이 사라진다..

  • 빛의 입사량은 최종 N dot L에 의해 계산되므로
  • 텍스처의 Normal을 샘플링하면 Low Poly 를 유지한채 입사량은 다르게 디테일 구현이 가능하다.
사람 근육의 힘줄 , 돌 이나 나무의 세부 쪼개짐 등등
즉, 노멀맵을 사용하면 삼각형 수는 줄이면서도 하이폴리곤 수준의 디테일한 음영 효과를 낼 수 있다.

 

 

 

1. Normal Texture (= Normal Map)

 

1-0. Normal (법선)

법선(Normal) = 어떤 표면이 "어느 방향을 바라보고 있는지" 나타내는 벡터

  • 평평한 바닥 → Normal = (0, 1, 0) (위쪽을 바라봄)
  • 큐브 앞면    → Normal = (0, 0, -1)

( 조명 계산에서 필수 )

빛 방향(L)과 표면 방향(N)의 내적 N·L 값으로 밝기(음영)가 정해진다.

 

 

1-1. Normal Texture (= Normal Map)

 

"텍스처" 란 그냥 이미지 파일 (jpg, png, tga 같은 거)

"노멀 텍스처(=노멀맵)"는 특별한 텍스처로, RGB 값에 표면의 법선 방향을 저장한다. 

 

 

즉, 아래와 같이 저장 됨 

RGB값  방향 
R (빨강) X축 방향
G (초록) Y축 방향
B (파랑)  Z축 방향

 

 

1-2. 범위 변환

  • 원래 Normal 벡터는 방향을 나타내니까 보통 (-1 ~ 1) 범위를 가진다.
    • (0, 0, 1) → 앞을 바라봄
    • (0.7, 0, -0.7) → 대각선 아래 방향
  • 하지만 텍스처 이미지(RGB 값)는 (0 ~ 1) 사이 값만 저장할 수 있다. 
    • (흰색=1, 검은색=0, 그 사이는 중간 색상)

그래서 Normal 벡터를 RGB에 저장하려면 (-1 ~ 1) → (0 ~ 1) 값으로 범위 변환을 해줘야 한다. 
법선 벡터 N의 범위 : (-1 ~ 1)
RGB 텍스처 값의 범위: (0 ~ 1)

 

 

 

 

1-2-1. 인코딩 (Normal → RGB)

float3 EncodeNormal(float3 N) 
{
    return N * 0.5 + 0.5;   // (-1~1 → 0~1)
}

예: (-1, 0, 1) → (0, 0.5, 1)

 

 

1-2-2. 디코딩 (RGB → Normal)

float3 DecodeNormal(float3 N) 
{
    return N * 2 - 1;       // (0~1 → -1~1)
}

예: (0, 0.5, 1) → (-1, 0, 1)

 

 

노멀맵이 보라색인 이유?
대부분의 표면은 "위쪽(Z축 = 1)"을 기본 방향으로 가짐 → (0, 0, 1)
인코딩하면: (0.5, 0.5, 1.0) → RGB(128, 128, 255)

이 색이 보라색이라서, 노멀맵은 보통 보라색 바탕에 파랗고 빨간 줄무늬가 있는 것처럼 보인다.

 

 

 

 

 

 

2. 접선 공간 (Tangent Space)

접선 공간(Tangent Space)는 메시 표면의 한 점에서 정의되는 지역 좌표계(Local Coordinate System)

 

이 공간의 기저 벡터는 보통 세 가지로 구성된다

Tangent (T)  UV 좌표의 U 방향에 해당하는 접선 벡터
Bitangent (B, 또는 Binormal)  UV 좌표의 V 방향에 해당하는 접선 벡터
Normal (N)  메시 표면에 수직인 벡터
즉, (T, B, N)이 서로 직교(orthogonal)하는 3차원 기저 좌표계를 형성한다. 

 

각 삼각형의  vertex local 포지션이 다르듯 T,B,N vector도 다르다.

 

2-1. 기하학적 정의

 

  1. 메시 표면의 한 점 P에서 가능한 모든 접선 벡터가 모여 접평면(Tangent Plane)을 이룬다.
  2. 여기에 UV 좌표계를 따라 T(=U축), B(=V축)를 잡고, 법선 N을 세워서 3차원 좌표계 (T, B, N)을 만든다.
  3. 이 좌표계가 곧 Tangent Space !! 
이 3개 (T, B, N)가 Tangent Space의 기저 벡터.

 

 

 

2-2. 수학적 정의 (TBN 행렬) 

 

1. TBN 행렬 이란?

어떤 점의 표면에는 3개의 축이 있다.

 

  • T (Tangent) : U 방향
  • B (Bitangent) : V 방향
  • N (Normal) : 표면에 수직인 방향

 

이 3개의 벡터를 묶어서 만든 것이 TBN 행렬이다.

 

 

2. 접선 공간(Tangent Space)의 벡터

 

  • 노멀맵에 저장된 색깔(RGB)은 접선 공간(Tangent Space)에서의 Normal 방향이다 
  • 즉,  모델 표면 기준 좌표계라서, 월드 좌표계와는 다르다는 것. 

 

 

 

3. TBN으로 월드 공간으로 변환 (아래에서 한번 더 설명 할 거임)

 

즉, 노멀맵에서 읽은 벡터를 실제 월드 공간 방향으로 돌려주는 변환

 

즉, TBN 행렬의 열벡터가 곧 Tangent Space의 기저 벡터들

 

" 열벡터가 기저 벡터 " 이다?
행렬의 첫 번째 열 = Tangent (T)
두 번째 열 = Bitangent (B)
세 번째 열 = Normal (N)
즉, 이 세 벡터가 Tangent Space의 좌표축(기저 벡터) 역할을 하는 것

 

Tangent Space 지역 좌표계에서의 Normal

 

노멀맵은 표면 기준 좌표계(Tangent Space)에 있는 벡터를 저장하고,
TBN 행렬은 그 벡터를 월드 공간 방향으로 변환해주는 다리 역할을 한다.

 

 

 

 

 

3. 월드 공간(World Space) 변환

탄젠트 공간 노말 벡터

 

NormalMap 에 저장된 값은 "접선 공간(Tangent Space)" 기준의 Normal 이므로, 

접선 공간(Tangent Space) 에서 정의된 노말 벡터
를 → TBN 월드 변환 행렬을 통해 → 월드 공간으로 변환 해야 함

 

 

3-1. Tangent Space

DX 왼손 좌표계 기준으로 생각한다면 

  • X축 (1,0,0) → Tangent 방향 (보통 UV의 U축)
  • Y축 (0,1,0) → Bitangent 방향 (보통 UV의 V축)
  • Z축 (0,0,1) → Normal 방향 (표면 수직 방향)

기준의 Normal 값

 

 

3-2. Local Space

 

물체 공간에서 다르게 표현되는 각 접선 공간의 기저벡터

struct Vertex
{
   Vector3 Position;
   Vector2 TexCoord;
   Vector3 Normal;
   Vector3 Tangent;
   Vector3 Binormal;
}

 

 

3-3. World Space

VertexShader 에서 각 T,B,N 에 월드행렬(3x3) 적용하여  월드공간에서의 벡터를 계산한다

기저(축)벡터로 표현한 월드 변환 행렬

3-4. Tangent World 변환 적용 

수학적 표현식은 다음과 같다.

PixelShader 에서 텍스처 샘플링 후에 3x3 회전 변환만 사용하여 변환한다.

 

 

 

4. 큐브에 적용해보기

 

하나의 면을 예로 설명한다면

 

1. vertex 정보에  T = (1, 0, 0),B = (0,-1, 0),N = (0, 0,-1) 설정한다. 

  • ( Normal,Tangent는 오브젝트 좌표계)

2. VertexShader에서

  • Tangent 도 Normal처럼 월드 변환하여 PixelShader에 전달

3. PixelShader에서는

  • Vertex의 Normal 대신 샘플링한 접선공간의 Normal을 월드로 변환 하여 라이팅 계산에 사용한다.

 

 월드공간으로 이동시킨 기저벡터( Tangent,BiTangent,Normal )을 사용하여 월드변환 행렬을 만든다.

 

 

 

 

5. 노말맵 적용 전 후

왼쪽: 블린 퐁 / 오른쪽: 블린 퐁 + NormalMap