此篇博文为个人OpenGL学习笔记,内容参考LearnOpenGL CNGLFW官方文档OpenGL 4 Reference Pages、OpenGL编程指南(原书第9版)

受计算机计算能力限制无法模拟现实世界极其复杂的光照,所以OpenGL的光照使用的是简化的模型。光照模型都是基于对光的物理特性的理解。其中一个模型被称为冯氏光照模型(Phong Lighting Model)。

冯氏光照模型主要结构由3个分量组成:环境(Ambient)、漫反射(Diffuse)和镜面(Specular)光照

  • 环境光照(Ambient Lighting):模拟即使在黑暗的情况下仍然存在的微弱光亮。
  • 漫反射光照(Diffuse Lighting):模拟光源对物体的方向性影响(Directional Impact)。物体的某一部分越是正对着光源它就会越亮。
  • 镜面光照(Specular Lighting):模拟有光泽物体上面出现的亮点。镜面光照的颜色相比于物体的颜色会更倾向于光的颜色。

光通常都不是来自同一个光源而是来自于周围分散的很多光源(即使它们可能并不是那么显而易见)。光的一个属性是它可以向很多方向发散并反弹从而能够达到不是直接临近的点。所以光能够在其它的表面上反射,对一个物体产生间接的影响。考虑到这种情况的算法叫做全局照明(Global Illumination)算法,但由于这种算法开销高昂且极其复杂所以首先使用一个简化的全局照明模型——环境光照。它使用一个很小的常量(光照)颜色添加到物体片段的最终颜色中,这样即便场景中没有直接的光源也能看起来存在有一些发散的光。

将环境光照添加到场景(light_shader.fs):

#version 460 core
out vec4 color;

uniform vec3 object_color;
uniform vec3 light_color;

void main() {
    float ambient_strength = 0.1;
    vec3 ambient = ambient_strength * light_color;
    color = vec4(ambient * object_color, 1.0f);
}

运行效果:

运行效果

漫反射光照可以使物体上与光纤方向越接近的片段能从光源处获得更多的亮度。

左上方光源发出的光线落在物体的一个片段上

上图需要测量光线以什么角度照射到片段。如果光线垂直于物体表面这束光对物体的影响会最大化。使用法向量(Normal vector)测量光纤和片段的角度,法向量是垂直于片段表面的一个向量。两个向量之间的角度可以使用点积进行计算。

为了得到向量夹角的余弦值使用的是单位向量,所以需要确保所有的向量都是标准化的,否则点积的返回值就不是余弦值。

法向量是一个垂直于定点表面的(单位)向量。由于顶点本身并没有表面,所以利用它周围的顶点来计算出这个表面。可以使用叉乘对立方体所有的顶点计算法向量的小技巧,但由于3D立方体形状并不复杂所以可以直接地把法线数据手工添加到顶点数据中:

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

更新光照地着色器:

#version 460 core
layout (location = 0) in vec3 v_pos;
layout (location = 1) in vec3 v_normal;
...

向每个顶点添加法向量并更新顶点着色器之后更新顶点属性指针。由于光源和物体使用相同的顶点数据但灯的着色器并不使用新添加的法向量,所以不需要更新灯的着色器或者是属性的配置,但必须至少修改顶点属性指针以适应新的顶点数组的大小:

