TL;DR — 비균일 스케일에서 노멀 벡터에 모델 행렬 을 그대로 곱하면 면과의 수직 관계가 깨집니다. 수직 조건 에서 출발하면 노멀 변환 행렬이 임을 유도할 수 있으며, 이것이
GLSL의transpose(inverse(mat3(model)))한 줄에 그대로 대응됩니다.
Table of contents
Open Table of contents
들어가며
3D 장면에서 물체를 배치할 때 모델 행렬 으로 정점 위치를 변환합니다. 위치 변환은 단순합니다 — 을 곱하면 됩니다.
그런데 노멀 벡터에도 같은 을 곱하면 될까요? 균일 스케일(모든 축을 같은 비율로)이면 문제가 없습니다. 하지만 비균일 스케일 — 예를 들어 x축만 2배로 늘리는 경우 — 에서는 표면의 기울기가 바뀌는데, 노멀에 같은 행렬을 곱하면 면과 노멀이 더 이상 수직이 아니게 됩니다.
조명 계산은 노멀이 면에 수직이라는 전제 위에서 돌아갑니다. dot(N, L)로 밝기를 구하는 Lambert 조명이든, 반사 벡터를 쓰는 Blinn-Phong이든, 노멀이 틀어지면 조명이 깨집니다. 게임에서 캐릭터를 한 축으로 늘렸는데 조명이 이상하게 보이는 현상의 원인이 보통 이것입니다.
이 글에서는 비균일 스케일에서 노멀이 틀어지는 원인을 확인하고, 수직 조건에서 역전치행렬을 유도한 뒤, GLSL 코드와 1:1로 대응시킨 과정을 정리했습니다.
이 글에서 다루는 내용:
- 위치와 노멀이 다르게 변환되어야 하는 이유
- 수직 조건에서 역전치행렬 를 유도하는 과정
GLSL코드의 각 부분이 유도의 어디에 대응하는지- 균일 스케일에서 왜 문제가 드러나지 않는지
사전 지식: 행렬 곱, 전치, 역행렬의 기본 개념과
dot product(내적)를 전제로 합니다.
1. 직관 — 위치와 노멀은 다르게 움직인다
핵심은 이 구분입니다:
- 위치는 “어디로 갔나”를 따라갑니다 — 모델 행렬 으로 변환합니다
- 노멀은 “면의 방향이 어떻게 바뀌었나”를 따라갑니다 — 변환된 면에 대해 수직이어야 합니다
비균일 스케일에서는 이 둘이 다르게 움직입니다. x축만 2배로 늘리면 표면은 x방향으로 늘어나면서 기울기가 바뀌는데, 노멀에 같은 변환을 적용하면 면이 기울어진 만큼을 반영하지 못합니다.
그래서 노멀에는 대신, 변환 후에도 면과의 수직 관계를 유지하는 별도의 행렬이 필요합니다.
2. 인터랙티브 시각화 — 을 그대로 곱하면 왜 틀어지는가
슬라이더로 Scale X, Scale Y를 조절하면서 을 그대로 적용한 노멀과 역전치를 적용한 노멀의 차이를 확인할 수 있습니다. 여기서 먼저 봐야 할 포인트는 “결론적으로 역전치가 맞다”보다, 비균일 스케일에서 을 그대로 곱하면 면의 기울기 변화와 노멀 방향이 어긋난다는 사실입니다.
균일 스케일(sx = sy)일 때는 양쪽 다 90도라 차이가 드러나지 않습니다. 하지만 비균일 스케일로 가면, 을 그대로 곱한 쪽은 더 이상 변환된 면에 수직이 아니고, 역전치를 적용한 쪽만 수직 관계를 유지합니다.
3. 수식 유도 — 수직 조건에서 역전치가 나오기까지
출발 조건
표면 위의 접선 벡터를 , 노멀 벡터를 이라 하면, 원래는 항상 수직입니다:
접선은 정점들의 차이와 비슷하게 움직이므로, 모델 행렬 으로 변환됩니다:
노멀의 변환 행렬을 미지수 로 놓습니다:
변환 후에도 수직이어야 합니다:
행렬 곱으로 바꾸기
내적을 행렬 곱으로 쓰려면, 두 열벡터 중 하나를 전치해서 (1x3)(3x1) = 스칼라를 만들어야 합니다. 을 전치하면:
조건 도출
원래 알고 있는 조건은 입니다. 변환 후에도 같은 형태가 되려면 중간의 이 항등행렬처럼 작동해야 합니다:
이러면:
풀기
이것이 역전치행렬(inverse-transpose) 입니다.
참고: 을 전치해서 왼쪽에 놓아도 같은 결론이 나옵니다. 에서 , 즉 . 전치와 역행렬의 순서는 바꿔도 같으므로 — 어느 쪽을 전치하든 결론은 동일합니다.
4. 코드 매핑 — GLSL 한 줄이 유도의 어디에 대응하는가
mat3 normalMatrix = transpose(inverse(mat3(model)));
vec3 normalWorld = normalize(normalMatrix * normalLocal);
| 코드 | 유도에서의 역할 |
|---|---|
mat3(model) | 이동(translation)은 방향 벡터인 노멀에 적용하면 안 되므로 3x3 선형 부분만 추출합니다 |
inverse(...) | — 비균일 스케일로 찌그러진 기저를 되돌립니다 |
transpose(...) | — 수직 조건이 유지되도록 보정합니다 |
normalize(...) | 역전치를 곱하면 길이가 바뀔 수 있으므로 단위 벡터로 재정규화합니다 |
즉 transpose(inverse(mat3(model)))은 유도에서 나온 를 그대로 코드로 옮긴 것입니다.
5. 균일 스케일에서 왜 문제가 안 보이는가
균일 스케일만 쓰는 동안에는 을 그대로 곱해도 노멀이 맞습니다. 왜 그런지 이해하면 역전치가 왜 필요한지도 더 명확해집니다.
모델 행렬의 선형 부분이 회전 + **균일 스케일 **로 구성된 경우를 보겠습니다:
회전 행렬은 직교행렬이라 입니다. 역전치를 구하면:
원래 행렬 과 역전치 은 스케일 계수만 다르고 방향은 같습니다. 노멀은 어차피 정규화하므로 스케일 차이는 사라지고, 결과가 동일해집니다.
반면 비균일 스케일에서는 축마다 스케일이 다르기 때문에, 역행렬을 취하면 각 축의 비율이 뒤집히고 전치까지 하면 방향이 달라집니다. 이때 을 그대로 쓰면 면과 노멀의 수직 관계가 깨지고, 역전치만이 올바른 방향을 보장합니다.
정리하며
- 위치 벡터와 노멀 벡터는 변환 특성이 다릅니다. 위치는 으로, 노멀은 면과의 수직 관계를 유지하는 별도 행렬로 변환해야 합니다
- 수직 조건 에서 출발하면, 노멀 변환 행렬이 (역전치행렬)임을 유도할 수 있습니다
GLSL에서transpose(inverse(mat3(model)))은 이 유도를 그대로 코드로 옮긴 것입니다- 균일 스케일에서는 과 의 방향이 같아서 문제가 드러나지 않지만, 비균일 스케일에서는 역전치만이 올바른 노멀 방향을 보장합니다
참고 자료
- The Normal Matrix — LearnOpenGL
- Why Transforming Normals with the Transpose of the Inverse — Lighthouse3D
이 게시물은 학습한 내용을 바탕으로 초안을 작성한 뒤, LLM의 도움을 받아 내용을 검수하고 다듬어 완성되었습니다.