개발하는 리프터 꽃게맨입니다.

[수학] 원근 투영: 화면에 현실감을 부여하는 변환 본문

컴퓨터 그래픽스/수학

[수학] 원근 투영: 화면에 현실감을 부여하는 변환

파워꽃게맨 2024. 7. 20. 20:27

1. 원근 투영 변환의 원리

시선을 한 점에 고정시키고, 고정된 점으로부터 화폭까지 곧게 뻗은 실을 활용해 그림을 그리는 기법을 투시 원근법이라고 한다.

 

우리가 만든 3차원 공간에 투시 원근법의 원리를 적용하기 위해선 공간의 모든 점이 한 점을 향해 모이는 형태로 변경해야 한다. 이러한 변환을 원근 투영 변환이라고 부른다.

 

가상 공간에서 눈에 대응하는 물체는 카메라다.

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

 

카메라에 화각을 설정하면 상하좌우가 균등한 사각뿔 영역이 만들어진다.

 

즉, 화각은 사각뿔 영역의 크기를 담당한다고 볼 수 있다. 

 

원근 투영 변환은 뷰공간을 카메라의 한 점으로 모이는 사각뿔 형태를 가진 공간으로 변환하는 작업이라고 할 수 있다.

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

 

이를 위해서 모든 물체의 상이 맺히는 가상의 평면을 생성해야 하는데, 이를 투영 평면이라고 부른다.

투영 평면과 카메라의 위치가 멀어질수록 투영 평면은 더 커진다.

 

카메라부터 투영 평면까지의 거리를 초점 거리라고 한다.

 

 

투영 공간의 단면도이다.

 

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

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

 

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

 

투영 평면에 대응하는 정사각형 영역은 2차원 평면의 좌표시스템을 가지는데, 이를 NDC(Nomalized device coordinate)라고 부른다. NDC는 가운데 중점을 원점으로 설정한다.

 

투영 평면에 대응하는 NDC가 언제나 [-1, 1] 값을 가져야 한다면, 카메라에 설정된 화각이 바뀔 때, 초점 거리를 달라져야 한다.

 

화각과 초점 거리의 관계는 아래와 같다.

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

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

 

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

이러한 공간을 유클리드 공간이라고 부른다.

 

이런 유클리드 공간에서 사각뿔 형태로 원근 투영 변환해야 하는데, 이러한 사각뿔 공간을 사영 공간이라고 한다.

 

사영 공간의 x축과 y축은 여전히 직교하기에 x축과 y축은 유클리드 공간과 동일한 성질을 가지지만, 사영 공간의 z축은 독립적으로 행동하지 않고 x축과 y축 모두 영향을 준다.

이는 초점 거리에 따라서 x축과 y축이 만들어내는 투영 평면의 면적이 달라지는 이유이기도 하다.

 

원근 투영 변환에 대응하는 행렬은 지금까지 사용한 표준기저벡터의 변화를 관찰하는 방법으로는 생성할 수 없다.

이를 위해서는 사영 공간이 지니는 특징을 파악해야 한다.

 

먼저, x축을 배제한 y축과 z축으로 공간을 설정하고 투영 평면에 상이 맺히는 과정을 생각해보자.

뷰 공간의 점 P, 이것이 투영 평면에 투영된 점을 P' 라고 하자. 그러면 위 그림과 같이 진행될 것이다.

 

뷰 공간의 점 P로부터 투영된 점 P'의 좌표를 구해야 한다. x값은 배제했으므로 둘의 좌표는 다음과 같다.

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

 

월드 좌표에서 카메라 앞에 위치한 점의 z값은 항상 음수이므로 큰 삼각형의 밑변 길이는 -v가 된다.

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

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

 

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

투영된 최종 좌표는 다음과 같다.

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

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

 

그러나 대부분의 경우 최종 화면의 크기 비율이 1:1 로 균등하지 않기 때문에 물체가 찌그러지는 현상이 발생한다.

문제의 원인이 되는 화면의 가로 세로 비를 종횡비라고 한다. 일반적으로 종횡비는 4:3, 16:10 처럼 균일하지 않다.

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

 

프로그램이 만약 800 * 600 해상도를 가지고 있다고 하면 종횡비는 a = 4/3 : 1 이다.

세로를 기준으로 삼았기 때문에, 가로를 찌그러트려야 한다.

