OpenGL集成imgui
LearnOpenGL教程采用了循序渐进的教学方式,由浅入深地讲解了OpenGL的使用。但是,在学习OpenGL的过程中不难发现,有时候只是简单的一个参数的改动,就不得不重新编译一遍程序,根据评论区的介绍发现了ImGui这款轻量级的GUI库,可以非常方便地和OpenGL程序进行交互。
ImGui
Dear ImGui(ImGui)是一个轻量级、便于集成的C++ GUI库,用于创建即时编辑器、调试工具等工具。ImGui最初是为游戏开发而设计,但也被广泛用于各种领域,如工具开发、仿真、数据可视化等。Dear ImGui库的主要特点和优势:
1.轻量级且易于集成:Dear ImGui是一个轻量级的库,只提供GUI绘制的基本功能,使其易于集成到各种项目中。
2.简单的API:ImGui提供了简单而直观的API,使用户可以轻松创建各种GUI元素,如按钮、文本框、复选框等。
3.即时渲染:ImGui使用即时渲染的方式工作,这意味着GUI元素在每一帧都会被重新绘制,从而实现动态更新和交互。
4.跨平台:ImGui支持跨平台开发,可以在各种操作系统上运行,包括Windows、macOS、Linux等。
5.自定义主题:ImGui允许用户轻松定制GUI的外观和样式,包括颜色、字体等,以满足不同项目的需求。
6.无依赖:ImGui本身不依赖于任何图形库,但可以与常见的图形库(如OpenGL、DirectX)集成使用。
在使用Dear ImGui时,你可以创建各种GUI元素,与用户交互并实时更新,从而为项目提供一个简单而有效的用户界面。ImGui的设计使得它特别适合用于快速原型设计、调试工具的开发,以及其他需要简单GUI的场景。
另外,ImGUI已经写好了主流平台和图形API的后端,能够非常方便地在应用程序中集成imgui。
imgui和OpenGL
imgui使用说明
imgui代码仓库中最外层文件目录中包含的.h .cpp
文件即是imgui库所有主要的源文件。仓库中的backends中包含了主流平台和图形API的后端源代码,极大地方便了快速集成在自己的应用中。我们可以将imgui自身的源代码文件拷贝到自己的项目目录中。然后,选择合适的后端平台和图形API拷贝到自己的项目目录中。
imgui示例介绍
本章节以GLFW+OpenGL
为例,使用CMake和MSVC编译imgui对应的示例代码。
1. GLFW版本3.4_x64位
2. MSVC版本17.6.3(随VS2022安装)
工程组织结构如下所示:
.
|-- CMakeLists.txt
|-- libraries
| |-- glfw
| | |-- include
| | | `-- GLFW
| | | |-- glfw3.h
| | | `-- glfw3native.h
| | `-- lib-vc2022
| | |-- glfw3.dll
| | |-- glfw3.lib
| | |-- glfw3_mt.lib
| | `-- glfw3dll.lib
| `-- imgui
| |-- backends
| | |-- imgui_impl_glfw.cpp
| | |-- imgui_impl_glfw.h
| | |-- imgui_impl_opengl3.cpp
| | |-- imgui_impl_opengl3.h
| | `-- imgui_impl_opengl3_loader.h
| |-- imconfig.h
| |-- imgui.cpp
| |-- imgui.h
| |-- imgui_demo.cpp
| |-- imgui_draw.cpp
| |-- imgui_internal.h
| |-- imgui_tables.cpp
| |-- imgui_widgets.cpp
| |-- imstb_rectpack.h
| |-- imstb_textedit.h
| `-- imstb_truetype.h
`-- source
`-- main.cpp
其中,libraries
目录用于存放第三方的库,GLFW
下载了对应平台预编译好的库;source
中存放源码,main.cpp
为imgui仓库中examples/example_glfa_opengl3
目录中的示例程序源代码。
CMakeLists.txt
文件的内容如下:
cmake_minimum_required(VERSION 3.15)
project(imguiOpenGL)
set(CMAKE_CXX_STANDARD 17)
# 设置默认的构建类型
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the type of build (Debug or Release)" FORCE)
endif()
if(MSVC)
add_compile_options(/wd4819) # cmake禁止msvc 4819警告(和字符集相关的警告)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) # 设置可执行文件输出目录
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib) # 设置库文件输出目录
endif()
# 设置include路径
include_directories(
libraries/
libraries/imgui
libraries/imgui/backends
libraries/glfw/include
)
# 设置要连接的库
set(GLFW_LIB ${CMAKE_CURRENT_SOURCE_DIR}/libraries/glfw/lib-vc2022/glfw3.lib)
set(LINKED_LIBS opengl32 ${GLFW_LIB})
# 将imgui编译成静态库
set(IMGUI_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libraries/imgui)
set(IMGUI_SOURCE_FILES
${IMGUI_DIR}/imgui.cpp
${IMGUI_DIR}/imgui_demo.cpp
${IMGUI_DIR}/imgui_draw.cpp
${IMGUI_DIR}/imgui_tables.cpp
${IMGUI_DIR}/imgui_widgets.cpp
${IMGUI_DIR}/backends/imgui_impl_glfw.cpp
${IMGUI_DIR}/backends/imgui_impl_opengl3.cpp
)
add_library(imgui ${IMGUI_SOURCE_FILES})
# 创建可执行程序并连接到imgui GLFW OpenGL库
add_executable(${PROJECT_NAME} source/main.cpp)
if(MSVC)
set_property(TARGET ${PROJECT_NAME} PROPERTY LINK_FLAGS /NODEFAULTLIB:MSVCRT.lib) # 禁用默认的msvc运行时库
endif()
target_link_libraries(${PROJECT_NAME} ${LINKED_LIBS} imgui)
编译整个项目,运行最后生成的可执行文件imguiOpenGL,结果如下:
界面上显示的所有示例元素都可以在imgui_demo.cpp
文件中找到,仿照demo中的例子就可以编写自己的GUI程序了。根据main.cpp
中的例子和官方文档的说明,imgui的整个渲染流程可以简单总结为:
At initialization:
call ImGui::CreateContext()
call ImGui_ImplXXXX_Init() for each backend.
At the beginning of your frame:
call ImGui_ImplXXXX_NewFrame() for each backend.
call ImGui::NewFrame()
At the end of your frame:
call ImGui::Render()
call ImGui_ImplXXXX_RenderDrawData() for your Renderer backend.
At shutdown:
call ImGui_ImplXXXX_Shutdown() for each backend.
call ImGui::DestroyContext()
旋转的Box
学习OpenGL的过程中,可以将OpenGL的渲染过程加入到示例代码中,以创建一个可以通过GUI界面旋转Box的OpenGL程序为例:
1. 添加了glad库;
2. 添加了glm库;
3. 添加了stb_image库和纹理;
4. 添加了LearnOpenGL教程中封装的Shader类;
5. 使用了自己编写的生成立方体顶点数据的类,使用了小米推出的字体MiSans
;
工程的目录结构如下:
.
|-- CMakeLists.txt
|-- assets
| |-- fonts
| | `-- MiSans-Regular.ttf
| |-- shaders
| | |-- cube.frag
| | `-- cube.vert
| `-- textures
| `-- boxtexture.jpg
|-- libraries
| |-- glad
| | |-- include
| | | |-- KHR
| | | | `-- khrplatform.h
| | | `-- glad
| | | `-- glad.h
| | `-- src
| | `-- glad.c
| |-- glfw
| | |-- include
| | | `-- GLFW
| | | |-- glfw3.h
| | | `-- glfw3native.h
| | `-- lib-vc2022
| | |-- glfw3.dll
| | |-- glfw3.lib
| | |-- glfw3_mt.lib
| | `-- glfw3dll.lib
| |-- glm
| | |-- glm库的源代码
| |-- imgui
| | |-- backends
| | | |-- imgui_impl_glfw.cpp
| | | |-- imgui_impl_glfw.h
| | | |-- imgui_impl_opengl3.cpp
| | | |-- imgui_impl_opengl3.h
| | | `-- imgui_impl_opengl3_loader.h
| | |-- imconfig.h
| | |-- imgui.cpp
| | |-- imgui.h
| | |-- imgui_demo.cpp
| | |-- imgui_draw.cpp
| | |-- imgui_internal.h
| | |-- imgui_tables.cpp
| | |-- imgui_widgets.cpp
| | |-- imstb_rectpack.h
| | |-- imstb_textedit.h
| | `-- imstb_truetype.h
| `-- stb_image.h
`-- source
|-- inc
| |-- box.h
| |-- shader.h
| `-- vec.h
|-- main.cpp
`-- src
CMakeLists
文件也做了适当修改,添加了对应的头文件路径,库编译,资源文件拷贝等:
cmake_minimum_required(VERSION 3.15)
project(imguiOpenGL)
set(CMAKE_CXX_STANDARD 17)
# 设置默认的构建类型
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the type of build (Debug or Release)" FORCE)
endif()
if(MSVC)
add_compile_options(/wd4819) # cmake禁止msvc 4819警告(和字符集相关的警告)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) # 设置可执行文件输出目录
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib) # 设置库文件输出目录
endif()
# 设置include路径
include_directories(
libraries/
libraries/imgui
libraries/imgui/backends
libraries/glm
libraries/glad/include
libraries/glfw/include
source/inc
)
set(GLFW_LIB ${CMAKE_CURRENT_SOURCE_DIR}/libraries/glfw/lib-vc2022/glfw3.lib)
set(LINKED_LIBS opengl32 ${GLFW_LIB})
set(GLAD_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libraries/glad)
set(GLAD_SOURCE_FILES
${GLAD_DIR}/src/glad.c
)
add_library(glad ${GLAD_SOURCE_FILES})
set(IMGUI_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libraries/imgui)
set(IMGUI_SOURCE_FILES
${IMGUI_DIR}/imgui.cpp
${IMGUI_DIR}/imgui_demo.cpp
${IMGUI_DIR}/imgui_draw.cpp
${IMGUI_DIR}/imgui_tables.cpp
${IMGUI_DIR}/imgui_widgets.cpp
${IMGUI_DIR}/backends/imgui_impl_glfw.cpp
${IMGUI_DIR}/backends/imgui_impl_opengl3.cpp
)
add_library(imgui ${IMGUI_SOURCE_FILES})
# 拷贝资源文件,将assets文件夹中的资源文件拷贝到可执行文件所在目录
file(COPY assets/ DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
add_executable(${PROJECT_NAME} source/main.cpp)
if(MSVC)
set_property(TARGET ${PROJECT_NAME} PROPERTY LINK_FLAGS /NODEFAULTLIB:MSVCRT.lib) # 禁用默认的msvc运行时库
endif()
target_link_libraries(${PROJECT_NAME} ${LINKED_LIBS} imgui glad)
修改后的main.cpp
文件如下:
#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"
#include <stdio.h>
#include <iostream>
#define GL_SILENCE_DEPRECATION
#if defined(IMGUI_IMPL_OPENGL_ES2)
#include <GLES2/gl2.h>
#endif
#include <glad/glad.h>
#include <GLFW/glfw3.h> // Will drag system OpenGL headers
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include "box.h"
#include "shader.h"
// [Win32] Our example includes a copy of glfw3.lib pre-compiled with VS2010 to maximize ease of testing and compatibility with old VS compilers.
// To link with VS2010-era libraries, VS2015+ requires linking with legacy_stdio_definitions.lib, which we do using this pragma.
// Your own project should not be affected, as you are likely to link with a newer binary of GLFW that is adequate for your version of Visual Studio.
#if defined(_MSC_VER) && (_MSC_VER >= 1900) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS)
#pragma comment(lib, "legacy_stdio_definitions")
#endif
// This example can also compile and run with Emscripten! See 'Makefile.emscripten' for details.
#ifdef __EMSCRIPTEN__
#include "../libs/emscripten/emscripten_mainloop_stub.h"
#endif
static void glfw_error_callback(int error, const char* description)
{
fprintf(stderr, "GLFW Error %d: %s\n", error, description);
}
unsigned int loadTexture(char const *path);
// Main code
int main(int, char**)
{
glfwSetErrorCallback(glfw_error_callback);
if (!glfwInit())
return 1;
const char* glsl_version = "#version 130";
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // 3.0+ only
// Create window with graphics context
GLFWwindow* window = glfwCreateWindow(1280, 720, "Dear ImGui GLFW+OpenGL3 example", nullptr, nullptr);
if (window == nullptr)
return 1;
glfwMakeContextCurrent(window);
glfwSwapInterval(1); // Enable vsync
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO(); (void)io;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
// Setup Dear ImGui style
ImGui::StyleColorsDark();
//ImGui::StyleColorsLight();
// Setup Platform/Renderer backends
ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init(glsl_version);
/******************** -- Load OPENGL Functions-- ********************/
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
/******************** -- Load Fonts-- ********************/
io.Fonts->AddFontFromFileTTF("../fonts/MiSans-Regular.ttf", 18.0f);
/******************** -- VAO and VBO-- ********************/
Box box;
auto vertices = box.vertices;
unsigned int VBO, VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), vertices.data(), GL_STATIC_DRAW);
// position attribute
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0);
glEnableVertexAttribArray(0);
// texture coordinate attribute
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)(2 * sizeof(Vec3)));
glEnableVertexAttribArray(2);
/******************** -- Texture -- ********************/
unsigned int texture = loadTexture("../textures/boxtexture.jpg");
/******************** -- Shader-- ********************/
Shader shaderProgram("../shaders/cube.vert", "../shaders/cube.frag");
glEnable(GL_DEPTH_TEST);
// Our state
ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);
// Main loop
#ifdef __EMSCRIPTEN__
// For an Emscripten build we are disabling file-system access, so let's not attempt to do a fopen() of the imgui.ini file.
// You may manually call LoadIniSettingsFromMemory() to load settings from your own storage.
io.IniFilename = nullptr;
EMSCRIPTEN_MAINLOOP_BEGIN
#else
while (!glfwWindowShouldClose(window))
#endif
{
// Poll and handle events (inputs, window resize, etc.)
// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data.
// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data.
// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
glfwPollEvents();
// Start the Dear ImGui frame
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
static float xAngle = 0.0f;
static float yAngle = 0.0f;
static float zAngle = 0.0f;
static float scale = 1.0f;
{
ImGui::Begin("Rotate Cube", 0, ImGuiWindowFlags_AlwaysAutoResize);
ImGui::ColorEdit3("clear Color", (float*)&clear_color);
ImGui::SliderFloat("X-Axis Angle", &xAngle, 0.0f, 360.0f);
ImGui::SliderFloat("Y-Axis Angle", &yAngle, 0.0f, 360.0f);
ImGui::SliderFloat("Z-Axis Angle", &zAngle, 0.0f, 360.0f);
ImGui::SliderFloat("Model Scale", &scale, 0.1f, 5.0f);
ImGui::End();
}
int display_w, display_h;
glfwGetFramebufferSize(window, &display_w, &display_h);
glViewport(0, 0, display_w, display_h);
glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
shaderProgram.use();
glm::mat4 projection = glm::perspective(glm::radians(45.0f), (float)display_w / (float)display_h, 0.1f, 100.0f);
glm::mat4 view = glm::lookAt(glm::vec3(0.0f, 0.0f, 5.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
glm::mat4 model = glm::mat4(1.0f);
model = glm::scale(model, glm::vec3(scale));
model = glm::rotate(model, glm::radians(xAngle), glm::vec3(1.0f, 0.0f, 0.0f));
model = glm::rotate(model, glm::radians(yAngle), glm::vec3(0.0f, 1.0f, 0.0f));
model = glm::rotate(model, glm::radians(zAngle), glm::vec3(0.0f, 0.0f, 1.0f));
shaderProgram.setMat4("projection", projection);
shaderProgram.setMat4("view", view);
shaderProgram.setMat4("model", model);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
// Rendering
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
glfwSwapBuffers(window);
}
#ifdef __EMSCRIPTEN__
EMSCRIPTEN_MAINLOOP_END;
#endif
// Cleanup
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
glfwDestroyWindow(window);
glfwTerminate();
return 0;
}
unsigned int loadTexture(char const *path)
{
unsigned int textureID;
glGenTextures(1, &textureID);
int width, height, nrComponents;
unsigned char *data = stbi_load(path, &width, &height, &nrComponents, 0);
if (data)
{
GLenum format;
if (nrComponents == 1)
format = GL_RED;
else if (nrComponents == 3)
format = GL_RGB;
else if (nrComponents == 4)
format = GL_RGBA;
glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
stbi_image_free(data);
}
else
{
stbi_image_free(data);
}
return textureID;
}
其中,顶点着色器和片段着色器代码如下:
# vertex shader
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 2) in vec2 aTexCoord;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
out vec2 TexCoord;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
TexCoord = aTexCoord;
}
# fragment shader
#version 330 core
out vec4 FragColor;
in vec2 TexCoord;
uniform sampler2D ourTexture;
void main()
{
FragColor = texture(ourTexture, TexCoord);
}
UI部分的代码如下(更多UI元素可以参考imgui_demo.cpp
,官方仓库的使用指导也推荐了一些热门的插件):
static float xAngle = 0.0f;
static float yAngle = 0.0f;
static float zAngle = 0.0f;
static float scale = 1.0f;
{
ImGui::Begin("Rotate Cube", 0, ImGuiWindowFlags_AlwaysAutoResize);
ImGui::ColorEdit3("clear Color", (float*)&clear_color);
ImGui::SliderFloat("X-Axis Angle", &xAngle, 0.0f, 360.0f);
ImGui::SliderFloat("Y-Axis Angle", &yAngle, 0.0f, 360.0f);
ImGui::SliderFloat("Z-Axis Angle", &zAngle, 0.0f, 360.0f);
ImGui::SliderFloat("Model Scale", &scale, 0.1f, 5.0f);
ImGui::End();
}
最终的效果如图所示:
Comments | NOTHING