LearnOpenGL学习笔记(五)- CMake、ImGui与光照

本文最后更新于 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. 添加头文件
1
2
3
#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"
  1. 初始化
1
2
3
4
5
6
7
IMGUI_CHECKVERSION(); //检查版本
ImGui::CreateContext(); //创建上下文
ImGuiIO& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; //激活键盘支持

ImGui_ImplGlfw_InitForOpenGL(window, true); //在GLFW窗口上进行初始化
ImGui_ImplOpenGL3_Init();
  1. 每次渲染初始化
1
2
3
4
5
6
7
8
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
if(ImGui::Begin("窗口名")){
//窗口控件逻辑放在这
//..
ImGui::End();
}
  1. 每次渲染结束
1
2
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
  1. 程序终止
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.0f, 0.5f, 0.31f);

严谨的物体颜色定义应为:物体从一个光源反射各个颜色分量的大小。考虑如下光源和物体:

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。借助这一点,实现漫反射效果,即:越正对光源,颜色越亮。

image-20240715205247231

这个算法需要获取两个数据:光照方向和法向量。前者通过使用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); //相加而非相乘

传入的法向量是基于局部坐标的。法向量不能简单地通过乘以模型矩阵变换,因为这些变换会破坏发现的垂直性质。

img

法线矩阵(Normal Matrix)是专门将法向量变换到世界坐标的矩阵。它是模型矩阵左上叫3x3部分的逆矩阵的法线矩阵。因此,可以使用Normal = mat3(transpose(inverse(model))) * aNormal来计算世界坐标下的法线。这一操作应当在cpp程序中进行,然后通过uniform传递到着色器,因为矩阵求逆对着色器来说是非常耗时的。

高光

高光取决于LightDir、Norm和ViewDir。

image-20240715205521385

如图,入射光和反射光与法线的夹角都是α,ViewDir和反射光的夹角是θ。高光最强的地方就是反射光所在的方向,因此,θ越小,高光越强。因此,同样可以通过点乘来得到高光系数。

1
2
3
4
//高光计算
vec3 viewDir = normalize(viewPos-fragWorldPos); //viewDir由uniform传入,指相机的世界坐标
vec3 reflectDir = reflect(-lightDir,normal); //reflect函数的参数一是由光源指向物体的方向向量
vec3 specular = pow(max(dot(reflectDir,viewDir),0.0),shineness)*lightColor*specularStrength;//shineness是反光度属性,越大则高光影响范围越大,强度越强

冯氏光照模型的三个部分是相加的。

在顶点着色器中完成的冯氏光照模型角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交互窗口
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; //关键在于使用法线矩阵变换法线位置
}

LearnOpenGL学习笔记(五)- CMake、ImGui与光照
http://example.com/2024/07/15/LearnOpenGL学习笔记(五)- CMake、ImGui与光照/
作者
Yoi
发布于
2024年7月15日
许可协议