서의 공간

제18장 노멀 매핑과 변위 매핑 본문

Graphics API/DirectX 11 - Luna

제18장 노멀 매핑과 변위 매핑

홍서의 2020. 12. 23. 21:54

목표

1. 노멀 매핑이 왜 필요한지 이해한다.

2. 노멀 맵을 저장하는 방법을 파악한다.

3. 노멀 맵을 만드는 방법을 알아본다.

4. 노멀 맵에서 노멀 벡터들의 기준이 되는 좌표계를 배우고, 그 좌표계와 3차원 삼각형의 물체 공간 좌표계의 관계를 이해한다.

5. 정점 셰이더와 픽셀 셰이더로 노멀 매핑을 구현하는 방법을 파악한다.

6. 변위 매핑과 테셀레이션을 결합해서 메시의 세부도를 개선하는 방법을 알아본다.

18.1 동기

18.2 노멀 맵

노멀 맵은 그냥 하나의 텍스처이지만, 각 픽셀마다 RGB 색상 자료를 담는 것이 아니라 노멀 정보를 담는다는 특징이 있다. 노멀 맵의 각 픽셀은 R, G, B 채널에 압축된 \(x, y, z\) 좌표성분을 담으며, 이 성분들은 하나의 노멀 벡터를 정의한다. 그림 18.1은 노멀 맵을 시각화한 예이다.

[그림 18.1] 벡터 T (x축), B (y축), N (z축)으로 정의되는 텍스처 좌표계를 기준으로 한, 노멀 맵에 저장된 노멀들. 벡터 T는 텍스처 이미지의 수평으로 오른쪽 방향이고 B는 수직으로 아래쪽 방향, N은 텍스처 평면에 직교인 방향이다.

설명을 위해 여기에서는 노멀 맵을 각 성분 당 8비트로 이루어진 24비트 이미지 형식에 저장한다고 가정한다. 따라서 각 성분마다 0에서 255까지의 값을 담을 수 있다.

[참고]
그림 18.1의 예에서처럼, 대체로 노멀 벡터들은 \(z\)축 방향에 가장 가깝다. 즉, 세 좌표성분 중 \(z\) 성분이 가장 크다. 노멀 맵을 색상 이미지로 표시하면 대체로 파란색을 띠는 이유가 바로 그것이다. 가장 큰 성분인 \(z\) 성분이 blue 채널에 저장되므로 전반적으로 청색이 지배하게 되는 것이다.

그렇다면 하나의 단위벡터를 이러한 24비트 형식으로 압축하려면 어떻게 해야 할까? 우선, 단위벡터의 각 성분의 범위가 [-1, 1]이라는 점을 주목하기 바란다. 이를 [0, 1] 구간으로 이동, 비례해서 255를 곱하고 소수부를 잘라내면 0에서 255까지의 정수가 나온다. 구체적으로 \(x\)가 [-1, 1] 구간의 한 성분일 때, \(f(x)\)의 값이 [0, 255] 구간의 정수인 함수 \(f\)는 다음과 같이 정의된다.

\[f(x)=(0.5x+0.5)\cdot255\]

따라서 단위벡터를 24비트 형식의 텍스처 이미지에 저장하려면 각 벡터의 각 성분에 \(f\)를 적용한 결과를 해당 픽셀의 해당 색상 채널에 기록하면 된다.

이제 다음으로 해결할 문제는 이러한 과정을 뒤집는 방법, 즉 [0, 255] 구간의 압축된 텍스처 좌표성분에서 [-1, 1] 구간의 원래의 벡터 성분 값을 복원하는 방법을 찾는 것이다. 방법은 간단하다. \(f\)의 역함수를 사용하면 된다. 약간의 계산을 거치면 다음과 같은 역함수를 구할 수 있다.

\[f^{-1}(x)=\frac{2x}{255}-1\]

즉, \(x\)가 [0, 255] 구간의 한 정수이면 \(f^{-1}(x)\)는 [-1, 1] 구간의 부동소수점 수이다.

