DirectX11

[DirectX11] 1인칭 카메라 조작

mstone8370 2023. 12. 18. 16:28

 

다이렉트x에서 Direct Input으로 1인칭 카메라 회전과 이동이 가능하게 구현했다.

참고한 곳은 https://www.rastertek.com/dx11win10tut16.html

 

Tutorial 16: Direct Input

Tutorial 16: Direct Input This tutorial will cover using Direct Input in DirectX 11. The code in this tutorial will be based on the previous tutorial code. Direct Input is the high-speed method of interfacing with input devices that the DirectX API provide

www.rastertek.com

위 글은 화면에서의 마우스 좌표값을 구하는 예제라서 1인칭 카메라 조작과는 다른 방법을 사용하지만, 중요한 부분은 입력을 받는 클래스를 생성하고 초기화하는 부분이라서 그 외의 것은 원하는 방식대로 구현하면 된다.

1인칭 카메라는 회전할 때 마우스 좌표의 델타값을 구한 다음에 마우스 커서를 화면의 중앙으로 이동 시켜주면 된다.

 

bool InputManager::ReadMouse()
{
    HRESULT result;

    // Read the mouse device.
    result = m_mouse->GetDeviceState(sizeof(DIMOUSESTATE), (LPVOID)&m_mouseState);
    if (FAILED(result))
    {
        // If the mouse lost focus or was not acquired then try to get control back.
        if ((result == DIERR_INPUTLOST) || (result == DIERR_NOTACQUIRED))
        {
            m_mouse->Acquire();
        }
        else
        {
            return false;
        }
    }
    
    // 커서를 화면 중앙으로 이동.
    SetCursorPos(window_center_x, window_center_y);

    return true;
}

GetDeviceState함수를 통해 마우스의 입력에 대한 정보를 m_mouseState 변수에 받아왔으니 커서를 화면 중앙으로 이동시킨다.

그리고 이 다음에는 화면 렌더링을 진행하고 그동안 변경된 마우스 상태는 다음 틱에 호출될 위 함수에서 적용된다.

 

그리고 마우스 커서는 화면 초기화 할 때 ShowCursor(false); 으로 숨기면 된다.

 

이동의 경우에는 키보드 입력에 따라 유닛 벡터를 더해 이동 방향을 정하고 노멀라이즈 한 다음에 적용한다.

이렇게 해야 대각선으로 이동할때 속도가 빨라지는 것을 방지할 수 있다.

그리고 속도는 DeltaTime을 곱해서 적용해야 초당 프레임 수에 따라 이동 속도가 바뀌지 않고 일정하게 유지된다.

 


 

키보드 입력으로 이동 방향을 정했다면 그걸 카메라에 적용해야하는데 이동 방향은 카메라에 상대적이므로 카메라는 자신의 회전 정보와 위치 정보를 알고있어야 한다.

월드 공간에서의 위치와 회전 정보를 가지고있는 카메라 클래스를 만들어서 해당 값을 변경하는 것도 쉽게 만들어줬다.

// Camera.h
#pragma once

#include <directxmath.h>

using namespace DirectX;

class Camera
{
public:
    Camera();
    ~Camera();

    XMFLOAT3 location;
    XMFLOAT3 rotation;
    bool clamp_pitch;
    float max_pitch;
    float min_pitch;
    float field_of_view;
    float near_clip;
    float far_clip;
    float aspect_ratio;

    // orthographic
    float view_height;
    float view_width;

protected:
    // 각도를 -180에서 180 사이로 조정.
    float RotationWrap(float value);
    XMVECTOR RotationToVector(XMFLOAT3 rot) const;
    XMFLOAT3 VectorToRotation(XMFLOAT3 vec) const;

    XMFLOAT3 up;

public:
    XMMATRIX GetViewMatrix() const;
    inline XMFLOAT3 GetLocation() const { return location; }
    inline XMFLOAT3 GetRotation() const { return rotation; }
    XMVECTOR GetForwardVector() const;
    XMVECTOR GetRightVector() const;
    XMMATRIX GetProjectionMatrix() const;

    void SetLocation(XMFLOAT3 new_location);
    void SetLocation(float x, float y, float z);
    void SetRotation(XMFLOAT3 new_rotation);
    void SetRotation(float pitch, float yaw, float roll);
    void SetLookAtLocation(XMFLOAT3 target);
    void SetLookAtLocation(float x, float y, float z);

    void AddPitch(float value);
    void AddYaw(float value);
};
// Camera.cpp
#include "Camera.h"

#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))

Camera::Camera()
    : clamp_pitch(true)
    , max_pitch(89.9f)
    , min_pitch(-89.9f)
    , field_of_view(70.f)
    , far_clip(1000.f)
    , near_clip(0.1f)
    , view_height(9)
    , view_width(16)
    , aspect_ratio(16/9)
{
    location = XMFLOAT3(0.f, 0.f, 0.f);
    rotation = XMFLOAT3(0.f, 0.f, 0.f);
    up = XMFLOAT3(0.f, 1.f, 0.f);
}

