一、光照模型概述

我们实现的是Phong光照模型,包含三个分量:

分量 作用 视觉效果
环境光 Ambient 模拟间接照明 让物体暗部不至于全黑
漫反射 Diffuse 模拟粗糙表面反射 面向光源的一面更亮
镜面高光 Specular 模拟光滑表面反光 产生高光亮点

二、顶点数据构造

每个顶点包含8个float:位置(xyz) + 法向量(xyz) + 纹理坐标(uv)

float vertices[] = {
    // 背面 (z = -0.5)
    -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,
    
    // 前面 (z = 0.5)
    -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,
    
    // 左面 (x = -0.5)
    -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,
    
    // 右面 (x = 0.5)
     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,
    
    // 下面 (y = -0.5)
    -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,
    
    // 上面 (y = 0.5)
    -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,
};

为什么法向量这么重要? 法向量决定了光线照射到表面后如何反射,没有法向量就无法计算光照。

三、VAO属性配置

// 绑定VAO和VBO
glBindVertexArray(cubeVAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

// 位置属性 (location = 0)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

// 法向量属性 (location = 1)
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);

// 纹理坐标属性 (location = 2)
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);

四、光照参数设置

// 光源位置(一个小立方体的位置)
glm::vec3 lightPos(1.2f, 1.0f, 2.0f);

// 在渲染循环中设置光照uniform
lightingShader.use();

// 光源位置和观察者位置
lightingShader.setVec3("light.position", lightPos);
lightingShader.setVec3("viewPos", camera.Position);

// 光源三要素
lightingShader.setVec3("light.ambient",  0.2f, 0.2f, 0.2f);   // 环境光
lightingShader.setVec3("light.diffuse",  0.5f, 0.5f, 0.5f);   // 漫反射光
lightingShader.setVec3("light.specular", 1.0f, 1.0f, 1.0f);   // 镜面光

// 材质属性
lightingShader.setFloat("material.shininess", 64.0f);  // 高光反光度

五、纹理绑定

cpp

// 加载纹理
unsigned int diffuseMap  = loadTexture("container2.png");   // 漫反射贴图
unsigned int specularMap = loadTexture("container2.png");   // 高光贴图

// 绑定到不同纹理单元
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, diffuseMap);
lightingShader.setInt("material.diffuse", 0);   // 对应纹理单元0

glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, specularMap);
lightingShader.setInt("material.specular", 1);  // 对应纹理单元1

六、顶点着色器 (lighting_maps.vs)

glsl

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;

out vec3 FragPos;      // 片段在世界空间中的位置
out vec3 Normal;       // 法向量(经过法线矩阵变换)
out vec2 TexCoords;    // 纹理坐标

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

void main() {
    FragPos = vec3(model * vec4(aPos, 1.0));
    Normal = mat3(transpose(inverse(model))) * aNormal;  // 法线矩阵
    TexCoords = aTexCoords;
    
    gl_Position = projection * view * vec4(FragPos, 1.0);
}

七、片段着色器 (lighting_maps.fs)

glsl

#version 330 core
out vec4 FragColor;

in vec3 FragPos;
in vec3 Normal;
in vec2 TexCoords;

uniform vec3 lightPos;      // 光源位置
uniform vec3 viewPos;       // 观察者位置
uniform vec3 light.ambient;
uniform vec3 light.diffuse;
uniform vec3 light.specular;

uniform sampler2D material.diffuse;   // 漫反射贴图
uniform sampler2D material.specular;  // 高光贴图
uniform float material.shininess;

void main() {
    // 1. 环境光分量
    vec3 ambient = light.ambient * texture(material.diffuse, TexCoords).rgb;
    
    // 2. 漫反射分量
    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(lightPos - FragPos);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = light.diffuse * diff * texture(material.diffuse, TexCoords).rgb;
    
    // 3. 镜面反射分量
    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 * texture(material.specular, TexCoords).rgb;
    
    // 最终颜色 = 环境光 + 漫反射 + 镜面高光
    vec3 result = ambient + diffuse + specular;
    FragColor = vec4(result, 1.0);
}

