카메라 클래스?
여기서 카메라는 3D 공간에서 우리의 눈이 되어줄 가상의 오브젝트를 말한다.
월드 공간에 카메라를 임의의 위치에 놓으면 그 카메라 위치에서 보이는 물체들을 화면에 적절하게 나타낼 수 있어야 한다. 예를 들어 물체는 그대로지만 우리가 보는 시점에 따라 그 물체는 다르게 보인다. 카메라가 움직이면 우리가 보는 화면도 자연스럽게 변해야 한다. 이러한 기능을 담당하는 카메라 클래스가 필요하다.
렌더링 순서
월드 공간에 물체들이 있다. 월드 공간의 물체들은 View Transform(뷰 변환)과 Perspective Projection Transform(원근 투영 변환)을 통해 뷰 공간와 투영 공간으로 이동되어야 한다. 뷰 공간은 카메라 공간이라고도 한다. 위에서 보았듯이 카메라가 중심인 공간이다. 이러한 뷰 공간은 카메라의 위치(Eye), 어디를 응시하고 있는지(LookAt), 카메라 기준 Up 방향(Up) 3가지 데이터로 정의된다. 즉 카메라의 위치가 변하거나 focusing하는 점이 달라질 때마다 뷰 행렬도 변경되어야 한다.
카메라 공간으로 이동되었다고 끝이 아니다. 카메라가 볼 수 있는 시야를 제한해야 한다. 카메라의 시야를 의미하는 View frustum을 구성하기 위해 4가지 데이터가 필요하다. 이 4가지 데이터가 있다면 원근 투영 행렬을 만들 수 있다. 원근 투영 행렬은 카메라의 이동과 회전에 따라 변하지 않으므로, 시야 범위를 변경하지 않는 이상 매번 업데이트할 필요는 없다.
카메라가 움직일 때마다 이러한 행렬들을 알아서 계산해준다면, 우리는 다른 것 신경쓰지 않고 그리기 전에 정점 셰이더에 카메라가 가진 두 행렬을 전달해주기만 하면 된다.
카메라 기능
- 이동 및 회전
- 카메라는 우리가 보는 시야를 의미한다. 따라서 상하좌우로 이동할 수 있어야 한다. 회전도 마찬가지다. 회전은 우리가 고개를 돌리는 것에 해당한다. - View Matrix 및 Perspective Projection Matrix 생성
- 카메라가 이동하거나 회전할 때마다 View Matrix를 알아서 갱신해주어야 한다.
간단한 구현 예
// 이동, 회전, 뷰 행렬 업데이트
// 이동
void Camera::Move(float x, float y, float z)
{
// 현재 카메라의 위치로부터 (x,y,z)만큼 카메라를 이동
m_F3Pos.x += x;
m_F3Pos.y += y;
m_F3Pos.z += z;
// 위 점의 벡터 버전도 따로 저장
m_PosVector = XMLoadFloat3(&m_F3Pos);
// 카메라가 이동 및 회전할 때마다 뷰 매트릭스 업데이트
UpdateMatrix();
}
// 회전
void Camera::Rotate(float x, float y, float z)
{
m_F3Rotation.x += x;
m_F3Rotation.y += y;
m_F3Rotation.z += z;
m_RotationVector = XMLoadFloat3(&m_F3Rotation);
UpdateMatrix();
}
// 행렬 업데이트
void UpdateMatrix()
{
// 변경된 회전 각을 기준으로 새로운 회전 행렬을 생성
XMMATRIX matCamRotation = XMMatrixRotationRollPitchYawFromVector(m_RotationVector);
// 뷰 행렬을 생성하기 위해선 Eye, At, Up 3가지 데이터가 필요
// Eye는 카메라의 위치이므로 Move()에서 업데이트되는 데이터를 그대로 사용하면된다.
XMVECTOR At = XMVector3Transform(UnitVector::AT, matCamRotation);
XMVECTOR Up = XMVector3Transform(UnitVector::Up, matCamRotation);
// m_PosVector 위치에서 At 방향을 바라보고, 카메라의 Up벡터가 Up인
// 왼손 좌표계 공간으로 이동시키는 행렬을 생성
m_ViewMatrix = XMMatrixLookToLH(m_PosVector, At, Up);
// 카메라가 회전할 경우 Front, Back, Left, Right 방향도 업데이트 필요
if(카메라가 회전했다면)
{
XMMATRIX matRotation = XMMatrixRotationRollPitchYaw(m_F3Rotation.x, m_F3Rotation.y, 0.f);
m_ForwardVector = XMVector3Transform(UnitVector::FORWARD, matRotation);
m_BackwardVector = XMVector3Transform(UnitVector::BACKWARD, matRotation);
m_LeftVector = XMVector3Transform(UnitVector::LEFT, matRotation);
m_RightVector = XMVector3Transform(UnitVector::RIGHT, matRotation);
}
}
UnitVector는 다음과 같이 static 상수로 정의해 놓은 단위 벡터이다.
m_ForwardVector와 같은 벡터는 카메라를 기준으로 앞뒤양옆의 방향을 나타내는 벡터이다. 이 벡터들이 필요한 이유는 예를 들어 아래 첫 번째 그림처럼 카메라가 정면(front, +z방향)를 바라보고 있다고 생각해보자.
카메라가 오른쪽으로 45도 회전할 경우 카메라가 바라보는 정면은 더 이상 +z방향이 아니다. 따라서 회전할 경우 기존에 front vector를 의미하던 방향 벡터를 현재의 front에 맞게 변경해주어야 한다. 이를 해주지 않으면 카메라가 앞으로 이동할 경우 생각하던 방향이 아닌 +z방향으로 이동할 것이다.
아래는 투영 행렬을 설정하는 코드이다. 카메라의 시야를 정의하는 4가지 데이터를 전달해주면 투영 행렬을 생성해준다.
void Camera::SetProjectionValues(float fovDegreesY, float aspectRatio, float nearZ, float farZ)
{
// 1 radian은 부채꼴에서
// 반지름 r과 호의 길이 l이 같을 때의 각도를 의미
// 이때 각도는 대략 57.2958도
// 따라서 1도는 대략 0.01745328627.. 라디안
// 이를 단순히 각도(degree)에 곱하여 사용하거나
// 각도(degree)에 PI/180을 곱하여 사용 -> 삼각 함수 사용할 때를 생각 PI/3이 60도였던거..
static const float RADIAN_PER_DEGREE = 0.01745328627f;
float fovRadiansY = fovDegreesY * RADIAN_PER_DEGREE;
m_ProjectionMatrix = XMMatrixPerspectiveFovLH(fovRadiansY, aspectRatio, nearZ, farZ);
}
이동 및 회전은 키보드 입력이나 마우스 입력을 받아서 원하는 키를 누르거나 할 때 실행되도록 하면 된다.
나의 경우 이동은 다음과 같이 W,A,S,D로 하였다. 위아래로 이동은 방향키이다. Move는 위에서 살펴본 함수인데, 좌표를 받는 것이 아닌 벡터를 받는 것만 다르다. 'W' 입력 시 앞으로 이동인데, 카메라의 현재 forward 방향 벡터를 얻어오고 그 방향으로 카메라 스피드만큼 이동하도록 했다.
void Engine::UpdateCamera(float deltaTime)
{
Camera* pCamera = m_Graphics.GetCamera();
float fCamSpeed = pCamera->GetSpeed() * deltaTime;
if (m_Keyboard.IsKeyPressed('W'))
{
pCamera->Move(pCamera->GetForwardVector() * fCamSpeed);
}
if (m_Keyboard.IsKeyPressed('A'))
{
pCamera->Move(pCamera->GetLeftVector() * fCamSpeed);
}
if (m_Keyboard.IsKeyPressed('S'))
{
pCamera->Move(pCamera->GetBackwardVector() * fCamSpeed);
}
if (m_Keyboard.IsKeyPressed('D'))
{
pCamera->Move(pCamera->GetRightVector() * fCamSpeed);
}
if (m_Keyboard.IsKeyPressed(VK_UP))
{
pCamera->Move(0.f, fCamSpeed, 0.f);
}
if (m_Keyboard.IsKeyPressed(VK_DOWN))
{
pCamera->Move(0.f, -fCamSpeed, 0.f);
}
}
아래는 카메라 회전과 관련된 코드이다. 마우스 왼쪽을 클릭한 상태에서 움직이면 회전한다. 마우스의 이전 좌표를 저장하여 마우스가 어디 방향으로 움직였는지 파악하여 좌우 회전을 구분하였다.
// 마우스를 클릭한 상태에서 움직이면 회전
if (m_Mouse.IsLeftDown())
{
static float fMouseRotationPerFrame = 0.03f;
MousePoint curPos = mEvent.GetPos();
static MousePoint prevPos = {};
float pitch = 0.f;
float yaw = 0.f;
Camera* pCamera = m_Graphics.GetCamera();
if (curPos.X < prevPos.X) // If mouse moves to left
{
yaw = -fMouseRotationPerFrame;
}
else if (curPos.X > prevPos.X) // If mouse moves to right
{
yaw = fMouseRotationPerFrame;
}
if (curPos.Y > prevPos.Y) // If mouse moves to down
{
pitch = fMouseRotationPerFrame;
}
else if (curPos.Y < prevPos.Y) // If mouse moves to up
{
pitch = -fMouseRotationPerFrame;
}
pCamera->Rotate(pitch, yaw, 0.f);
prevPos = curPos;
}
'게임 공부 > 게임 개발 일지' 카테고리의 다른 글
현재 라이팅 문제점 (1) (0) | 2021.07.17 |
---|---|
비동기 소켓과 멀티 스레딩을 활용한 소켓 함수 사용의 차이 (0) | 2021.05.30 |
assimp를 이용하여 3D 메쉬 가져와서 렌더링 해보기 (0) | 2021.04.08 |
카메라(eye) 이동 및 회전 (0) | 2021.03.24 |
애니메이션 테스트 (0) | 2020.12.21 |
댓글