Dragon Engine:摄像机

摄像机在着色器方面来说就是提供观察矩阵和投影矩阵,它的一些交互可以实时改变这两个矩阵。

因为涉及矩阵操作,我们使用介绍过的GLM库。

首先构建摄像机类:

namespace Dragon
{
    class Camera
    {
    public:
        Camera(glm::vec3 position = glm::vec3(0.0f, 0.0f, 5.0f), glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f), float farPlane = 0.1f, float nearPlane = 100.0f);
        Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float nearPlane, float farPlane);
        ~Camera() = default;

        Camera(Camera& camera) 
        {
            m_Position = camera.m_Position;
            m_Front = camera.m_Front;
            m_Up = camera.m_Up;
            m_Right = camera.m_Right;
            m_WorldUp = camera.m_WorldUp;
            m_Zoom = camera.m_Zoom;
            m_NearPlane = camera.m_NearPlane;
            m_FarPlane = camera.m_FarPlane;
            m_AspectRatio = camera.m_AspectRatio;

        }

        inline glm::mat4 GetViewMatrix() { return glm::lookAt(m_Position, m_Position + m_Front, m_Up); }
        inline glm::mat4 GetProjectionMatrix() {return glm::perspective((float)glm::radians(m_Zoom), m_AspectRatio, m_NearPlane, m_FarPlane);}
        glm::vec3 GetPosition() { return m_Position; }
        void SetPosition(glm::vec3 position) { m_Position = position; }

        glm::vec3 GetFront() { return m_Front; }
        void SetFront(glm::vec3 front) { m_Front = front; }

        glm::vec3 GetUp() { return m_Up; }
        void SetUp(glm::vec3 up) { m_Up = up; }

        glm::vec3 GetRight() { return m_Right; }
        void SetRight(glm::vec3 right) { m_Right = right; }

        glm::vec3 GetWorldUp() { return m_WorldUp; }
        void SetWorldUp(glm::vec3 worldUp) { m_WorldUp = worldUp; }

        float GetZoom() { return m_Zoom; }
        void SetZoom(float zoom) { m_Zoom = zoom; }

        float GetAspectRatio() { return m_AspectRatio; }
        void SetAspectRatio(float aspectRatio) { m_AspectRatio = aspectRatio; }

        float GetNearPlane() { return m_NearPlane; }
        void SetNearPlane(float nearPlane) { m_NearPlane = nearPlane; }

        float GetFarPlane() { return m_FarPlane; }
        void SetFarPlane(float farPlane) { m_FarPlane = farPlane; }
    private:
        //摄像机参数
        glm::vec3 m_Position;
        glm::vec3 m_Front;
        glm::vec3 m_Up;
        glm::vec3 m_Right;
        glm::vec3 m_WorldUp;
        float m_NearPlane;
        float m_FarPlane;
        float m_AspectRatio = 1280.0f/720.f;

        float m_Zoom = 45.0f;
    };

}

重点不在这些get|set方法上。构造函数中,就像我们之前介绍过的,摄像机有一个位置,指向前的矢量,指向上的矢量,指向右的矢量,然后有一个指向世界坐标系上的矢量用于摄像机的矢量计算。GetViewMatrix()函数使用glm::lookAt方法来构建view矩阵,GetProjectionMatrix()函数使用glm::perspective来构建透视投影,也可以使用正交投影,也可以同时使用,搞一个切换参数。

构造函数实现:

namespace Dragon
{
    Camera::Camera(glm::vec3 position, glm::vec3 up, float nearPlane, float farPlane)
        :m_Front(glm::vec3(0.0f, 0.0f, -1.0f))
    {
        m_NearPlane = nearPlane;
        m_FarPlane = farPlane;
        m_Position = position;
        m_WorldUp = up;
        m_Up = m_WorldUp;

    }
    Camera::Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float nearPlane, float farPlane)
        : m_Front(glm::vec3(0.0f, 0.0f, -1.0f))
    {
        m_NearPlane = nearPlane;
        m_FarPlane = farPlane;
        m_Position = glm::vec3(posX, posY, posZ);
        m_WorldUp = glm::vec3(upX, upY, upZ);
        m_Up = m_WorldUp;
    }

}

一个摄像机类完成,接下来构建CameraController类:

namespace Dragon
{
    class CameraController
    {
    public:
        CameraController(float aspectRatio);

        void OnUpdate(Timestep ts);
        void OnEvent(Event& e);

