OpenGL光照:颜色

news2024/12/26 11:35:42

知识点归纳

 现实世界中有无数种颜色,每一个物体都有它们自己的颜色。我们要做的工作是使用(有限的)数字来模拟真实世界中(无限)的颜色,因此并不是所有的现实世界中的颜色都可以用数字来表示。然而我们依然可以用数字来代表许多种颜色,并且你甚至可能根本感觉不到他们与真实颜色之间的差异。颜色可以数字化的由红色(Red)、绿色(Green)和蓝色(Blue)三个分量组成,它们通常被缩写为RGB。这三个不同的分量组合在一起几乎可以表示存在的任何一种颜色。
我们在现实生活中看到某一物体的颜色并不是这个物体的真实颜色,而是它所反射(Reflected)的颜色。换句话说,那些不能被物体吸收(Absorb)的颜色(被反射的颜色)就是我们能够感知到的物体的颜色。例如,太阳光被认为是由许多不同的颜色组合成的白色光。如果我们将白光照在一个蓝色的玩具上,这个蓝色的玩具会吸收白光中除了蓝色以外的所有颜色,不被吸收的蓝色光被反射到我们的眼中,使我们看到了一个蓝色的玩具。
白色的阳光是一种所有可见颜色的集合,物体吸收了其中的大部分颜色,它仅反射了那些代表这个物体颜色的部分,这些被反射颜色的组合就是我们感知到的颜色
当我们把光源的颜色与物体的颜色相乘,所得到的就是这个物体所反射该光源的颜色(也就是我们感知到的颜色)
 来看一下光源色和物体颜色的反射运算:

// 光源的颜色值lightColor(RGB每个分量值可取范围为0到1)
glm::vec3 lightColor(1.0f, 1.0f, 1.0f);
// 物体的颜色值toyColor
glm::vec3 toyColor(1.0f, 0.5f, 0.31f);
// 当光源直射物体时,反射后的反射光颜色值result
glm::vec3 result = lightColor * toyColor; // = (1.0f, 0.5f, 0.31f);

 我们可以看到玩具在进行反射时吸收了白色光源颜色中的大部分颜色,但它对红、绿、蓝三个分量都有一定的反射,反射量是由物体本身的颜色所决定的。这也代表着现实中的光线原理。由此,我们可以定义物体的颜色为这个物体从一个光源反射各个颜色分量的多少
我们可以通过物体对不同颜色光的反射来的得到意想不到的不到的颜色,从此创作颜色已经变得非常简单。
 教程原文

程序归纳

 该章节程序的最终运行结果:
在这里插入图片描述
 通用的顶点着色器:

#version 330 core
layout( location = 0 ) in vec3 position;

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

void main()
{
	gl_Position = projection * view * model * vec4(position, 1.0f);
}

 普通物体的片段着色器

#version 330 core
out vec4 color;

uniform vec3 objectColor;
uniform vec3 lightColor;

void main()
{
	color = vec4(lightColor * objectColor, 1.0f);
}

 光源的片段着色器

#version 330 core
out vec4 color;

void main()
{
    color = vec4(1.0f); //设置四维向量的所有元素为 1.0f
}

 主程序

// 标准输出
#include <string>

// GLEW
#define GLEW_STATIC
#include <GL/glew.h>

// GLFW
#include <GLFW/glfw3.h>

// 着色器类和摄像机类
#include "Shader.h"
#include "Camera.h"

// GLM 数学库
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

// 图片数据读取库
#include <SOIL.h>

// 定义窗口大小
GLuint screenWidth = 800, screenHeight = 600;

// GLFW窗口需要注册的函数
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
// 处理摄像机位置移动的函数
void Do_Movement();

// 定义摄像机
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
// 定义数组存储按键信息
bool keys[1024];
// 
GLfloat lastX = 400, lastY = 300;
bool firstMouse = true;

GLfloat deltaTime = 0.0f;
GLfloat lastFrame = 0.0f;

glm::vec3 lightPos(1.2f, 0.0f, -2.0f);

