C++ Algorithm & Study/Game Math & DirectX 11

[Direct11] 37. 3D Direct Application - Normal Mapping

GameChoi 2023. 6. 30. 16:25

1. 3D Direct Application

1.1 Normal Mapping

1.1.1 Normal Mapping System

1.1.1.1 Normal Mapping

 - 보통의 Texture Mapping에서 법선 벡터들이 여전히 덜 조밀한 정점 수준에 정의되어 삼각형을 따라 보간

   - 표면 법선들을 좀 더 높은 해상도에서 지정

     - 조명의 세부도가 높아지지만 메시 기하구조 자체의 세부도까지는 높아지지 않음

       - 테셀레이션과 결합된 Normal Mapping을 사용하면 메시의 세부도를 높일 수 있음

 - 벽돌 Texture를 사용할 때 올록볼록한 요철 형태에 비해 너무 매끄러워 부자연스럽게 보이는 경우 발생

   - 메시 기하구조가 매끄럽기 때문에 이러한 현상 발생, 조명 계산은 메시 기하구조에 근거하여 수행하므로 일치X

     - 광원이 동적으로 움직이는 상황에 Texture Map에 있는 세부사항들이 조명에 제대로 반영되게 하는 기법 사용

1.1.1.2 Normal Map

 - Normal Map의 경우 Texture지만 픽셀마다 RGB 색상 자료를 담는 것이 아닌 법선 정보를 담음

   - 번선 맵의 각 픽셀은 X, Y ,Z 성분을 담으며, 하나의 법선 벡터를 정의

     - Normal Map을 각 성분 당 8비트로 이루어진 24비트 이미지 형식에 저장, 각 성분마다 0 ~ 255까지 값을 담을 수 있음

 - 단위 벡터를 24비트 형식으로 변환, 단위 벡터는 [-1, 1]의 범위를 가지고 있음

   - 이를 [0, 1] 구간으로 이동 및 255의 값을 곱하면 24비트 형식으로 압축할 수 있음

 - Normal Map을 사용하기 위해 [0, 255] 구간의 압축된 Texture 좌표 성분에서 [-1, 1] 구간의 원래 벡터 성분 값 복원

   - 위의 방법으로 부터 구해진 함수를 역함수를 통해 구하게 되면 복원할 수 있음

float3 normaIT = gNormalMap.Sample(gTriLinearSam, pin.Tex);

 - 위의 코드로 0부터 1까지 만족하는 정규화된 성분들로 이루어진 색상 벡터가 설정

   - 복원 과정의 일부가 이미 자동으로 일어남, 즉 [0, 255] 구간의 정수를 255로 나눠 [0, 1] 구간을 만드는 작업이 끝남

     - 이후 [-1, 1]로 값을 변경

normalT = 2.0f * normalT - l.Of;

1.1.1.3 Tangent Space

 - 3차원 Texture가 입혀진 삼각형이 Texture Mapping을 할 때 왜곡이 일어나지 않는 경우

   - Texture 공간은 삼각형 평면에 놓여 삼각형과 접함

     - 삼각형 면 법선 N을 도입하여 삼각형 평면과 접하며 기저벡터 T, B, N으로 이루어진 3차원 좌표계가 형성

       - 좌표계로 정의되는 공간을 삼각형의 Texture Space & Tangent Space, 접공간은 삼각형마다 다름

 - 빛은 세계 공간을 기준으로 하므로 조명 공식을 계산하려면 법선 벡터와 빛이 같은 공간에 있어야 함

   - 접공간 좌표계를 삼각형 정점들이 기준으로 하는 물체 공간 좌표계와 연관

     - Texture 좌표들을 물체 공간으로 이동하면 세계 행렬을 이용하여 물체 공간 좌표를 세계 공간 좌표로 변환