八、光源标记物绘制

为了直观看到光源位置,我们绘制一个小立方体:

cpp

// 光源立方体着色器 (light_cube.vs / light_cube.fs)
lightCubeShader.use();
lightCubeShader.setMat4("projection", projection);
lightCubeShader.setMat4("view", view);

model = glm::mat4(1.0f);
model = glm::translate(model, lightPos);   // 移动到光源位置
model = glm::scale(model, glm::vec3(0.2f)); // 缩小到0.2倍
lightCubeShader.setMat4("model", model);

glBindVertexArray(lightCubeVAO);
glDrawArrays(GL_TRIANGLES, 0, 36);

光源立方体的片段着色器非常简单:

glsl

#version 330 core
out vec4 FragColor;
void main() {
    FragColor = vec4(1.0);  // 白色
}

九、光照计算流程图

text

┌─────────────────────────────────────────────────────────────┐
│                      每帧渲染流程                            │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  顶点数据 ──→ 顶点着色器 ──→ 片段着色器 ──→ 最终颜色         │
│  (位置)        (计算FragPos)    (计算光照)                   │
│  (法线)        (变换法线)                                    │
│  (UV)          (传递UV)                                      │
│                                                              │
├─────────────────────────────────────────────────────────────┤
│                   片段着色器光照计算                         │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│   环境光 = 光源环境光 × 漫反射贴图颜色                        │
│                                                              │
│   漫反射 = 光源漫反射光 × max(dot(法线, 光方向), 0) × 漫反射贴图│
│                                                              │
│   镜面光 = 光源镜面光 × pow(max(dot(视线, 反射光), 0), 高光)  │
│            × 高光贴图                                        │
│                                                              │
│   最终颜色 = 环境光 + 漫反射 + 镜面光                         │
│                                                              │
└─────────────────────────────────────────────────────────────┘

十、参数调优建议

参数 作用 推荐值 说明
light.ambient 环境光强度 0.1~0.3 太高画面发灰,太低暗部死黑
light.diffuse 漫反射强度 0.5~0.8 影响整体亮度
light.specular 高光强度 0.5~1.0 金属质感需要更高值
material.shininess 高光集中度 32~128 值越大高光越小越亮

十 一、完整代码

#include <glad.h>      // OpenGL函数指针管理库
#include <glfw3.h>     // 窗口管理和输入处理库
#include <stb_image.h> // 图像加载库(支持png/jpg等)

#include <glm.hpp>              // GLM数学库核心
#include <gtc/matrix_transform.hpp> // 矩阵变换(透视、平移、旋转、缩放)
#include <gtc/type_ptr.hpp>         // GLM矩阵转OpenGL指针

#include <shader.h>     // 自定义着色器类
#include <my_camera.h>  // 自定义摄像机类

#include <iostream>     // 标准输入输出

// 回调函数声明
void framebuffer_size_callback(GLFWwindow* window, int width, int height); // 窗口大小改变回调
void mouse_callback(GLFWwindow* window, double xpos, double ypos);         // 鼠标移动回调
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);  // 滚轮滚动回调
void processInput(GLFWwindow* window);      // 键盘输入处理函数
unsigned int loadTexture(const char* path); // 纹理加载函数

// 窗口设置
const unsigned int SCR_WIDTH = 800;   // 窗口宽度
const unsigned int SCR_HEIGHT = 600;  // 窗口高度

// 摄像机
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f)); // 摄像机位置(0,0,3)
float lastX = SCR_WIDTH / 2.0f;   // 鼠标上一帧X坐标(屏幕中心)
float lastY = SCR_HEIGHT / 2.0f;  // 鼠标上一帧Y坐标(屏幕中心)
bool firstMouse = true;            // 是否为首次获取鼠标位置(用于初始化)

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

// 光照
glm::vec3 lightPos(1.2f, 1.0f, 2.0f);  // 光源位置

