카메라에 대한 포스팅을 적기 전, 이 두가지 행렬에 대한 글을 적고자 한다.
ModelView Matrix는 물체의 버텍스의 좌표를 Object Space에서 View Space로 바꿔준다.
이는 물체 자체를 중심으로 하는 좌표계에서, 카메라를 원점으로하는 좌표계로 바꿔준다는 의미이다.
앞선 포스팅에서 T * R * S 로 Model Matrix를 만들어, 이를 통해 물체를 움직인 바가 있었다.
이는 그림으로 나타내면 다음과 같다.
Model Matrix를 곱함으로써, 오브젝트를 World Space 내 임의의 위치에 배치할 수 있다.
하지만 실제 대부분의 그래픽스 프로그램에서는, 카메라가 존재한다.
물체의 위치는 카메라에서 본 위치로 변환되어야 한다. 이를 그림으로 나타내면 다음과 같다.
생각해보면, 카메라도 원점에 있던 것이 Translate, Rotation을 통해 저 위치에 배치된 것이다.
(카메라의 Scale은 의미가 없으므로 생략)
그러므로 카메라를 움직인 행렬도 T * R로 표현될 수 있다.
그리고 그 행렬을 View Matrix라고 칭할 것이다.
이제 잘 생각해보면,
(World Space 기준 좌표) = Model Matrix * (Object Space 기준 좌표)
(World Space 기준 좌표) = View Matrix * (Camera Space 기준 좌표)
좌항이 같으므로 식을 정리해보자면,
Model Matrix * (Object Space 기준 좌표) = View Matrix * (Camera Space 기준 좌표)
이제 양변에 View Matrix의 역행렬을 곱해서 최종적으로 Camera Space 기준 좌표를 얻어낼 수 있다.
(Camera Space 기준 좌표) = (View Matrix)^-1 * Model Matrix * (Object Space 기준 좌표)
(View Matrix)^-1 * Model Matrix를 바로 ModelView Matrix라고 부르며,
View Matrix를 V로, Model Matrix를 M으로 하여 수식으로 예쁘게 나타내면 다음과 같다.
M은 이미 구해놓았으므로, V^-1을 구하면 된다.
결론부터 말하자면, V = T * R이므로 V^-1은 행렬의 곱의 역행렬 공식(?)에 따라 다음과 같다.
역행렬의 기하학적 의미를 생각해보면, 원본 행렬이 하는 일을 반대로 하는 행렬을 의미한다.
따라서 T^-1은 T의 반대 역할, 즉 카메라가 원점에서 움직여온 translate의 반대 방향 translate를 의미한다.
예를들어, T가 (1, -2, 3)만큼 이동시키는 행렬이었다면, T^-1은 (-1, 2, -3)만큼 이동시키는 행렬이 된다.
그리고 역행렬의 수학적 의미를 살펴보면, 역행렬과 원본 행렬을 곱하면 Identity Matrix가 된다.
R^-1은 R과 곱했을 시 Identity Matrix를 만드는 행렬을 의미하는데,
R의 각 열은 각각 x축 단위, y축 단위, z축 단위, 원점의 위치를 나타낸다고 했었다. (상단 Model Matrix 글 참조)
그렇다면 R^-1의 각 행은 x축 단위, y축 단위, z축 단위, 원점의 위치를 나타내도록 되어야 한다.
왜냐하면 행렬의 곱셈 방식을 살펴보면, R * R^-1을 했을 때 R의 각 열이 R^-1의 각 행과 곱해지게 된다.
R^-1이 그렇게 되어야 R * R^-1을 했을 때 행렬의 곱셈 법칙에 의해 x축끼리, y축끼리, z축끼리 곱해져서 Identity Matrix가 만들어진다.
x축, y축, z축은 서로 직교하므로, 만약 서로 다른 축이 곱해진다면 벡터의 내적 법칙에 의해 그 값은 1이 아닌 0이 되어, Identity Matrix가 만들어질 수 없기 때문이다.
그렇다면 그 R^-1을 만드는 방법은 간단하다. R을 전치(transpose)시키면 되는 것이다.
이렇게 R^-1과 T^-1을 각각 구하여, V^-1을 구할 수 있게 되었다.
-------------------------------------------------------------------------------------------------------------------------
사실 V^-1을 구하는 건 이미 OpenGL 함수로 만들어져있다.
굳이 저런 계산을 안해도 된다.
glm::lookAt(position, target, up) 을 사용하면 된다. 이 함수가 V^-1을 바로 뱉어준다.
position은 카메라의 위치, target은 카메라가 보는 포인트, up은 월드의 위 방향(0, 1, 0)을 넘겨주면 된다.
그런데 매개변수를 저렇게 받는 이유가 무엇일까?
원래 회전은 Pitch, Yaw, Roll 3가지로 나타내질 수 있다. 그런데, 이 3가지가 혼합된다면 순서에 따라 결과가 달라진다.
사람이 고개를 끄덕하고 갸웃하는 것, 갸웃하고 끄덕하는 것의 결과가 다르다는 것을 생각해보면 쉽게 알 수 있다.
그래서 순서에 구애받지 않게 저렇게 매개변수를 받는 것이다.
뜬금없이 up을 넘겨주는 이유는 다음과 같다.
일단 View Matrix를 구한다는 것은, 카메라의 좌표계를 구하는 것과 같다.
position과 target을 통해, 카메라의 front를 얻어낼 수 있다.
OpenGL에서, 카메라의 front는 z축의 반대 방향과 같다.
이는 OpenGL이 오른손 법칙을 따르기 때문이다.
눈 앞에 x축, y축으로 이루어진 2차원 좌표계가 그려진다고 상상해보자.
x축, y축 순으로 오른손의 네 손가락으로 감아쥐었을 때, 엄지가 향하는 방향이 z축의 방향이다.
참고로 DirectX는 반대로 왼손법칙을 따른다.
우리는 -z를 얻어냈지만, x축과 y축은 아직 모른다.
그래서 일단 월드의 위 방향에 해당하는, (0, 1, 0)을 대충 up으로 넘겨주었다. (어설픈 y축)
이 up은 카메라의 y축에 비해 조금 뒤틀려있을 것이다. 카메라가 기울어져있다면 up은 똑바른 위 방향이 아닐 것이기 때문.
그렇다면 우리는 어설픈 y축과, 올바른 z축을 가지고 있다.
이제 벡터의 외적을 활용할 시간이다.
핵심은, 두 벡터의 외적은 두 벡터를 포함하는 평면의 법선 벡터를 구한다는 것이다.
따라서 (어설픈 y축) X (올바른 z축)을 한다면? 올바른 x축을 얻어낼 수 있다.
그리고 다시 (올바른 z축) X (올바른 x축)을 한다면? 올바른 y축을 얻을 수 있다!
이런 과정을 통해 glm::lookAt 함수는 position, front, up만으로 View Matrix를 만들 수 있는 것이다.
결론적으로, glm::lookAt의 리턴값 (View Matrix)^-1을 이전에 구한 Model Matrix와 곱하면?
ModelView Matrix가 완성된다.
코드로 나타내면 다음과 같다.
// Camera 클래스 멤버 함수.
glm::mat4 Camera::calculateViewMatrix()
{
return glm::lookAt(position, position+front, worldUp);
}
//------------------------------------------------------------------
// Model Matrix
glm::mat4 identityMatrix(1.f);
glm::mat4 T = glm::translate(identityMatrix, glm::vec3(0.f, 0.f, -1.f));
glm::mat4 R = glm::rotate(identityMatrix, rotationOffset, glm::vec3(0.f, 1.f, 0.f));
glm::mat4 S = glm::scale(identityMatrix, glm::vec3(2.f));
glm::mat4 model = T * R * S;
// View Matrix (역행렬)
glm::mat4 view = camera.calculateViewMatrix();
// ModelView Matrix
glm::mat4 modelView = view * model
다소 복잡한 내용을 다뤘는데, 사실 이런거 몰라도 glm::lookAt에 파라미터만 올바르게 넣어주면 잘 작동한다.
그래도 이러한 이론적 배경을 아는 것이 장기적으로는 확실히 도움이 될 것이라고 생각한다.
'OpenGL > 공부' 카테고리의 다른 글
[OpenGL] 카메라 구현, 키보드/마우스를 통한 조작 (0) | 2024.06.01 |
---|---|
[OpenGL] Projection Matrix (glm::perspective, glm::ortho) (0) | 2024.06.01 |
[OpenGL] Polygon Rasterization (Bilinear Interpolation) (0) | 2024.05.28 |
[OpenGL] Model Matrix (Translate, Rotate, Scale) (0) | 2024.05.28 |
[OpenGL] 쉐이더 사용, 삼각형 그리기 (+ VAO, VBO) (0) | 2024.05.27 |