OpenGL光照教程之 光照贴图

news2025/2/23 5:42:44

引言

 前面的教程,我们讨论了让不同的物体拥有各自不同的材质并对光照做出不同的反应的方法。在一个光照场景中,让每个物体拥有和其他物体不同的外观很棒,但是这仍然不能对一个物体的图像输出提供足够多的灵活性。
 前面的教程中我们将一个物体自身作为一个整体为其定义了一个材质,但是现实世界的物体通常不会只有这么一种材质,而是由多种材质组成。想象一辆车:它的外表质地光亮,车窗会部分反射环境,它的轮胎没有specular高光,轮彀却非常闪亮(在洗过之后)。汽车同样有diffuse和ambient颜色,它们在整个车上都不相同;一辆车显示了多种不同的ambient/diffuse颜色。总之,这样一个物体每个部分都有多种材质属性。
 所以,前面的材质系统对于除了最简单的模型以外都是不够的,所以我们需要扩展前面的系统,我们要介绍diffuse和specular贴图。它们允许你对一个物体的diffuse(而对于简洁的ambient成分来说,它们几乎总是是一样的)和specular成分能够有更精确的影响。

漫反射贴图

 我们希望通过某种方式对每个原始像素独立设置diffuse颜色。有可以让我们基于物体原始像素的位置来获取颜色值的系统吗?
 这可能听起来极其相似,坦白来讲我们使用这样的系统已经有一段时间了。听起来很像在一个之前的教程中谈论的纹理,它基本就是一个纹理。我们其实是使用同一个潜在原则下的不同名称:使用一张图片覆盖住物体,以便我们为每个原始像素索引独立颜色值。在光照场景中,通过纹理来呈现一个物体的diffuse颜色,这个做法被称做漫反射贴图(Diffuse texture)(因为3D建模师就是这么称呼这个做法的)。
 为了演示漫反射贴图,我们将会使用下面的图片,它是一个有一圈钢边的木箱:
在这里插入图片描述
 在着色器中使用漫反射贴图和纹理教程介绍的一样。这次我们把纹理以sampler2D类型储存在Material结构体中。我们使用diffuse贴图替代早期定义的vec3类型的diffuse颜色。
 要记住的是sampler2D也叫做模糊类型,这意味着我们不能以某种类型对它实例化,只能用uniform定义它们。如果我们用结构体而不是uniform实例化(就像函数的参数那样),GLSL会抛出奇怪的错误;这同样也适用于其他模糊类型。
 我们也要移除amibient材质颜色向量,因为ambient颜色绝大多数情况等于diffuse颜色,所以不需要分别去储存它:

struct Material
{
    sampler2D diffuse;
    vec3 specular;
    float shininess;
};
...
in vec2 TexCoords;

 如果你非把ambient颜色设置为不同的值不可(不同于diffuse值),你可以继续保留ambient的vec3,但是整个物体的ambient颜色会继续保持不变。为了使每个原始像素得到不同ambient值,你需要对ambient值单独使用另一个纹理。

vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));

 同样,不要忘记把ambient材质的颜色设置为diffuse材质的颜色:

vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));

 更新的顶点数据可以从这里找到。顶点数据现在包括了顶点位置,法线向量和纹理坐标,每个立方体的顶点都有这些属性。让我们更新顶点着色器来接受纹理坐标作为顶点属性,然后发送到片段着色器:

#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normal;
layout (location = 2) in vec2 texCoords;
...
out vec2 TexCoords;

void main()
{
    ...
    TexCoords = texCoords;
}

 要保证更新的顶点属性指针,不仅是VAO匹配新的顶点数据,也要把箱子图片加载为纹理。在绘制箱子之前,我们希望首选纹理单元被赋为material.diffuse这个uniform采样器,并绑定箱子的纹理到这个纹理单元:

glUniform1i(glGetUniformLocation(lightingShader.Program, "material.diffuse"), 0);
...
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, diffuseMap);

 在,使用一个diffuse贴图,我们在细节上再次获得惊人的提升,这次添加到箱子上的光照开始闪光了(名符其实)。你的箱子现在可能看起来像这样:
在这里插入图片描述)

完整代码

 顶点着色器:

#version 330 core
layout( location = 0 ) in vec3 position;
layout( location = 1 ) in vec3 normal;
layout( location = 2 ) in vec2 texCoords;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
uniform mat3 normatr;

out vec3 FragPos;
out vec3 Normal;
out vec2 TexCoords;

void main()
{
	gl_Position = projection * view * model * vec4(position, 1.0f);
	FragPos = vec3(model * vec4(position, 1.0f));
	Normal  = normatr * normal;
	TexCoords = texCoords; 
}

 片段着色器:

#version 330 core
struct Material
{
	sampler2D diffuse;
	vec3 specular;
	float shininess;
};
uniform Material material;

struct Light
{
    vec3 position;
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};
uniform Light light;

// 输入顶点位置和法向量
in vec3 FragPos;
in vec3 Normal;
in vec2 TexCoords;

// 输出顶点的颜色
out vec4 color;

// 顶点本身的颜色
uniform vec3 objectColor;
// 光源的颜色和位置
uniform vec3 lightColor;
uniform vec3 lightPos;
// 观察者的位置(世界坐标)
uniform vec3 viewPos;

void main()
{
    // 环境光
    vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));

    // 漫反射光
    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(lightPos - FragPos);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));

    // 镜面高光
    vec3 viewDir = normalize(viewPos - FragPos);
    vec3 reflectDir = reflect(-lightDir, norm);  
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
    vec3 specular = light.specular * (spec * material.specular);  

    vec3 result = ambient + diffuse + specular;
    color = vec4(result, 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, 1.0f, -0.5f);