픽셀 셰이더에서 노멀 맵을 추출할 때에는 압축 해제 과정을 직접 구현해야 한다. 픽셀 셰이더에서는 우선 다음과 같은 코드로 노멀 맵의 한 표본을 추출한다.

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

이렇게 하면 normalT에는 \(0\le r,g,b\le 1\)을 만족하는 정규화된 성분들로 이루어진 색상 벡터 \((r,g,b)\)가 설정된다. 여기서 복원 과정의 일부가 이미 자동으로 일어났음을 주목하기 바란다. 즉, [0, 255] 구간의 정수를 255로 나누어서 [0, 1] 구간의 부동소수점 수로 만드는 작업은 이미 완료가 된 것이다.

이제 남은 일은 [0, 1] 구간의 각 성분을 [-1, 1]로 이동, 비례하는 것이다. 이를 위한 함수 \(g:[0,1]\to[-1, 1]\)는 다음과 같이 정의된다.

\[g(x)=2x-1\]

이를 코드로 옮기면 다음과 같다.

// 각 성분을 [0, 1]에서 [-1, 1]로 사상한다.
normalT = 2.0f * normalT - 1.0f;

여기서 스칼라 1.0f은 벡터 (1, 1, 1)로 자동으로 확장된다.

18.3 텍스처 공간·접공간(탄젠트 공간)

3차원 텍스처가 입혀진 삼각형을 생각해 보자. 그리고 텍스처의 삼각형을 3차원 삼각형에 입히는데에는 오직 강체 변환(rigid body transformation, 이동과 회전)만 필요하다고 하자. 이제 텍스처가 데칼(decal, 판박이 또는 스티커) 같은 것이라고 상상하기 바란다. 즉, 데칼을 집어 들어서 삼각형 쪽으로 이동한 후 적절한 방향으로 회전해서 삼각형에 붙이면 텍스처 매핑이 끝난다. 그림 18.2에 텍스처 공간과 3차원 삼각형 사이의 관계가 나와 있다. 텍스처 공간은 삼각형의 평면에 놓여서 삼각형과 접한다. 여기에 삼각형 면 노멀 \(\mathbf{N}\)을 도입하면, 삼각형 평면과 접하며 기저벡터 \(\mathbf{T}\)와 \(\mathbf{B}\), \(\mathbf{N}\)(이를 'TBN-기저'라고 부른다)으로 이루어진 하나의 3차원 좌표계가 형성된다. 이 좌표계로 정의되는 공간을 삼각형의 텍스처 공간 또는 접공간(tangent space)이라고 부른다. 텍스처 좌표들은 바로 이 텍스처 공간 좌표계를 기준으로 한다. 일반적으로 탄젠트 공간은 삼각형마다 다르다는 점에 주의해야 한다.

[그림 18.2] 삼각형의 텍스처 공간과 물체 공간이 관계, 3차원 접벡터 T는 텍스처 좌표계의 u축 방향이고 3차원 접벡터 B는 텍스처 좌표계의 v축 방향이다.

다시 그림 18.1을 보자. 노멀 맵의 노멀 벡터들은 텍스처 공간을 기준으로 정의된다. 그러니 빛은 월드 공간을 기준으로 한다. 조명 공식을 계산하려면 노멀 벡터와 빛이 같은 공간에 있어야 한다. 따라서 가장 먼저 할 일은 접공간 좌표계를 삼각형 정점들이 기준으로 삼은 물체 공간 좌표계와 연관시키는 것이다. 텍스처 좌표들을 일단 물체 공간으로 옮기면, 월드 행렬을 이용해서 물체 공간 좌표를 월드 공간 좌표로 변환할 수 있다.\(\mathbf{v}_0,\mathbf{v}_1,\mathbf{v}_2\)가 어떤 3차원 삼각형의 세 정점이고, 그에 해당하는 텍스처 평면의 삼각형을 정의하는 텍스처 좌표들이 텍스처 공간 좌표축들(즉 \(\mathbf{T}\)와 \(\mathbf{B}\))을 기준으로 \((u_0, v_0), (u_1, v_1), (u_2, v_2)\)라고 하자. 그리고 \(\mathbf{e}_0=\mathbf{v}_1-\mathbf{v}_0\)과 \(\mathbf{e}_1=\mathbf{v}_2-\mathbf{v}_0\)이 그 3차원 삼각형의 두 변 벡터이고 그에 해당하는 텍스처 삼각형 변 벡터는 \((\Delta u_0, \Delta v_0)=(u_1-u_0, v_1-v_0)\)과 \((\Delta u_1, \Delta v_1)=(u_2-u_0, v_2-v_0)\)이라고 하자. 그림 18.2에서 보듯이 이들이 다음과 같은 관계를 만족한다.

