요약

3차원 장면을 2차원 화면에 옮기는 방법은 두 가지다. 평행 투영은 모든 점을 같은 방향으로 곧장 화면에 떨어뜨려, 멀든 가깝든 크기가 그대로이고 평행선이 평행하게 남는다. 원근 투영은 눈 한 점을 향해 모이는 선으로 옮겨, 먼 것이 작게 보이고 평행선이 한 점으로 모인다. 원근이 실제 눈에 가깝지만, 길이와 평행을 재야 하는 설계도에는 평행 투영을 쓴다.

예시로 보기

눈이 원점에 있고 화면이 \(z = 1\)에 있다. 높이 4인 막대가 거리 4에 있으면 화면에서 높이 1로 보인다. 같은 막대가 거리 8로 물러나면 높이 0.5로 보인다. 두 배 멀면 절반 크기다(원근 단축)1.

철길처럼 \(z\) 방향으로 뻗은 두 평행선 \(x = \pm1\)은 거리 1에서 폭 2, 거리 10에서 폭 0.2, 거리 1000에서 0.002로 보인다. 멀어질수록 한 점(소실점)으로 모인다. 평행 투영이었다면 폭이 늘 2다.

그림

왼쪽은 옆에서 본 모습이다. 눈에서 막대 끝으로 그은 선이 화면을 지나는 높이가 화면 속 크기다. 거리 4의 막대는 1, 거리 8의 막대는 0.5로 보인다. 오른쪽은 철길이 화면에 비친 모습이고, 가로선은 깊이 1, 2, 3, 5, 10의 자리다. 두 레일이 멀어질수록 좁아져 소실점 한 점으로 모인다2.

검증: 평행 투영의 평행 유지, 원근 투영의 예시 값, 화면 깊이가 늘 \(d\), 소실점, 중점이 깨짐, 직선이 직선으로 감, 카드 C2 — 11_projection_verify.py

정의

투영은 3차원 월드의 점을 2차원 화면으로 옮긴다. 두 투영이 무엇을 지키는지 견주면 다음과 같다.

  평행(직교) 투영 원근 투영
길이 늘거나 줄 수 있다 늘거나 줄 수 있다
각 지키지 않을 수 있다 지키지 않을 수 있다
평행선 평행하게 남는다 평행하지 않을 수 있다
거리에 따른 크기 같다 가까운 것이 크다(원근 단축)

평행 투영은 평면 \(z = d\)로 \(z\) 좌표만 \(d\)로 바꾼다.

\[\begin{pmatrix}x'\\ y'\\ z'\\ 1\end{pmatrix} = \begin{pmatrix}1 & 0 & 0 & 0\\ 0 & 1 & 0 & 0\\ 0 & 0 & 0 & d\\ 0 & 0 & 0 & 1\end{pmatrix}\begin{pmatrix}x\\ y\\ z\\ 1\end{pmatrix}\]

원근 투영은 눈(원점)에서 점 \((x, y, z)\)로 가는 선이 화면 \(z = d\)와 만나는 점이다. 닮은 삼각형으로 \(\frac{x'}{d} = \frac xz\)라 다음과 같다.

\[x' = \frac{x}{z/d}, \qquad y' = \frac{y}{z/d}\]

\(z\)로 나누기는 선형변환이 아니다. 그래서 동차 좌표의 마지막 성분 \(w'\)에 \(z/d\)를 넣고, 마지막에 \(w'\)로 나눈다.

\[\begin{pmatrix}x'\\ y'\\ z'\\ w'\end{pmatrix} = \begin{pmatrix}1 & 0 & 0 & 0\\ 0 & 1 & 0 & 0\\ 0 & 0 & 1 & 0\\ 0 & 0 & 1/d & 0\end{pmatrix}\begin{pmatrix}x\\ y\\ z\\ 1\end{pmatrix}, \qquad \frac{x'}{w'} = \frac{x}{z/d},\ \frac{y'}{w'} = \frac{y}{z/d},\ \frac{z'}{w'} = d\]
flowchart LR
    L["물체의 로컬 좌표"] -->|"모델 행렬"| W["월드 좌표"]
    W -->|"뷰 행렬 R⁻¹T⁻¹"| C["카메라 좌표"]
    C -->|"투영 행렬"| H["동차 좌표 x', y', z', w'"]
    H -->|"w'로 나누기"| S["화면 좌표"]

투영은 맨 끝의 두 칸이다. 그 앞에서 점을 카메라 기준 좌표로 바꿔 두어야 눈이 원점에 오고 화면이 \(z = d\)에 놓인다. 세 행렬은 모두 \(4 \times 4\)라 미리 곱해 둘 수 있지만, 마지막 나누기는 행렬이 아니라 따로 한다.

활용

  • 3D 게임과 영화는 원근 투영, CAD 도면과 2D 게임의 아이소메트릭 화면은 평행 투영을 쓴다. 그래픽스 파이프라인의 투영 행렬도 마지막 성분으로 나누는 이 방법을 쓴다(원근 나누기)1.
  • 원근 투영은 직선을 직선으로 보내지만 선분의 중점은 중점으로 보내지 않는다. 그래서 화면에서 텍스처 좌표를 그냥 선형 보간하면 일그러진다.
  • 흔한 실수: \(w'\)로 나누는 단계를 빠뜨리는 것. 그러면 행렬 곱 결과가 원래 좌표와 같아 원근이 없다.

연결

확인 문제

C1 화면 \(z = d\)로의 원근 투영 식과, 동차 좌표 행렬로 쓰는 법을 쓰라.

답: \(x' = \frac{x}{z/d}\), \(y' = \frac{y}{z/d}\). 행렬의 마지막 행을 \((0, 0, 1/d, 0)\)으로 두어 \(w' = z/d\)로 만들고, 결과를 \(w'\)로 나눈다.

C2 \(d = 2\)일 때 점 \((3, 6, 6)\)의 원근 투영은?

답: \(z/d = 3\)이라 \((1, 2, 2)\).

C3 건물 설계 도면에는 원근 투영과 평행 투영 중 무엇을 쓰나? 다른 쪽은 왜 아닌가?

답: 평행 투영. 거리와 상관없이 같은 길이가 같게 그려지고 평행선이 평행하게 남아, 도면에서 길이를 재고 맞출 수 있다. 원근 투영은 먼 쪽이 작아져 같은 기둥도 길이가 다르게 그려진다.

  1. 검증 코드로 확인했다. ↩ ↩2

  2. 11_projection_plot.py로 그렸고, 같은 코드로 다음 값을 확인했다: 막대 높이 1과 0.5, 폭 2가 거리 10에서 0.2, 거리 1000에서 0.002. ↩