본문으로 바로가기

투영 변환 행렬 유도방법

아래의 내용은 DirectX 12를 이용한 3D 게임 프로그래밍 입문의 내용 일부를 이해하기 쉽게 수정 하여 작성한 내용입니다. 

투영 변환 행렬이란

투영 변환 행렬이란 3차원 공간 \((x,y,z)\)의 점을 2차원 투영창 평면으로 변환하는 행렬이며, 원근 투영과 직교 투영으로 나뉜다.

  • 직교 투영: 카메라와 물체사이의 거리와 상관없이 물체의 크기가 동일. 건축 도면과 같이 실제 크기를 표현할 때 사용.
  • 원근 투영: 카메라와 물체사이의 거리가 멀수록 물체의 크기가 작아짐. 현실감을 위해서 많은 3D 게임에서 사용.

여기서는 일반 3D 게임에서 많이 사용되는 원근 투영 변환 행렬을 다룬다.

Step 1 절두체의 정의

투영공간의 절두체는 아래의 4가지 수치로 정의 내릴 수 있다. 즉, 변환행렬은 아래의 4개의 수치에 종속되며, 이들로 표현할 수 있다.

  • \(n\): 원점으로부터 가까운 평면까지 거리
  • \(f\): 원점으로 부터 먼 평면까지의 거리
  • \(\alpha\): 수직 시야각
  • \(r\): 투영창의 종횡비\(({w\over h})\).일반적으로 후면 버퍼의 종횡비와 동일하게 설정한다.

Step 2 투영창까지의 거리 \(d\) 유도

투영창을 표현하는 기호를 다음과 같이 나타낼 때 투영창까지의 거리 \(d\)는 다음과 같이 유도할 수 있다.

  • \(h\): 투영창의 높이의 절반
  • \(w\): 투영창의 너비의 절반
  • \(d\): 시점으로부터 투영창까지의 거리

 

삼각함수 공식을 사용하여 \(d\) 값을 다음과 같이 표현할 수 있다.

\[ \tan ({\alpha /  2}) = {h \over d} \]

\[ \therefore  d = {h \over \tan ({a / 2})} \]

Step 3 정점의 투영

닮은 꼴 삼각형의 원리를 이용하여 투영된 \(x’\) 와 \(y’\) 값을 계산하면 다음과 같다.

\[ {x’ \over d} = {x \over z}\]\[x’ = {xd \over z} \]

step2에서 계산한 \( d = {h \over \tan ({a / 2})} \) 를 대입하면 다음과 같다.
\[ \therefore x’ = {hx \over z \tan ( {\alpha / 2})}\]


\[{y’ \over d} = {y \over z}\]\[y’ = {yd \over z}\]
step2에서 계산한 \( d = {h \over \tan ({a / 2})} \) 를 대입하면 다음과 같다.
\[ \therefore y’ = {hy \over z \tan ( {\alpha / 2})}\]

Step 4 정규화된 장치 좌표(NDC)로 변환

\(x'\) 과 \(y'\)의 값의 범위는 각각 \([-w,w]\)와 \([-h,h]\)로 투영창의 크기에 종속된다. 이렇게 될 경우 그래픽 하드웨어에서 다음 단계 연산을 진행할 때 \(w\)와 \(h\)의 값이 필요하게 된다. 이를 방지하기 위해서 \([-1, 1]\) 구간으로 정규화된 NDC 좌표로 변환이 필요하다. NDC 좌표로의 변환은 \(x'\)와 \(y'\)값에 각각 \(w\)와 \(h\) 값으로 나누면 된다.

 

\[x’_{ndc} = {x’ \over w} = {xh \over wz \tan ({\alpha / 2})}\] \[\therefore x’_{ndc} = {x \over rz \tan ({\alpha / 2})} (\because r = {w \over h})\]

 

\[y’_{ndc} = {y’ \over h}\] \[\therefore y’_{ndc} = {y \over z \tan (\alpha/2)} \]

Step5 투영 변환을 행렬로 표현