// The MAIN function, from here we start our application and run our Game loop
int main()
{
    // 初始化GLFW 
    glfwInit();
    // 设置glfw使用的OpenGL版本
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    // 设置使用OpenGL的核心模式
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    // 设置窗口大小可改变性 为不可变
    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
    // GLFW会自动创建一个每像素4个子采样点的深度和样本缓冲。这也意味着所有缓冲的大小都增长了4倍。
    glfwWindowHint(GLFW_SAMPLES, 4);

    // 创建GLFW的窗口
    GLFWwindow* window = glfwCreateWindow(screenWidth, screenHeight, "LearnOpenGL", nullptr, nullptr);
    // 设置window窗口线程为主线程
    glfwMakeContextCurrent(window);

    // 注册窗口的事件
    glfwSetKeyCallback(window, key_callback);         // 按键检测
    glfwSetCursorPosCallback(window, mouse_callback); // 鼠标移动检测 
    glfwSetScrollCallback(window, scroll_callback);   // 滚轮滑动检测

    // 设置隐藏光标模式
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

    // 设置GLEW为更现代的模式
    glewExperimental = GL_TRUE;
    // 初始化GLEW
    glewInit();

    // 设置视口的位置和大小(位置是相对于窗口左下角的)
    glViewport(0, 0, screenWidth, screenHeight);

    // 开启深度测试功能
    glEnable(GL_DEPTH_TEST);

    // 根据顶点着色器和片段着色器位置创建着色器程序
    Shader lightingShader("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\vertexShader.txt", "C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\fragmentShader.txt");
    Shader lampShader("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\vertexShader.txt", "C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\lightFragmentShader.txt");

    // 顶点数据(位置+纹理坐标)
    GLfloat vertices[] = {
        -0.5f, -0.5f, -0.5f,
         0.5f, -0.5f, -0.5f,
         0.5f,  0.5f, -0.5f,
         0.5f,  0.5f, -0.5f,
        -0.5f,  0.5f, -0.5f,
        -0.5f, -0.5f, -0.5f,

        -0.5f, -0.5f,  0.5f,
         0.5f, -0.5f,  0.5f,
         0.5f,  0.5f,  0.5f,
         0.5f,  0.5f,  0.5f,
        -0.5f,  0.5f,  0.5f,
        -0.5f, -0.5f,  0.5f,

        -0.5f,  0.5f,  0.5f,
        -0.5f,  0.5f, -0.5f,
        -0.5f, -0.5f, -0.5f,
        -0.5f, -0.5f, -0.5f,
        -0.5f, -0.5f,  0.5f,
        -0.5f,  0.5f,  0.5f,

         0.5f,  0.5f,  0.5f,
         0.5f,  0.5f, -0.5f,
         0.5f, -0.5f, -0.5f,
         0.5f, -0.5f, -0.5f,
         0.5f, -0.5f,  0.5f,
         0.5f,  0.5f,  0.5f,

        -0.5f, -0.5f, -0.5f,
         0.5f, -0.5f, -0.5f,
         0.5f, -0.5f,  0.5f,
         0.5f, -0.5f,  0.5f,
        -0.5f, -0.5f,  0.5f,
        -0.5f, -0.5f, -0.5f,

        -0.5f,  0.5f, -0.5f,
         0.5f,  0.5f, -0.5f,
         0.5f,  0.5f,  0.5f,
         0.5f,  0.5f,  0.5f,
        -0.5f,  0.5f,  0.5f,
        -0.5f,  0.5f, -0.5f
    };

    // 设置VBO和VAO
    GLuint VBO, VAO;
    // 先申请VAO和VBO的显存
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    // 绑定VAO
    glBindVertexArray(VAO);
    // 绑定VBO
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    // 将顶点数据传输到显存中
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    // 位置数据解析
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(0);
    glBindVertexArray(0); // 解绑VAO

    GLuint lightVAO;
    glGenVertexArrays(1, &lightVAO);
    glBindVertexArray(lightVAO);
    // 只需要绑定VBO不用再次设置VBO的数据,因为容器(物体)的VBO数据中已经包含了正确的立方体顶点数据
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    // 设置灯立方体的顶点属性指针(仅设置灯的顶点数据)
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(0);
    glBindVertexArray(0);

    // 游戏循环
    while (!glfwWindowShouldClose(window))
    {
        // 计算帧相差时间
        GLfloat currentFrame = glfwGetTime();
        deltaTime = currentFrame - lastFrame;
        lastFrame = currentFrame;

        // 窗口的事件检测
        glfwPollEvents();
        // 根据事件移动摄像机
        Do_Movement();

        // 设置清空屏幕颜色缓存所使用颜色
        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        // 清空 屏幕颜色缓存、深度缓存
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // 被光源照亮的普通物体的着色器
        lightingShader.Use();

        GLint objectColorLoc = glGetUniformLocation(lightingShader.Program, "objectColor");
        GLint lightColorLoc = glGetUniformLocation(lightingShader.Program, "lightColor");
        glUniform3f(objectColorLoc, 1.0f, 0.5f, 0.31f);// 我们所熟悉的珊瑚红
        glUniform3f(lightColorLoc, 1.0f, 1.0f, 1.0f); // 依旧把光源设置为白色

        // 创建观察矩阵( 只要创建矩阵记得初始化= glm::mat4(1.0f) )
        glm::mat4 view = glm::mat4(1.0f);
        // 获取观察矩阵(根据摄像机的状态)
        view = camera.GetViewMatrix();
        // 创建投影矩阵
        glm::mat4 projection = glm::mat4(1.0f);
        // 计算投影矩阵(fov视野为摄像机的属性camera.Zoom)
        projection = glm::perspective(camera.Zoom, (float)screenWidth / (float)screenHeight, 0.1f, 1000.0f);
        // 计算顶点着色器中矩阵的位置值
        GLint modelLoc = glGetUniformLocation(lightingShader.Program, "model");
        GLint viewLoc = glGetUniformLocation(lightingShader.Program, "view");
        GLint projLoc = glGetUniformLocation(lightingShader.Program, "projection");
        // 将观察矩阵和投影矩阵传入对应的位置(记得转换)
        glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
        glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projection));

       // 定义模型矩阵
        glBindVertexArray(VAO);
        glm::mat4 model = glm::mat4(1.0f);
        glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
        glDrawArrays(GL_TRIANGLES, 0, 36);
        glBindVertexArray(0);

        // 使用光源着色器
        lampShader.Use();
        modelLoc = glGetUniformLocation(lampShader.Program, "model");
        viewLoc = glGetUniformLocation(lampShader.Program, "view");
        projLoc = glGetUniformLocation(lampShader.Program, "projection");

        glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
        glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projection));

        model = glm::mat4(1.0f);
        model = glm::translate(model, lightPos);
        model = glm::scale(model, glm::vec3(1.0f)); // Make it a smaller cube
        glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
        glBindVertexArray(lightVAO);
        glDrawArrays(GL_TRIANGLES, 0, 36);
        glBindVertexArray(0);

        // 交换前后缓冲区
        glfwSwapBuffers(window);
    }
    // 释放VAO和VBO的显存
    glDeleteVertexArrays(1, &VAO);
    glDeleteVertexArrays(1, &lightVAO);
    glDeleteBuffers(1, &VBO);
    // 释放glfw的内存
    glfwTerminate();
    return 0;
}

