本文最后更新于 2024年7月15日 晚上
TA壬的舒适区
CMake与ImGui
设置 CMake 最低版本和项目名称
1 2
| cmake_minimum_required(VERSION 3.28) project(LearnOpenGL)
|
cmake_minimum_required(VERSION 3.28)
:这行代码指定了我们希望使用的最低 CMake 版本是 3.28。
project(LearnOpenGL)
:这行代码将我们的项目命名为 “LearnOpenGL”。
设置 C++ 标准
为了确保代码能够使用特定的 C++ 标准,我们需要明确指定它:
1
| set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
:这行代码将 C++ 标准设置为 C17。这样可以确保我们的代码能够使用 C17 的特性,同时也能确保编译器正确处理这些特性。
手动设置 GLFW 路径
由于我们使用的是本地安装的 GLFW 库,因此需要手动设置其包含目录和库目录:
1 2 3
| set(GLFW_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/include/GLFW") set(GLFW_LIB_DIR "${CMAKE_SOURCE_DIR}/libs") set(GLFW_LIBRARY "${CMAKE_SOURCE_DIR}/libs/glfw3.dll")
|
set(GLFW_INCLUDE_DIR "C:/Users/msik/CLionProjects/LearnOpenGL/include/GLFW")
:设置 GLFW 的包含目录路径,使编译器能够找到 GLFW 的头文件。
set(GLFW_LIB_DIR "C:/Users/msik/CLionProjects/LearnOpenGL/libs")
:设置 GLFW 的库目录路径。
set(GLFW_LIBRARY "C:/Users/msik/CLionProjects/LearnOpenGL/libs/glfw3.dll")
:设置 GLFW 的库文件路径。
${CMAKE_SOURCE_DIR}是内置宏,指CMakeList.txt所在的目录。
添加 IMGUI 库
IMGUI 是一个常用的图形用户界面库。我们需要将其添加到我们的项目中:
1 2 3 4 5 6 7 8 9 10
| add_library(IMGUI SHARED ./imgui/imgui.cpp ./imgui/imgui_impl_glfw.cpp ./imgui/imgui_impl_opengl3.cpp ./imgui/imgui_draw.cpp ./imgui/imgui_tables.cpp ./imgui/imgui_widgets.cpp ./imgui/imgui_demo.cpp ./imgui/imgui_stdlib.cpp )
|
add_library(IMGUI SHARED ...)
:这行代码定义了一个共享库,名为 IMGUI
,并包含了多个源文件。共享库可以在多个程序之间共享,提高了代码复用性。
接下来设置 IMGUI 库的包含目录和链接库:
1 2
| target_include_directories(IMGUI PRIVATE ${GLFW_INCLUDE_DIR}) target_link_libraries(IMGUI PRIVATE ${GLFW_LIBRARY})
|
target_include_directories(IMGUI PRIVATE ${GLFW_INCLUDE_DIR})
:将 GLFW 的包含目录添加到 IMGUI 库的私有包含目录中,确保 IMGUI 库可以访问 GLFW 的头文件。
target_link_libraries(IMGUI PRIVATE ${GLFW_LIBRARY})
:将 GLFW 库文件链接到 IMGUI 库中,使 IMGUI 库能够使用 GLFW 的功能。
添加可执行文件
接下来,我们为项目添加一个可执行文件:
1 2 3 4 5 6 7 8
| add_executable(LearnOpenGL Archive/main.cpp glad.c include/shader_s.h GLMTest.cpp stbitmp.cpp include/camera.h )
|
add_executable(LearnOpenGL ...)
:这行代码定义了一个可执行文件,名为 LearnOpenGL
,并指定了其源文件列表。可执行文件是最终生成的程序,可以运行。
包含路径
我们需要指定包含目录,以便编译器能够找到所有头文件:
1
| include_directories(${GLFW_INCLUDE_DIR} "${CMAKE_SOURCE_DIR}/include")
|
include_directories(...)
:这行代码将 GLFW 的包含目录和项目的包含目录添加到编译器的搜索路径中,使编译器能够找到这些头文件。
链接库
最后,我们需要将所有必要的库链接到可执行文件中:
1
| target_link_libraries(LearnOpenGL PRIVATE ${GLFW_LIBRARY} IMGUI)
|
target_link_libraries(LearnOpenGL PRIVATE ${GLFW_LIBRARY} IMGUI)
:这行代码将 GLFW 库和 IMGUI 库链接到 LearnOpenGL
可执行文件中,使其能够使用这些库的功能。
与OpenGL工程集成
- 添加头文件
1 2 3
| #include "imgui.h" #include "imgui_impl_glfw.h" #include "imgui_impl_opengl3.h"
|
- 初始化
1 2 3 4 5 6 7
| IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
ImGui_ImplGlfw_InitForOpenGL(window, true); ImGui_ImplOpenGL3_Init();
|
- 每次渲染初始化
1 2 3 4 5 6 7 8
| ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); if(ImGui::Begin("窗口名")){ ImGui::End(); }
|
- 每次渲染结束
1 2
| ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
- 程序终止
1 2 3
| ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplGlfw_Shutdown(); ImGui::DestroyContext();
|
控件
文本
变体函数 |
作用描述 |
示例代码 |
ImGui::Text |
显示简单文本 |
ImGui::Text("This is some useful text."); |
ImGui::TextColored |
显示带颜色的文本 |
ImVec4 color = ImVec4(1.0f, 0.0f, 0.0f, 1.0f);
ImGui::TextColored(color, "This is red text."); |
ImGui::TextDisabled |
显示灰色文本,表示禁用状态 |
ImGui::TextDisabled("This is disabled text."); |
ImGui::TextWrapped |
显示自动换行的文本 |
ImGui::TextWrapped("This is some long text that will automatically wrap."); |
ImGui::TextUnformatted |
显示不进行格式化的文本 |
const char* text = "This is unformatted text.";
ImGui::TextUnformatted(text); |
ImGui::Text (格式化字符串) |
使用格式化字符串显示文本 |
int value = 42;
ImGui::Text("The answer is %d", value); |
按钮
控件函数 |
作用描述 |
示例代码 |
ImGui::Button |
创建一个按钮 |
if (ImGui::Button("Click Me")) { /* 按钮被点击时执行的代码 */ } |
ImGui::SmallButton |
创建一个小按钮 |
if (ImGui::SmallButton("Click Me")) { /* 小按钮被点击时执行的代码 */ } |
ImGui::InvisibleButton |
创建一个不可见的按钮 |
if (ImGui::InvisibleButton("Click Me", ImVec2(100, 20))) { /* 按钮被点击时执行的代码 */ } |
复选框
控件函数 |
作用描述 |
示例代码 |
ImGui::Checkbox |
创建一个复选框 |
static bool checked = false;
ImGui::Checkbox("Check Me", &checked); |
ImGui::CheckboxFlags |
创建一个带有标志的复选框 |
static int flags = 0;
ImGui::CheckboxFlags("Flag 1", &flags, 1); |
输入框
控件函数 |
作用描述 |
示例代码 |
ImGui::InputText |
创建一个文本输入框 |
static char text[128] = "";
ImGui::InputText("Input Text", text, IM_ARRAYSIZE(text)); |
ImGui::InputTextMultiline |
创建一个多行文本输入框 |
static char text[128] = "";
ImGui::InputTextMultiline("Input Text", text, IM_ARRAYSIZE(text)); |
ImGui::InputInt |
创建一个整数输入框 |
static int value = 0;
ImGui::InputInt("Input Int", &value); |
ImGui::InputFloat |
创建一个浮点数输入框 |
static float value = 0.0f;
ImGui::InputFloat("Input Float", &value); |
滑块
控件函数 |
作用描述 |
示例代码 |
ImGui::SliderFloat |
创建一个浮点数滑块 |
static float value = 0.0f;
ImGui::SliderFloat("Float Slider", &value, 0.0f, 1.0f); |
ImGui::SliderInt |
创建一个整数滑块 |
static int value = 0;
ImGui::SliderInt("Int Slider", &value, 0, 100); |
ImGui::VSliderFloat |
创建一个垂直浮点数滑块 |
static float value = 0.0f;
ImGui::VSliderFloat("VFloat Slider", ImVec2(20,100), &value, 0.0f, 1.0f); |
ImGui::VSliderInt |
创建一个垂直整数滑块 |
static int value = 0;
ImGui::VSliderInt("VInt Slider", ImVec2(20,100), &value, 0, 100); |
下拉框
控件函数 |
作用描述 |
示例代码 |
ImGui::Combo |
创建一个下拉框 |
static int item = 0;
const char* items[] = { "Item 1", "Item 2", "Item 3" };
ImGui::Combo("Combo", &item, items, IM_ARRAYSIZE(items)); |
光照
颜色
颜色可以数字化的由红色(Red)、绿色(Green)和蓝色(Blue)三个分量组成,它们通常被缩写为RGB。
glm中,使用glm::vec3 COLOR_NAME(float x, float y, float z)
定义颜色变量。
现实中,我们看到物体的颜色实际上是被物体反射(即无法被物体吸收)的颜色与光源颜色的叠加。因此,要计算最终我们看到物体的颜色,可以用光源颜色*物体颜色的方式得到最终的颜色项链。
1 2 3
| glm::vec3 lightColor(1.0f, 1.0f, 1.0f); glm::vec3 toyColor(1.0f, 0.5f, 0.31f); glm::vec3 result = lightColor * toyColor;
|
严谨的物体颜色定义应为:物体从一个光源反射各个颜色分量的大小。考虑如下光源和物体:
1 2
| glm::vec3 lightColor(0.0f, 1.0f, 0.0f); glm::vec3 toyColor(1.0f, 0.5f, 0.31f);
|
可以得出,物体在反射光源颜色时,R和B通道压根就没有颜色给它反射。而G通道可以反射一半,最终得到颜色(0.0f, 0.5f, 0.0f)
,即深绿色。
当两个物体使用不同材质时,需要创建两个不同的Shader对象。
注意:
绘制应用不同Shader的对象时,需要use()。
同时,也需要设置新Shader的View和Projection矩阵。
基础光照
冯氏光照模型(Phong Lighting Model)是一种简单的光照模型。它分为三个部分:
- 环境光(Ambient):在任何情况下都给予物体的颜色。
- 漫反射光(Diffuse):模拟光源对物体的方向性影响。物体的某部分越正对光源,就越亮。
- 高光(Specular):有光泽表面上出现的亮点,其颜色更加接近于光照颜色本身。
环境光
全局光照(Global Illumination,GI)是考虑到间接光照的算法。环境光是一种极其简化的全局光照方式。其实现方式如下:
1 2 3 4
| float ambientStrength = 0.1; vec3 ambient = ambientStrength * lightColor; vec3 result = ambient * objectColor; FragColor = vec4(result, 1.0);
|
漫反射光
法向量(Normal Vector)是垂直于片段表面的向量。法向量一般存储在顶点数据里,作为一个顶点属性。
两个单位向量的夹角越小,它们点乘的结果越倾向于1。借助这一点,实现漫反射效果,即:越正对光源,颜色越亮。