\(x’_{ndc}\) 와 \(y’_{ndc}\)를 표현하는 수식을 행렬로 표현할 방법은 없다. \(x\)와 \(y\)를 \(z\)로 나누는 부분이 선형 연산이 아니기 때문이다.

 

이런 문제를 해결하기 위해서 컴퓨터 그래픽스에서는 \(z\)로 나누는 부분을 제외한 “선형 연산”과 \(z\)를 나누는 “비선형 연산”을 분리해서 진행하는 방식으로 해결한다.

 

이를 위해서는 선형 연산 과정에 \(z\) 값을 저장해야 하는데 선형 연산의 결과는 4차원 동차좌표계 이므로 \(w\)항을 \(z\)값을 임시로 저장하는 용도로 사용할 수 있다. 이를 반영한 투영 변환 행렬\(P\)는 다음과 같다. (\(A\)와 \(B\) 값은 아래에서 계산 예정.)

\[ P = \begin{bmatrix} {1 \over r \tan {(\alpha / 2)}} & 0 & 0& 0 \\ 0 &{1 \over \tan {(\alpha / 2)}} & 0 & 0 \\ 0 & 0& A & 1 \\ 0& 0& B & 0 \end{bmatrix}\]

 

\[ \begin{bmatrix}x , y , z ,1\end{bmatrix} \begin{bmatrix} {1 \over r \tan (\alpha / 2)} & 0 & 0& 0 \\ 0 &{1 \over \tan (\alpha / 2)} & 0 & 0 \\ 0 & 0& A & 1 \\ 0& 0& B & 0 \end{bmatrix}\]
\[= \begin{bmatrix} {x \over r \tan(\alpha/2)}, {y\over \tan(\alpha/2)} , Az + B , z\end{bmatrix}\]

 

선형 연산을 한 뒤 \(z\)로 나누는 과정을 원근 나누기 혹은 동차 나누기라고 하며, 이는 하드웨어에서 수행되기 때문에 프로그래머가 신경 쓸 필요는 없다.

\[\begin{bmatrix} {x \over r \tan(\alpha/2)}, {y\over \tan(\alpha/2)} , Az + B , z\end{bmatrix} \div z  = \begin{bmatrix} {x \over rz \tan(\alpha/2)}, {y\over z \tan(\alpha/2)} , A + {B\over z} , 1\end{bmatrix} \]

Step6 정규화된 깊이 값 (\(A, B\) 값 계산)

2차원 평면에 투영 변환을 했지만 깊이 정보는 이후 렌더링 단계에서도 사용된다. (ex 깊이 버퍼링)

이후 렌더링 단계에서 필요한 깊이 값 또한 \([0, 1]\) 범위로 정규화가 필요한데 깊이값을 정규화할 수 있는 \(A\)와 \(B\)의 값을 구해본다.

 

정규화된 깊이값 \(g(z)\) 가 \( A + {B\over z}\) 라고 했을 때 조건은 다음과 같다.

  • \(g(n) = 0\) (가까운 평면이 0으로 사상됨)
  • \(g(f) = 1\) (먼 평면은 1로 사상됨)

 

먼저 \(g(n) = 0\)인 조건을 통해서 \(A\)와 \(B\)의 관계를 표현하면 다음과 같다.

\[A + {B\over n} = 0 \]

\[B = - {nA}\]

 

다음으로 \( g(f) =1\) 조건으로 \(A\) 값을 계산하면 다음과 같다.

 

\[A + {B \over f} = A - {nA \over f} = A ( {f - n \over f}) = 1\]

\[\therefore A = {f \over f - n} , B = {-nf \over f - n}\]

 

따라서 최종 투영 변환 행렬의 모습은 다음과 같다.

\[ P = \begin{bmatrix} {1 \over r \tan {\alpha / 2}} & 0 & 0& 0 \\ 0 &{1 \over \tan {\alpha / 2}} & 0 & 0 \\ 0 & 0& {f \over (f-n)} & 1 \\ 0& 0& {-nf \over (f - n)} & 0 \end{bmatrix}\]

 


출처:

한빛미디어 출판: <DirectX 12를 이용한 3D 게임 프로그래밍 입문>