unsigned int vbo;
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
unsigned int cube_vao;
glGenVertexArrays(1, &cube_vao);
glBindVertexArray(cube_vao);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float));
unsigned int light_vao;
glGenVertexArrays(1, &light_vao);
glBindVertexArray(light_vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

修改步长即可达到只使用定点数据中前三个float而忽略后三个float的目的。

实际上在不能完全利用物体的顶点数据情况下利用物体的顶点数据比给灯专门分配一个新的VBO更高效。

所有光照的计算都在片段着色器中进行,所以需要将法向量由顶点着色器传递到片段着色器:

...
layout (location = 1) in vec3 v_normal;

...
out vec3 f_normal;

...

void main() {
    ...
    normal = v_normal;
    gl_Position = projection * view * model * vec4(v_pos, 1.0f);
}

并在片段着色器中接收法向量变量:

...

in vec3 normal;

...

void main() {
    ...
}

现在每个顶点都有了它的法向量但仍需要光源的位置向量和片段的位置向量,由于光源的位置是一个静态变量所以在片段着色器中将它声明为uniform:

uniform vec3 lamp_pos;

之后在渲染循环中(循环外也可以因为光源位置不会改变)更新此uniform变量:

light_shader.SetVec3("lamp_pos", lamp_pos);

计算世界空间中片段的位置。在顶点着色器中将顶点位置乘以模型矩阵将其变换到世界空间坐标:

...

out vec3 f_pos;
out vec3 f_normal;

...

void main() {
    f_pos = vec3(model * vec4(v_pos, 1.0));
    f_normal = v_normal;
    gl_Position = projection * view * model * vec4(v_pos, 1.0f);
}

在片段着色器中添加位置输入变量:

in vec3 f_pos;

光的方向向量是光源位置向量与片段位置向量之间的向量差,而需要确保所有相关向量最后都转化为单位向量,所以标准化法线和最终方向向量:

vec3 norm = normalize(f_normal);
vec3 light_dir = normalize(lamp_pos - f_pos);

在计算光照时通常只关心方向而不关心模长或位置。所以几乎所有的计算都使用单位向量完成,这简化了大部分的计算。

对norm和light_dir向量计算点积,计算光源对当前片段实际的漫反射影响。结果值再乘以光的颜色得到漫反射分量。两个向量之间角度越大漫反射分量就会越小:

float diff = max(dot(norm, light_dir), 0.0f);
vec3 diffuse = diff * light_color;

如果两个向量之间的角度大于90°点积的结果就是负数,所以使用max函数将其与0.0f返回最大值。

将环境光分量与漫反射分量相加后乘以物体的颜色获得片段最后输出的颜色:

vec3 result = (ambient + diffuse) * object_color;
color = vec4(result, 1.0f);

完整代码:

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <iostream>
#include "shader.h"
#include "camera.h"

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

const unsigned int window_width = 800;
const unsigned int window_height = 600;

Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
float mouse_last_x = window_width / 2.0f;
float mouse_last_y = window_height / 2.0f;
bool first_mouse = true;

float delta_time = 0.0f;
float last_frame = 0.0f;

glm::vec3 lamp_pos(1.2f, 1.0f, 2.0f);

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

void ProcessInput(GLFWwindow* window) {
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
        glfwSetWindowShouldClose(window, true);
    }
    if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) {
        camera.ProcessKeyboard(FORWARD, delta_time);
    }
    if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) {
        camera.ProcessKeyboard(BACKWARD, delta_time);
    }
    if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) {
        camera.ProcessKeyboard(LEFT, delta_time);
    }
    if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) {
        camera.ProcessKeyboard(RIGHT, delta_time);
    }
}

void MouseCallback(GLFWwindow* window, double x_pos, double y_pos) {
    if (first_mouse) {
        mouse_last_x = x_pos;
        mouse_last_y = y_pos;
        first_mouse = false;
    }
    float x_offset = x_pos - mouse_last_x;
    float y_offset = mouse_last_y - y_pos;
    mouse_last_x = x_pos;
    mouse_last_y = y_pos;
    camera.ProcessMouseMovement(x_offset, y_offset);
}

void ScrollCallback(GLFWwindow* window, double x_offset, double y_offset) {
    camera.ProcessMouseScroll(y_offset);
}

