平行光
当一个光源处于很远的地方时,来自光源的每条光线就会近似于互相平行。不论物体和/或者观察者的位置,看起来好像所有的光都来自于同一个方向。当我们使用一个假设光源处于无限远处的模型时,它就被称为定向光,因为它的所有光线都有着相同的方向,它与光源的位置是没有关系的。
定向光非常好的一个例子就是太阳。太阳距离我们并不是无限远,但它已经远到在光照计算中可以把它视为无限远了。所以来自太阳的所有光线将被模拟为平行光线,我们可以在下图看到:

因为所有的光线都是平行的,所以物体与光源的相对位置是不重要的,因为对场景中每一个物体光的方向都是一致的。由于光的位置向量保持一致,场景中每个物体的光照计算将会是类似的。
我们可以定义一个光线方向向量而不是位置向量来模拟一个定向光。着色器的计算基本保持不变,但这次我们将直接使用光的direction向量而不是通过position来计算lightDir向量。
将光源方块注释之后,完整的平行光代码为:
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <cmath>
#include <imgui.h>
#include <imgui_impl_glfw.h>
#include <imgui_impl_opengl3.h>
#include <opencv2/opencv.hpp>
#include <opencv2/imgcodecs.hpp>
#include "utils/shader.h"
#include "utils/glmCompute.h"
#include "include/dpi_stabilizer.h"
#include "include/imgui_manager.h"
using namespace glm;
using namespace cv;
// 使用 OpenCV 加载纹理
unsigned int loadTexture(const char* path) {
unsigned int textureID;
glGenTextures(1, &textureID);
// 使用 OpenCV 读取图像
Mat image = imread(path, IMREAD_COLOR);
if (image.empty()) {
std::cout << "Failed to load texture at path: " << path << std::endl;
return textureID;
}
// OpenCV 使用 BGR,OpenGL 使用 RGB,需要转换
Mat rgbImage;
cvtColor(image, rgbImage, COLOR_BGR2RGB);
// 翻转图像(OpenGL 期望的 Y 轴方向)
flip(rgbImage, rgbImage, 0);
glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, rgbImage.cols, rgbImage.rows, 0, GL_RGB, GL_UNSIGNED_BYTE, rgbImage.data);
glGenerateMipmap(GL_TEXTURE_2D);
// 设置纹理参数
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_LINEAR);
return textureID;
}
// 窗口大小改变时的回调函数
void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
glViewport(0, 0, width, height);
}
// 全局变量
float deltaTime = 0.0f; // 当前帧与上一帧的时间差
float lastFrame = 0.0f; // 上一帧的时间
// DPI 稳定器实例
DPIStabilizer dpiStabilizer(0.1f, 50.0f, 0.8f);
// ImGui 管理器实例
ImGuiManager imguiManager;
// 简化的输入处理函数
void processInput(GLFWwindow *window){
// 使用 DPI 稳定器处理所有输入
dpiStabilizer.processKeyboardInput(window);
if (glfwGetKey(window, GLFW_KEY_F2) == GLFW_PRESS) {
imguiManager.toggleWindow("控制面板");
}
if (glfwGetKey(window, GLFW_KEY_F3) == GLFW_PRESS) {
imguiManager.toggleWindow("性能监控");
}
if (glfwGetKey(window, GLFW_KEY_F4) == GLFW_PRESS) {
imguiManager.toggleWindow("调试信息");
}
// 显示统计信息 (按 T 键)
if (glfwGetKey(window, GLFW_KEY_T) == GLFW_PRESS) {
std::cout << dpiStabilizer.getStats() << std::endl;
}
}
int main() {
// 投影矩阵
mat4 projection = perspective(radians(45.0f), (float)800 / (float)600, 0.1f, 100.0f);
// 初始化GLFW
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// 设置 DPI 感知提示
DPIStabilizer::setDPIHint();
// 创建窗口
GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGL Window", NULL, NULL);
if (window == NULL) {
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
// 初始化 DPI 稳定器
dpiStabilizer.initialize(window);
// 初始化GLAD
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
// 初始化 ImGui 管理器
imguiManager.initialize(window);
// 注册窗口
imguiManager.registerWindow("Hello ImGui!", []() {
ImGui::Text("DPI 稳定器状态: %s", dpiStabilizer.isMouseCaptured() ? "已捕获" : "已释放");
ImGui::Text("鼠标敏感度: %.3f", dpiStabilizer.getSensitivity());
if (ImGui::Button("重置统计")) {
dpiStabilizer.resetStats();
}
float sensitivity = dpiStabilizer.getSensitivity();
if (ImGui::SliderFloat("鼠标敏感度", &sensitivity, 0.01f, 0.5f)) {
dpiStabilizer.setSensitivity(sensitivity);
}
});
imguiManager.registerWindow("控制面板", []() {
ImGuiIO& io = ImGui::GetIO();
ImGui::Text("FPS: %.1f", io.Framerate);
ImGui::Text("帧时间: %.3f ms", 1000.0f / io.Framerate);
});
// 启用深度测试
glEnable(GL_DEPTH_TEST);
float vertices[] = {
// 立方体顶点数据(位置 + 法线 + 纹理坐标)
// 位置(3) + 法线(3) + 纹理坐标(2) = 8个浮点数
// 前面
-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
};
// 创建VAO
unsigned int VAO;
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO); // 绑定VAO
// 创建VBO
unsigned int VBO;
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO); // 绑定VBO
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
int nrAttributes;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;
// 位置属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0); // 启用顶点属性
// 法线属性
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1); // 启用法线属性
// 纹理坐标属性
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2); // 启用纹理坐标属性
// 构建并编译着色器程序
Shader lightingShader("shader/lighting.vert", "shader/lighting.frag");
Shader lampShader("shader/lamp.vert", "shader/lamp.frag");
// 加载纹理
unsigned int texture1 = loadTexture("Assets/image.png");
if (texture1 == 0) {
std::cout << "Failed to load texture" << std::endl;
}
// 创建光源的 VAO
unsigned int lightVAO;
glGenVertexArrays(1, &lightVAO);
glBindVertexArray(lightVAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 光源位置
vec3 lightPos(1.2f, 1.0f, 2.0f);
// 相机位置(观察空间)
vec3 viewPos = dpiStabilizer.getCameraPosition();
// 渲染循环
while (!glfwWindowShouldClose(window)) {
// 处理输入
processInput(window);
glfwPollEvents();
// 从 DPI 稳定器获取 view 矩阵
mat4 view = dpiStabilizer.getViewMatrix();
float currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
// 渲染指令
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 使用光照着色器绘制物体
lightingShader.use();
// 绑定纹理
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
lightingShader.setInt("material.diffuse", 0);
lightingShader.setMat4("projection", projection);
lightingShader.setMat4("view", view);
lightingShader.setVec3("light.direction", -0.2f, -1.0f, -0.3f);
lightingShader.setVec3("light.ambient", 1.0f, 1.0f, 1.0f); // 最大环境光强度
lightingShader.setVec3("light.diffuse", 1.0f, 1.0f, 1.0f); // 增加漫反射强度
lightingShader.setVec3("light.specular", 1.0f, 1.0f, 1.0f);
lightingShader.setVec3("material.specular", 0.633f, 0.727811f, 0.633f);
// 高光指数:通常乘以 128 (OpenGL 最大的 1.0 对应 128)
lightingShader.setFloat("material.shininess", 0.6f * 128.0f); // 76.8
// 设置相机位置(观察空间)
vec3 currentViewPos = dpiStabilizer.getCameraPosition();
lightingShader.setVec3("viewPos", currentViewPos.x, currentViewPos.y, currentViewPos.z);
glBindVertexArray(VAO);
// 定义多个立方体的位置(更分散的布局)
glm::vec3 cubePositions[] = {
glm::vec3( 0.0f, 0.0f, 0.0f),
glm::vec3( 2.0f, 0.0f, 0.0f),
glm::vec3(-2.0f, 0.0f, 0.0f),
glm::vec3( 0.0f, 0.0f, -2.0f),
glm::vec3( 0.0f, 0.0f, 2.0f),
glm::vec3( 2.0f, 0.0f, -2.0f),
glm::vec3(-2.0f, 0.0f, -2.0f),
glm::vec3( 2.0f, 0.0f, 2.0f),
glm::vec3(-2.0f, 0.0f, 2.0f),
glm::vec3( 0.0f, 2.0f, 0.0f)
};
for(unsigned int i = 0; i < 10; i++)
{
glm::mat4 model = glm::mat4(1.0f);
model = glm::translate(model, cubePositions[i]);
float angle = 20.0f * i;
model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
lightingShader.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
}
// 绘制光源立方体(平行光不需要光源立方体,已注释)
/*
lampShader.use();
lampShader.setMat4("projection", projection);
lampShader.setMat4("view", view);
// 光源立方体的模型矩阵
mat4 lampModel = mat4(1.0f);
lampModel = translate(lampModel, lightPos);
lampModel = scale(lampModel, vec3(0.2f)); // 缩小光源立方体
lampShader.setMat4("model", lampModel);
glBindVertexArray(lightVAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
*/
// 使用 ImGui 管理器渲染所有窗口
imguiManager.render();
// 交换缓冲区和检查IO事件
glfwSwapBuffers(window);
}
// 清理 ImGui 管理器
imguiManager.shutdown();
// 清理资源
glDeleteVertexArrays(1, &VAO);
glDeleteVertexArrays(1, &lightVAO);
glDeleteBuffers(1, &VBO);
glDeleteTextures(1, &texture1);
glfwTerminate();
return 0;
}
先来来看看点光源: 我们需要一个衰减公式来表示光线随着距离增加而减小。
下面这个公式根据片段距光源的距离计算了衰减值,之后我们会将它乘以光的强度向量:

在这里d代表了片段距光源的距离。接下来为了计算衰减值,我们定义3个(可配置的)项:常数项Kc、一次项Kl和二次项Kq。
- 常数项通常保持为1.0,它的主要作用是保证分母永远不会比1小,否则的话在某些距离上它反而会增加强度,这肯定不是我们想要的效果。
- 一次项会与距离值相乘,以线性的方式减少强度。
- 二次项会与距离的平方相乘,让光源以二次递减的方式减少强度。二次项在距离比较小的时候影响会比一次项小很多,但当距离值比较大的时候它就会比一次项更大了。
由于二次项的存在,光线会在大部分时候以线性的方式衰退,直到距离变得足够大,让二次项超过一次项,光的强度会以更快的速度下降。这样的结果就是,光在近距离时亮度很高,但随着距离变远亮度迅速降低,最后会以更慢的速度减少亮度。下面这张图显示了在100的距离内衰减的效果:

你可以看到光在近距离的时候有着最高的强度,但随着距离增长,它的强度明显减弱,并缓慢地在距离大约100的时候强度接近0。这正是我们想要的。
选择正确的值
但是,该对这三个项设置什么值呢?正确地设定它们的值取决于很多因素:环境、希望光覆盖的距离、光的类型等。在大多数情况下,这都是经验的问题,以及适量的调整。下面这个表格显示了模拟一个(大概)真实的,覆盖特定半径(距离)的光源时,这些项可能取的一些值。第一列指定的是在给定的三项时光所能覆盖的距离。这些值是大多数光源很好的起始点,它们由Ogre3D的Wiki所提供:
| 距离 | 常数项 | 一次项 | 二次项 |
|---|---|---|---|
| 7 | 1.0 | 0.7 | 1.8 |
| 13 | 1.0 | 0.35 | 0.44 |
| 20 | 1.0 | 0.22 | 0.20 |
| 32 | 1.0 | 0.14 | 0.07 |
| 50 | 1.0 | 0.09 | 0.032 |
| 65 | 1.0 | 0.07 | 0.017 |
| 100 | 1.0 | 0.045 | 0.0075 |
| 160 | 1.0 | 0.027 | 0.0028 |
| 200 | 1.0 | 0.022 | 0.0019 |
| 325 | 1.0 | 0.014 | 0.0007 |
| 600 | 1.0 | 0.007 | 0.0002 |
| 3250 | 1.0 | 0.0014 | 0.000007 |
你可以看到,常数项Kc在所有的情况下都是1.0。一次项Kl为了覆盖更远的距离通常都很小,二次项Kq甚至更小。尝试对这些值进行实验,看看它们在你的实现中有什么效果。在我们的环境中,32到100的距离对大多数的光源都足够了。
下面完整代码:
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <cmath>
#include <imgui.h>
#include <imgui_impl_glfw.h>
#include <imgui_impl_opengl3.h>
#include <opencv2/opencv.hpp>
#include <opencv2/imgcodecs.hpp>
#include "utils/shader.h"
#include "utils/glmCompute.h"
#include "include/dpi_stabilizer.h"
#include "include/imgui_manager.h"
using namespace glm;
using namespace cv;
// 使用 OpenCV 加载纹理
unsigned int loadTexture(const char* path) {
unsigned int textureID;
glGenTextures(1, &textureID);
// 使用 OpenCV 读取图像
Mat image = imread(path, IMREAD_COLOR);
if (image.empty()) {
std::cout << "Failed to load texture at path: " << path << std::endl;
return textureID;
}
// OpenCV 使用 BGR,OpenGL 使用 RGB,需要转换
Mat rgbImage;
cvtColor(image, rgbImage, COLOR_BGR2RGB);
// 翻转图像(OpenGL 期望的 Y 轴方向)
flip(rgbImage, rgbImage, 0);
glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, rgbImage.cols, rgbImage.rows, 0, GL_RGB, GL_UNSIGNED_BYTE, rgbImage.data);
glGenerateMipmap(GL_TEXTURE_2D);
// 设置纹理参数
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_LINEAR);
return textureID;
}
// 窗口大小改变时的回调函数
void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
glViewport(0, 0, width, height);
}
// 全局变量
float deltaTime = 0.0f; // 当前帧与上一帧的时间差
float lastFrame = 0.0f; // 上一帧的时间
// DPI 稳定器实例
DPIStabilizer dpiStabilizer(0.1f, 50.0f, 0.8f);
// ImGui 管理器实例
ImGuiManager imguiManager;
// 简化的输入处理函数
void processInput(GLFWwindow *window){
// 使用 DPI 稳定器处理所有输入
dpiStabilizer.processKeyboardInput(window);
if (glfwGetKey(window, GLFW_KEY_F2) == GLFW_PRESS) {
imguiManager.toggleWindow("控制面板");
}
if (glfwGetKey(window, GLFW_KEY_F3) == GLFW_PRESS) {
imguiManager.toggleWindow("性能监控");
}
if (glfwGetKey(window, GLFW_KEY_F4) == GLFW_PRESS) {
imguiManager.toggleWindow("调试信息");
}
// 显示统计信息 (按 T 键)
if (glfwGetKey(window, GLFW_KEY_T) == GLFW_PRESS) {
std::cout << dpiStabilizer.getStats() << std::endl;
}
}
int main() {
// 投影矩阵
mat4 projection = perspective(radians(45.0f), (float)800 / (float)600, 0.1f, 100.0f);
// 初始化GLFW
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// 设置 DPI 感知提示
DPIStabilizer::setDPIHint();
// 创建窗口
GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGL Window", NULL, NULL);
if (window == NULL) {
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
// 初始化 DPI 稳定器
dpiStabilizer.initialize(window);
// 初始化GLAD
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
// 初始化 ImGui 管理器
imguiManager.initialize(window);
// 注册窗口
imguiManager.registerWindow("Hello ImGui!", []() {
ImGui::Text("DPI 稳定器状态: %s", dpiStabilizer.isMouseCaptured() ? "已捕获" : "已释放");
ImGui::Text("鼠标敏感度: %.3f", dpiStabilizer.getSensitivity());
if (ImGui::Button("重置统计")) {
dpiStabilizer.resetStats();
}
float sensitivity = dpiStabilizer.getSensitivity();
if (ImGui::SliderFloat("鼠标敏感度", &sensitivity, 0.01f, 0.5f)) {
dpiStabilizer.setSensitivity(sensitivity);
}
});
imguiManager.registerWindow("控制面板", []() {
ImGuiIO& io = ImGui::GetIO();
ImGui::Text("FPS: %.1f", io.Framerate);
ImGui::Text("帧时间: %.3f ms", 1000.0f / io.Framerate);
});
// 启用深度测试
glEnable(GL_DEPTH_TEST);
float vertices[] = {
// 立方体顶点数据(位置 + 法线 + 纹理坐标)
// 位置(3) + 法线(3) + 纹理坐标(2) = 8个浮点数
// 前面
-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
};
// 创建VAO
unsigned int VAO;
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO); // 绑定VAO
// 创建VBO
unsigned int VBO;
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO); // 绑定VBO
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
int nrAttributes;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;
// 位置属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0); // 启用顶点属性
// 法线属性
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1); // 启用法线属性
// 纹理坐标属性
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2); // 启用纹理坐标属性
// 构建并编译着色器程序
Shader lightingShader("shader/lighting.vert", "shader/lighting.frag");
Shader lampShader("shader/lamp.vert", "shader/lamp.frag");
// 加载纹理
unsigned int texture1 = loadTexture("Assets/image.png");
if (texture1 == 0) {
std::cout << "Failed to load texture" << std::endl;
}
// 创建光源的 VAO
unsigned int lightVAO;
glGenVertexArrays(1, &lightVAO);
glBindVertexArray(lightVAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 光源位置
vec3 lightPos(1.2f, 1.0f, 2.0f);
// 相机位置(观察空间)
vec3 viewPos = dpiStabilizer.getCameraPosition();
// 渲染循环
while (!glfwWindowShouldClose(window)) {
// 处理输入
processInput(window);
glfwPollEvents();
// 从 DPI 稳定器获取 view 矩阵
mat4 view = dpiStabilizer.getViewMatrix();
float currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
// 渲染指令
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 使用光照着色器绘制物体
lightingShader.use();
// 绑定纹理
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
lightingShader.setInt("material.diffuse", 0);
lightingShader.setMat4("projection", projection);
lightingShader.setMat4("view", view);
lightingShader.setVec3("light.position", lightPos.x, lightPos.y, lightPos.z);
lightingShader.setFloat("light.constant", 1.0f);
lightingShader.setFloat("light.linear", 0.09f);
lightingShader.setFloat("light.quadratic", 0.032f);
lightingShader.setVec3("light.ambient", 1.0f, 1.0f, 1.0f); // 最大环境光强度
lightingShader.setVec3("light.diffuse", 1.0f, 1.0f, 1.0f); // 增加漫反射强度
lightingShader.setVec3("light.specular", 1.0f, 1.0f, 1.0f);
lightingShader.setVec3("material.specular", 0.633f, 0.727811f, 0.633f);
// 高光指数:通常乘以 128 (OpenGL 最大的 1.0 对应 128)
lightingShader.setFloat("material.shininess", 0.6f * 128.0f); // 76.8
// 设置相机位置(观察空间)
vec3 currentViewPos = dpiStabilizer.getCameraPosition();
lightingShader.setVec3("viewPos", currentViewPos.x, currentViewPos.y, currentViewPos.z);
glBindVertexArray(VAO);
// 定义多个立方体的位置(更分散的布局)
glm::vec3 cubePositions[] = {
glm::vec3( 0.0f, 0.0f, 0.0f),
glm::vec3( 4.0f, 0.0f, 0.0f),
glm::vec3(-4.0f, 0.0f, 0.0f),
glm::vec3( 0.0f, 0.0f, -8.0f),
glm::vec3( 0.0f, 0.0f, 8.0f),
glm::vec3( 8.0f, 0.0f, -8.0f),
glm::vec3(-10.0f, 0.0f, -2.0f),
glm::vec3( 2.0f, 0.0f, 2.0f),
glm::vec3(-2.0f, 0.0f, 2.0f),
glm::vec3( 0.0f, 2.0f, 0.0f)
};
for(unsigned int i = 0; i < 10; i++)
{
glm::mat4 model = glm::mat4(1.0f);
model = glm::translate(model, cubePositions[i]);
float angle = 20.0f * i;
model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
lightingShader.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
}
lampShader.use();
lampShader.setMat4("projection", projection);
lampShader.setMat4("view", view);
// 光源立方体的模型矩阵
mat4 lampModel = mat4(1.0f);
lampModel = translate(lampModel, lightPos);
lampModel = scale(lampModel, vec3(0.2f)); // 缩小光源立方体
lampShader.setMat4("model", lampModel);
glBindVertexArray(lightVAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
// 使用 ImGui 管理器渲染所有窗口
imguiManager.render();
// 交换缓冲区和检查IO事件
glfwSwapBuffers(window);
}
// 清理 ImGui 管理器
imguiManager.shutdown();
// 清理资源
glDeleteVertexArrays(1, &VAO);
glDeleteVertexArrays(1, &lightVAO);
glDeleteBuffers(1, &VBO);
glDeleteTextures(1, &texture1);
glfwTerminate();
return 0;
}
#version 330 core
out vec4 FragColor;
struct Material{
sampler2D diffuse;
vec3 specular;
float shininess;
};
struct Light{
vec3 position;
vec3 ambient;
vec3 diffuse;
vec3 specular;
float constant;
float linear;
float quadratic;
};
uniform Light light;
uniform vec3 viewPos;
uniform Material material;
in vec3 Normal;
in vec3 FragPos;
in vec2 TexCoords;
void main()
{
// 从纹理采样颜色
vec3 textureColor = vec3(texture(material.diffuse, TexCoords));
float distance = length(light.position - FragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance +
light.quadratic * (distance * distance));
// 环境光
vec3 ambient = attenuation* light.ambient * textureColor;
// 漫反射
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(light.position - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = attenuation* light.diffuse * (diff * textureColor);
// 镜面反射
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 specular = attenuation* light.specular * (spec * material.specular);
// 最终结果
vec3 result = ambient + diffuse + specular;
FragColor = vec4(result, 1.0);
}
聚光
我们要讨论的最后一种类型的光是聚光(Spotlight)。聚光是位于环境中某个位置的光源,它只朝一个特定方向而不是所有方向照射光线。这样的结果就是只有在聚光方向的特定半径内的物体才会被照亮,其它的物体都会保持黑暗。聚光很好的例子就是路灯或手电筒。
OpenGL中聚光是用一个世界空间位置、一个方向和一个切光角(Cutoff Angle)来表示的,切光角指定了聚光的半径(译注:是圆锥的半径不是距光源距离那个半径)。对于每个片段,我们会计算片段是否位于聚光的切光方向之间(也就是在锥形内),如果是的话,我们就会相应地照亮片段。下面这张图会让你明白聚光是如何工作的:

LightDir:从片段指向光源的向量。SpotDir:聚光所指向的方向。Phiϕ:指定了聚光半径的切光角。落在这个角度之外的物体都不会被这个聚光所照亮。Thetaθ:LightDir向量和SpotDir向量之间的夹角。在聚光内部的话θ值应该比ϕ值小
所以我们要做的就是计算LightDir向量和SpotDir向量之间的点积(还记得它会返回两个单位向量夹角的余弦值吗?),并将它与切光角ϕ值对比。你现在应该了解聚光究竟是什么了,下面我们将以手电筒的形式创建一个聚光。
手电筒
手电筒(Flashlight)是一个位于观察者位置的聚光,通常它都会瞄准玩家视角的正前方。基本上说,手电筒就是普通的聚光,但它的位置和方向会随着玩家的位置和朝向不断更新。
所以,在片段着色器中我们需要的值有聚光的位置向量(来计算光的方向向量,感觉就像是平行光和点光源合成一样。。。。。)、聚光的方向向量和一个切光角。我们可以将它们储存在Light结构体中:
struct Light {
vec3 position;
vec3 direction;
float cutOff;
...
};
接下来我们将合适的值传到着色器中:
lightingShader.setVec3("light.position", camera.Position);
lightingShader.setVec3("light.direction", camera.Front);
lightingShader.setFloat("light.cutOff", glm::cos(glm::radians(12.5f)));
接下来就是计算θ值,并将它和切光角ϕ对比,来决定是否在聚光的内部:
float theta = dot(lightDir, normalize(-light.direction));
if(theta > light.cutOff)
{
// 执行光照计算
}
else // 否则,使用环境光,让场景在聚光之外时不至于完全黑暗
color = vec4(light.ambient * vec3(texture(material.diffuse, TexCoords)), 1.0);
我们首先计算了lightDir和取反的direction向量(取反的是因为我们想让向量指向光源而不是从光源出发)之间的点积。记住要对所有的相关向量标准化。
平滑/软化边缘
为了创建一种看起来边缘平滑的聚光,我们需要模拟聚光有一个内圆锥(Inner Cone)和一个外圆锥(Outer Cone)。我们可以将内圆锥设置为上一部分中的那个圆锥,但我们也需要一个外圆锥,来让光从内圆锥逐渐减暗,直到外圆锥的边界。
为了创建一个外圆锥,我们只需要再定义一个余弦值来代表聚光方向向量和外圆锥向量(等于它的半径)的夹角。然后,如果一个片段处于内外圆锥之间,将会给它计算出一个0.0到1.0之间的强度值。如果片段在内圆锥之内,它的强度就是1.0,如果在外圆锥之外强度值就是0.0。
我们可以用下面这个公式来计算这个值:

很难去表现这个公式是怎么工作的,所以我们用一些实例值来看看:
| θ | θ(角度) | ϕ(内光切) | ϕ(角度) | γ(外光切) | γ(角度) | ϵ | I |
|---|---|---|---|---|---|---|---|
| 0.87 | 30 | 0.91 | 25 | 0.82 | 35 | 0.91 - 0.82 = 0.09 | 0.87 - 0.82 / 0.09 = 0.56 |
| 0.9 | 26 | 0.91 | 25 | 0.82 | 35 | 0.91 - 0.82 = 0.09 | 0.9 - 0.82 / 0.09 = 0.89 |
| 0.97 | 14 | 0.91 | 25 | 0.82 | 35 | 0.91 - 0.82 = 0.09 | 0.97 - 0.82 / 0.09 = 1.67 |
| 0.83 | 34 | 0.91 | 25 | 0.82 | 35 | 0.91 - 0.82 = 0.09 | 0.83 - 0.82 / 0.09 = 0.11 |
| 0.64 | 50 | 0.91 | 25 | 0.82 | 35 | 0.91 - 0.82 = 0.09 | 0.64 - 0.82 / 0.09 = -2.0 |
| 0.966 | 15 | 0.9978 | 12.5 | 0.953 | 17.5 | 0.9978 - 0.953 = 0.0448 | 0.966 - 0.953 / 0.0448 = 0.29 |
我们现在有了一个在聚光外是负的,在内圆锥内大于1.0的,在边缘处于两者之间的强度值了。如果我们正确地约束(Clamp)这个值,在片段着色器中就不再需要if-else了,我们能够使用计算出来的强度值直接乘以光照分量:
float theta = dot(lightDir, normalize(-light.direction));
float epsilon = light.cutOff - light.outerCutOff;
float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0);
...
// 将不对环境光做出影响,让它总是能有一点光
diffuse *= intensity;
specular *= intensity;
...
注意我们使用了clamp函数,它把第一个参数约束(Clamp)在了0.0到1.0之间。这保证强度值不会在0, 1区间之外。
确定你将outerCutOff值添加到了Light结构体之中,并在程序中设置它的uniform值。下面的图片中,我们使用的内切光角是12.5,外切光角是17.5:
完整代码:
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <cmath>
#include <imgui.h>
#include <imgui_impl_glfw.h>
#include <imgui_impl_opengl3.h>
#include <opencv2/opencv.hpp>
#include <opencv2/imgcodecs.hpp>
#include "utils/shader.h"
#include "utils/glmCompute.h"
#include "include/dpi_stabilizer.h"
#include "include/imgui_manager.h"
using namespace glm;
using namespace cv;
// 使用 OpenCV 加载纹理
unsigned int loadTexture(const char* path) {
unsigned int textureID;
glGenTextures(1, &textureID);
// 使用 OpenCV 读取图像
Mat image = imread(path, IMREAD_COLOR);
if (image.empty()) {
std::cout << "Failed to load texture at path: " << path << std::endl;
return textureID;
}
// OpenCV 使用 BGR,OpenGL 使用 RGB,需要转换
Mat rgbImage;
cvtColor(image, rgbImage, COLOR_BGR2RGB);
// 翻转图像(OpenGL 期望的 Y 轴方向)
flip(rgbImage, rgbImage, 0);
glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, rgbImage.cols, rgbImage.rows, 0, GL_RGB, GL_UNSIGNED_BYTE, rgbImage.data);
glGenerateMipmap(GL_TEXTURE_2D);
// 设置纹理参数
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_LINEAR);
return textureID;
}
// 窗口大小改变时的回调函数
void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
glViewport(0, 0, width, height);
}
// 全局变量
float deltaTime = 0.0f; // 当前帧与上一帧的时间差
float lastFrame = 0.0f; // 上一帧的时间
// DPI 稳定器实例
DPIStabilizer dpiStabilizer(0.1f, 50.0f, 0.8f);
// ImGui 管理器实例
ImGuiManager imguiManager;
// 简化的输入处理函数
void processInput(GLFWwindow *window){
// 使用 DPI 稳定器处理所有输入
dpiStabilizer.processKeyboardInput(window);
if (glfwGetKey(window, GLFW_KEY_F2) == GLFW_PRESS) {
imguiManager.toggleWindow("控制面板");
}
if (glfwGetKey(window, GLFW_KEY_F3) == GLFW_PRESS) {
imguiManager.toggleWindow("性能监控");
}
if (glfwGetKey(window, GLFW_KEY_F4) == GLFW_PRESS) {
imguiManager.toggleWindow("调试信息");
}
// 显示统计信息 (按 T 键)
if (glfwGetKey(window, GLFW_KEY_T) == GLFW_PRESS) {
std::cout << dpiStabilizer.getStats() << std::endl;
}
}
int main() {
// 投影矩阵
mat4 projection = perspective(radians(45.0f), (float)800 / (float)600, 0.1f, 100.0f);
// 初始化GLFW
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// 设置 DPI 感知提示
DPIStabilizer::setDPIHint();
// 创建窗口
GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGL Window", NULL, NULL);
if (window == NULL) {
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
// 初始化 DPI 稳定器
dpiStabilizer.initialize(window);
// 初始化GLAD
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
// 初始化 ImGui 管理器
imguiManager.initialize(window);
// 注册窗口
imguiManager.registerWindow("Hello ImGui!", []() {
ImGui::Text("DPI 稳定器状态: %s", dpiStabilizer.isMouseCaptured() ? "已捕获" : "已释放");
ImGui::Text("鼠标敏感度: %.3f", dpiStabilizer.getSensitivity());
if (ImGui::Button("重置统计")) {
dpiStabilizer.resetStats();
}
float sensitivity = dpiStabilizer.getSensitivity();
if (ImGui::SliderFloat("鼠标敏感度", &sensitivity, 0.01f, 0.5f)) {
dpiStabilizer.setSensitivity(sensitivity);
}
});
imguiManager.registerWindow("控制面板", []() {
ImGuiIO& io = ImGui::GetIO();
ImGui::Text("FPS: %.1f", io.Framerate);
ImGui::Text("帧时间: %.3f ms", 1000.0f / io.Framerate);
});
// 启用深度测试
glEnable(GL_DEPTH_TEST);
float vertices[] = {
// 立方体顶点数据(位置 + 法线 + 纹理坐标)
// 位置(3) + 法线(3) + 纹理坐标(2) = 8个浮点数
// 前面
-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
};
// 创建VAO
unsigned int VAO;
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO); // 绑定VAO
// 创建VBO
unsigned int VBO;
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO); // 绑定VBO
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
int nrAttributes;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;
// 位置属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0); // 启用顶点属性
// 法线属性
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1); // 启用法线属性
// 纹理坐标属性
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2); // 启用纹理坐标属性
// 构建并编译着色器程序
Shader lightingShader("shader/lighting.vert", "shader/lighting.frag");
Shader lampShader("shader/lamp.vert", "shader/lamp.frag");
// 加载纹理
unsigned int texture1 = loadTexture("Assets/image.png");
if (texture1 == 0) {
std::cout << "Failed to load texture" << std::endl;
}
// 创建光源的 VAO
unsigned int lightVAO;
glGenVertexArrays(1, &lightVAO);
glBindVertexArray(lightVAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 光源位置
vec3 lightPos(1.2f, 1.0f, 2.0f);
// 相机位置(观察空间)
vec3 viewPos = dpiStabilizer.getCameraPosition();
// 渲染循环
while (!glfwWindowShouldClose(window)) {
// 处理输入
processInput(window);
glfwPollEvents();
// 从 DPI 稳定器获取 view 矩阵
mat4 view = dpiStabilizer.getViewMatrix();
float currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
// 渲染指令
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 使用光照着色器绘制物体
lightingShader.use();
// 绑定纹理
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
lightingShader.setInt("material.diffuse", 0);
// 设置相机位置(观察空间)
vec3 currentViewPos = dpiStabilizer.getCameraPosition();
vec3 cameraFront = dpiStabilizer.getCameraFront();
lightingShader.setMat4("projection", projection);
lightingShader.setMat4("view", view);
// 手电筒效果:使用相机位置和朝向
lightingShader.setVec3("light.position", currentViewPos.x, currentViewPos.y, currentViewPos.z);
lightingShader.setVec3("light.direction", cameraFront.x, cameraFront.y, cameraFront.z);
lightingShader.setFloat("light.cutOff", glm::cos(glm::radians(12.5f))); // 内切角
lightingShader.setFloat("light.outerCutOff", glm::cos(glm::radians(17.5f))); // 外切角
lightingShader.setFloat("light.constant", 1.0f);
lightingShader.setFloat("light.linear", 0.09f);
lightingShader.setFloat("light.quadratic", 0.032f);
lightingShader.setVec3("light.ambient", 0.1f, 0.1f, 0.1f); // 降低环境光强度,只有聚光灯照到的地方才亮
lightingShader.setVec3("light.diffuse", 1.0f, 1.0f, 1.0f); // 漫反射强度
lightingShader.setVec3("light.specular", 1.0f, 1.0f, 1.0f);
lightingShader.setVec3("material.specular", 0.633f, 0.727811f, 0.633f);
// 高光指数:通常乘以 128 (OpenGL 最大的 1.0 对应 128)
lightingShader.setFloat("material.shininess", 0.6f * 128.0f); // 76.8
// 设置相机位置(用于镜面反射计算)
lightingShader.setVec3("viewPos", currentViewPos.x, currentViewPos.y, currentViewPos.z);
glBindVertexArray(VAO);
// 定义多个立方体的位置(更分散的布局)
glm::vec3 cubePositions[] = {
glm::vec3( 0.0f, 0.0f, 0.0f),
glm::vec3( 4.0f, 0.0f, 0.0f),
glm::vec3(-4.0f, 0.0f, 0.0f),
glm::vec3( 0.0f, 0.0f, -8.0f),
glm::vec3( 0.0f, 0.0f, 8.0f),
glm::vec3( 8.0f, 0.0f, -8.0f),
glm::vec3(-10.0f, 0.0f, -2.0f),
glm::vec3( 2.0f, 0.0f, 2.0f),
glm::vec3(-2.0f, 0.0f, 2.0f),
glm::vec3( 0.0f, 2.0f, 0.0f)
};
for(unsigned int i = 0; i < 10; i++)
{
glm::mat4 model = glm::mat4(1.0f);
model = glm::translate(model, cubePositions[i]);
float angle = 20.0f * i;
model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
lightingShader.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
}
// 点光源立方体绘制(手电筒不需要显示光源立方体,已注释)
/*
lampShader.use();
lampShader.setMat4("projection", projection);
lampShader.setMat4("view", view);
// 光源立方体的模型矩阵
mat4 lampModel = mat4(1.0f);
lampModel = translate(lampModel, lightPos);
lampModel = scale(lampModel, vec3(0.2f)); // 缩小光源立方体
lampShader.setMat4("model", lampModel);
glBindVertexArray(lightVAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
*/
// 使用 ImGui 管理器渲染所有窗口
imguiManager.render();
// 交换缓冲区和检查IO事件
glfwSwapBuffers(window);
}
// 清理 ImGui 管理器
imguiManager.shutdown();
// 清理资源
glDeleteVertexArrays(1, &VAO);
glDeleteVertexArrays(1, &lightVAO);
glDeleteBuffers(1, &VBO);
glDeleteTextures(1, &texture1);
glfwTerminate();
return 0;
}
#version 330 core
out vec4 FragColor;
struct Material{
sampler2D diffuse;
vec3 specular;
float shininess;
};
struct Light{
vec3 position;
vec3 direction;
float cutOff;
float outerCutOff;
vec3 ambient;
vec3 diffuse;
vec3 specular;
float constant;
float linear;
float quadratic;
};
uniform Light light;
uniform vec3 viewPos;
uniform Material material;
in vec3 Normal;
in vec3 FragPos;
in vec2 TexCoords;
void main()
{
// 从纹理采样颜色
vec3 textureColor = vec3(texture(material.diffuse, TexCoords));
// 计算距离衰减
float distance = length(light.position - FragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance +
light.quadratic * (distance * distance));
// 环境光(先计算,因为无论是否在聚光灯范围内都需要)
vec3 ambient = light.ambient * textureColor;
// 计算聚光灯效果(手电筒)
vec3 lightDir = normalize(FragPos - light.position); // 从光源指向片段的方向
vec3 spotDir = normalize(light.direction); // 手电筒的朝向方向(相机朝向)
float theta = dot(lightDir, spotDir); // 计算角度(点积),如果 theta > cutOff 说明在聚光灯范围内
// 计算聚光灯强度(使用 clamp 方法,无需 if-else)
float intensity = clamp((theta - light.outerCutOff) / (light.cutOff - light.outerCutOff), 0.0, 1.0);
// 计算漫反射和镜面反射(无论强度如何都计算)
vec3 norm = normalize(Normal);
float diff = max(dot(norm, -lightDir), 0.0); // -lightDir 是从片段指向光源
vec3 diffuse = light.diffuse * (diff * textureColor);
// 镜面反射
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(lightDir, norm); // lightDir 是从光源指向片段(入射方向)
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 specular = light.specular * (spec * material.specular);
// 应用衰减和聚光灯强度
diffuse *= attenuation * intensity;
specular *= attenuation * intensity;
// 最终结果
vec3 result = ambient + diffuse + specular;
FragColor = vec4(result, 1.0);
}