// 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\\lightVertexShader.txt", "C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\lightFragmentShader.txt");

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

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

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

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

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

        -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  0.0f, 1.0f,
         0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  1.0f, 1.0f,
         0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  1.0f, 0.0f,
         0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  1.0f, 0.0f,
        -0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  0.0f, 0.0f,
        -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  0.0f, 1.0f
    };

    // 加载图片
    int width, height;
    unsigned char* image;
    image = SOIL_load_image("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Resource\\container2.png", &width, &height, 0, SOIL_LOAD_RGB);

    // 将图片绑定为纹理
    GLuint diffuseMap;
    glGenTextures(1, &diffuseMap);
    glBindTexture(GL_TEXTURE_2D, diffuseMap);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);  
    glGenerateMipmap(GL_TEXTURE_2D);    //为当前绑定的纹理自动生成所有需要的多级渐远纹理
    SOIL_free_image_data(image);
    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_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST_MIPMAP_NEAREST);
    glBindTexture(GL_TEXTURE_2D, 0);

    // 设置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, 8 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));
    glEnableVertexAttribArray(2);
    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, 8 * 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");
        GLint lightPosLoc = glGetUniformLocation(lightingShader.Program, "lightPos");
        GLint viewPosLoc = glGetUniformLocation(lightingShader.Program, "viewPos");
        glUniform3f(objectColorLoc, 1.0f, 0.5f, 0.31f);// 我们所熟悉的珊瑚红
        glUniform3f(lightColorLoc, 1.0f, 1.0f, 1.0f); // 依旧把光源设置为白色
        glUniform3f(lightPosLoc, lightPos.x, lightPos.y, lightPos.z);
        glUniform3f(viewPosLoc, camera.Position.x, camera.Position.y, camera.Position.z);

        // 创建观察矩阵( 只要创建矩阵记得初始化= 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));

        // 激活纹理单元并绑定纹理
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, diffuseMap);

        // 绑定VAO
        glBindVertexArray(VAO);   
        // 计算模型矩阵
        glm::mat4 model = glm::mat4(1.0f);
        glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
        // 根据模型矩阵model计算正规矩阵normatr。inverse取逆和transpose转置 函数使用glm对应函数
        GLuint normalLoc = glGetUniformLocation(lightingShader.Program, "normatr");
        glm::mat3 normatr = (glm::mat3)(glm::transpose(glm::inverse(model)));
        // 向物体的顶点着色器传入正规矩阵normatr
        glUniformMatrix3fv(normalLoc, 1, GL_FALSE, glm::value_ptr(normatr));

        // 设置材质
        GLint matAmbientLoc = glGetUniformLocation(lightingShader.Program, "material.ambient");
        GLint matDiffuseLoc = glGetUniformLocation(lightingShader.Program, "material.diffuse");
        GLint matSpecularLoc = glGetUniformLocation(lightingShader.Program, "material.specular");
        GLint matShineLoc = glGetUniformLocation(lightingShader.Program, "material.shininess");

        glUniform3f(matAmbientLoc, 0.135f, 0.2225f, 0.1575f);
        glUniform3f(matDiffuseLoc, 0.54f, 0.89f, 0.63f);
        glUniform3f(matSpecularLoc, 0.316228f, 0.316228f, 0.316228f);
        glUniform1f(matShineLoc, 32.0f);

        // 设置光源属性
        GLint lightAmbientLoc = glGetUniformLocation(lightingShader.Program, "light.ambient");
        GLint lightDiffuseLoc = glGetUniformLocation(lightingShader.Program, "light.diffuse");
        GLint lightSpecularLoc = glGetUniformLocation(lightingShader.Program, "light.specular");

        glUniform3f(glGetUniformLocation(lightingShader.Program, "light.ambient"), 0.2f, 0.2f, 0.2f);
        glUniform3f(glGetUniformLocation(lightingShader.Program, "light.diffuse"), 0.5f, 0.5f, 0.5f);
        glUniform3f(glGetUniformLocation(lightingShader.Program, "light.specular"), 1.0f, 1.0f, 1.0f);
        // Set material properties
        glUniform3f(glGetUniformLocation(lightingShader.Program, "material.specular"), 0.5f, 0.5f, 0.5f);
        glUniform1f(glGetUniformLocation(lightingShader.Program, "material.shininess"), 64.0f);

        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);
        GLfloat lightX = sin(glfwGetTime());
        GLfloat lightZ = cos(glfwGetTime());
        GLfloat lightY = 1;
        lightPos = glm::vec3(lightX, lightY, lightZ);
        model = glm::translate(model, lightPos);
        model = glm::scale(model, glm::vec3(0.1f)); // 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);
}

镜面贴图

 你可能注意到,specular高光看起来不怎么样,由于我们的物体是个箱子,大部分是木头,我们知道木头是不应该有镜面高光的。我们通过把物体设置specular材质设置为vec3(0.0f)来修正它。但是这样意味着铁边会不再显示镜面高光,我们知道钢铁是会显示一些镜面高光的。我们会想要控制物体部分地显示镜面高光,它带有修改了的亮度。这个问题看起来和diffuse贴图的讨论一样。这是巧合吗?我想不是。
 我们同样用一个纹理贴图,来获得镜面高光。这意味着我们需要生成一个黑白(或者你喜欢的颜色)纹理来定义specular亮度,把它应用到物体的每个部分。下面是一个镜面贴图(Specular Map)的例子:
在这里插入图片描述)
 一个specular高光的亮度可以通过图片中每个纹理的亮度来获得。specular贴图的每个像素可以显示为一个颜色向量,比如:在那里黑色代表颜色向量vec3(0.0f),灰色是vec3(0.5f)。在片段着色器中,我们采样相应的颜色值,把它乘以光的specular亮度。像素越“白”,乘积的结果越大,物体的specualr部分越亮。
 由于箱子几乎是由木头组成,木头作为一个材质不会有镜面高光,整个木头部分的diffuse纹理被用黑色覆盖:黑色部分不会包含任何specular高光。箱子的铁边有一个修改的specular亮度,它自身更容易受到镜面高光影响,木纹部分则不会。
 从技术上来讲,木头也有镜面高光,尽管这个闪亮值很小(更多的光被散射),影响很小,但是为了学习目的,我们可以假装木头不会有任何specular光反射。
 使用Photoshop或Gimp之类的工具,通过将图片进行裁剪,将某部分调整成黑白图样,并调整亮度/对比度的做法,可以非常容易将一个diffuse纹理贴图处理为specular贴图。

