欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

我们自己的摄像机类(在OpenGL中造一个摄像机)

程序员文章站 2022-03-25 22:25:02
...

在学图形学时,我们都是通过把场景中的所有物体向z轴负方向移动来模拟出摄像头的感觉,但那样的话我们模拟摄像机移动时其实也只能移动物体,当然我们还可以用glLookAt改变对应参数来模拟摄像机移动会更真实一些。

但我们如果要应用到着色器中只用glLookAt很不方便,比如我们经常要计算镜面反射光就需要视线方向,而且OpenGL本身没有摄像机的概念,但我们可以通过摄像机向后移动来模拟出摄像机,产生一种我们在移动的感觉,而不是场景在移动。所以我们自己构造一个摄像机,并通过键盘和鼠标输入让我们能够在3D场景中*移动,就像我的世界中的操作一样。

几点重要的说明:

(1)鼠标移动伴随的摄像机角度的移动:

我们自己的摄像机类(在OpenGL中造一个摄像机)
我们自己的摄像机类(在OpenGL中造一个摄像机)
我们自己的摄像机类(在OpenGL中造一个摄像机)
这些角度对于计算鼠标移动对应的摄像机角度的变化很关键。也是其原理。在程序中用的就是同名变量,注意理解。

(2)在主程序中的使用:

摄像机对象的函数看最后的类结合使用理解。

初始化:

// camera定义摄像机对象
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
bool firstMouse = true;
//我们跟踪两个全局变量来计算出deltaTime值:为了在不同性能的计算机上运行速度一样
float deltaTime = 0.0f; // 当前帧与上一帧的时间差
float lastFrame = 0.0f; // 上一帧的时间
//第一步是计算鼠标自上一帧的偏移量。我们必须先在程序中储存上一帧的鼠标位置,我们把它的初始值设置为屏幕的中心:
float lastX = SCR_WIDTH / 2.0f;
float lastY = SCR_HEIGHT / 2.0f;
//判断是否是第一次鼠标事件
bool firstMouse=true;
//滚轮控制观察范围
float fov=45.0;

glfw提供的用于交互的回调函数:

    glfwSetCursorPosCallback(window, mouse_callback);//鼠标
    glfwSetScrollCallback(window, scroll_callback);//滚轮

    //我们要告诉GLFW,它应该隐藏光标,并捕捉(Capture)它。
    //捕捉光标表示的是,如果焦点在你的程序上
    //(译注:即表示你正在操作这个程序,Windows中拥有焦点的程序标题栏通常是有颜色的那个,而失去焦点的程序标题栏则是灰色的)
    //光标应该停留在窗口中(除非程序失去焦点或者退出)。
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

也就是说在调用glfwSetInputMode函数之后,无论我们怎么去移动鼠标,光标都不会显示了,它也不会离开窗口。

上面鼠标、滚轮时间的回调函数:mouse_callback、scroll_callback

void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
    if (firstMouse)
    {
        lastX = xpos;
        lastY = ypos;
        firstMouse = false;
    }
    float xoffset = xpos - lastX;
    float yoffset = lastY - ypos; //别忘了OpenGL是左下角为原点
    lastX = xpos;
    lastY = ypos;
	//我们只需要得到鼠标的位置,剩下的摄像机位置交给ProcessMouseMovement函数
    camera.ProcessMouseMovement(xoffset, yoffset);
}

当滚动鼠标滚轮的时候,yoffset值代表我们竖直滚动的大小,ProcessMouseScroll根据yoffset值改变投影时平截头体的角度范围(也就是下面的camera.Zoom)来模拟缩放。

void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
    camera.ProcessMouseScroll(yoffset);
}

render loop 渲染循环中:

投影矩阵和观察矩阵的计算:

glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
glm::mat4 view = camera.GetViewMatrix();

键盘的输入processInput(window);

void processInput(GLFWwindow *window)
{
	//按esc退出程序
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
	//按w、s、a、d可以分别前后左右移动摄像机位置
    if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
        camera.ProcessKeyboard(FORWARD, deltaTime);
    if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
        camera.ProcessKeyboard(BACKWARD, deltaTime);
    if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
        camera.ProcessKeyboard(LEFT, deltaTime);
    if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
        camera.ProcessKeyboard(RIGHT, deltaTime);
}

摄像机类

#ifndef CAMERA_H
#define CAMERA_H
//#define STB_IMAGE_IMPLEMENTATION//用glm库的必备(如果主程序中已经定义就不用再在这里重复了)
#include <glad/glad.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

#include <vector>

//用在ProcessKeyboard函数中实现键盘控制摄像机前后左右的移动
enum Camera_Movement {
    FORWARD,
    BACKWARD,
    LEFT,
    RIGHT
};