// 根据按键信息移动摄像机
void Do_Movement()
{
    // 如果某个键按下,就执行摄像机对应的方法(更新摄像机的位置)
    if (keys[GLFW_KEY_W])
        camera.ProcessKeyboard(FORWARD, deltaTime);
    if (keys[GLFW_KEY_S])
        camera.ProcessKeyboard(BACKWARD, deltaTime);
    if (keys[GLFW_KEY_A])
        camera.ProcessKeyboard(LEFT, deltaTime);
    if (keys[GLFW_KEY_D])
        camera.ProcessKeyboard(RIGHT, deltaTime);
}

// 按键回调函数
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
    // 如果按下ESE则设置窗口应该关闭
    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
        glfwSetWindowShouldClose(window, GL_TRUE);
    if (key >= 0 && key < 1024)
    {
        // 如果按下某一个键,则设置其keys为true,如果松开则设置回false
        if (action == GLFW_PRESS)
            keys[key] = true;
        else if (action == GLFW_RELEASE)
            keys[key] = false;
    }
}

// 鼠标移动的回调函数
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
    // 第一次进窗口鼠标坐标很大,所以第一次调用函数我们需要把它设置为一个正常的值
    if (firstMouse)
    {
        lastX = xpos;
        lastY = ypos;
        firstMouse = false;
    }

    // 计算鼠标在竖直方向和水平方向的偏移(屏幕坐标系往右x大,但往下是y大,所以运算顺序不同)
    GLfloat xoffset = xpos - lastX;
    GLfloat yoffset = lastY - ypos;  // Reversed since y-coordinates go from bottom to left

    // 更新鼠标的坐标
    lastX = xpos;
    lastY = ypos;

    // 调用摄像机的鼠标移动函数(根据位置偏移更新摄像机方式)
    camera.ProcessMouseMovement(xoffset, yoffset);
}