镜面贴图采样

 一个specular贴图和其他纹理一样,所以代码和diffuse贴图的代码也相似。确保合理的加载了图片,生成一个纹理对象。由于我们在同样的片段着色器中使用另一个纹理采样器,我们必须为specular贴图使用一个不同的纹理单元(参见纹理),所以在渲染前让我们把它绑定到合适的纹理单元

glUniform1i(glGetUniformLocation(lightingShader.Program, "material.specular"), 1);
...
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, specularMap);

然后更新片段着色器材质属性,接受一个sampler2D作为这个specular部分的类型,而不是vec3:

struct Material
{
    sampler2D diffuse;
    sampler2D specular;
    float shininess;
};

 最后我们希望采样这个specular贴图,来获取原始像素相应的specular亮度:

vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
color = vec4(ambient + diffuse + specular, 1.0f);

 通过使用一个specular贴图我们可以定义极为精细的细节,物体的这个部分会获得闪亮的属性,我们可以设置它们相应的亮度。specular贴图给我们一个附加的高于diffuse贴图的控制权限。
 如果你不想成为主流,你可以在specular贴图里使用颜色,不单单为每个原始像素设置specular亮度,同时也设置specular高光的颜色。从真实角度来说,specular的颜色基本是由光源自身决定的,所以它不会生成真实的图像(这就是为什么图片通常是黑色和白色的:我们只关心亮度)。
 如果你现在运行应用,你可以清晰地看到箱子的材质现在非常类似真实的铁边的木头箱子了。
 运行结果:
在这里插入图片描述)
 使用diffuse和specular贴图,我们可以给相关但简单物体添加一个极为明显的细节。我们可以使用其他纹理贴图,比如法线/bump贴图或者反射贴图,给物体添加更多的细节。但是这些在后面教程才会涉及。把你的箱子给你所有的朋友和家人看,有一天你会很满足,我们的箱子会比现在更漂亮!

练习

 增强环境光照和镜面反射的效果,即增大ambient、diffuse的值。效果如下所示:
在这里插入图片描述)
 尝试在片段着色器中反转镜面贴图(Specular Map)的颜色值,然后木头就会变得反光而边框不会反光了:
在这里插入图片描述)
 使用漫反射纹理(Diffuse Texture)原本的颜色而不是黑白色来创建镜面贴图,并观察,你会发现结果显得并不那么真实了。原因是高光并不会显现出一个光源圈,而是使得高光处的物体更亮(不该这样,因为高光本就是光滑表面映射出光源)。运行结果:
在这里插入图片描述)
 加一个叫做放射光贴图(Emission Map)的东西,即记录每个片段发光值(Emission Value)大小的贴图,发光值是(模拟)物体自身发光(Emit)时可能产生的颜色。这样的话物体就可以忽略环境光自身发光。通常在你看到游戏里某个东西(比如 机器人的眼,或是箱子上的小灯)在发光时,使用的就是放射光贴图。使用这个贴图(作者为 creativesam)作为放射光贴图并使用在箱子上,你就会看到箱子上有会发光的字了。
在这里插入图片描述)
 游戏里的箱子:
在这里插入图片描述)
 由于游戏中箱子上的灯光一定会亮,即不依赖于其他光源的存在,所以将其箱子上发光的地方设为放射光(自身发光)。至于放射光贴图灯以外的地方可以设为和反射光贴图一样。它之所以要独立出来作为放射光贴图,是因为它不像反射光需要依赖其他光源照射,它是自发性的,和环境光本质一样,所以使用环境光实现。

