errorman

不积跬步,无以至千里

导航

learnopengl(9)摄像机

Posted on 2023-08-21 17:23  Clemens  阅读(119)  评论(0编辑  收藏  举报

在上一节坐标系统中提到过:观察空间(view space)经常被人们称之为Opengl的摄像机,所以有时候也称为摄像机空间(Camear Space)或者视觉空间(Eye Space)。观察空间就是从摄像机的视角所观察到的空间。

 

一、摄像机/观察空间

当我们讨论观察/摄像机空间的时候,是讨论以摄像机的视角作为场景原点时场景中所有顶点的坐标:观察矩阵把所有的世界坐标变换为相对于摄像机位置与方向的观察坐标。

要定义一个摄像机,我们需要它在世界空间中的位置、观察的方向、一个指向它右侧的向量和一个指向它上方的向量。有了以上四个向量,就可以构建成一个以摄像机位置为原点,三个单位轴相互垂直的坐标系。

1. 摄像机位置

摄像机位置就是世界空间中指向摄像机的一个向量。

glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);

注:Z轴正方向是从屏幕指向我们自己的,我们的视角就相当于摄像机的视角,如果我们希望摄像机向后移动,那么我们就应该沿着z轴正方向移动。

 

2. 摄像机方向

摄像机方向指的是:摄像机指向的方向。

现在我们让摄像机指向场景原点:(0, 0, 0)。用场景原点向量减去摄像机位置向量的结果就是摄像机的指向向量。摄像机指向场景原点,那么指向向量就为:场景原点向量减去摄像机位置,即(0, 0, 0)-(0, 0, 3)=(0, 0, -3.0f),也就是说,如果以摄像机位置为原点,那么场景原点在摄像机位置的z轴负方向处。

在世界空间中,摄像机实际指向的是z轴负方向,但我们希望方向向量(Direction Vector)指向摄像机的z轴正方向。如果我们交换相减的顺序,我们就会获得一个指向摄像机正z轴方向的向量:

glm::vec3 cameraTarget = glm::vec3(0.0f, 0.0f, 0.0f);
glm::vec3 cameraDirection = glm::normalize(cameraPos - cameraTarget);

注:所以此处的方向向量实际上是指向摄像机到目标向量的相反方向。

 

3. 右轴

右向量(Right Vector),它代表摄像机空间的x轴的正方向。为此,我们先定义一个上向量(Up Vector),然后利用向量叉乘的计算特性,将上向量和上一步骤中得到的方向向量叉乘,就可以得到一个垂直于上向量和方向向量的右向量。

glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f); 
glm::vec3 cameraRight = glm::normalize(glm::cross(up, cameraDirection));

注:注意glm::cross里面的参数顺序,因为上一步计算方向向量与实际的指向向量的方向相反,根据向量叉乘的右手螺旋定则,我们右手四指的方向应该是从上向量转向方向向量,而不是实际的指向向量。如果交换参数顺序,那么就会得到方向相反的右向量。

 

4. 上轴

我们在上面已经计算出了右向量和方向向量,即x轴和z轴,还是利用叉乘性质,就可以计算出上向量,即y轴:

glm::vec3 cameraUp = glm::cross(cameraDirection, cameraRight);

注:注意glm::cross里面的参数顺序。

 

二、Look At

从上面可以看出,我们从世界空间通过观察矩阵变换到摄像机空间,实际上是对坐标系进行了变换:先将世界空间的坐标系原点平移到摄像机坐标系原点,然后再进行旋转,我们可以得到一个lookat矩阵,将世界坐标变换到观察坐标。

根据矩阵的相关知识,可以看出第一个矩阵相当于旋转变换,第二个矩阵相当于平移变换。具体推导可以参考:观察矩阵推导过程。其中R是右向量,U是上向量,D是方向向量,P是摄像机位置。

GLM提供了lookat函数,可以直接计算:

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));

参数分别为:摄像机位置向量,目标位置向量,上向量。

 

自由移动

接下来,在上一节的基础上,我们将摄像机的目标位置固定在(0,0,0),然后让摄像机在xz平面上以一个圆形旋转,每帧对摄像机x和z的位置进行计算,即每帧重新计算观察矩阵:

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));

主要代码如下:

查看代码
 ourShader.use();    