int main() {
    if (!glfwInit()) {
        std::cout << "Failed to initialize GLFW" << std::endl;
        return -1;
    }
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    GLFWwindow* window = glfwCreateWindow(window_width, window_height, "Hello OpenGL", NULL, NULL);
    if (window == NULL) {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, FramebufferSizeCallback);
    glfwSetCursorPosCallback(window, MouseCallback);
    glfwSetScrollCallback(window, ScrollCallback);
    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 light_shader("light_shader.vs", "light_shader.fs");
    Shader lamp_shader("lamp_shader.vs", "lamp_shader.fs");
    float vertices[] = {
        -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
        0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
        0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
        0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
        -0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
        0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
        0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
        0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
        -0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
        -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,
        -0.5f,  0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
        -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
        -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
        -0.5f, -0.5f,  0.5f, -1.0f,  0.0f,  0.0f,
        -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,
        0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,
        0.5f,  0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
        0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
        0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
        0.5f, -0.5f,  0.5f,  1.0f,  0.0f,  0.0f,
        0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,
        0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,
        0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
        0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,
        -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,
        0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,
        0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
        0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
        -0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
        -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f
    };
    unsigned int vbo;
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    unsigned int cube_vao;
    glGenVertexArrays(1, &cube_vao);
    glBindVertexArray(cube_vao);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);
    unsigned int light_vao;
    glGenVertexArrays(1, &light_vao);
    glBindVertexArray(light_vao);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    while (!glfwWindowShouldClose(window)) {
        float current_frame = glfwGetTime();
        delta_time = current_frame - last_frame;
        last_frame = current_frame;
        ProcessInput(window);
        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        light_shader.Use();
        light_shader.SetVec3("object_color", 1.0f, 0.5f, 0.31f);
        light_shader.SetVec3("light_color", 1.0f, 1.0f, 1.0f);
        light_shader.SetVec3("lamp_pos", lamp_pos);
        glm::mat4 projection = glm::perspective(glm::radians(camera.fov), (float)window_width / (float)window_height, 0.1f, 100.0f);
        glm::mat4 view = camera.GetViewMatrix();
        light_shader.SetMat4("projection", projection);
        light_shader.SetMat4("view", view);
        glm::mat4 model = glm::mat4(1.0f);
        light_shader.SetMat4("model", model);
        glBindVertexArray(cube_vao);
        glDrawArrays(GL_TRIANGLES, 0, 36);
        lamp_shader.Use();
        lamp_shader.SetMat4("projection", projection);
        lamp_shader.SetMat4("view", view);
        model = glm::mat4(1.0f);
        model = glm::translate(model, lamp_pos);
        model = glm::scale(model, glm::vec3(0.2f));
        lamp_shader.SetMat4("model", model);
        glBindVertexArray(light_vao);
        glDrawArrays(GL_TRIANGLES, 0, 36);
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    glDeleteVertexArrays(1, &cube_vao);
    glDeleteVertexArrays(1, &light_vao);
    glDeleteBuffers(1, &vbo);
    glfwTerminate();
    return 0;
}

运行效果:

运行效果

片段着色器里的计算都是再世界空间坐标中进行,所以需要把法向量也转换为世界空间坐标,但并不能通过乘以一个模型矩阵进行转换。

法向量只是一个方向向量,不能表达空间中的特定位置。同时,法向量没有齐次坐标(顶点坐标中的w分量)。所以位移不应该影响到法向量。因此如果把法向量乘以一个模型矩阵,就要从矩阵中移除位移部分,只选用模型矩阵左上角$3 \times 3$的矩阵(也可以把法向量的w分量设置为0再乘以$4 \times 4$矩阵,这也可以移除位移)。

如果模型矩阵执行了不等比缩放,顶点的改变会导致法向量不再垂直于表面,所以不能用模型矩阵变换法向量。

不等比缩放的模型矩阵对法向量的影响

每当应用一个不等比缩放法向量将不在垂直于对应的表面(等比缩放不会破坏法线,因为法线的方向没被改变仅仅改变了法线的长度,而这很容易通过标准化来修复),所以光照效果会被破坏。

可以使用一个为法向量专门定制的模型矩阵来修复这个行为。这个矩阵是法线矩阵(Normal Matrix),它使用一些线性代数的操作移除对法向量错误缩放的影响。法线矩阵被定义为「模型矩阵左上角的逆矩阵的转置矩阵」。

在顶点着色器中可以使用inverse()函数和transpose()函数生成法线矩阵,这两个函数对所有类型矩阵都有效。要把被处理过的矩阵强制转换为$3 \times 3$矩阵以保证它失去了位移属性以及能够乘以vec3的法向量。

f_normal = mat3(transpose(inverse(model))) * v_normal;

如果进行不等比缩放,使用法线矩阵乘以法向量是必不可少的操作。