그러면 x축 값에 3/4 (종비의 역수)를 곱하면 된다.

따라서 NDC 좌표를 계산하는 공식은 다음과 같이 수정된다.

 

 

이를 반영한 투영 행렬 P는 다음과 같이 설계할 수 있다.

이 행렬에는 문제점이 하나 있다.

행렬 계산에 있어 변환할 점의 z값이 사용되다 보니 변환할 점마다 다른 행렬을 새롭게 생성해야만 한다.

 

행렬의 가장 큰 장점은 변환 행렬을 미리 곱해준뒤 적용하여 연산량을 줄이는 것이였다.

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

 

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

 

 

즉, 해당 원근 투영 행렬에서 문제점은 v_z 값 이였다.

NDC값을 만들어내는 과정을 두 단계로 분리하면, 하나의 행렬만을 이용해서 원근 투영을 할 수 있게 된다.

 

첫 번째 연산에서는 원근 투영 행렬 P를 위와같이 변형하여 봐 좌표계의 점에 곱해준다.

이러면 NDC 좌표를 얻을 수는 없지만, 통일된 행렬을 사용할 수 있다.

 

이 변환으로 만들어진 공간을 클립 공간, 클립 좌표라고 한다.

 

원근 투영 행렬은 점과 무관하게 카메라 설정 값으로만 구성된다.

 

클립좌표계 점은 위와같이 계산된다.

 

두 번째 연산으로 클립 좌표의 점 P의 각 요소를 -v_z 로 나눈다. 그러면 원하는 NDC의 좌표를 얻을 수 있다.

 


2. 동차 좌표계

동차 좌표계의 동차는 모든 항의 차수가 같음을 의미하는 수학 용어다. 컴퓨터 그래픽스에서 사용하는 좌표계를 동차 좌표계라고 하는데, 이것은 앞에서 원근 투영을 위해 하나의 점으로 모이는 사각뿔의 공간을 사영 공간이라 부르는 것과 연관있다.

사영 공간에서 평행하게 점을 이동시켜보자.

사영 공간의 점과 투영된 점의 좌표는 카메라로부터 멀어질수록 원점(투영 평면의 원점을 뜻함) 에 가까워지고, 카메라에 가까워질수록 커지는 반비례 관계를 가진다.

 