\[\mathbf{e}_0=\Delta u_0\mathbf{T}+\Delta v_0\mathbf{B}\]

\[\mathbf{e}_1=\Delta u_1\mathbf{T}+\Delta v_1\mathbf{B}\]

벡터들을 물체 공간 기준의 좌표로 나타내면 다음과 같은 행렬 방정식을 얻을 수 있다.

\[\begin{bmatrix}e_{0,x}&e_{0,y}&e_{0,z}\\e_{1,x}&e_{1,y}&e_{1,z}\end{bmatrix}=\begin{bmatrix}\Delta u_0&\Delta v_0\\\Delta u_1&\Delta v_1\end{bmatrix}\begin{bmatrix}T_x&T_y&T_z\\B_x&B_y&B_z\end{bmatrix}\]

삼각형 정점들의 물체 공간 좌표는 이미 알고 있으며, 이들을 이용해서 삼각형 변 벡터들의 물체 공간 좌표도 구할 수 있다. 따라서 행렬

\begin{bmatrix}e_{0,x}&e_{0,y}&e_{0,z}\\e_{1,x}&e_{1,y}&e_{1,z}\end{bmatrix}

도 구할 수 있다. 마찬가지로, 텍스처 좌표들을 알고 있으므로 행렬

\begin{bmatrix}\Delta u_0&\Delta v_0\\\Delta u_1&\Delta v_1\end{bmatrix}

도 이미 알고 있는 셈이다. 이를 \(\mathbf{T}\)와 \(\mathbf{B}\)의 물체 공간 좌표들에 대해 정리하면 다음 공식이 나온다.

\begin{align}\begin{bmatrix}T_x&T_y&T_z\\B_x&B_y&B_z\end{bmatrix}&=\begin{bmatrix}\Delta u_0&\Delta v_0\\\Delta u_1&\Delta v_1\end{bmatrix}^{-1}\begin{bmatrix}e_{0,x}&e_{0,y}&e_{0,z}\\e_{1,x}&e_{1,y}&e_{1,z}\end{bmatrix}\\\\&=\frac{1}{\Delta u_0\Delta v_1-\Delta v_0\Delta u_1}\begin{bmatrix}\Delta v_1&-\Delta v_0\\-\Delta u_1&\Delta u_0\end{bmatrix}\begin{bmatrix}e_{0,x}&e_{0,y}&e_{0,z}\\e_{1,x}&e_{1,y}&e_{1,z}\end{bmatrix}\end{align}

이 공식을 유도하는 과정에서는 행렬 \(\mathbf{A}=\begin{bmatrix}a&b\\c&d\end{bmatrix}\)의 역행렬이 다음과 같이 주어진다는 사실을 사용했다.

\[\mathbf{A}^{-1}=\frac{1}{ad-bc}\begin{bmatrix}d&-b\\-c&a\end{bmatrix}\]

일반적으로 벡터 \(\mathbf{T}\)와 \(\mathbf{B}\)가 물체 공간에서 반드시 단위 길이는 아님을 주의한다. 그리고 만일 텍스처 왜곡이 있다면 이들은 정규직교도 아니다.

흔히 기저벡터 \(\mathbf{T}\)를 접벡터(tangent vector) 또는 접선 벡터라 부르고 \(\mathbf{B}\)는 종법선(binormal) 벡터 또는 종접선(bitangent) 벡터, \(\mathbf{N}\)은 법선(normal) 벡터라고 부른다.