即使对于着色器逆矩阵也是一个开销很大的运算,因此需要尽可能避免在着色器中进行逆矩阵运算(它必须为场景中的每个顶点都进行这样的处理)。所以对于一个对效率有要求的应用来说在绘制之前最好用CPU计算出法线矩阵后通过uniform变量把值传递给着色器。

和漫反射光照一样镜面光照也是依据光的方向向量和物体的法向量来决定的,但它同样取决于观察方向(摄像机方向)。镜面光照基于光的反射特性,就像无论从哪里去看一面镜子所反射的光镜面光照都会达到最大化。

镜面反射由光的方向向量、物体的法向量以及观察方向决定

通过光的方向向量和物体法向量计算反射光的方向向量,再计算反射光的方向向量与观察方向向量角度差,若夹角越小则镜面光的影响越大,用它乘以光源的颜色再将它加上环境光和漫反射分量。

将摄像机位置坐标传给片段着色器:

...

...

uniform vec3 view_pos;
...

void main() {
    ...
}
light_shader.SetVec3("view_pos", camera.position);

定义一个镜面强度(Specular Intensity)变量,给镜面高光一个亮度(设置适中,不要过曝):

float specular_strength = 0.5f;

计算视线方向向量和光反射方向向量:

vec3 view_dir = normalize(view_pos - f_pos);
vec3 reflect_dir = reflect(-light_dir, norm);

reflect()函数要求第一个向量是从光源指向片段位置的向量,但light_dir向量正好与其相反所以对其进行取反再计算。

计算镜面分量:

float spec = pow(max(dot(view_dir, reflect_dir), 0.0f), 32);
vec3 specular = specular_strength * spec * light_color;

首先计算视线方向与反射方向的点积,然后取它的32次幂。32为高光的反光度。一个物体的反光度越高反射光的能力越强,散射越少,高光点越小。

不同反光度的视觉效果影响

将镜面光分量加到环境光分量和漫反射分量里再用结果乘以物体的颜色:

vec3 result = (ambient + diffuse + specular) * object_color;
color = vec4(result, 1.0f);

完整代码:

light_shader.vs:

#version 460 core
layout (location = 0) in vec3 v_pos;
layout (location = 1) in vec3 v_normal;

out vec3 f_pos;
out vec3 f_normal;

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

void main() {
    f_pos = vec3(model * vec4(v_pos, 1.0));
    f_normal = mat3(transpose(inverse(model))) * v_normal;
    gl_Position = projection * view * model * vec4(v_pos, 1.0f);
}

light_shader.fs:

#version 460 core
out vec4 color;

in vec3 f_normal;
in vec3 f_pos;

uniform vec3 lamp_pos;
uniform vec3 view_pos;
uniform vec3 object_color;
uniform vec3 light_color;

void main() {
    float ambient_strength = 0.1f;
    vec3 ambient = ambient_strength * light_color;
    vec3 norm = normalize(f_normal);
    vec3 light_dir = normalize(lamp_pos - f_pos);
    float diff = max(dot(norm, light_dir), 0.0f);
    vec3 diffuse = diff * light_color;
    float specular_strength = 0.5f;
    vec3 view_dir = normalize(view_pos - f_pos);
    vec3 reflect_dir = reflect(-light_dir, norm);
    float spec = pow(max(dot(view_dir, reflect_dir), 0.0f), 32);
    vec3 specular = specular_strength * spec * light_color;
    vec3 result = (ambient + diffuse + specular) * object_color;
    color = vec4(result, 1.0f);
}

lamp_shader.vs:

#version 460 core
layout (location = 0) in vec3 v_pos;

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

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

lamp_shder.fs:

#version 460 core
out vec4 color;

void main() {
    color = vec4(1.0f);
}

main.cpp:

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <iostream>
#include "shader.h"
#include "camera.h"

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

const unsigned int window_width = 800;
const unsigned int window_height = 600;

Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
float mouse_last_x = window_width / 2.0f;
float mouse_last_y = window_height / 2.0f;
bool first_mouse = true;

float delta_time = 0.0f;
float last_frame = 0.0f;

glm::vec3 lamp_pos(1.2f, 1.0f, 2.0f);

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

