본문으로 바로가기

원근 투영 변환 ( Perspective projection transformation )

category Graphics/이론 2022. 11. 14. 16:15

투영 변환

3D 투영은 2차원 표면에 3D개체를 표시하는데 사용되는 디자인 기술이다.

즉, 우리 눈이 바라보는 방식으로 가상 공간을 변환하는 것이다.

가상 공간에서 눈과 동일한 행동을 하는 물체는 카메라다.

 

즉 눈과 동일한 행동을 하려면, 멀리 있는것은 작게 가까이 있는 것은 크게 보이는 '원근감' 을 나타내줄 필요가 있는데

이런 원근감을 나타내면서 투영하는 작업을 원근 투영 변환이라고 한다.

그래서 원근 투영 변환을 설계하기 위해서는 눈에 보이는 범위를 카메라에도 설정해야하는데, 이를 화각 ( Field Of View ) 이라고 한다.

카메라에 화각을 설정하면 아래의 그림과 같이 좌우와 위아래가 균등한 사각뿔 영역이 만들어 진다.

 

원근 투영 변환은 x, y, z축이 모두 직교하는 정육면체 형태를 가진 뷰 공간을, 카메라의 한 점으로 모이는 사각뿔 형태를 가진 공간으로 변환하는 작업이라고 할 수 있다.

 

3차원 공간을 변환한 후에는 공간의 물체를 투영해 2차원의 모니터 평면에 담아내야한다.

이를 위해서 모든 물체가 모여질 하나의 가상 평면을 생성해야 하는데, 이를 투영 평면 ( Projection Plane ) 이라고 한다.

투영 평면의 개념은 아래의 그림과 같으며, 투영 평면의 위치가 카메라로부터 멀어질수록 투영 평면은 더 커진다.

투영 평면의 위치를 지정하기 위해 설정한 카메라로부터 투영 평면까지의 거리를 초점 거리 ( Focal length ) 라고 한다.

Viewing distance = Focal length

해당 하는 투영 공간을 오른쪽에서 바라본 2차원 단면도로 생각해보자

 

 

원근 투영을 구현하기 위해서는 우선 투영 평면의 위치를 지정해야 한다.

일반적으로 투영 평면의 위치는 계산의 편의를 위해 위 아래의 크기가 각각 1이 되는 지점으로 결정한다. 

좌우와 상하의 화각이 동일하므로 투영 평면은 좌우와 상하가 각각 [-1, 1]의 범위를 가지는 정사각형의 모습을 띈다.

투영 평면에 대응하는 정사각형 영역은 2차원 평면의 좌표시스템을 가지는데 이를 NDC ( Normalized device coordinate )

라고한다.

이는 가운데 중점을 원점으로 한다.

 

투영 평면에 대응하는 NDC가 언제나 일정한 값을 가져야 한다면, 카메라에 설정된 화각이 변할때, 초점 거리는 달라질 수 밖에 없다. 화각이 커질수록 초점 거리는 가까워지고, 화각이 작아질수록 초점 거리는 멀어진다.

초점 거리의 관계는 다음과 같이 설정된다.

 

$$\tan\left(\frac{\theta}{2}\right) = \frac{1}{d}$$

 

양 변에 역수를 취해도 등식은 성립하므로, 주어진 화각에 따른 초점거리 d는 다음 식으로 계산할 수 있다.

$$d = \frac{1}{tan\left(\frac{\theta}{2}\right)}$$

 

tan 함수를 통해서 초점 거리를 구했다면, 뷰 공간의 점을 투영 평면 위의 점으로 대응시키는 원근 투영 변환을 설계해야 한다.

이 역시 행렬로 설계할 수 있다면 행렬 곱의 장접을 활용해 로컬 공간의 점을 투영 평면의 점으로 한 번에 변환해주는 파이프라인을 만들 수 있다.

 

지금까지 진행한 공간 변환은 모두 x, y, z축이 서로 직교하고 크기가 동일한 정육면체 형태의 3차원 공간이였다.