// Default camera values
const float YAW         = -90.0f;
const float PITCH       =  0.0f;
//YAW、PITCH对应前面图的角度
const float SPEED       =  2.5f;
const float SENSITIVITY =  0.1f;
//我们把偏移量乘以了sensitivity(灵敏度)值。
//如果我们忽略这个值,鼠标移动就会太大了;你可以自己实验一下,找到适合自己的灵敏度值。
const float ZOOM        =  45.0f;
//上面已经提到过ZOOM——投影时平截头体的角度范围fovy


// An abstract camera class that processes input and calculates the corresponding Euler Angles, Vectors and Matrices for use in OpenGL
class Camera
{
public:
	//这些变量在很多地方都有重要的应用注意体会
    // Camera Attributes
    glm::vec3 Position;//位置
    glm::vec3 Front;//朝向
    glm::vec3 Up;//头向上的位置
    glm::vec3 Right;
    glm::vec3 WorldUp;
    // Euler Angles
    float Yaw;
    float Pitch;
    // Camera options
    float MovementSpeed;//移动速度
    float MouseSensitivity;//灵敏度
    float Zoom;//fovy
	//下面是两种构造方式
    // Constructor with vectors
    Camera(glm::vec3 position = glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f), float yaw = YAW, float pitch = PITCH) : Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM)
    {
        Position = position;
        WorldUp = up;
        Yaw = yaw;
        Pitch = pitch;
        updateCameraVectors();
    }
    // Constructor with scalar values
    Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float yaw, float pitch) : Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM)
    {
        Position = glm::vec3(posX, posY, posZ);
        WorldUp = glm::vec3(upX, upY, upZ);
        Yaw = yaw;
        Pitch = pitch;
        updateCameraVectors();
    }

    // Returns the view matrix calculated using Euler Angles and the LookAt Matrix
    glm::mat4 GetViewMatrix()
    {//计算观察矩阵
        return glm::lookAt(Position, Position + Front, Up);
    }

    // Processes input received from any keyboard-like input system. Accepts input parameter in the form of camera defined ENUM (to abstract it from windowing systems)
    void ProcessKeyboard(Camera_Movement direction, float deltaTime)
    {
		//为了让不同性能的电脑移动速度相同
        float velocity = MovementSpeed * deltaTime;
        
        if (direction == FORWARD)
            Position += Front * velocity;
        if (direction == BACKWARD)
            Position -= Front * velocity;
        if (direction == LEFT)
            Position -= Right * velocity;
        if (direction == RIGHT)
            Position += Right * velocity;
    }

    // Processes input received from a mouse input system. Expects the offset value in both the x and y direction.
    void ProcessMouseMovement(float xoffset, float yoffset, GLboolean constrainPitch = true)
    {
    	//注意我们把偏移量乘以了sensitivity(灵敏度)值。
		//如果我们忽略这个值,鼠标移动就会太大了;
		//你可以自己实验一下,找到适合自己的灵敏度值。
        xoffset *= MouseSensitivity;
        yoffset *= MouseSensitivity;

        Yaw   += xoffset;
        Pitch += yoffset;

        // Make sure that when pitch is out of bounds, screen doesn't get flipped
        //对于俯仰角,要让用户不能看向高于89度的地方(在90度时视角会发生逆转,所以我们把89度作为极限),同样也不允许小于-89度。
		//这样能够保证用户只能看到天空或脚下,但是不能超越这个限制。
		//我们可以在值超过限制的时候将其改为极限值来实现:
        if (constrainPitch)
        {
            if (Pitch > 89.0f)
                Pitch = 89.0f;
            if (Pitch < -89.0f)
                Pitch = -89.0f;
        }

        // Update Front, Right and Up Vectors using the updated Euler angles
        updateCameraVectors();
    }

    // Processes input received from a mouse scroll-wheel event. Only requires input on the vertical wheel-axis
    void ProcessMouseScroll(float yoffset)
    {//滚轮实现视野的缩放(前面提到过了)
        if (Zoom >= 1.0f && Zoom <= 45.0f)
            Zoom -= yoffset;
        if (Zoom <= 1.0f)
            Zoom = 1.0f;
        if (Zoom >= 45.0f)
            Zoom = 45.0f;
    }
//最后写一个用来对方向变换后更新摄像机对象中内容的函数。
private:
    // Calculates the front vector from the Camera's (updated) Euler Angles
    void updateCameraVectors()
    {
        // Calculate the new Front vector
        glm::vec3 front;
        front.x = cos(glm::radians(Yaw)) * cos(glm::radians(Pitch));
        front.y = sin(glm::radians(Pitch));
        front.z = sin(glm::radians(Yaw)) * cos(glm::radians(Pitch));
        Front = glm::normalize(front);
        // Also re-calculate the Right and Up vector
        Right = glm::normalize(glm::cross(Front, WorldUp));  // Normalize the vectors, because their length gets closer to 0 the more you look up or down which results in slower movement.
        Up    = glm::normalize(glm::cross(Right, Front));
    }
};
#endif