完整代码

 片段着色器:

#version 330 core
struct Material
{
	sampler2D diffuse;
	sampler2D specular;
	sampler2D emission;
	float shininess;
};
uniform Material material;

struct Light
{
    vec3 position;
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};
uniform Light light;

// 输入顶点位置和法向量
in vec3 FragPos;
in vec3 Normal;
in vec2 TexCoords;

// 输出顶点的颜色
out vec4 color;

// 顶点本身的颜色
uniform vec3 objectColor;
// 光源的颜色和位置
uniform vec3 lightColor;
uniform vec3 lightPos;
// 观察者的位置(世界坐标)
uniform vec3 viewPos;

void main()
{
    // 环境光
   vec3 ambient = light.ambient * vec3(texture(material.emission, TexCoords));

    // 漫反射光
    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(lightPos - FragPos);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));

    // 镜面高光
    vec3 viewDir = normalize(viewPos - FragPos);
    vec3 reflectDir = reflect(-lightDir, norm);  
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
    vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));  

    vec3 result = ambient + diffuse + specular;
    color = vec4(result, 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, 1.0f, -0.5f);

// 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\\lightVertexShader.txt", "C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\lightFragmentShader.txt");

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

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

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

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

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

        -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  0.0f, 1.0f,
         0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  1.0f, 1.0f,
         0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  1.0f, 0.0f,
         0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  1.0f, 0.0f,
        -0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  0.0f, 0.0f,
        -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  0.0f, 1.0f
    };

    // 加载图片
    int width, height;
    unsigned char* image;
    image = SOIL_load_image("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Resource\\container2.png", &width, &height, 0, SOIL_LOAD_RGB);

    // 将图片绑定为纹理
    GLuint diffuseMap;
    glGenTextures(1, &diffuseMap);
    glBindTexture(GL_TEXTURE_2D, diffuseMap);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);  
    glGenerateMipmap(GL_TEXTURE_2D);    //为当前绑定的纹理自动生成所有需要的多级渐远纹理
    SOIL_free_image_data(image);
    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_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST_MIPMAP_NEAREST);
    glBindTexture(GL_TEXTURE_2D, 0);

    image = SOIL_load_image("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Resource\\container2_specular.png", &width, &height, 0, SOIL_LOAD_RGB);
    GLuint specularMap;
    glGenTextures(1, &specularMap);
    glBindTexture(GL_TEXTURE_2D, specularMap);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
    glGenerateMipmap(GL_TEXTURE_2D);    //为当前绑定的纹理自动生成所有需要的多级渐远纹理
    SOIL_free_image_data(image);
    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_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST_MIPMAP_NEAREST);
    glBindTexture(GL_TEXTURE_2D, 0);

    image = SOIL_load_image("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Resource\\matrix.jpg", &width, &height, 0, SOIL_LOAD_RGB);
    GLuint emissionMap;
    glGenTextures(1, &emissionMap);
    glBindTexture(GL_TEXTURE_2D, emissionMap);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
    glGenerateMipmap(GL_TEXTURE_2D);    //为当前绑定的纹理自动生成所有需要的多级渐远纹理
    SOIL_free_image_data(image);
    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_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST_MIPMAP_NEAREST);
    glBindTexture(GL_TEXTURE_2D, 0);

    // 设置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, 8 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));
    glEnableVertexAttribArray(2);
    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, 8 * 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");
        GLint lightPosLoc = glGetUniformLocation(lightingShader.Program, "lightPos");
        GLint viewPosLoc = glGetUniformLocation(lightingShader.Program, "viewPos");
        glUniform3f(objectColorLoc, 1.0f, 0.5f, 0.31f);// 我们所熟悉的珊瑚红
        glUniform3f(lightColorLoc, 1.0f, 1.0f, 1.0f); // 依旧把光源设置为白色
        glUniform3f(lightPosLoc, lightPos.x, lightPos.y, lightPos.z);
        glUniform3f(viewPosLoc, camera.Position.x, camera.Position.y, camera.Position.z);

        // 创建观察矩阵( 只要创建矩阵记得初始化= 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));

        // 绑定VAO
        glBindVertexArray(VAO);   
        // 计算模型矩阵
        glm::mat4 model = glm::mat4(1.0f);
        glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
        // 根据模型矩阵model计算正规矩阵normatr。inverse取逆和transpose转置 函数使用glm对应函数
        GLuint normalLoc = glGetUniformLocation(lightingShader.Program, "normatr");
        glm::mat3 normatr = (glm::mat3)(glm::transpose(glm::inverse(model)));
        // 向物体的顶点着色器传入正规矩阵normatr
        glUniformMatrix3fv(normalLoc, 1, GL_FALSE, glm::value_ptr(normatr));

        // 设置材质
        GLint matAmbientLoc = glGetUniformLocation(lightingShader.Program, "material.ambient");
        GLint matDiffuseLoc = glGetUniformLocation(lightingShader.Program, "material.diffuse");
        GLint matSpecularLoc = glGetUniformLocation(lightingShader.Program, "material.specular");
        GLint matShineLoc = glGetUniformLocation(lightingShader.Program, "material.shininess");

        glUniform3f(matAmbientLoc, 0.135f, 0.2225f, 0.1575f);
        glUniform3f(matDiffuseLoc, 0.54f, 0.89f, 0.63f);
        glUniform3f(matSpecularLoc, 0.316228f, 0.316228f, 0.316228f);
        glUniform1f(matShineLoc, 32.0f);

        // 设置光源属性
        GLint lightAmbientLoc = glGetUniformLocation(lightingShader.Program, "light.ambient");
        GLint lightDiffuseLoc = glGetUniformLocation(lightingShader.Program, "light.diffuse");
        GLint lightSpecularLoc = glGetUniformLocation(lightingShader.Program, "light.specular");

        // 激活纹理单元并绑定纹理
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, diffuseMap);
        glUniform1i(glGetUniformLocation(lightingShader.Program, "material.specular"), 1);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, specularMap);
        glUniform1i(glGetUniformLocation(lightingShader.Program, "material.emission"), 2);
        glActiveTexture(GL_TEXTURE2);
        glBindTexture(GL_TEXTURE_2D, emissionMap);

        glUniform3f(glGetUniformLocation(lightingShader.Program, "light.ambient"), 0.3f, 0.3f, 0.3f);
        glUniform3f(glGetUniformLocation(lightingShader.Program, "light.diffuse"), 0.7f, 0.7f, 0.7f);
        glUniform3f(glGetUniformLocation(lightingShader.Program, "light.specular"), 1.0f, 1.0f, 1.0f);
        // Set material properties
        glUniform3f(glGetUniformLocation(lightingShader.Program, "material.specular"), 0.5f, 0.5f, 0.5f);
        glUniform1f(glGetUniformLocation(lightingShader.Program, "material.shininess"), 64.0f);

        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);
        GLfloat lightX = sin(glfwGetTime());
        GLfloat lightZ = cos(glfwGetTime());
        GLfloat lightY = 1;
        lightPos = glm::vec3(lightX, lightY, lightZ);
        model = glm::translate(model, lightPos);
        model = glm::scale(model, glm::vec3(0.1f)); // 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);
}

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

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