이렇게 세 축이 모두 직교하는 공간을 유클리드 공간 ( Euclidean space ) 이라고 한다.

우리가 이전까지 다룬 로컬, 월드, 뷰 공간은 모두 유클리드 공간의 형태다.

하지만 우리가 투영 공간으로 점을 변환시키면서 유클리드 공간은 위의 투영 공간과 같은 사각뿔 형태로 변하게 되었다.

이를 투영 공간 ( Projective space ) 이라고 한다.

 

투영 공간의 x축과 y축은 여전히 직교하기 때문에 투영 공간의 x, y 축은 유클리드 공간과 동일한 성질을 가진다.

하지만 z축은 독립적으로 행동하지 않고 x, y축에 모두 영향을 준다. 이는 초점 거리에 다라 x, y축이 만들어내는 투영 평면의 면적이 달라지는 이유기도 하다.

 

이제 실제로 투영 행렬을 설계해보자.

우측에서 바라본 관점에서, x축을 무시한 채 y, z 축으로 공간을 설정하고 투영 평면에 어떤 물체가 찍힌다고 생각해보자.

뷰 공간의 점 P가 투영평면에 투영될 때 다음과 같이 진행될 것이다.

 

뷰 공간의 점 view 로부터 투영된 점 ndc의 좌표를 구해야 한다. x 값을 0으로 고정한 상태이므로, 둘의 좌표는 다음과 같다.

$$P_{view} = (0, v_{y}, v_{z})$$ 

$$P_{ndc} = (0, v_{y})$$

 

두 점의 관계를 파악하기 위해, 닮은꼴 삼각형 두 개를 그려보자.

 

두 삼각형의 닮은꼴 성질로부터 다음의 비가 성립된다.

$$n_{y} : d = v_{y} : v_{z}$$

이로써 투영 평면의 y값은 다음 식으로 얻을 수 있다.

$$n_{y} = \frac{d \cdot v_{y}}{v_{z}}$$

 

카메라의 좌우와 상하의 시야각은 동일하므로 NDC의 x값 또한 y값을 0으로 고정한 후 x, y축 평면을 사용하는 방식으로 구할 수 있다.

 

$$n_{x} = \frac{d \cdot v_{x}}{v_{z}}$$

 

따라서 초점거리와 뷰 좌표로부터 ndc좌표는 다음과 같이 구할 수 있다.

$$P_{ndc} = (n_{x}, n_{y}) =  \left(\frac{d \cdot v_{x}}{v_{z}},  
\frac{d \cdot v_{y}}{v_{z}}  \right) = 
\frac{d}{v_{z}}(v_{x}, v_{y})$$

 

이와 같이 NDC 좌표를 계산했다면 모니터의 최종 화면을 구상하는 작업이 남았다.

NDC좌표를 화면 해상도만큼 가로와 세로를 늘려주면 최종 스크린 좌표가 완성된다.

예를들어 800 x 600 해상도의 화면으로 늘린다고 한다면 다음과 같다.

 

 

하지만 이렇게 늘리게 되면 물체가 찌그러지는 문제가 발생한다.

대부분의 최종화면의 크기 비율은 1:1로 균등하지 않기 때문이다.

예를 들어, 가로와 세로를 늘리면 다음같이 보이게 된다.

 

이처럼 원인이 되는 화면의 가로 세로 비를 종횡비 ( Aspect ratio ) 라고 한다. 모니타터 화면은 보통 16 : 10, 4 : 3 등 가로 세로 비율이 다르다.

 

이 문제를 해결하는 방법은 종횡비를 미리 파악해, NDC 영역에서 미리 찌그러트린 후에 펼치는 것 이다.

종횡비는 보통 하나의 축을 1의 크기로 지정하고 다른 축의 크기를 상대적으로 측정해 나타낸다.

