→ 이 글은 「이득우의 게임 수학」을 바탕으로 작성했습니다.
■ 원근 투영 변환 원리

우리 눈은 멀리 있는 물체일수록 작게 보이는 구조를 가지고 있다. 이는 눈이 외부의 빛을 한 점(망막)으로 모아 받아들이기 때문이다.
르네상스 시대의 화가들은 이 원리를 그림에 적용해 입체감을 표현하는 기법을 만들었고, 이를 투시 원근법(Perspective projection drawing)이라 한다.

우리가 만드는 3D 공간은 결국 2D화면(모니터)에 표현되어야 한다.
따라서 3차원 공간의 모든 점들을 2차원 화면 위에 자연스럽게 투영하기 위해, 멀리 있는 것은 작게, 가까운 것은 크게 보이도록 하는 변환 과정이 필요하다.
이 변환을 바로 “원근 투영 변환(Perspective projection transformation)”이라 한다.
- 가상공간에서 우리 눈에 대응되는 물체는 카메라이고, 이 카메라가 보는 범위를 화각(Field of view)라 한다.
- 카메라에 화각을 설정하면 위와 같은 균등한 사각뿔 영역이 만들어진다.

카메라에서부터 3차원 공간의 모든 물체의 상이 맺히는 “가상의 평면”을 투영 평면(Projection plane)이라 한다.
- 투영 평면은 “3D 점의 상이 어디에 맺힐 것인가”를 정의하는 개념적 기준이고, 실제 계산은 원근 투영 변환 행렬을 통해 수행된다.
- 카메라로부터 투영 평면까지의 거리를 초점 거리(Focal Length)라 하며, 이는 원근감과 화면에 보이는 물체 크기에 영향을 준다.
1. NDC(Normalized device coordinate) 영역

원근 투영을 구현하기 위해 투영 평면의 위치를 지정해야 하는데, 일반적으로 계산의 편의를 위해 위아래 크기가 각각 1이 되는 지점으로 결정한다.
따라서 좌우와 상하가 각각 $[-1,1]$의 범위를 가지는 정사각형 평면에 물체의 상이 맺힌다.
- 투영 평면에 대응하는 정사각형 영역의 2차원 좌표 시스템을 “NDC(Normalized device coordinate)”라 부른다.

투영 평면에 대응되는 NDC가 언제나 일정한 값을 가져야 한다면, 카메라의 화각이 변활 때, 초점 거리는 달라질 수밖에 없다.
- 화각이 커질수록 초점 거리는 짧아진다.
- 화각이 작아질수록 초점 거리는 멀어진다.
그렇다면 화각과 초점 거리의 관계는 아래처럼 직각삼각형의 $\tan$ 함수로 결정된다.

$$ \tan(\frac{\theta}{2}) = \frac{1}{d} $$
[화곽과 초점거리의 관계]
따라서 주어진 화각 $\theta$에 따른 초점 거리 $d$는 양변에 $d$를 곱하고, 다시 양변에 $\tan(\frac{\theta}{2})$로 나누면 아래와 같이 정리된다.
$$ d = \frac{1}{\tan(\frac{\theta}{2})} $$
2. 원근 투영 행렬
지금까지의 공간 변환은 $x,y,z$축이 서로 직교하고 크기가 동일한 형태였다. 이러한 공간을 유클리드 공간(Euclidean space)라 하고, 여태까지 다룬 로컬, 월드, 뷰 모두 유클리드 공간의 형태다.
- 정육면체 형태의 공간.
유클리드 공간의 점들은 원근 투영 변환을 통해 사각뿔 형태로 바뀌게 되며, 이를 사영 공간(동차좌표 공간)이라 한다. 그중 카메라 시야 영역은 절두체 형태로 정의된다.
- 사영 공간에서 $x,y$축은 여전히 직교하지만, $z$축은 깊이에 따라 $x,y$축에 영향을 준다. → 이는 초점 거리에 따라 투영 평면의 크기가 달라지기 때문.
이러한 사영 공간의 성질 때문에 기존의 표준기저벡터의 변화를 관찰하는 방법으로는 행렬을 설계할 수 없다.