相关文章

关于FPGA基础知识 LCMXO2-7000HC-4TG144C MachXO2系列 FPGA可编程逻辑简介

关于FPGA基础知识 LCMXO2-7000HC-4TG144C lattice莱迪斯深力科 MachXO2系列 FPGA可编程逻辑简介 FPGA基础知识&#xff1a;FPGA是英文Field&#xff0d;Programmable Gate Array的缩写&#xff0c;即现场可编程门阵列&#xff0c;它是在PAL、GAL、CPLD等可编程器件的基础上进一…

【测评】飞凌i.MX8MM开发板,为你带来卓越的影音体验

来源&#xff1a;飞凌嵌入式官网 OKMX8MM-C是飞凌基于NXP公司i.MX8M Mini 四核64位处理器所设计的一款开发板&#xff0c;主频最高达1.8GHz&#xff0c;可提供多种音频接口&#xff0c;包括I2S、AC97、TDM、PDM和SPDIF。在性能和算力都大幅提高的同时&#xff0c;系统的运行也更…

idea配置Tomcat服务和创建javaweb项目

前言 我的idea版本是Ultimate 2022.3 步骤 1.先创建一个空的java项目 2.点击project structure 然后点击moudle – > dependcies —>点&#xff0b; 选择JArs or … 找到你安装的tomcat里面的lin依次添加jsp-api.jar、servlet-api.jar 右击项目然后点add Framework s…

