셰이더와 GPU 파이프라인 · 노멀과 접선 공간 · 이론
법선을 텍스처에 담는다
한 줄 요약
조명은 법선만 보고 밝기를 정하므로, 법선을 텍스처에서 읽어 오면 평평한 면도 울퉁불퉁해 보입니다. 그 법선을 어느 좌표계에 담을지가 접선 공간의 문제입니다.
왜 이게 필요했나
벽돌 벽의 요철을 실제 기하로 만들면 삼각형이 수십만 개 필요합니다. 그런데 사람이 요철을 알아보는 단서는 대부분 밝기의 변화입니다. 튀어나온 부분은 빛을 정면으로 받아 밝고, 들어간 부분은 비스듬히 받아 어둡습니다.
앞 코스에서 본 것처럼 밝기는 법선과 빛 방향의 내적으로 정해집니다. 그렇다면 기하를 바꾸는 대신 법선만 픽셀마다 바꿔치기하면 같은 밝기 변화를 만들 수 있습니다. 삼각형 두 개짜리 평면에 벽돌의 요철이 생깁니다. 실루엣은 여전히 평평하지만 정면에서 보면 구분이 어렵습니다.
어떻게 동작하나
법선은 각 성분이 -1에서 1 사이인 단위 벡터인데, 텍스처의 채널은 0에서 255 사이입니다. 그래서 옮겨 담는 규칙이 필요합니다.
저장할 때 c = (n * 0.5 + 0.5) * 255읽을 때 n = c / 255 * 2 - 1 (그리고 다시 정규화)평평한 부분의 법선 (0, 0, 1) 은 (128, 128, 255) 이 됩니다. 노멀 맵이 전체적으로 연보라빛으로 보이는 이유가 이것입니다. 그 색이 곧 "여기는 평평하다" 는 뜻입니다.
어느 좌표계의 법선을 담을 것인가 가 다음 문제입니다. 월드 공간의 법선을 그대로 담으면 그 텍스처는 그 물체가 그 자세로 있을 때만 맞습니다. 물체가 회전하면 전부 틀립니다.
그래서 접선 공간을 씁니다. 표면의 각 지점에 그 표면을 기준으로 한 좌표계를 세우는 것입니다. 세 축은 이렇습니다.
N (normal) 표면에 수직인 방향T (tangent) 표면 위에서 텍스처의 u 가 늘어나는 방향B (bitangent) 표면 위에서 텍스처의 v 가 늘어나는 방향이 좌표계에서 "평평함" 은 언제나 (0, 0, 1) 입니다. 물체가 어떻게 놓여 있든 같습니다. 그래서 같은 노멀 맵을 여러 물체에 붙일 수 있고, 물체가 움직여도 그대로 맞습니다.
T 와 B 는 정점 위치와 UV 로부터 계산합니다. 삼각형의 두 변과 그에 대응하는 UV 변화량을 연립해 풀면 나옵니다.
e1 = p1 - p0, e2 = p2 - p0d1 = uv1 - uv0, d2 = uv2 - uv0r = 1 / (d1.x * d2.y - d2.x * d1.y)T = (e1 * d2.y - e2 * d1.y) * rB = (e2 * d1.x - e1 * d2.x) * r분모가 0 이 되는 경우가 있습니다. 삼각형의 UV 가 한 점으로 뭉쳤거나 한 직선 위에 놓였을 때인데, 그런 삼각형에는 접선 공간이 정의되지 않습니다. UV 를 펼칠 때 생기는 흔한 문제라 도구들이 경고를 냅니다.
노멀 맵은 대개 고해상도 모델에서 구워 냅니다. 삼각형 수백만 개짜리 모델을 만들어 두고, 저해상도 모델의 각 지점에서 고해상도 표면의 법선을 찾아 접선 공간으로 옮겨 텍스처에 적는 것입니다. 그래서 게임의 캐릭터는 삼각형 몇만 개인데 표면의 주름은 수백만 개짜리 모델의 것을 그대로 갖고 있습니다.
관련된 기법도 몇 가지 있습니다. 높이 맵은 법선 대신 높이 하나만 저장하고 셰이더에서 기울기를 계산해 법선을 만듭니다. 저장이 3분의 1이지만 계산이 더 듭니다. 시차 매핑은 보는 방향에 따라 UV 를 조금 밀어 깊이감을 흉내 내는데, 노멀 맵만으로는 안 되는 '가려짐' 을 어느 정도 표현합니다. 변위 매핑은 아예 실제로 정점을 밀어내므로 실루엣까지 바뀌지만 기하가 그만큼 필요합니다.
현장에서 만나는 모습
노멀 맵을 붙였는데 조명이 반대로 보이는 문제의 대부분은 초록 채널의 방향입니다. 어떤 도구는 v 축이 위를 향한다고 보고 굽고, 어떤 도구는 아래를 향한다고 봅니다. 두 관례가 섞이면 요철이 반대로 보입니다. 해법은 셰이더에서 초록 채널을 뒤집거나 텍스처를 다시 굽는 것입니다.
또 하나는 압축입니다. 노멀 맵은 일반 이미지 압축을 그대로 쓰면 눈에 띄게 망가집니다. 색의 미세한 오차는 사람이 못 알아채지만 법선의 오차는 밝기 오차로 증폭되기 때문입니다. 그래서 노멀 맵 전용 압축 형식을 쓰고, 그때 z 성분은 저장하지 않고 z = sqrt(1 - x² - y²) 로 복원하기도 합니다.
그리고 노멀 맵은 실루엣을 바꾸지 못합니다. 물체의 가장자리는 여전히 평평하게 잘려 보이고, 비스듬히 보면 요철이 사라집니다. 그래서 실루엣이 중요한 곳에는 실제 기하나 시차 매핑 같은 다른 기법을 씁니다.
마지막으로 노멀 맵과 조명 계산이 만나는 자리를 정리해 두겠습니다. 방법은 두 가지입니다. 하나는 텍스처에서 읽은 접선 공간 법선을 TBN 행렬로 월드 공간으로 옮겨 월드 공간의 빛과 내적하는 것이고, 다른 하나는 반대로 빛의 방향을 접선 공간으로 옮겨 거기서 내적하는 것입니다. 결과는 같지만 비용이 다릅니다. 빛이 하나뿐이라면 빛을 옮기는 편이 싸고, 그것도 정점 셰이더에서 미리 옮겨 보간할 수 있습니다. 반대로 빛이 여럿이거나 환경 맵을 함께 쓴다면 법선을 월드로 옮기는 편이 낫습니다.
다음 실습에서 할 것
법선을 색으로 옮겨 담는 규칙을 만들고, 반구가 격자로 배열된 노멀 맵을 직접 생성합니다. 그다음 접선 공간의 세 축을 정점 위치와 UV 에서 계산하고, 같은 평면을 상수 법선과 노멀 맵으로 각각 비춰 그림을 비교합니다. 마지막에는 광원을 왼쪽에서 오른쪽으로 옮겨 가며 여섯 장을 뽑아 밝은 쪽이 광원을 따라가는 것을 숫자로 확인합니다.