18.4 정점 접공간(탄젠트 공간)

앞에서는 삼각형에 대한 탄젠트 공간을 유도했다. 그런데 이 텍스처 공간을 노멀 매핑에 그대로 사용하면 조명 결과가 삼각형별로 나누어진 것 같은 모습이 나타난다. 이는 탄젠트 공간이 삼각형의 면 전체에서 일정하기 때문이다. 이런 현상을 피하려면 탄젠트 벡터들을 정점별로 지정하고, 정점 노멀로 매끄러운 표면을 흉내 낼 때와 마찬가지 방식으로 그 탄젠트 벡터들의 평균을 계산해서 적용해야 한다.

1. 메시의 임의의 정점 \(\mathbf{v}\)에 대한 탄젠트 벡터 \(\mathbf{T}\)를, 정점 \(\mathbf{v}\)를 공유하는 메시의 모든 삼각형의 탄젠트 벡터들의 평균을 내서 구한다.

2. 메시의 임의의 정점 \(\mathbf{v}\)에 대한 바이탄젠트 벡터 \(\mathbf{B}\)를, 정점 \(\mathbf{v}\)를 공유하는 메시의 모든 삼각형의 바이탄젠트 벡터들의 평균을 내서 구한다.

일반적으로 평균으로 구한 TBN-기저들은 다시 정규직교화해서 서로 직교인 단위벡터들로 만들 필요가 있다. 흔히 그람-슈미트 절차를 이용해서 그러한 재정규화 작업을 수행한다.

18.5 접선 공간과 물체 공간 사이의 변환

이제 메시의 각 정점마다 정규직교 TBN-기저가 만들어졌다. 또한 메시의 물체 공간을 기준으로 한 TBN-기저벡터들의 좌표도 알고 있다. 즉, 물체 공간 좌표계에 상대적인 TBN-기저좌표를 알고 있는 것이다. 따라서 탄젠트 공간의 좌표를 물체 공간으로 변환해 주는 행렬

\[\mathbf{M}_{object}=\begin{bmatrix}T_x&T_y&T_z\\B_x&B_y&B_z\\N_x&N_y&N_z\end{bmatrix}\]

의 성분들도 결정이 되었다. 이 행렬은 직교행렬이므로 그 전치행렬이 곧 역행렬이다. 따라서 물체 공간에서 탄젠트 공간으로의 좌표 변경 행렬은 다음과 같이 주어진다.

\[\mathbf{M}_{tangent}=\mathbf{M}^{-1}_{object}=\mathbf{M}^{T}_{object}=\begin{bmatrix}T_x&B_x&N_x\\T_y&B_y&N_y\\T_z&B_z&N_z\end{bmatrix}\]

셰이더 프로그램들에서 조명 계산 시 실제로 필요한 일은, 노멀 벡터를 탄젠트 공간에서 월드 공간으로 변환하는 것이다. 한 가지 방법은 노멀을 탄젠트 공간에서 물체 공간으로 변환한 후 월드 행렬을 이용해서 물체 공간에서 월드 공간으로 변환하는 것이다.

\[\mathbf{n}_{world}=(\mathbf{n}_{tangent}\mathbf{M}_{object})\mathbf{M}_{world}\]

그런데 행렬 곱셈에는 결합법칙이 적용되므로 다음과 같이 계산해도 같은 결과가 나온다.

\[\mathbf{n}_{world}=\mathbf{n}_{tangent}(\mathbf{M}_{object}\mathbf{M}_{world})\]

또한 다음 관계가 성립한다는 점도 주목하기 바란다.