// 鼠标滚动的回调函数
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
    // 调用摄像机的鼠标滚动函数(根据位置偏移更新摄像机fov视野大小)
    camera.ProcessMouseScroll(yoffset);
}

 如果你想运行以上程序,我为你提供着色器头文件和摄像机头文件的源代码。记得更改程序中创建着色器时的路径。
 着色器头文件(Shader.h):

#ifndef SHADER_H
#define SHADER_H

#include <string>
#include <fstream>
#include <sstream>
#include <iostream>

#include <GL/glew.h>

class Shader
{
public:
    GLuint Program;
    // Constructor generates the shader on the fly
    Shader(const GLchar* vertexPath, const GLchar* fragmentPath)
    {
        // 1. Retrieve the vertex/fragment source code from filePath
        std::string vertexCode;
        std::string fragmentCode;
        std::ifstream vShaderFile;
        std::ifstream fShaderFile;
        // ensures ifstream objects can throw exceptions:
        vShaderFile.exceptions(std::ifstream::badbit);
        fShaderFile.exceptions(std::ifstream::badbit);
        try
        {
            // Open files
            vShaderFile.open(vertexPath);
            fShaderFile.open(fragmentPath);
            std::stringstream vShaderStream, fShaderStream;
            // Read file's buffer contents into streams
            vShaderStream << vShaderFile.rdbuf();
            fShaderStream << fShaderFile.rdbuf();
            // close file handlers
            vShaderFile.close();
            fShaderFile.close();
            // Convert stream into string
            vertexCode = vShaderStream.str();
            fragmentCode = fShaderStream.str();
        }
        catch (std::ifstream::failure e)
        {
            std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;
        }
        const GLchar* vShaderCode = vertexCode.c_str();
        const GLchar* fShaderCode = fragmentCode.c_str();
        // 2. Compile shaders
        GLuint vertex, fragment;
        GLint success;
        GLchar infoLog[512];
        // Vertex Shader
        vertex = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(vertex, 1, &vShaderCode, NULL);
        glCompileShader(vertex);
        // Print compile errors if any
        glGetShaderiv(vertex, GL_COMPILE_STATUS, &success);
        if (!success)
        {
            glGetShaderInfoLog(vertex, 512, NULL, infoLog);
            std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
        }
        // Fragment Shader
        fragment = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(fragment, 1, &fShaderCode, NULL);
        glCompileShader(fragment);
        // Print compile errors if any
        glGetShaderiv(fragment, GL_COMPILE_STATUS, &success);
        if (!success)
        {
            glGetShaderInfoLog(fragment, 512, NULL, infoLog);
            std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
        }
        // Shader Program
        this->Program = glCreateProgram();
        glAttachShader(this->Program, vertex);
        glAttachShader(this->Program, fragment);
        glLinkProgram(this->Program);
        // Print linking errors if any
        glGetProgramiv(this->Program, GL_LINK_STATUS, &success);
        if (!success)
        {
            glGetProgramInfoLog(this->Program, 512, NULL, infoLog);
            std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
        }
        // Delete the shaders as they're linked into our program now and no longer necessery
        glDeleteShader(vertex);
        glDeleteShader(fragment);

    }
    // Uses the current shader
    void Use()
    {
        glUseProgram(this->Program);
    }
};

#endif

 摄像机头文件(Camera.h):

#pragma once

// Std. Includes
#include <vector>

// GL Includes
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

// 常量指定摄像机的移动按键
enum Camera_Movement {
    FORWARD,
    BACKWARD,
    LEFT,
    RIGHT
};

