이번에는 Human Model을 다양한 각도에서 보고 판단할 수 있도록 카메라 제어 기능을 추가했다. 마우스랑 키보드를 사용해서 모델을 자유롭게 움직이면서 볼 수 있게 만들 었고, OpenGL 강좌에서 배운 내용을 바탕으로 아래 기능들을 추가했다.
이 기능들을 위해 기존의 context 클래스에서 카메라 관련 부분을 분리해서 Camera라는 클래스를 새로 만들었다. 이 클래스는 카메라의 위치와 회전 상태를 관리하면서 움직임이나 회전을 처리하는 데 필요한 함수들을 포함하고 있다.
#ifndef CAMERA_H
# define CAMERA_H
# include "humanGL.h"
class Camera {
public:
Camera() = default;
void move(bool isPressW, bool isPressS, bool isPressD,
bool isPressA, bool isPressE, bool isPressQ);
void rotate(glmath::vec2& pos);
void saveCurrentPos(float x, float y);
glmath::mat4 getViewMatrix();
private:
float m_cameraPitch { 0.0f };
float m_cameraYaw { 0.0f };
glmath::vec2 m_prevMousePos { glmath::vec2(0.0f)};
glmath::vec3 m_cameraPos {0.0f, 0.0f, 20.0f};
glmath::vec3 m_cameraFront {0.0f, 0.0f, -1.0f};
glmath::vec3 m_cameraUp {0.0f, 1.0f, 0.0f};
const float m_cameraSpeed { 0.04f };
const float m_cameraRotSpeed { 0.2f };
};
#endif
1. saveCurrentPos() 함수
void Camera::saveCurrentPos(float x, float y) {
m_prevMousePos = glmath::vec2((float)x, (float)y);
}
이 함수는 현재 마우스 위치를 저장하는 역할을 한다. Context::mouseButton() 함수에서 마우스 우클릭이 감지되면 호출되고, 마우스의 좌표가 m_prevMousePos에 저장된다.
void Context::mouseButton(int button, int action, double x, double y) {
if (button == GLFW_MOUSE_BUTTON_RIGHT)
{
if (action == GLFW_PRESS)
{
m_camera.saveCurrentPos((float)x, (float)y);
m_cameraControl = true;
}
else if (action == GLFW_RELEASE)
m_cameraControl = false;
}
}
mouseButton() 함수에서는 마우스 우클릭 press 시 카메라 제어 모드가 활성화되고, release되면 비활성화된다.
2. rotate() 함수
void Camera::rotate(glmath::vec2& pos) {
glmath::vec2 deltaPos = pos - m_prevMousePos;
m_cameraYaw -= deltaPos.x * m_cameraRotSpeed;
m_cameraPitch -= deltaPos.y * m_cameraRotSpeed;
if (m_cameraYaw < 0.0f) { m_cameraYaw += 360.0f; }
if (m_cameraYaw > 360.0f) { m_cameraYaw -= 360.0f; }
if (m_cameraPitch > 89.0f) { m_cameraPitch = 89.0f; }
if (m_cameraPitch < -89.0f) { m_cameraPitch = -89.0f; }
m_prevMousePos = pos;
}
Yaw와 Pitch를 마우스 위치 차이(deltaPos)를 이용해 계산하는 함수이다. m_cameraYaw는 Y축 기준 회전(좌우 회전), m_cameraPitch는 X축 기준 회전(상하 회전)을 나타내고, 이 함수는 Context::mouseMove() 함수에 의해 호출 된다.
void Context::mouseMove(double x, double y) {
if (!m_cameraControl) { return ; }
glmath::vec2 pos ((float)x, (float)y);
m_camera.rotate(pos);
}
마우스가 움직이는 이벤트에 의해 호출되고 카메라 제어 모드인 경우 현재 마우스의 위치를 가지고 rotate() 함수를 호출한다.
3. move() 함수
void Camera::move(bool isPressW, bool isPressS, bool isPressD,
bool isPressA, bool isPressE, bool isPressQ) {
glmath::vec3 cameraRight = glmath::normalize(glmath::cross(m_cameraFront, m_cameraUp));
glmath::vec3 cameraUp = glmath::normalize(glmath::cross(cameraRight, m_cameraFront));
if (isPressW) {
m_cameraPos = m_cameraPos + m_cameraSpeed * m_cameraFront;
}
if (isPressS) {
m_cameraPos = m_cameraPos - m_cameraSpeed * m_cameraFront;
}
if (isPressD) {
m_cameraPos = m_cameraPos + m_cameraSpeed * cameraRight;
}
if (isPressA) {
m_cameraPos = m_cameraPos - m_cameraSpeed * cameraRight;
}
if (isPressE) {
m_cameraPos = m_cameraPos + m_cameraSpeed * cameraUp;
}
if (isPressQ) {
m_cameraPos = m_cameraPos - m_cameraSpeed * cameraUp;
}
}
이 함수는 카메라를 W, S, A, D, Q, E 키 입력에 맞게 3축 방향으로 이동시키는 함수이다. 카메라의 정면 벡터(m_cameraFront), 우측 벡터(cameraRight), 수직 벡터(cameraUp)를 이용해서 움직임을 처리한다.
카메라의 3개의 축을 찾는 과정
1. 카메라 정면 벡터 (m_cameraFront)
m_cameraFront 벡터를 그대로 사용한다. (getViewMatrix() 함수에서 Yaw, Pitch를 통해 계속 계산해 놓음)
2. 카메라 우측 벡터 (cameraRight)
m_cameraFront 벡터와 카메라 기준 천장 방향인 m_cameraUp (0, 1, 0) 벡터를 외적후 정규화 하여 구한다.
3. 카메라 수직 벡터 (cameraUp)
cameraRight 벡터와 m_cameraFront 벡터를 외적하여 정규화하면 cameraUp 벡터가 나온다.
W, S, A, D, Q, E 키중 눌린 것에 따라 구해놓은 3축을 기준으로 m_cameraSpeed 만큼 m_cameraPos를 이동시킨다.
이 함수는 Context::processInput() 함수에 의해 호출된다.
void Context::processInput(GLFWwindow *window) {
if (!m_cameraControl) { return ; }
bool isPressW = glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS;
bool isPressS = glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS;
bool isPressD = glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS;
bool isPressA = glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS;
bool isPressE = glfwGetKey(window, GLFW_KEY_E) == GLFW_PRESS;
bool isPressQ = glfwGetKey(window, GLFW_KEY_Q) == GLFW_PRESS;
m_camera.move(isPressW, isPressS, isPressD, isPressA, isPressE, isPressQ);
}
processInput() 함수는 프레임마다 현재 어떤 키가 눌렸는지 확인하고, 그에 맞춰 카메라를 이동시킨다.
4. getViewMatrix() 함수
glmath::mat4 Camera::getViewMatrix() {
glmath::vec3 eulerAngle(m_cameraPitch, m_cameraYaw, 0);
glmath::vec4 cameraFront = glmath::mat4_cast(glmath::quat(eulerAngle)) * glmath::vec4(0.0f, 0.0f, -1.0f, 0.0f);
m_cameraFront = glmath::vec3(cameraFront.x, cameraFront.y, cameraFront.z);
return glmath::lookAt(m_cameraPos, m_cameraPos + m_cameraFront, m_cameraUp);
}
이 함수는 Yaw와 Pitch로 카메라의 방향을 계산한 다음, lookAt() 함수를 이용해 카메라의 뷰 매트릭스를 반환한다. 오일러 회전 대신 쿼터니언을 사용해서 짐벌락 문제를 해결했다.
짐벌락은 오일러 각을 사용할 때 발생하는 문제로, 회전 축이 겹쳐서 회전 자유도가 제한되는 현상이다. 이를 해결하기 위해 쿼터니언을 사용했는데, 쿼터니언은 3D 회전을 4차원 벡터로 표현해서, 회전 순서에 상관없이 자유롭게 회전할 수 있고, 짐벌락 문제가 발생하지 않는다.
쿼터니언을 사용하여 회전 변환을 구하기 위해 glmath 라이브러리에 quat 클래스와 쿼터니언을 회전 변환으로 만들어주는 mat4_cast 함수를 구현하였다.
위 함수들을 통해 카메라 제어를 구현하였고 다음과 같은 결과를 얻을 수 있었다.
다음에는 Human model의 움직임을 구현해 볼 것이다.
HumanGL: Walk, Rotate, Stop 애니메이션 구현 - (7) (1) | 2024.10.19 |
---|---|
HumanGL: Human Model에 애니메이션을 적용하기 위한 준비 - (6) (2) | 2024.10.19 |
HumanGL: 계층적 모델링과 변환 행렬 스택 구현 - (4) (0) | 2024.10.14 |
HumanGL: 계층적 모델링과 변환 행렬 스택 - (3) (1) | 2024.10.09 |
HumanGL: 프로젝트 진행 계획 - (2) (1) | 2024.10.09 |