3차원 사영 공간의 점을 (x', y', z')로 표기하고 해당 점이 투영된 NDC 좌표를 (x, y)로 표기해보자. NDC 좌푯값은 사영 공간의 마지막 차원 값 z'에 반비례로 영향을 받으므로 다음과 같은 관계가 성립할 것이다.

 

이번에는 NDC 공간에서 직선의 방정식 y = ax + b 를 생각해보자. 이 NDC 공간의 직선이 실은 사영 공간의 점들이 투영되어 만들어졌다고 생각해보자.

 

그렇다면 직선의 방정식을 사영 공간의 좌표로 표현해보자.

이 식의 양변에 z' 를 곱하면 상수가 사라지고 세 미지수의 차수가 모두 1차식으로 동일한 수식이 구성된다.

 이렇게 미지수에 대한 차수가 모두 동일한 방정식을 동차 방정식이라고 부르며, 이런 이유로 사영 공간이 사용하는 좌표계를 동차좌표계라고도 부른다.

 

원근 투영 변환에서 등장한 사영 공간, 클립 공간, 동차 공간은 모두 동일한 공간을 가리키고, 클리 ㅂ좌표, 동차 좌표 역시 동일한 자표계를 가리킨다.

 

사영 공간의 점이 카메라로부터 멀어질수록 투영된 NDC 좌푯값은 원점 (0, 0) 에 가까워진다.

이는 회화에서 말하는 소실점에 해당한다.

 

다른 행렬과의 곱을 위해서 4차원으로 확장하면 위와 같은 원근 투영 행렬을 얻을 수 있다.


3. 깊이 값

3차원 물체를 결국 2차원으로 투영해서 그릴경우, 나중에 그린 물체가 먼저 그린 물체보다 앞에 있을 수 밖에 없다. 이 문제를 해결하는 방법은, 물체가 카메라로부터 얼마나 떨어져 있는지에 대한 정보를 기록하는 것이다.

 

이를 깊이 값이라고 한다.

 

2차원 평면이었던 NDC 영역에 깊이 값을 추가하면 3차원으로 NDC 영역이 확장된다. 깊이 값의 범위는 동일하게 [-1, 1]로 설정한다.

 

 

 

카메라에 부여한 시야각은 깊이와 무관하기 떄문에 카메라에 추가 속성을 부여해야 한다.

이를 각각 근평면, 원평면 이라고 한다.

 

카메라 시야를 구성하는 사영 공간을 근평면과 원평면으로 잘라주면 사각뿔에서 뾰족한 부분이 잘린 형태가 나오는데 이를 절두체라고 한다.

 

절두체로 생성되는 3차원 NDC 영역의 범위는 위와같이 설정된다.

NDC의 깊이 값은 멀어질수록 증가하기 떄문에 3차원 NDC 공간은 왼손 좌표계를 형성한다.

 

기존 원근 투영 행렬에서 사용하지 않았던 행을 사용해보자.

 

깊이 값은 x축과 y축에 각각 직교하므로 영향을 받지 않는다.

그러므로 i와 j값은 0으로 설정해야 한다.

이제 2개의 미지수가 남는데, 이를 구하기 위해서는 두 개의 샘플 데이터가 필요하다.

 

두 미지수를 얻기 위해,

카메라로부터 근평면까지의 거리를 n, 원평면까지의 거리를 f로 설정하자.

그리고 카메라의 중심으로부터 시선 방향으로 n만큼 이동한 근평면 상에 위치한 점을 생각해보자.

 

뷰 공간에서 카메라 시선 방향은 -z축이므로 뷰 공간에서 이 점의 좌표는 (0, 0, -n, 1) 이 된다.

이는 깊이 값의 시작 지점이기 때문에 NDC의 좌표 (0,0, -1) 에 대응된다.

 

동일하게 뷰 공간의 원 평면상의 좌표는 (0, 0, -f, 1) 인데 이는 깊이 값의 끝지점이기 때문에 NDC 좌표 (0,0,1) 에 대응한다.

 

원근 투영 행렬에 뷰 공간의 점을 곱한 결과는 클립 좌표가 된다.

근평면의 점을 P1, 원평면의 점을 P2라고 할 떄 이들은 다음과 같이 계산된다.

 

 

아직 원근 투영 행렬을 k와 l을 모르기 때문에 클립 좌표의 세 번째 요소를 계산할 수 없다.

하지만 클립 좌표의 네 번째 요소로 모든 요소를 나눈 값은 NDC 좌표를 가리키고 NDC 좌표의 z 값은 각각 -1과 1임을 알고 있다. 따라서 근평면의 클립 좌표는 (0, 0, -n, n) 이 되고, 원평면의 클립 좌표는 (0, 0, f, f) 이 되어야 한다.

 

 

 

따라서 다음 식을 얻을 수 있다.

 

여기로 부터 다음과 같은 연립 방정식을 구할 수 있다.

 

각각을 풀면 다음의 결과를 얻을 수 있다.

 

사용할 원근 투영 행렬은 최종적으로 위와 같다.


4. 원근 보정 매핑

무게중심을 이용하여 보간하는 방식을 현재 원근 변환 행렬과 사용하면 오류가 발생하게 된다. 

뷰 공간의 점이 투영 평면에 투영 되는 경우 각 점이 가지는 무게중심좌표 값이 달라지는데, 그 값을 이용해서 보간을 진행하기 때문이다.

 

무게중심좌표가 달라지는 이유는  NDC 공간으로의 변환 과정에서 사영 공간의 마지막 요소 w의 값을 -vz 로 나눴기 때문이다. 이는 사영 공간과 NDC 공간 사이에 형성된 바비례 관계로부터 기인한다.

 

텍스처 매핑을 진행할 때는 변경된 무게중심좌표를 사용하는 것이 아니라, 퉅영되기 전 사영 공간에서의 무게중심좌표를 사용해야 한다.

따라서 투영 과정을 거꾸로 추적해 NDC에서 구한 무게중심좌표로부텉 사영 공간의 무게중심좌표를 계산해야 한다.

이렇게 투영 전의 무게중심좌표 값을 계산해 텍스처를 매핑하는 것을 투영 보정 보간이라고 한다.

 

NDC의 무게중심좌표를 사영 공간의 무게중심좌표로 돌려주는 투영 보정 보간의 계산식을 유도하기 위해서는 y = 1/x 가 가진 성질을 살펴봐야 한다. 

 

 

여기서 값이 2, 6인 경우를 생각해보자.

x좌표에서 4는 2와 6의 중점이다.

그런데 함수를 통해 최종 결과 값은 1/2, 1/6이다. 여기에서의 중점은 1/3 이 된다.

 

뷰 공간의 값은 x축이라고 할 수 있고, NDC 공간의 값은 y축이라고 할 수 있다.

즉, 우리가 NDC 공간에서 구한 무게중심좌표는 1/3인데, 이로부터 거꾸로 4를 얻어내야 하는 상황이다.

 

이를 게산하기 위한 수식을 알아보자.

y축의 무게중심좌표 (q1, q2) 는 다음과 같은 수식이 성립한다.

 

x축의 무게중심좌표 (t1, t2)도 다음과 같은 수식이 성립한다.

x와 y는 서로 반비례 관계다. 따라서 다음의 수식이 성립한다.

 

양변에 x'를 곱하면 다음의 수식으로 전개된다.

그런데 무게중심좌표의 정의에서 모든 무게중심좌표의 합은 1이다. 따라서 다음과 같이 변환할 수 있다.

최종적으로 이와같은 비례관계를 얻어낼 수 있다.

 

세 점을 조합하는 경우에도 동일한 관계를 얻을 수 있다.

 

이제 NDC 공간에서 구한 세 무게중심좌표를 (q1, q2, q3)로 가정하자.

위 식에서 x를 뷰 공간의 z로 바꿔 적용하고 우리가 구해야 할 무게중심좌표를 (t1, t2, t3) 로 지정하면 다음의 식이 성립된다.

이 때 z'의 값은 다음과 같이 구할 수 있다.

투영행렬변환이 적용된 클립공간의 좌표를 (x, y, z, w) 라고 할 때, 클립공간의 w값은 뷰 공간의 -z에 대응된다고 할 수 있다.


5. 깊이 버퍼

다수의 게임 오브젝트를 그리는 경우 멀리있는 오브젝트를 먼저 그리고 가까운 오브젝트를 다음에 그리도록 해야 원근감이 표현된다. 하지만 같은 거리에 있는 두 게임 오브젝트가 서로 겹쳐 있다면, 물체 단위로 그리는 순서를 조절하는 것만으로는 부족하다.

 

해결방법은 오브젝트 단위가 아닌 삼각형의 픽셀 단위로 깊이를 비교해 가까운 곳에 있는 픽셀만 그리는 것이다.

 

이를 구현하기 위해 화면의 픽셀마다 현재의 깊이 값을 보관해주는 별도의 저장 공간이 필요한데, 이를 깊이 버퍼라고 한다.

 

최종 픽셀을 찍는 과정에서 현재 깊이 값을 깊이 버퍼에 저장된 값과 비교해 현재 깊이 값이 작은 경우에만 픽셀을 찍도록 로직을 구성하면 그리기 순서 문제를 해결하면서 물체에 가려져 그리지 않아도 되는 픽셀을 파악해 그리기를 건너뛸 수 있다.

 

이 작업을 깊이 테스팅이라고 한다.


6. 연습 문제

1. 계산상 편의를 위해서 NDC를 상하, 좌우로 [-1, 1] 인 영역으로 만들고자 한다. 화각이 60' 일 때, 초점거리는 얼마인가

 

2. 카메라는 (0,0,0)의 좌표에 위치하고, 점 P 는 (10, 23, 19) 의 좌표를 가지고 있다. 화각이 90' 인 사영 공간이 있을 때, NDC 좌표로 점 P를 투영하면 그 좌표는 어떻게 되는가?

 

(1) 종횡비를 고려하지 않을 경우

 

(2) 16:9 모니터의 종횡비를 고려할 경우 (여기서 비율은 y = 1 로 한다.)

 

3. NDC영역을 모니터 크기만큼 늘려서 화면을 구성하면 물체가 찌그러지는 현상이 발생한다. 이에대한 해결책은 무엇인가?

 

4. 한 번의 행렬곱으로 원근 투영을 할 때 문제점은 무엇인지 쓰고, 해결법을 말하여라

 

5. 깊이를 고려해서 4차원 원근 투영 행렬을 만든다고 할 때, 최종 원근 투영 행렬을 식을 유도하시오

 

6. 텍스처 매핑을 보간함에 있어 NDC 공간 좌표를 사용하면 안되는 이유는 무엇인가?