int main()
{
    // ==================== GLFW初始化与配置 ====================
    glfwInit();   // 初始化GLFW库
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);   // 设置OpenGL主版本号为3
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);   // 设置OpenGL次版本号为3 → OpenGL 3.3
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 使用核心模式(不兼容旧版)

#ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // MacOS系统需要这行
#endif

    // ==================== 创建GLFW窗口 ====================
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)  // 窗口创建失败检查
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();  // 终止GLFW
        return -1;
    }
    glfwMakeContextCurrent(window);  // 将窗口上下文设置为当前线程的上下文
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); // 注册窗口大小改变回调
    glfwSetCursorPosCallback(window, mouse_callback);   // 注册鼠标移动回调
    glfwSetScrollCallback(window, scroll_callback);     // 注册滚轮回调

    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); // 隐藏并捕获鼠标(第一人称效果)

    // ==================== GLAD加载OpenGL函数指针 ====================
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))  // 加载所有OpenGL函数
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    // ==================== 配置OpenGL全局状态 ====================
    glEnable(GL_DEPTH_TEST);  // 启用深度测试(解决前后遮挡问题)

    // ==================== 编译着色器程序 ====================
    Shader lightingShader("4.2.lighting_maps.vs", "4.2.lighting_maps.fs"); // 主立方体的光照纹理着色器
    Shader lightCubeShader("4.2.light_cube.vs", "4.2.light_cube.fs");       // 光源立方体的简单着色器

    // ==================== 顶点数据 ====================
    // 每个顶点:位置(xyz) + 法向量(xyz) + 纹理坐标(uv),共8个float
    float vertices[] = {
        // 背面 (z = -0.5) 法线指向 -Z
        -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,

        // 前面 (z = 0.5) 法线指向 +Z
        -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,

        // 左面 (x = -0.5) 法线指向 -X
        -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,

        // 右面 (x = 0.5) 法线指向 +X
         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,

        // 下面 (y = -0.5) 法线指向 -Y
        -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,

        // 上面 (y = 0.5) 法线指向 +Y
        -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
    };
    
    // ==================== 配置主立方体的VAO和VBO ====================
    unsigned int VBO, cubeVAO;  // VBO:顶点缓冲对象, cubeVAO:主立方体顶点数组对象
    glGenVertexArrays(1, &cubeVAO);  // 生成1个VAO
    glGenBuffers(1, &VBO);           // 生成1个VBO

    glBindBuffer(GL_ARRAY_BUFFER, VBO);                        // 绑定VBO
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 将顶点数据复制到VBO

    glBindVertexArray(cubeVAO);  // 绑定VAO
    // 位置属性 (location=0): 3个float,步长8*sizeof(float),偏移0
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);  // 启用位置属性
    // 法向量属性 (location=1): 3个float,步长8*sizeof(float),偏移3*sizeof(float)
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);  // 启用法向量属性
    // 纹理坐标属性 (location=2): 2个float,步长8*sizeof(float),偏移6*sizeof(float)
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
    glEnableVertexAttribArray(2);  // 启用纹理坐标属性

    // ==================== 配置光源立方体的VAO(复用VBO) ====================
    unsigned int lightCubeVAO;  // 光源立方体的VAO
    glGenVertexArrays(1, &lightCubeVAO);  // 生成1个VAO
    glBindVertexArray(lightCubeVAO);      // 绑定光源VAO

    glBindBuffer(GL_ARRAY_BUFFER, VBO);    // 复用同一个VBO(顶点数据相同)
    // 光源只需要位置属性 (location=0): 3个float,步长8*sizeof(float),偏移0
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);  // 启用位置属性

    // ==================== 加载纹理 ====================
    unsigned int diffuseMap = loadTexture("container2.png");   // 漫反射贴图(物体固有色)
    unsigned int specularMap = loadTexture("container2.png");  // 高光贴图(控制镜面反射区域)

    // ==================== 着色器配置 ====================
    lightingShader.use();  // 激活光照着色器
    lightingShader.setInt("material.diffuse", 0);   // 漫反射贴图绑定到纹理单元0
    lightingShader.setInt("material.specular", 1);  // 高光贴图绑定到纹理单元1

    // ==================== 渲染循环 ====================
    while (!glfwWindowShouldClose(window))  // 窗口未关闭时持续渲染
    {
        // ----- 计算帧间隔时间 -----
        float currentFrame = static_cast<float>(glfwGetTime());  // 获取当前时间
        deltaTime = currentFrame - lastFrame;  // 计算与上一帧的时间差
        lastFrame = currentFrame;              // 更新上一帧时间

        // ----- 处理键盘输入 -----
        processInput(window);  // 检测WASD/ESC等按键

        // ----- 渲染命令 -----
        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);  // 设置清屏颜色(深灰色)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  // 清除颜色缓冲和深度缓冲

        // ========== 绘制主立方体(带光照和纹理) ==========
        lightingShader.use();  // 激活光照着色器
        
        // 设置光源位置和观察者位置
        lightingShader.setVec3("light.position", lightPos);  // 光源在世界中的位置
        lightingShader.setVec3("viewPos", camera.Position);  // 摄像机位置(用于镜面反射计算)
        
        // 设置光源的三要素
        lightingShader.setVec3("light.ambient", 0.2f, 0.2f, 0.2f);   // 环境光(暗部基础照明)
        lightingShader.setVec3("light.diffuse", 0.5f, 0.5f, 0.5f);   // 漫反射光(面向光源的一面更亮)
        lightingShader.setVec3("light.specular", 1.0f, 1.0f, 1.0f);  // 镜面光(高光颜色)
        
        // 设置材质属性
        lightingShader.setFloat("material.shininess", 64.0f);  // 高光反光度(值越大高光越集中)
        
        // 计算投影矩阵和视图矩阵
        glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), 
                                  (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);  // 透视投影
        glm::mat4 view = camera.GetViewMatrix();  // 视图矩阵(摄像机变换)
        lightingShader.setMat4("projection", projection);  // 传递投影矩阵到着色器
        lightingShader.setMat4("view", view);              // 传递视图矩阵到着色器
        
        // 模型矩阵(世界变换)
        glm::mat4 model = glm::mat4(1.0f);  // 单位矩阵(不做任何平移/旋转/缩放)
        lightingShader.setMat4("model", model);  // 传递模型矩阵到着色器
        
        // 绑定纹理
        glActiveTexture(GL_TEXTURE0);            // 激活纹理单元0
        glBindTexture(GL_TEXTURE_2D, diffuseMap); // 绑定漫反射贴图
        glActiveTexture(GL_TEXTURE1);            // 激活纹理单元1
        glBindTexture(GL_TEXTURE_2D, specularMap); // 绑定高光贴图
        
        // 绘制立方体
        glBindVertexArray(cubeVAO);   // 绑定主立方体VAO
        glDrawArrays(GL_TRIANGLES, 0, 36);  // 绘制36个顶点(12个三角形 = 6个面 × 2个三角形/面 × 3个顶点/三角形)

        // ========== 绘制光源标记物(小白立方体) ==========
        lightCubeShader.use();  // 激活光源着色器(非常简单,只输出白色)
        lightCubeShader.setMat4("projection", projection);  // 传递投影矩阵
        lightCubeShader.setMat4("view", view);              // 传递视图矩阵
        
        model = glm::mat4(1.0f);                     // 重置模型矩阵
        model = glm::translate(model, lightPos);     // 平移到光源位置
        model = glm::scale(model, glm::vec3(0.2f));  // 缩放到原来的0.2倍(小立方体)
        lightCubeShader.setMat4("model", model);     // 传递模型矩阵
        
        glBindVertexArray(lightCubeVAO);  // 绑定光源VAO
        glDrawArrays(GL_TRIANGLES, 0, 36);  // 绘制36个顶点

        // ----- 交换缓冲区和轮询事件 -----
        glfwSwapBuffers(window);  // 交换前后缓冲区(显示绘制结果)
        glfwPollEvents();         // 轮询所有等待处理的事件(键盘、鼠标等)
    }

    // ==================== 清理资源 ====================
    glDeleteVertexArrays(1, &cubeVAO);      // 删除主立方体VAO
    glDeleteVertexArrays(1, &lightCubeVAO); // 删除光源VAO
    glDeleteBuffers(1, &VBO);               // 删除VBO

    glfwTerminate();  // 终止GLFW,释放所有资源
    return 0;         // 程序正常退出
}