따라서, $x$축을 배제(0으로 고정)한 $y, z$축으로 공간을 설정하고 투명 평면에 상이 맺히는 과정을 생각해 보자.
- 뷰 공간의 점 $P_{view}$가 투영 평면에 투영된 점을 $P_{ndc}$라 할 때, 투영 과정은 위와 같이 진행된다.
$x$축을 0으로 고정했으니, 각 공간의 점 $P$의 좌표는 다음과 같이 표현할 수 있다.
$$ P_{view} = (0, v_y, v_z) \\ P_{ndc} = (0, n_y) $$

두 점의 관계를 파악하기 위해 닮은 꼴 삼각형 두 개를 그리면, 닮은꼴 성질로부터 아래의 비가 완성된다.
$$ n_y : d = v_y : -v_z $$
여기서 투영평면의 $y$값인 $n_y$는 아래와 같은 방식으로 얻을 수 있고,
$$ n_y = \frac{d \cdot v_y}{-v_z} $$
카메라의 좌우와 상하의 시야각은 동일하므로, NDC의 $y$값 또한 0으로 고정시키면 $x,z$축의 평면을 사용하는 방식으로 구할 수 있다.
$$ n_x = \frac{d \cdot v_x}{-v_z} $$
따라서 초점거리와 뷰 좌표로부터 $P_{ndc}$는 아래와 같이 계산된다.
$$ P_{ndc} = (n_x, n_y) = (\frac{d \cdot v_x}{-v_z},\frac{d \cdot v_y}{-v_z}) = -\frac{d}{v_z} (v_x, v_y) $$
■ 종횡비
NDC영역은 $[-1,1]$의 범위를 가진, 정사각형 모양의 정규화된 좌표계를 가지므로 실제 화면에 출력하기 위해서는 변환을 통해 화면 해상도에 맞게 변환해주어야 한다.

하지만 모니터는 1:1 비율이 아닌 16:9, 4:3처럼 가로 세로 비율이 다르다.
이때, 가로와 세로를 균등하게 늘리면 물체가 찌그러지게 되는데, 이는 화면의 가로 세로 비, 즉(종횡비 - Aspect ratio)를 고려하지 않았기 때문이다.
- 따라서 종횡비를 미리 파악해 NDC에 원을 투영할 때 종횡비를 거꾸로 뒤집은 비율을 적용해 먼저 찌그러트려보자.

