본문 바로가기

Library/Computer Graphics

시점 좌표계를 통한 자유로운 카메라 구현

모델뷰 변환(modelview transformation)은 사실 같은 변환이라고 할 수 있다. 그러나, 임의의 공간에서 물체에 변환을 가하는 모델 변환과, 이를 관찰하는 카메라의 입장에서의 뷰 변환을 제대로 이해하고 있지 않다면, 자유롭게 시점 변환을 할 수 있는 카메라를 구현하는데 어려움을 겪을 것이다. OpenGL이나 DirectX에서 제공하고 있는 gluLookAt, D3DXMatrixLookAtLH와 같은 함수는 간단하게 카메라의 위치, 바라보는 방향, 그리고 업벡터만을 지정하는 것으로 뷰행렬을 계산해주지만, 보통 이것보다 더 다양한 시점이 필요하다. 이런 시점을 지원하는 카메라를 구현하기 위해서는, 시점 좌표계에 대한 이해가 필요하다.

현재 임의의 공간의 물체들은 모델 변환이 적용되어 있는 상태라고 하자. 이 물체들은 카메라 필름과 같은 뷰평면을 통해 보여지기 때문에, 뷰행렬을 적용하여 물체들을 시점에 맞게 다시 변환해야 한다. 물체를 그대로 두고 관찰자가 직접 이동하여 물체를 관찰할 수 없기 때문에, 공간에 위치한 물체들을 변환해야 한다. 특히, gluLookAt, D3DXMatrixLookAtLH와 같은 함수들은 제한적인 시점만 지원하기 때문에, 보다 자유로운 시점을 얻기 위해서는 뷰행렬을 직접 작성해야 한다.

임의의 공간에 물체들이 놓여 있을 때, 이들을 관찰하는 카메라가 있다고 하자. 카메라의 위치와 바라보는 지점이 좌표로 주어졌을 때, 관찰하는 영상이 맺히는 것은 결국 평면이므로, 이 관찰 평면에 수직한 벡터를 정의할 수 있다. 예를 들어, 오른손 좌표계에서 카메라가 원점을 바라보고 있고, 카메라가 (0, 0, 5) 위치에 있다면 뷰평면은 z = 5가 되고, 이 평면에 수직한 벡터는 (0, 0, 5)가 된다.

여기서의 작업은, 간단히 말해 시점 좌표계를 월드 좌표계로 옮기는 작업을 하기 위해, 시점 좌표계를 정의하는 것이다. 카메라는 회전한 상태일 수도 있도, 축과 나란하지 않은 경우도 있을 수 있기 때문에 일반적인 시점 좌표계의 기저 벡터(basis vector)를 얻어야 하며, 뷰평면과 수직인 벡터를 얻은 것은 첫 번째 작업이다. 그리고, 이렇게 얻어진 벡터는 정규화해야 한다.

뷰평면과 수직이면서, 정규화된 벡터를 얻었다면, 직각 좌표계로 +y 방향을 설정해 주어야 한다. 시점 좌표계에서의 +z 방향으로의 기저 벡터는 이미 구했기 때문에, +y 방향의 기저 벡터를 지정해주면 나머지 +x 방향의 기저 벡터를 벡터의 cross product로 구할 수 있게 된다. 이렇게 +y 방향을 지정하는 벡터를 업벡터(up vector)라고 한다. +y와 +z가 결정되면, 오른손 좌표계 기준으로 cross product를 해주면 +x 방향이 결정된다. 따라서, 카메라가 바라보는 방향과 카메라의 위치가 주어졌을 때 카메라의 시점 좌표계를 정의할 수 있게 된다. 이와 같은 방법으로 정의된 시점 좌표계를 uvn 좌표 기준 프레임(uvn viewing coordinate reference frame)이라 한다.

이제, 시점 좌표계가 정의되었으면 두 좌표계 사이의 상관 관계를 알 수 있다. 이제 다시 그래픽 시스템 구현자 입장으로 돌아가보면, 카메라에 비치는 영상에 맞춰 정규화된 볼륨을 생성하기 때문에, 시점에 맞게 물체들을 이동시키면 된다. 즉, (0, 0, 5) 위치에 카메라가 위치하고 있다면, 이것은 원점에 위치한 카메라가 -z 방향으로 5만큼 이동된 물체를 관찰하는 것과 동일하다(이런 이유로 모델 변환과 뷰 변환을 동일하게 간주하는 것이다)

즉, 카메라를 원점으로 이동하는 변환(T), 카메라의 시점 좌표계를 월드 좌표계와 일치시키는 변환(R)을 물체에 적용하면 결과적으로 시점 좌표에 맞게 물체들이 변환되고, 원했던 것은 이것이다.

T 변환을 동차 좌표계(homogenous coordinate)를 사용하여 4 × 4 행렬로 표현하면,

1      0      0      0
0      1      0      0
0      0      1      0
-px  -py  -pz   1

이다. px, py, pz는 카메라가 원점에서 얼마나 떨어져 있는가를 나타내는데, 시점 좌표계를 원점으로 가져가기 위한 스칼라 값이며, 유클리드 내적으로 구할 수 있다. 카메라의 시점 좌표계(V)를 월드 좌표계와 일치시키는 변환은

V · R = I

이다. I는 4 × 4 단위행렬인데, 월드 좌표계의 기저 벡터를 나타낸다. 구하고자 하는 것은 R이며, 위에서 뷰평면에 수직한 벡터에서부터 구한 시점 좌표계를 표현한 행렬과 R을 곱하면 단위 행렬이 되므로, R은 직교 행렬(orthogonal matrix)이라 할 수 있다. 직교 행렬이란 자신의 전치 행렬(transpose matrix, 자신의 행과 열을 바꾼 행렬)과 곱했을 때 단위 행렬이 나오는 행렬을 말하는 것이다. V는 이미 알려져 있기 때문에 R은 V의 전치 행렬이다. 이제, R · T의 모든 성분이 알게 되었고, 이것이 구하고자 하는 뷰행렬이 된다. 공간 상의 물체에 이 뷰행렬을 적용하면, 카메라의 시점에 맞게 물체의 변환 결과를 얻게 된다.


여담으로, DX를 사용하여 시점을 자유롭게 조작할 수 있는 카메라를 구현한다면, pitch, yaw, roll과 같은 동작을 구현할 때 D3DXVec3TransformCoord이란 함수가 무엇을 하는지 궁금할 것이다. 사실, 행동 자체는 특정 D3DXVECTOR3 벡터를 주어진 행렬과 곱한 뒤, 그 결과를 다시 D3DXVECTOR3에 저장하는 것이다. 문제는 MSDN 설명인데, w = 1인 D3DXVECTOR3으로 투영(projection)한다는 설명이 쉽게 이해되지 않을 것이다. 그러나, 이것은 벡터 공간을 이해하고 있다면 어려운 문제가 아니다. D3DXMATRIX는 4 × 4 행렬이며, D3DXVECTOR3 벡터를 이 행렬과 직접 연산할 수 없다. 따라서, D3DXVECTOR3의 벡터 공간을 D3DXVECTOR4 벡터 공간으로 옮겨야 하는데, 투영은 단순히 이것을 의미하는 것이다. 그리고 D3DXVec3TransformCoord의 결과는 D3DXVECTOR3 벡터 공간인데, 결과적으로 w = 1인 D3DXVECTOR4 벡터 공간을 다시 D3DXVECTOR3으로 투영한다는 의미이다.