[{"data":1,"prerenderedAt":2321},["ShallowReactive",2],{"page-\u002Fcpp\u002Fopengel\u002F光照贴图":3},{"id":4,"title":5,"body":6,"description":2314,"extension":2315,"meta":2316,"navigation":198,"path":2317,"seo":2318,"stem":2319,"__hash__":2320},"content\u002Fcpp\u002Fopengel\u002F光照贴图.md","光照贴图",{"type":7,"value":8,"toc":2311},"minimark",[9,30,34,37,51,60,66,81,95,114,121,136,142,151,161,174,177,229,232,2121,2124,2135,2143,2148,2158,2161,2164,2175,2179,2186,2211,2220,2249,2252,2277,2284,2287,2290,2295,2304,2307],[10,11,12,13,17,18,21,22,25,26,29],"p",{},"上一节中的那个材质系统是肯定不够的，它只是一个最简单的模型，所以我们需要拓展之前的系统，引入",[14,15,16],"strong",{},"漫反射","和",[14,19,20],{},"镜面光","贴图(Map)。",[14,23,24],{},"这允许我们对物体的漫反射分量","（以及间接地对环境光分量，它们几乎总是一样的）和",[14,27,28],{},"镜面光分量有着更精确的控制","。",[31,32,33],"h1",{"id":33},"漫反射贴图",[10,35,36],{},"我们希望通过某种方式对物体的每个片段单独设置漫反射颜色。有能够让我们根据片段在物体上的位置来获取颜色值的系统吗？",[10,38,39,40,46,47,50],{},"这可能听起来很熟悉，而且事实上这个系统我们已经使用很长时间了。这听起来很像在[之前](",[41,42,43],"a",{"href":43,"rel":44},"https:\u002F\u002Flearnopengl-cn.github.io\u002F01",[45],"nofollow"," Getting started\u002F06 Textures\u002F)教程中详细讨论过的",[14,48,49],{},"纹理","，而这基本就是这样：一个纹理。我们仅仅是对同样的原理使用了不同的名字：其实都是使用一张覆盖物体的图像，让我们能够逐片段索引其独立的颜色值。在光照场景中，它通常叫做一个漫反射贴图(Diffuse Map)（在PBR之前3D艺术家通常都这么叫它），它是一个表现了物体所有的漫反射颜色的纹理图像。",[10,52,53,54,59],{},"为了演示漫反射贴图，我们将会使用",[41,55,58],{"href":56,"rel":57},"https:\u002F\u002Flearnopengl-cn.github.io\u002Fimg\u002F02\u002F04\u002Fcontainer2.png",[45],"下面的图片","，它是一个有钢边框的木箱：",[10,61,62],{},[63,64],"img",{"alt":63,"src":65},".\u002Fassets\u002Fcontainer2.png",[10,67,68,71,72,76,77,80],{},[14,69,70],{},"在着色器中使用漫反射贴图的方法和纹理教程中是完全一样的","。但这次我们会将纹理储存为Material结构体中的一个",[73,74,75],"code",{},"sampler2D","。我们将之前定义的",[73,78,79],{},"vec3","漫反射颜色向量替换为漫反射贴图。",[10,82,83,84,86,87,90,91,94],{},"注意",[73,85,75],{},"是所谓的不透明类型(Opaque Type)，",[14,88,89],{},"也就是说我们不能将它实例化","，只能",[14,92,93],{},"通过uniform来定义它","。如果我们使用除uniform以外的方法（比如函数的参数）实例化这个结构体，GLSL会抛出一些奇怪的错误。这同样也适用于任何封装了不透明类型的结构体。",[96,97,98,104],"blockquote",{},[10,99,100],{},[101,102,103],"span",{},"!NOTE",[10,105,106,107,109,110,113],{},"如果你非常固执，仍想将环境光颜色设置为一个（漫反射值之外）不同的值，你也可以保留这个环境光的",[73,108,79],{},"，但",[14,111,112],{},"整个物体仍只能拥有一个环境光颜色","。如果想要对不同片段有不同的环境光值，你需要对环境光值单独使用另外一个纹理。",[10,115,116,117,120],{},"注意我们将在片段着色器中再次需要纹理坐标，所以我们声明一个额外的输入变量。接下来我们",[14,118,119],{},"只需要从纹理中采样片段的漫反射颜色值即可","：",[122,123,128],"pre",{"className":124,"code":125,"language":126,"meta":127,"style":127},"language-glsl shiki shiki-themes github-light github-dark","vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));\n","glsl","",[73,129,130],{"__ignoreMap":127},[101,131,134],{"class":132,"line":133},"line",1,[101,135,125],{},[10,137,138,139,29],{},"不要忘记将环境光的材质颜色",[14,140,141],{},"设置为漫反射材质颜色同样的值",[122,143,145],{"className":124,"code":144,"language":126,"meta":127,"style":127},"vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));\n",[73,146,147],{"__ignoreMap":127},[101,148,149],{"class":132,"line":133},[101,150,144],{},[10,152,153,154,157,158],{},"这就是使用漫反射贴图的全部步骤了。你可以看到，这并不是什么新的东西，但这能够极大地提高视觉品质。",[14,155,156],{},"为了让它正常工作，我们还需要使用纹理坐标更新顶点数据","，将它们作为顶点属性传递到片段着色器，",[14,159,160],{},"加载材质并绑定材质到合适的纹理单元。",[162,163,164,168,171],"ul",{},[165,166,167],"li",{},"纹理模式：颜色来自纹理，光照强度来自光源",[165,169,170],{},"纯颜色模式：颜色来自材质属性，光照强度来自光源",[165,172,173],{},"混合模式：颜色 = 纹理颜色 × 材质颜色，光照强度来自光源",[10,175,176],{},"当前代码使用的是：纹理颜色（环境光\u002F漫反射）+ 材质颜色（镜面反射）",[122,178,180],{"className":124,"code":179,"language":126,"meta":127,"style":127},"\u002F\u002F 环境光：使用纹理颜色\nvec3 ambient = light.ambient * textureColor;\n\n\u002F\u002F 漫反射：使用纹理颜色\nvec3 diffuse = light.diffuse * (diff * textureColor);\n\n\u002F\u002F 镜面反射：使用材质颜色（material.specular）\nvec3 specular = light.specular * (spec * material.specular);\n",[73,181,182,187,193,200,206,212,217,223],{"__ignoreMap":127},[101,183,184],{"class":132,"line":133},[101,185,186],{},"\u002F\u002F 环境光：使用纹理颜色\n",[101,188,190],{"class":132,"line":189},2,[101,191,192],{},"vec3 ambient = light.ambient * textureColor;\n",[101,194,196],{"class":132,"line":195},3,[101,197,199],{"emptyLinePlaceholder":198},true,"\n",[101,201,203],{"class":132,"line":202},4,[101,204,205],{},"\u002F\u002F 漫反射：使用纹理颜色\n",[101,207,209],{"class":132,"line":208},5,[101,210,211],{},"vec3 diffuse = light.diffuse * (diff * textureColor);\n",[101,213,215],{"class":132,"line":214},6,[101,216,199],{"emptyLinePlaceholder":198},[101,218,220],{"class":132,"line":219},7,[101,221,222],{},"\u002F\u002F 镜面反射：使用材质颜色（material.specular）\n",[101,224,226],{"class":132,"line":225},8,[101,227,228],{},"vec3 specular = light.specular * (spec * material.specular);\n",[10,230,231],{},"完整代码如下：",[122,233,237],{"className":234,"code":235,"language":236,"meta":127,"style":127},"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\n    \u002F\u002F 模型矩阵\n    mat4 model = mat4(1.0f);\n    model = rotate(model, radians(-55.0f), vec3(1.0f, 0.0f, 0.0f));\n\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.setMat4(\"model\", model);\n        lightingShader.setVec3(\"light.position\", lightPos.x, lightPos.y, lightPos.z);\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        glDrawArrays(GL_TRIANGLES, 0, 36);\n        \n        \u002F\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        \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++",[73,238,239,244,249,254,259,264,269,274,279,285,291,297,303,309,314,320,326,331,337,343,349,355,361,367,373,379,385,391,397,402,408,414,420,425,431,437,442,448,454,460,465,471,477,483,489,495,500,506,512,517,523,529,535,540,545,550,556,562,568,573,579,585,590,596,602,607,613,619,625,631,636,642,648,653,659,665,670,676,682,687,692,698,704,710,715,720,725,731,736,742,748,754,759,765,771,776,782,788,794,800,806,811,817,823,828,834,840,846,852,858,864,869,874,880,886,891,897,903,908,914,920,926,931,936,941,947,953,958,964,970,976,982,988,994,1000,1006,1011,1017,1023,1029,1034,1040,1045,1051,1057,1063,1069,1074,1079,1084,1090,1096,1101,1107,1113,1119,1125,1131,1137,1143,1148,1154,1159,1164,1170,1176,1182,1188,1193,1199,1204,1209,1215,1221,1227,1233,1238,1244,1249,1254,1260,1266,1272,1278,1283,1289,1294,1299,1305,1311,1317,1323,1328,1334,1339,1344,1350,1356,1362,1368,1373,1379,1385,1391,1396,1402,1408,1414,1420,1425,1431,1437,1443,1449,1455,1460,1466,1472,1478,1483,1489,1495,1501,1506,1512,1518,1524,1529,1535,1541,1547,1552,1558,1564,1570,1575,1581,1587,1593,1599,1604,1609,1615,1621,1627,1633,1639,1644,1650,1655,1661,1667,1672,1678,1684,1689,1695,1701,1707,1713,1719,1724,1730,1736,1741,1747,1753,1759,1764,1770,1776,1782,1787,1793,1799,1804,1810,1816,1822,1828,1833,1839,1845,1851,1857,1863,1869,1875,1881,1887,1893,1898,1904,1910,1916,1921,1927,1933,1938,1944,1950,1956,1962,1967,1973,1979,1985,1991,1997,2002,2008,2013,2018,2024,2030,2035,2041,2047,2052,2057,2063,2069,2074,2080,2086,2092,2098,2104,2110,2116],{"__ignoreMap":127},[101,240,241],{"class":132,"line":133},[101,242,243],{},"#include \u003Cglad\u002Fglad.h>\n",[101,245,246],{"class":132,"line":189},[101,247,248],{},"#include \u003CGLFW\u002Fglfw3.h>\n",[101,250,251],{"class":132,"line":195},[101,252,253],{},"#include \u003Ciostream>\n",[101,255,256],{"class":132,"line":202},[101,257,258],{},"#include \u003Ccmath>\n",[101,260,261],{"class":132,"line":208},[101,262,263],{},"#include \u003Cimgui.h>\n",[101,265,266],{"class":132,"line":214},[101,267,268],{},"#include \u003Cimgui_impl_glfw.h>\n",[101,270,271],{"class":132,"line":219},[101,272,273],{},"#include \u003Cimgui_impl_opengl3.h>\n",[101,275,276],{"class":132,"line":225},[101,277,278],{},"#include \u003Copencv2\u002Fopencv.hpp>\n",[101,280,282],{"class":132,"line":281},9,[101,283,284],{},"#include \u003Copencv2\u002Fimgcodecs.hpp>\n",[101,286,288],{"class":132,"line":287},10,[101,289,290],{},"#include \"utils\u002Fshader.h\"\n",[101,292,294],{"class":132,"line":293},11,[101,295,296],{},"#include \"utils\u002FglmCompute.h\"\n",[101,298,300],{"class":132,"line":299},12,[101,301,302],{},"#include \"include\u002Fdpi_stabilizer.h\"\n",[101,304,306],{"class":132,"line":305},13,[101,307,308],{},"#include \"include\u002Fimgui_manager.h\"\n",[101,310,312],{"class":132,"line":311},14,[101,313,199],{"emptyLinePlaceholder":198},[101,315,317],{"class":132,"line":316},15,[101,318,319],{},"using namespace glm;\n",[101,321,323],{"class":132,"line":322},16,[101,324,325],{},"using namespace cv;\n",[101,327,329],{"class":132,"line":328},17,[101,330,199],{"emptyLinePlaceholder":198},[101,332,334],{"class":132,"line":333},18,[101,335,336],{},"\u002F\u002F 使用 OpenCV 加载纹理\n",[101,338,340],{"class":132,"line":339},19,[101,341,342],{},"unsigned int loadTexture(const char* path) {\n",[101,344,346],{"class":132,"line":345},20,[101,347,348],{},"    unsigned int textureID;\n",[101,350,352],{"class":132,"line":351},21,[101,353,354],{},"    glGenTextures(1, &textureID);\n",[101,356,358],{"class":132,"line":357},22,[101,359,360],{},"    \n",[101,362,364],{"class":132,"line":363},23,[101,365,366],{},"    \u002F\u002F 使用 OpenCV 读取图像\n",[101,368,370],{"class":132,"line":369},24,[101,371,372],{},"    Mat image = imread(path, IMREAD_COLOR);\n",[101,374,376],{"class":132,"line":375},25,[101,377,378],{},"    if (image.empty()) {\n",[101,380,382],{"class":132,"line":381},26,[101,383,384],{},"        std::cout \u003C\u003C \"Failed to load texture at path: \" \u003C\u003C path \u003C\u003C std::endl;\n",[101,386,388],{"class":132,"line":387},27,[101,389,390],{},"        return textureID;\n",[101,392,394],{"class":132,"line":393},28,[101,395,396],{},"    }\n",[101,398,400],{"class":132,"line":399},29,[101,401,360],{},[101,403,405],{"class":132,"line":404},30,[101,406,407],{},"    \u002F\u002F OpenCV 使用 BGR，OpenGL 使用 RGB，需要转换\n",[101,409,411],{"class":132,"line":410},31,[101,412,413],{},"    Mat rgbImage;\n",[101,415,417],{"class":132,"line":416},32,[101,418,419],{},"    cvtColor(image, rgbImage, COLOR_BGR2RGB);\n",[101,421,423],{"class":132,"line":422},33,[101,424,360],{},[101,426,428],{"class":132,"line":427},34,[101,429,430],{},"    \u002F\u002F 翻转图像（OpenGL 期望的 Y 轴方向）\n",[101,432,434],{"class":132,"line":433},35,[101,435,436],{},"    flip(rgbImage, rgbImage, 0);\n",[101,438,440],{"class":132,"line":439},36,[101,441,360],{},[101,443,445],{"class":132,"line":444},37,[101,446,447],{},"    glBindTexture(GL_TEXTURE_2D, textureID);\n",[101,449,451],{"class":132,"line":450},38,[101,452,453],{},"    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, rgbImage.cols, rgbImage.rows, 0, GL_RGB, GL_UNSIGNED_BYTE, rgbImage.data);\n",[101,455,457],{"class":132,"line":456},39,[101,458,459],{},"    glGenerateMipmap(GL_TEXTURE_2D);\n",[101,461,463],{"class":132,"line":462},40,[101,464,360],{},[101,466,468],{"class":132,"line":467},41,[101,469,470],{},"    \u002F\u002F 设置纹理参数\n",[101,472,474],{"class":132,"line":473},42,[101,475,476],{},"    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);\n",[101,478,480],{"class":132,"line":479},43,[101,481,482],{},"    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);\n",[101,484,486],{"class":132,"line":485},44,[101,487,488],{},"    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);\n",[101,490,492],{"class":132,"line":491},45,[101,493,494],{},"    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n",[101,496,498],{"class":132,"line":497},46,[101,499,360],{},[101,501,503],{"class":132,"line":502},47,[101,504,505],{},"    return textureID;\n",[101,507,509],{"class":132,"line":508},48,[101,510,511],{},"}\n",[101,513,515],{"class":132,"line":514},49,[101,516,199],{"emptyLinePlaceholder":198},[101,518,520],{"class":132,"line":519},50,[101,521,522],{},"\u002F\u002F 窗口大小改变时的回调函数\n",[101,524,526],{"class":132,"line":525},51,[101,527,528],{},"void framebuffer_size_callback(GLFWwindow* window, int width, int height) {\n",[101,530,532],{"class":132,"line":531},52,[101,533,534],{},"    glViewport(0, 0, width, height);\n",[101,536,538],{"class":132,"line":537},53,[101,539,511],{},[101,541,543],{"class":132,"line":542},54,[101,544,199],{"emptyLinePlaceholder":198},[101,546,548],{"class":132,"line":547},55,[101,549,199],{"emptyLinePlaceholder":198},[101,551,553],{"class":132,"line":552},56,[101,554,555],{},"\u002F\u002F 全局变量\n",[101,557,559],{"class":132,"line":558},57,[101,560,561],{},"float deltaTime = 0.0f; \u002F\u002F 当前帧与上一帧的时间差\n",[101,563,565],{"class":132,"line":564},58,[101,566,567],{},"float lastFrame = 0.0f; \u002F\u002F 上一帧的时间\n",[101,569,571],{"class":132,"line":570},59,[101,572,199],{"emptyLinePlaceholder":198},[101,574,576],{"class":132,"line":575},60,[101,577,578],{},"\u002F\u002F DPI 稳定器实例\n",[101,580,582],{"class":132,"line":581},61,[101,583,584],{},"DPIStabilizer dpiStabilizer(0.1f, 50.0f, 0.8f);\n",[101,586,588],{"class":132,"line":587},62,[101,589,199],{"emptyLinePlaceholder":198},[101,591,593],{"class":132,"line":592},63,[101,594,595],{},"\u002F\u002F ImGui 管理器实例\n",[101,597,599],{"class":132,"line":598},64,[101,600,601],{},"ImGuiManager imguiManager;\n",[101,603,605],{"class":132,"line":604},65,[101,606,199],{"emptyLinePlaceholder":198},[101,608,610],{"class":132,"line":609},66,[101,611,612],{},"\u002F\u002F 简化的输入处理函数\n",[101,614,616],{"class":132,"line":615},67,[101,617,618],{},"void processInput(GLFWwindow *window){\n",[101,620,622],{"class":132,"line":621},68,[101,623,624],{},"    \u002F\u002F 使用 DPI 稳定器处理所有输入\n",[101,626,628],{"class":132,"line":627},69,[101,629,630],{},"    dpiStabilizer.processKeyboardInput(window);\n",[101,632,634],{"class":132,"line":633},70,[101,635,199],{"emptyLinePlaceholder":198},[101,637,639],{"class":132,"line":638},71,[101,640,641],{},"    if (glfwGetKey(window, GLFW_KEY_F2) == GLFW_PRESS) {\n",[101,643,645],{"class":132,"line":644},72,[101,646,647],{},"        imguiManager.toggleWindow(\"控制面板\");\n",[101,649,651],{"class":132,"line":650},73,[101,652,396],{},[101,654,656],{"class":132,"line":655},74,[101,657,658],{},"    if (glfwGetKey(window, GLFW_KEY_F3) == GLFW_PRESS) {\n",[101,660,662],{"class":132,"line":661},75,[101,663,664],{},"        imguiManager.toggleWindow(\"性能监控\");\n",[101,666,668],{"class":132,"line":667},76,[101,669,396],{},[101,671,673],{"class":132,"line":672},77,[101,674,675],{},"    if (glfwGetKey(window, GLFW_KEY_F4) == GLFW_PRESS) {\n",[101,677,679],{"class":132,"line":678},78,[101,680,681],{},"        imguiManager.toggleWindow(\"调试信息\");\n",[101,683,685],{"class":132,"line":684},79,[101,686,396],{},[101,688,690],{"class":132,"line":689},80,[101,691,360],{},[101,693,695],{"class":132,"line":694},81,[101,696,697],{},"    \u002F\u002F 显示统计信息 (按 T 键)\n",[101,699,701],{"class":132,"line":700},82,[101,702,703],{},"    if (glfwGetKey(window, GLFW_KEY_T) == GLFW_PRESS) {\n",[101,705,707],{"class":132,"line":706},83,[101,708,709],{},"        std::cout \u003C\u003C dpiStabilizer.getStats() \u003C\u003C std::endl;\n",[101,711,713],{"class":132,"line":712},84,[101,714,396],{},[101,716,718],{"class":132,"line":717},85,[101,719,511],{},[101,721,723],{"class":132,"line":722},86,[101,724,199],{"emptyLinePlaceholder":198},[101,726,728],{"class":132,"line":727},87,[101,729,730],{},"int main() {\n",[101,732,734],{"class":132,"line":733},88,[101,735,199],{"emptyLinePlaceholder":198},[101,737,739],{"class":132,"line":738},89,[101,740,741],{},"    \u002F\u002F 模型矩阵\n",[101,743,745],{"class":132,"line":744},90,[101,746,747],{},"    mat4 model = mat4(1.0f);\n",[101,749,751],{"class":132,"line":750},91,[101,752,753],{},"    model = rotate(model, radians(-55.0f), vec3(1.0f, 0.0f, 0.0f));\n",[101,755,757],{"class":132,"line":756},92,[101,758,199],{"emptyLinePlaceholder":198},[101,760,762],{"class":132,"line":761},93,[101,763,764],{},"    \u002F\u002F 投影矩阵\n",[101,766,768],{"class":132,"line":767},94,[101,769,770],{},"    mat4 projection = perspective(radians(45.0f), (float)800 \u002F (float)600, 0.1f, 100.0f);\n",[101,772,774],{"class":132,"line":773},95,[101,775,199],{"emptyLinePlaceholder":198},[101,777,779],{"class":132,"line":778},96,[101,780,781],{},"    \u002F\u002F 初始化GLFW\n",[101,783,785],{"class":132,"line":784},97,[101,786,787],{},"    glfwInit();\n",[101,789,791],{"class":132,"line":790},98,[101,792,793],{},"    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);\n",[101,795,797],{"class":132,"line":796},99,[101,798,799],{},"    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);\n",[101,801,803],{"class":132,"line":802},100,[101,804,805],{},"    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);\n",[101,807,809],{"class":132,"line":808},101,[101,810,360],{},[101,812,814],{"class":132,"line":813},102,[101,815,816],{},"    \u002F\u002F 设置 DPI 感知提示\n",[101,818,820],{"class":132,"line":819},103,[101,821,822],{},"    DPIStabilizer::setDPIHint();\n",[101,824,826],{"class":132,"line":825},104,[101,827,199],{"emptyLinePlaceholder":198},[101,829,831],{"class":132,"line":830},105,[101,832,833],{},"    \u002F\u002F 创建窗口\n",[101,835,837],{"class":132,"line":836},106,[101,838,839],{},"    GLFWwindow* window = glfwCreateWindow(800, 600, \"OpenGL Window\", NULL, NULL);\n",[101,841,843],{"class":132,"line":842},107,[101,844,845],{},"    if (window == NULL) {\n",[101,847,849],{"class":132,"line":848},108,[101,850,851],{},"        std::cout \u003C\u003C \"Failed to create GLFW window\" \u003C\u003C std::endl;\n",[101,853,855],{"class":132,"line":854},109,[101,856,857],{},"        glfwTerminate();\n",[101,859,861],{"class":132,"line":860},110,[101,862,863],{},"        return -1;\n",[101,865,867],{"class":132,"line":866},111,[101,868,396],{},[101,870,872],{"class":132,"line":871},112,[101,873,199],{"emptyLinePlaceholder":198},[101,875,877],{"class":132,"line":876},113,[101,878,879],{},"    glfwMakeContextCurrent(window);\n",[101,881,883],{"class":132,"line":882},114,[101,884,885],{},"    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);\n",[101,887,889],{"class":132,"line":888},115,[101,890,360],{},[101,892,894],{"class":132,"line":893},116,[101,895,896],{},"    \u002F\u002F 初始化 DPI 稳定器\n",[101,898,900],{"class":132,"line":899},117,[101,901,902],{},"    dpiStabilizer.initialize(window);\n",[101,904,906],{"class":132,"line":905},118,[101,907,199],{"emptyLinePlaceholder":198},[101,909,911],{"class":132,"line":910},119,[101,912,913],{},"    \u002F\u002F 初始化GLAD\n",[101,915,917],{"class":132,"line":916},120,[101,918,919],{},"    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {\n",[101,921,923],{"class":132,"line":922},121,[101,924,925],{},"        std::cout \u003C\u003C \"Failed to initialize GLAD\" \u003C\u003C std::endl;\n",[101,927,929],{"class":132,"line":928},122,[101,930,863],{},[101,932,934],{"class":132,"line":933},123,[101,935,396],{},[101,937,939],{"class":132,"line":938},124,[101,940,199],{"emptyLinePlaceholder":198},[101,942,944],{"class":132,"line":943},125,[101,945,946],{},"    \u002F\u002F 初始化 ImGui 管理器\n",[101,948,950],{"class":132,"line":949},126,[101,951,952],{},"    imguiManager.initialize(window);\n",[101,954,956],{"class":132,"line":955},127,[101,957,360],{},[101,959,961],{"class":132,"line":960},128,[101,962,963],{},"    \u002F\u002F 注册窗口\n",[101,965,967],{"class":132,"line":966},129,[101,968,969],{},"    imguiManager.registerWindow(\"Hello ImGui!\", []() {\n",[101,971,973],{"class":132,"line":972},130,[101,974,975],{},"        ImGui::Text(\"DPI 稳定器状态: %s\", dpiStabilizer.isMouseCaptured() ? \"已捕获\" : \"已释放\");\n",[101,977,979],{"class":132,"line":978},131,[101,980,981],{},"        ImGui::Text(\"鼠标敏感度: %.3f\", dpiStabilizer.getSensitivity());\n",[101,983,985],{"class":132,"line":984},132,[101,986,987],{},"        \n",[101,989,991],{"class":132,"line":990},133,[101,992,993],{},"        if (ImGui::Button(\"重置统计\")) {\n",[101,995,997],{"class":132,"line":996},134,[101,998,999],{},"            dpiStabilizer.resetStats();\n",[101,1001,1003],{"class":132,"line":1002},135,[101,1004,1005],{},"        }\n",[101,1007,1009],{"class":132,"line":1008},136,[101,1010,987],{},[101,1012,1014],{"class":132,"line":1013},137,[101,1015,1016],{},"        float sensitivity = dpiStabilizer.getSensitivity();\n",[101,1018,1020],{"class":132,"line":1019},138,[101,1021,1022],{},"        if (ImGui::SliderFloat(\"鼠标敏感度\", &sensitivity, 0.01f, 0.5f)) {\n",[101,1024,1026],{"class":132,"line":1025},139,[101,1027,1028],{},"            dpiStabilizer.setSensitivity(sensitivity);\n",[101,1030,1032],{"class":132,"line":1031},140,[101,1033,1005],{},[101,1035,1037],{"class":132,"line":1036},141,[101,1038,1039],{},"    });\n",[101,1041,1043],{"class":132,"line":1042},142,[101,1044,360],{},[101,1046,1048],{"class":132,"line":1047},143,[101,1049,1050],{},"    imguiManager.registerWindow(\"控制面板\", []() {\n",[101,1052,1054],{"class":132,"line":1053},144,[101,1055,1056],{},"        ImGuiIO& io = ImGui::GetIO();\n",[101,1058,1060],{"class":132,"line":1059},145,[101,1061,1062],{},"        ImGui::Text(\"FPS: %.1f\", io.Framerate);\n",[101,1064,1066],{"class":132,"line":1065},146,[101,1067,1068],{},"        ImGui::Text(\"帧时间: %.3f ms\", 1000.0f \u002F io.Framerate);\n",[101,1070,1072],{"class":132,"line":1071},147,[101,1073,1039],{},[101,1075,1077],{"class":132,"line":1076},148,[101,1078,360],{},[101,1080,1082],{"class":132,"line":1081},149,[101,1083,199],{"emptyLinePlaceholder":198},[101,1085,1087],{"class":132,"line":1086},150,[101,1088,1089],{},"    \u002F\u002F 启用深度测试\n",[101,1091,1093],{"class":132,"line":1092},151,[101,1094,1095],{},"    glEnable(GL_DEPTH_TEST);\n",[101,1097,1099],{"class":132,"line":1098},152,[101,1100,199],{"emptyLinePlaceholder":198},[101,1102,1104],{"class":132,"line":1103},153,[101,1105,1106],{},"    float vertices[] = {\n",[101,1108,1110],{"class":132,"line":1109},154,[101,1111,1112],{},"    \u002F\u002F 立方体顶点数据（位置 + 法线 + 纹理坐标）\n",[101,1114,1116],{"class":132,"line":1115},155,[101,1117,1118],{},"    \u002F\u002F 位置(3) + 法线(3) + 纹理坐标(2) = 8个浮点数\n",[101,1120,1122],{"class":132,"line":1121},156,[101,1123,1124],{},"    \u002F\u002F 前面\n",[101,1126,1128],{"class":132,"line":1127},157,[101,1129,1130],{},"    -0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  0.0f, 0.0f,\n",[101,1132,1134],{"class":132,"line":1133},158,[101,1135,1136],{},"     0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  1.0f, 0.0f,\n",[101,1138,1140],{"class":132,"line":1139},159,[101,1141,1142],{},"     0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  1.0f, 1.0f,\n",[101,1144,1146],{"class":132,"line":1145},160,[101,1147,1142],{},[101,1149,1151],{"class":132,"line":1150},161,[101,1152,1153],{},"    -0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  0.0f, 1.0f,\n",[101,1155,1157],{"class":132,"line":1156},162,[101,1158,1130],{},[101,1160,1162],{"class":132,"line":1161},163,[101,1163,360],{},[101,1165,1167],{"class":132,"line":1166},164,[101,1168,1169],{},"    \u002F\u002F 后面\n",[101,1171,1173],{"class":132,"line":1172},165,[101,1174,1175],{},"    -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f, 0.0f,\n",[101,1177,1179],{"class":132,"line":1178},166,[101,1180,1181],{},"     0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f, 0.0f,\n",[101,1183,1185],{"class":132,"line":1184},167,[101,1186,1187],{},"     0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f, 1.0f,\n",[101,1189,1191],{"class":132,"line":1190},168,[101,1192,1187],{},[101,1194,1196],{"class":132,"line":1195},169,[101,1197,1198],{},"    -0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f, 1.0f,\n",[101,1200,1202],{"class":132,"line":1201},170,[101,1203,1175],{},[101,1205,1207],{"class":132,"line":1206},171,[101,1208,360],{},[101,1210,1212],{"class":132,"line":1211},172,[101,1213,1214],{},"    \u002F\u002F 左面\n",[101,1216,1218],{"class":132,"line":1217},173,[101,1219,1220],{},"    -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,  1.0f, 0.0f,\n",[101,1222,1224],{"class":132,"line":1223},174,[101,1225,1226],{},"    -0.5f,  0.5f, -0.5f, -1.0f,  0.0f,  0.0f,  1.0f, 1.0f,\n",[101,1228,1230],{"class":132,"line":1229},175,[101,1231,1232],{},"    -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,  0.0f, 1.0f,\n",[101,1234,1236],{"class":132,"line":1235},176,[101,1237,1232],{},[101,1239,1241],{"class":132,"line":1240},177,[101,1242,1243],{},"    -0.5f, -0.5f,  0.5f, -1.0f,  0.0f,  0.0f,  0.0f, 0.0f,\n",[101,1245,1247],{"class":132,"line":1246},178,[101,1248,1220],{},[101,1250,1252],{"class":132,"line":1251},179,[101,1253,360],{},[101,1255,1257],{"class":132,"line":1256},180,[101,1258,1259],{},"    \u002F\u002F 右面\n",[101,1261,1263],{"class":132,"line":1262},181,[101,1264,1265],{},"     0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  1.0f, 0.0f,\n",[101,1267,1269],{"class":132,"line":1268},182,[101,1270,1271],{},"     0.5f,  0.5f, -0.5f,  1.0f,  0.0f,  0.0f,  1.0f, 1.0f,\n",[101,1273,1275],{"class":132,"line":1274},183,[101,1276,1277],{},"     0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,  0.0f, 1.0f,\n",[101,1279,1281],{"class":132,"line":1280},184,[101,1282,1277],{},[101,1284,1286],{"class":132,"line":1285},185,[101,1287,1288],{},"     0.5f, -0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  0.0f, 0.0f,\n",[101,1290,1292],{"class":132,"line":1291},186,[101,1293,1265],{},[101,1295,1297],{"class":132,"line":1296},187,[101,1298,360],{},[101,1300,1302],{"class":132,"line":1301},188,[101,1303,1304],{},"    \u002F\u002F 下面\n",[101,1306,1308],{"class":132,"line":1307},189,[101,1309,1310],{},"    -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,  0.0f, 1.0f,\n",[101,1312,1314],{"class":132,"line":1313},190,[101,1315,1316],{},"     0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,  1.0f, 1.0f,\n",[101,1318,1320],{"class":132,"line":1319},191,[101,1321,1322],{},"     0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,  1.0f, 0.0f,\n",[101,1324,1326],{"class":132,"line":1325},192,[101,1327,1322],{},[101,1329,1331],{"class":132,"line":1330},193,[101,1332,1333],{},"    -0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,  0.0f, 0.0f,\n",[101,1335,1337],{"class":132,"line":1336},194,[101,1338,1310],{},[101,1340,1342],{"class":132,"line":1341},195,[101,1343,360],{},[101,1345,1347],{"class":132,"line":1346},196,[101,1348,1349],{},"    \u002F\u002F 上面\n",[101,1351,1353],{"class":132,"line":1352},197,[101,1354,1355],{},"    -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  0.0f, 1.0f,\n",[101,1357,1359],{"class":132,"line":1358},198,[101,1360,1361],{},"     0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  1.0f, 1.0f,\n",[101,1363,1365],{"class":132,"line":1364},199,[101,1366,1367],{},"     0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  1.0f, 0.0f,\n",[101,1369,1371],{"class":132,"line":1370},200,[101,1372,1367],{},[101,1374,1376],{"class":132,"line":1375},201,[101,1377,1378],{},"    -0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  0.0f, 0.0f,\n",[101,1380,1382],{"class":132,"line":1381},202,[101,1383,1384],{},"    -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  0.0f, 1.0f\n",[101,1386,1388],{"class":132,"line":1387},203,[101,1389,1390],{},"};\n",[101,1392,1394],{"class":132,"line":1393},204,[101,1395,360],{},[101,1397,1399],{"class":132,"line":1398},205,[101,1400,1401],{},"    \u002F\u002F 创建VAO\n",[101,1403,1405],{"class":132,"line":1404},206,[101,1406,1407],{},"    unsigned int VAO;\n",[101,1409,1411],{"class":132,"line":1410},207,[101,1412,1413],{},"    glGenVertexArrays(1, &VAO);\n",[101,1415,1417],{"class":132,"line":1416},208,[101,1418,1419],{},"    glBindVertexArray(VAO); \u002F\u002F 绑定VAO\n",[101,1421,1423],{"class":132,"line":1422},209,[101,1424,199],{"emptyLinePlaceholder":198},[101,1426,1428],{"class":132,"line":1427},210,[101,1429,1430],{},"    \u002F\u002F 创建VBO\n",[101,1432,1434],{"class":132,"line":1433},211,[101,1435,1436],{},"    unsigned int VBO;\n",[101,1438,1440],{"class":132,"line":1439},212,[101,1441,1442],{},"    glGenBuffers(1, &VBO);\n",[101,1444,1446],{"class":132,"line":1445},213,[101,1447,1448],{},"    glBindBuffer(GL_ARRAY_BUFFER, VBO); \u002F\u002F 绑定VBO\n",[101,1450,1452],{"class":132,"line":1451},214,[101,1453,1454],{},"    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);\n",[101,1456,1458],{"class":132,"line":1457},215,[101,1459,199],{"emptyLinePlaceholder":198},[101,1461,1463],{"class":132,"line":1462},216,[101,1464,1465],{},"    int nrAttributes;\n",[101,1467,1469],{"class":132,"line":1468},217,[101,1470,1471],{},"    glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);\n",[101,1473,1475],{"class":132,"line":1474},218,[101,1476,1477],{},"    std::cout \u003C\u003C \"Maximum nr of vertex attributes supported: \" \u003C\u003C nrAttributes \u003C\u003C std::endl;\n",[101,1479,1481],{"class":132,"line":1480},219,[101,1482,199],{"emptyLinePlaceholder":198},[101,1484,1486],{"class":132,"line":1485},220,[101,1487,1488],{},"    \u002F\u002F 位置属性\n",[101,1490,1492],{"class":132,"line":1491},221,[101,1493,1494],{},"    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);\n",[101,1496,1498],{"class":132,"line":1497},222,[101,1499,1500],{},"    glEnableVertexAttribArray(0); \u002F\u002F 启用顶点属性\n",[101,1502,1504],{"class":132,"line":1503},223,[101,1505,360],{},[101,1507,1509],{"class":132,"line":1508},224,[101,1510,1511],{},"    \u002F\u002F 法线属性\n",[101,1513,1515],{"class":132,"line":1514},225,[101,1516,1517],{},"    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));\n",[101,1519,1521],{"class":132,"line":1520},226,[101,1522,1523],{},"    glEnableVertexAttribArray(1); \u002F\u002F 启用法线属性\n",[101,1525,1527],{"class":132,"line":1526},227,[101,1528,360],{},[101,1530,1532],{"class":132,"line":1531},228,[101,1533,1534],{},"    \u002F\u002F 纹理坐标属性\n",[101,1536,1538],{"class":132,"line":1537},229,[101,1539,1540],{},"    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));\n",[101,1542,1544],{"class":132,"line":1543},230,[101,1545,1546],{},"    glEnableVertexAttribArray(2); \u002F\u002F 启用纹理坐标属性\n",[101,1548,1550],{"class":132,"line":1549},231,[101,1551,199],{"emptyLinePlaceholder":198},[101,1553,1555],{"class":132,"line":1554},232,[101,1556,1557],{},"    \u002F\u002F 构建并编译着色器程序\n",[101,1559,1561],{"class":132,"line":1560},233,[101,1562,1563],{},"    Shader lightingShader(\"shader\u002Flighting.vert\", \"shader\u002Flighting.frag\");\n",[101,1565,1567],{"class":132,"line":1566},234,[101,1568,1569],{},"    Shader lampShader(\"shader\u002Flamp.vert\", \"shader\u002Flamp.frag\");\n",[101,1571,1573],{"class":132,"line":1572},235,[101,1574,199],{"emptyLinePlaceholder":198},[101,1576,1578],{"class":132,"line":1577},236,[101,1579,1580],{},"    \u002F\u002F 加载纹理\n",[101,1582,1584],{"class":132,"line":1583},237,[101,1585,1586],{},"    unsigned int texture1 = loadTexture(\"Assets\u002Fimage.png\");\n",[101,1588,1590],{"class":132,"line":1589},238,[101,1591,1592],{},"    if (texture1 == 0) {\n",[101,1594,1596],{"class":132,"line":1595},239,[101,1597,1598],{},"        std::cout \u003C\u003C \"Failed to load texture\" \u003C\u003C std::endl;\n",[101,1600,1602],{"class":132,"line":1601},240,[101,1603,396],{},[101,1605,1607],{"class":132,"line":1606},241,[101,1608,199],{"emptyLinePlaceholder":198},[101,1610,1612],{"class":132,"line":1611},242,[101,1613,1614],{},"    \u002F\u002F 创建光源的 VAO\n",[101,1616,1618],{"class":132,"line":1617},243,[101,1619,1620],{},"    unsigned int lightVAO;\n",[101,1622,1624],{"class":132,"line":1623},244,[101,1625,1626],{},"    glGenVertexArrays(1, &lightVAO);\n",[101,1628,1630],{"class":132,"line":1629},245,[101,1631,1632],{},"    glBindVertexArray(lightVAO);\n",[101,1634,1636],{"class":132,"line":1635},246,[101,1637,1638],{},"    glBindBuffer(GL_ARRAY_BUFFER, VBO);\n",[101,1640,1642],{"class":132,"line":1641},247,[101,1643,1494],{},[101,1645,1647],{"class":132,"line":1646},248,[101,1648,1649],{},"    glEnableVertexAttribArray(0);\n",[101,1651,1653],{"class":132,"line":1652},249,[101,1654,199],{"emptyLinePlaceholder":198},[101,1656,1658],{"class":132,"line":1657},250,[101,1659,1660],{},"    \u002F\u002F 光源位置\n",[101,1662,1664],{"class":132,"line":1663},251,[101,1665,1666],{},"    vec3 lightPos(1.2f, 1.0f, 2.0f);\n",[101,1668,1670],{"class":132,"line":1669},252,[101,1671,360],{},[101,1673,1675],{"class":132,"line":1674},253,[101,1676,1677],{},"    \u002F\u002F 相机位置（观察空间）\n",[101,1679,1681],{"class":132,"line":1680},254,[101,1682,1683],{},"    vec3 viewPos = dpiStabilizer.getCameraPosition();\n",[101,1685,1687],{"class":132,"line":1686},255,[101,1688,199],{"emptyLinePlaceholder":198},[101,1690,1692],{"class":132,"line":1691},256,[101,1693,1694],{},"    \u002F\u002F 渲染循环\n",[101,1696,1698],{"class":132,"line":1697},257,[101,1699,1700],{},"    while (!glfwWindowShouldClose(window)) {\n",[101,1702,1704],{"class":132,"line":1703},258,[101,1705,1706],{},"        \u002F\u002F 处理输入\n",[101,1708,1710],{"class":132,"line":1709},259,[101,1711,1712],{},"        processInput(window);\n",[101,1714,1716],{"class":132,"line":1715},260,[101,1717,1718],{},"        glfwPollEvents();\n",[101,1720,1722],{"class":132,"line":1721},261,[101,1723,199],{"emptyLinePlaceholder":198},[101,1725,1727],{"class":132,"line":1726},262,[101,1728,1729],{},"        \u002F\u002F 从 DPI 稳定器获取 view 矩阵\n",[101,1731,1733],{"class":132,"line":1732},263,[101,1734,1735],{},"        mat4 view = dpiStabilizer.getViewMatrix();\n",[101,1737,1739],{"class":132,"line":1738},264,[101,1740,987],{},[101,1742,1744],{"class":132,"line":1743},265,[101,1745,1746],{},"        float currentFrame = glfwGetTime();\n",[101,1748,1750],{"class":132,"line":1749},266,[101,1751,1752],{},"        deltaTime = currentFrame - lastFrame;\n",[101,1754,1756],{"class":132,"line":1755},267,[101,1757,1758],{},"        lastFrame = currentFrame;\n",[101,1760,1762],{"class":132,"line":1761},268,[101,1763,199],{"emptyLinePlaceholder":198},[101,1765,1767],{"class":132,"line":1766},269,[101,1768,1769],{},"        \u002F\u002F 渲染指令\n",[101,1771,1773],{"class":132,"line":1772},270,[101,1774,1775],{},"        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);\n",[101,1777,1779],{"class":132,"line":1778},271,[101,1780,1781],{},"        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);\n",[101,1783,1785],{"class":132,"line":1784},272,[101,1786,987],{},[101,1788,1790],{"class":132,"line":1789},273,[101,1791,1792],{},"        \u002F\u002F 使用光照着色器绘制物体\n",[101,1794,1796],{"class":132,"line":1795},274,[101,1797,1798],{},"        lightingShader.use();\n",[101,1800,1802],{"class":132,"line":1801},275,[101,1803,987],{},[101,1805,1807],{"class":132,"line":1806},276,[101,1808,1809],{},"        \u002F\u002F 绑定纹理\n",[101,1811,1813],{"class":132,"line":1812},277,[101,1814,1815],{},"        glActiveTexture(GL_TEXTURE0);\n",[101,1817,1819],{"class":132,"line":1818},278,[101,1820,1821],{},"        glBindTexture(GL_TEXTURE_2D, texture1);\n",[101,1823,1825],{"class":132,"line":1824},279,[101,1826,1827],{},"        lightingShader.setInt(\"material.diffuse\", 0);\n",[101,1829,1831],{"class":132,"line":1830},280,[101,1832,987],{},[101,1834,1836],{"class":132,"line":1835},281,[101,1837,1838],{},"        lightingShader.setMat4(\"projection\", projection);\n",[101,1840,1842],{"class":132,"line":1841},282,[101,1843,1844],{},"        lightingShader.setMat4(\"view\", view);\n",[101,1846,1848],{"class":132,"line":1847},283,[101,1849,1850],{},"        lightingShader.setMat4(\"model\", model);\n",[101,1852,1854],{"class":132,"line":1853},284,[101,1855,1856],{},"        lightingShader.setVec3(\"light.position\", lightPos.x, lightPos.y, lightPos.z);\n",[101,1858,1860],{"class":132,"line":1859},285,[101,1861,1862],{},"        lightingShader.setVec3(\"light.ambient\",  1.0f, 1.0f, 1.0f);   \u002F\u002F 最大环境光强度\n",[101,1864,1866],{"class":132,"line":1865},286,[101,1867,1868],{},"        lightingShader.setVec3(\"light.diffuse\",  1.0f, 1.0f, 1.0f);   \u002F\u002F 增加漫反射强度\n",[101,1870,1872],{"class":132,"line":1871},287,[101,1873,1874],{},"        lightingShader.setVec3(\"light.specular\", 1.0f, 1.0f, 1.0f); \n",[101,1876,1878],{"class":132,"line":1877},288,[101,1879,1880],{},"        lightingShader.setVec3(\"material.specular\", 0.633f, 0.727811f, 0.633f);\n",[101,1882,1884],{"class":132,"line":1883},289,[101,1885,1886],{},"        \u002F\u002F 高光指数：通常乘以 128 (OpenGL 最大的 1.0 对应 128)\n",[101,1888,1890],{"class":132,"line":1889},290,[101,1891,1892],{},"        lightingShader.setFloat(\"material.shininess\", 0.6f * 128.0f); \u002F\u002F 76.8\n",[101,1894,1896],{"class":132,"line":1895},291,[101,1897,987],{},[101,1899,1901],{"class":132,"line":1900},292,[101,1902,1903],{},"        \u002F\u002F 设置相机位置（观察空间）\n",[101,1905,1907],{"class":132,"line":1906},293,[101,1908,1909],{},"        vec3 currentViewPos = dpiStabilizer.getCameraPosition();\n",[101,1911,1913],{"class":132,"line":1912},294,[101,1914,1915],{},"        lightingShader.setVec3(\"viewPos\", currentViewPos.x, currentViewPos.y, currentViewPos.z);\n",[101,1917,1919],{"class":132,"line":1918},295,[101,1920,987],{},[101,1922,1924],{"class":132,"line":1923},296,[101,1925,1926],{},"        glBindVertexArray(VAO);\n",[101,1928,1930],{"class":132,"line":1929},297,[101,1931,1932],{},"        glDrawArrays(GL_TRIANGLES, 0, 36);\n",[101,1934,1936],{"class":132,"line":1935},298,[101,1937,987],{},[101,1939,1941],{"class":132,"line":1940},299,[101,1942,1943],{},"        \u002F\u002F 绘制光源立方体\n",[101,1945,1947],{"class":132,"line":1946},300,[101,1948,1949],{},"        lampShader.use();\n",[101,1951,1953],{"class":132,"line":1952},301,[101,1954,1955],{},"        lampShader.setMat4(\"projection\", projection);\n",[101,1957,1959],{"class":132,"line":1958},302,[101,1960,1961],{},"        lampShader.setMat4(\"view\", view);\n",[101,1963,1965],{"class":132,"line":1964},303,[101,1966,987],{},[101,1968,1970],{"class":132,"line":1969},304,[101,1971,1972],{},"        \u002F\u002F 光源立方体的模型矩阵\n",[101,1974,1976],{"class":132,"line":1975},305,[101,1977,1978],{},"        mat4 lampModel = mat4(1.0f);\n",[101,1980,1982],{"class":132,"line":1981},306,[101,1983,1984],{},"        lampModel = translate(lampModel, lightPos);\n",[101,1986,1988],{"class":132,"line":1987},307,[101,1989,1990],{},"        lampModel = scale(lampModel, vec3(0.2f)); \u002F\u002F 缩小光源立方体\n",[101,1992,1994],{"class":132,"line":1993},308,[101,1995,1996],{},"        lampShader.setMat4(\"model\", lampModel);\n",[101,1998,2000],{"class":132,"line":1999},309,[101,2001,987],{},[101,2003,2005],{"class":132,"line":2004},310,[101,2006,2007],{},"        glBindVertexArray(lightVAO);\n",[101,2009,2011],{"class":132,"line":2010},311,[101,2012,1932],{},[101,2014,2016],{"class":132,"line":2015},312,[101,2017,987],{},[101,2019,2021],{"class":132,"line":2020},313,[101,2022,2023],{},"        \u002F\u002F 使用 ImGui 管理器渲染所有窗口\n",[101,2025,2027],{"class":132,"line":2026},314,[101,2028,2029],{},"        imguiManager.render();\n",[101,2031,2033],{"class":132,"line":2032},315,[101,2034,199],{"emptyLinePlaceholder":198},[101,2036,2038],{"class":132,"line":2037},316,[101,2039,2040],{},"        \u002F\u002F 交换缓冲区和检查IO事件\n",[101,2042,2044],{"class":132,"line":2043},317,[101,2045,2046],{},"        glfwSwapBuffers(window);\n",[101,2048,2050],{"class":132,"line":2049},318,[101,2051,396],{},[101,2053,2055],{"class":132,"line":2054},319,[101,2056,199],{"emptyLinePlaceholder":198},[101,2058,2060],{"class":132,"line":2059},320,[101,2061,2062],{},"    \u002F\u002F 清理 ImGui 管理器\n",[101,2064,2066],{"class":132,"line":2065},321,[101,2067,2068],{},"    imguiManager.shutdown();\n",[101,2070,2072],{"class":132,"line":2071},322,[101,2073,199],{"emptyLinePlaceholder":198},[101,2075,2077],{"class":132,"line":2076},323,[101,2078,2079],{},"    \u002F\u002F 清理资源\n",[101,2081,2083],{"class":132,"line":2082},324,[101,2084,2085],{},"    glDeleteVertexArrays(1, &VAO);\n",[101,2087,2089],{"class":132,"line":2088},325,[101,2090,2091],{},"    glDeleteVertexArrays(1, &lightVAO);\n",[101,2093,2095],{"class":132,"line":2094},326,[101,2096,2097],{},"    glDeleteBuffers(1, &VBO);\n",[101,2099,2101],{"class":132,"line":2100},327,[101,2102,2103],{},"    glDeleteTextures(1, &texture1);\n",[101,2105,2107],{"class":132,"line":2106},328,[101,2108,2109],{},"    glfwTerminate();\n",[101,2111,2113],{"class":132,"line":2112},329,[101,2114,2115],{},"    return 0;\n",[101,2117,2119],{"class":132,"line":2118},330,[101,2120,511],{},[31,2122,2123],{"id":2123},"镜面光贴图",[10,2125,2126,2127,2130,2131,2134],{},"你可能会注意到，镜面高光看起来有些奇怪，因为我们的物体大部分都是木头，我们知道木头不应该有这么强的镜面高光的。我们可以将物体的镜面光材质设置为",[73,2128,2129],{},"vec3(0.0)","来解决这个问题，但这也意味着箱子钢制的边框将不再能够显示镜面高光了，我们知道钢铁",[14,2132,2133],{},"应该","是有一些镜面高光的。所以，我们想要让物体的某些部分以不同的强度显示镜面高光。这个问题看起来和漫反射贴图非常相似。是巧合吗？我想不是。",[10,2136,2137,2138,2142],{},"我们同样可以使用一个专门用于镜面高光的纹理贴图。这也就意味着我们需要生成一个黑白的（如果你想得话也可以是彩色的）纹理，来定义物体每部分的镜面光强度。下面是一个",[41,2139,2123],{"href":2140,"rel":2141},"https:\u002F\u002Flearnopengl-cn.github.io\u002Fimg\u002F02\u002F04\u002Fcontainer2_specular.png",[45],"(Specular Map)的例子：",[10,2144,2145],{},[63,2146],{"alt":63,"src":2147},".\u002Fassets\u002Fcontainer2_specular.png",[10,2149,2150,2151,2153,2154,2157],{},"镜面高光的强度可以通过图像每个像素的亮度来获取。镜面光贴图上的每个像素都可以由一个颜色向量来表示，比如说黑色代表颜色向量",[73,2152,2129],{},"，灰色代表颜色向量",[73,2155,2156],{},"vec3(0.5)","。在片段着色器中，我们接下来会取样对应的颜色值并将它乘以光源的镜面强度。一个像素越「白」，乘积就会越大，物体的镜面光分量就会越亮。",[10,2159,2160],{},"由于箱子大部分都由木头所组成，而且木头材质应该没有镜面高光，所以漫反射纹理的整个木头部分全部都转换成了黑色。箱子钢制边框的镜面光强度是有细微变化的，钢铁本身会比较容易受到镜面高光的影响，而裂缝则不会。",[10,2162,2163],{},"从实际角度来说，木头其实也有镜面高光，尽管它的反光度(Shininess)很小（更多的光被散射），影响也比较小，但是为了教学目的，我们可以假设木头不会对镜面光有任何反应。",[10,2165,2166,2167,2170,2171,2174],{},"使用",[14,2168,2169],{},"Photoshop","或",[14,2172,2173],{},"Gimp","之类的工具，将漫反射纹理转换为镜面光纹理还是比较容易的，只需要剪切掉一些部分，将图像转换为黑白的，并增加亮度\u002F对比度就好了。",[2176,2177,2178],"h2",{"id":2178},"采样镜面光贴图",[10,2180,2181,2182,2185],{},"镜面光贴图和其它的纹理非常类似，所以代码也和漫反射贴图的代码很类似。记得要保证正确地加载图像并生成一个纹理对象。由于我们正在同一个片段着色器中使用另一个纹理采样器，我们必须要对镜面光贴图使用一个不同的纹理单元（见[纹理](",[41,2183,43],{"href":43,"rel":2184},[45]," Getting started\u002F06 Textures\u002F)），所以我们在渲染之前先把它绑定到合适的纹理单元上：",[122,2187,2189],{"className":234,"code":2188,"language":236,"meta":127,"style":127},"lightingShader.setInt(\"material.specular\", 1);\n...\nglActiveTexture(GL_TEXTURE1);\nglBindTexture(GL_TEXTURE_2D, specularMap);\n",[73,2190,2191,2196,2201,2206],{"__ignoreMap":127},[101,2192,2193],{"class":132,"line":133},[101,2194,2195],{},"lightingShader.setInt(\"material.specular\", 1);\n",[101,2197,2198],{"class":132,"line":189},[101,2199,2200],{},"...\n",[101,2202,2203],{"class":132,"line":195},[101,2204,2205],{},"glActiveTexture(GL_TEXTURE1);\n",[101,2207,2208],{"class":132,"line":202},[101,2209,2210],{},"glBindTexture(GL_TEXTURE_2D, specularMap);\n",[10,2212,2213,2214,2216,2217,2219],{},"接下来更新片段着色器的材质属性，让其接受一个",[73,2215,75],{},"而不是",[73,2218,79],{},"作为镜面光分量：",[122,2221,2223],{"className":234,"code":2222,"language":236,"meta":127,"style":127},"struct Material {\n    sampler2D diffuse;\n    sampler2D specular;\n    float     shininess;\n};\n",[73,2224,2225,2230,2235,2240,2245],{"__ignoreMap":127},[101,2226,2227],{"class":132,"line":133},[101,2228,2229],{},"struct Material {\n",[101,2231,2232],{"class":132,"line":189},[101,2233,2234],{},"    sampler2D diffuse;\n",[101,2236,2237],{"class":132,"line":195},[101,2238,2239],{},"    sampler2D specular;\n",[101,2241,2242],{"class":132,"line":202},[101,2243,2244],{},"    float     shininess;\n",[101,2246,2247],{"class":132,"line":208},[101,2248,1390],{},[10,2250,2251],{},"最后我们希望采样镜面光贴图，来获取片段所对应的镜面光强度：",[122,2253,2255],{"className":234,"code":2254,"language":236,"meta":127,"style":127},"vec3 ambient  = light.ambient  * vec3(texture(material.diffuse, TexCoords));\nvec3 diffuse  = light.diffuse  * diff * vec3(texture(material.diffuse, TexCoords));  \nvec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));\nFragColor = vec4(ambient + diffuse + specular, 1.0);\n",[73,2256,2257,2262,2267,2272],{"__ignoreMap":127},[101,2258,2259],{"class":132,"line":133},[101,2260,2261],{},"vec3 ambient  = light.ambient  * vec3(texture(material.diffuse, TexCoords));\n",[101,2263,2264],{"class":132,"line":189},[101,2265,2266],{},"vec3 diffuse  = light.diffuse  * diff * vec3(texture(material.diffuse, TexCoords));  \n",[101,2268,2269],{"class":132,"line":195},[101,2270,2271],{},"vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));\n",[101,2273,2274],{"class":132,"line":202},[101,2275,2276],{},"FragColor = vec4(ambient + diffuse + specular, 1.0);\n",[10,2278,2279,2280,2283],{},"通过使用镜面光贴图我们可以可以对物体设置大量的细节，比如物体的哪些部分需要有",[14,2281,2282],{},"闪闪发光","的属性，我们甚至可以设置它们对应的强度。镜面光贴图能够在漫反射贴图之上给予我们更高一层的控制。",[10,2285,2286],{},"如果你想另辟蹊径，你也可以在镜面光贴图中使用真正的颜色，不仅设置每个片段的镜面光强度，还设置了镜面高光的颜色。从现实角度来说，镜面高光的颜色大部分（甚至全部）都是由光源本身所决定的，所以这样并不能生成非常真实的视觉效果（这也是为什么图像通常是黑白的，我们只关心强度）。",[10,2288,2289],{},"如果你现在运行程序的话，你可以清楚地看到箱子的材质现在和真实的钢制边框箱子非常类似了：",[10,2291,2292],{},[63,2293],{"alt":63,"src":2294},".\u002Fassets\u002Fmaterials_specular_map.png",[10,2296,2297,2298,2303],{},"你可以在",[41,2299,2302],{"href":2300,"rel":2301},"https:\u002F\u002Flearnopengl.com\u002Fcode_viewer_gh.php?code=src\u002F2.lighting\u002F4.2.lighting_maps_specular_map\u002Flighting_maps_specular.cpp",[45],"这里","找到程序的全部源码。",[10,2305,2306],{},"通过使用漫反射和镜面光贴图，我们可以给相对简单的物体添加大量的细节。我们甚至可以使用法线\u002F凹凸贴图(Normal\u002FBump Map)或者反射贴图(Reflection Map)给物体添加更多的细节，但这些将会留到之后的教程中。把你的箱子给你的朋友或者家人看看，并且坚信我们的箱子有一天会比现在更加漂亮！",[2308,2309,2310],"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":127,"searchDepth":189,"depth":189,"links":2312},[2313],{"id":2178,"depth":189,"text":2178},"上一节中的那个材质系统是肯定不够的，它只是一个最简单的模型，所以我们需要拓展之前的系统，引入漫反射和镜面光贴图(Map)。这允许我们对物体的漫反射分量（以及间接地对环境光分量，它们几乎总是一样的）和镜面光分量有着更精确的控制。","md",{},"\u002Fcpp\u002Fopengel\u002F光照贴图",{"description":2314},"cpp\u002Fopengel\u002F光照贴图","1pc-R9taBr4R4MUqMnwIOHGyPZQNxiLhAtJJo7cRnOQ",1791042463214]