1.1.1.4 Vertex Tangent Space

 - Texture 공간을 법선 매핑에 그대로 사용하면 조명 결과가 삼각형 별로 나누어진 것 같은 모습이 나타남

   - 접공간이 삼각형의 면 전체가 일정하기 때문, 접벡터들을 정점별로 지정

   - 정점 법선으로 매끄러운 표면을 흉내 낼때 방식으로 접벡터들의 평균을 계산해서 적용

 - 일반적으로 펴균으로 구한 TBN 기저들은 다시 정규 직교화를 통해 서로 직교인 단위벡터들로 만들 필요가 있음

   - 흔히 그람 슈미트 절차를 이용하여 재정규화 작업을 수행

struct NormalMap { XMFLOAT3 Pos; XMFLOAT3 Normal; XMFLOAT2 Tex; XMFLOAT3 TangentU; }

 - 위를 통해 메시의 각 정점마다 정규 직교 TBN 기저가 생성 & 물체 공간 좌표계에 상대적인 TBN 기저 좌표를 알고 있음

   - 접공간의 좌표를 물체 공간으로 변환해 주는 행렬의 성분들도 결정, 행렬은 직교행렬이므로 전치행렬이 역행렬임

     - 조명 계산 시 법선 벡터를 접공간에서 세계 공간으로 변환

       - 법선을 접공간에서 물체 공간으로 변환 후 세계 형렬을 이용하여 물체 공간에서 세계 공간으로 변환

 - 즉 접공간에서 세계공간으로 바로가고 싶으면 접공간 기저벡터들을 직접 세계공간 좌표로 서술

   - 그런 좌표들은 TBN 기저를 물체공간 좌표에서 세계 공간 좌표로 변환해서 얻을 수 있음

1.1.2 Tangent Space Shader

 - Normal Map을 이용하여 프로그램 초기화 시점에서 이미지 파일로 부터 2차원 Texture를 생성

 - 각 삼각형마다 접벡터 T를 계산

   - 메시의 각 정점마다 메시에서 정점을 공유하는 모든 삼각형의 접벡터의 평균을 내서 정점별 접벡터를 구함

 - 정점 셰이더에서 정점 법선 벡터와 접벡터를 세계 공간으로 변환하고, 그 결과를 픽셀 셰이더로 출력

 - 주어진 픽셀 위치에서의 TBN 기저를 삼각형 표면을 따라 보간된 접벡터와 법선 벡터를 이용해서 구축

   - 법선 맵에서 추출한 법선 벡터를 기저를 이용해서 접공간에서 세계 공간으로 변환

     - 세계 공간 법선 벡터를 조명 계산에 사용

1.1.2.1 Normal Sample To World Space

float3 NormalSampleToWorldSpace(float3 normalMapSample, float3 unitNormalW, float3 tangentW);

 - Normal Map이 [0, 255]의 구간에서 Texture를 샘플링을 통해 각 성분이 [0, 1]로 변환

   - 이후 단위 벡터 [-1, 1]의 구간으로 변환하기 위해 밑의 코드 적용

float3 normalT = 2.0f * normalMapSample - 1.0f;

 - 정규 직교 기저를 생성하여 접공간에서 세계 공간으로 변환

   - 보간을 거치고 난 접벡터와 법선 벡터는 더 이상 정규직교가 아닐 수 있음

     - T의 N방향 부분을 T에서 빼서 N에 직교하는 벡터를 얻음

float3 N = unitNormalW; float3 T = normalize(tangentW - dot(tangentW, N) * N); float3 B = cross(N, T);
float3x3 TBN = float3x3(T, B, N); float3 bumpedNormalW = mul(normalT, TBN);
return bumpedNormalW;

 - 위에서 만든 함수를 통해 픽셀 셰이더에서 적용하여 접공간에서 세계 공간으로 변환된 Normal Map 사용

   - 이후 조명, 입방체 맵핑에 적용

float3 normalMapSample = gNormalMap.Sample(samLinear, pin.Tex).rgb;
float3 bumpedNormalW = NormalSampleToWorldSpace(normalMapSample, pin.NormalW, pin.TangentW);