首页 > 其他分享 >learnopengl(9)摄像机

learnopengl(9)摄像机

时间:2023-08-21 17:35:19浏览次数:39  
标签:glm 0.0 float 摄像机 learnopengl vec3 向量

在上一节坐标系统中提到过:观察空间(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);
}

 

标签:glm,0.0,float,摄像机,learnopengl,vec3,向量
From: https://www.cnblogs.com/errorman/p/17639819.html

相关文章

  • learnopengl(7)变换
    一、基础知识主要是一些向量和矩阵的计算方式。大学本科期间的线性代数里面的内容。坦白来讲,当时学线性代数,虽然考了个还不错的分数,但是实际这些向量、矩阵后面的意义是什么并不知道。只学会了一些基础的计算方法。 二、实践使用GLM库。我们在上一节的基础上,先将每个轴都缩......
  • LearnOpenGL(6) 纹理
    一、纹理是什么?我的第一反应是一张图片。在计算机图形学中,纹理被更多的认为是一块数据,它也不再局限于2D空间。具体请参考这篇文章:纹理那些事。 二、基础知识纹理坐标纹理坐标是纹理与图形的映射关系,图形中每个顶点都会关联一个纹理坐标,表示顶点需要从该位置读取纹理图像的......
  • 全景+特写镜头双目4K教学会议AI跟踪摄像机
    采用4K全新一代CMOS图像传感器,双4K输出,集成多路高清接口,SDI,HDMI,IP,USB同出不同场景的图像,内置AI跟踪算法,自动跟踪老师、学生、或者与会人员特定目标。在多媒体会议室、录播教学等场景下快速搭建高清视频会议系统广泛使用。全景无畸变超大广角镜头,水平视野达到95度。多路高清接口......
  • 如何使用 After Effects 导出摄像机跟踪数据到 3ds Max
     推荐:NSDT场景编辑器助你快速搭建可二次开发的3D应用场景在本教程中,我将展示如何在AfterEffects中跟踪实景场景,然后将相机数据导出到3dsMax。1.项目设置步骤1打开“后效”。打开后效果步骤2转到合成>新合成以创建新合成。或者,您可以按 Ctrl-N 键。它打开合成设......
  • 了解 3DS MAX 3D摄像机跟踪设置:第 1 部分
     这是一个关于使用行业标准插件RayFire在3dsMax中破坏元素的新系列。在本教程的第一部分中,我将向您展示如何在RayFire中使用在3dsMax中拆除元素的最基本操作和方法。推荐:NSDT场景编辑器助你快速搭建可二次开发的3D应用场景1.准备场景步骤1打开 3dsMax。......
  • 了解 3DS MAX 3D摄像机跟踪设置:第 5部分
    推荐:NSDT场景编辑器助你快速搭建可二次开发的3D应用场景1.创建陨石坑步骤1启动 3dsMax 和打开本教程最后一部分中保存的文件。启动3dsMax步骤2删除所有占位符从头开始创建陨石坑。删除所有占位符步骤3创建具有“长度”的平面 段和宽度段各为 150。创建平面......
  • 了解 3DS MAX 3D摄像机跟踪设置:第 4 部分
    推荐:NSDT场景编辑器助你快速搭建可二次开发的3D应用场景1.项目设置步骤1打开“后效”。打开后效果步骤2转到合成>新合成以创建新合成。将“宽度”和“高度”值分别设置为 1280 和 720。将帧速率设置为 25,将持续时间设置为 12 秒。单击确定。作曲>新作曲步骤3......
  • 了解 3DS MAX 3D摄像机跟踪设置:第 2 部分
    推荐:NSDT场景编辑器助你快速搭建可二次开发的3D应用场景1.项目设置步骤1打开“后效”。打开后效果步骤2转到合成>新合成以创建新合成。将“宽度”和“高度”值分别设置为 1280 和 720。将帧速率设置为 25,将持续时间设置为 12 秒。单击确定。作曲>新作曲步骤3......
  • 黑魂复刻摄像机修改
    打开CameraController在13行下面新建publicboolisAI=false;在Awake函数里,增加判断条件,并把下列原先的代码放假判断式:if(!isAI){piCamera=Camera.main.gameObject;lockDot.enabled=false;Cursor.lockState=CursorLockMode.Locked;}FixUpd......
  • 了解 3DS MAX 3D摄像机跟踪设置:第 7 部分
    推荐:NSDT场景编辑器助你快速搭建可二次开发的3D应用场景1.在SynthEyes中跟踪素材步骤1打开SynthEyes软件。打开合成之眼步骤2在跟踪素材之前,您需要设置首选项。因为,你稍后将在 3ds Max中工作,必须根据 3dsMax 设置首选项。转到编辑>编辑首选项。转到编辑>编辑首......