뷰행렬(view matrix)
월드에서의 좌표가 어떻든 카메라 관점에서 봤을 때 카메라는 세상의 좌표와 반대로 바라보는 모습이 됩니다.
결국 서로 반대되는 좌표계로 인해 x와 y값이 예상과 다르게 반대로 뒤집힐 것입니다.
따라서 원래 좌표계를 사용하고자 한다면 카메라를 180도 회전시켜서 +z축이 카메라 뒤편을 향하게 하면 우리가 원하는 좌표계로 물체가 나타납니다.
카메라에는 크기의 개념이 없기 때문에, 카메라의 트랜스폼은 크기 변환을 제외한 회전과 이동변환으로만 구성 된다. 카메라의 트랜스폼에 저장된 위치 값을 t(t1, t2, t3)로 지정하고 로컬 축 값을 각각 x = (x1, x2, x3)
y = (y1, y2, y3), z = (z1, z2, z3)로 지정해보자. 그러면 이동행렬 T와 회전행렬 R은 다음과 같다.
회전 변환 역시 이동 변환과 동일하게 역행렬을 적용시킨다. 예를 들어 물체는 가만히 있는데 카메라가 30도 회전하는 상황이라면, 화면을 바라보는 사용자는 마치 물체가 -30도 회전하는 것으로 느낄 것이다. 직교행렬인 회전 변환행렬 R의 역행렬 R-1은 다음과 같이 전치 행렬로 구할 수 있다.
이동행렬의 역행렬도 마저 구해보자.
이 둘을 곱한 최종 뷰 변환 행렬은 다음과 같다.
이동의 역행렬을 먼저 적용할지, 회전의 역행렬을 먼저 적용할지를 선택해야 한다. 적용 순서를 곰곰히 생각해 본다면 이동의 역행렬을 적용한 후에 회전의 역행렬을 적용해야 함을 알 수 있다. 왜냐하면 카메라에 회전을 적용할 시점에 모든 좌표는 카메라를 중심으로 변환되어 있어야 하기 때문이다. 이는 트랜스폼 순서를 거꾸로 돌리는 것과 동일한 원리로 볼수 있다.
정리하면 크기변환 S를 제외한 카메라의 트랜스폼으로 부터 얻어지는 모델링 행렬은 다음과 같다.
M = R * T
모델링 행렬의 역행렬 M(-1) 은 바로 뷰 행렬이 되며, 다음과 같이 전개된다.
M(-1) = ( T * R ) (-1) = R(-1) * T(-1)
우리가 사는 세상은 3차원으로 이뤄져 있고, 이를 모방한 갓아의 게임 공간도 3차원의 체계로 구성된다. 그런데 3차원으로 가상공간을 구성하더라도 모니터 화면에 보이는 최종 결과물은 2차원 평면이 될수 밖에 없다.
2차원의 평면에서 3차원 공간감을 느낄 수 있게 하려면 공간을 어떻게 변환해야 할까?
해답은 우리 눈이 보는 세상을 보는 방식으로 공간을 변환하는 것이다. 우리 눈이 세상을 보는 방식으로 변환하는 방식을 원근 투영 변환이라고 한다.
원근 투영변환의 원리
원근 기법은 르네상스 시대의 화가들에 의해 창안되었다. 당시 르네상스 화가들은 눈에 보이는 상을 그대로 화폭에 담기 위해 그림과 같이 시선을 한 점에 고정시키고 고정된 점으로부터 화폭까지 곧게 뻗은 실을 활용해 그림을 그렸다. 이러한 화법을 투시 원근법 이라고한다.
우리가 만든 3차원 공간에 투시 원근법의 원리를 적용하기 위해서는 그림과 같이 공간의 모든점이 한점을 향해 모이는 형태로 변경해야 한다.
원근 투영이란 우리눈이 바라보는 방식으로 가상 공간을 변환하는 것이다. 가상 공간에서 눈에 대응하는 물체는 카메라다. 그래서 원근 투영 변환을 설계하기 위해서는 눈에 보이는 범위를 카메라에도 설정해야 하는데 이를 화각(filed of view)라고 한다. 카메라에 화각을 설정하면 그림과 같이 좌우와 위아래가 균등한 사각뿔 영역이 만들어진다.
원근 투영 변환은 x, y, z축이 모두 직교하는 정육면체 형태를 가진 뷰 공간을 카메라의 한점으로 모이는 사각뿔 형태를 가진 공간으로 변환하는 작업이라고 할수 있다.
3차원 공간을 변환한후에는 공간의 물체를 투영해 2차원 모니터 평면에 담아내야 한다. 이를 위해서 모든 물체의 상이 맺히는 가상의 평면을 생성해야 하는데 이를 투영 평면이라고 부른다. 투영 평면의 개념은 그림과 같이 자동차가 Image에 그려진 평면을 이야기한다.
투영 평면의 위차가 카메라와 멀어질수록 투영 평면은 더 커진다. 투영평면의 위치를 지정하기 위해 설정한 카메라로부터 투영 평면까지의 거리를 초점거리 Forcal length라고 한다.
화각을 초점거리를 d 로 표시했다.
원근 투영을 구현하기 위해서는 우선 투영 평면의 위치를 지정 해주어야 한다. 일반적으로 투영 평면의 위치는 계산의 편의를 위해 위아래 크기가 1이 되는 지점으로 결정한다. 좌우 상하의 화각이 동일하므로 투영평면 은 [-1, 1]의 범위를 가지는 정사각형 모습을 띈다.
2차원 투영평면의 좌표시스템을 NDC(normailized device coordinate)라고 부른다.
투영 평면에 대응 하는 NDC가 일정한 값을 가져야 한다면, 카메라에 설정된 화각이 변할떄 초점 거리는 달라질 수 밖에 없다. 화각이 커질수록 초점 거리는 가까워지고 화각이 작아질수록 초점거리는 멀어진다. 화각과 초점거리의 관계는 tan함수로 설정된다.
Tan함수로 초점 거리를 구했다면, 뷰 공간의 점을 투영 평면 위의 점으로 대응시키는 원근 투영 변환을 설계해야 한다. 이 역시 행렬로 설계할 수 있다면 행렬곱의 장점을 활용해 로컬 공간의 점을 투영 평면의 점으로 한번에 변환해주는 파이프라인을 만들수 있다.
원근 투영 행렬을 설계하기 위해 카메라가 바라보는 공간은 어떤 성질을 가지고 있는지 알아보자. 지금까지 진행한 공간변환은 모두 정육면체 형태의 3차원 공간이었다. 이렇게 세축이 직교하는 공간을 유클리드 공간이라고 한다. 로컬, 월드, 카메라 공간이 모두 유클리드 공간이다.
그런데 정육면체 형태의 유클리드 공간은 원근 투영 변환에 의해 사각뿔 형태로 바뀌게 되었다. 이를 사영공간 Project Space라고 한다.
사영공간은 x, y 축은 여전히 직교형태를 띄고 있다. 하지만 z축은 독립적으로 행동하지 않고 x축과 y축에 모두영향을 준다. 이는 초점 거리에 따라 x축과 y축이 만들어내는 투영평면의 면적이 달라지는 이유이기도 하다.
그렇다면 원근 투영 변환에 대응하는 행렬은 어떻게 설계해야 할까? 유클리드 공간이 사영공간으로 변환되면서 x, y, z축이 모두 직교하는 체계가 달라지기 때문에 원근 투영 변환에 대응하는 행렬은 지금까지 사용한 표준 기저벡터의 변화를 관찰하는 방법으로는 생성할수 없다. 이를 위해서 사영 공간이 지니는 특징을 파악해 행렬의 각 구성 요소를 퍼즐 다루듯이 하나씩 풀어가야 한다. 문제를 단순화 시키기 위해 x축을 배제한 y축과 z축으로 공간을 설정하고 투영 평면에 상에 맺히는 과정을 생각해보자. 뷰 공간의 점 p가 평면에 투영된 p(ndc)라고 할때 투영 과정은 다음과 같다.
P(view) = ( 0, y, z)
p(ndc) = (0, y)
두 삼각형의 닮은 꼴 성질로부터 다음의 비가 성립된다.
위 비례식으로 부터 다음의 식이 성립된다.
X축과 y축은 같은 시야각을 가지고 서로 직교하므로 x축도 동일하게 구현이 된다.
투영된 최종 좌표는 다음과 같다.
이와 같이 NDC 좌표를 계산했다면 모니터의 최종 화면을 구성하는 작업이 남았다. 그림과 같이 NDC 좌표를 화면 해상도만큼 가로와 세로를 늘려주면 최종 스크린 좌표가 완성된다.
그런데 같은 방법으로 가로와 세로를 늘리면 물체가 찌그러지는 문제가 발생한다.
대부분의 경우 최종화면이 1:1 비율이 아닌 가로가 더 긴 비율을 사용한다.
이를 방지하기 위해 종횡비(가로 세로의 비율)를 미리 파악해, NDC 영역에서 미리 찌그러트린 후에 펼치는 것이다. NDC에 원을 투영할 때 종횡비를 거꾸로 뒤집은 비율을 적용해 먼저 찌그러트려보자.
종횡비
이제 NDC의 투영 결과를 찌그려트려보자. 좌우로 찌그러트리려면 x축 값을 변경해야 하는데, 이는 종횡비의 역수 a/1을 곱하면 된다. 따라서 NDC 좌표를 p(ndc)를 계산하는 공식은 다음과 같이 수정된다.
따라서 최종 NDC값을 계산하는 원근 투영행렬 P는 다음과 같이 설계 할수 있다.
여기서 한가지 짚고 넘어가야 할 문제가 있다. 이렇게 만들어진 원근 투영행렬은 사용한다고 할 때, 변환할 점의 z값이 행렬에 사용되다 보니 변환할 점마다 항상 행렬을 새롭게 생성해야 한다는 점이다.
행렬의 가장 큰 장점은 모든 변환의 행렬을 미리 곱해둔 행렬을 적요함으로써 반복되는 연산량을 줄이는 데 있었다. 하지만 이렇게 원근 투영 행렬에 변환할 점의 값이 사용 되면 행렬을 사용하는 장점이 사라지고 만다.
따라서 원근 투영 행렬을 구성 할 때 점의 값을 사용하지 않고 카메라 설정만으로 행렬을 만들 수 있어야 한다.
우리가 유도했던 투영행렬을 살펴보면, 행렬의 구성요소는 모두 -v(z)값을 분모로 한다. 그렇다면 행렬에서 -v(z)값을 제거하고 대신 행렬의 결과값에서 -v(z)값을 나누어주면 어떨까?
NDC 값을 만들어내는 과정을 두단계로 분리해보자. 첫번째로 앞서설계한 원근 투영 행렬 P를 다음과 같이 변경하고 행렬에 곱하는 벡터의 값을 v로 지정하자 벡터 v는 뷰좌표계와 동일한 값을 가진다.
이 행렬 곱에서 생성된 벡터는 우리가 얻어야 할 NDC 좌표와는 다르지만 원근 투영 행렬 P점과 무관하게 카메라 설정 값으로만 구성된다. 그렇다면 모든 점에 대해서 보편적으로 설계된 원근 투영 행렬 P를 사용 할수 있을 것이다. 이렇게 원근 투영 행렬 P로 변환되는 좌표계를 클립 좌표라고 부르며 클립좌표의 점은 다음과 같이 계산된다.
두번째로는 클립좌표의 점 P의 각요소를 p의 세번쨰 값인 -v(z)값으로 나눈다. 그렇다면 우리가 원하는 NDC 의 좌표를 얻을 수 있다.
깊이(depth) vs 거리(distance)
•
요약
◦
카메라와 vertex(x, y, z)의 거리와 z(depth)는 다르다.
◦
뒤에서 깊이 버퍼에 저장된 z로부터 거리를 계산할 수 있다.
•
원근 투영에서는 아래 그림에서 한 점 (x, y, z)가 Near clipping plane 위의 (x', y', z')으로 투영됩니다. (이 그림에는 y'만 표시되어 있고 x', z'은 없네요.)
•
삼차원 공간상의 (x, y, z)에 있는 물체에 의해서 (x', y', z') 위치에 있는 픽셀의 색이 결정되는 것입니다.
•
이때 시점(camera)과 점 (x, y, z)의 거리(distance)를 뒤에서 여러 가지 효과를 구현할 때 사용할 수 있습니다.
•
그러나 그 거리가 픽셀의 깊이(depth)와는 다릅니다.
•
(x', y, z') 위치에 있는 픽셀의 깊이 값은 z - near입니다.
•
카메라와 (x, y, z)의 거리는 카메라의 좌표가 (0, 0, 0)이라면 sqrt(x*x + y*y + z*z) 입니다.
•
깊이로부터 거리를 계산할 수 있습니다. 카메라로부터 픽셀 (x', y', z')로 향하는 방향을 계산할 수 있기 때문입니다. 뒤에서 투영 행렬의 역행렬을 사용하게 됩니다.
깊이값(행렬 유도)
3차원 공간에서 다수의 물체를 그릴 때 그리는 순서를 고려하지 않고 그리면 어떤물체가 앞에있는지 뒤에있는지 눈으로 확인하기가 어렵다.
이 문제를 해결하는 방법은, 물체가 카메라로부터 얼마나 떨어져 있는지에 대한 정보를 기록해 앞에 있는 물체를 나중에 그리도록 설정하는 것이다.
NDC공간을 3차원으로 확장해보자.
우리가 카메라에 부여한 시야각은 깊이와 무관하기 때문에 카메라에 추가 속성을 부여해야 한다. 이를 각각 근평면(near plane) 과 원평면 (far plane)이라고 한다.
카메라 시야를 구성하는 사영공간을 근평면과 원평면으로 잘라주면 사각뿔에서 뾰족한 부분이 잘린 형태가 나오는데 이를 절두체라고 한다.
NDC깊이값은 멀어질수록 증가하기 때문에 3차원 NDC공간은 왼손좌표계를 형성한다.
directX에서는 0 ~ 1을 사용한다.
기존 원근 투영 행렬은 다른 행렬과 의 곱을 위해 3x3 크기의 행렬을 인위적으로 4x4 크기로 늘렸는데, 이제는 깊이 값을 계산할 수 있도록 모든 행렬을 알차게 활용할수 있게 되었다.
기존행렬에서 쓸모 없던 한 행을 사용해 깊이 값 까지 구할 수 있는 최종 행렬을 구해보자
깊이값을 사용하는 3행은 네 개의 미지수 I,j,k,l로 지정 했다. 깊이 값은 뷰 좌표계의 x축과 y축에 각각 직교 하므로 영향을 받지 않는다. 그러므로 앞의 두 요소 i와 j값은 0으로 설정해야 한다.
두개의 미지수 k,l 이 남는데, 이를 구하기 위해서는 두개의 샘플 데이터가 필요하다.
두 미지수를 얻기 위해 카메라에 설정된 근평만과 원평면의 값을 사용해보자. 카메로부터 근평면까지의 거리를 n, 원평면까지의 거리를 f로 설정하자.
카메라 좌표 중심으로부터 시선방향으로 n만큼 이동한 근평면 상에 위치한 점을 생각해보자. 뷰 공간에서 카메라 시선방향은 -z축이므로 뷰 공간에서 이 점의 좌표는 (0, 0, -n, 1)이 된다. 이는 깊이값의 시작 지점이기 때문에 NDC좌표 (0, 0, -1)에 대응한다. 동일하게 뷰 공간의 원평명산의 좌표도 대응한다.
원근 투영 행렬에 뷰 공간의 점을 곱한 결과는 클립좌표가 된다. 근 평면의 점을 p1, 원평면의 점을 p2라고 할떄 이들은 다음과 같이 계산된다.
아직 원근 투영 행렬 k와 l을 모르기 떄문에 클립좌표의 세번째 요소를 계산 할수 없다. 하지만 클립좌표의 네번째 요소로 모든 요소를 나눈 값은 NDC좌표를 가리키고, NDC좌표의 z값은 각각 -1, 1임을 알고있다.
따라서 근평면의 클립좌표는 (0, 0, -n, n)이 되고, 원평면의 클립좌표는 (0, 0, f, f)이 되어야 한다.
따라서 다음 식을 얻을 수 있다.
이로 부터 다음과 같은 연립방정식을 구할 수 있다.
•
kn + l = -n
•
kf + l = f
각각을 풀면 다음의 결과를 얻을 수 있다.
이를 적용하면 다음과같은 최종 투영 행렬을 구할수 있다.