        Camera& GetCamera() { return m_Camera; }
        const Camera& GetCamera()const { return m_Camera; }

        Timestep& GetTimestep(){return m_Timestep;};
        void SetTimestep(Timestep& ts) { m_Timestep = ts; }
    private:
        bool OnMouseScrolled(MouseScrolledEvent& e);
        bool OnWindowResized(WindowResizeEvent& e);
        bool OnMouseMoved(MouseMovedEvent& e);
        bool OnKeyBoard(KeyPressedEvent& e);
        bool OnKeyReleased(KeyReleasedEvent& e);
        bool OnMouseClicked(MouseButtonPressedEvent& e);
        bool OnMouseReleased(MouseButtonReleasedEvent& e);
        void UpdateCameraVectors();
    private:
        Camera m_Camera;
        Camera m_LastCamera;
        glm::vec3 m_CameraPosition = glm::vec3(0.0f,0.0f,5.0f);
        float m_AspectRatio;
        float m_CameraTranslationSpeed = 5.0f;
        float m_ZoomLevel = 45.0f;
        float m_MouseSensitivity = 0.01f;
        float m_Yaw = -90.0f;
        float m_Pitch = 0.0f;
        bool firstMouse = true;
        float lastX , lastY ;
        Timestep m_Timestep;

        bool m_Quit = true;
        bool m_CTRL = false;
        bool m_ALT = false;
        bool m_MouseLeft = false;
        bool m_MouseRight = false;
        bool m_MouseMiddle = false;
    };
}

注意这里我们使用的Timestep类,它的定义如下:

namespace Dragon
{
    class Timestep
    {
    public:
        Timestep(float time = 0.0f)
            :m_Time(time)
        {

        }

        operator float() const { return m_Time; }

        float GetSeconds() const { return m_Time; }
        float GetMilliseconds() const { return m_Time * 1000.0f; }
    private:
        float m_Time;
    };
}

主要是用来获取时间的,将它用于计算间隔时间的话,可以减少帧率变化的影响,间隔的计算在Application类中:

void Application::Run()
    {
        while (m_Running)
        {
            if(!m_Cursor)
                glfwSetInputMode((GLFWwindow*)m_Window->GetNativeWindow(), GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
            else
                glfwSetInputMode((GLFWwindow*)m_Window->GetNativeWindow(), GLFW_CURSOR, GLFW_CURSOR_DISABLED);

            float time = (float)glfwGetTime();
            Timestep timestep = time - m_LastFrameTime;
            m_LastFrameTime = time;

            for (Layer* layer : m_LayerStack)
                layer->OnUpdate(timestep);

            m_ImGuiLayer->Begin();
            for (Layer* layer : m_LayerStack)
                layer->OnImGuiRender();
            m_ImGuiLayer->End();

            m_Window->OnUpdate();
        }
    }

计算的deltatime用于层的update()函数。

CameraController类设置了许多事件函数:

    private:
        bool OnMouseScrolled(MouseScrolledEvent& e);
        bool OnWindowResized(WindowResizeEvent& e);
        bool OnMouseMoved(MouseMovedEvent& e);
        bool OnKeyBoard(KeyPressedEvent& e);
        bool OnKeyReleased(KeyReleasedEvent& e);
        bool OnMouseClicked(MouseButtonPressedEvent& e);
        bool OnMouseReleased(MouseButtonReleasedEvent& e);
        void UpdateCameraVectors();

在事件系统那一节介绍过它们的用法。

实现中:

    bool CameraController::OnMouseScrolled(MouseScrolledEvent& e)
    {
        if (m_ZoomLevel >= 1.0f && m_ZoomLevel <= 90.0f)
            m_ZoomLevel -= e.GetYOffset();
        if (m_ZoomLevel <= 1.0f)
            m_ZoomLevel = 1.0f;
        if (m_ZoomLevel >= 90.0f)
            m_ZoomLevel = 90.0f;
        m_Camera.SetZoom(m_ZoomLevel);
        return false;
    }

鼠标滚轮可以改变摄像机焦距,使用事件类对象来捕捉鼠标滚轮偏移。

    bool CameraController::OnWindowResized(WindowResizeEvent& e)
    {
        m_Camera.SetAspectRatio(float(e.GetWidth()) / float(e.GetHeight()));
        return false;
    }

窗口缩放要改变摄像机大小的比例。

    bool CameraController::OnMouseMoved(MouseMovedEvent& e)
    {
        if (!m_Quit)
        {
            float xPos, yPos;
            xPos = e.GetX();
            yPos = e.GetY();
            if (firstMouse)
            {
                lastX = xPos;
                lastY = yPos;
                firstMouse = false;
            }

            float xOffset = 0.0f;
            float yOffset = 0.0f;
            xOffset = (xPos - lastX) * m_MouseSensitivity;
            yOffset = (lastY - yPos) * m_MouseSensitivity;
            lastX = xPos;
            lastY = yPos;
            if (m_ALT)
            {
                if (m_MouseLeft)
                {
                    m_Yaw += xOffset*5;
                    m_Pitch += yOffset*5;
                    if (m_Pitch > 89.0f)
                        m_Pitch = 89.0f;
                    if (m_Pitch < -89.0f)
                        m_Pitch = -89.0f;
                }
                if (m_MouseMiddle)
                {
                    m_Camera.SetPosition(m_Camera.GetPosition() - m_Camera.GetRight() * (xOffset*20 * m_Timestep));
                    m_Camera.SetPosition(m_Camera.GetPosition() - m_Camera.GetUp() * (yOffset *20* m_Timestep));
                }
            }
        }
        else if (m_Quit)
        {
            float xPos, yPos;
            xPos = e.GetX();
            yPos = e.GetY();
            if (firstMouse)
            {
                lastX = xPos;
                lastY = yPos;
                firstMouse = false;
            }

            float xOffset = 0.0f;
            float yOffset = 0.0f;
            xOffset = (xPos - lastX) * m_MouseSensitivity;
            yOffset = (lastY - yPos) * m_MouseSensitivity;
            lastX = xPos;
            lastY = yPos;
            m_Yaw += xOffset;
            m_Pitch += yOffset;
            if (m_Pitch > 89.0f)
                m_Pitch = 89.0f;
            if (m_Pitch < -89.0f)
                m_Pitch = -89.0f;
        }

        return false;
    }

对于摄像机的移动,这里提供了两种方式,第一是FPS游戏类的摄像机移动,第二是大部分三维相关软件的ALT类控制,两种方式都捕捉鼠标的移动来改变俯仰角和倾斜角,使用欧拉角来控制摄像机很方便。

    bool CameraController::OnKeyBoard(KeyPressedEvent& e)
    {
        if (m_Quit)
        {
            if (e.GetKeyCode() == DG_KEY_A)
                    m_Camera.SetPosition(m_Camera.GetPosition() - m_Camera.GetRight() * (m_CameraTranslationSpeed * m_Timestep));
            if (e.GetKeyCode() == DG_KEY_D)
                m_Camera.SetPosition(m_Camera.GetPosition() + m_Camera.GetRight() * (m_CameraTranslationSpeed * m_Timestep));
            if (e.GetKeyCode() == DG_KEY_W)
                m_Camera.SetPosition(m_Camera.GetPosition() + m_Camera.GetFront() * (m_CameraTranslationSpeed * m_Timestep));
            if (e.GetKeyCode() == DG_KEY_S)
                m_Camera.SetPosition(m_Camera.GetPosition() - m_Camera.GetFront() * (m_CameraTranslationSpeed * m_Timestep));
        }
        if (e.GetKeyCode() == DG_KEY_Q)
        {
            if (m_Quit)
            {
                m_LastCamera = m_Camera;
                firstMouse = true;
                m_Quit = !m_Quit;
            }
            else
            {
                m_Camera = m_LastCamera;
                m_Quit = !m_Quit;
            }
        }
        if (!m_Quit)
        {
            if (e.GetKeyCode() == DG_KEY_LEFT_CONTROL)
                m_CTRL = true;
            if (e.GetKeyCode() == DG_KEY_LEFT_ALT)
                m_ALT = true;
        }
        return false;
    }

键盘按键也是配合上面提到了两种方式,FPS方式是使用WASD来控制前后左右,另一种方式是使用CTRLALTQ键可以切换两种方式。

这里使用了m_LastCameram_Camera来配合解决一个问题,即在切换方式时(从三维方式到FPS方式),摄像机的的参数会切换为初始状态的问题,使用这两个变量可以在切换时存储当前摄像机状态,并在之后赋予,还记得我们之前摄像机类的一个函数吗:

        Camera(Camera& camera) 
        {
            m_Position = camera.m_Position;
            m_Front = camera.m_Front;
            m_Up = camera.m_Up;
            m_Right = camera.m_Right;
            m_WorldUp = camera.m_WorldUp;
            m_Zoom = camera.m_Zoom;
            m_NearPlane = camera.m_NearPlane;
            m_FarPlane = camera.m_FarPlane;
            m_AspectRatio = camera.m_AspectRatio;
        }

这是一个重写的复制函数,用来帮助解决我的切换问题。

    bool CameraController::OnKeyReleased(KeyReleasedEvent& e)
    {
        if (!m_Quit)
        {
            if (e.GetKeyCode() == DG_KEY_LEFT_CONTROL)
                m_CTRL = false;
            if (e.GetKeyCode() == DG_KEY_LEFT_ALT)
                m_ALT = false;
        }
        return false;
    }

键盘按键松开函数。

    bool CameraController::OnMouseClicked(MouseButtonPressedEvent& e)
    {
        if (!m_Quit)
        {
            if (e.GetMouseButton() == DG_MOUSE_BUTTON_LEFT)
                m_MouseLeft = true;
            if (e.GetMouseButton() == DG_MOUSE_BUTTON_RIGHT)
                m_MouseRight = true;
            if (e.GetMouseButton() == DG_MOUSE_BUTTON_MIDDLE)
                m_MouseMiddle = true;
        }
        return false;
    }

鼠标按键事件在三维模式下使用。

    bool CameraController::OnMouseReleased(MouseButtonReleasedEvent& e)
    {
        if (!m_Quit)
        {
            if (e.GetMouseButton() == DG_MOUSE_BUTTON_LEFT)
                m_MouseLeft = false;
            if (e.GetMouseButton() == DG_MOUSE_BUTTON_RIGHT)
                m_MouseRight = false;
            if (e.GetMouseButton() == DG_MOUSE_BUTTON_MIDDLE)
                m_MouseMiddle = false;
        }
        return false;
    }

鼠标按键松开事件,和上面的函数配合,用于切换按键的状态。

    void CameraController::UpdateCameraVectors()
    {
        glm::vec3 front;
        front.x = cos(glm::radians(m_Yaw)) * cos(glm::radians(m_Pitch));
        front.y = sin(glm::radians(m_Pitch));
        front.z = sin(glm::radians(m_Yaw)) * cos(glm::radians(m_Pitch));

        m_Camera.SetFront(glm::normalize(front));
        m_Camera.SetWorldUp(glm::vec3(0.0f, 1.0f, 0.0f));
        m_Camera.SetRight(glm::normalize(glm::cross(m_Camera.GetFront(), m_Camera.GetWorldUp())));
        m_Camera.SetUp(glm::cross(m_Camera.GetRight(), m_Camera.GetFront()));
    }

最后是摄像机矢量更新函数,使用新计算来的两个欧拉角来更新相关的矢量。

摄像机的更新函数:

    void CameraController::OnUpdate(Timestep ts)
    {
        m_Timestep = ts;
        UpdateCameraVectors();
        //m_CameraTranslationSpeed = m_ZoomLevel;
    }

用于设置时间间隔,以及更新摄像机矢量。

事件函数:

    void CameraController::OnEvent(Event& e)
    {
        EventDispatcher dispatcher(e);
        dispatcher.Dispatch(DG_BIND_EVENT_FN(CameraController::OnMouseScrolled));
        dispatcher.Dispatch(DG_BIND_EVENT_FN(CameraController::OnWindowResized));
        dispatcher.Dispatch(DG_BIND_EVENT_FN(CameraController::OnMouseMoved));
        dispatcher.Dispatch(DG_BIND_EVENT_FN(CameraController::OnKeyBoard));
        dispatcher.Dispatch(DG_BIND_EVENT_FN(CameraController::OnMouseClicked));
        dispatcher.Dispatch(DG_BIND_EVENT_FN(CameraController::OnMouseReleased));
        dispatcher.Dispatch(DG_BIND_EVENT_FN(CameraController::OnKeyReleased));
    }

主要是创建事件批处理对象来派送编写好的事件响应函数。

在客户端的层类中,构造函数中初始化摄像机控制器类对象:

ExampleLayer()
        : Layer("Example"), m_CameraController(1280.0 / 720.f)

OnUpdate函数中,调用摄像机控制器的更新函数:

    void OnUpdate(Dragon::Timestep ts) override
    {
        m_CameraController.OnUpdate(ts);

至此,摄像机组件完成。下一节介绍材质系统。

项目github地址:https://github.com/Dragon-Baby/Dragon

你可能感兴趣的:(Dragon Engine:摄像机)