SPSS如何进行生存分析之案例实训?

文章目录 0.引言1.寿命表分析2.Kaplan-Meier分析方法3.Cox回归分析 0.引言 因科研等多场景需要进行绘图处理&#xff0c;笔者对SPSS进行了学习&#xff0c;本文通过《SPSS统计分析从入门到精通》及其配套素材结合网上相关资料进行学习笔记总结&#xff0c;本文对生存分析进行阐…

JVM虚拟机中的类加载机制和双亲委派模型

目录 虚拟机的类加载机制 名词解释 类加载的时机 类加载的过程 1.加载&#xff08;Loading&#xff09;阶段 非数组类型的加载阶段与数组类型区别 2.验证&#xff08;Verification&#xff09;阶段 1&#xff09;文件格式验证 2&#xff09;元数据验证 3&#xff09;…

深入理解Java虚拟机——对象的创建和内存布局

1.对象的创建 首先声明这一篇博客是在HotSpot虚拟机的前提之下记录的。主要参考书籍来源于周志明老师的《深入理解JVM虚拟机》。 在语言层面&#xff0c;创建对象仅仅是使用一个new关键字。但是从虚拟机的角度来看&#xff0c;创建一个对象一共有5个步骤&#xff1a;类加载检查…

排序大师:探秘C语言中神奇的qsort库函数

本篇文章中会详细讲解C语言中的qsort库函数。我准备分2个方面来讲&#xff1a; qsort如何使用。模拟实现qsort的效果。&#xff08;注意&#xff1a;只是用冒泡排序的思想实现类似的效果&#xff0c;实际qsort的底层采用的是快速排序的思想。&#xff09; 如何使用 先来看看q…

反调试与反反调试

参考文本 (190条消息) C 反反调试&#xff08;NtQueryInformationProcess&#xff09;_(-: LYSM :-)的博客-CSDN博客 Windows 平台反调试相关的技术方法总结—part 2 - 先知社区 C/C MinHook 库的使用技巧 - lyshark - 博客园 (cnblogs.com) (177条消息) C 反反调试&#x…

C结构简单而不失强大的表格

2023年了&#xff0c;想必已经不会有人对嵌入式开发中“数据结构&#xff08;Data Structure&#xff09;”的作用产生疑问了吧&#xff1f;无论你是否心存疑惑&#xff0c;本文都将给你一个完全不同的视角。 每每说起数据结构&#xff0c;很多人脑海里复现的一定是以下的内容&…

unity中用异步的whenAny,实现:当点击铲子任一部件,拾取整个铲子

一、铲子的组成 铲子包含很多部件组成&#xff0c;当拾取铲子的时候&#xff0c;只要点击铲子的任意一个部件就可以。 如图&#xff0c;点击【木柄】、【螺母】、【铁铲】都可以拾取该物体。 &#xff08;1&#xff09;打开高亮 &#xff08;2&#xff09;等待土铲被点击&…

为什么要通过API接口来获取数据

API接口&#xff08;应用编程接口 application/programming接口&#xff09;&#xff0c;准许应用程序通过定义的接口标准来访问另一个应用程序或服务的编程方式。简单来说&#xff0c;API就是两个软件或系统之间的通信语言或接口。 在当今的互联网时代&#xff0c;数据无处不…