// 传递投影矩阵给shader(因为投影矩阵很少改变,所以没有必要每帧都进行计算)
    glm::mat4 projection = glm::perspective(glm::radians(45.0f), 
        (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
    ourShader.setMat4("projection", projection);

    while (!glfwWindowShouldClose(window))
    {   
        processInput(window);

        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // 绑定纹理,绑定纹理到纹理单元
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, texture);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, texture2);
        
        ourShader.use();
        // 摄像机/观察变换
        glm::mat4 view = glm::mat4(1.0);
        float radius = 10;
        float camX = static_cast<float>(sin(glfwGetTime()) * radius);
        float camZ = static_cast<float>(cos(glfwGetTime()) * radius);
        view = glm::lookAt(glm::vec3(camX, 0., camZ), glm::vec3(0, 0, 0), glm::vec3(0, 1, 0));
        ourShader.setMat4("view", view);

        glBindVertexArray(VAO);
        for (uint32_t i = 0; i < 10; i++) {
            glm::mat4 model = glm::mat4(1.0);
            model = glm::translate(model, cubePositions[i]);
            float angle = 20.0 * i;
            model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0, 0.3, 0.5));
            ourShader.setMat4("model", model);
            glDrawArrays(GL_TRIANGLES, 0, 36);
        }

        glfwSwapBuffers(window);
        glfwPollEvents();
    }

 

移动速度

图形程序和游戏通常会跟踪一个时间差(Deltatime)变量,它储存了渲染上一帧所用的时间。我们把所有速度都去乘以deltaTime值。结果就是,如果我们的deltaTime很大,就意味着上一帧的渲染花费了更多时间,所以这一帧的速度需要变得更高来平衡渲染所花去的时间。使用这种方法时,无论你的电脑快还是慢,摄像机的速度都会相应平衡,这样每个用户的体验就都一样了。

设置两个全局变量值,用来计算当前帧的速度:

float deltaTime = 0.0f; // 当前帧与上一帧的时间差
float lastFrame = 0.0f; // 上一帧的时间

在while循环中的每一帧中,我们计算新的deltaTime,用于计算当前帧的移动速度:

while (!glfwWindowShouldClose(window)){
    float currentFrame = glfwGetTime();
    deltaTime = currentFrame - lastFrame;
    lastFrame = currentFrame;
    processInput(window);
    ......
}

在processInput函数中重新计算当前速度:

void processInput(GLFWwindow *window)
{
  float cameraSpeed = 2.5f * deltaTime;
  ...
}

这样我们可以得到一个更流畅的移动。

 

三、视角移动

上面只能前后左右移动,但是不能转向。接下来我们使用鼠标,让其能进行转向。

欧拉角

欧拉角(Euler Angle)是可以表示3D空间中任何旋转的3个值,由莱昂哈德·欧拉(Leonhard Euler)在18世纪提出。一共有3种欧拉角:俯仰角(Pitch)、偏航角(Yaw)和滚转角(Roll),下面的图片展示了它们的含义:

从左往右依次是俯仰角:描述上下方向看的角;偏航角:描述左右看的角;滚转角:描述翻滚摄像机的角。将这三个角结合起来,我们就能描述3D空间中的任何旋转向量。对于摄像机来说,我们只关心偏航角和俯仰角。

假设摄像机方向向量的长度为1.根据下图,我们可以计算出在进行上下和左右移动方向后,每个轴分量的长度,从而得到基于俯仰角和偏航角的方向向量。

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));

 

鼠标移动

通过鼠标坐标来计算俯仰角和偏航角,从而计算真正的方向向量。主要代码如下:

void mouse_callback(GLFWwindow* window, double xposIn, double yposIn)
{
    float xpos = static_cast<float>(xposIn);
    float ypos = static_cast<float>(yposIn);

    if (firstMouse)
    {
        lastX = xpos;
        lastY = ypos;
        firstMouse = false;
    }
    // 计算当前帧和上一帧鼠标位置的偏移量
    float xoffset = xpos - lastX;
    float yoffset = lastY - ypos; // 注意这里是相反的,因为y坐标是从底部往顶部依次增大的
    lastX = xpos;
    lastY = ypos;

    float sensitivity = 0.1f; // 灵敏度,可根据需要自己更改
    xoffset *= sensitivity;
    yoffset *= sensitivity;
    // 将偏移量加在俯仰角和偏航角上
    yaw += xoffset;
    pitch += yoffset;

    // 确保当俯仰超出边界时,屏幕不会翻转
    if (pitch > 89.0f)
        pitch = 89.0f;
    if (pitch < -89.0f)
        pitch = -89.0f;
    // 通过俯仰角和偏航角来计算以得到真正的方向向量
    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);
}

 

