• 【OpenGL】笔记八、摄像机(View矩阵)


    流程

    上回说到了MVP矩阵的使用,但是在view矩阵的设置时比较粗略,view矩阵变换本质是转换到相机坐标系,是根据相机的位置变换的,上次我们只是将相机往z轴正方向移了三个单位而已,但是现实的情况是相机可能会以各种方式移动,现在就来正式讨论相机坐标系的变换

    首先要知道,相机坐标系需要三个属性来定义,分别是位置坐标(原点),看向的方向(-z轴)和向上向量(y轴),这是默认的,知道了这些,就能根据叉乘等算出整个坐标系的轴的向量,然后就能根据这些求出世界坐标系到相机坐标系的变换了(知道了相机位置,求平移到原点的矩阵很容易,那旋转矩阵怎么求呢?逆向思维,先求相机坐标系到世界坐标系的变换,得到它的逆矩阵,然后根据旋转矩阵是正交矩阵,而正交矩阵的转置等于它的逆矩阵这个性质就可以求得我们要的矩阵了)
    在这里插入图片描述
    所以,可以调用lookat函数来创建view矩阵:

    glm::mat4 view;
    view = glm::lookAt(glm::vec3(0.0f, 0.0f, 3.0f), 
               glm::vec3(0.0f, 0.0f, 0.0f), 
               glm::vec3(0.0f, 1.0f, 0.0f));//位置、目标和上向量
    
    • 1
    • 2
    • 3
    • 4

    接下来还能让相机环绕:

    float radius = 10.0f;
    float camX = sin(glfwGetTime()) * radius;
    float camZ = cos(glfwGetTime()) * radius;
    glm::mat4 view;
    view = glm::lookAt(glm::vec3(camX, 0.0, camZ), glm::vec3(0.0, 0.0, 0.0), glm::vec3(0.0, 1.0, 0.0)); 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    效果:
    在这里插入图片描述

    接下来我们要实现利用键盘控制相机平移的效果:

    首先是规定相机的朝向,如果只改变相机位置,不改变相机目标的话,它的视角方向依然会朝向目标,所以首先把它的目标定为当前位置加一个方向,这样平移相机时它的朝向就不会变了

    glm::vec3 cameraPos   = glm::vec3(0.0f, 0.0f,  3.0f);
    glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f);
    glm::vec3 cameraUp    = glm::vec3(0.0f, 1.0f,  0.0f);
    view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);
    
    • 1
    • 2
    • 3
    • 4

    接下来我们在之前的processInput函数里加入输入检测,通过WASD来改变相机位置:

    void processInput(GLFWwindow *window)
    {
        ...
        float cameraSpeed = 0.05f; // adjust accordingly
        if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
            cameraPos += cameraSpeed * cameraFront;
        if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
            cameraPos -= cameraSpeed * cameraFront;
        if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
            cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
        if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
            cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    效果:
    在这里插入图片描述

    实际情况下根据处理器的能力不同,有些人可能会比其他人每秒绘制更多帧,也就是以更高的频率调用processInput函数。结果就是,根据配置的不同,有些人可能移动很快,而有些人会移动很慢。当你发布你的程序的时候,你必须确保它在所有硬件上移动速度都一样。

    所以我们记录每帧渲染的时间差,把它乘以我们的移动速度,这样渲染耗时越大,速度也就越快,就能保持所有硬件一致了

    float deltaTime = 0.0f; // 当前帧与上一帧的时间差
    float lastFrame = 0.0f; // 上一帧的时间
    
    • 1
    • 2
    float currentFrame = glfwGetTime();
    deltaTime = currentFrame - lastFrame;
    lastFrame = currentFrame;
    
    • 1
    • 2
    • 3
    void processInput(GLFWwindow *window)
    {
      float cameraSpeed = 2.5f * deltaTime;
      ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    解决完键盘移动,现在该考虑鼠标的作用了,之前的平移并不能帮助我们转动相机视角,现在我们来尝试利用捕捉鼠标位置来转动相机,目前只考虑pitch和yaw:
    在这里插入图片描述
    当pitch角确定时,由图:
    在这里插入图片描述
    得到相机目标方向为:

    direction.y = sin(glm::radians(pitch)); 
    direction.x = cos(glm::radians(pitch));
    direction.z = cos(glm::radians(pitch));
    
    • 1
    • 2
    • 3

    同理对于yaw角,它的示意图如下
    在这里插入图片描述
    我们将其叠加在pitch角的结果上

    direction.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw)); // 译注:direction代表摄像机的前轴(Front),这个前轴是和本文第一幅图片的第二个摄像机的方向向量是相反的
    direction.y = sin(glm::radians(pitch));
    direction.z = cos(glm::radians(pitch)) * sin(glm::radians(yaw));
    
    • 1
    • 2
    • 3

    有了两个角的公式,我们接下来将鼠标的移动转换为两个角的输入

    首先隐藏光标并捕捉它,让它不会离开窗口

    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
    
    • 1

    接下来让GLFW监听鼠标移动事件(使用一个和窗口大小改变类似的回调函数)

    void mouse_callback(GLFWwindow* window, double xpos, double ypos);
    
    • 1

    接下来注册这个函数

    glfwSetCursorPosCallback(window, mouse_callback);
    
    • 1

    准备好了之后我们计算鼠标自上一帧的偏移量。我们必须先在程序中储存上一帧的鼠标位置,我们把它的初始值设置为屏幕的中心(屏幕的尺寸是800x600):

    float lastX = 400, lastY = 300;
    
    • 1

    然后定义初始的yaw为-90,pitch为0(因为按照原公式来说,都设为0的话初始是向x轴正方向看的,我们需要的是初始向-z轴方向看):

    float yaw = -90.0f;
    float pitch = 0.0f;
    
    • 1
    • 2

    一切准备就绪后就开始编写我们的鼠标回调函数吧:

    首先为了防止鼠标进入屏幕时跳变,我们判断是不是第一次进入屏幕,是的话就设置初始位置值为当前值,这样偏移量就为0,就不会跳变

    void mouse_callback(GLFWwindow* window, double xpos, double ypos)
    {
        if(firstMouse)
        {
            lastX = xpos;
            lastY = ypos;
            firstMouse = false;
        }
        ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    接下来计算相比上一帧的偏移量,并将它乘以一个敏感度,赋给偏移量,加在yaw和pitch角上(glfwSetCursorPosCallback返回给mouse_callback函数的 (x,y) 是鼠标相对于窗口左【上】角的位置,所以需要将 (ypos - lastY) 取反

        ...
        float xoffset = xpos - lastX;
        float yoffset = lastY - ypos; 
        lastX = xpos;
        lastY = ypos;
    
        float sensitivity = 0.05;
        xoffset *= sensitivity;
        yoffset *= sensitivity;
    
        yaw   += xoffset;
        pitch += yoffset;
        ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    然后一般FPS游戏中都不能抬低头超过90°,所以也要对pitch角做一个限制

        ...
        if(pitch > 89.0f)
            pitch = 89.0f;
        if(pitch < -89.0f)
            pitch = -89.0f;
        ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    最后就是用之前的公式根据yaw和pitch角计算相机方向

        ...
        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));
        cameraFront = glm::normalize(front);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    效果:
    在这里插入图片描述

    同样的,我们也可以通过鼠标滚轮来改变fov:

    glfwSetScrollCallback(window, scroll_callback);
    
    • 1
    void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
    {
        if (fov >= 1.0f && fov <= 45.0f)
            fov -= yoffset;
        if (fov <= 1.0f)
            fov = 1.0f;
        if (fov >= 45.0f)
            fov = 45.0f;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    效果:在这里插入图片描述

    2. 练习

    2.1 看看你是否能够修改摄像机类,使得其能够变成一个真正的FPS摄像机(也就是说不能够随意飞行);你只能够呆在xz平面上

    在鼠标回调函数中去除对pitch的赋值即可,让它保持初始值0

    2.2 试着创建你自己的LookAt函数,其中你需要手动创建一个我们在一开始讨论的观察矩阵。用你的函数实现来替换GLM的LookAt函数,看看它是否还能一样地工作

    在这里插入图片描述因为mat4在内存中按列序存储,所以我们可以这样按序赋值

    glm::mat4 lookat(glm::vec3 eyePos, glm::vec3 cameraTarget, glm::vec3 Up){
    	glm::vec3 direction = eyePos - cameraTarget;
    	glm::vec3 right = glm::cross(Up, direction);
    	glm::mat4 out = glm::mat4(1.0f);
    	out[0][0] = right.x; out[1][0] = right.y; out[1][0] = right.z;
    	out[0][1] = Up.x; out[1][1] = Up.y; out[1][1] = Up.z;
    	out[0][2] = direction .x; out[1][2] = direction .y; out[1][2] = direction .z;
    	out = glm::translate(out, -eyePos);
    	return out;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
  • 相关阅读:
    【目标跟踪】|数据集汇总
    Java基础篇 数组
    自媒体赚钱方式有哪些?如何高效运营自媒体
    nginx+keepalived 高可用项目实战
    学习笔记-TP5框架学习笔记进阶之Contorller
    12. Java异常及异常处理处理
    javaweb教师人事管理系统的设计
    更好的print :嫌弃print太单调 那么来试试这几种方法吧
    FPGA是什么呢,通透讲解单片机和FPGA的区别
    MySQL DDL执行方式-Online DDL介绍
  • 原文地址:https://blog.csdn.net/ycrsw/article/details/124868236