// ==================== 键盘输入处理函数 ====================
void processInput(GLFWwindow* window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)  // 按下ESC键
        glfwSetWindowShouldClose(window, true);  // 设置窗口关闭标志

    // WASD控制摄像机移动
    if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
        camera.ProcessKeyboard(FORWARD, deltaTime);   // 向前移动
    if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
        camera.ProcessKeyboard(BACKWARD, deltaTime);  // 向后移动
    if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
        camera.ProcessKeyboard(LEFT, deltaTime);      // 向左移动
    if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
        camera.ProcessKeyboard(RIGHT, deltaTime);     // 向右移动
}

// ==================== 窗口大小改变回调 ====================
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    glViewport(0, 0, width, height);  // 设置OpenGL渲染视口为整个窗口
}

// ==================== 鼠标移动回调 ====================
void mouse_callback(GLFWwindow* window, double xposIn, double yposIn)
{
    float xpos = static_cast<float>(xposIn);  // 当前鼠标X坐标
    float ypos = static_cast<float>(yposIn);  // 当前鼠标Y坐标

    if (firstMouse)  // 首次进入,初始化上一帧坐标
    {
        lastX = xpos;
        lastY = ypos;
        firstMouse = false;
    }

    float xoffset = xpos - lastX;           // 计算X轴偏移
    float yoffset = lastY - ypos;           // 计算Y轴偏移(注意反转:屏幕Y轴向上为正)
    lastX = xpos;  // 更新上一帧X坐标
    lastY = ypos;  // 更新上一帧Y坐标

    camera.ProcessMouseMovement(xoffset, yoffset);  // 更新摄像机朝向
}