종횡비는 보통 하나의 축 크기를 1로 지정하고, 다른 축의 크기를 상대적으로 측정해 나타낸다.
많이 사용하는 1920x1080 해상도의 세로축을 기준으로 측정한 종횡비는 $a = \frac{1920}{1080} = \frac{16}{9} \approx 1.777...$ 이 된다.
$$ P_{ndc} = -\frac{d}{v_z}(\frac{v_x}{a}, v_y) $$
[NDC 좌표 수정]
NDC의 투영 결과를 좌우로 찌그러트리려면 $x$축 값을 변경해야 하는데, 종횡비의 역수 $\frac{1}{a}$을 곱하면 된다.
- 따라서 NDC 좌표를 계산하는 공식은 위와 같이 변경된다.
마지막으로 뷰 공간의 점$P_{view}$의 $x,y$ 값으로 만든 벡터를 $\vec v = (v_x, v_y)$로 지정하면, $P_{ndc}$의 두 점의 좌표는 다음과 같이 계산할 수 있다.
$$ P_{ndc} = P \cdot \vec{v}= \begin{bmatrix}\frac{1}{a}\,\frac{d}{-v_z} & 0 \\0 & \frac{d}{-v_z}\end{bmatrix}\cdot\begin{bmatrix}v_x \\v_y\end{bmatrix} $$
[원근 투영 행렬]
하지만 이 행렬엔 문제가 있는데, 변환할 점의 $z$값이 행렬에 사용되다 보니 변환할 점마다 행렬을 새로 생성해야 한다.
- 미리 곱해둔 행렬을 사용하여 연산량을 줄이는 장점이 사라짐.
그렇다면 우리는 카메라 설정만으로 행렬을 만들어야 한다.
행렬의 구성 요소를 자세히 보면 $-v_z$를 모두 분모로 사용하는데, 이러면 행렬에서 이 값을 제거하고 결과에서 나눠줄 수 있을 것이다.
1. 원근 투영 행렬에서 값 제거
앞서 설계한 원근 투영 행렬에서 $-v_z$값을 제거하고 행렬의 곱하는 벡터의 값을 $\vec v = (v_x, v_y, v_z)$로 지정하자.
- $\vec v$는 뷰 좌표계의 점과 동일한 값.
$$ P \cdot \vec{v}=\begin{bmatrix}\frac{d}{a} & 0 & 0 \\0 & d & 0 \\0 & 0 & -1\end{bmatrix}\begin{bmatrix}v_x \\v_y \\v_z\end{bmatrix}=\begin{bmatrix}\frac{d}{a}\, v_x \\d\, v_y \\- v_z\end{bmatrix} $$
최종적으로 얻어야 할 NDC 좌표와는 다르지만, 위 행렬$P$는 카메라 설정 값으로만 구성된다. 이렇게 되면 모든 점에 대해 원근 투영 행렬 $P$를 사용할 수 있다.
$$ P_{clip} = (\frac{d \cdot v_x}{a}, d \cdot v_y, -v_z) $$
[클립 좌표]
이렇게 원근 투영 행렬 $P$로 변환되는 좌표계 클립을 클립 좌표(Clip coordinate)라 부른다.
2. 뷰 공간 좌표 적용
$$ P_{ndc} = (\frac{d \cdot v_x}{-v_z \cdot a}, \frac{d \cdot v_y}{-v_z}, 1) $$
마지막으로 클립 좌표의 점의 각 요소를 세 번째 값인 $-v_z$로 나누면 최종적인 NDC의 좌표를 얻을 수 있다.
■ 동차 좌표계
모든 점에서 원근 투영 행렬을 사용할 수 있도록 두 단계로 구분하고, 사용하는 벡터의 값도 한 차원 높였다.
이렇게 한 차원 높인 벡터를 사용하는 것을 게임 제작에서 보통 동차 좌표계(Homogenous coordinate system)를 사용한다고 표현한다.
좀 전에 “유클리드 공간의 점들은 원근 투영 변환을 통해 사각뿔 형태로 바뀌게 되며, 이를 사영 공간이라 한다.”라고 설명하였다.
정확히 말하자면, 사영 공간은 어떠한 모양이 있는 것이 아니라 원근을 선형변환으로 다루기 위한 수학적 구조이다. 그렇다면 사영 공간과 동차 좌표게는 어떤 관련이 있는지 살펴보자.