缩放

使用鼠标滚轮计算视野(Field of View),定义我们看到多的范围,使用fov来计算投影矩阵。

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;
}

 现在要每帧计算:

projection = glm::perspective(glm::radians(fov), 800.0f / 600.0f, 0.1f, 100.0f);

 完成。

 

完整代码如下:

查看代码
 #include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <direct.h>
#include <filesystem>
#include "shader.h"
#include "stb_image.h"
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include "Camera.h"
// 纹理
void processInput(GLFWwindow* window);
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void mouse_callback(GLFWwindow* window, double xposIn, double yposIn);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);

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);

const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

bool firstMouse = true;
float yaw = -90.0f;	// yaw is initialized to -90.0 degrees since a yaw of 0.0 results in a direction vector pointing to the right so we initially rotate a bit to the left.
float pitch = 0.0f;
float lastX = 800.0f / 2.0;     //将鼠标光标置于窗口中央位置,
float lastY = 600.0 / 2.0;      //将鼠标光标置于窗口中央位置,
float fov = 45.0f;
float deltaTime = 0.0f;	// 当前帧和上一帧的时间差
float lastFrame = 0.0f; // 上一帧时间


int main()
{
    std::cout << std::filesystem::current_path() << std::endl;

	std::cout << "sdfs" << std::endl;
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
    glfwSetCursorPosCallback(window, mouse_callback);
    glfwSetScrollCallback(window, scroll_callback);

    // tell GLFW to capture our mouse
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);


    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    glEnable(GL_DEPTH_TEST);

    Shader ourShader("./shader.vs", "./shader.fs");

    float vertices[] = {
        -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,
         0.5f, -0.5f, -0.5f,  1.0f, 0.0f,
         0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
         0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,

        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
         0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
         0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
         0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
        -0.5f,  0.5f,  0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,

        -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        -0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
        -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

         0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
         0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
         0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
         0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
         0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
         0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
         0.5f, -0.5f, -0.5f,  1.0f, 1.0f,
         0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
         0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,

        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
         0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
         0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
         0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        -0.5f,  0.5f,  0.5f,  0.0f, 0.0f,
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f
    };
    // world space positions of our cubes
    glm::vec3 cubePositions[] = {
        glm::vec3(0.0f,  0.0f,  0.0f),
        glm::vec3(2.0f,  5.0f, -15.0f),
        glm::vec3(-1.5f, -2.2f, -2.5f),
        glm::vec3(-3.8f, -2.0f, -12.3f),
        glm::vec3(2.4f, -0.4f, -3.5f),
        glm::vec3(-1.7f,  3.0f, -7.5f),
        glm::vec3(1.3f, -2.0f, -2.5f),
        glm::vec3(1.5f,  2.0f, -2.5f),
        glm::vec3(1.5f,  0.2f, -1.5f),
        glm::vec3(-1.3f,  1.0f, -1.5f)
    };

    /*!多个VAO/VBO/EBO顺序
    * 1、创建VAO,VBO,EBO;
    * 2、一定要首先绑定VAO
    * 3、分别绑定VBO和EBO的数据
    * 4、链接顶点属性,并使能顶点属性
    * 5、多个VAO,VBO,EBO,要创建相同数量的VAO,VBO,EBO,并且都要一个一个来。
    */

    //创建顶点数组对象,并绑定
    unsigned int VBO, VAO;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);

    glBindVertexArray(VAO);

    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    // 位置属性
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    // 纹理属性
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);

    // 载入图片
    int width, height, nrChannels;
    unsigned char* data = stbi_load("./container.jpg", &width, &height, &nrChannels, 0);
    /* 加载纹理 */ 
    // 创建纹理
    unsigned int texture, texture2;
    glGenTextures(1, &texture);
    // 绑定纹理
    glBindTexture(GL_TEXTURE_2D, texture);
    // 设置两个轴的纹理环绕方式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    // 设置多级渐远纹理过滤方式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    // 使用图片数据生成一个纹理
    if (data) {
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
        glGenerateMipmap(GL_TEXTURE_2D);
    }
    else {
        std::cout << "failed to load texture" << std::endl;
    }
    // 释放图片内存
    stbi_image_free(data);

    // 纹理2
    glGenTextures(1, &texture2);
    glBindTexture(GL_TEXTURE_2D, texture2);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    stbi_set_flip_vertically_on_load(true);
    data = stbi_load("./awesomeface.png", &width, &height, &nrChannels, 0);
    // 使用图片数据生成一个纹理
    if (data) {
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
        glGenerateMipmap(GL_TEXTURE_2D);
    }
    else {
        std::cout << "failed to load texture" << std::endl;
    }
    // 释放图片内存
    stbi_image_free(data);

    ourShader.use();    //设置uniforms之前,不要忘了active/use着色器程序
    glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0); //手动设置
    ourShader.setInt("texture2", 1);    // 通过类函数设置



    while (!glfwWindowShouldClose(window))
    {   
        float currentFrame = static_cast<float>(glfwGetTime());
        deltaTime = currentFrame - lastFrame;
        lastFrame = currentFrame;
        processInput(window);

        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // 绑定纹理,绑定纹理到纹理单元
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, texture);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, texture2);
        // 获取矩阵的uniform位置,并给他设置值。
        ourShader.use();
        glm::mat4 projection = glm::perspective(glm::radians(fov), 
            (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
        ourShader.setMat4("projection", projection);
        glm::mat4 view = glm::mat4(1.0);
        view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);
        ourShader.setMat4("view", view);

        glBindVertexArray(VAO);
        for (unsigned int i = 0; i < 10; i++)
        {
            // calculate the model matrix for each object and pass it to shader before drawing
            glm::mat4 model = glm::mat4(1.0f); // make sure to initialize matrix to identity matrix first
            model = glm::translate(model, cubePositions[i]);
            float angle = 20.0f * i;
            model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
            ourShader.setMat4("model", model);

            glDrawArrays(GL_TRIANGLES, 0, 36);
        }
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    ourShader.deleteProgram();

    glfwTerminate();
    return 0;
}