// ==================== 鼠标滚轮回调 ====================
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
    camera.ProcessMouseScroll(static_cast<float>(yoffset));  // 调整摄像机缩放(视野FOV)
}

// ==================== 纹理加载函数 ====================
unsigned int loadTexture(char const* path)
{
    unsigned int textureID;  // 纹理ID
    glGenTextures(1, &textureID);  // 生成一个纹理对象

    int width, height, nrComponents;  // 宽度、高度、颜色通道数
    unsigned char* data = stbi_load(path, &width, &height, &nrComponents, 0);  // 加载图片
    if (data)  // 加载成功
    {
        GLenum format;  // OpenGL纹理格式
        if (nrComponents == 1)
            format = GL_RED;      // 单通道(灰度图)
        else if (nrComponents == 3)
            format = GL_RGB;      // 三通道(RGB彩色图)
        else if (nrComponents == 4)
            format = GL_RGBA;     // 四通道(RGBA带透明度)

        glBindTexture(GL_TEXTURE_2D, textureID);  // 绑定纹理
        // 生成纹理图像
        glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
        glGenerateMipmap(GL_TEXTURE_2D);  // 生成多级渐远纹理(Mipmap)

        // 设置纹理环绕方式
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);  // S方向重复
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);  // T方向重复
        // 设置纹理过滤方式
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); // 缩小:三线性过滤
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);              // 放大:线性过滤

        stbi_image_free(data);  // 释放图片数据内存
    }
    else  // 加载失败
    {
        std::cout << "Texture failed to load at path: " << path << std::endl;
        stbi_image_free(data);  // 释放图片数据内存
    }

    return textureID;  // 返回纹理ID
}

常见问题排查

Q1:物体全黑没有光照?

  • 检查法向量是否正确传递到片段着色器

  • 确认光源位置没有在物体内部

Q2:纹理不显示?

  • 检查纹理加载路径是否正确

  • 确认纹理单元绑定和uniform设置匹配

Q3:高光太强或太弱?

  • 调整light.specular

  • 修改material.shininess(32更分散,128更集中)

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