// 这里定义摄像机一些属性的初始值
// 摄像机旋转的两个值
const GLfloat YAW = -90.0f;
const GLfloat PITCH = 0.0f;
// 摄像机移动速度
const GLfloat SPEED = 3.0f;
// 摄像机旋转速度
const GLfloat SENSITIVTY = 0.25f;
// 摄像机角度(关乎远近)
const GLfloat ZOOM = 45.0f;

// An abstract camera class that processes input and calculates the corresponding Eular Angles, Vectors and Matrices for use in OpenGL
class Camera
{
public:
    // 摄像机的位置、方向、上向量、右向量、世界上向量
    glm::vec3 Position;
    glm::vec3 Front;
    glm::vec3 Up;
    glm::vec3 Right;
    glm::vec3 WorldUp;
    // 旋转角度
    GLfloat Yaw;
    GLfloat Pitch;
    // 移动速度和旋转速度
    GLfloat MovementSpeed;
    GLfloat MouseSensitivity;
    // 摄像机的视野(fov)
    GLfloat Zoom;

    // 无参构造函数将使用定义的全局变量值对摄像机对象的所有属性进行初始化
    Camera(glm::vec3 position = glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f), GLfloat yaw = YAW, GLfloat pitch = PITCH) : Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVTY), Zoom(ZOOM)
    {
        this->Position = position;
        this->WorldUp = up;
        this->Yaw = yaw;
        this->Pitch = pitch;
        this->updateCameraVectors();
    }
    // 有参构造函数接收的参数:摄像机位置、摄像机的上向量、摄像机的旋转角度
    Camera(GLfloat posX, GLfloat posY, GLfloat posZ, GLfloat upX, GLfloat upY, GLfloat upZ, GLfloat yaw, GLfloat pitch) : Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVTY), Zoom(ZOOM)
    {
        this->Position = glm::vec3(posX, posY, posZ);
        this->WorldUp = glm::vec3(upX, upY, upZ);
        this->Yaw = yaw;
        this->Pitch = pitch;
        this->updateCameraVectors();
    }
    // 根据摄像机位置、朝向、上向量生成其对应的观察矩阵
    glm::mat4 GetViewMatrix()
    {
        return glm::lookAt(this->Position, this->Position + this->Front, this->Up);
    }
    // 当按下某一个键时,根据按键和帧时间进行移动(移动摄像机的位置)
    void ProcessKeyboard(Camera_Movement direction, GLfloat deltaTime)
    {
        GLfloat velocity = this->MovementSpeed * deltaTime;
        if (direction == FORWARD)
            this->Position += this->Front * velocity;
        if (direction == BACKWARD)
            this->Position -= this->Front * velocity;
        if (direction == LEFT)
            this->Position -= this->Right * velocity;
        if (direction == RIGHT)
            this->Position += this->Right * velocity;
    }
    // 当鼠标移动时,根据横纵坐标差值移动摄像机的朝向,可以设置是否限制角度
    void ProcessMouseMovement(GLfloat xoffset, GLfloat yoffset, GLboolean constrainPitch = true)
    {
        xoffset *= this->MouseSensitivity;
        yoffset *= this->MouseSensitivity;

        this->Yaw += xoffset;
        this->Pitch += yoffset;

        // Make sure that when pitch is out of bounds, screen doesn't get flipped
        if (constrainPitch)
        {
            if (this->Pitch > 89.0f)
                this->Pitch = 89.0f;
            if (this->Pitch < -89.0f)
                this->Pitch = -89.0f;
        }

        // 更新摄像机状态
        this->updateCameraVectors();
    }

    // 根据鼠标滚轮的缩放信息调整视野fov的大小
    void ProcessMouseScroll(GLfloat yoffset)
    {
        // 鼠标滚动数值太大因此缩小一些
        yoffset = yoffset * 0.1f;
        if (this->Zoom >= 1.0f && this->Zoom <= 45.0f)
            this->Zoom -= yoffset;
        if (this->Zoom <= 1.0f)
            this->Zoom = 1.0f;
        if (this->Zoom >= 45.0f)
            this->Zoom = 45.0f;
    }

