OpenGL光照渲染:漫反射+高光+纹理贴图完整解析
一、光照模型概述
我们实现的是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更集中)
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)