Camera::~Camera()
{
}

// 각도를 -180에서 180 사이로 조정.
float Camera::RotationWrap(float value)
{
    value = fmod(value, 360.0);

    if (value < -180.0)
        value += 360.0;
    else if (value > 180.0)
        value -= 360.0;
    
    return value;
}

XMVECTOR Camera::RotationToVector(XMFLOAT3 rot) const
{
    // rotation값으로 회전 행렬을 만들고 기본 forward 벡터와 곱해서 회전된 벡터를 계산.
    float pitch = XMConvertToRadians(rot.x);
    float yaw = XMConvertToRadians(rot.y);
    float roll = XMConvertToRadians(rot.z);

    XMMATRIX rotationMat = XMMatrixRotationRollPitchYaw(pitch, yaw, roll);

    XMVECTOR direction = XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f); // +Z forward
    direction = XMVector3Transform(direction, rotationMat);
    return direction;
}

XMFLOAT3 Camera::VectorToRotation(XMFLOAT3 vec) const
{
    float pitch = -asinf(vec.y);
    float yaw = atan2f(vec.x, vec.z);

    return XMFLOAT3(XMConvertToDegrees(pitch), XMConvertToDegrees(yaw), 0.0f);
}

XMMATRIX Camera::GetViewMatrix() const
{
    XMVECTOR Eye = XMLoadFloat3(&location);
    XMVECTOR location_vec = XMLoadFloat3(&location);
    XMVECTOR At = GetForwardVector() + location_vec;
    XMVECTOR Up = XMLoadFloat3(&up);
    return XMMatrixLookAtLH(Eye, At, Up);
}

XMVECTOR Camera::GetForwardVector() const
{
    return RotationToVector(rotation);
}

XMVECTOR Camera::GetRightVector() const
{
    float pitch = XMConvertToRadians(rotation.x);
    float yaw = XMConvertToRadians(rotation.y);
    float roll = XMConvertToRadians(rotation.z);

    XMMATRIX rotationMat = XMMatrixRotationRollPitchYaw(pitch, yaw, roll);

    XMVECTOR direction = XMVectorSet(1.0f, 0.0f, 0.0f, 0.0f); // +X right
    direction = XMVector3Transform(direction, rotationMat);
    return direction;
}

XMMATRIX Camera::GetProjectionMatrix() const
{
    float FovDegreeToRadian = XMConvertToRadians(field_of_view / aspect_ratio);
    return XMMatrixTranspose(XMMatrixPerspectiveFovLH(FovDegreeToRadian, aspect_ratio, near_clip, far_clip));
}

void Camera::SetLocation(XMFLOAT3 new_location)
{
    location = new_location;
}

void Camera::SetLocation(float x, float y, float z)
{
    SetLocation(XMFLOAT3(x, y, z));
}

void Camera::SetRotation(XMFLOAT3 new_rotation)
{
    rotation = new_rotation;
    if (clamp_pitch)
    {
        rotation.x = MIN(rotation.x, max_pitch);
        rotation.x = MAX(rotation.x, min_pitch);
    }

    rotation.x = RotationWrap(rotation.x);
    rotation.y = RotationWrap(rotation.y);
    rotation.z = RotationWrap(rotation.z);
}

void Camera::SetRotation(float pitch, float yaw, float roll)
{
    SetRotation(XMFLOAT3(pitch, yaw, roll));
}

void Camera::SetLookAtLocation(XMFLOAT3 target)
{
    XMVECTOR look_vector = XMVectorSet(
        target.x - location.x,
        target.y - location.y,
        target.z - location.z,
        0.f
    );
    look_vector = XMVector3Normalize(look_vector);
    XMFLOAT3 vec;
    XMStoreFloat3(&vec, look_vector);
    SetRotation(VectorToRotation(vec));
}

void Camera::SetLookAtLocation(float x, float y, float z)
{
    SetLookAtLocation(XMFLOAT3(x, y, z));
}

void Camera::AddPitch(float value)
{
    float new_pitch_value = RotationWrap(rotation.x + value);
    SetRotation(new_pitch_value, rotation.y, rotation.z);
}

void Camera::AddYaw(float value)
{
    float new_yaw_value = RotationWrap(rotation.y + value);
    SetRotation(rotation.x, new_yaw_value, rotation.z);
}

 

Forward 벡터와 Right 벡터를 통해 이동해야할 방향을 계산할 수 있고, 회전 입력은 AddPitch와 AddYaw 함수에 마우스의 델타값을 넣어주면 된다.

카메라의 Roll 회전은 필요 없을듯 해서 제대로 다루지 않았다.

그리고 Pitch값을 제한해서 위 아래로 과하게 도는 것을 방지했다.

 

 

 


 

 

 

적용 결과

 

카메라는 이동 속도 변경, FOV값 변경, 상하 이동 입력을 받게 했다.

조명도 토글 입력으로 회전하거나 멈출 수 있게 해봤다.