private:
    // 更新摄像机状态(使用Yaw、Pitch、WorldUp,得到Front、Right、Up)
    void updateCameraVectors()
    {
        // 根据旋转信息Yaw和Pitch计算摄像机的朝向
        glm::vec3 front;
        front.x = cos(glm::radians(this->Yaw)) * cos(glm::radians(this->Pitch));
        front.y = sin(glm::radians(this->Pitch));
        front.z = sin(glm::radians(this->Yaw)) * cos(glm::radians(this->Pitch));
        this->Front = glm::normalize(front);
        // 使用摄像机朝向和世界上向量计算摄像机右朝向
        this->Right = glm::normalize(glm::cross(this->Front, this->WorldUp));
        // 使用摄像机朝向和右向量计算摄像机上向量
        this->Up = glm::normalize(glm::cross(this->Right, this->Front));
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/465125.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

autosar

一 autosar简介 AUTOSAR&#xff0c;汽车开放系统架构&#xff08;AUTomotive Open System Architecture&#xff09;是一家致力于制定汽车电子软件标准的联盟。AUTOSAR是由全球汽车制造商、部件供应商及其他电子、半导体和软件系统公司联合建立&#xff0c;各成员保持开发合作…

QT DAY2

#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);this->setFixedSize(600,600); //设置固定尺寸this->setWindowTitle("汪玉洁大聪明")…

Hadoop学习笔记(一)Hadoop的组成

1. HDFS NameNode用于记录整个数据的存储情况&#xff0c;具体的数据存储在各个Hadoop节点中&#xff0c;每个Hadoop的节点可以称为DataNode。假设Hadoop1到Hadoop100的机器每个都有1T的容量。那么一共就可以存储100T的数据。 NameNode(nn)&#xff1a;存储文件的元数据&…

位运算【巧妙思路、两种常见题型】

这里介绍两种代码中位运算非常常用的操作 n的二进制表示中第k位数——右移操作 &1 例如说&#xff0c;我们需要计算11的第2位数。 11 (1011)2 我们常规思路就是将其转化为二进制数后&#xff0c;直接观察对应位置的值 这里需要注意的是第k位数指的是从右开始的第k位&a…

Linux shell编程 条件语句

条件测试 test命令 测试表达式是否成立&#xff0c;若成立返回0&#xff0c;否则返回其他数值 格式1: test 条件表达式 格式2: [ 条件表达式 ]文件测试 [ 操作符 文件或者目录 ][ -e 1.txt ]#查看1.txt是否存在&#xff0c;存在返回0 echo $? #查看是上一步命令执行结果 0成…

DJ4-3 连续分配存储管理方式

目录 4.3.1 单一连续分配 4.3.2 固定分区分配 1. 分区说明表 2. 内存分配过程 4.3.3 动态分区分配 一、分区分配中数据结构 二、分区分配算法 三、分区分配操作 4.3.4 可重定位分区分配 1. 紧凑 2. 动态重定位 3. 动态重定位分区分配算法 连续分配是指为用户程…

【数据结构】堆(一)

&#x1f61b;作者&#xff1a;日出等日落 &#x1f4d8; 专栏&#xff1a;数据结构 如果我每天都找出所犯错误和坏习惯&#xff0c;那么我身上最糟糕的缺点就会慢慢减少。这种自省后的睡眠将是多么惬意啊。 目录 &#x1f384;堆的概念及结构&#xff1a; &#x1f384;堆的实…

万丈高楼平地起 AI帮你做自己

AI的自我介绍 AI是人工智能&#xff08;Artificial Intelligence&#xff09;的英文缩写&#xff0c;是一种通过计算机技术模拟和延伸人类智能的技术和应用。AI可以被看作是一种智能化的计算机程序或系统&#xff0c;它能够自动地执行一些需要人类智能才能完成的任务&#xf…

JavaEE初阶学习:初识网络

1.网络发展史 1.独立模式 独立模式:计算机之间相互独立&#xff1b; 2.网络互连 随着时代的发展&#xff0c;越来越需要计算机之间互相通信&#xff0c;共享软件和数据&#xff0c;即以多个计算机协同工作来完成业务&#xff0c;就有了网络互连。 网络互连&#xff1a;将多…

除了Figma,再给你介绍10款好用的协同设计软件

组织结构越来越复杂&#xff0c;团队中的每个人都有独特的技能、经验和专业知识。我们怎样才能让团队更好地合作&#xff1f;在这种情况下&#xff0c;协同设计应运而生。 UI的未来是协同设计&#xff01;如果你想把握未来的设计趋势&#xff0c;不妨从使用高效的协同设计软件…

Docker的安装以及本地部署ILLA Builder

1.安装Docker&#xff0c;当前版本V4.18.0 。Docker引擎启动运行之后&#xff0c;效果如下图&#xff08;喜欢暗黑主题&#xff09; Docker启动可能出错&#xff0c;“Docker Desktop requires a newer WSL kernel version.” 如下图所示 解决方法&#xff0c;比较简单&#xf…

测试用例的基本要素和设计方法

作者&#xff1a;爱塔居 专栏&#xff1a;软件测试 作者简介&#xff1a;大三学生&#xff0c;希望同大家一起进步&#xff01; 文章简介&#xff1a;介绍写测试案例的功能需求测试和非功能需求测试和具体方法&#xff1a;判定表、正交表、等价类、边界值等 文章目录 目录 文章…

if条件语句

if条件语句 条件测试 test 测试表达式是否成立&#xff0c;若成立返回0&#xff0c;否则返回其他数值 格式1 &#xff1a;test 条件表达式&#xff1b;格式2 &#xff1a;[ 条件表达式 ] echo $?参数作用-d测试是否为目录 (Directory)-e测试目录或文件是否存在(Exist)-f测…

好物周刊#1:提示工程师养成指南

文章目录 &#x1f388; 项目ddruntiny-vuenetease-recent-profile &#x1f4bb; 软件BobScreenToGifSnipaste &#x1f578;️ 网站BrowserFramedocsmallDimmy.club &#x1f50c; 插件AdGuard[Global Speed: 视频速度控制](https://microsoftedge.microsoft.com/addons/deta…

强化学习-Double DQN、竞争网络结构和Rainbow(第4章)

来源书籍&#xff1a; TENSORFLOW REINFORCEMENT LEARNING QUICK START GUIDE 《TensorFlow强化学习快速入门指南-使用Python动手搭建自学习的智能体》 著者&#xff1a;[美]考希克巴拉克里希南&#xff08;Kaushik Balakrishnan&#xff09; 译者&#xff1a;赵卫东 出版…

【每日一题】leetcode21 - - 合并两个有序链表

文章目录 1.题目描述2.解题思路方法1&#xff1a;方法2&#xff1a; 1.题目描述 题目链接&#xff1a;力扣21&#xff0c;合并两个有序链表 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 2.解题思路 方法1&#xff1a;…

牛客网Verilog刷题——VL38

牛客网Verilog刷题——VL38 题目答案 题目 设计一个自动贩售机&#xff0c;输入货币有三种&#xff0c;为0.5/1/2元&#xff0c;饮料价格是1.5元&#xff0c;要求进行找零&#xff0c;找零只会支付0.5元。需要注意的是&#xff0c;投入的货币会自动经过边沿检测并输出一个在时钟…

【采坑专栏】【错误记录】起系统

doc说明 一级标题分大的&#xff0c;二级标题尽量加&#xff0c;三级标题是具体问题 语法 我的老毛病-易错的 多驱 复制粘贴导致前后一样 管脚约束还是直接选吧 多驱动 Vivado WARNING&#xff1a;Multi-driven net Q with xth driver pin 警告的原因和消除方法 出现这…

注意力机制:基于Yolov8的Triplet注意力模块,即插即用,效果优于cbam、se,涨点明显

论文&#xff1a;https://arxiv.org/pdf/2010.03045.pdf 本文提出了可以有效解决跨维度交互的triplet attention。相较于以往的注意力方法&#xff0c;主要有两个优点&#xff1a; 1.可以忽略的计算开销 2.强调了多维交互而不降低维度的重要性&#xff0c;因此消除了通道和权…

STM32F4_SRAM中调试代码

目录 1. 在RAM中调试代码 2. STM32的三种存储方式 3. STM32的启动方式 4. 实验过程 通过上一节的学习&#xff0c;我们已经了解了SRAM静态存储器&#xff1b; 1. 在RAM中调试代码 一般情况下&#xff0c;我们在MDK中编写工程应用后&#xff0c;调试时都是把程序下载到芯片…