Geospatial和Redis事务操作

一、Geospatial 1.简介 基于位置信息服务 (Location-Based Service,LBS) 的应用。 Redis3.2 版本后增加了对 GEO 类型的支持。主要来维护元素的经纬度。redis 基于这种类型&#xff0c;提供了经纬度设置、查询、范围查询、距离查询、经纬度hash等一些相关操作。 2.GEO底层结构…

DataEase 数据源插件分享 - 时序数据库 InfluxDB

前言 InfluxDB 是一个时序数据库&#xff0c;使用的是非标准的 SQL 语法&#xff0c;我使用 DataEase 的插件扩展机制开发了此数据源插件&#xff0c;在这里共享出来&#xff0c;想用的朋友可以下载安装使用。 插件包下载地址 https://north-dataease-1251506367.cos.ap-bei…

Centos 7.X WordPress博客网站详细教程 FTP/PHP/mysql/Apache环境构建

此教程适用于服务器系统为centos 7.x&#xff0c;php安装版本为7.4&#xff0c;mysql安装本部为5.7. 一、mysql安装 1.1 安装三个工具 yum install wget yum install vim yum install unzip 1.2 下载并安装msql 在线下载安装包&#xff1a; wget https://dev.mysql.com/g…

JZS-7/221静态可调延时中间继电器 JOSEF约瑟

JZS-7/2系列静态可调延时中间继电器品牌&#xff1a;JOSEF约瑟型号&#xff1a;JZS-7/2名称&#xff1a;静态可调延时中间继电器额定电压&#xff1a;48380V触点容量&#xff1a;10A/250V返回系数&#xff1a;≤15%延时范围&#xff1a;15ms3s15ms5s15ms10s JZS-7/2系列静态可…

SQL中使用的运算符号详解

文章目录 前言1. 算术运算符加法与减法运算符乘法与除法运算符求模&#xff08;求余&#xff09;运算符 2. 比较运算符1&#xff0e;等号运算符2&#xff0e;安全等于运算符3&#xff0e;不等于运算符4. 空运算符5. 非空运算符6. 最小值运算符7. 最大值运算符8. BETWEEN AND运算…

射频功率放大器(RF PA)线性化技术及分类介绍

基本概念 射频功率放大器(RF PA)是发射系统中的主要部分&#xff0c;其重要性不言而喻。在发射机的前级电路中&#xff0c;调制振荡电路所产生的射频信号功率很小&#xff0c;需要经过一系列的放大&#xff08;缓冲级、中间放大级、末级功率放大级&#xff09;获得足够的射频功…

Zabbix“专家坐诊”第190期问答汇总

问题一 Q&#xff1a;请问为啥用拓扑图监控交换机接口流量&#xff0c;获取不到数据&#xff0c;显示未知&#xff0c;键值也没错 &#xff0c;最新数据也能看到&#xff0c;是什么原因呢&#xff1f; A&#xff1a;把第一个值改成主机名。 问题二 Q&#xff1a;请问下zabbi…

如何进行AI换脸,AI换脸从 “0“ 到 “1” 详细教程 ——从配置环境开始

后续文章读起来可能会影响观看可以前往鄙人博客查看&#xff1a;http://www.anyuer.club/?id199 前言&#xff1a; 本人吃计算机这口饭的&#xff0c;说实话AI换脸很火的时候自己却没碰&#xff0c;挺吃亏的&#xff0c;最近时间比较充裕&#xff0c;整理了一下AI换脸的一个简…

Pyecharts 输出到 html 白屏?终极解决方案来了。

问题起因 公司内部网络&#xff0c;想要做个饼图输出到 html 。 找了教程&#xff1a;https://pyecharts.org/#/zh-cn/quickstart 我看教程写得这么规范&#xff0c;直接 CtrlC&#xff0c;CtrlV&#xff0c;百度来的代码怎么可能会有问题嘛&#xff01; 人生处处有惊喜。 样…