这个算法需要获取两个数据:光照方向和法向量。前者通过使用uniform向着色器传递lightPos
获得,后者通过顶点属性配置获得。
任何不涉及距离,只涉及方向的向量与计算都应当归一化
1 2 3 4 5 6
| vec3 normal = normalize(norm); vec3 lightDir = normalize(lightPos-fragWorldPos); float diffuseStrength = max(dot(normal,lightDir),0.0); vec3 diffuse = diffuseStrength*lightColor; vec3 ambient = ambientStrength*lightColor; FragColor = vec4((ambient+diffuse)*objectColor,1.0f);
|
传入的法向量是基于局部坐标的。法向量不能简单地通过乘以模型矩阵变换,因为这些变换会破坏发现的垂直性质。

法线矩阵(Normal Matrix)是专门将法向量变换到世界坐标的矩阵。它是模型矩阵左上叫3x3部分的逆矩阵的法线矩阵。因此,可以使用Normal = mat3(transpose(inverse(model))) * aNormal
来计算世界坐标下的法线。这一操作应当在cpp程序中进行,然后通过uniform传递到着色器,因为矩阵求逆对着色器来说是非常耗时的。
高光
高光取决于LightDir、Norm和ViewDir。
如图,入射光和反射光与法线的夹角都是α,ViewDir和反射光的夹角是θ。高光最强的地方就是反射光所在的方向,因此,θ越小,高光越强。因此,同样可以通过点乘来得到高光系数。
1 2 3 4
| vec3 viewDir = normalize(viewPos-fragWorldPos); vec3 reflectDir = reflect(-lightDir,normal); vec3 specular = pow(max(dot(reflectDir,viewDir),0.0),shineness)*lightColor*specularStrength;
|
冯氏光照模型的三个部分是相加的。
在顶点着色器中完成的冯氏光照模型角Gouraud着色。它的效果不好,因为其颜色由插值决定。
代码
世界空间下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| float ambientStrength = 0.1f; float specularStrength = 0.5f; int shineness = 32; while(!glfwWindowShouldClose(window)) { process_keyboard_input(window); glClearColor(0.2f,0.3f,0.4f,1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); shader.use(); shader.setVec3("viewPos",camera.Position.x,camera.Position.y,camera.Position.z); imgui_frame_init(); if(ImGui::Begin("window")){ ImGui::SliderFloat("AmbientStrength",&ambientStrength,0.0f,1.0f); shader.setFloat("ambientStrength",ambientStrength); ImGui::SliderFloat("SpecularStrength",&specularStrength,0.0f,1.0f); shader.setFloat("specularStrength",specularStrength); ImGui::SliderInt("Shineness",&shineness,2,256); shader.setInt("shineness",shineness); ImGui::SliderFloat3("LightPos",glm::value_ptr(lightPos),-3.0f,3.0f); ImGui::Checkbox("canViewRotate(K to switch)",&canMove); ImGui::End(); } float currFrame=glfwGetTime(); deltaTime = currFrame-lastFrame; lastFrame = currFrame; glm::mat4 view = camera.GetViewMatrix(); glm::mat4 proj = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f); shader.setMat4("view",view); shader.setMat4("proj",proj); glBindVertexArray(VAO); glm::mat4 model = glm::mat4(1.0f); shader.setMat4("model", model); shader.setVec3("lightPos",lightPos.x,lightPos.y,lightPos.z); glDrawArrays(GL_TRIANGLES, 0, 36); glBindVertexArray(lightVAO); lightShader.use(); lightShader.setMat4("view",view); lightShader.setMat4("proj",proj); model = glm::translate(model,lightPos); model = glm::scale(model,glm::vec3(0.5f)); lightShader.setMat4("model", model); glDrawArrays(GL_TRIANGLES, 0, 36); imgui_end_draw(); glfwSwapBuffers(window); glfwPollEvents(); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| #version 330 core out vec4 FragColor; in vec3 norm; in vec3 fragWorldPos;
uniform vec3 objectColor; uniform vec3 lightColor; uniform float ambientStrength; uniform float specularStrength; uniform int shineness; uniform vec3 lightPos; uniform vec3 viewPos;
void main() { vec3 normal = normalize(norm); vec3 lightDir = normalize(lightPos-fragWorldPos); float diffuseStrength = max(dot(normal,lightDir),0.0); vec3 diffuse = diffuseStrength*lightColor; vec3 ambient = ambientStrength*lightColor; vec3 viewDir = normalize(viewPos-fragWorldPos); vec3 reflectDir = reflect(-lightDir,normal); vec3 specular = pow(max(dot(reflectDir,viewDir),0.0),shineness)*lightColor*specularStrength; FragColor = vec4((ambient+diffuse+specular)*objectColor,1.0f); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aNorm;
uniform mat4 model; uniform mat4 view; uniform mat4 proj; out vec3 norm; out vec3 fragWorldPos;
void main() { gl_Position = proj * view * model * vec4(aPos, 1.0); fragWorldPos = (model*vec4(aPos,1.0f)).xyz; norm = aNorm; }
|
观察空间下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #version 330 core out vec4 FragColor; in vec3 norm; in vec3 fragViewPos; in vec3 lightViewPos;
uniform vec3 objectColor; uniform vec3 lightColor; uniform float ambientStrength; uniform float specularStrength; uniform int shineness;
void main() { vec3 normal = normalize(norm); vec3 lightDir = normalize(lightViewPos-fragViewPos); float diffuseStrength = max(dot(normal,lightDir),0.0); vec3 diffuse = diffuseStrength*lightColor; vec3 ambient = ambientStrength*lightColor; vec3 viewDir = normalize(-fragViewPos); vec3 reflectDir = reflect(-lightDir,normal); vec3 specular = pow(max(dot(reflectDir,viewDir),0.0),shineness)*lightColor*specularStrength; FragColor = vec4((ambient+diffuse+specular)*objectColor,1.0f); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aNorm;
uniform mat4 model; uniform mat4 view; uniform mat4 proj; uniform vec3 lightPos; out vec3 norm; out vec3 fragViewPos; out vec3 lightViewPos;
void main() { gl_Position = proj * view * model * vec4(aPos, 1.0); fragViewPos = (view*model*vec4(aPos,1.0f)).xyz; lightViewPos = (view*vec4(lightPos,1.0f)).xyz; norm = mat3(transpose(inverse(view * model)))*aNorm; }
|