平行光

当一个光源处于很远的地方时,来自光源的每条光线就会近似于互相平行。不论物体和/或者观察者的位置,看起来好像所有的光都来自于同一个方向。当我们使用一个假设光源处于无限远处的模型时,它就被称为定向光,因为它的所有光线都有着相同的方向,它与光源的位置是没有关系的。

定向光非常好的一个例子就是太阳。太阳距离我们并不是无限远,但它已经远到在光照计算中可以把它视为无限远了。所以来自太阳的所有光线将被模拟为平行光线,我们可以在下图看到:

img

因为所有的光线都是平行的,所以物体与光源的相对位置是不重要的,因为对场景中每一个物体光的方向都是一致的。由于光的位置向量保持一致,场景中每个物体的光照计算将会是类似的。

我们可以定义一个光线方向向量而不是位置向量来模拟一个定向光。着色器的计算基本保持不变,但这次我们将直接使用光的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;
}

先来来看看点光源: 我们需要一个衰减公式来表示光线随着距离增加而减小。

下面这个公式根据片段距光源的距离计算了衰减值,之后我们会将它乘以光的强度向量:

image-20251029202203628

在这里d代表了片段距光源的距离。接下来为了计算衰减值,我们定义3个(可配置的)项:常数项Kc、一次项Kl和二次项Kq。

  • 常数项通常保持为1.0,它的主要作用是保证分母永远不会比1小,否则的话在某些距离上它反而会增加强度,这肯定不是我们想要的效果。
  • 一次项会与距离值相乘,以线性的方式减少强度。
  • 二次项会与距离的平方相乘,让光源以二次递减的方式减少强度。二次项在距离比较小的时候影响会比一次项小很多,但当距离值比较大的时候它就会比一次项更大了。

由于二次项的存在,光线会在大部分时候以线性的方式衰退,直到距离变得足够大,让二次项超过一次项,光的强度会以更快的速度下降。这样的结果就是,光在近距离时亮度很高,但随着距离变远亮度迅速降低,最后会以更慢的速度减少亮度。下面这张图显示了在100的距离内衰减的效果:

image-20251029202400723

你可以看到光在近距离的时候有着最高的强度,但随着距离增长,它的强度明显减弱,并缓慢地在距离大约100的时候强度接近0。这正是我们想要的。

选择正确的值

但是,该对这三个项设置什么值呢?正确地设定它们的值取决于很多因素:环境、希望光覆盖的距离、光的类型等。在大多数情况下,这都是经验的问题,以及适量的调整。下面这个表格显示了模拟一个(大概)真实的,覆盖特定半径(距离)的光源时,这些项可能取的一些值。第一列指定的是在给定的三项时光所能覆盖的距离。这些值是大多数光源很好的起始点,它们由Ogre3D的Wiki所提供:

距离常数项一次项二次项
71.00.71.8
131.00.350.44
201.00.220.20
321.00.140.07
501.00.090.032
651.00.070.017
1001.00.0450.0075
1601.00.0270.0028
2001.00.0220.0019
3251.00.0140.0007
6001.00.0070.0002
32501.00.00140.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)来表示的,切光角指定了聚光的半径(译注:是圆锥的半径不是距光源距离那个半径)。对于每个片段,我们会计算片段是否位于聚光的切光方向之间(也就是在锥形内),如果是的话,我们就会相应地照亮片段。下面这张图会让你明白聚光是如何工作的:

img

  • 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。

我们可以用下面这个公式来计算这个值:

image-20251029204700049

很难去表现这个公式是怎么工作的,所以我们用一些实例值来看看:

θθ(角度)ϕ(内光切)ϕ(角度)γ(外光切)γ(角度)ϵI
0.87300.91250.82350.91 - 0.82 = 0.090.87 - 0.82 / 0.09 = 0.56
0.9260.91250.82350.91 - 0.82 = 0.090.9 - 0.82 / 0.09 = 0.89
0.97140.91250.82350.91 - 0.82 = 0.090.97 - 0.82 / 0.09 = 1.67
0.83340.91250.82350.91 - 0.82 = 0.090.83 - 0.82 / 0.09 = 0.11
0.64500.91250.82350.91 - 0.82 = 0.090.64 - 0.82 / 0.09 = -2.0
0.966150.997812.50.95317.50.9978 - 0.953 = 0.04480.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:img

完整代码:

#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);
}