\[\mathbf{M}_{object}\mathbf{M}_{world}=\begin{bmatrix}\gets \mathbf{T}\to\\\gets \mathbf{B}\to\\\gets \mathbf{N}\to\end{bmatrix}\mathbf{M}_{world}=\begin{bmatrix}\gets \mathbf{T'}\to\\\gets \mathbf{B'}\to\\\gets \mathbf{N'}\to\end{bmatrix}=\begin{bmatrix}T'_x&T'_y&T'_z\\B'_x&B'_y&B'_z\\N'_x&N'_y&N'_z\end{bmatrix}\]

여기서 \(\mathbf{T'}=\mathbf{T}\cdot \mathbf{M}_{world},\ \mathbf{B'}=\mathbf{B}\cdot \mathbf{M}_{world}\)이고 \(\mathbf{N'}=\mathbf{N}\cdot \mathbf{M}_{world}\)이다. 정리하자면, 탄젠트 공간에서 월드 공간으로 바로 가고 싶으면 탄젠트 공간 기저벡터들을 직접 월드 공간 좌표로 서술하면 되며, 그런 좌표들을 TBN-기저를 물체 공간 좌표에서 월드 공간 좌표로 변환해서 얻으면 된다.

18.6 노멀 매핑 셰이더 코드

노멀 매핑의 전체적인 과정을 정리하자면 다음과 같다.

1. 그래픽 프로그램이나 기타 편의용 도구를 이용해서 원하는 노멀 맵을 생성해서 이미지 파일에 저장한다. 프로그램 초기화 시점에서 그 이미지 파일로부터 2차원 텍스처를 생성한다.

2. 각 삼각형마다 탄젠트 벡터 \(\mathbf{T}\)를 계산한다. 메시의 각 정점 \(\mathbf{v}\)마다, 메시에서 그 정점을 공유하는 모든 삼각형의 탄젠트 벡터의 평균을 내서 정점별 탄젠트 벡터를 구한다.

3. 정점 셰이더에서 정점 노멀 벡터와 탄젠트 벡터를 월드 공간으로 변환하고, 그 결과를 픽셀 셰이더로 출력한다.

4. 픽셀 셰이더에서는, 주어진 픽셀 위치에서의 TBN-기저를 삼각형 표면을 따라 보간된 탄젠트 벡터와 노멀 벡터를 이용해서 구축한다. 그리고 노멀 맵에서 추출한 노멀 벡터를 이 기저를 이용해서 탄젠트 공간에서 월드 공간으로 변환한다. 그런 다음 그 월드 공간 노멀 벡터를 조명 계산에 사용한다.

18.7 변위 매핑

테셀레이션과 함께 사용해서 렌더링 품질을 더욱 높일 수 있는 변위 매핑을 살펴본다. 변위 매핑을 고려하는 주된 이유는 노멀 매핑이 단지 조명의 세부도만 개선할 뿐 실제 기하구조의 세부도는 개선하지 않는다. 변위 매핑(displacement mapping)의 핵심은 표면의 요철(굴곡)과 균열을 묘사하는, 높이맵(heightmap)이라고 부르는 또 다른 맵을 이용해서 기하구조를 변형하는 것이다. 노멀 맵이 각 픽셀마다 세 색상 성분에 하나의 노멀 벡터 \((x, y, z)\)를 저장하는 반면 높이맵은 각 픽셀마다 하나의 색상 성분에 높이 값 h를 저장한다. 시각적으로 높이맵은 그냥 하나의 회색조 이미지이다(색상 채널이 단 하나이므로 회색으로 나타난다). 기본적으로 높이맵은 2차원 스칼라장 \(h=f(x,z)\)의 이산적인(discrete) 표현이다. 이러한 높이 값들은 테셀레이션으로 메시를 더 잘게 쪼개고 영역 셰이더에서 정점을 노멀 벡터 방향으로 이동시킬 대의 이동량으로 쓰인다. 이에 의해 메시 기하구조 자체의 세부도가 높아진다.

'Graphics API > DirectX 11 - Luna' 카테고리의 다른 글

제12장 계산 셰이더  (0) 2020.12.23
제11장 기하 셰이더  (0) 2020.12.23
제10장 스텐실 적용  (0) 2020.12.23
제9장 혼합  (0) 2020.12.23
제8장 텍스처 적용  (0) 2020.12.22
Comments