예를 들어 우리의 프로그램이 800 x 600 해상도를 사용한다면, 세로를 기준으로 측정한 종횡비는 1.3333 이된다.

( 800 / 600 = 1.3333.... )

종횡비 기호는 a로 표시하겠다.

 

이제 NDC의 투영 결과를 찌그러트려보자. 좌우로 찌그러트리려면 x축 값을 변경해야하는데, 이는 종횡비의 역수를 곱하면 된다. 따라서 ndc 좌표를 계산하는 공식은 다음과 같이 수정된다.

$$P_{ndc} = \frac{d}{vz}\left(\frac{v_{x}}{a}, v_{y}\right)$$

 

따라서 최종 NDC값을 계산하는 원근 투영 행렬 P는 다음과 같이 설계할 수 있다. 뷰 공간 점의 x, y값으로 만든 벡터를 지정하면 두 점의 좌표는 다음과 같이 계산할 수 있다.

 

$$P_{ndc} = 
P\cdot v =
\begin{bmatrix}v_{x} & v_{y} & v_{z}\end{bmatrix}
\begin{bmatrix}
\frac{1}{a} \cdot \frac{d}{v_{z}} & 0 & 0 \\
0 & \frac{d}{v_{z}} & 0\\
0 & 0 & 1
\end{bmatrix}$$

 

여기서 한 가지 짚고 넘어가야 할 문제가 있다.

이렇게 만들어진 원근 투영 행렬을 사용한다고 할 때, 변환할 점의 z값이 행렬에 사용되다 보니 변환할 점마다 항상 행렬을 새롭게 생성해야 한다.

 

행렬의 가장 큰 장점은 모든 변환의 행렬을 미리 곱해둔 행렬을 적용함으로써 반복되는 연산량을 줄이는데 있었다. ( 선형변환 )

하지만 이렇게 원근 투영 행렬에 변환할 점의 값이 사용되면 행렬을 사용하는 장점이 사라지고 만다.

( 10만개의 점을 변환한다고 하면 10 만개의 행렬을 생성해야 한다. )

따라서 원근 투영 행렬을 구성할 때 점의 값을 사용하지 않고 카메라 설정만으로 행렬을 만들 수 있어야 한다.

 

클립공간

범용적 행렬을 만들기 위해 사용하는 부차적인 공간을 클립공간이라고 한다.

즉, 뷰 공간과 NDC 공간의 중간 공간이라고 생각하면 된다. 3차원 공간이다.

클립 공간으로 1차적으로 변환하고 이후에 한 번 더 변환을 진행하여 최종 좌표를 만들어낸다.

보면, 모든 좌표에 z값으로 나누고 있는데, 나누기 전의 값을 가지고 좌표를 구성하자는 것이다.

 

$$P_{clip} = 
\left(
\frac{d \cdot v_{x}}{a},
d \cdot v_{y},
v_{z}
\right)$$

 

여기서 각 좌표를 z값으로 나누게 되면

$$P_{ndc} = 
\left(
\frac{d \cdot v_{x}}{v_{z}\cdot a},
\frac{d \cdot v_{y}}{v_{z}},
1
\right)$$

 

이렇게 ndc좌표와 동일하게 된다.

 

따라서 클립 공간을 만들기 위한 행렬을 다음과 같이 설계할 수 있다. 이를 원근투영행렬이라고 한다.

$$P\cdot v = 
\begin{bmatrix}v_{x} & v_{y} & v_{z} \end{bmatrix}
\cdot
\begin{bmatrix}\frac{d}{a} & 0 & 0 \\0 & d & 0\\0 & 0 & 1 \end{bmatrix}
=
\begin{bmatrix}\frac{v_{x} \cdot\ d}{a} & v_{y} \cdot d & v_{z} \end{bmatrix}$$

 

이 하나의 행렬로 클립 공간을 구성한 후, 마지막 z값 나누기를 통해서 ndc공간으로 이동시키면 된다.

 

출처 : 이득우의 게임수학