[{"data":1,"prerenderedAt":6321},["ShallowReactive",2],{"page-\u002Fcpp\u002Fopengel\u002F光源和投光物":3},{"id":4,"title":5,"body":6,"description":6314,"extension":6315,"meta":6316,"navigation":154,"path":6317,"seo":6318,"stem":6319,"__hash__":6320},"content\u002Fcpp\u002Fopengel\u002F光源和投光物.md","平行光",{"type":7,"value":8,"toc":6309},"minimark",[9,12,21,24,30,45,56,59,2082,2085,2092,2098,2108,2121,2124,2130,2133,2137,2152,2335,2338,2341,4072,4075,4089,4092,4097,4123,4126,4130,4133,4144,4178,4181,4201,4204,4247,4250,4254,4261,4264,4267,4273,4276,4443,4454,4498,4505,4511,4514,6305],[10,11,5],"h1",{"id":5},[13,14,15,16,20],"p",{},"当一个光源处于很远的地方时，来自光源的每条光线就会近似于互相平行。不论物体和\u002F或者观察者的位置，看起来好像所有的光都来自于同一个方向。当我们使用一个假设光源处于",[17,18,19],"strong",{},"无限","远处的模型时，它就被称为定向光，因为它的所有光线都有着相同的方向，它与光源的位置是没有关系的。",[13,22,23],{},"定向光非常好的一个例子就是太阳。太阳距离我们并不是无限远，但它已经远到在光照计算中可以把它视为无限远了。所以来自太阳的所有光线将被模拟为平行光线，我们可以在下图看到：",[13,25,26],{},[27,28],"img",{"alt":27,"src":29},".\u002Fassets\u002Flight_casters_directional.png",[13,31,32,33,36,37,40,41,44],{},"因为所有的",[17,34,35],{},"光线都是平行的","，所以",[17,38,39],{},"物体与光源的相对位置是不重要的","，因为",[17,42,43],{},"对场景中每一个物体光的方向都是一致的。由于光的位置向量保持一致","，场景中每个物体的光照计算将会是类似的。",[13,46,47,48,51,52,55],{},"我们可以定义一个光线方向向量",[17,49,50],{},"而不是位置向量来模拟一个定向光","。着色器的计算基本保持不变，但这次我们将直接",[17,53,54],{},"使用光的direction向量","而不是通过position来计算lightDir向量。",[13,57,58],{},"将光源方块注释之后，完整的平行光代码为：",[60,61,66],"pre",{"className":62,"code":63,"language":64,"meta":65,"style":65},"language-c++ shiki shiki-themes github-light github-dark","#include \u003Cglad\u002Fglad.h>\n#include \u003CGLFW\u002Fglfw3.h>\n#include \u003Ciostream>\n#include \u003Ccmath>\n#include \u003Cimgui.h>\n#include \u003Cimgui_impl_glfw.h>\n#include \u003Cimgui_impl_opengl3.h>\n#include \u003Copencv2\u002Fopencv.hpp>\n#include \u003Copencv2\u002Fimgcodecs.hpp>\n#include \"utils\u002Fshader.h\"\n#include \"utils\u002FglmCompute.h\"\n#include \"include\u002Fdpi_stabilizer.h\"\n#include \"include\u002Fimgui_manager.h\"\n\nusing namespace glm;\nusing namespace cv;\n\n\u002F\u002F 使用 OpenCV 加载纹理\nunsigned int loadTexture(const char* path) {\n    unsigned int textureID;\n    glGenTextures(1, &textureID);\n    \n    \u002F\u002F 使用 OpenCV 读取图像\n    Mat image = imread(path, IMREAD_COLOR);\n    if (image.empty()) {\n        std::cout \u003C\u003C \"Failed to load texture at path: \" \u003C\u003C path \u003C\u003C std::endl;\n        return textureID;\n    }\n    \n    \u002F\u002F OpenCV 使用 BGR，OpenGL 使用 RGB，需要转换\n    Mat rgbImage;\n    cvtColor(image, rgbImage, COLOR_BGR2RGB);\n    \n    \u002F\u002F 翻转图像（OpenGL 期望的 Y 轴方向）\n    flip(rgbImage, rgbImage, 0);\n    \n    glBindTexture(GL_TEXTURE_2D, textureID);\n    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, rgbImage.cols, rgbImage.rows, 0, GL_RGB, GL_UNSIGNED_BYTE, rgbImage.data);\n    glGenerateMipmap(GL_TEXTURE_2D);\n    \n    \u002F\u002F 设置纹理参数\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n    \n    return textureID;\n}\n\n\u002F\u002F 窗口大小改变时的回调函数\nvoid framebuffer_size_callback(GLFWwindow* window, int width, int height) {\n    glViewport(0, 0, width, height);\n}\n\n\n\u002F\u002F 全局变量\nfloat deltaTime = 0.0f; \u002F\u002F 当前帧与上一帧的时间差\nfloat lastFrame = 0.0f; \u002F\u002F 上一帧的时间\n\n\u002F\u002F DPI 稳定器实例\nDPIStabilizer dpiStabilizer(0.1f, 50.0f, 0.8f);\n\n\u002F\u002F ImGui 管理器实例\nImGuiManager imguiManager;\n\n\u002F\u002F 简化的输入处理函数\nvoid processInput(GLFWwindow *window){\n    \u002F\u002F 使用 DPI 稳定器处理所有输入\n    dpiStabilizer.processKeyboardInput(window);\n\n    if (glfwGetKey(window, GLFW_KEY_F2) == GLFW_PRESS) {\n        imguiManager.toggleWindow(\"控制面板\");\n    }\n    if (glfwGetKey(window, GLFW_KEY_F3) == GLFW_PRESS) {\n        imguiManager.toggleWindow(\"性能监控\");\n    }\n    if (glfwGetKey(window, GLFW_KEY_F4) == GLFW_PRESS) {\n        imguiManager.toggleWindow(\"调试信息\");\n    }\n    \n    \u002F\u002F 显示统计信息 (按 T 键)\n    if (glfwGetKey(window, GLFW_KEY_T) == GLFW_PRESS) {\n        std::cout \u003C\u003C dpiStabilizer.getStats() \u003C\u003C std::endl;\n    }\n}\n\nint main() {\n    \u002F\u002F 投影矩阵\n    mat4 projection = perspective(radians(45.0f), (float)800 \u002F (float)600, 0.1f, 100.0f);\n\n    \u002F\u002F 初始化GLFW\n    glfwInit();\n    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);\n    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);\n    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);\n    \n    \u002F\u002F 设置 DPI 感知提示\n    DPIStabilizer::setDPIHint();\n\n    \u002F\u002F 创建窗口\n    GLFWwindow* window = glfwCreateWindow(800, 600, \"OpenGL Window\", NULL, NULL);\n    if (window == NULL) {\n        std::cout \u003C\u003C \"Failed to create GLFW window\" \u003C\u003C std::endl;\n        glfwTerminate();\n        return -1;\n    }\n\n    glfwMakeContextCurrent(window);\n    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);\n    \n    \u002F\u002F 初始化 DPI 稳定器\n    dpiStabilizer.initialize(window);\n\n    \u002F\u002F 初始化GLAD\n    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {\n        std::cout \u003C\u003C \"Failed to initialize GLAD\" \u003C\u003C std::endl;\n        return -1;\n    }\n\n    \u002F\u002F 初始化 ImGui 管理器\n    imguiManager.initialize(window);\n    \n    \u002F\u002F 注册窗口\n    imguiManager.registerWindow(\"Hello ImGui!\", []() {\n        ImGui::Text(\"DPI 稳定器状态: %s\", dpiStabilizer.isMouseCaptured() ? \"已捕获\" : \"已释放\");\n        ImGui::Text(\"鼠标敏感度: %.3f\", dpiStabilizer.getSensitivity());\n        \n        if (ImGui::Button(\"重置统计\")) {\n            dpiStabilizer.resetStats();\n        }\n        \n        float sensitivity = dpiStabilizer.getSensitivity();\n        if (ImGui::SliderFloat(\"鼠标敏感度\", &sensitivity, 0.01f, 0.5f)) {\n            dpiStabilizer.setSensitivity(sensitivity);\n        }\n    });\n    \n    imguiManager.registerWindow(\"控制面板\", []() {\n        ImGuiIO& io = ImGui::GetIO();\n        ImGui::Text(\"FPS: %.1f\", io.Framerate);\n        ImGui::Text(\"帧时间: %.3f ms\", 1000.0f \u002F io.Framerate);\n    });\n    \n\n    \u002F\u002F 启用深度测试\n    glEnable(GL_DEPTH_TEST);\n\n    float vertices[] = {\n    \u002F\u002F 立方体顶点数据（位置 + 法线 + 纹理坐标）\n    \u002F\u002F 位置(3) + 法线(3) + 纹理坐标(2) = 8个浮点数\n    \u002F\u002F 前面\n    -0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  0.0f, 0.0f,\n     0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  1.0f, 0.0f,\n     0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  1.0f, 1.0f,\n     0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  1.0f, 1.0f,\n    -0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  0.0f, 1.0f,\n    -0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  0.0f, 0.0f,\n    \n    \u002F\u002F 后面\n    -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f, 0.0f,\n     0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f, 0.0f,\n     0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f, 1.0f,\n     0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f, 1.0f,\n    -0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f, 1.0f,\n    -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f, 0.0f,\n    \n    \u002F\u002F 左面\n    -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,  1.0f, 0.0f,\n    -0.5f,  0.5f, -0.5f, -1.0f,  0.0f,  0.0f,  1.0f, 1.0f,\n    -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,  0.0f, 1.0f,\n    -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,  0.0f, 1.0f,\n    -0.5f, -0.5f,  0.5f, -1.0f,  0.0f,  0.0f,  0.0f, 0.0f,\n    -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,  1.0f, 0.0f,\n    \n    \u002F\u002F 右面\n     0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  1.0f, 0.0f,\n     0.5f,  0.5f, -0.5f,  1.0f,  0.0f,  0.0f,  1.0f, 1.0f,\n     0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,  0.0f, 1.0f,\n     0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,  0.0f, 1.0f,\n     0.5f, -0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  0.0f, 0.0f,\n     0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  1.0f, 0.0f,\n    \n    \u002F\u002F 下面\n    -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,  0.0f, 1.0f,\n     0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,  1.0f, 1.0f,\n     0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,  1.0f, 0.0f,\n     0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,  1.0f, 0.0f,\n    -0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,  0.0f, 0.0f,\n    -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,  0.0f, 1.0f,\n    \n    \u002F\u002F 上面\n    -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  0.0f, 1.0f,\n     0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  1.0f, 1.0f,\n     0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  1.0f, 0.0f,\n     0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  1.0f, 0.0f,\n    -0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  0.0f, 0.0f,\n    -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  0.0f, 1.0f\n};\n    \n    \u002F\u002F 创建VAO\n    unsigned int VAO;\n    glGenVertexArrays(1, &VAO);\n    glBindVertexArray(VAO); \u002F\u002F 绑定VAO\n\n    \u002F\u002F 创建VBO\n    unsigned int VBO;\n    glGenBuffers(1, &VBO);\n    glBindBuffer(GL_ARRAY_BUFFER, VBO); \u002F\u002F 绑定VBO\n    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);\n\n    int nrAttributes;\n    glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);\n    std::cout \u003C\u003C \"Maximum nr of vertex attributes supported: \" \u003C\u003C nrAttributes \u003C\u003C std::endl;\n\n    \u002F\u002F 位置属性\n    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);\n    glEnableVertexAttribArray(0); \u002F\u002F 启用顶点属性\n    \n    \u002F\u002F 法线属性\n    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));\n    glEnableVertexAttribArray(1); \u002F\u002F 启用法线属性\n    \n    \u002F\u002F 纹理坐标属性\n    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));\n    glEnableVertexAttribArray(2); \u002F\u002F 启用纹理坐标属性\n\n    \u002F\u002F 构建并编译着色器程序\n    Shader lightingShader(\"shader\u002Flighting.vert\", \"shader\u002Flighting.frag\");\n    Shader lampShader(\"shader\u002Flamp.vert\", \"shader\u002Flamp.frag\");\n\n    \u002F\u002F 加载纹理\n    unsigned int texture1 = loadTexture(\"Assets\u002Fimage.png\");\n    if (texture1 == 0) {\n        std::cout \u003C\u003C \"Failed to load texture\" \u003C\u003C std::endl;\n    }\n\n    \u002F\u002F 创建光源的 VAO\n    unsigned int lightVAO;\n    glGenVertexArrays(1, &lightVAO);\n    glBindVertexArray(lightVAO);\n    glBindBuffer(GL_ARRAY_BUFFER, VBO);\n    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);\n    glEnableVertexAttribArray(0);\n\n    \u002F\u002F 光源位置\n    vec3 lightPos(1.2f, 1.0f, 2.0f);\n    \n    \u002F\u002F 相机位置（观察空间）\n    vec3 viewPos = dpiStabilizer.getCameraPosition();\n\n    \u002F\u002F 渲染循环\n    while (!glfwWindowShouldClose(window)) {\n        \u002F\u002F 处理输入\n        processInput(window);\n        glfwPollEvents();\n\n        \u002F\u002F 从 DPI 稳定器获取 view 矩阵\n        mat4 view = dpiStabilizer.getViewMatrix();\n        \n        float currentFrame = glfwGetTime();\n        deltaTime = currentFrame - lastFrame;\n        lastFrame = currentFrame;\n\n        \u002F\u002F 渲染指令\n        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);\n        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);\n        \n        \u002F\u002F 使用光照着色器绘制物体\n        lightingShader.use();\n        \n        \u002F\u002F 绑定纹理\n        glActiveTexture(GL_TEXTURE0);\n        glBindTexture(GL_TEXTURE_2D, texture1);\n        lightingShader.setInt(\"material.diffuse\", 0);\n        \n        lightingShader.setMat4(\"projection\", projection);\n        lightingShader.setMat4(\"view\", view);\n        lightingShader.setVec3(\"light.direction\", -0.2f, -1.0f, -0.3f);\n        lightingShader.setVec3(\"light.ambient\",  1.0f, 1.0f, 1.0f);   \u002F\u002F 最大环境光强度\n        lightingShader.setVec3(\"light.diffuse\",  1.0f, 1.0f, 1.0f);   \u002F\u002F 增加漫反射强度\n        lightingShader.setVec3(\"light.specular\", 1.0f, 1.0f, 1.0f); \n        lightingShader.setVec3(\"material.specular\", 0.633f, 0.727811f, 0.633f);\n        \u002F\u002F 高光指数：通常乘以 128 (OpenGL 最大的 1.0 对应 128)\n        lightingShader.setFloat(\"material.shininess\", 0.6f * 128.0f); \u002F\u002F 76.8\n        \n        \u002F\u002F 设置相机位置（观察空间）\n        vec3 currentViewPos = dpiStabilizer.getCameraPosition();\n        lightingShader.setVec3(\"viewPos\", currentViewPos.x, currentViewPos.y, currentViewPos.z);\n        \n        glBindVertexArray(VAO);\n        \n        \u002F\u002F 定义多个立方体的位置（更分散的布局）\n        glm::vec3 cubePositions[] = {\n            glm::vec3( 0.0f,  0.0f,  0.0f),\n            glm::vec3( 2.0f,  0.0f,  0.0f),\n            glm::vec3(-2.0f,  0.0f,  0.0f),\n            glm::vec3( 0.0f,  0.0f, -2.0f),\n            glm::vec3( 0.0f,  0.0f,  2.0f),\n            glm::vec3( 2.0f,  0.0f, -2.0f),\n            glm::vec3(-2.0f,  0.0f, -2.0f),\n            glm::vec3( 2.0f,  0.0f,  2.0f),\n            glm::vec3(-2.0f,  0.0f,  2.0f),\n            glm::vec3( 0.0f,  2.0f,  0.0f)\n        };\n        \n        for(unsigned int i = 0; i \u003C 10; i++)\n        {\n            glm::mat4 model = glm::mat4(1.0f);\n            model = glm::translate(model, cubePositions[i]);\n            float angle = 20.0f * i;\n            model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));\n            lightingShader.setMat4(\"model\", model);\n\n            glDrawArrays(GL_TRIANGLES, 0, 36);\n        }\n        \n        \u002F\u002F 绘制光源立方体（平行光不需要光源立方体，已注释）\n        \u002F*\n        lampShader.use();\n        lampShader.setMat4(\"projection\", projection);\n        lampShader.setMat4(\"view\", view);\n        \n        \u002F\u002F 光源立方体的模型矩阵\n        mat4 lampModel = mat4(1.0f);\n        lampModel = translate(lampModel, lightPos);\n        lampModel = scale(lampModel, vec3(0.2f)); \u002F\u002F 缩小光源立方体\n        lampShader.setMat4(\"model\", lampModel);\n        \n        glBindVertexArray(lightVAO);\n        glDrawArrays(GL_TRIANGLES, 0, 36);\n        *\u002F\n        \n        \u002F\u002F 使用 ImGui 管理器渲染所有窗口\n        imguiManager.render();\n\n        \u002F\u002F 交换缓冲区和检查IO事件\n        glfwSwapBuffers(window);\n    }\n\n    \u002F\u002F 清理 ImGui 管理器\n    imguiManager.shutdown();\n\n    \u002F\u002F 清理资源\n    glDeleteVertexArrays(1, &VAO);\n    glDeleteVertexArrays(1, &lightVAO);\n    glDeleteBuffers(1, &VBO);\n    glDeleteTextures(1, &texture1);\n    glfwTerminate();\n    return 0;\n}\n\n","c++","",[67,68,69,77,83,89,95,101,107,113,119,125,131,137,143,149,156,162,168,173,179,185,191,197,203,209,215,221,227,233,239,244,250,256,262,267,273,279,284,290,296,302,307,313,319,325,331,337,342,348,354,359,365,371,377,382,387,392,398,404,410,415,421,427,432,438,444,449,455,461,467,473,478,484,490,495,501,507,512,518,524,529,534,540,546,552,557,562,567,573,579,585,590,596,602,608,614,620,625,631,637,642,648,654,660,666,672,678,683,688,694,700,705,711,717,722,728,734,740,745,750,755,761,767,772,778,784,790,796,802,808,814,820,825,831,837,843,848,854,859,865,871,877,883,888,893,898,904,910,915,921,927,933,939,945,951,957,962,968,973,978,984,990,996,1002,1007,1013,1018,1023,1029,1035,1041,1047,1052,1058,1063,1068,1074,1080,1086,1092,1097,1103,1108,1113,1119,1125,1131,1137,1142,1148,1153,1158,1164,1170,1176,1182,1187,1193,1199,1205,1210,1216,1222,1228,1234,1239,1245,1251,1257,1263,1269,1274,1280,1286,1292,1297,1303,1309,1315,1320,1326,1332,1338,1343,1349,1355,1361,1366,1372,1378,1384,1389,1395,1401,1407,1413,1418,1423,1429,1435,1441,1447,1453,1458,1464,1469,1475,1481,1486,1492,1498,1503,1509,1515,1521,1527,1533,1538,1544,1550,1555,1561,1567,1573,1578,1584,1590,1596,1601,1607,1613,1618,1624,1630,1636,1642,1647,1653,1659,1665,1671,1677,1683,1689,1695,1701,1706,1712,1718,1724,1729,1735,1740,1746,1752,1758,1764,1770,1776,1782,1788,1794,1800,1806,1812,1818,1823,1829,1835,1841,1847,1853,1859,1865,1870,1876,1881,1886,1892,1898,1904,1910,1916,1921,1927,1933,1939,1945,1951,1956,1962,1968,1974,1979,1985,1991,1996,2002,2008,2013,2018,2024,2030,2035,2041,2047,2053,2059,2065,2071,2077],"code",{"__ignoreMap":65},[70,71,74],"span",{"class":72,"line":73},"line",1,[70,75,76],{},"#include \u003Cglad\u002Fglad.h>\n",[70,78,80],{"class":72,"line":79},2,[70,81,82],{},"#include \u003CGLFW\u002Fglfw3.h>\n",[70,84,86],{"class":72,"line":85},3,[70,87,88],{},"#include \u003Ciostream>\n",[70,90,92],{"class":72,"line":91},4,[70,93,94],{},"#include \u003Ccmath>\n",[70,96,98],{"class":72,"line":97},5,[70,99,100],{},"#include \u003Cimgui.h>\n",[70,102,104],{"class":72,"line":103},6,[70,105,106],{},"#include \u003Cimgui_impl_glfw.h>\n",[70,108,110],{"class":72,"line":109},7,[70,111,112],{},"#include \u003Cimgui_impl_opengl3.h>\n",[70,114,116],{"class":72,"line":115},8,[70,117,118],{},"#include \u003Copencv2\u002Fopencv.hpp>\n",[70,120,122],{"class":72,"line":121},9,[70,123,124],{},"#include \u003Copencv2\u002Fimgcodecs.hpp>\n",[70,126,128],{"class":72,"line":127},10,[70,129,130],{},"#include \"utils\u002Fshader.h\"\n",[70,132,134],{"class":72,"line":133},11,[70,135,136],{},"#include \"utils\u002FglmCompute.h\"\n",[70,138,140],{"class":72,"line":139},12,[70,141,142],{},"#include \"include\u002Fdpi_stabilizer.h\"\n",[70,144,146],{"class":72,"line":145},13,[70,147,148],{},"#include \"include\u002Fimgui_manager.h\"\n",[70,150,152],{"class":72,"line":151},14,[70,153,155],{"emptyLinePlaceholder":154},true,"\n",[70,157,159],{"class":72,"line":158},15,[70,160,161],{},"using namespace glm;\n",[70,163,165],{"class":72,"line":164},16,[70,166,167],{},"using namespace cv;\n",[70,169,171],{"class":72,"line":170},17,[70,172,155],{"emptyLinePlaceholder":154},[70,174,176],{"class":72,"line":175},18,[70,177,178],{},"\u002F\u002F 使用 OpenCV 加载纹理\n",[70,180,182],{"class":72,"line":181},19,[70,183,184],{},"unsigned int loadTexture(const char* path) {\n",[70,186,188],{"class":72,"line":187},20,[70,189,190],{},"    unsigned int textureID;\n",[70,192,194],{"class":72,"line":193},21,[70,195,196],{},"    glGenTextures(1, &textureID);\n",[70,198,200],{"class":72,"line":199},22,[70,201,202],{},"    \n",[70,204,206],{"class":72,"line":205},23,[70,207,208],{},"    \u002F\u002F 使用 OpenCV 读取图像\n",[70,210,212],{"class":72,"line":211},24,[70,213,214],{},"    Mat image = imread(path, IMREAD_COLOR);\n",[70,216,218],{"class":72,"line":217},25,[70,219,220],{},"    if (image.empty()) {\n",[70,222,224],{"class":72,"line":223},26,[70,225,226],{},"        std::cout \u003C\u003C \"Failed to load texture at path: \" \u003C\u003C path \u003C\u003C std::endl;\n",[70,228,230],{"class":72,"line":229},27,[70,231,232],{},"        return textureID;\n",[70,234,236],{"class":72,"line":235},28,[70,237,238],{},"    }\n",[70,240,242],{"class":72,"line":241},29,[70,243,202],{},[70,245,247],{"class":72,"line":246},30,[70,248,249],{},"    \u002F\u002F OpenCV 使用 BGR，OpenGL 使用 RGB，需要转换\n",[70,251,253],{"class":72,"line":252},31,[70,254,255],{},"    Mat rgbImage;\n",[70,257,259],{"class":72,"line":258},32,[70,260,261],{},"    cvtColor(image, rgbImage, COLOR_BGR2RGB);\n",[70,263,265],{"class":72,"line":264},33,[70,266,202],{},[70,268,270],{"class":72,"line":269},34,[70,271,272],{},"    \u002F\u002F 翻转图像（OpenGL 期望的 Y 轴方向）\n",[70,274,276],{"class":72,"line":275},35,[70,277,278],{},"    flip(rgbImage, rgbImage, 0);\n",[70,280,282],{"class":72,"line":281},36,[70,283,202],{},[70,285,287],{"class":72,"line":286},37,[70,288,289],{},"    glBindTexture(GL_TEXTURE_2D, textureID);\n",[70,291,293],{"class":72,"line":292},38,[70,294,295],{},"    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, rgbImage.cols, rgbImage.rows, 0, GL_RGB, GL_UNSIGNED_BYTE, rgbImage.data);\n",[70,297,299],{"class":72,"line":298},39,[70,300,301],{},"    glGenerateMipmap(GL_TEXTURE_2D);\n",[70,303,305],{"class":72,"line":304},40,[70,306,202],{},[70,308,310],{"class":72,"line":309},41,[70,311,312],{},"    \u002F\u002F 设置纹理参数\n",[70,314,316],{"class":72,"line":315},42,[70,317,318],{},"    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);\n",[70,320,322],{"class":72,"line":321},43,[70,323,324],{},"    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);\n",[70,326,328],{"class":72,"line":327},44,[70,329,330],{},"    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);\n",[70,332,334],{"class":72,"line":333},45,[70,335,336],{},"    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n",[70,338,340],{"class":72,"line":339},46,[70,341,202],{},[70,343,345],{"class":72,"line":344},47,[70,346,347],{},"    return textureID;\n",[70,349,351],{"class":72,"line":350},48,[70,352,353],{},"}\n",[70,355,357],{"class":72,"line":356},49,[70,358,155],{"emptyLinePlaceholder":154},[70,360,362],{"class":72,"line":361},50,[70,363,364],{},"\u002F\u002F 窗口大小改变时的回调函数\n",[70,366,368],{"class":72,"line":367},51,[70,369,370],{},"void framebuffer_size_callback(GLFWwindow* window, int width, int height) {\n",[70,372,374],{"class":72,"line":373},52,[70,375,376],{},"    glViewport(0, 0, width, height);\n",[70,378,380],{"class":72,"line":379},53,[70,381,353],{},[70,383,385],{"class":72,"line":384},54,[70,386,155],{"emptyLinePlaceholder":154},[70,388,390],{"class":72,"line":389},55,[70,391,155],{"emptyLinePlaceholder":154},[70,393,395],{"class":72,"line":394},56,[70,396,397],{},"\u002F\u002F 全局变量\n",[70,399,401],{"class":72,"line":400},57,[70,402,403],{},"float deltaTime = 0.0f; \u002F\u002F 当前帧与上一帧的时间差\n",[70,405,407],{"class":72,"line":406},58,[70,408,409],{},"float lastFrame = 0.0f; \u002F\u002F 上一帧的时间\n",[70,411,413],{"class":72,"line":412},59,[70,414,155],{"emptyLinePlaceholder":154},[70,416,418],{"class":72,"line":417},60,[70,419,420],{},"\u002F\u002F DPI 稳定器实例\n",[70,422,424],{"class":72,"line":423},61,[70,425,426],{},"DPIStabilizer dpiStabilizer(0.1f, 50.0f, 0.8f);\n",[70,428,430],{"class":72,"line":429},62,[70,431,155],{"emptyLinePlaceholder":154},[70,433,435],{"class":72,"line":434},63,[70,436,437],{},"\u002F\u002F ImGui 管理器实例\n",[70,439,441],{"class":72,"line":440},64,[70,442,443],{},"ImGuiManager imguiManager;\n",[70,445,447],{"class":72,"line":446},65,[70,448,155],{"emptyLinePlaceholder":154},[70,450,452],{"class":72,"line":451},66,[70,453,454],{},"\u002F\u002F 简化的输入处理函数\n",[70,456,458],{"class":72,"line":457},67,[70,459,460],{},"void processInput(GLFWwindow *window){\n",[70,462,464],{"class":72,"line":463},68,[70,465,466],{},"    \u002F\u002F 使用 DPI 稳定器处理所有输入\n",[70,468,470],{"class":72,"line":469},69,[70,471,472],{},"    dpiStabilizer.processKeyboardInput(window);\n",[70,474,476],{"class":72,"line":475},70,[70,477,155],{"emptyLinePlaceholder":154},[70,479,481],{"class":72,"line":480},71,[70,482,483],{},"    if (glfwGetKey(window, GLFW_KEY_F2) == GLFW_PRESS) {\n",[70,485,487],{"class":72,"line":486},72,[70,488,489],{},"        imguiManager.toggleWindow(\"控制面板\");\n",[70,491,493],{"class":72,"line":492},73,[70,494,238],{},[70,496,498],{"class":72,"line":497},74,[70,499,500],{},"    if (glfwGetKey(window, GLFW_KEY_F3) == GLFW_PRESS) {\n",[70,502,504],{"class":72,"line":503},75,[70,505,506],{},"        imguiManager.toggleWindow(\"性能监控\");\n",[70,508,510],{"class":72,"line":509},76,[70,511,238],{},[70,513,515],{"class":72,"line":514},77,[70,516,517],{},"    if (glfwGetKey(window, GLFW_KEY_F4) == GLFW_PRESS) {\n",[70,519,521],{"class":72,"line":520},78,[70,522,523],{},"        imguiManager.toggleWindow(\"调试信息\");\n",[70,525,527],{"class":72,"line":526},79,[70,528,238],{},[70,530,532],{"class":72,"line":531},80,[70,533,202],{},[70,535,537],{"class":72,"line":536},81,[70,538,539],{},"    \u002F\u002F 显示统计信息 (按 T 键)\n",[70,541,543],{"class":72,"line":542},82,[70,544,545],{},"    if (glfwGetKey(window, GLFW_KEY_T) == GLFW_PRESS) {\n",[70,547,549],{"class":72,"line":548},83,[70,550,551],{},"        std::cout \u003C\u003C dpiStabilizer.getStats() \u003C\u003C std::endl;\n",[70,553,555],{"class":72,"line":554},84,[70,556,238],{},[70,558,560],{"class":72,"line":559},85,[70,561,353],{},[70,563,565],{"class":72,"line":564},86,[70,566,155],{"emptyLinePlaceholder":154},[70,568,570],{"class":72,"line":569},87,[70,571,572],{},"int main() {\n",[70,574,576],{"class":72,"line":575},88,[70,577,578],{},"    \u002F\u002F 投影矩阵\n",[70,580,582],{"class":72,"line":581},89,[70,583,584],{},"    mat4 projection = perspective(radians(45.0f), (float)800 \u002F (float)600, 0.1f, 100.0f);\n",[70,586,588],{"class":72,"line":587},90,[70,589,155],{"emptyLinePlaceholder":154},[70,591,593],{"class":72,"line":592},91,[70,594,595],{},"    \u002F\u002F 初始化GLFW\n",[70,597,599],{"class":72,"line":598},92,[70,600,601],{},"    glfwInit();\n",[70,603,605],{"class":72,"line":604},93,[70,606,607],{},"    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);\n",[70,609,611],{"class":72,"line":610},94,[70,612,613],{},"    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);\n",[70,615,617],{"class":72,"line":616},95,[70,618,619],{},"    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);\n",[70,621,623],{"class":72,"line":622},96,[70,624,202],{},[70,626,628],{"class":72,"line":627},97,[70,629,630],{},"    \u002F\u002F 设置 DPI 感知提示\n",[70,632,634],{"class":72,"line":633},98,[70,635,636],{},"    DPIStabilizer::setDPIHint();\n",[70,638,640],{"class":72,"line":639},99,[70,641,155],{"emptyLinePlaceholder":154},[70,643,645],{"class":72,"line":644},100,[70,646,647],{},"    \u002F\u002F 创建窗口\n",[70,649,651],{"class":72,"line":650},101,[70,652,653],{},"    GLFWwindow* window = glfwCreateWindow(800, 600, \"OpenGL Window\", NULL, NULL);\n",[70,655,657],{"class":72,"line":656},102,[70,658,659],{},"    if (window == NULL) {\n",[70,661,663],{"class":72,"line":662},103,[70,664,665],{},"        std::cout \u003C\u003C \"Failed to create GLFW window\" \u003C\u003C std::endl;\n",[70,667,669],{"class":72,"line":668},104,[70,670,671],{},"        glfwTerminate();\n",[70,673,675],{"class":72,"line":674},105,[70,676,677],{},"        return -1;\n",[70,679,681],{"class":72,"line":680},106,[70,682,238],{},[70,684,686],{"class":72,"line":685},107,[70,687,155],{"emptyLinePlaceholder":154},[70,689,691],{"class":72,"line":690},108,[70,692,693],{},"    glfwMakeContextCurrent(window);\n",[70,695,697],{"class":72,"line":696},109,[70,698,699],{},"    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);\n",[70,701,703],{"class":72,"line":702},110,[70,704,202],{},[70,706,708],{"class":72,"line":707},111,[70,709,710],{},"    \u002F\u002F 初始化 DPI 稳定器\n",[70,712,714],{"class":72,"line":713},112,[70,715,716],{},"    dpiStabilizer.initialize(window);\n",[70,718,720],{"class":72,"line":719},113,[70,721,155],{"emptyLinePlaceholder":154},[70,723,725],{"class":72,"line":724},114,[70,726,727],{},"    \u002F\u002F 初始化GLAD\n",[70,729,731],{"class":72,"line":730},115,[70,732,733],{},"    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {\n",[70,735,737],{"class":72,"line":736},116,[70,738,739],{},"        std::cout \u003C\u003C \"Failed to initialize GLAD\" \u003C\u003C std::endl;\n",[70,741,743],{"class":72,"line":742},117,[70,744,677],{},[70,746,748],{"class":72,"line":747},118,[70,749,238],{},[70,751,753],{"class":72,"line":752},119,[70,754,155],{"emptyLinePlaceholder":154},[70,756,758],{"class":72,"line":757},120,[70,759,760],{},"    \u002F\u002F 初始化 ImGui 管理器\n",[70,762,764],{"class":72,"line":763},121,[70,765,766],{},"    imguiManager.initialize(window);\n",[70,768,770],{"class":72,"line":769},122,[70,771,202],{},[70,773,775],{"class":72,"line":774},123,[70,776,777],{},"    \u002F\u002F 注册窗口\n",[70,779,781],{"class":72,"line":780},124,[70,782,783],{},"    imguiManager.registerWindow(\"Hello ImGui!\", []() {\n",[70,785,787],{"class":72,"line":786},125,[70,788,789],{},"        ImGui::Text(\"DPI 稳定器状态: %s\", dpiStabilizer.isMouseCaptured() ? \"已捕获\" : \"已释放\");\n",[70,791,793],{"class":72,"line":792},126,[70,794,795],{},"        ImGui::Text(\"鼠标敏感度: %.3f\", dpiStabilizer.getSensitivity());\n",[70,797,799],{"class":72,"line":798},127,[70,800,801],{},"        \n",[70,803,805],{"class":72,"line":804},128,[70,806,807],{},"        if (ImGui::Button(\"重置统计\")) {\n",[70,809,811],{"class":72,"line":810},129,[70,812,813],{},"            dpiStabilizer.resetStats();\n",[70,815,817],{"class":72,"line":816},130,[70,818,819],{},"        }\n",[70,821,823],{"class":72,"line":822},131,[70,824,801],{},[70,826,828],{"class":72,"line":827},132,[70,829,830],{},"        float sensitivity = dpiStabilizer.getSensitivity();\n",[70,832,834],{"class":72,"line":833},133,[70,835,836],{},"        if (ImGui::SliderFloat(\"鼠标敏感度\", &sensitivity, 0.01f, 0.5f)) {\n",[70,838,840],{"class":72,"line":839},134,[70,841,842],{},"            dpiStabilizer.setSensitivity(sensitivity);\n",[70,844,846],{"class":72,"line":845},135,[70,847,819],{},[70,849,851],{"class":72,"line":850},136,[70,852,853],{},"    });\n",[70,855,857],{"class":72,"line":856},137,[70,858,202],{},[70,860,862],{"class":72,"line":861},138,[70,863,864],{},"    imguiManager.registerWindow(\"控制面板\", []() {\n",[70,866,868],{"class":72,"line":867},139,[70,869,870],{},"        ImGuiIO& io = ImGui::GetIO();\n",[70,872,874],{"class":72,"line":873},140,[70,875,876],{},"        ImGui::Text(\"FPS: %.1f\", io.Framerate);\n",[70,878,880],{"class":72,"line":879},141,[70,881,882],{},"        ImGui::Text(\"帧时间: %.3f ms\", 1000.0f \u002F io.Framerate);\n",[70,884,886],{"class":72,"line":885},142,[70,887,853],{},[70,889,891],{"class":72,"line":890},143,[70,892,202],{},[70,894,896],{"class":72,"line":895},144,[70,897,155],{"emptyLinePlaceholder":154},[70,899,901],{"class":72,"line":900},145,[70,902,903],{},"    \u002F\u002F 启用深度测试\n",[70,905,907],{"class":72,"line":906},146,[70,908,909],{},"    glEnable(GL_DEPTH_TEST);\n",[70,911,913],{"class":72,"line":912},147,[70,914,155],{"emptyLinePlaceholder":154},[70,916,918],{"class":72,"line":917},148,[70,919,920],{},"    float vertices[] = {\n",[70,922,924],{"class":72,"line":923},149,[70,925,926],{},"    \u002F\u002F 立方体顶点数据（位置 + 法线 + 纹理坐标）\n",[70,928,930],{"class":72,"line":929},150,[70,931,932],{},"    \u002F\u002F 位置(3) + 法线(3) + 纹理坐标(2) = 8个浮点数\n",[70,934,936],{"class":72,"line":935},151,[70,937,938],{},"    \u002F\u002F 前面\n",[70,940,942],{"class":72,"line":941},152,[70,943,944],{},"    -0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  0.0f, 0.0f,\n",[70,946,948],{"class":72,"line":947},153,[70,949,950],{},"     0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  1.0f, 0.0f,\n",[70,952,954],{"class":72,"line":953},154,[70,955,956],{},"     0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  1.0f, 1.0f,\n",[70,958,960],{"class":72,"line":959},155,[70,961,956],{},[70,963,965],{"class":72,"line":964},156,[70,966,967],{},"    -0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  0.0f, 1.0f,\n",[70,969,971],{"class":72,"line":970},157,[70,972,944],{},[70,974,976],{"class":72,"line":975},158,[70,977,202],{},[70,979,981],{"class":72,"line":980},159,[70,982,983],{},"    \u002F\u002F 后面\n",[70,985,987],{"class":72,"line":986},160,[70,988,989],{},"    -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f, 0.0f,\n",[70,991,993],{"class":72,"line":992},161,[70,994,995],{},"     0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f, 0.0f,\n",[70,997,999],{"class":72,"line":998},162,[70,1000,1001],{},"     0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f, 1.0f,\n",[70,1003,1005],{"class":72,"line":1004},163,[70,1006,1001],{},[70,1008,1010],{"class":72,"line":1009},164,[70,1011,1012],{},"    -0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f, 1.0f,\n",[70,1014,1016],{"class":72,"line":1015},165,[70,1017,989],{},[70,1019,1021],{"class":72,"line":1020},166,[70,1022,202],{},[70,1024,1026],{"class":72,"line":1025},167,[70,1027,1028],{},"    \u002F\u002F 左面\n",[70,1030,1032],{"class":72,"line":1031},168,[70,1033,1034],{},"    -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,  1.0f, 0.0f,\n",[70,1036,1038],{"class":72,"line":1037},169,[70,1039,1040],{},"    -0.5f,  0.5f, -0.5f, -1.0f,  0.0f,  0.0f,  1.0f, 1.0f,\n",[70,1042,1044],{"class":72,"line":1043},170,[70,1045,1046],{},"    -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,  0.0f, 1.0f,\n",[70,1048,1050],{"class":72,"line":1049},171,[70,1051,1046],{},[70,1053,1055],{"class":72,"line":1054},172,[70,1056,1057],{},"    -0.5f, -0.5f,  0.5f, -1.0f,  0.0f,  0.0f,  0.0f, 0.0f,\n",[70,1059,1061],{"class":72,"line":1060},173,[70,1062,1034],{},[70,1064,1066],{"class":72,"line":1065},174,[70,1067,202],{},[70,1069,1071],{"class":72,"line":1070},175,[70,1072,1073],{},"    \u002F\u002F 右面\n",[70,1075,1077],{"class":72,"line":1076},176,[70,1078,1079],{},"     0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  1.0f, 0.0f,\n",[70,1081,1083],{"class":72,"line":1082},177,[70,1084,1085],{},"     0.5f,  0.5f, -0.5f,  1.0f,  0.0f,  0.0f,  1.0f, 1.0f,\n",[70,1087,1089],{"class":72,"line":1088},178,[70,1090,1091],{},"     0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,  0.0f, 1.0f,\n",[70,1093,1095],{"class":72,"line":1094},179,[70,1096,1091],{},[70,1098,1100],{"class":72,"line":1099},180,[70,1101,1102],{},"     0.5f, -0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  0.0f, 0.0f,\n",[70,1104,1106],{"class":72,"line":1105},181,[70,1107,1079],{},[70,1109,1111],{"class":72,"line":1110},182,[70,1112,202],{},[70,1114,1116],{"class":72,"line":1115},183,[70,1117,1118],{},"    \u002F\u002F 下面\n",[70,1120,1122],{"class":72,"line":1121},184,[70,1123,1124],{},"    -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,  0.0f, 1.0f,\n",[70,1126,1128],{"class":72,"line":1127},185,[70,1129,1130],{},"     0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,  1.0f, 1.0f,\n",[70,1132,1134],{"class":72,"line":1133},186,[70,1135,1136],{},"     0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,  1.0f, 0.0f,\n",[70,1138,1140],{"class":72,"line":1139},187,[70,1141,1136],{},[70,1143,1145],{"class":72,"line":1144},188,[70,1146,1147],{},"    -0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,  0.0f, 0.0f,\n",[70,1149,1151],{"class":72,"line":1150},189,[70,1152,1124],{},[70,1154,1156],{"class":72,"line":1155},190,[70,1157,202],{},[70,1159,1161],{"class":72,"line":1160},191,[70,1162,1163],{},"    \u002F\u002F 上面\n",[70,1165,1167],{"class":72,"line":1166},192,[70,1168,1169],{},"    -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  0.0f, 1.0f,\n",[70,1171,1173],{"class":72,"line":1172},193,[70,1174,1175],{},"     0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  1.0f, 1.0f,\n",[70,1177,1179],{"class":72,"line":1178},194,[70,1180,1181],{},"     0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  1.0f, 0.0f,\n",[70,1183,1185],{"class":72,"line":1184},195,[70,1186,1181],{},[70,1188,1190],{"class":72,"line":1189},196,[70,1191,1192],{},"    -0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  0.0f, 0.0f,\n",[70,1194,1196],{"class":72,"line":1195},197,[70,1197,1198],{},"    -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  0.0f, 1.0f\n",[70,1200,1202],{"class":72,"line":1201},198,[70,1203,1204],{},"};\n",[70,1206,1208],{"class":72,"line":1207},199,[70,1209,202],{},[70,1211,1213],{"class":72,"line":1212},200,[70,1214,1215],{},"    \u002F\u002F 创建VAO\n",[70,1217,1219],{"class":72,"line":1218},201,[70,1220,1221],{},"    unsigned int VAO;\n",[70,1223,1225],{"class":72,"line":1224},202,[70,1226,1227],{},"    glGenVertexArrays(1, &VAO);\n",[70,1229,1231],{"class":72,"line":1230},203,[70,1232,1233],{},"    glBindVertexArray(VAO); \u002F\u002F 绑定VAO\n",[70,1235,1237],{"class":72,"line":1236},204,[70,1238,155],{"emptyLinePlaceholder":154},[70,1240,1242],{"class":72,"line":1241},205,[70,1243,1244],{},"    \u002F\u002F 创建VBO\n",[70,1246,1248],{"class":72,"line":1247},206,[70,1249,1250],{},"    unsigned int VBO;\n",[70,1252,1254],{"class":72,"line":1253},207,[70,1255,1256],{},"    glGenBuffers(1, &VBO);\n",[70,1258,1260],{"class":72,"line":1259},208,[70,1261,1262],{},"    glBindBuffer(GL_ARRAY_BUFFER, VBO); \u002F\u002F 绑定VBO\n",[70,1264,1266],{"class":72,"line":1265},209,[70,1267,1268],{},"    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);\n",[70,1270,1272],{"class":72,"line":1271},210,[70,1273,155],{"emptyLinePlaceholder":154},[70,1275,1277],{"class":72,"line":1276},211,[70,1278,1279],{},"    int nrAttributes;\n",[70,1281,1283],{"class":72,"line":1282},212,[70,1284,1285],{},"    glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);\n",[70,1287,1289],{"class":72,"line":1288},213,[70,1290,1291],{},"    std::cout \u003C\u003C \"Maximum nr of vertex attributes supported: \" \u003C\u003C nrAttributes \u003C\u003C std::endl;\n",[70,1293,1295],{"class":72,"line":1294},214,[70,1296,155],{"emptyLinePlaceholder":154},[70,1298,1300],{"class":72,"line":1299},215,[70,1301,1302],{},"    \u002F\u002F 位置属性\n",[70,1304,1306],{"class":72,"line":1305},216,[70,1307,1308],{},"    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);\n",[70,1310,1312],{"class":72,"line":1311},217,[70,1313,1314],{},"    glEnableVertexAttribArray(0); \u002F\u002F 启用顶点属性\n",[70,1316,1318],{"class":72,"line":1317},218,[70,1319,202],{},[70,1321,1323],{"class":72,"line":1322},219,[70,1324,1325],{},"    \u002F\u002F 法线属性\n",[70,1327,1329],{"class":72,"line":1328},220,[70,1330,1331],{},"    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));\n",[70,1333,1335],{"class":72,"line":1334},221,[70,1336,1337],{},"    glEnableVertexAttribArray(1); \u002F\u002F 启用法线属性\n",[70,1339,1341],{"class":72,"line":1340},222,[70,1342,202],{},[70,1344,1346],{"class":72,"line":1345},223,[70,1347,1348],{},"    \u002F\u002F 纹理坐标属性\n",[70,1350,1352],{"class":72,"line":1351},224,[70,1353,1354],{},"    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));\n",[70,1356,1358],{"class":72,"line":1357},225,[70,1359,1360],{},"    glEnableVertexAttribArray(2); \u002F\u002F 启用纹理坐标属性\n",[70,1362,1364],{"class":72,"line":1363},226,[70,1365,155],{"emptyLinePlaceholder":154},[70,1367,1369],{"class":72,"line":1368},227,[70,1370,1371],{},"    \u002F\u002F 构建并编译着色器程序\n",[70,1373,1375],{"class":72,"line":1374},228,[70,1376,1377],{},"    Shader lightingShader(\"shader\u002Flighting.vert\", \"shader\u002Flighting.frag\");\n",[70,1379,1381],{"class":72,"line":1380},229,[70,1382,1383],{},"    Shader lampShader(\"shader\u002Flamp.vert\", \"shader\u002Flamp.frag\");\n",[70,1385,1387],{"class":72,"line":1386},230,[70,1388,155],{"emptyLinePlaceholder":154},[70,1390,1392],{"class":72,"line":1391},231,[70,1393,1394],{},"    \u002F\u002F 加载纹理\n",[70,1396,1398],{"class":72,"line":1397},232,[70,1399,1400],{},"    unsigned int texture1 = loadTexture(\"Assets\u002Fimage.png\");\n",[70,1402,1404],{"class":72,"line":1403},233,[70,1405,1406],{},"    if (texture1 == 0) {\n",[70,1408,1410],{"class":72,"line":1409},234,[70,1411,1412],{},"        std::cout \u003C\u003C \"Failed to load texture\" \u003C\u003C std::endl;\n",[70,1414,1416],{"class":72,"line":1415},235,[70,1417,238],{},[70,1419,1421],{"class":72,"line":1420},236,[70,1422,155],{"emptyLinePlaceholder":154},[70,1424,1426],{"class":72,"line":1425},237,[70,1427,1428],{},"    \u002F\u002F 创建光源的 VAO\n",[70,1430,1432],{"class":72,"line":1431},238,[70,1433,1434],{},"    unsigned int lightVAO;\n",[70,1436,1438],{"class":72,"line":1437},239,[70,1439,1440],{},"    glGenVertexArrays(1, &lightVAO);\n",[70,1442,1444],{"class":72,"line":1443},240,[70,1445,1446],{},"    glBindVertexArray(lightVAO);\n",[70,1448,1450],{"class":72,"line":1449},241,[70,1451,1452],{},"    glBindBuffer(GL_ARRAY_BUFFER, VBO);\n",[70,1454,1456],{"class":72,"line":1455},242,[70,1457,1308],{},[70,1459,1461],{"class":72,"line":1460},243,[70,1462,1463],{},"    glEnableVertexAttribArray(0);\n",[70,1465,1467],{"class":72,"line":1466},244,[70,1468,155],{"emptyLinePlaceholder":154},[70,1470,1472],{"class":72,"line":1471},245,[70,1473,1474],{},"    \u002F\u002F 光源位置\n",[70,1476,1478],{"class":72,"line":1477},246,[70,1479,1480],{},"    vec3 lightPos(1.2f, 1.0f, 2.0f);\n",[70,1482,1484],{"class":72,"line":1483},247,[70,1485,202],{},[70,1487,1489],{"class":72,"line":1488},248,[70,1490,1491],{},"    \u002F\u002F 相机位置（观察空间）\n",[70,1493,1495],{"class":72,"line":1494},249,[70,1496,1497],{},"    vec3 viewPos = dpiStabilizer.getCameraPosition();\n",[70,1499,1501],{"class":72,"line":1500},250,[70,1502,155],{"emptyLinePlaceholder":154},[70,1504,1506],{"class":72,"line":1505},251,[70,1507,1508],{},"    \u002F\u002F 渲染循环\n",[70,1510,1512],{"class":72,"line":1511},252,[70,1513,1514],{},"    while (!glfwWindowShouldClose(window)) {\n",[70,1516,1518],{"class":72,"line":1517},253,[70,1519,1520],{},"        \u002F\u002F 处理输入\n",[70,1522,1524],{"class":72,"line":1523},254,[70,1525,1526],{},"        processInput(window);\n",[70,1528,1530],{"class":72,"line":1529},255,[70,1531,1532],{},"        glfwPollEvents();\n",[70,1534,1536],{"class":72,"line":1535},256,[70,1537,155],{"emptyLinePlaceholder":154},[70,1539,1541],{"class":72,"line":1540},257,[70,1542,1543],{},"        \u002F\u002F 从 DPI 稳定器获取 view 矩阵\n",[70,1545,1547],{"class":72,"line":1546},258,[70,1548,1549],{},"        mat4 view = dpiStabilizer.getViewMatrix();\n",[70,1551,1553],{"class":72,"line":1552},259,[70,1554,801],{},[70,1556,1558],{"class":72,"line":1557},260,[70,1559,1560],{},"        float currentFrame = glfwGetTime();\n",[70,1562,1564],{"class":72,"line":1563},261,[70,1565,1566],{},"        deltaTime = currentFrame - lastFrame;\n",[70,1568,1570],{"class":72,"line":1569},262,[70,1571,1572],{},"        lastFrame = currentFrame;\n",[70,1574,1576],{"class":72,"line":1575},263,[70,1577,155],{"emptyLinePlaceholder":154},[70,1579,1581],{"class":72,"line":1580},264,[70,1582,1583],{},"        \u002F\u002F 渲染指令\n",[70,1585,1587],{"class":72,"line":1586},265,[70,1588,1589],{},"        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);\n",[70,1591,1593],{"class":72,"line":1592},266,[70,1594,1595],{},"        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);\n",[70,1597,1599],{"class":72,"line":1598},267,[70,1600,801],{},[70,1602,1604],{"class":72,"line":1603},268,[70,1605,1606],{},"        \u002F\u002F 使用光照着色器绘制物体\n",[70,1608,1610],{"class":72,"line":1609},269,[70,1611,1612],{},"        lightingShader.use();\n",[70,1614,1616],{"class":72,"line":1615},270,[70,1617,801],{},[70,1619,1621],{"class":72,"line":1620},271,[70,1622,1623],{},"        \u002F\u002F 绑定纹理\n",[70,1625,1627],{"class":72,"line":1626},272,[70,1628,1629],{},"        glActiveTexture(GL_TEXTURE0);\n",[70,1631,1633],{"class":72,"line":1632},273,[70,1634,1635],{},"        glBindTexture(GL_TEXTURE_2D, texture1);\n",[70,1637,1639],{"class":72,"line":1638},274,[70,1640,1641],{},"        lightingShader.setInt(\"material.diffuse\", 0);\n",[70,1643,1645],{"class":72,"line":1644},275,[70,1646,801],{},[70,1648,1650],{"class":72,"line":1649},276,[70,1651,1652],{},"        lightingShader.setMat4(\"projection\", projection);\n",[70,1654,1656],{"class":72,"line":1655},277,[70,1657,1658],{},"        lightingShader.setMat4(\"view\", view);\n",[70,1660,1662],{"class":72,"line":1661},278,[70,1663,1664],{},"        lightingShader.setVec3(\"light.direction\", -0.2f, -1.0f, -0.3f);\n",[70,1666,1668],{"class":72,"line":1667},279,[70,1669,1670],{},"        lightingShader.setVec3(\"light.ambient\",  1.0f, 1.0f, 1.0f);   \u002F\u002F 最大环境光强度\n",[70,1672,1674],{"class":72,"line":1673},280,[70,1675,1676],{},"        lightingShader.setVec3(\"light.diffuse\",  1.0f, 1.0f, 1.0f);   \u002F\u002F 增加漫反射强度\n",[70,1678,1680],{"class":72,"line":1679},281,[70,1681,1682],{},"        lightingShader.setVec3(\"light.specular\", 1.0f, 1.0f, 1.0f); \n",[70,1684,1686],{"class":72,"line":1685},282,[70,1687,1688],{},"        lightingShader.setVec3(\"material.specular\", 0.633f, 0.727811f, 0.633f);\n",[70,1690,1692],{"class":72,"line":1691},283,[70,1693,1694],{},"        \u002F\u002F 高光指数：通常乘以 128 (OpenGL 最大的 1.0 对应 128)\n",[70,1696,1698],{"class":72,"line":1697},284,[70,1699,1700],{},"        lightingShader.setFloat(\"material.shininess\", 0.6f * 128.0f); \u002F\u002F 76.8\n",[70,1702,1704],{"class":72,"line":1703},285,[70,1705,801],{},[70,1707,1709],{"class":72,"line":1708},286,[70,1710,1711],{},"        \u002F\u002F 设置相机位置（观察空间）\n",[70,1713,1715],{"class":72,"line":1714},287,[70,1716,1717],{},"        vec3 currentViewPos = dpiStabilizer.getCameraPosition();\n",[70,1719,1721],{"class":72,"line":1720},288,[70,1722,1723],{},"        lightingShader.setVec3(\"viewPos\", currentViewPos.x, currentViewPos.y, currentViewPos.z);\n",[70,1725,1727],{"class":72,"line":1726},289,[70,1728,801],{},[70,1730,1732],{"class":72,"line":1731},290,[70,1733,1734],{},"        glBindVertexArray(VAO);\n",[70,1736,1738],{"class":72,"line":1737},291,[70,1739,801],{},[70,1741,1743],{"class":72,"line":1742},292,[70,1744,1745],{},"        \u002F\u002F 定义多个立方体的位置（更分散的布局）\n",[70,1747,1749],{"class":72,"line":1748},293,[70,1750,1751],{},"        glm::vec3 cubePositions[] = {\n",[70,1753,1755],{"class":72,"line":1754},294,[70,1756,1757],{},"            glm::vec3( 0.0f,  0.0f,  0.0f),\n",[70,1759,1761],{"class":72,"line":1760},295,[70,1762,1763],{},"            glm::vec3( 2.0f,  0.0f,  0.0f),\n",[70,1765,1767],{"class":72,"line":1766},296,[70,1768,1769],{},"            glm::vec3(-2.0f,  0.0f,  0.0f),\n",[70,1771,1773],{"class":72,"line":1772},297,[70,1774,1775],{},"            glm::vec3( 0.0f,  0.0f, -2.0f),\n",[70,1777,1779],{"class":72,"line":1778},298,[70,1780,1781],{},"            glm::vec3( 0.0f,  0.0f,  2.0f),\n",[70,1783,1785],{"class":72,"line":1784},299,[70,1786,1787],{},"            glm::vec3( 2.0f,  0.0f, -2.0f),\n",[70,1789,1791],{"class":72,"line":1790},300,[70,1792,1793],{},"            glm::vec3(-2.0f,  0.0f, -2.0f),\n",[70,1795,1797],{"class":72,"line":1796},301,[70,1798,1799],{},"            glm::vec3( 2.0f,  0.0f,  2.0f),\n",[70,1801,1803],{"class":72,"line":1802},302,[70,1804,1805],{},"            glm::vec3(-2.0f,  0.0f,  2.0f),\n",[70,1807,1809],{"class":72,"line":1808},303,[70,1810,1811],{},"            glm::vec3( 0.0f,  2.0f,  0.0f)\n",[70,1813,1815],{"class":72,"line":1814},304,[70,1816,1817],{},"        };\n",[70,1819,1821],{"class":72,"line":1820},305,[70,1822,801],{},[70,1824,1826],{"class":72,"line":1825},306,[70,1827,1828],{},"        for(unsigned int i = 0; i \u003C 10; i++)\n",[70,1830,1832],{"class":72,"line":1831},307,[70,1833,1834],{},"        {\n",[70,1836,1838],{"class":72,"line":1837},308,[70,1839,1840],{},"            glm::mat4 model = glm::mat4(1.0f);\n",[70,1842,1844],{"class":72,"line":1843},309,[70,1845,1846],{},"            model = glm::translate(model, cubePositions[i]);\n",[70,1848,1850],{"class":72,"line":1849},310,[70,1851,1852],{},"            float angle = 20.0f * i;\n",[70,1854,1856],{"class":72,"line":1855},311,[70,1857,1858],{},"            model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));\n",[70,1860,1862],{"class":72,"line":1861},312,[70,1863,1864],{},"            lightingShader.setMat4(\"model\", model);\n",[70,1866,1868],{"class":72,"line":1867},313,[70,1869,155],{"emptyLinePlaceholder":154},[70,1871,1873],{"class":72,"line":1872},314,[70,1874,1875],{},"            glDrawArrays(GL_TRIANGLES, 0, 36);\n",[70,1877,1879],{"class":72,"line":1878},315,[70,1880,819],{},[70,1882,1884],{"class":72,"line":1883},316,[70,1885,801],{},[70,1887,1889],{"class":72,"line":1888},317,[70,1890,1891],{},"        \u002F\u002F 绘制光源立方体（平行光不需要光源立方体，已注释）\n",[70,1893,1895],{"class":72,"line":1894},318,[70,1896,1897],{},"        \u002F*\n",[70,1899,1901],{"class":72,"line":1900},319,[70,1902,1903],{},"        lampShader.use();\n",[70,1905,1907],{"class":72,"line":1906},320,[70,1908,1909],{},"        lampShader.setMat4(\"projection\", projection);\n",[70,1911,1913],{"class":72,"line":1912},321,[70,1914,1915],{},"        lampShader.setMat4(\"view\", view);\n",[70,1917,1919],{"class":72,"line":1918},322,[70,1920,801],{},[70,1922,1924],{"class":72,"line":1923},323,[70,1925,1926],{},"        \u002F\u002F 光源立方体的模型矩阵\n",[70,1928,1930],{"class":72,"line":1929},324,[70,1931,1932],{},"        mat4 lampModel = mat4(1.0f);\n",[70,1934,1936],{"class":72,"line":1935},325,[70,1937,1938],{},"        lampModel = translate(lampModel, lightPos);\n",[70,1940,1942],{"class":72,"line":1941},326,[70,1943,1944],{},"        lampModel = scale(lampModel, vec3(0.2f)); \u002F\u002F 缩小光源立方体\n",[70,1946,1948],{"class":72,"line":1947},327,[70,1949,1950],{},"        lampShader.setMat4(\"model\", lampModel);\n",[70,1952,1954],{"class":72,"line":1953},328,[70,1955,801],{},[70,1957,1959],{"class":72,"line":1958},329,[70,1960,1961],{},"        glBindVertexArray(lightVAO);\n",[70,1963,1965],{"class":72,"line":1964},330,[70,1966,1967],{},"        glDrawArrays(GL_TRIANGLES, 0, 36);\n",[70,1969,1971],{"class":72,"line":1970},331,[70,1972,1973],{},"        *\u002F\n",[70,1975,1977],{"class":72,"line":1976},332,[70,1978,801],{},[70,1980,1982],{"class":72,"line":1981},333,[70,1983,1984],{},"        \u002F\u002F 使用 ImGui 管理器渲染所有窗口\n",[70,1986,1988],{"class":72,"line":1987},334,[70,1989,1990],{},"        imguiManager.render();\n",[70,1992,1994],{"class":72,"line":1993},335,[70,1995,155],{"emptyLinePlaceholder":154},[70,1997,1999],{"class":72,"line":1998},336,[70,2000,2001],{},"        \u002F\u002F 交换缓冲区和检查IO事件\n",[70,2003,2005],{"class":72,"line":2004},337,[70,2006,2007],{},"        glfwSwapBuffers(window);\n",[70,2009,2011],{"class":72,"line":2010},338,[70,2012,238],{},[70,2014,2016],{"class":72,"line":2015},339,[70,2017,155],{"emptyLinePlaceholder":154},[70,2019,2021],{"class":72,"line":2020},340,[70,2022,2023],{},"    \u002F\u002F 清理 ImGui 管理器\n",[70,2025,2027],{"class":72,"line":2026},341,[70,2028,2029],{},"    imguiManager.shutdown();\n",[70,2031,2033],{"class":72,"line":2032},342,[70,2034,155],{"emptyLinePlaceholder":154},[70,2036,2038],{"class":72,"line":2037},343,[70,2039,2040],{},"    \u002F\u002F 清理资源\n",[70,2042,2044],{"class":72,"line":2043},344,[70,2045,2046],{},"    glDeleteVertexArrays(1, &VAO);\n",[70,2048,2050],{"class":72,"line":2049},345,[70,2051,2052],{},"    glDeleteVertexArrays(1, &lightVAO);\n",[70,2054,2056],{"class":72,"line":2055},346,[70,2057,2058],{},"    glDeleteBuffers(1, &VBO);\n",[70,2060,2062],{"class":72,"line":2061},347,[70,2063,2064],{},"    glDeleteTextures(1, &texture1);\n",[70,2066,2068],{"class":72,"line":2067},348,[70,2069,2070],{},"    glfwTerminate();\n",[70,2072,2074],{"class":72,"line":2073},349,[70,2075,2076],{},"    return 0;\n",[70,2078,2080],{"class":72,"line":2079},350,[70,2081,353],{},[13,2083,2084],{},"先来来看看点光源： 我们需要一个衰减公式来表示光线随着距离增加而减小。",[13,2086,2087,2088,2091],{},"下面这个公式根据片段距光源的距离计算了衰减值，",[17,2089,2090],{},"之后我们会将它乘以光的强度向量","：",[13,2093,2094],{},[27,2095],{"alt":2096,"src":2097},"image-20251029202203628",".\u002Fassets\u002Fimage-20251029202203628.png",[13,2099,2100,2101,2104,2105],{},"在这里",[17,2102,2103],{},"d代表了片段距光源的距离","。接下来为了计算衰减值，",[17,2106,2107],{},"我们定义3个（可配置的）项：常数项Kc、一次项Kl和二次项Kq。",[2109,2110,2111,2115,2118],"ul",{},[2112,2113,2114],"li",{},"常数项通常保持为1.0，它的主要作用是保证分母永远不会比1小，否则的话在某些距离上它反而会增加强度，这肯定不是我们想要的效果。",[2112,2116,2117],{},"一次项会与距离值相乘，以线性的方式减少强度。",[2112,2119,2120],{},"二次项会与距离的平方相乘，让光源以二次递减的方式减少强度。二次项在距离比较小的时候影响会比一次项小很多，但当距离值比较大的时候它就会比一次项更大了。",[13,2122,2123],{},"由于二次项的存在，光线会在大部分时候以线性的方式衰退，直到距离变得足够大，让二次项超过一次项，光的强度会以更快的速度下降。这样的结果就是，光在近距离时亮度很高，但随着距离变远亮度迅速降低，最后会以更慢的速度减少亮度。下面这张图显示了在100的距离内衰减的效果：",[13,2125,2126],{},[27,2127],{"alt":2128,"src":2129},"image-20251029202400723",".\u002Fassets\u002Fimage-20251029202400723.png",[13,2131,2132],{},"你可以看到光在近距离的时候有着最高的强度，但随着距离增长，它的强度明显减弱，并缓慢地在距离大约100的时候强度接近0。这正是我们想要的。",[2134,2135,2136],"h3",{"id":2136},"选择正确的值",[13,2138,2139,2140,2143,2144,2151],{},"但是，该对这三个项设置什么值呢？正确地设定它们的值取决于很多因素：环境、希望光覆盖的距离、光的类型等。在大多数情况下，这都是经验的问题，以及适量的调整。下面这个表格显示了模拟一个（大概）真实的，覆盖特定半径（距离）的光源时，这些项可能取的一些值。",[17,2141,2142],{},"第一列指定的是在给定的三项时光所能覆盖的距离","。这些值是大多数光源很好的起始点，它们由",[2145,2146,2150],"a",{"href":2147,"rel":2148},"http:\u002F\u002Fwww.ogre3d.org\u002Ftikiwiki\u002Ftiki-index.php?page=-Point+Light+Attenuation",[2149],"nofollow","Ogre3D的Wiki","所提供：",[2153,2154,2155,2175],"table",{},[2156,2157,2158],"thead",{},[2159,2160,2161,2166,2169,2172],"tr",{},[2162,2163,2165],"th",{"align":2164},"left","距离",[2162,2167,2168],{"align":2164},"常数项",[2162,2170,2171],{"align":2164},"一次项",[2162,2173,2174],{"align":2164},"二次项",[2176,2177,2178,2193,2206,2219,2232,2245,2257,2270,2283,2296,2309,2322],"tbody",{},[2159,2179,2180,2184,2187,2190],{},[2181,2182,2183],"td",{"align":2164},"7",[2181,2185,2186],{"align":2164},"1.0",[2181,2188,2189],{"align":2164},"0.7",[2181,2191,2192],{"align":2164},"1.8",[2159,2194,2195,2198,2200,2203],{},[2181,2196,2197],{"align":2164},"13",[2181,2199,2186],{"align":2164},[2181,2201,2202],{"align":2164},"0.35",[2181,2204,2205],{"align":2164},"0.44",[2159,2207,2208,2211,2213,2216],{},[2181,2209,2210],{"align":2164},"20",[2181,2212,2186],{"align":2164},[2181,2214,2215],{"align":2164},"0.22",[2181,2217,2218],{"align":2164},"0.20",[2159,2220,2221,2224,2226,2229],{},[2181,2222,2223],{"align":2164},"32",[2181,2225,2186],{"align":2164},[2181,2227,2228],{"align":2164},"0.14",[2181,2230,2231],{"align":2164},"0.07",[2159,2233,2234,2237,2239,2242],{},[2181,2235,2236],{"align":2164},"50",[2181,2238,2186],{"align":2164},[2181,2240,2241],{"align":2164},"0.09",[2181,2243,2244],{"align":2164},"0.032",[2159,2246,2247,2250,2252,2254],{},[2181,2248,2249],{"align":2164},"65",[2181,2251,2186],{"align":2164},[2181,2253,2231],{"align":2164},[2181,2255,2256],{"align":2164},"0.017",[2159,2258,2259,2262,2264,2267],{},[2181,2260,2261],{"align":2164},"100",[2181,2263,2186],{"align":2164},[2181,2265,2266],{"align":2164},"0.045",[2181,2268,2269],{"align":2164},"0.0075",[2159,2271,2272,2275,2277,2280],{},[2181,2273,2274],{"align":2164},"160",[2181,2276,2186],{"align":2164},[2181,2278,2279],{"align":2164},"0.027",[2181,2281,2282],{"align":2164},"0.0028",[2159,2284,2285,2288,2290,2293],{},[2181,2286,2287],{"align":2164},"200",[2181,2289,2186],{"align":2164},[2181,2291,2292],{"align":2164},"0.022",[2181,2294,2295],{"align":2164},"0.0019",[2159,2297,2298,2301,2303,2306],{},[2181,2299,2300],{"align":2164},"325",[2181,2302,2186],{"align":2164},[2181,2304,2305],{"align":2164},"0.014",[2181,2307,2308],{"align":2164},"0.0007",[2159,2310,2311,2314,2316,2319],{},[2181,2312,2313],{"align":2164},"600",[2181,2315,2186],{"align":2164},[2181,2317,2318],{"align":2164},"0.007",[2181,2320,2321],{"align":2164},"0.0002",[2159,2323,2324,2327,2329,2332],{},[2181,2325,2326],{"align":2164},"3250",[2181,2328,2186],{"align":2164},[2181,2330,2331],{"align":2164},"0.0014",[2181,2333,2334],{"align":2164},"0.000007",[13,2336,2337],{},"你可以看到，常数项Kc在所有的情况下都是1.0。一次项Kl为了覆盖更远的距离通常都很小，二次项Kq甚至更小。尝试对这些值进行实验，看看它们在你的实现中有什么效果。在我们的环境中，32到100的距离对大多数的光源都足够了。",[13,2339,2340],{},"下面完整代码：",[60,2342,2344],{"className":62,"code":2343,"language":64,"meta":65,"style":65},"#include \u003Cglad\u002Fglad.h>\n#include \u003CGLFW\u002Fglfw3.h>\n#include \u003Ciostream>\n#include \u003Ccmath>\n#include \u003Cimgui.h>\n#include \u003Cimgui_impl_glfw.h>\n#include \u003Cimgui_impl_opengl3.h>\n#include \u003Copencv2\u002Fopencv.hpp>\n#include \u003Copencv2\u002Fimgcodecs.hpp>\n#include \"utils\u002Fshader.h\"\n#include \"utils\u002FglmCompute.h\"\n#include \"include\u002Fdpi_stabilizer.h\"\n#include \"include\u002Fimgui_manager.h\"\n\nusing namespace glm;\nusing namespace cv;\n\n\u002F\u002F 使用 OpenCV 加载纹理\nunsigned int loadTexture(const char* path) {\n    unsigned int textureID;\n    glGenTextures(1, &textureID);\n    \n    \u002F\u002F 使用 OpenCV 读取图像\n    Mat image = imread(path, IMREAD_COLOR);\n    if (image.empty()) {\n        std::cout \u003C\u003C \"Failed to load texture at path: \" \u003C\u003C path \u003C\u003C std::endl;\n        return textureID;\n    }\n    \n    \u002F\u002F OpenCV 使用 BGR，OpenGL 使用 RGB，需要转换\n    Mat rgbImage;\n    cvtColor(image, rgbImage, COLOR_BGR2RGB);\n    \n    \u002F\u002F 翻转图像（OpenGL 期望的 Y 轴方向）\n    flip(rgbImage, rgbImage, 0);\n    \n    glBindTexture(GL_TEXTURE_2D, textureID);\n    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, rgbImage.cols, rgbImage.rows, 0, GL_RGB, GL_UNSIGNED_BYTE, rgbImage.data);\n    glGenerateMipmap(GL_TEXTURE_2D);\n    \n    \u002F\u002F 设置纹理参数\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n    \n    return textureID;\n}\n\n\u002F\u002F 窗口大小改变时的回调函数\nvoid framebuffer_size_callback(GLFWwindow* window, int width, int height) {\n    glViewport(0, 0, width, height);\n}\n\n\n\u002F\u002F 全局变量\nfloat deltaTime = 0.0f; \u002F\u002F 当前帧与上一帧的时间差\nfloat lastFrame = 0.0f; \u002F\u002F 上一帧的时间\n\n\u002F\u002F DPI 稳定器实例\nDPIStabilizer dpiStabilizer(0.1f, 50.0f, 0.8f);\n\n\u002F\u002F ImGui 管理器实例\nImGuiManager imguiManager;\n\n\u002F\u002F 简化的输入处理函数\nvoid processInput(GLFWwindow *window){\n    \u002F\u002F 使用 DPI 稳定器处理所有输入\n    dpiStabilizer.processKeyboardInput(window);\n\n    if (glfwGetKey(window, GLFW_KEY_F2) == GLFW_PRESS) {\n        imguiManager.toggleWindow(\"控制面板\");\n    }\n    if (glfwGetKey(window, GLFW_KEY_F3) == GLFW_PRESS) {\n        imguiManager.toggleWindow(\"性能监控\");\n    }\n    if (glfwGetKey(window, GLFW_KEY_F4) == GLFW_PRESS) {\n        imguiManager.toggleWindow(\"调试信息\");\n    }\n    \n    \u002F\u002F 显示统计信息 (按 T 键)\n    if (glfwGetKey(window, GLFW_KEY_T) == GLFW_PRESS) {\n        std::cout \u003C\u003C dpiStabilizer.getStats() \u003C\u003C std::endl;\n    }\n}\n\nint main() {\n    \u002F\u002F 投影矩阵\n    mat4 projection = perspective(radians(45.0f), (float)800 \u002F (float)600, 0.1f, 100.0f);\n\n    \u002F\u002F 初始化GLFW\n    glfwInit();\n    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);\n    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);\n    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);\n    \n    \u002F\u002F 设置 DPI 感知提示\n    DPIStabilizer::setDPIHint();\n\n    \u002F\u002F 创建窗口\n    GLFWwindow* window = glfwCreateWindow(800, 600, \"OpenGL Window\", NULL, NULL);\n    if (window == NULL) {\n        std::cout \u003C\u003C \"Failed to create GLFW window\" \u003C\u003C std::endl;\n        glfwTerminate();\n        return -1;\n    }\n\n    glfwMakeContextCurrent(window);\n    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);\n    \n    \u002F\u002F 初始化 DPI 稳定器\n    dpiStabilizer.initialize(window);\n\n    \u002F\u002F 初始化GLAD\n    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {\n        std::cout \u003C\u003C \"Failed to initialize GLAD\" \u003C\u003C std::endl;\n        return -1;\n    }\n\n    \u002F\u002F 初始化 ImGui 管理器\n    imguiManager.initialize(window);\n    \n    \u002F\u002F 注册窗口\n    imguiManager.registerWindow(\"Hello ImGui!\", []() {\n        ImGui::Text(\"DPI 稳定器状态: %s\", dpiStabilizer.isMouseCaptured() ? \"已捕获\" : \"已释放\");\n        ImGui::Text(\"鼠标敏感度: %.3f\", dpiStabilizer.getSensitivity());\n        \n        if (ImGui::Button(\"重置统计\")) {\n            dpiStabilizer.resetStats();\n        }\n        \n        float sensitivity = dpiStabilizer.getSensitivity();\n        if (ImGui::SliderFloat(\"鼠标敏感度\", &sensitivity, 0.01f, 0.5f)) {\n            dpiStabilizer.setSensitivity(sensitivity);\n        }\n    });\n    \n    imguiManager.registerWindow(\"控制面板\", []() {\n        ImGuiIO& io = ImGui::GetIO();\n        ImGui::Text(\"FPS: %.1f\", io.Framerate);\n        ImGui::Text(\"帧时间: %.3f ms\", 1000.0f \u002F io.Framerate);\n    });\n    \n\n    \u002F\u002F 启用深度测试\n    glEnable(GL_DEPTH_TEST);\n\n    float vertices[] = {\n    \u002F\u002F 立方体顶点数据（位置 + 法线 + 纹理坐标）\n    \u002F\u002F 位置(3) + 法线(3) + 纹理坐标(2) = 8个浮点数\n    \u002F\u002F 前面\n    -0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  0.0f, 0.0f,\n     0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  1.0f, 0.0f,\n     0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  1.0f, 1.0f,\n     0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  1.0f, 1.0f,\n    -0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  0.0f, 1.0f,\n    -0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  0.0f, 0.0f,\n    \n    \u002F\u002F 后面\n    -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f, 0.0f,\n     0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f, 0.0f,\n     0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f, 1.0f,\n     0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f, 1.0f,\n    -0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f, 1.0f,\n    -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f, 0.0f,\n    \n    \u002F\u002F 左面\n    -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,  1.0f, 0.0f,\n    -0.5f,  0.5f, -0.5f, -1.0f,  0.0f,  0.0f,  1.0f, 1.0f,\n    -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,  0.0f, 1.0f,\n    -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,  0.0f, 1.0f,\n    -0.5f, -0.5f,  0.5f, -1.0f,  0.0f,  0.0f,  0.0f, 0.0f,\n    -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,  1.0f, 0.0f,\n    \n    \u002F\u002F 右面\n     0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  1.0f, 0.0f,\n     0.5f,  0.5f, -0.5f,  1.0f,  0.0f,  0.0f,  1.0f, 1.0f,\n     0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,  0.0f, 1.0f,\n     0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,  0.0f, 1.0f,\n     0.5f, -0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  0.0f, 0.0f,\n     0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  1.0f, 0.0f,\n    \n    \u002F\u002F 下面\n    -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,  0.0f, 1.0f,\n     0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,  1.0f, 1.0f,\n     0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,  1.0f, 0.0f,\n     0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,  1.0f, 0.0f,\n    -0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,  0.0f, 0.0f,\n    -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,  0.0f, 1.0f,\n    \n    \u002F\u002F 上面\n    -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  0.0f, 1.0f,\n     0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  1.0f, 1.0f,\n     0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  1.0f, 0.0f,\n     0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  1.0f, 0.0f,\n    -0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  0.0f, 0.0f,\n    -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  0.0f, 1.0f\n};\n    \n    \u002F\u002F 创建VAO\n    unsigned int VAO;\n    glGenVertexArrays(1, &VAO);\n    glBindVertexArray(VAO); \u002F\u002F 绑定VAO\n\n    \u002F\u002F 创建VBO\n    unsigned int VBO;\n    glGenBuffers(1, &VBO);\n    glBindBuffer(GL_ARRAY_BUFFER, VBO); \u002F\u002F 绑定VBO\n    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);\n\n    int nrAttributes;\n    glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);\n    std::cout \u003C\u003C \"Maximum nr of vertex attributes supported: \" \u003C\u003C nrAttributes \u003C\u003C std::endl;\n\n    \u002F\u002F 位置属性\n    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);\n    glEnableVertexAttribArray(0); \u002F\u002F 启用顶点属性\n    \n    \u002F\u002F 法线属性\n    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));\n    glEnableVertexAttribArray(1); \u002F\u002F 启用法线属性\n    \n    \u002F\u002F 纹理坐标属性\n    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));\n    glEnableVertexAttribArray(2); \u002F\u002F 启用纹理坐标属性\n\n    \u002F\u002F 构建并编译着色器程序\n    Shader lightingShader(\"shader\u002Flighting.vert\", \"shader\u002Flighting.frag\");\n    Shader lampShader(\"shader\u002Flamp.vert\", \"shader\u002Flamp.frag\");\n\n    \u002F\u002F 加载纹理\n    unsigned int texture1 = loadTexture(\"Assets\u002Fimage.png\");\n    if (texture1 == 0) {\n        std::cout \u003C\u003C \"Failed to load texture\" \u003C\u003C std::endl;\n    }\n\n    \u002F\u002F 创建光源的 VAO\n    unsigned int lightVAO;\n    glGenVertexArrays(1, &lightVAO);\n    glBindVertexArray(lightVAO);\n    glBindBuffer(GL_ARRAY_BUFFER, VBO);\n    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);\n    glEnableVertexAttribArray(0);\n\n    \u002F\u002F 光源位置\n    vec3 lightPos(1.2f, 1.0f, 2.0f);\n    \n    \u002F\u002F 相机位置（观察空间）\n    vec3 viewPos = dpiStabilizer.getCameraPosition();\n\n    \u002F\u002F 渲染循环\n    while (!glfwWindowShouldClose(window)) {\n        \u002F\u002F 处理输入\n        processInput(window);\n        glfwPollEvents();\n\n        \u002F\u002F 从 DPI 稳定器获取 view 矩阵\n        mat4 view = dpiStabilizer.getViewMatrix();\n        \n        float currentFrame = glfwGetTime();\n        deltaTime = currentFrame - lastFrame;\n        lastFrame = currentFrame;\n\n        \u002F\u002F 渲染指令\n        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);\n        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);\n        \n        \u002F\u002F 使用光照着色器绘制物体\n        lightingShader.use();\n        \n        \u002F\u002F 绑定纹理\n        glActiveTexture(GL_TEXTURE0);\n        glBindTexture(GL_TEXTURE_2D, texture1);\n        lightingShader.setInt(\"material.diffuse\", 0);\n        \n        lightingShader.setMat4(\"projection\", projection);\n        lightingShader.setMat4(\"view\", view);\n        lightingShader.setVec3(\"light.position\", lightPos.x, lightPos.y, lightPos.z);\n        lightingShader.setFloat(\"light.constant\", 1.0f);\n        lightingShader.setFloat(\"light.linear\", 0.09f);\n        lightingShader.setFloat(\"light.quadratic\", 0.032f);\n        lightingShader.setVec3(\"light.ambient\",  1.0f, 1.0f, 1.0f);   \u002F\u002F 最大环境光强度\n        lightingShader.setVec3(\"light.diffuse\",  1.0f, 1.0f, 1.0f);   \u002F\u002F 增加漫反射强度\n        lightingShader.setVec3(\"light.specular\", 1.0f, 1.0f, 1.0f); \n        lightingShader.setVec3(\"material.specular\", 0.633f, 0.727811f, 0.633f);\n        \u002F\u002F 高光指数：通常乘以 128 (OpenGL 最大的 1.0 对应 128)\n        lightingShader.setFloat(\"material.shininess\", 0.6f * 128.0f); \u002F\u002F 76.8\n        \n        \u002F\u002F 设置相机位置（观察空间）\n        vec3 currentViewPos = dpiStabilizer.getCameraPosition();\n        lightingShader.setVec3(\"viewPos\", currentViewPos.x, currentViewPos.y, currentViewPos.z);\n        \n        glBindVertexArray(VAO);\n        \n        \u002F\u002F 定义多个立方体的位置（更分散的布局）\n        glm::vec3 cubePositions[] = {\n            glm::vec3( 0.0f,  0.0f,  0.0f),\n            glm::vec3( 4.0f,  0.0f,  0.0f),\n            glm::vec3(-4.0f,  0.0f,  0.0f),\n            glm::vec3( 0.0f,  0.0f, -8.0f),\n            glm::vec3( 0.0f,  0.0f,  8.0f),\n            glm::vec3( 8.0f,  0.0f, -8.0f),\n            glm::vec3(-10.0f,  0.0f, -2.0f),\n            glm::vec3( 2.0f,  0.0f,  2.0f),\n            glm::vec3(-2.0f,  0.0f,  2.0f),\n            glm::vec3( 0.0f,  2.0f,  0.0f)\n        };\n        \n        for(unsigned int i = 0; i \u003C 10; i++)\n        {\n            glm::mat4 model = glm::mat4(1.0f);\n            model = glm::translate(model, cubePositions[i]);\n            float angle = 20.0f * i;\n            model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));\n            lightingShader.setMat4(\"model\", model);\n\n            glDrawArrays(GL_TRIANGLES, 0, 36);\n        }\n\n        lampShader.use();\n        lampShader.setMat4(\"projection\", projection);\n        lampShader.setMat4(\"view\", view);\n        \n        \u002F\u002F 光源立方体的模型矩阵\n        mat4 lampModel = mat4(1.0f);\n        lampModel = translate(lampModel, lightPos);\n        lampModel = scale(lampModel, vec3(0.2f)); \u002F\u002F 缩小光源立方体\n        lampShader.setMat4(\"model\", lampModel);\n        \n        glBindVertexArray(lightVAO);\n        glDrawArrays(GL_TRIANGLES, 0, 36);\n        \n        \u002F\u002F 使用 ImGui 管理器渲染所有窗口\n        imguiManager.render();\n\n        \u002F\u002F 交换缓冲区和检查IO事件\n        glfwSwapBuffers(window);\n    }\n\n    \u002F\u002F 清理 ImGui 管理器\n    imguiManager.shutdown();\n\n    \u002F\u002F 清理资源\n    glDeleteVertexArrays(1, &VAO);\n    glDeleteVertexArrays(1, &lightVAO);\n    glDeleteBuffers(1, &VBO);\n    glDeleteTextures(1, &texture1);\n    glfwTerminate();\n    return 0;\n}\n\n\n#version 330 core\nout vec4 FragColor;\n\nstruct Material{\n    sampler2D diffuse;\n    vec3 specular;\n    float shininess;\n};\n\nstruct Light{\n    vec3 position;\n    vec3 ambient;\n    vec3 diffuse;\n    vec3 specular;\n\n    float constant;\n    float linear;\n    float quadratic;\n};\n\nuniform Light light;\nuniform vec3 viewPos;\nuniform Material material;\nin vec3 Normal;\nin vec3 FragPos;\nin vec2 TexCoords;\n\nvoid main()\n{\n    \u002F\u002F 从纹理采样颜色\n    vec3 textureColor = vec3(texture(material.diffuse, TexCoords));\n    float distance    = length(light.position - FragPos);\n    float attenuation = 1.0 \u002F (light.constant + light.linear * distance + \n                 light.quadratic * (distance * distance));\n    \u002F\u002F 环境光\n    vec3 ambient = attenuation* light.ambient * textureColor;\n    \n    \u002F\u002F 漫反射\n    vec3 norm = normalize(Normal);\n    vec3 lightDir = normalize(light.position - FragPos);\n    float diff = max(dot(norm, lightDir), 0.0);\n    vec3 diffuse = attenuation* light.diffuse * (diff * textureColor);\n    \n    \u002F\u002F 镜面反射\n    vec3 viewDir = normalize(viewPos - FragPos);\n    vec3 reflectDir = reflect(-lightDir, norm);\n    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);\n    vec3 specular = attenuation* light.specular * (spec * material.specular);\n    \n    \u002F\u002F 最终结果\n    vec3 result = ambient + diffuse + specular;\n    FragColor = vec4(result, 1.0);\n}\n\n\n",[67,2345,2346,2350,2354,2358,2362,2366,2370,2374,2378,2382,2386,2390,2394,2398,2402,2406,2410,2414,2418,2422,2426,2430,2434,2438,2442,2446,2450,2454,2458,2462,2466,2470,2474,2478,2482,2486,2490,2494,2498,2502,2506,2510,2514,2518,2522,2526,2530,2534,2538,2542,2546,2550,2554,2558,2562,2566,2570,2574,2578,2582,2586,2590,2594,2598,2602,2606,2610,2614,2618,2622,2626,2630,2634,2638,2642,2646,2650,2654,2658,2662,2666,2670,2674,2678,2682,2686,2690,2694,2698,2702,2706,2710,2714,2718,2722,2726,2730,2734,2738,2742,2746,2750,2754,2758,2762,2766,2770,2774,2778,2782,2786,2790,2794,2798,2802,2806,2810,2814,2818,2822,2826,2830,2834,2838,2842,2846,2850,2854,2858,2862,2866,2870,2874,2878,2882,2886,2890,2894,2898,2902,2906,2910,2914,2918,2922,2926,2930,2934,2938,2942,2946,2950,2954,2958,2962,2966,2970,2974,2978,2982,2986,2990,2994,2998,3002,3006,3010,3014,3018,3022,3026,3030,3034,3038,3042,3046,3050,3054,3058,3062,3066,3070,3074,3078,3082,3086,3090,3094,3098,3102,3106,3110,3114,3118,3122,3126,3130,3134,3138,3142,3146,3150,3154,3158,3162,3166,3170,3174,3178,3182,3186,3190,3194,3198,3202,3206,3210,3214,3218,3222,3226,3230,3234,3238,3242,3246,3250,3254,3258,3262,3266,3270,3274,3278,3282,3286,3290,3294,3298,3302,3306,3310,3314,3318,3322,3326,3330,3334,3338,3342,3346,3350,3354,3358,3362,3366,3370,3374,3378,3382,3386,3390,3394,3398,3402,3406,3410,3414,3418,3422,3426,3430,3434,3438,3442,3446,3450,3454,3459,3464,3469,3474,3478,3482,3486,3490,3494,3498,3502,3506,3510,3514,3518,3522,3526,3530,3534,3538,3543,3548,3553,3558,3563,3568,3572,3576,3580,3584,3588,3592,3596,3600,3604,3608,3612,3616,3620,3624,3628,3632,3636,3640,3644,3648,3652,3656,3660,3664,3668,3672,3676,3680,3684,3688,3692,3696,3700,3704,3708,3712,3716,3720,3724,3728,3732,3736,3740,3744,3748,3752,3756,3761,3766,3772,3778,3783,3789,3795,3801,3807,3812,3817,3823,3829,3835,3841,3846,3851,3857,3863,3869,3874,3879,3885,3891,3897,3903,3909,3915,3920,3926,3932,3938,3944,3950,3956,3962,3968,3974,3979,3985,3991,3997,4003,4009,4014,4020,4026,4032,4038,4044,4049,4055,4061,4067],{"__ignoreMap":65},[70,2347,2348],{"class":72,"line":73},[70,2349,76],{},[70,2351,2352],{"class":72,"line":79},[70,2353,82],{},[70,2355,2356],{"class":72,"line":85},[70,2357,88],{},[70,2359,2360],{"class":72,"line":91},[70,2361,94],{},[70,2363,2364],{"class":72,"line":97},[70,2365,100],{},[70,2367,2368],{"class":72,"line":103},[70,2369,106],{},[70,2371,2372],{"class":72,"line":109},[70,2373,112],{},[70,2375,2376],{"class":72,"line":115},[70,2377,118],{},[70,2379,2380],{"class":72,"line":121},[70,2381,124],{},[70,2383,2384],{"class":72,"line":127},[70,2385,130],{},[70,2387,2388],{"class":72,"line":133},[70,2389,136],{},[70,2391,2392],{"class":72,"line":139},[70,2393,142],{},[70,2395,2396],{"class":72,"line":145},[70,2397,148],{},[70,2399,2400],{"class":72,"line":151},[70,2401,155],{"emptyLinePlaceholder":154},[70,2403,2404],{"class":72,"line":158},[70,2405,161],{},[70,2407,2408],{"class":72,"line":164},[70,2409,167],{},[70,2411,2412],{"class":72,"line":170},[70,2413,155],{"emptyLinePlaceholder":154},[70,2415,2416],{"class":72,"line":175},[70,2417,178],{},[70,2419,2420],{"class":72,"line":181},[70,2421,184],{},[70,2423,2424],{"class":72,"line":187},[70,2425,190],{},[70,2427,2428],{"class":72,"line":193},[70,2429,196],{},[70,2431,2432],{"class":72,"line":199},[70,2433,202],{},[70,2435,2436],{"class":72,"line":205},[70,2437,208],{},[70,2439,2440],{"class":72,"line":211},[70,2441,214],{},[70,2443,2444],{"class":72,"line":217},[70,2445,220],{},[70,2447,2448],{"class":72,"line":223},[70,2449,226],{},[70,2451,2452],{"class":72,"line":229},[70,2453,232],{},[70,2455,2456],{"class":72,"line":235},[70,2457,238],{},[70,2459,2460],{"class":72,"line":241},[70,2461,202],{},[70,2463,2464],{"class":72,"line":246},[70,2465,249],{},[70,2467,2468],{"class":72,"line":252},[70,2469,255],{},[70,2471,2472],{"class":72,"line":258},[70,2473,261],{},[70,2475,2476],{"class":72,"line":264},[70,2477,202],{},[70,2479,2480],{"class":72,"line":269},[70,2481,272],{},[70,2483,2484],{"class":72,"line":275},[70,2485,278],{},[70,2487,2488],{"class":72,"line":281},[70,2489,202],{},[70,2491,2492],{"class":72,"line":286},[70,2493,289],{},[70,2495,2496],{"class":72,"line":292},[70,2497,295],{},[70,2499,2500],{"class":72,"line":298},[70,2501,301],{},[70,2503,2504],{"class":72,"line":304},[70,2505,202],{},[70,2507,2508],{"class":72,"line":309},[70,2509,312],{},[70,2511,2512],{"class":72,"line":315},[70,2513,318],{},[70,2515,2516],{"class":72,"line":321},[70,2517,324],{},[70,2519,2520],{"class":72,"line":327},[70,2521,330],{},[70,2523,2524],{"class":72,"line":333},[70,2525,336],{},[70,2527,2528],{"class":72,"line":339},[70,2529,202],{},[70,2531,2532],{"class":72,"line":344},[70,2533,347],{},[70,2535,2536],{"class":72,"line":350},[70,2537,353],{},[70,2539,2540],{"class":72,"line":356},[70,2541,155],{"emptyLinePlaceholder":154},[70,2543,2544],{"class":72,"line":361},[70,2545,364],{},[70,2547,2548],{"class":72,"line":367},[70,2549,370],{},[70,2551,2552],{"class":72,"line":373},[70,2553,376],{},[70,2555,2556],{"class":72,"line":379},[70,2557,353],{},[70,2559,2560],{"class":72,"line":384},[70,2561,155],{"emptyLinePlaceholder":154},[70,2563,2564],{"class":72,"line":389},[70,2565,155],{"emptyLinePlaceholder":154},[70,2567,2568],{"class":72,"line":394},[70,2569,397],{},[70,2571,2572],{"class":72,"line":400},[70,2573,403],{},[70,2575,2576],{"class":72,"line":406},[70,2577,409],{},[70,2579,2580],{"class":72,"line":412},[70,2581,155],{"emptyLinePlaceholder":154},[70,2583,2584],{"class":72,"line":417},[70,2585,420],{},[70,2587,2588],{"class":72,"line":423},[70,2589,426],{},[70,2591,2592],{"class":72,"line":429},[70,2593,155],{"emptyLinePlaceholder":154},[70,2595,2596],{"class":72,"line":434},[70,2597,437],{},[70,2599,2600],{"class":72,"line":440},[70,2601,443],{},[70,2603,2604],{"class":72,"line":446},[70,2605,155],{"emptyLinePlaceholder":154},[70,2607,2608],{"class":72,"line":451},[70,2609,454],{},[70,2611,2612],{"class":72,"line":457},[70,2613,460],{},[70,2615,2616],{"class":72,"line":463},[70,2617,466],{},[70,2619,2620],{"class":72,"line":469},[70,2621,472],{},[70,2623,2624],{"class":72,"line":475},[70,2625,155],{"emptyLinePlaceholder":154},[70,2627,2628],{"class":72,"line":480},[70,2629,483],{},[70,2631,2632],{"class":72,"line":486},[70,2633,489],{},[70,2635,2636],{"class":72,"line":492},[70,2637,238],{},[70,2639,2640],{"class":72,"line":497},[70,2641,500],{},[70,2643,2644],{"class":72,"line":503},[70,2645,506],{},[70,2647,2648],{"class":72,"line":509},[70,2649,238],{},[70,2651,2652],{"class":72,"line":514},[70,2653,517],{},[70,2655,2656],{"class":72,"line":520},[70,2657,523],{},[70,2659,2660],{"class":72,"line":526},[70,2661,238],{},[70,2663,2664],{"class":72,"line":531},[70,2665,202],{},[70,2667,2668],{"class":72,"line":536},[70,2669,539],{},[70,2671,2672],{"class":72,"line":542},[70,2673,545],{},[70,2675,2676],{"class":72,"line":548},[70,2677,551],{},[70,2679,2680],{"class":72,"line":554},[70,2681,238],{},[70,2683,2684],{"class":72,"line":559},[70,2685,353],{},[70,2687,2688],{"class":72,"line":564},[70,2689,155],{"emptyLinePlaceholder":154},[70,2691,2692],{"class":72,"line":569},[70,2693,572],{},[70,2695,2696],{"class":72,"line":575},[70,2697,578],{},[70,2699,2700],{"class":72,"line":581},[70,2701,584],{},[70,2703,2704],{"class":72,"line":587},[70,2705,155],{"emptyLinePlaceholder":154},[70,2707,2708],{"class":72,"line":592},[70,2709,595],{},[70,2711,2712],{"class":72,"line":598},[70,2713,601],{},[70,2715,2716],{"class":72,"line":604},[70,2717,607],{},[70,2719,2720],{"class":72,"line":610},[70,2721,613],{},[70,2723,2724],{"class":72,"line":616},[70,2725,619],{},[70,2727,2728],{"class":72,"line":622},[70,2729,202],{},[70,2731,2732],{"class":72,"line":627},[70,2733,630],{},[70,2735,2736],{"class":72,"line":633},[70,2737,636],{},[70,2739,2740],{"class":72,"line":639},[70,2741,155],{"emptyLinePlaceholder":154},[70,2743,2744],{"class":72,"line":644},[70,2745,647],{},[70,2747,2748],{"class":72,"line":650},[70,2749,653],{},[70,2751,2752],{"class":72,"line":656},[70,2753,659],{},[70,2755,2756],{"class":72,"line":662},[70,2757,665],{},[70,2759,2760],{"class":72,"line":668},[70,2761,671],{},[70,2763,2764],{"class":72,"line":674},[70,2765,677],{},[70,2767,2768],{"class":72,"line":680},[70,2769,238],{},[70,2771,2772],{"class":72,"line":685},[70,2773,155],{"emptyLinePlaceholder":154},[70,2775,2776],{"class":72,"line":690},[70,2777,693],{},[70,2779,2780],{"class":72,"line":696},[70,2781,699],{},[70,2783,2784],{"class":72,"line":702},[70,2785,202],{},[70,2787,2788],{"class":72,"line":707},[70,2789,710],{},[70,2791,2792],{"class":72,"line":713},[70,2793,716],{},[70,2795,2796],{"class":72,"line":719},[70,2797,155],{"emptyLinePlaceholder":154},[70,2799,2800],{"class":72,"line":724},[70,2801,727],{},[70,2803,2804],{"class":72,"line":730},[70,2805,733],{},[70,2807,2808],{"class":72,"line":736},[70,2809,739],{},[70,2811,2812],{"class":72,"line":742},[70,2813,677],{},[70,2815,2816],{"class":72,"line":747},[70,2817,238],{},[70,2819,2820],{"class":72,"line":752},[70,2821,155],{"emptyLinePlaceholder":154},[70,2823,2824],{"class":72,"line":757},[70,2825,760],{},[70,2827,2828],{"class":72,"line":763},[70,2829,766],{},[70,2831,2832],{"class":72,"line":769},[70,2833,202],{},[70,2835,2836],{"class":72,"line":774},[70,2837,777],{},[70,2839,2840],{"class":72,"line":780},[70,2841,783],{},[70,2843,2844],{"class":72,"line":786},[70,2845,789],{},[70,2847,2848],{"class":72,"line":792},[70,2849,795],{},[70,2851,2852],{"class":72,"line":798},[70,2853,801],{},[70,2855,2856],{"class":72,"line":804},[70,2857,807],{},[70,2859,2860],{"class":72,"line":810},[70,2861,813],{},[70,2863,2864],{"class":72,"line":816},[70,2865,819],{},[70,2867,2868],{"class":72,"line":822},[70,2869,801],{},[70,2871,2872],{"class":72,"line":827},[70,2873,830],{},[70,2875,2876],{"class":72,"line":833},[70,2877,836],{},[70,2879,2880],{"class":72,"line":839},[70,2881,842],{},[70,2883,2884],{"class":72,"line":845},[70,2885,819],{},[70,2887,2888],{"class":72,"line":850},[70,2889,853],{},[70,2891,2892],{"class":72,"line":856},[70,2893,202],{},[70,2895,2896],{"class":72,"line":861},[70,2897,864],{},[70,2899,2900],{"class":72,"line":867},[70,2901,870],{},[70,2903,2904],{"class":72,"line":873},[70,2905,876],{},[70,2907,2908],{"class":72,"line":879},[70,2909,882],{},[70,2911,2912],{"class":72,"line":885},[70,2913,853],{},[70,2915,2916],{"class":72,"line":890},[70,2917,202],{},[70,2919,2920],{"class":72,"line":895},[70,2921,155],{"emptyLinePlaceholder":154},[70,2923,2924],{"class":72,"line":900},[70,2925,903],{},[70,2927,2928],{"class":72,"line":906},[70,2929,909],{},[70,2931,2932],{"class":72,"line":912},[70,2933,155],{"emptyLinePlaceholder":154},[70,2935,2936],{"class":72,"line":917},[70,2937,920],{},[70,2939,2940],{"class":72,"line":923},[70,2941,926],{},[70,2943,2944],{"class":72,"line":929},[70,2945,932],{},[70,2947,2948],{"class":72,"line":935},[70,2949,938],{},[70,2951,2952],{"class":72,"line":941},[70,2953,944],{},[70,2955,2956],{"class":72,"line":947},[70,2957,950],{},[70,2959,2960],{"class":72,"line":953},[70,2961,956],{},[70,2963,2964],{"class":72,"line":959},[70,2965,956],{},[70,2967,2968],{"class":72,"line":964},[70,2969,967],{},[70,2971,2972],{"class":72,"line":970},[70,2973,944],{},[70,2975,2976],{"class":72,"line":975},[70,2977,202],{},[70,2979,2980],{"class":72,"line":980},[70,2981,983],{},[70,2983,2984],{"class":72,"line":986},[70,2985,989],{},[70,2987,2988],{"class":72,"line":992},[70,2989,995],{},[70,2991,2992],{"class":72,"line":998},[70,2993,1001],{},[70,2995,2996],{"class":72,"line":1004},[70,2997,1001],{},[70,2999,3000],{"class":72,"line":1009},[70,3001,1012],{},[70,3003,3004],{"class":72,"line":1015},[70,3005,989],{},[70,3007,3008],{"class":72,"line":1020},[70,3009,202],{},[70,3011,3012],{"class":72,"line":1025},[70,3013,1028],{},[70,3015,3016],{"class":72,"line":1031},[70,3017,1034],{},[70,3019,3020],{"class":72,"line":1037},[70,3021,1040],{},[70,3023,3024],{"class":72,"line":1043},[70,3025,1046],{},[70,3027,3028],{"class":72,"line":1049},[70,3029,1046],{},[70,3031,3032],{"class":72,"line":1054},[70,3033,1057],{},[70,3035,3036],{"class":72,"line":1060},[70,3037,1034],{},[70,3039,3040],{"class":72,"line":1065},[70,3041,202],{},[70,3043,3044],{"class":72,"line":1070},[70,3045,1073],{},[70,3047,3048],{"class":72,"line":1076},[70,3049,1079],{},[70,3051,3052],{"class":72,"line":1082},[70,3053,1085],{},[70,3055,3056],{"class":72,"line":1088},[70,3057,1091],{},[70,3059,3060],{"class":72,"line":1094},[70,3061,1091],{},[70,3063,3064],{"class":72,"line":1099},[70,3065,1102],{},[70,3067,3068],{"class":72,"line":1105},[70,3069,1079],{},[70,3071,3072],{"class":72,"line":1110},[70,3073,202],{},[70,3075,3076],{"class":72,"line":1115},[70,3077,1118],{},[70,3079,3080],{"class":72,"line":1121},[70,3081,1124],{},[70,3083,3084],{"class":72,"line":1127},[70,3085,1130],{},[70,3087,3088],{"class":72,"line":1133},[70,3089,1136],{},[70,3091,3092],{"class":72,"line":1139},[70,3093,1136],{},[70,3095,3096],{"class":72,"line":1144},[70,3097,1147],{},[70,3099,3100],{"class":72,"line":1150},[70,3101,1124],{},[70,3103,3104],{"class":72,"line":1155},[70,3105,202],{},[70,3107,3108],{"class":72,"line":1160},[70,3109,1163],{},[70,3111,3112],{"class":72,"line":1166},[70,3113,1169],{},[70,3115,3116],{"class":72,"line":1172},[70,3117,1175],{},[70,3119,3120],{"class":72,"line":1178},[70,3121,1181],{},[70,3123,3124],{"class":72,"line":1184},[70,3125,1181],{},[70,3127,3128],{"class":72,"line":1189},[70,3129,1192],{},[70,3131,3132],{"class":72,"line":1195},[70,3133,1198],{},[70,3135,3136],{"class":72,"line":1201},[70,3137,1204],{},[70,3139,3140],{"class":72,"line":1207},[70,3141,202],{},[70,3143,3144],{"class":72,"line":1212},[70,3145,1215],{},[70,3147,3148],{"class":72,"line":1218},[70,3149,1221],{},[70,3151,3152],{"class":72,"line":1224},[70,3153,1227],{},[70,3155,3156],{"class":72,"line":1230},[70,3157,1233],{},[70,3159,3160],{"class":72,"line":1236},[70,3161,155],{"emptyLinePlaceholder":154},[70,3163,3164],{"class":72,"line":1241},[70,3165,1244],{},[70,3167,3168],{"class":72,"line":1247},[70,3169,1250],{},[70,3171,3172],{"class":72,"line":1253},[70,3173,1256],{},[70,3175,3176],{"class":72,"line":1259},[70,3177,1262],{},[70,3179,3180],{"class":72,"line":1265},[70,3181,1268],{},[70,3183,3184],{"class":72,"line":1271},[70,3185,155],{"emptyLinePlaceholder":154},[70,3187,3188],{"class":72,"line":1276},[70,3189,1279],{},[70,3191,3192],{"class":72,"line":1282},[70,3193,1285],{},[70,3195,3196],{"class":72,"line":1288},[70,3197,1291],{},[70,3199,3200],{"class":72,"line":1294},[70,3201,155],{"emptyLinePlaceholder":154},[70,3203,3204],{"class":72,"line":1299},[70,3205,1302],{},[70,3207,3208],{"class":72,"line":1305},[70,3209,1308],{},[70,3211,3212],{"class":72,"line":1311},[70,3213,1314],{},[70,3215,3216],{"class":72,"line":1317},[70,3217,202],{},[70,3219,3220],{"class":72,"line":1322},[70,3221,1325],{},[70,3223,3224],{"class":72,"line":1328},[70,3225,1331],{},[70,3227,3228],{"class":72,"line":1334},[70,3229,1337],{},[70,3231,3232],{"class":72,"line":1340},[70,3233,202],{},[70,3235,3236],{"class":72,"line":1345},[70,3237,1348],{},[70,3239,3240],{"class":72,"line":1351},[70,3241,1354],{},[70,3243,3244],{"class":72,"line":1357},[70,3245,1360],{},[70,3247,3248],{"class":72,"line":1363},[70,3249,155],{"emptyLinePlaceholder":154},[70,3251,3252],{"class":72,"line":1368},[70,3253,1371],{},[70,3255,3256],{"class":72,"line":1374},[70,3257,1377],{},[70,3259,3260],{"class":72,"line":1380},[70,3261,1383],{},[70,3263,3264],{"class":72,"line":1386},[70,3265,155],{"emptyLinePlaceholder":154},[70,3267,3268],{"class":72,"line":1391},[70,3269,1394],{},[70,3271,3272],{"class":72,"line":1397},[70,3273,1400],{},[70,3275,3276],{"class":72,"line":1403},[70,3277,1406],{},[70,3279,3280],{"class":72,"line":1409},[70,3281,1412],{},[70,3283,3284],{"class":72,"line":1415},[70,3285,238],{},[70,3287,3288],{"class":72,"line":1420},[70,3289,155],{"emptyLinePlaceholder":154},[70,3291,3292],{"class":72,"line":1425},[70,3293,1428],{},[70,3295,3296],{"class":72,"line":1431},[70,3297,1434],{},[70,3299,3300],{"class":72,"line":1437},[70,3301,1440],{},[70,3303,3304],{"class":72,"line":1443},[70,3305,1446],{},[70,3307,3308],{"class":72,"line":1449},[70,3309,1452],{},[70,3311,3312],{"class":72,"line":1455},[70,3313,1308],{},[70,3315,3316],{"class":72,"line":1460},[70,3317,1463],{},[70,3319,3320],{"class":72,"line":1466},[70,3321,155],{"emptyLinePlaceholder":154},[70,3323,3324],{"class":72,"line":1471},[70,3325,1474],{},[70,3327,3328],{"class":72,"line":1477},[70,3329,1480],{},[70,3331,3332],{"class":72,"line":1483},[70,3333,202],{},[70,3335,3336],{"class":72,"line":1488},[70,3337,1491],{},[70,3339,3340],{"class":72,"line":1494},[70,3341,1497],{},[70,3343,3344],{"class":72,"line":1500},[70,3345,155],{"emptyLinePlaceholder":154},[70,3347,3348],{"class":72,"line":1505},[70,3349,1508],{},[70,3351,3352],{"class":72,"line":1511},[70,3353,1514],{},[70,3355,3356],{"class":72,"line":1517},[70,3357,1520],{},[70,3359,3360],{"class":72,"line":1523},[70,3361,1526],{},[70,3363,3364],{"class":72,"line":1529},[70,3365,1532],{},[70,3367,3368],{"class":72,"line":1535},[70,3369,155],{"emptyLinePlaceholder":154},[70,3371,3372],{"class":72,"line":1540},[70,3373,1543],{},[70,3375,3376],{"class":72,"line":1546},[70,3377,1549],{},[70,3379,3380],{"class":72,"line":1552},[70,3381,801],{},[70,3383,3384],{"class":72,"line":1557},[70,3385,1560],{},[70,3387,3388],{"class":72,"line":1563},[70,3389,1566],{},[70,3391,3392],{"class":72,"line":1569},[70,3393,1572],{},[70,3395,3396],{"class":72,"line":1575},[70,3397,155],{"emptyLinePlaceholder":154},[70,3399,3400],{"class":72,"line":1580},[70,3401,1583],{},[70,3403,3404],{"class":72,"line":1586},[70,3405,1589],{},[70,3407,3408],{"class":72,"line":1592},[70,3409,1595],{},[70,3411,3412],{"class":72,"line":1598},[70,3413,801],{},[70,3415,3416],{"class":72,"line":1603},[70,3417,1606],{},[70,3419,3420],{"class":72,"line":1609},[70,3421,1612],{},[70,3423,3424],{"class":72,"line":1615},[70,3425,801],{},[70,3427,3428],{"class":72,"line":1620},[70,3429,1623],{},[70,3431,3432],{"class":72,"line":1626},[70,3433,1629],{},[70,3435,3436],{"class":72,"line":1632},[70,3437,1635],{},[70,3439,3440],{"class":72,"line":1638},[70,3441,1641],{},[70,3443,3444],{"class":72,"line":1644},[70,3445,801],{},[70,3447,3448],{"class":72,"line":1649},[70,3449,1652],{},[70,3451,3452],{"class":72,"line":1655},[70,3453,1658],{},[70,3455,3456],{"class":72,"line":1661},[70,3457,3458],{},"        lightingShader.setVec3(\"light.position\", lightPos.x, lightPos.y, lightPos.z);\n",[70,3460,3461],{"class":72,"line":1667},[70,3462,3463],{},"        lightingShader.setFloat(\"light.constant\", 1.0f);\n",[70,3465,3466],{"class":72,"line":1673},[70,3467,3468],{},"        lightingShader.setFloat(\"light.linear\", 0.09f);\n",[70,3470,3471],{"class":72,"line":1679},[70,3472,3473],{},"        lightingShader.setFloat(\"light.quadratic\", 0.032f);\n",[70,3475,3476],{"class":72,"line":1685},[70,3477,1670],{},[70,3479,3480],{"class":72,"line":1691},[70,3481,1676],{},[70,3483,3484],{"class":72,"line":1697},[70,3485,1682],{},[70,3487,3488],{"class":72,"line":1703},[70,3489,1688],{},[70,3491,3492],{"class":72,"line":1708},[70,3493,1694],{},[70,3495,3496],{"class":72,"line":1714},[70,3497,1700],{},[70,3499,3500],{"class":72,"line":1720},[70,3501,801],{},[70,3503,3504],{"class":72,"line":1726},[70,3505,1711],{},[70,3507,3508],{"class":72,"line":1731},[70,3509,1717],{},[70,3511,3512],{"class":72,"line":1737},[70,3513,1723],{},[70,3515,3516],{"class":72,"line":1742},[70,3517,801],{},[70,3519,3520],{"class":72,"line":1748},[70,3521,1734],{},[70,3523,3524],{"class":72,"line":1754},[70,3525,801],{},[70,3527,3528],{"class":72,"line":1760},[70,3529,1745],{},[70,3531,3532],{"class":72,"line":1766},[70,3533,1751],{},[70,3535,3536],{"class":72,"line":1772},[70,3537,1757],{},[70,3539,3540],{"class":72,"line":1778},[70,3541,3542],{},"            glm::vec3( 4.0f,  0.0f,  0.0f),\n",[70,3544,3545],{"class":72,"line":1784},[70,3546,3547],{},"            glm::vec3(-4.0f,  0.0f,  0.0f),\n",[70,3549,3550],{"class":72,"line":1790},[70,3551,3552],{},"            glm::vec3( 0.0f,  0.0f, -8.0f),\n",[70,3554,3555],{"class":72,"line":1796},[70,3556,3557],{},"            glm::vec3( 0.0f,  0.0f,  8.0f),\n",[70,3559,3560],{"class":72,"line":1802},[70,3561,3562],{},"            glm::vec3( 8.0f,  0.0f, -8.0f),\n",[70,3564,3565],{"class":72,"line":1808},[70,3566,3567],{},"            glm::vec3(-10.0f,  0.0f, -2.0f),\n",[70,3569,3570],{"class":72,"line":1814},[70,3571,1799],{},[70,3573,3574],{"class":72,"line":1820},[70,3575,1805],{},[70,3577,3578],{"class":72,"line":1825},[70,3579,1811],{},[70,3581,3582],{"class":72,"line":1831},[70,3583,1817],{},[70,3585,3586],{"class":72,"line":1837},[70,3587,801],{},[70,3589,3590],{"class":72,"line":1843},[70,3591,1828],{},[70,3593,3594],{"class":72,"line":1849},[70,3595,1834],{},[70,3597,3598],{"class":72,"line":1855},[70,3599,1840],{},[70,3601,3602],{"class":72,"line":1861},[70,3603,1846],{},[70,3605,3606],{"class":72,"line":1867},[70,3607,1852],{},[70,3609,3610],{"class":72,"line":1872},[70,3611,1858],{},[70,3613,3614],{"class":72,"line":1878},[70,3615,1864],{},[70,3617,3618],{"class":72,"line":1883},[70,3619,155],{"emptyLinePlaceholder":154},[70,3621,3622],{"class":72,"line":1888},[70,3623,1875],{},[70,3625,3626],{"class":72,"line":1894},[70,3627,819],{},[70,3629,3630],{"class":72,"line":1900},[70,3631,155],{"emptyLinePlaceholder":154},[70,3633,3634],{"class":72,"line":1906},[70,3635,1903],{},[70,3637,3638],{"class":72,"line":1912},[70,3639,1909],{},[70,3641,3642],{"class":72,"line":1918},[70,3643,1915],{},[70,3645,3646],{"class":72,"line":1923},[70,3647,801],{},[70,3649,3650],{"class":72,"line":1929},[70,3651,1926],{},[70,3653,3654],{"class":72,"line":1935},[70,3655,1932],{},[70,3657,3658],{"class":72,"line":1941},[70,3659,1938],{},[70,3661,3662],{"class":72,"line":1947},[70,3663,1944],{},[70,3665,3666],{"class":72,"line":1953},[70,3667,1950],{},[70,3669,3670],{"class":72,"line":1958},[70,3671,801],{},[70,3673,3674],{"class":72,"line":1964},[70,3675,1961],{},[70,3677,3678],{"class":72,"line":1970},[70,3679,1967],{},[70,3681,3682],{"class":72,"line":1976},[70,3683,801],{},[70,3685,3686],{"class":72,"line":1981},[70,3687,1984],{},[70,3689,3690],{"class":72,"line":1987},[70,3691,1990],{},[70,3693,3694],{"class":72,"line":1993},[70,3695,155],{"emptyLinePlaceholder":154},[70,3697,3698],{"class":72,"line":1998},[70,3699,2001],{},[70,3701,3702],{"class":72,"line":2004},[70,3703,2007],{},[70,3705,3706],{"class":72,"line":2010},[70,3707,238],{},[70,3709,3710],{"class":72,"line":2015},[70,3711,155],{"emptyLinePlaceholder":154},[70,3713,3714],{"class":72,"line":2020},[70,3715,2023],{},[70,3717,3718],{"class":72,"line":2026},[70,3719,2029],{},[70,3721,3722],{"class":72,"line":2032},[70,3723,155],{"emptyLinePlaceholder":154},[70,3725,3726],{"class":72,"line":2037},[70,3727,2040],{},[70,3729,3730],{"class":72,"line":2043},[70,3731,2046],{},[70,3733,3734],{"class":72,"line":2049},[70,3735,2052],{},[70,3737,3738],{"class":72,"line":2055},[70,3739,2058],{},[70,3741,3742],{"class":72,"line":2061},[70,3743,2064],{},[70,3745,3746],{"class":72,"line":2067},[70,3747,2070],{},[70,3749,3750],{"class":72,"line":2073},[70,3751,2076],{},[70,3753,3754],{"class":72,"line":2079},[70,3755,353],{},[70,3757,3759],{"class":72,"line":3758},351,[70,3760,155],{"emptyLinePlaceholder":154},[70,3762,3764],{"class":72,"line":3763},352,[70,3765,155],{"emptyLinePlaceholder":154},[70,3767,3769],{"class":72,"line":3768},353,[70,3770,3771],{},"#version 330 core\n",[70,3773,3775],{"class":72,"line":3774},354,[70,3776,3777],{},"out vec4 FragColor;\n",[70,3779,3781],{"class":72,"line":3780},355,[70,3782,155],{"emptyLinePlaceholder":154},[70,3784,3786],{"class":72,"line":3785},356,[70,3787,3788],{},"struct Material{\n",[70,3790,3792],{"class":72,"line":3791},357,[70,3793,3794],{},"    sampler2D diffuse;\n",[70,3796,3798],{"class":72,"line":3797},358,[70,3799,3800],{},"    vec3 specular;\n",[70,3802,3804],{"class":72,"line":3803},359,[70,3805,3806],{},"    float shininess;\n",[70,3808,3810],{"class":72,"line":3809},360,[70,3811,1204],{},[70,3813,3815],{"class":72,"line":3814},361,[70,3816,155],{"emptyLinePlaceholder":154},[70,3818,3820],{"class":72,"line":3819},362,[70,3821,3822],{},"struct Light{\n",[70,3824,3826],{"class":72,"line":3825},363,[70,3827,3828],{},"    vec3 position;\n",[70,3830,3832],{"class":72,"line":3831},364,[70,3833,3834],{},"    vec3 ambient;\n",[70,3836,3838],{"class":72,"line":3837},365,[70,3839,3840],{},"    vec3 diffuse;\n",[70,3842,3844],{"class":72,"line":3843},366,[70,3845,3800],{},[70,3847,3849],{"class":72,"line":3848},367,[70,3850,155],{"emptyLinePlaceholder":154},[70,3852,3854],{"class":72,"line":3853},368,[70,3855,3856],{},"    float constant;\n",[70,3858,3860],{"class":72,"line":3859},369,[70,3861,3862],{},"    float linear;\n",[70,3864,3866],{"class":72,"line":3865},370,[70,3867,3868],{},"    float quadratic;\n",[70,3870,3872],{"class":72,"line":3871},371,[70,3873,1204],{},[70,3875,3877],{"class":72,"line":3876},372,[70,3878,155],{"emptyLinePlaceholder":154},[70,3880,3882],{"class":72,"line":3881},373,[70,3883,3884],{},"uniform Light light;\n",[70,3886,3888],{"class":72,"line":3887},374,[70,3889,3890],{},"uniform vec3 viewPos;\n",[70,3892,3894],{"class":72,"line":3893},375,[70,3895,3896],{},"uniform Material material;\n",[70,3898,3900],{"class":72,"line":3899},376,[70,3901,3902],{},"in vec3 Normal;\n",[70,3904,3906],{"class":72,"line":3905},377,[70,3907,3908],{},"in vec3 FragPos;\n",[70,3910,3912],{"class":72,"line":3911},378,[70,3913,3914],{},"in vec2 TexCoords;\n",[70,3916,3918],{"class":72,"line":3917},379,[70,3919,155],{"emptyLinePlaceholder":154},[70,3921,3923],{"class":72,"line":3922},380,[70,3924,3925],{},"void main()\n",[70,3927,3929],{"class":72,"line":3928},381,[70,3930,3931],{},"{\n",[70,3933,3935],{"class":72,"line":3934},382,[70,3936,3937],{},"    \u002F\u002F 从纹理采样颜色\n",[70,3939,3941],{"class":72,"line":3940},383,[70,3942,3943],{},"    vec3 textureColor = vec3(texture(material.diffuse, TexCoords));\n",[70,3945,3947],{"class":72,"line":3946},384,[70,3948,3949],{},"    float distance    = length(light.position - FragPos);\n",[70,3951,3953],{"class":72,"line":3952},385,[70,3954,3955],{},"    float attenuation = 1.0 \u002F (light.constant + light.linear * distance + \n",[70,3957,3959],{"class":72,"line":3958},386,[70,3960,3961],{},"                 light.quadratic * (distance * distance));\n",[70,3963,3965],{"class":72,"line":3964},387,[70,3966,3967],{},"    \u002F\u002F 环境光\n",[70,3969,3971],{"class":72,"line":3970},388,[70,3972,3973],{},"    vec3 ambient = attenuation* light.ambient * textureColor;\n",[70,3975,3977],{"class":72,"line":3976},389,[70,3978,202],{},[70,3980,3982],{"class":72,"line":3981},390,[70,3983,3984],{},"    \u002F\u002F 漫反射\n",[70,3986,3988],{"class":72,"line":3987},391,[70,3989,3990],{},"    vec3 norm = normalize(Normal);\n",[70,3992,3994],{"class":72,"line":3993},392,[70,3995,3996],{},"    vec3 lightDir = normalize(light.position - FragPos);\n",[70,3998,4000],{"class":72,"line":3999},393,[70,4001,4002],{},"    float diff = max(dot(norm, lightDir), 0.0);\n",[70,4004,4006],{"class":72,"line":4005},394,[70,4007,4008],{},"    vec3 diffuse = attenuation* light.diffuse * (diff * textureColor);\n",[70,4010,4012],{"class":72,"line":4011},395,[70,4013,202],{},[70,4015,4017],{"class":72,"line":4016},396,[70,4018,4019],{},"    \u002F\u002F 镜面反射\n",[70,4021,4023],{"class":72,"line":4022},397,[70,4024,4025],{},"    vec3 viewDir = normalize(viewPos - FragPos);\n",[70,4027,4029],{"class":72,"line":4028},398,[70,4030,4031],{},"    vec3 reflectDir = reflect(-lightDir, norm);\n",[70,4033,4035],{"class":72,"line":4034},399,[70,4036,4037],{},"    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);\n",[70,4039,4041],{"class":72,"line":4040},400,[70,4042,4043],{},"    vec3 specular = attenuation* light.specular * (spec * material.specular);\n",[70,4045,4047],{"class":72,"line":4046},401,[70,4048,202],{},[70,4050,4052],{"class":72,"line":4051},402,[70,4053,4054],{},"    \u002F\u002F 最终结果\n",[70,4056,4058],{"class":72,"line":4057},403,[70,4059,4060],{},"    vec3 result = ambient + diffuse + specular;\n",[70,4062,4064],{"class":72,"line":4063},404,[70,4065,4066],{},"    FragColor = vec4(result, 1.0);\n",[70,4068,4070],{"class":72,"line":4069},405,[70,4071,353],{},[10,4073,4074],{"id":4074},"聚光",[13,4076,4077,4078,4081,4082,4085,4086],{},"我们要讨论的最后一种类型的光是",[17,4079,4080],{},"聚光(Spotlight)。聚光是位于环境中某个位置的光源","，它只朝一个特定方向而不是所有方向照射光线。这样的结果就是只有在聚光方向的",[17,4083,4084],{},"特定半径内的物体才会被照亮，其它的物体都会保持黑暗","。",[17,4087,4088],{},"聚光很好的例子就是路灯或手电筒。",[13,4090,4091],{},"OpenGL中聚光是用一个世界空间位置、一个方向和一个切光角(Cutoff Angle)来表示的，切光角指定了聚光的半径（译注：是圆锥的半径不是距光源距离那个半径）。对于每个片段，我们会计算片段是否位于聚光的切光方向之间（也就是在锥形内），如果是的话，我们就会相应地照亮片段。下面这张图会让你明白聚光是如何工作的：",[13,4093,4094],{},[27,4095],{"alt":27,"src":4096},".\u002Fassets\u002Flight_casters_spotlight_angles.png",[2109,4098,4099,4105,4111,4117],{},[2112,4100,4101,4104],{},[67,4102,4103],{},"LightDir","：从片段指向光源的向量。",[2112,4106,4107,4110],{},[67,4108,4109],{},"SpotDir","：聚光所指向的方向。",[2112,4112,4113,4116],{},[67,4114,4115],{},"Phi","ϕ：指定了聚光半径的切光角。落在这个角度之外的物体都不会被这个聚光所照亮。",[2112,4118,4119,4122],{},[67,4120,4121],{},"Theta","θ：LightDir向量和SpotDir向量之间的夹角。在聚光内部的话θ值应该比ϕ值小",[13,4124,4125],{},"所以我们要做的就是计算LightDir向量和SpotDir向量之间的点积（还记得它会返回两个单位向量夹角的余弦值吗？），并将它与切光角ϕ值对比。你现在应该了解聚光究竟是什么了，下面我们将以手电筒的形式创建一个聚光。",[4127,4128,4129],"h2",{"id":4129},"手电筒",[13,4131,4132],{},"手电筒(Flashlight)是一个位于观察者位置的聚光，通常它都会瞄准玩家视角的正前方。基本上说，手电筒就是普通的聚光，但它的位置和方向会随着玩家的位置和朝向不断更新。",[13,4134,4135,4136,4139,4140,4143],{},"所以，在",[17,4137,4138],{},"片段着色器中我们需要的值有聚光的位置向量","（来计算光的方向向量，感觉就像是平行光和点光源合成一样。。。。。）、",[17,4141,4142],{},"聚光的方向向量和一个切光角","。我们可以将它们储存在Light结构体中：",[60,4145,4147],{"className":62,"code":4146,"language":64,"meta":65,"style":65},"struct Light {\n    vec3  position;\n    vec3  direction;\n    float cutOff;\n    ...\n};\n",[67,4148,4149,4154,4159,4164,4169,4174],{"__ignoreMap":65},[70,4150,4151],{"class":72,"line":73},[70,4152,4153],{},"struct Light {\n",[70,4155,4156],{"class":72,"line":79},[70,4157,4158],{},"    vec3  position;\n",[70,4160,4161],{"class":72,"line":85},[70,4162,4163],{},"    vec3  direction;\n",[70,4165,4166],{"class":72,"line":91},[70,4167,4168],{},"    float cutOff;\n",[70,4170,4171],{"class":72,"line":97},[70,4172,4173],{},"    ...\n",[70,4175,4176],{"class":72,"line":103},[70,4177,1204],{},[13,4179,4180],{},"接下来我们将合适的值传到着色器中：",[60,4182,4184],{"className":62,"code":4183,"language":64,"meta":65,"style":65},"lightingShader.setVec3(\"light.position\",  camera.Position);\nlightingShader.setVec3(\"light.direction\", camera.Front);\nlightingShader.setFloat(\"light.cutOff\",   glm::cos(glm::radians(12.5f)));\n",[67,4185,4186,4191,4196],{"__ignoreMap":65},[70,4187,4188],{"class":72,"line":73},[70,4189,4190],{},"lightingShader.setVec3(\"light.position\",  camera.Position);\n",[70,4192,4193],{"class":72,"line":79},[70,4194,4195],{},"lightingShader.setVec3(\"light.direction\", camera.Front);\n",[70,4197,4198],{"class":72,"line":85},[70,4199,4200],{},"lightingShader.setFloat(\"light.cutOff\",   glm::cos(glm::radians(12.5f)));\n",[13,4202,4203],{},"接下来就是计算θ值，并将它和切光角ϕ对比，来决定是否在聚光的内部：",[60,4205,4207],{"className":62,"code":4206,"language":64,"meta":65,"style":65},"float theta = dot(lightDir, normalize(-light.direction));\n\nif(theta > light.cutOff) \n{       \n  \u002F\u002F 执行光照计算\n}\nelse  \u002F\u002F 否则，使用环境光，让场景在聚光之外时不至于完全黑暗\n  color = vec4(light.ambient * vec3(texture(material.diffuse, TexCoords)), 1.0);\n",[67,4208,4209,4214,4218,4223,4228,4233,4237,4242],{"__ignoreMap":65},[70,4210,4211],{"class":72,"line":73},[70,4212,4213],{},"float theta = dot(lightDir, normalize(-light.direction));\n",[70,4215,4216],{"class":72,"line":79},[70,4217,155],{"emptyLinePlaceholder":154},[70,4219,4220],{"class":72,"line":85},[70,4221,4222],{},"if(theta > light.cutOff) \n",[70,4224,4225],{"class":72,"line":91},[70,4226,4227],{},"{       \n",[70,4229,4230],{"class":72,"line":97},[70,4231,4232],{},"  \u002F\u002F 执行光照计算\n",[70,4234,4235],{"class":72,"line":103},[70,4236,353],{},[70,4238,4239],{"class":72,"line":109},[70,4240,4241],{},"else  \u002F\u002F 否则，使用环境光，让场景在聚光之外时不至于完全黑暗\n",[70,4243,4244],{"class":72,"line":115},[70,4245,4246],{},"  color = vec4(light.ambient * vec3(texture(material.diffuse, TexCoords)), 1.0);\n",[13,4248,4249],{},"我们首先计算了lightDir和取反的direction向量（取反的是因为我们想让向量指向光源而不是从光源出发）之间的点积。记住要对所有的相关向量标准化。",[4127,4251,4253],{"id":4252},"平滑软化边缘","平滑\u002F软化边缘",[13,4255,4256,4257,4260],{},"为了",[17,4258,4259],{},"创建一种看起来边缘平滑的聚光","，我们需要模拟聚光有一个内圆锥(Inner Cone)和一个外圆锥(Outer Cone)。我们可以将内圆锥设置为上一部分中的那个圆锥，但我们也需要一个外圆锥，来让光从内圆锥逐渐减暗，直到外圆锥的边界。",[13,4262,4263],{},"为了创建一个外圆锥，我们只需要再定义一个余弦值来代表聚光方向向量和外圆锥向量（等于它的半径）的夹角。然后，如果一个片段处于内外圆锥之间，将会给它计算出一个0.0到1.0之间的强度值。如果片段在内圆锥之内，它的强度就是1.0，如果在外圆锥之外强度值就是0.0。",[13,4265,4266],{},"我们可以用下面这个公式来计算这个值：",[13,4268,4269],{},[27,4270],{"alt":4271,"src":4272},"image-20251029204700049",".\u002Fassets\u002Fimage-20251029204700049.png",[13,4274,4275],{},"很难去表现这个公式是怎么工作的，所以我们用一些实例值来看看：",[2153,4277,4278,4306],{},[2156,4279,4280],{},[2159,4281,4282,4285,4288,4291,4294,4297,4300,4303],{},[2162,4283,4284],{"align":2164},"θ",[2162,4286,4287],{"align":2164},"θ（角度）",[2162,4289,4290],{"align":2164},"ϕ（内光切）",[2162,4292,4293],{"align":2164},"ϕ（角度）",[2162,4295,4296],{"align":2164},"γ（外光切）",[2162,4298,4299],{"align":2164},"γ（角度）",[2162,4301,4302],{"align":2164},"ϵ",[2162,4304,4305],{"align":2164},"I",[2176,4307,4308,4334,4355,4376,4397,4417],{},[2159,4309,4310,4313,4316,4319,4322,4325,4328,4331],{},[2181,4311,4312],{"align":2164},"0.87",[2181,4314,4315],{"align":2164},"30",[2181,4317,4318],{"align":2164},"0.91",[2181,4320,4321],{"align":2164},"25",[2181,4323,4324],{"align":2164},"0.82",[2181,4326,4327],{"align":2164},"35",[2181,4329,4330],{"align":2164},"0.91 - 0.82 = 0.09",[2181,4332,4333],{"align":2164},"0.87 - 0.82 \u002F 0.09 = 0.56",[2159,4335,4336,4339,4342,4344,4346,4348,4350,4352],{},[2181,4337,4338],{"align":2164},"0.9",[2181,4340,4341],{"align":2164},"26",[2181,4343,4318],{"align":2164},[2181,4345,4321],{"align":2164},[2181,4347,4324],{"align":2164},[2181,4349,4327],{"align":2164},[2181,4351,4330],{"align":2164},[2181,4353,4354],{"align":2164},"0.9 - 0.82 \u002F 0.09 = 0.89",[2159,4356,4357,4360,4363,4365,4367,4369,4371,4373],{},[2181,4358,4359],{"align":2164},"0.97",[2181,4361,4362],{"align":2164},"14",[2181,4364,4318],{"align":2164},[2181,4366,4321],{"align":2164},[2181,4368,4324],{"align":2164},[2181,4370,4327],{"align":2164},[2181,4372,4330],{"align":2164},[2181,4374,4375],{"align":2164},"0.97 - 0.82 \u002F 0.09 = 1.67",[2159,4377,4378,4381,4384,4386,4388,4390,4392,4394],{},[2181,4379,4380],{"align":2164},"0.83",[2181,4382,4383],{"align":2164},"34",[2181,4385,4318],{"align":2164},[2181,4387,4321],{"align":2164},[2181,4389,4324],{"align":2164},[2181,4391,4327],{"align":2164},[2181,4393,4330],{"align":2164},[2181,4395,4396],{"align":2164},"0.83 - 0.82 \u002F 0.09 = 0.11",[2159,4398,4399,4402,4404,4406,4408,4410,4412,4414],{},[2181,4400,4401],{"align":2164},"0.64",[2181,4403,2236],{"align":2164},[2181,4405,4318],{"align":2164},[2181,4407,4321],{"align":2164},[2181,4409,4324],{"align":2164},[2181,4411,4327],{"align":2164},[2181,4413,4330],{"align":2164},[2181,4415,4416],{"align":2164},"0.64 - 0.82 \u002F 0.09 = -2.0",[2159,4418,4419,4422,4425,4428,4431,4434,4437,4440],{},[2181,4420,4421],{"align":2164},"0.966",[2181,4423,4424],{"align":2164},"15",[2181,4426,4427],{"align":2164},"0.9978",[2181,4429,4430],{"align":2164},"12.5",[2181,4432,4433],{"align":2164},"0.953",[2181,4435,4436],{"align":2164},"17.5",[2181,4438,4439],{"align":2164},"0.9978 - 0.953 = 0.0448",[2181,4441,4442],{"align":2164},"0.966 - 0.953 \u002F 0.0448 = 0.29",[13,4444,4445,4446,4449,4450,4453],{},"我们现在有了一个",[17,4447,4448],{},"在聚光外是负的，在内圆锥内大于1.0的，在边缘处于两者之间的强度值了","。如果我们正确地约束(Clamp)这个值，在片段着色器中就不再需要",[67,4451,4452],{},"if-else","了，我们能够使用计算出来的强度值直接乘以光照分量：",[60,4455,4457],{"className":62,"code":4456,"language":64,"meta":65,"style":65},"float theta     = dot(lightDir, normalize(-light.direction));\nfloat epsilon   = light.cutOff - light.outerCutOff;\nfloat intensity = clamp((theta - light.outerCutOff) \u002F epsilon, 0.0, 1.0);    \n...\n\u002F\u002F 将不对环境光做出影响，让它总是能有一点光\ndiffuse  *= intensity;\nspecular *= intensity;\n...\n",[67,4458,4459,4464,4469,4474,4479,4484,4489,4494],{"__ignoreMap":65},[70,4460,4461],{"class":72,"line":73},[70,4462,4463],{},"float theta     = dot(lightDir, normalize(-light.direction));\n",[70,4465,4466],{"class":72,"line":79},[70,4467,4468],{},"float epsilon   = light.cutOff - light.outerCutOff;\n",[70,4470,4471],{"class":72,"line":85},[70,4472,4473],{},"float intensity = clamp((theta - light.outerCutOff) \u002F epsilon, 0.0, 1.0);    \n",[70,4475,4476],{"class":72,"line":91},[70,4477,4478],{},"...\n",[70,4480,4481],{"class":72,"line":97},[70,4482,4483],{},"\u002F\u002F 将不对环境光做出影响，让它总是能有一点光\n",[70,4485,4486],{"class":72,"line":103},[70,4487,4488],{},"diffuse  *= intensity;\n",[70,4490,4491],{"class":72,"line":109},[70,4492,4493],{},"specular *= intensity;\n",[70,4495,4496],{"class":72,"line":115},[70,4497,4478],{},[13,4499,4500,4501,4504],{},"注意我们使用了clamp函数，它把第一个参数约束(Clamp)在了0.0到1.0之间。这保证强度值不会在",[70,4502,4503],{},"0, 1","区间之外。",[13,4506,4507,4508],{},"确定你将outerCutOff值添加到了Light结构体之中，并在程序中设置它的uniform值。下面的图片中，我们使用的内切光角是12.5，外切光角是17.5：",[27,4509],{"alt":27,"src":4510},".\u002Fassets\u002Flight_casters_spotlight.png",[13,4512,4513],{},"完整代码：",[60,4515,4517],{"className":62,"code":4516,"language":64,"meta":65,"style":65},"#include \u003Cglad\u002Fglad.h>\n#include \u003CGLFW\u002Fglfw3.h>\n#include \u003Ciostream>\n#include \u003Ccmath>\n#include \u003Cimgui.h>\n#include \u003Cimgui_impl_glfw.h>\n#include \u003Cimgui_impl_opengl3.h>\n#include \u003Copencv2\u002Fopencv.hpp>\n#include \u003Copencv2\u002Fimgcodecs.hpp>\n#include \"utils\u002Fshader.h\"\n#include \"utils\u002FglmCompute.h\"\n#include \"include\u002Fdpi_stabilizer.h\"\n#include \"include\u002Fimgui_manager.h\"\n\nusing namespace glm;\nusing namespace cv;\n\n\u002F\u002F 使用 OpenCV 加载纹理\nunsigned int loadTexture(const char* path) {\n    unsigned int textureID;\n    glGenTextures(1, &textureID);\n    \n    \u002F\u002F 使用 OpenCV 读取图像\n    Mat image = imread(path, IMREAD_COLOR);\n    if (image.empty()) {\n        std::cout \u003C\u003C \"Failed to load texture at path: \" \u003C\u003C path \u003C\u003C std::endl;\n        return textureID;\n    }\n    \n    \u002F\u002F OpenCV 使用 BGR，OpenGL 使用 RGB，需要转换\n    Mat rgbImage;\n    cvtColor(image, rgbImage, COLOR_BGR2RGB);\n    \n    \u002F\u002F 翻转图像（OpenGL 期望的 Y 轴方向）\n    flip(rgbImage, rgbImage, 0);\n    \n    glBindTexture(GL_TEXTURE_2D, textureID);\n    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, rgbImage.cols, rgbImage.rows, 0, GL_RGB, GL_UNSIGNED_BYTE, rgbImage.data);\n    glGenerateMipmap(GL_TEXTURE_2D);\n    \n    \u002F\u002F 设置纹理参数\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n    \n    return textureID;\n}\n\n\u002F\u002F 窗口大小改变时的回调函数\nvoid framebuffer_size_callback(GLFWwindow* window, int width, int height) {\n    glViewport(0, 0, width, height);\n}\n\n\n\u002F\u002F 全局变量\nfloat deltaTime = 0.0f; \u002F\u002F 当前帧与上一帧的时间差\nfloat lastFrame = 0.0f; \u002F\u002F 上一帧的时间\n\n\u002F\u002F DPI 稳定器实例\nDPIStabilizer dpiStabilizer(0.1f, 50.0f, 0.8f);\n\n\u002F\u002F ImGui 管理器实例\nImGuiManager imguiManager;\n\n\u002F\u002F 简化的输入处理函数\nvoid processInput(GLFWwindow *window){\n    \u002F\u002F 使用 DPI 稳定器处理所有输入\n    dpiStabilizer.processKeyboardInput(window);\n\n    if (glfwGetKey(window, GLFW_KEY_F2) == GLFW_PRESS) {\n        imguiManager.toggleWindow(\"控制面板\");\n    }\n    if (glfwGetKey(window, GLFW_KEY_F3) == GLFW_PRESS) {\n        imguiManager.toggleWindow(\"性能监控\");\n    }\n    if (glfwGetKey(window, GLFW_KEY_F4) == GLFW_PRESS) {\n        imguiManager.toggleWindow(\"调试信息\");\n    }\n    \n    \u002F\u002F 显示统计信息 (按 T 键)\n    if (glfwGetKey(window, GLFW_KEY_T) == GLFW_PRESS) {\n        std::cout \u003C\u003C dpiStabilizer.getStats() \u003C\u003C std::endl;\n    }\n}\n\nint main() {\n    \u002F\u002F 投影矩阵\n    mat4 projection = perspective(radians(45.0f), (float)800 \u002F (float)600, 0.1f, 100.0f);\n\n    \u002F\u002F 初始化GLFW\n    glfwInit();\n    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);\n    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);\n    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);\n    \n    \u002F\u002F 设置 DPI 感知提示\n    DPIStabilizer::setDPIHint();\n\n    \u002F\u002F 创建窗口\n    GLFWwindow* window = glfwCreateWindow(800, 600, \"OpenGL Window\", NULL, NULL);\n    if (window == NULL) {\n        std::cout \u003C\u003C \"Failed to create GLFW window\" \u003C\u003C std::endl;\n        glfwTerminate();\n        return -1;\n    }\n\n    glfwMakeContextCurrent(window);\n    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);\n    \n    \u002F\u002F 初始化 DPI 稳定器\n    dpiStabilizer.initialize(window);\n\n    \u002F\u002F 初始化GLAD\n    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {\n        std::cout \u003C\u003C \"Failed to initialize GLAD\" \u003C\u003C std::endl;\n        return -1;\n    }\n\n    \u002F\u002F 初始化 ImGui 管理器\n    imguiManager.initialize(window);\n    \n    \u002F\u002F 注册窗口\n    imguiManager.registerWindow(\"Hello ImGui!\", []() {\n        ImGui::Text(\"DPI 稳定器状态: %s\", dpiStabilizer.isMouseCaptured() ? \"已捕获\" : \"已释放\");\n        ImGui::Text(\"鼠标敏感度: %.3f\", dpiStabilizer.getSensitivity());\n        \n        if (ImGui::Button(\"重置统计\")) {\n            dpiStabilizer.resetStats();\n        }\n        \n        float sensitivity = dpiStabilizer.getSensitivity();\n        if (ImGui::SliderFloat(\"鼠标敏感度\", &sensitivity, 0.01f, 0.5f)) {\n            dpiStabilizer.setSensitivity(sensitivity);\n        }\n    });\n    \n    imguiManager.registerWindow(\"控制面板\", []() {\n        ImGuiIO& io = ImGui::GetIO();\n        ImGui::Text(\"FPS: %.1f\", io.Framerate);\n        ImGui::Text(\"帧时间: %.3f ms\", 1000.0f \u002F io.Framerate);\n    });\n    \n\n    \u002F\u002F 启用深度测试\n    glEnable(GL_DEPTH_TEST);\n\n    float vertices[] = {\n    \u002F\u002F 立方体顶点数据（位置 + 法线 + 纹理坐标）\n    \u002F\u002F 位置(3) + 法线(3) + 纹理坐标(2) = 8个浮点数\n    \u002F\u002F 前面\n    -0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  0.0f, 0.0f,\n     0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  1.0f, 0.0f,\n     0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  1.0f, 1.0f,\n     0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  1.0f, 1.0f,\n    -0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  0.0f, 1.0f,\n    -0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  0.0f, 0.0f,\n    \n    \u002F\u002F 后面\n    -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f, 0.0f,\n     0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f, 0.0f,\n     0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f, 1.0f,\n     0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f, 1.0f,\n    -0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f, 1.0f,\n    -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f, 0.0f,\n    \n    \u002F\u002F 左面\n    -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,  1.0f, 0.0f,\n    -0.5f,  0.5f, -0.5f, -1.0f,  0.0f,  0.0f,  1.0f, 1.0f,\n    -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,  0.0f, 1.0f,\n    -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,  0.0f, 1.0f,\n    -0.5f, -0.5f,  0.5f, -1.0f,  0.0f,  0.0f,  0.0f, 0.0f,\n    -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,  1.0f, 0.0f,\n    \n    \u002F\u002F 右面\n     0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  1.0f, 0.0f,\n     0.5f,  0.5f, -0.5f,  1.0f,  0.0f,  0.0f,  1.0f, 1.0f,\n     0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,  0.0f, 1.0f,\n     0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,  0.0f, 1.0f,\n     0.5f, -0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  0.0f, 0.0f,\n     0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  1.0f, 0.0f,\n    \n    \u002F\u002F 下面\n    -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,  0.0f, 1.0f,\n     0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,  1.0f, 1.0f,\n     0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,  1.0f, 0.0f,\n     0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,  1.0f, 0.0f,\n    -0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,  0.0f, 0.0f,\n    -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,  0.0f, 1.0f,\n    \n    \u002F\u002F 上面\n    -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  0.0f, 1.0f,\n     0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  1.0f, 1.0f,\n     0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  1.0f, 0.0f,\n     0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  1.0f, 0.0f,\n    -0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  0.0f, 0.0f,\n    -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  0.0f, 1.0f\n};\n    \n    \u002F\u002F 创建VAO\n    unsigned int VAO;\n    glGenVertexArrays(1, &VAO);\n    glBindVertexArray(VAO); \u002F\u002F 绑定VAO\n\n    \u002F\u002F 创建VBO\n    unsigned int VBO;\n    glGenBuffers(1, &VBO);\n    glBindBuffer(GL_ARRAY_BUFFER, VBO); \u002F\u002F 绑定VBO\n    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);\n\n    int nrAttributes;\n    glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);\n    std::cout \u003C\u003C \"Maximum nr of vertex attributes supported: \" \u003C\u003C nrAttributes \u003C\u003C std::endl;\n\n    \u002F\u002F 位置属性\n    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);\n    glEnableVertexAttribArray(0); \u002F\u002F 启用顶点属性\n    \n    \u002F\u002F 法线属性\n    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));\n    glEnableVertexAttribArray(1); \u002F\u002F 启用法线属性\n    \n    \u002F\u002F 纹理坐标属性\n    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));\n    glEnableVertexAttribArray(2); \u002F\u002F 启用纹理坐标属性\n\n    \u002F\u002F 构建并编译着色器程序\n    Shader lightingShader(\"shader\u002Flighting.vert\", \"shader\u002Flighting.frag\");\n    Shader lampShader(\"shader\u002Flamp.vert\", \"shader\u002Flamp.frag\");\n\n    \u002F\u002F 加载纹理\n    unsigned int texture1 = loadTexture(\"Assets\u002Fimage.png\");\n    if (texture1 == 0) {\n        std::cout \u003C\u003C \"Failed to load texture\" \u003C\u003C std::endl;\n    }\n\n    \u002F\u002F 创建光源的 VAO\n    unsigned int lightVAO;\n    glGenVertexArrays(1, &lightVAO);\n    glBindVertexArray(lightVAO);\n    glBindBuffer(GL_ARRAY_BUFFER, VBO);\n    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);\n    glEnableVertexAttribArray(0);\n\n    \u002F\u002F 光源位置\n    vec3 lightPos(1.2f, 1.0f, 2.0f);\n    \n    \u002F\u002F 相机位置（观察空间）\n    vec3 viewPos = dpiStabilizer.getCameraPosition();\n\n    \u002F\u002F 渲染循环\n    while (!glfwWindowShouldClose(window)) {\n        \u002F\u002F 处理输入\n        processInput(window);\n        glfwPollEvents();\n\n        \u002F\u002F 从 DPI 稳定器获取 view 矩阵\n        mat4 view = dpiStabilizer.getViewMatrix();\n        \n        float currentFrame = glfwGetTime();\n        deltaTime = currentFrame - lastFrame;\n        lastFrame = currentFrame;\n\n        \u002F\u002F 渲染指令\n        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);\n        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);\n        \n        \u002F\u002F 使用光照着色器绘制物体\n        lightingShader.use();\n        \n        \u002F\u002F 绑定纹理\n        glActiveTexture(GL_TEXTURE0);\n        glBindTexture(GL_TEXTURE_2D, texture1);\n        lightingShader.setInt(\"material.diffuse\", 0);\n        \n        \u002F\u002F 设置相机位置（观察空间）\n        vec3 currentViewPos = dpiStabilizer.getCameraPosition();\n        vec3 cameraFront = dpiStabilizer.getCameraFront();\n        \n        lightingShader.setMat4(\"projection\", projection);\n        lightingShader.setMat4(\"view\", view);\n        \n        \u002F\u002F 手电筒效果：使用相机位置和朝向\n        lightingShader.setVec3(\"light.position\", currentViewPos.x, currentViewPos.y, currentViewPos.z);\n        lightingShader.setVec3(\"light.direction\", cameraFront.x, cameraFront.y, cameraFront.z);\n        lightingShader.setFloat(\"light.cutOff\", glm::cos(glm::radians(12.5f))); \u002F\u002F 内切角\n        lightingShader.setFloat(\"light.outerCutOff\", glm::cos(glm::radians(17.5f))); \u002F\u002F 外切角\n        lightingShader.setFloat(\"light.constant\", 1.0f);\n        lightingShader.setFloat(\"light.linear\", 0.09f);\n        lightingShader.setFloat(\"light.quadratic\", 0.032f);\n        lightingShader.setVec3(\"light.ambient\",  0.1f, 0.1f, 0.1f);   \u002F\u002F 降低环境光强度，只有聚光灯照到的地方才亮\n        lightingShader.setVec3(\"light.diffuse\",  1.0f, 1.0f, 1.0f);   \u002F\u002F 漫反射强度\n        lightingShader.setVec3(\"light.specular\", 1.0f, 1.0f, 1.0f); \n        lightingShader.setVec3(\"material.specular\", 0.633f, 0.727811f, 0.633f);\n        \u002F\u002F 高光指数：通常乘以 128 (OpenGL 最大的 1.0 对应 128)\n        lightingShader.setFloat(\"material.shininess\", 0.6f * 128.0f); \u002F\u002F 76.8\n        \n        \u002F\u002F 设置相机位置（用于镜面反射计算）\n        lightingShader.setVec3(\"viewPos\", currentViewPos.x, currentViewPos.y, currentViewPos.z);\n        \n        glBindVertexArray(VAO);\n        \n        \u002F\u002F 定义多个立方体的位置（更分散的布局）\n        glm::vec3 cubePositions[] = {\n            glm::vec3( 0.0f,  0.0f,  0.0f),\n            glm::vec3( 4.0f,  0.0f,  0.0f),\n            glm::vec3(-4.0f,  0.0f,  0.0f),\n            glm::vec3( 0.0f,  0.0f, -8.0f),\n            glm::vec3( 0.0f,  0.0f,  8.0f),\n            glm::vec3( 8.0f,  0.0f, -8.0f),\n            glm::vec3(-10.0f,  0.0f, -2.0f),\n            glm::vec3( 2.0f,  0.0f,  2.0f),\n            glm::vec3(-2.0f,  0.0f,  2.0f),\n            glm::vec3( 0.0f,  2.0f,  0.0f)\n        };\n        \n        for(unsigned int i = 0; i \u003C 10; i++)\n        {\n            glm::mat4 model = glm::mat4(1.0f);\n            model = glm::translate(model, cubePositions[i]);\n            float angle = 20.0f * i;\n            model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));\n            lightingShader.setMat4(\"model\", model);\n\n            glDrawArrays(GL_TRIANGLES, 0, 36);\n        }\n\n        \u002F\u002F 点光源立方体绘制（手电筒不需要显示光源立方体，已注释）\n        \u002F*\n        lampShader.use();\n        lampShader.setMat4(\"projection\", projection);\n        lampShader.setMat4(\"view\", view);\n        \n        \u002F\u002F 光源立方体的模型矩阵\n        mat4 lampModel = mat4(1.0f);\n        lampModel = translate(lampModel, lightPos);\n        lampModel = scale(lampModel, vec3(0.2f)); \u002F\u002F 缩小光源立方体\n        lampShader.setMat4(\"model\", lampModel);\n        \n        glBindVertexArray(lightVAO);\n        glDrawArrays(GL_TRIANGLES, 0, 36);\n        *\u002F\n        \n        \u002F\u002F 使用 ImGui 管理器渲染所有窗口\n        imguiManager.render();\n\n        \u002F\u002F 交换缓冲区和检查IO事件\n        glfwSwapBuffers(window);\n    }\n\n    \u002F\u002F 清理 ImGui 管理器\n    imguiManager.shutdown();\n\n    \u002F\u002F 清理资源\n    glDeleteVertexArrays(1, &VAO);\n    glDeleteVertexArrays(1, &lightVAO);\n    glDeleteBuffers(1, &VBO);\n    glDeleteTextures(1, &texture1);\n    glfwTerminate();\n    return 0;\n}\n\n#version 330 core\nout vec4 FragColor;\n\nstruct Material{\n    sampler2D diffuse;\n    vec3 specular;\n    float shininess;\n};\n\nstruct Light{\n    vec3 position;\n    vec3 direction;\n    float cutOff;\n    float outerCutOff;\n    vec3 ambient;\n    vec3 diffuse;\n    vec3 specular;\n\n    float constant;\n    float linear;\n    float quadratic;\n};\n\nuniform Light light;\nuniform vec3 viewPos;\nuniform Material material;\nin vec3 Normal;\nin vec3 FragPos;\nin vec2 TexCoords;\n\nvoid main()\n{\n    \u002F\u002F 从纹理采样颜色\n    vec3 textureColor = vec3(texture(material.diffuse, TexCoords));\n    \n    \u002F\u002F 计算距离衰减\n    float distance = length(light.position - FragPos);\n    float attenuation = 1.0 \u002F (light.constant + light.linear * distance + \n                                light.quadratic * (distance * distance));\n    \n    \u002F\u002F 环境光（先计算，因为无论是否在聚光灯范围内都需要）\n    vec3 ambient = light.ambient * textureColor;\n    \n    \u002F\u002F 计算聚光灯效果（手电筒）\n    vec3 lightDir = normalize(FragPos - light.position); \u002F\u002F 从光源指向片段的方向\n    vec3 spotDir = normalize(light.direction); \u002F\u002F 手电筒的朝向方向（相机朝向）\n    float theta = dot(lightDir, spotDir); \u002F\u002F 计算角度（点积），如果 theta > cutOff 说明在聚光灯范围内\n    \n    \u002F\u002F 计算聚光灯强度（使用 clamp 方法，无需 if-else）\n    float intensity = clamp((theta - light.outerCutOff) \u002F (light.cutOff - light.outerCutOff), 0.0, 1.0);\n    \n    \u002F\u002F 计算漫反射和镜面反射（无论强度如何都计算）\n    vec3 norm = normalize(Normal);\n    float diff = max(dot(norm, -lightDir), 0.0); \u002F\u002F -lightDir 是从片段指向光源\n    vec3 diffuse = light.diffuse * (diff * textureColor);\n    \n    \u002F\u002F 镜面反射\n    vec3 viewDir = normalize(viewPos - FragPos);\n    vec3 reflectDir = reflect(lightDir, norm); \u002F\u002F lightDir 是从光源指向片段（入射方向）\n    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);\n    vec3 specular = light.specular * (spec * material.specular);\n    \n    \u002F\u002F 应用衰减和聚光灯强度\n    diffuse *= attenuation * intensity;\n    specular *= attenuation * intensity;\n    \n    \u002F\u002F 最终结果\n    vec3 result = ambient + diffuse + specular;\n    FragColor = vec4(result, 1.0);\n}\n\n\n",[67,4518,4519,4523,4527,4531,4535,4539,4543,4547,4551,4555,4559,4563,4567,4571,4575,4579,4583,4587,4591,4595,4599,4603,4607,4611,4615,4619,4623,4627,4631,4635,4639,4643,4647,4651,4655,4659,4663,4667,4671,4675,4679,4683,4687,4691,4695,4699,4703,4707,4711,4715,4719,4723,4727,4731,4735,4739,4743,4747,4751,4755,4759,4763,4767,4771,4775,4779,4783,4787,4791,4795,4799,4803,4807,4811,4815,4819,4823,4827,4831,4835,4839,4843,4847,4851,4855,4859,4863,4867,4871,4875,4879,4883,4887,4891,4895,4899,4903,4907,4911,4915,4919,4923,4927,4931,4935,4939,4943,4947,4951,4955,4959,4963,4967,4971,4975,4979,4983,4987,4991,4995,4999,5003,5007,5011,5015,5019,5023,5027,5031,5035,5039,5043,5047,5051,5055,5059,5063,5067,5071,5075,5079,5083,5087,5091,5095,5099,5103,5107,5111,5115,5119,5123,5127,5131,5135,5139,5143,5147,5151,5155,5159,5163,5167,5171,5175,5179,5183,5187,5191,5195,5199,5203,5207,5211,5215,5219,5223,5227,5231,5235,5239,5243,5247,5251,5255,5259,5263,5267,5271,5275,5279,5283,5287,5291,5295,5299,5303,5307,5311,5315,5319,5323,5327,5331,5335,5339,5343,5347,5351,5355,5359,5363,5367,5371,5375,5379,5383,5387,5391,5395,5399,5403,5407,5411,5415,5419,5423,5427,5431,5435,5439,5443,5447,5451,5455,5459,5463,5467,5471,5475,5479,5483,5487,5491,5495,5499,5503,5507,5511,5515,5519,5523,5527,5531,5535,5539,5543,5547,5551,5555,5559,5563,5567,5571,5575,5579,5583,5587,5591,5595,5599,5603,5607,5611,5615,5619,5623,5627,5632,5636,5640,5644,5648,5653,5658,5663,5668,5673,5677,5681,5685,5690,5695,5699,5703,5707,5711,5715,5720,5724,5728,5732,5736,5740,5744,5748,5752,5756,5760,5764,5768,5772,5776,5780,5784,5788,5792,5796,5800,5804,5808,5812,5816,5820,5824,5828,5832,5836,5841,5845,5849,5853,5857,5861,5865,5869,5873,5877,5881,5885,5889,5893,5897,5901,5905,5909,5913,5917,5921,5925,5929,5933,5937,5941,5945,5949,5953,5957,5961,5965,5969,5973,5977,5981,5985,5989,5993,5997,6001,6005,6009,6013,6017,6021,6026,6030,6035,6039,6043,6047,6051,6055,6059,6063,6067,6071,6075,6079,6083,6087,6091,6095,6099,6103,6107,6111,6115,6119,6124,6129,6133,6138,6142,6147,6152,6156,6162,6168,6174,6180,6185,6191,6197,6202,6208,6213,6219,6225,6230,6235,6240,6246,6251,6257,6262,6268,6274,6280,6285,6290,6295,6300],{"__ignoreMap":65},[70,4520,4521],{"class":72,"line":73},[70,4522,76],{},[70,4524,4525],{"class":72,"line":79},[70,4526,82],{},[70,4528,4529],{"class":72,"line":85},[70,4530,88],{},[70,4532,4533],{"class":72,"line":91},[70,4534,94],{},[70,4536,4537],{"class":72,"line":97},[70,4538,100],{},[70,4540,4541],{"class":72,"line":103},[70,4542,106],{},[70,4544,4545],{"class":72,"line":109},[70,4546,112],{},[70,4548,4549],{"class":72,"line":115},[70,4550,118],{},[70,4552,4553],{"class":72,"line":121},[70,4554,124],{},[70,4556,4557],{"class":72,"line":127},[70,4558,130],{},[70,4560,4561],{"class":72,"line":133},[70,4562,136],{},[70,4564,4565],{"class":72,"line":139},[70,4566,142],{},[70,4568,4569],{"class":72,"line":145},[70,4570,148],{},[70,4572,4573],{"class":72,"line":151},[70,4574,155],{"emptyLinePlaceholder":154},[70,4576,4577],{"class":72,"line":158},[70,4578,161],{},[70,4580,4581],{"class":72,"line":164},[70,4582,167],{},[70,4584,4585],{"class":72,"line":170},[70,4586,155],{"emptyLinePlaceholder":154},[70,4588,4589],{"class":72,"line":175},[70,4590,178],{},[70,4592,4593],{"class":72,"line":181},[70,4594,184],{},[70,4596,4597],{"class":72,"line":187},[70,4598,190],{},[70,4600,4601],{"class":72,"line":193},[70,4602,196],{},[70,4604,4605],{"class":72,"line":199},[70,4606,202],{},[70,4608,4609],{"class":72,"line":205},[70,4610,208],{},[70,4612,4613],{"class":72,"line":211},[70,4614,214],{},[70,4616,4617],{"class":72,"line":217},[70,4618,220],{},[70,4620,4621],{"class":72,"line":223},[70,4622,226],{},[70,4624,4625],{"class":72,"line":229},[70,4626,232],{},[70,4628,4629],{"class":72,"line":235},[70,4630,238],{},[70,4632,4633],{"class":72,"line":241},[70,4634,202],{},[70,4636,4637],{"class":72,"line":246},[70,4638,249],{},[70,4640,4641],{"class":72,"line":252},[70,4642,255],{},[70,4644,4645],{"class":72,"line":258},[70,4646,261],{},[70,4648,4649],{"class":72,"line":264},[70,4650,202],{},[70,4652,4653],{"class":72,"line":269},[70,4654,272],{},[70,4656,4657],{"class":72,"line":275},[70,4658,278],{},[70,4660,4661],{"class":72,"line":281},[70,4662,202],{},[70,4664,4665],{"class":72,"line":286},[70,4666,289],{},[70,4668,4669],{"class":72,"line":292},[70,4670,295],{},[70,4672,4673],{"class":72,"line":298},[70,4674,301],{},[70,4676,4677],{"class":72,"line":304},[70,4678,202],{},[70,4680,4681],{"class":72,"line":309},[70,4682,312],{},[70,4684,4685],{"class":72,"line":315},[70,4686,318],{},[70,4688,4689],{"class":72,"line":321},[70,4690,324],{},[70,4692,4693],{"class":72,"line":327},[70,4694,330],{},[70,4696,4697],{"class":72,"line":333},[70,4698,336],{},[70,4700,4701],{"class":72,"line":339},[70,4702,202],{},[70,4704,4705],{"class":72,"line":344},[70,4706,347],{},[70,4708,4709],{"class":72,"line":350},[70,4710,353],{},[70,4712,4713],{"class":72,"line":356},[70,4714,155],{"emptyLinePlaceholder":154},[70,4716,4717],{"class":72,"line":361},[70,4718,364],{},[70,4720,4721],{"class":72,"line":367},[70,4722,370],{},[70,4724,4725],{"class":72,"line":373},[70,4726,376],{},[70,4728,4729],{"class":72,"line":379},[70,4730,353],{},[70,4732,4733],{"class":72,"line":384},[70,4734,155],{"emptyLinePlaceholder":154},[70,4736,4737],{"class":72,"line":389},[70,4738,155],{"emptyLinePlaceholder":154},[70,4740,4741],{"class":72,"line":394},[70,4742,397],{},[70,4744,4745],{"class":72,"line":400},[70,4746,403],{},[70,4748,4749],{"class":72,"line":406},[70,4750,409],{},[70,4752,4753],{"class":72,"line":412},[70,4754,155],{"emptyLinePlaceholder":154},[70,4756,4757],{"class":72,"line":417},[70,4758,420],{},[70,4760,4761],{"class":72,"line":423},[70,4762,426],{},[70,4764,4765],{"class":72,"line":429},[70,4766,155],{"emptyLinePlaceholder":154},[70,4768,4769],{"class":72,"line":434},[70,4770,437],{},[70,4772,4773],{"class":72,"line":440},[70,4774,443],{},[70,4776,4777],{"class":72,"line":446},[70,4778,155],{"emptyLinePlaceholder":154},[70,4780,4781],{"class":72,"line":451},[70,4782,454],{},[70,4784,4785],{"class":72,"line":457},[70,4786,460],{},[70,4788,4789],{"class":72,"line":463},[70,4790,466],{},[70,4792,4793],{"class":72,"line":469},[70,4794,472],{},[70,4796,4797],{"class":72,"line":475},[70,4798,155],{"emptyLinePlaceholder":154},[70,4800,4801],{"class":72,"line":480},[70,4802,483],{},[70,4804,4805],{"class":72,"line":486},[70,4806,489],{},[70,4808,4809],{"class":72,"line":492},[70,4810,238],{},[70,4812,4813],{"class":72,"line":497},[70,4814,500],{},[70,4816,4817],{"class":72,"line":503},[70,4818,506],{},[70,4820,4821],{"class":72,"line":509},[70,4822,238],{},[70,4824,4825],{"class":72,"line":514},[70,4826,517],{},[70,4828,4829],{"class":72,"line":520},[70,4830,523],{},[70,4832,4833],{"class":72,"line":526},[70,4834,238],{},[70,4836,4837],{"class":72,"line":531},[70,4838,202],{},[70,4840,4841],{"class":72,"line":536},[70,4842,539],{},[70,4844,4845],{"class":72,"line":542},[70,4846,545],{},[70,4848,4849],{"class":72,"line":548},[70,4850,551],{},[70,4852,4853],{"class":72,"line":554},[70,4854,238],{},[70,4856,4857],{"class":72,"line":559},[70,4858,353],{},[70,4860,4861],{"class":72,"line":564},[70,4862,155],{"emptyLinePlaceholder":154},[70,4864,4865],{"class":72,"line":569},[70,4866,572],{},[70,4868,4869],{"class":72,"line":575},[70,4870,578],{},[70,4872,4873],{"class":72,"line":581},[70,4874,584],{},[70,4876,4877],{"class":72,"line":587},[70,4878,155],{"emptyLinePlaceholder":154},[70,4880,4881],{"class":72,"line":592},[70,4882,595],{},[70,4884,4885],{"class":72,"line":598},[70,4886,601],{},[70,4888,4889],{"class":72,"line":604},[70,4890,607],{},[70,4892,4893],{"class":72,"line":610},[70,4894,613],{},[70,4896,4897],{"class":72,"line":616},[70,4898,619],{},[70,4900,4901],{"class":72,"line":622},[70,4902,202],{},[70,4904,4905],{"class":72,"line":627},[70,4906,630],{},[70,4908,4909],{"class":72,"line":633},[70,4910,636],{},[70,4912,4913],{"class":72,"line":639},[70,4914,155],{"emptyLinePlaceholder":154},[70,4916,4917],{"class":72,"line":644},[70,4918,647],{},[70,4920,4921],{"class":72,"line":650},[70,4922,653],{},[70,4924,4925],{"class":72,"line":656},[70,4926,659],{},[70,4928,4929],{"class":72,"line":662},[70,4930,665],{},[70,4932,4933],{"class":72,"line":668},[70,4934,671],{},[70,4936,4937],{"class":72,"line":674},[70,4938,677],{},[70,4940,4941],{"class":72,"line":680},[70,4942,238],{},[70,4944,4945],{"class":72,"line":685},[70,4946,155],{"emptyLinePlaceholder":154},[70,4948,4949],{"class":72,"line":690},[70,4950,693],{},[70,4952,4953],{"class":72,"line":696},[70,4954,699],{},[70,4956,4957],{"class":72,"line":702},[70,4958,202],{},[70,4960,4961],{"class":72,"line":707},[70,4962,710],{},[70,4964,4965],{"class":72,"line":713},[70,4966,716],{},[70,4968,4969],{"class":72,"line":719},[70,4970,155],{"emptyLinePlaceholder":154},[70,4972,4973],{"class":72,"line":724},[70,4974,727],{},[70,4976,4977],{"class":72,"line":730},[70,4978,733],{},[70,4980,4981],{"class":72,"line":736},[70,4982,739],{},[70,4984,4985],{"class":72,"line":742},[70,4986,677],{},[70,4988,4989],{"class":72,"line":747},[70,4990,238],{},[70,4992,4993],{"class":72,"line":752},[70,4994,155],{"emptyLinePlaceholder":154},[70,4996,4997],{"class":72,"line":757},[70,4998,760],{},[70,5000,5001],{"class":72,"line":763},[70,5002,766],{},[70,5004,5005],{"class":72,"line":769},[70,5006,202],{},[70,5008,5009],{"class":72,"line":774},[70,5010,777],{},[70,5012,5013],{"class":72,"line":780},[70,5014,783],{},[70,5016,5017],{"class":72,"line":786},[70,5018,789],{},[70,5020,5021],{"class":72,"line":792},[70,5022,795],{},[70,5024,5025],{"class":72,"line":798},[70,5026,801],{},[70,5028,5029],{"class":72,"line":804},[70,5030,807],{},[70,5032,5033],{"class":72,"line":810},[70,5034,813],{},[70,5036,5037],{"class":72,"line":816},[70,5038,819],{},[70,5040,5041],{"class":72,"line":822},[70,5042,801],{},[70,5044,5045],{"class":72,"line":827},[70,5046,830],{},[70,5048,5049],{"class":72,"line":833},[70,5050,836],{},[70,5052,5053],{"class":72,"line":839},[70,5054,842],{},[70,5056,5057],{"class":72,"line":845},[70,5058,819],{},[70,5060,5061],{"class":72,"line":850},[70,5062,853],{},[70,5064,5065],{"class":72,"line":856},[70,5066,202],{},[70,5068,5069],{"class":72,"line":861},[70,5070,864],{},[70,5072,5073],{"class":72,"line":867},[70,5074,870],{},[70,5076,5077],{"class":72,"line":873},[70,5078,876],{},[70,5080,5081],{"class":72,"line":879},[70,5082,882],{},[70,5084,5085],{"class":72,"line":885},[70,5086,853],{},[70,5088,5089],{"class":72,"line":890},[70,5090,202],{},[70,5092,5093],{"class":72,"line":895},[70,5094,155],{"emptyLinePlaceholder":154},[70,5096,5097],{"class":72,"line":900},[70,5098,903],{},[70,5100,5101],{"class":72,"line":906},[70,5102,909],{},[70,5104,5105],{"class":72,"line":912},[70,5106,155],{"emptyLinePlaceholder":154},[70,5108,5109],{"class":72,"line":917},[70,5110,920],{},[70,5112,5113],{"class":72,"line":923},[70,5114,926],{},[70,5116,5117],{"class":72,"line":929},[70,5118,932],{},[70,5120,5121],{"class":72,"line":935},[70,5122,938],{},[70,5124,5125],{"class":72,"line":941},[70,5126,944],{},[70,5128,5129],{"class":72,"line":947},[70,5130,950],{},[70,5132,5133],{"class":72,"line":953},[70,5134,956],{},[70,5136,5137],{"class":72,"line":959},[70,5138,956],{},[70,5140,5141],{"class":72,"line":964},[70,5142,967],{},[70,5144,5145],{"class":72,"line":970},[70,5146,944],{},[70,5148,5149],{"class":72,"line":975},[70,5150,202],{},[70,5152,5153],{"class":72,"line":980},[70,5154,983],{},[70,5156,5157],{"class":72,"line":986},[70,5158,989],{},[70,5160,5161],{"class":72,"line":992},[70,5162,995],{},[70,5164,5165],{"class":72,"line":998},[70,5166,1001],{},[70,5168,5169],{"class":72,"line":1004},[70,5170,1001],{},[70,5172,5173],{"class":72,"line":1009},[70,5174,1012],{},[70,5176,5177],{"class":72,"line":1015},[70,5178,989],{},[70,5180,5181],{"class":72,"line":1020},[70,5182,202],{},[70,5184,5185],{"class":72,"line":1025},[70,5186,1028],{},[70,5188,5189],{"class":72,"line":1031},[70,5190,1034],{},[70,5192,5193],{"class":72,"line":1037},[70,5194,1040],{},[70,5196,5197],{"class":72,"line":1043},[70,5198,1046],{},[70,5200,5201],{"class":72,"line":1049},[70,5202,1046],{},[70,5204,5205],{"class":72,"line":1054},[70,5206,1057],{},[70,5208,5209],{"class":72,"line":1060},[70,5210,1034],{},[70,5212,5213],{"class":72,"line":1065},[70,5214,202],{},[70,5216,5217],{"class":72,"line":1070},[70,5218,1073],{},[70,5220,5221],{"class":72,"line":1076},[70,5222,1079],{},[70,5224,5225],{"class":72,"line":1082},[70,5226,1085],{},[70,5228,5229],{"class":72,"line":1088},[70,5230,1091],{},[70,5232,5233],{"class":72,"line":1094},[70,5234,1091],{},[70,5236,5237],{"class":72,"line":1099},[70,5238,1102],{},[70,5240,5241],{"class":72,"line":1105},[70,5242,1079],{},[70,5244,5245],{"class":72,"line":1110},[70,5246,202],{},[70,5248,5249],{"class":72,"line":1115},[70,5250,1118],{},[70,5252,5253],{"class":72,"line":1121},[70,5254,1124],{},[70,5256,5257],{"class":72,"line":1127},[70,5258,1130],{},[70,5260,5261],{"class":72,"line":1133},[70,5262,1136],{},[70,5264,5265],{"class":72,"line":1139},[70,5266,1136],{},[70,5268,5269],{"class":72,"line":1144},[70,5270,1147],{},[70,5272,5273],{"class":72,"line":1150},[70,5274,1124],{},[70,5276,5277],{"class":72,"line":1155},[70,5278,202],{},[70,5280,5281],{"class":72,"line":1160},[70,5282,1163],{},[70,5284,5285],{"class":72,"line":1166},[70,5286,1169],{},[70,5288,5289],{"class":72,"line":1172},[70,5290,1175],{},[70,5292,5293],{"class":72,"line":1178},[70,5294,1181],{},[70,5296,5297],{"class":72,"line":1184},[70,5298,1181],{},[70,5300,5301],{"class":72,"line":1189},[70,5302,1192],{},[70,5304,5305],{"class":72,"line":1195},[70,5306,1198],{},[70,5308,5309],{"class":72,"line":1201},[70,5310,1204],{},[70,5312,5313],{"class":72,"line":1207},[70,5314,202],{},[70,5316,5317],{"class":72,"line":1212},[70,5318,1215],{},[70,5320,5321],{"class":72,"line":1218},[70,5322,1221],{},[70,5324,5325],{"class":72,"line":1224},[70,5326,1227],{},[70,5328,5329],{"class":72,"line":1230},[70,5330,1233],{},[70,5332,5333],{"class":72,"line":1236},[70,5334,155],{"emptyLinePlaceholder":154},[70,5336,5337],{"class":72,"line":1241},[70,5338,1244],{},[70,5340,5341],{"class":72,"line":1247},[70,5342,1250],{},[70,5344,5345],{"class":72,"line":1253},[70,5346,1256],{},[70,5348,5349],{"class":72,"line":1259},[70,5350,1262],{},[70,5352,5353],{"class":72,"line":1265},[70,5354,1268],{},[70,5356,5357],{"class":72,"line":1271},[70,5358,155],{"emptyLinePlaceholder":154},[70,5360,5361],{"class":72,"line":1276},[70,5362,1279],{},[70,5364,5365],{"class":72,"line":1282},[70,5366,1285],{},[70,5368,5369],{"class":72,"line":1288},[70,5370,1291],{},[70,5372,5373],{"class":72,"line":1294},[70,5374,155],{"emptyLinePlaceholder":154},[70,5376,5377],{"class":72,"line":1299},[70,5378,1302],{},[70,5380,5381],{"class":72,"line":1305},[70,5382,1308],{},[70,5384,5385],{"class":72,"line":1311},[70,5386,1314],{},[70,5388,5389],{"class":72,"line":1317},[70,5390,202],{},[70,5392,5393],{"class":72,"line":1322},[70,5394,1325],{},[70,5396,5397],{"class":72,"line":1328},[70,5398,1331],{},[70,5400,5401],{"class":72,"line":1334},[70,5402,1337],{},[70,5404,5405],{"class":72,"line":1340},[70,5406,202],{},[70,5408,5409],{"class":72,"line":1345},[70,5410,1348],{},[70,5412,5413],{"class":72,"line":1351},[70,5414,1354],{},[70,5416,5417],{"class":72,"line":1357},[70,5418,1360],{},[70,5420,5421],{"class":72,"line":1363},[70,5422,155],{"emptyLinePlaceholder":154},[70,5424,5425],{"class":72,"line":1368},[70,5426,1371],{},[70,5428,5429],{"class":72,"line":1374},[70,5430,1377],{},[70,5432,5433],{"class":72,"line":1380},[70,5434,1383],{},[70,5436,5437],{"class":72,"line":1386},[70,5438,155],{"emptyLinePlaceholder":154},[70,5440,5441],{"class":72,"line":1391},[70,5442,1394],{},[70,5444,5445],{"class":72,"line":1397},[70,5446,1400],{},[70,5448,5449],{"class":72,"line":1403},[70,5450,1406],{},[70,5452,5453],{"class":72,"line":1409},[70,5454,1412],{},[70,5456,5457],{"class":72,"line":1415},[70,5458,238],{},[70,5460,5461],{"class":72,"line":1420},[70,5462,155],{"emptyLinePlaceholder":154},[70,5464,5465],{"class":72,"line":1425},[70,5466,1428],{},[70,5468,5469],{"class":72,"line":1431},[70,5470,1434],{},[70,5472,5473],{"class":72,"line":1437},[70,5474,1440],{},[70,5476,5477],{"class":72,"line":1443},[70,5478,1446],{},[70,5480,5481],{"class":72,"line":1449},[70,5482,1452],{},[70,5484,5485],{"class":72,"line":1455},[70,5486,1308],{},[70,5488,5489],{"class":72,"line":1460},[70,5490,1463],{},[70,5492,5493],{"class":72,"line":1466},[70,5494,155],{"emptyLinePlaceholder":154},[70,5496,5497],{"class":72,"line":1471},[70,5498,1474],{},[70,5500,5501],{"class":72,"line":1477},[70,5502,1480],{},[70,5504,5505],{"class":72,"line":1483},[70,5506,202],{},[70,5508,5509],{"class":72,"line":1488},[70,5510,1491],{},[70,5512,5513],{"class":72,"line":1494},[70,5514,1497],{},[70,5516,5517],{"class":72,"line":1500},[70,5518,155],{"emptyLinePlaceholder":154},[70,5520,5521],{"class":72,"line":1505},[70,5522,1508],{},[70,5524,5525],{"class":72,"line":1511},[70,5526,1514],{},[70,5528,5529],{"class":72,"line":1517},[70,5530,1520],{},[70,5532,5533],{"class":72,"line":1523},[70,5534,1526],{},[70,5536,5537],{"class":72,"line":1529},[70,5538,1532],{},[70,5540,5541],{"class":72,"line":1535},[70,5542,155],{"emptyLinePlaceholder":154},[70,5544,5545],{"class":72,"line":1540},[70,5546,1543],{},[70,5548,5549],{"class":72,"line":1546},[70,5550,1549],{},[70,5552,5553],{"class":72,"line":1552},[70,5554,801],{},[70,5556,5557],{"class":72,"line":1557},[70,5558,1560],{},[70,5560,5561],{"class":72,"line":1563},[70,5562,1566],{},[70,5564,5565],{"class":72,"line":1569},[70,5566,1572],{},[70,5568,5569],{"class":72,"line":1575},[70,5570,155],{"emptyLinePlaceholder":154},[70,5572,5573],{"class":72,"line":1580},[70,5574,1583],{},[70,5576,5577],{"class":72,"line":1586},[70,5578,1589],{},[70,5580,5581],{"class":72,"line":1592},[70,5582,1595],{},[70,5584,5585],{"class":72,"line":1598},[70,5586,801],{},[70,5588,5589],{"class":72,"line":1603},[70,5590,1606],{},[70,5592,5593],{"class":72,"line":1609},[70,5594,1612],{},[70,5596,5597],{"class":72,"line":1615},[70,5598,801],{},[70,5600,5601],{"class":72,"line":1620},[70,5602,1623],{},[70,5604,5605],{"class":72,"line":1626},[70,5606,1629],{},[70,5608,5609],{"class":72,"line":1632},[70,5610,1635],{},[70,5612,5613],{"class":72,"line":1638},[70,5614,1641],{},[70,5616,5617],{"class":72,"line":1644},[70,5618,801],{},[70,5620,5621],{"class":72,"line":1649},[70,5622,1711],{},[70,5624,5625],{"class":72,"line":1655},[70,5626,1717],{},[70,5628,5629],{"class":72,"line":1661},[70,5630,5631],{},"        vec3 cameraFront = dpiStabilizer.getCameraFront();\n",[70,5633,5634],{"class":72,"line":1667},[70,5635,801],{},[70,5637,5638],{"class":72,"line":1673},[70,5639,1652],{},[70,5641,5642],{"class":72,"line":1679},[70,5643,1658],{},[70,5645,5646],{"class":72,"line":1685},[70,5647,801],{},[70,5649,5650],{"class":72,"line":1691},[70,5651,5652],{},"        \u002F\u002F 手电筒效果：使用相机位置和朝向\n",[70,5654,5655],{"class":72,"line":1697},[70,5656,5657],{},"        lightingShader.setVec3(\"light.position\", currentViewPos.x, currentViewPos.y, currentViewPos.z);\n",[70,5659,5660],{"class":72,"line":1703},[70,5661,5662],{},"        lightingShader.setVec3(\"light.direction\", cameraFront.x, cameraFront.y, cameraFront.z);\n",[70,5664,5665],{"class":72,"line":1708},[70,5666,5667],{},"        lightingShader.setFloat(\"light.cutOff\", glm::cos(glm::radians(12.5f))); \u002F\u002F 内切角\n",[70,5669,5670],{"class":72,"line":1714},[70,5671,5672],{},"        lightingShader.setFloat(\"light.outerCutOff\", glm::cos(glm::radians(17.5f))); \u002F\u002F 外切角\n",[70,5674,5675],{"class":72,"line":1720},[70,5676,3463],{},[70,5678,5679],{"class":72,"line":1726},[70,5680,3468],{},[70,5682,5683],{"class":72,"line":1731},[70,5684,3473],{},[70,5686,5687],{"class":72,"line":1737},[70,5688,5689],{},"        lightingShader.setVec3(\"light.ambient\",  0.1f, 0.1f, 0.1f);   \u002F\u002F 降低环境光强度，只有聚光灯照到的地方才亮\n",[70,5691,5692],{"class":72,"line":1742},[70,5693,5694],{},"        lightingShader.setVec3(\"light.diffuse\",  1.0f, 1.0f, 1.0f);   \u002F\u002F 漫反射强度\n",[70,5696,5697],{"class":72,"line":1748},[70,5698,1682],{},[70,5700,5701],{"class":72,"line":1754},[70,5702,1688],{},[70,5704,5705],{"class":72,"line":1760},[70,5706,1694],{},[70,5708,5709],{"class":72,"line":1766},[70,5710,1700],{},[70,5712,5713],{"class":72,"line":1772},[70,5714,801],{},[70,5716,5717],{"class":72,"line":1778},[70,5718,5719],{},"        \u002F\u002F 设置相机位置（用于镜面反射计算）\n",[70,5721,5722],{"class":72,"line":1784},[70,5723,1723],{},[70,5725,5726],{"class":72,"line":1790},[70,5727,801],{},[70,5729,5730],{"class":72,"line":1796},[70,5731,1734],{},[70,5733,5734],{"class":72,"line":1802},[70,5735,801],{},[70,5737,5738],{"class":72,"line":1808},[70,5739,1745],{},[70,5741,5742],{"class":72,"line":1814},[70,5743,1751],{},[70,5745,5746],{"class":72,"line":1820},[70,5747,1757],{},[70,5749,5750],{"class":72,"line":1825},[70,5751,3542],{},[70,5753,5754],{"class":72,"line":1831},[70,5755,3547],{},[70,5757,5758],{"class":72,"line":1837},[70,5759,3552],{},[70,5761,5762],{"class":72,"line":1843},[70,5763,3557],{},[70,5765,5766],{"class":72,"line":1849},[70,5767,3562],{},[70,5769,5770],{"class":72,"line":1855},[70,5771,3567],{},[70,5773,5774],{"class":72,"line":1861},[70,5775,1799],{},[70,5777,5778],{"class":72,"line":1867},[70,5779,1805],{},[70,5781,5782],{"class":72,"line":1872},[70,5783,1811],{},[70,5785,5786],{"class":72,"line":1878},[70,5787,1817],{},[70,5789,5790],{"class":72,"line":1883},[70,5791,801],{},[70,5793,5794],{"class":72,"line":1888},[70,5795,1828],{},[70,5797,5798],{"class":72,"line":1894},[70,5799,1834],{},[70,5801,5802],{"class":72,"line":1900},[70,5803,1840],{},[70,5805,5806],{"class":72,"line":1906},[70,5807,1846],{},[70,5809,5810],{"class":72,"line":1912},[70,5811,1852],{},[70,5813,5814],{"class":72,"line":1918},[70,5815,1858],{},[70,5817,5818],{"class":72,"line":1923},[70,5819,1864],{},[70,5821,5822],{"class":72,"line":1929},[70,5823,155],{"emptyLinePlaceholder":154},[70,5825,5826],{"class":72,"line":1935},[70,5827,1875],{},[70,5829,5830],{"class":72,"line":1941},[70,5831,819],{},[70,5833,5834],{"class":72,"line":1947},[70,5835,155],{"emptyLinePlaceholder":154},[70,5837,5838],{"class":72,"line":1953},[70,5839,5840],{},"        \u002F\u002F 点光源立方体绘制（手电筒不需要显示光源立方体，已注释）\n",[70,5842,5843],{"class":72,"line":1958},[70,5844,1897],{},[70,5846,5847],{"class":72,"line":1964},[70,5848,1903],{},[70,5850,5851],{"class":72,"line":1970},[70,5852,1909],{},[70,5854,5855],{"class":72,"line":1976},[70,5856,1915],{},[70,5858,5859],{"class":72,"line":1981},[70,5860,801],{},[70,5862,5863],{"class":72,"line":1987},[70,5864,1926],{},[70,5866,5867],{"class":72,"line":1993},[70,5868,1932],{},[70,5870,5871],{"class":72,"line":1998},[70,5872,1938],{},[70,5874,5875],{"class":72,"line":2004},[70,5876,1944],{},[70,5878,5879],{"class":72,"line":2010},[70,5880,1950],{},[70,5882,5883],{"class":72,"line":2015},[70,5884,801],{},[70,5886,5887],{"class":72,"line":2020},[70,5888,1961],{},[70,5890,5891],{"class":72,"line":2026},[70,5892,1967],{},[70,5894,5895],{"class":72,"line":2032},[70,5896,1973],{},[70,5898,5899],{"class":72,"line":2037},[70,5900,801],{},[70,5902,5903],{"class":72,"line":2043},[70,5904,1984],{},[70,5906,5907],{"class":72,"line":2049},[70,5908,1990],{},[70,5910,5911],{"class":72,"line":2055},[70,5912,155],{"emptyLinePlaceholder":154},[70,5914,5915],{"class":72,"line":2061},[70,5916,2001],{},[70,5918,5919],{"class":72,"line":2067},[70,5920,2007],{},[70,5922,5923],{"class":72,"line":2073},[70,5924,238],{},[70,5926,5927],{"class":72,"line":2079},[70,5928,155],{"emptyLinePlaceholder":154},[70,5930,5931],{"class":72,"line":3758},[70,5932,2023],{},[70,5934,5935],{"class":72,"line":3763},[70,5936,2029],{},[70,5938,5939],{"class":72,"line":3768},[70,5940,155],{"emptyLinePlaceholder":154},[70,5942,5943],{"class":72,"line":3774},[70,5944,2040],{},[70,5946,5947],{"class":72,"line":3780},[70,5948,2046],{},[70,5950,5951],{"class":72,"line":3785},[70,5952,2052],{},[70,5954,5955],{"class":72,"line":3791},[70,5956,2058],{},[70,5958,5959],{"class":72,"line":3797},[70,5960,2064],{},[70,5962,5963],{"class":72,"line":3803},[70,5964,2070],{},[70,5966,5967],{"class":72,"line":3809},[70,5968,2076],{},[70,5970,5971],{"class":72,"line":3814},[70,5972,353],{},[70,5974,5975],{"class":72,"line":3819},[70,5976,155],{"emptyLinePlaceholder":154},[70,5978,5979],{"class":72,"line":3825},[70,5980,3771],{},[70,5982,5983],{"class":72,"line":3831},[70,5984,3777],{},[70,5986,5987],{"class":72,"line":3837},[70,5988,155],{"emptyLinePlaceholder":154},[70,5990,5991],{"class":72,"line":3843},[70,5992,3788],{},[70,5994,5995],{"class":72,"line":3848},[70,5996,3794],{},[70,5998,5999],{"class":72,"line":3853},[70,6000,3800],{},[70,6002,6003],{"class":72,"line":3859},[70,6004,3806],{},[70,6006,6007],{"class":72,"line":3865},[70,6008,1204],{},[70,6010,6011],{"class":72,"line":3871},[70,6012,155],{"emptyLinePlaceholder":154},[70,6014,6015],{"class":72,"line":3876},[70,6016,3822],{},[70,6018,6019],{"class":72,"line":3881},[70,6020,3828],{},[70,6022,6023],{"class":72,"line":3887},[70,6024,6025],{},"    vec3 direction;\n",[70,6027,6028],{"class":72,"line":3893},[70,6029,4168],{},[70,6031,6032],{"class":72,"line":3899},[70,6033,6034],{},"    float outerCutOff;\n",[70,6036,6037],{"class":72,"line":3905},[70,6038,3834],{},[70,6040,6041],{"class":72,"line":3911},[70,6042,3840],{},[70,6044,6045],{"class":72,"line":3917},[70,6046,3800],{},[70,6048,6049],{"class":72,"line":3922},[70,6050,155],{"emptyLinePlaceholder":154},[70,6052,6053],{"class":72,"line":3928},[70,6054,3856],{},[70,6056,6057],{"class":72,"line":3934},[70,6058,3862],{},[70,6060,6061],{"class":72,"line":3940},[70,6062,3868],{},[70,6064,6065],{"class":72,"line":3946},[70,6066,1204],{},[70,6068,6069],{"class":72,"line":3952},[70,6070,155],{"emptyLinePlaceholder":154},[70,6072,6073],{"class":72,"line":3958},[70,6074,3884],{},[70,6076,6077],{"class":72,"line":3964},[70,6078,3890],{},[70,6080,6081],{"class":72,"line":3970},[70,6082,3896],{},[70,6084,6085],{"class":72,"line":3976},[70,6086,3902],{},[70,6088,6089],{"class":72,"line":3981},[70,6090,3908],{},[70,6092,6093],{"class":72,"line":3987},[70,6094,3914],{},[70,6096,6097],{"class":72,"line":3993},[70,6098,155],{"emptyLinePlaceholder":154},[70,6100,6101],{"class":72,"line":3999},[70,6102,3925],{},[70,6104,6105],{"class":72,"line":4005},[70,6106,3931],{},[70,6108,6109],{"class":72,"line":4011},[70,6110,3937],{},[70,6112,6113],{"class":72,"line":4016},[70,6114,3943],{},[70,6116,6117],{"class":72,"line":4022},[70,6118,202],{},[70,6120,6121],{"class":72,"line":4028},[70,6122,6123],{},"    \u002F\u002F 计算距离衰减\n",[70,6125,6126],{"class":72,"line":4034},[70,6127,6128],{},"    float distance = length(light.position - FragPos);\n",[70,6130,6131],{"class":72,"line":4040},[70,6132,3955],{},[70,6134,6135],{"class":72,"line":4046},[70,6136,6137],{},"                                light.quadratic * (distance * distance));\n",[70,6139,6140],{"class":72,"line":4051},[70,6141,202],{},[70,6143,6144],{"class":72,"line":4057},[70,6145,6146],{},"    \u002F\u002F 环境光（先计算，因为无论是否在聚光灯范围内都需要）\n",[70,6148,6149],{"class":72,"line":4063},[70,6150,6151],{},"    vec3 ambient = light.ambient * textureColor;\n",[70,6153,6154],{"class":72,"line":4069},[70,6155,202],{},[70,6157,6159],{"class":72,"line":6158},406,[70,6160,6161],{},"    \u002F\u002F 计算聚光灯效果（手电筒）\n",[70,6163,6165],{"class":72,"line":6164},407,[70,6166,6167],{},"    vec3 lightDir = normalize(FragPos - light.position); \u002F\u002F 从光源指向片段的方向\n",[70,6169,6171],{"class":72,"line":6170},408,[70,6172,6173],{},"    vec3 spotDir = normalize(light.direction); \u002F\u002F 手电筒的朝向方向（相机朝向）\n",[70,6175,6177],{"class":72,"line":6176},409,[70,6178,6179],{},"    float theta = dot(lightDir, spotDir); \u002F\u002F 计算角度（点积），如果 theta > cutOff 说明在聚光灯范围内\n",[70,6181,6183],{"class":72,"line":6182},410,[70,6184,202],{},[70,6186,6188],{"class":72,"line":6187},411,[70,6189,6190],{},"    \u002F\u002F 计算聚光灯强度（使用 clamp 方法，无需 if-else）\n",[70,6192,6194],{"class":72,"line":6193},412,[70,6195,6196],{},"    float intensity = clamp((theta - light.outerCutOff) \u002F (light.cutOff - light.outerCutOff), 0.0, 1.0);\n",[70,6198,6200],{"class":72,"line":6199},413,[70,6201,202],{},[70,6203,6205],{"class":72,"line":6204},414,[70,6206,6207],{},"    \u002F\u002F 计算漫反射和镜面反射（无论强度如何都计算）\n",[70,6209,6211],{"class":72,"line":6210},415,[70,6212,3990],{},[70,6214,6216],{"class":72,"line":6215},416,[70,6217,6218],{},"    float diff = max(dot(norm, -lightDir), 0.0); \u002F\u002F -lightDir 是从片段指向光源\n",[70,6220,6222],{"class":72,"line":6221},417,[70,6223,6224],{},"    vec3 diffuse = light.diffuse * (diff * textureColor);\n",[70,6226,6228],{"class":72,"line":6227},418,[70,6229,202],{},[70,6231,6233],{"class":72,"line":6232},419,[70,6234,4019],{},[70,6236,6238],{"class":72,"line":6237},420,[70,6239,4025],{},[70,6241,6243],{"class":72,"line":6242},421,[70,6244,6245],{},"    vec3 reflectDir = reflect(lightDir, norm); \u002F\u002F lightDir 是从光源指向片段（入射方向）\n",[70,6247,6249],{"class":72,"line":6248},422,[70,6250,4037],{},[70,6252,6254],{"class":72,"line":6253},423,[70,6255,6256],{},"    vec3 specular = light.specular * (spec * material.specular);\n",[70,6258,6260],{"class":72,"line":6259},424,[70,6261,202],{},[70,6263,6265],{"class":72,"line":6264},425,[70,6266,6267],{},"    \u002F\u002F 应用衰减和聚光灯强度\n",[70,6269,6271],{"class":72,"line":6270},426,[70,6272,6273],{},"    diffuse *= attenuation * intensity;\n",[70,6275,6277],{"class":72,"line":6276},427,[70,6278,6279],{},"    specular *= attenuation * intensity;\n",[70,6281,6283],{"class":72,"line":6282},428,[70,6284,202],{},[70,6286,6288],{"class":72,"line":6287},429,[70,6289,4054],{},[70,6291,6293],{"class":72,"line":6292},430,[70,6294,4060],{},[70,6296,6298],{"class":72,"line":6297},431,[70,6299,4066],{},[70,6301,6303],{"class":72,"line":6302},432,[70,6304,353],{},[6306,6307,6308],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":65,"searchDepth":79,"depth":79,"links":6310},[6311,6312,6313],{"id":2136,"depth":85,"text":2136},{"id":4129,"depth":79,"text":4129},{"id":4252,"depth":79,"text":4253},"当一个光源处于很远的地方时，来自光源的每条光线就会近似于互相平行。不论物体和\u002F或者观察者的位置，看起来好像所有的光都来自于同一个方向。当我们使用一个假设光源处于无限远处的模型时，它就被称为定向光，因为它的所有光线都有着相同的方向，它与光源的位置是没有关系的。","md",{},"\u002Fcpp\u002Fopengel\u002F光源和投光物",{"title":5,"description":6314},"cpp\u002Fopengel\u002F光源和投光物","GmFfLId47_HhHV6-3nFoxS3jb2x3PyuckqutDYpp_j4",1791042463005]