void ProcessInput(GLFWwindow* window) {
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
        glfwSetWindowShouldClose(window, true);
    }
    if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) {
        camera.ProcessKeyboard(FORWARD, delta_time);
    }
    if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) {
        camera.ProcessKeyboard(BACKWARD, delta_time);
    }
    if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) {
        camera.ProcessKeyboard(LEFT, delta_time);
    }
    if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) {
        camera.ProcessKeyboard(RIGHT, delta_time);
    }
}

void MouseCallback(GLFWwindow* window, double x_pos, double y_pos) {
    if (first_mouse) {
        mouse_last_x = x_pos;
        mouse_last_y = y_pos;
        first_mouse = false;
    }
    float x_offset = x_pos - mouse_last_x;
    float y_offset = mouse_last_y - y_pos;
    mouse_last_x = x_pos;
    mouse_last_y = y_pos;
    camera.ProcessMouseMovement(x_offset, y_offset);
}

void ScrollCallback(GLFWwindow* window, double x_offset, double y_offset) {
    camera.ProcessMouseScroll(y_offset);
}

int main() {
    if (!glfwInit()) {
        std::cout << "Failed to initialize GLFW" << std::endl;
        return -1;
    }
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    GLFWwindow* window = glfwCreateWindow(window_width, window_height, "Hello OpenGL", NULL, NULL);
    if (window == NULL) {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, FramebufferSizeCallback);
    glfwSetCursorPosCallback(window, MouseCallback);
    glfwSetScrollCallback(window, ScrollCallback);
    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 light_shader("light_shader.vs", "light_shader.fs");
    Shader lamp_shader("lamp_shader.vs", "lamp_shader.fs");
    float vertices[] = {
        -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
        0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
        0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
        0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
        -0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
        0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
        0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
        0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
        -0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,
        -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,
        -0.5f,  0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
        -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
        -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
        -0.5f, -0.5f,  0.5f, -1.0f,  0.0f,  0.0f,
        -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,
        0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,
        0.5f,  0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
        0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
        0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
        0.5f, -0.5f,  0.5f,  1.0f,  0.0f,  0.0f,
        0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,
        0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,
        0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
        0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,
        -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,
        0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,
        0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
        0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
        -0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
        -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f
    };
    unsigned int vbo;
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    unsigned int cube_vao;
    glGenVertexArrays(1, &cube_vao);
    glBindVertexArray(cube_vao);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);
    unsigned int light_vao;
    glGenVertexArrays(1, &light_vao);
    glBindVertexArray(light_vao);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    while (!glfwWindowShouldClose(window)) {
        float current_frame = glfwGetTime();
        delta_time = current_frame - last_frame;
        last_frame = current_frame;
        ProcessInput(window);
        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        light_shader.Use();
        light_shader.SetVec3("object_color", 1.0f, 0.5f, 0.31f);
        light_shader.SetVec3("light_color", 1.0f, 1.0f, 1.0f);
        light_shader.SetVec3("lamp_pos", lamp_pos);
        light_shader.SetVec3("view_pos", camera.position);
        glm::mat4 projection = glm::perspective(glm::radians(camera.fov), (float)window_width / (float)window_height, 0.1f, 100.0f);
        glm::mat4 view = camera.GetViewMatrix();
        light_shader.SetMat4("projection", projection);
        light_shader.SetMat4("view", view);
        glm::mat4 model = glm::mat4(1.0f);
        light_shader.SetMat4("model", model);
        glBindVertexArray(cube_vao);
        glDrawArrays(GL_TRIANGLES, 0, 36);
        lamp_shader.Use();
        lamp_shader.SetMat4("projection", projection);
        lamp_shader.SetMat4("view", view);
        model = glm::mat4(1.0f);
        model = glm::translate(model, lamp_pos);
        model = glm::scale(model, glm::vec3(0.2f));
        lamp_shader.SetMat4("model", model);
        glBindVertexArray(light_vao);
        glDrawArrays(GL_TRIANGLES, 0, 36);
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    glDeleteVertexArrays(1, &cube_vao);
    glDeleteVertexArrays(1, &light_vao);
    glDeleteBuffers(1, &vbo);
    glfwTerminate();
    return 0;
}

运行效果:

运行效果

Last modification:March 18th, 2020 at 10:50 pm
如果觉得我的文章对你有用,请随意赞赏