void processInput(GLFWwindow* window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);

    float cameraSpeed = static_cast<float>(2.5 * deltaTime);
    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;
}

void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    glViewport(0, 0, width, height);
}

void mouse_callback(GLFWwindow* window, double xposIn, double yposIn)
{
    float xpos = static_cast<float>(xposIn);
    float ypos = static_cast<float>(yposIn);

    if (firstMouse)
    {
        lastX = xpos;
        lastY = ypos;
        firstMouse = false;
    }
    // 计算当前帧和上一帧鼠标位置的偏移量
    float xoffset = xpos - lastX;
    float yoffset = lastY - ypos; // 注意这里是相反的,因为y坐标是从底部往顶部依次增大的
    lastX = xpos;
    lastY = ypos;

    float sensitivity = 0.1f; // 灵敏度,可根据需要自己更改
    xoffset *= sensitivity;
    yoffset *= sensitivity;
    // 将偏移量加在俯仰角和偏航角上
    yaw += xoffset;
    pitch += yoffset;

    // 确保当俯仰超出边界时,屏幕不会翻转
    if (pitch > 89.0f)
        pitch = 89.0f;
    if (pitch < -89.0f)
        pitch = -89.0f;
    // 通过俯仰角和偏航角来计算以得到真正的方向向量
    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);
}

void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
    fov -= (float)yoffset;
    if (fov < 1.0f)
        fov = 1.0f;
    if (fov > 45.0f)
        fov = 45.0f;
}

 

 顶点着色器代码:

查看代码
 #version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;

out vec2 TexCoord;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
	gl_Position = projection * view * model * vec4(aPos, 1.0f);
	TexCoord = vec2(aTexCoord.x, aTexCoord.y);
}

 

片段着色器代码:

查看代码
 #version 330 core
out vec4 FragColor;

in vec2 TexCoord;

// texture samplers
uniform sampler2D texture1;
uniform sampler2D texture2;

void main()
{
	// linearly interpolate between both textures (80% container, 20% awesomeface)
	FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
}