사영 공간의 점은 카메라와 가까울수록 투영 평면의 원점과 멀어지고, 멀수록 투영 평면의 원점과 가까워지는 반비례 관계이다.
3차원 사영 공간의 점을 $(x', y', z')$으로 표기하고, 투영된 NDC 좌표를 $(x,y)$로 표기한다면, 사영 공간의 마지막 차원 값의 반비례로 영향을 받으므로 아래와 같은 관계가 성립한다.
$$ x = \frac{x'}{z'} \ \ \ \ y = \frac{y'}{z'} $$

여기서 직선의 방정식을 사영 공간의 좌표로 표현해 보자. 위 식을 사용하여 $x,y$를 치환하면,
$$ \frac{y'}{z'} = a\frac{x'}{z'}+b $$
위 식의 양변에 $z'$을 곱하면 상수가 사라지고, 세 미지수의 차수가 모두 1차식으로 동일한 수식이 구성된다.
$$ y' = ax' +bz' $$
이렇게 미지수에 대한 차수가 모두 동일한 방정식을 동차 방정식이라 부르며, 사영 공간에서 사용하는 좌표계도 동차 좌표계라 부를 수 있다.
정리하자면 원근 투영 행렬을 구하기 위한 첫 단계의 클립 좌표는, 투영되기 전 사영 공간의 좌표를 의미한다.
따라서 원근 투영에서 등장한 사영, 클립, 동차 공간은 모두 동일한 공간을 가리키고, 클립, 동차 좌표 역시 동일한 좌표계를 가리킨다.
사영 공간의 점이 카메라로부터 멀어질수록 투영 평면의 원점$(0,0)$에 가까워지는데, NDC의 원점은 회화의 투시 원근 기법에서 이야기하는 “소실점(Vanishing Point)” 에 해당된다.
■ 정리
다음 내용인 깊이값으로 넘어가기 전에 내용을 다시 한번 정리해 보겠다.
1. 월드 → 뷰 공간
월드 공간의 물체 좌표를 카메라를 원점으로 다시 계산한 공간.
- 카메라가 바라보는 방향이 기준축이 되고, View 행렬은 카메라 트랜스폼의 역행렬이다.
2. 투영 평면
뷰 공간에서 카메라 앞에는 투영 평면이 있고, 원근 투영으로 모든 물체의 상이 해당 평면에 맺힌다.
- 여기서 이 투영 평면을 $[-1,1]$로 정규화한 공간을 NDC라 한다.
투영 평면에 대응되는 NDC는 언제나 일정한 값을 가져야 하므로, 화각$(\frac{\theta}{2})$에 따라 초점 거리는 달라진다.
화각이 클수록 초점 거리$(d)$는 짧아지고, 화각이 작을수록 초점 거리는 멀어지는 $d = \frac{1}{\tan(\frac{\theta}{2})}$ 관계를 가지게 된다.
3. 투영 행렬
사영 공간에서 x, y축은 직교하지만 두 축은 z축의 영향을 받는다(초점 거리에 따라 투영 평면의 크기가 달라지기 때문). x축을 0으로 고정하고, view와 ndc 좌표는 다음과 같이 표현한다.
$$ P_{view} = (0, v_y, v_z) \\ P_{ndc} = (0, n_y) $$
두 점의 관계는 두 개의 닮은 꼴 삼각형 성질로부터 구할 수 있고, x값과 y값을 0으로 고정시키는 방법으로 $P_{ndc}$ 좌표를 구할 수 있다.
$$ P_{ndc} = (n_x, n_y) = (\frac{d \cdot v_x}{-v_z},\frac{d \cdot v_y}{-v_z}) = -\frac{d}{v_z} (v_x, v_y) $$
NDC 영역은 정사각형 영역이기 때문에, 화면 모니터 비율에 맞추어 늘리게 되면 물체가 찌그러지는 현상이 생긴다.
$$ P_{ndc} = P \cdot \vec{v}= \begin{bmatrix}\frac{1}{a}\,\frac{d}{-v_z} & 0 \\0 & \frac{d}{-v_z}\end{bmatrix}\cdot\begin{bmatrix}v_x \\v_y\end{bmatrix} $$
따라서 미리 좌우로 찌그러트리면 종횡비 문제를 해결할 수 있고, 종횡비의 역수인$\frac{1}{a}$를 $x$좌표에 곱하면 된다.
하지만 위 행렬은 변환할 점의 $z$값이 사용되다 보니, 이를 배제해야 한다. 앞서 설계한 원근 투영에서 $-v_z$값을 제거하고 곱하는 벡터의 값을 $\vec v = (v_x, v_y, v_z)$로 지정하면,
$$ P \cdot \vec{v}=\begin{bmatrix}\frac{d}{a} & 0 & 0 \\0 & d & 0 \\0 & 0 & -1\end{bmatrix}\begin{bmatrix}v_x \\v_y \\v_z\end{bmatrix}=\begin{bmatrix}\frac{d}{a}\, v_x \\d\, v_y \\- v_z\end{bmatrix} $$
행렬 $P$는 카메라 설정 값으로만 구성할 수 있다.
$$ P_{clip} = (\frac{d \cdot v_x}{a}, d \cdot v_y, -v_z) $$
[클립 좌표]
$$ P_{ndc} = (\frac{d \cdot v_x}{-v_z \cdot a}, \frac{d \cdot v_y}{-v_z}, 1) $$
마지막으로 클립 좌표의 점의 각 요소를 세 번째 값인 $-v_z$로 나누면 최종적인 NDC의 좌표를 얻을 수 있다.
'게임수학' 카테고리의 다른 글
| [게임수학] 10-2. 깊이 값 (0) | 2026.02.28 |
|---|---|
| [게임수학] 9-3. 외적(3) - 삼중곱 (0) | 2025.11.11 |
| [게임수학] 9-2. 외적(2) - 카메라의 회전 행렬, 로드리게스 공식 (0) | 2025.11.11 |
| [게임수학] 9-1. 외적(Cross product) (0) | 2025.11.11 |
| [게임수학] 8-2. 오일러 각의 단점(짐벌락, 회전 보간) (0) | 2025.10.14 |