1. 什么是 Dear ImGui
Dear ImGui 是一个 C++ 即时模式(Immediate Mode)GUI 库 ,由 Omar Cornut 于 2014 年创建,目前托管在 GitHub,累计 64k+ Stars。它不依赖任何特定图形后端,通过 Backend + Renderer 的抽象层接入 OpenGL、DirectX、Vulkan、Metal 等主流图形 API。
核心定位:面向工具与调试的轻量级 GUI 框架,而非面向最终用户的精美 UI 框架。
与其他 GUI 库的本质区别
| 维度 | Dear ImGui | Qt / WPF / wxWidgets |
|---|---|---|
| 模式 | 即时模式(Immediate Mode) | 保留模式(Retained Mode) |
| 状态管理 | 每一帧重新生成 UI 代码 | 状态持久化在控件树中 |
| 布局 | 声明式布局,自动换行/对齐 | 布局管理器 + 信号/槽 |
| 控件句柄 | 无,代码即声明 | 每个控件有对象句柄 |
| 学习曲线 | 低,100 行可出完整界面 | 高,需学习信号槽/布局/继承体系 |
| 二进制体积 | ~500KB(静态库) | 数十 MB |
| 启动时间 | 毫秒级 | 秒级 |
2. 核心设计哲学:即时模式
2.1 什么是即时模式
传统 GUI 框架(Qt、Windows Forms)采用保留模式:创建一个按钮后,按钮对象在内存中持久存在,直到显式销毁。框架维护一个完整的控件树,事件由框架分发给控件。
ImGui 采用即时模式 :每一帧从头到尾重新声明 UI。没有持久化的控件对象,没有事件循环分发。代码像这样:
cpp
// 每一帧都执行
if (ImGui::Button("Click Me")) {
// 只有这一帧按钮被点击时才进入
printf("Button clicked!\n");
}
按钮在内存中不存在------它只是由 ImGui::Button 函数在当前帧渲染出的一个矩形区域。当这一帧结束时,这个"按钮"消失。下一帧再次调用 ImGui::Button 时,ImGui 通过内部状态(鼠标位置、上次点击状态等)判断是否被点击。
2.2 内部状态流
2.3 底层工作原理
ImGui 的核心数据结构是 ID Stack 和 DrawList:
- ID Stack:每个控件有一个唯一 ID(由文本/地址/隐式计数器合成),用于关联帧间状态(如输入框焦点、折叠状态)。
- DrawList:ImGui::Render() 将整帧的 UI 声明转换为一个 ImDrawData 结构体,包含顶点缓冲(ImDrawVert)和索引缓冲,提交给图形 API 渲染。ImGui 不负责渲染,只负责生成三角形网格。
抗锯齿原理:ImGui 的圆角矩形、圆形等使用 GPU 渲染,通过顶点着色器实现抗锯齿,所有控件最终都是三角形网格。
3. 使用优点
3.1 开发效率极高
- 代码即 UI:不需要拖拽控件、不需要 XML/QML 布局文件,所有 UI 在 C++ 代码中一行行声明。
- 零样板代码:不需要创建窗口类、注册消息回调、维护析构顺序。
- 热重载友好:修改代码后重新编译,UI 结构自动跟随变化,无需手动同步状态文件。
3.2 跨平台与后端无关
- 官方提供 DX9/10/11/12、OpenGL 2/3、Vulkan、Metal 的 Renderer 实现。
- 官方提供 GLFW、SDL2、Win32、GLUT、Allegro 5 的 Platform 后端。
- 单个 .h + .cpp 即可嵌入现有项目,零外部依赖。
3.3 可嵌入任何现有渲染管线
cpp
// 在你的渲染循环中嵌入 ImGui
while (running) {
glfwPollEvents();
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
// 你的 UI 声明
my_app_gui();
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
// 你的渲染
glfwSwapBuffers(window);
}
3.4 调试工具首选
- 嵌入到游戏引擎、3D 建模工具、性能分析器中,几行代码即可弹出调试面板。
- 游戏内控制台、实体属性编辑器、材质预览面板等场景,ImGui 是事实标准。
3.5 极致轻量
- 编译后静态库约 500KB。
- 无运行时依赖,无动态链接。
- 启动时间 < 10ms。
- 内存占用:~1MB 基线 + 按 UI 复杂度线性增长。
4. 使用场景
4.1 游戏引擎内嵌工具(最核心场景)
Unreal Engine、Unity、Godot 等引擎的编辑器大量使用 ImGui 或其变体:
- 场景大纲视图(Entity List)
- Inspector 属性编辑器
- 材质/贴图预览面板
- 性能 HUD(FPS、Draw Call 统计)
- 地形编辑器工具面板
4.2 性能分析器与 Profiler
- 实时显示 CPU/GPU 帧时间曲线(ImGui::PlotLines / PlotHistogram)
- 内存分配热点图
- 网络包流量监控
- 自定义性能计数器仪表盘
4.3 3D 建模与 DCC 工具
- Blender 插件界面
- 节点编辑器(ImGui 提供 Node Editor 示例代码)
- 曲线编辑器(Bezier 手柄交互)
- 自定义导入导出面板
4.4 嵌入式系统调试
- 通过串口帧缓冲,在嵌入式设备屏幕上渲染 ImGui 调试面板
- 配合 SDL 或 Framebuffer 后端,在 Linux 嵌入式设备上运行
4.5 原型验证与内部工具
- 快速搭建配置编辑器
- 数据集标注工具
- 日志浏览器
- A/B 测试结果可视化面板
不适合的场景
- 面向最终用户的精美 UI(如办公软件、电商界面)
- 需要复杂动画与过渡效果的应用
- 对无障碍/国际化/HiDPI 有严格要求的场景
- 移动端原生 App(虽可运行,但触摸交互不如原生流畅)
5. 快速上手:从零搭建 ImGui 应用
5.1 集成方式
方式一:官方 Bundle(推荐)
bash
git clone https://github.com/ocornut/imgui.git --branch v1.91.6
cd imgui
# 复制 backend 文件
# 复制到你的项目,加入 cmake 或直接编译
方式二:CMake FetchContent
FetchContent_Declare(
imgui
GIT_REPOSITORY https://github.com/ocornut/imgui.git
GIT_TAG v1.91.6
)
FetchContent_MakeAvailable(imgui)
target_link_libraries(myapp PRIVATE imgui)
5.2 完整示例(GLFW + OpenGL3)
cpp
#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"
#include <GLFW/glfw3.h>
int main() {
// 1. 初始化 GLFW
if (!glfwInit()) return -1;
GLFWwindow* window = glfwCreateWindow(1280, 720, "Dear ImGui Demo", nullptr, nullptr);
glfwMakeContextCurrent(window);
glfwSwapInterval(1); // 启用垂直同步
// 2. 初始化 ImGui
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // 键盘导航
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // 启用停靠
// 3. 设置 Platform + Renderer 后端
ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init("#version 130");
// 4. 主循环
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
// -- 你的 UI 代码 --
ImGui::ShowDemoWindow(); // 官方 Demo 窗口
// 自定义窗口
ImGui::Begin("Hello, ImGui!");
ImGui::Text("This is a text label.");
ImGui::Button("Click Me");
static float f = 0.0f;
ImGui::SliderFloat("Float", &f, 0.0f, 1.0f);
ImGui::End();
// -- 渲染 --
ImGui::Render();
int display_w, display_h;
glfwGetFramebufferSize(window, &display_w, &display_h);
glViewport(0, 0, display_w, display_h);
glClearColor(0.45f, 0.55f, 0.60f, 1.00f);
glClear(GL_COLOR_BUFFER_BIT);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
glfwSwapBuffers(window);
}
// 5. 清理
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
glfwDestroyWindow(window);
glfwTerminate();
return 0;
}
5.3 CMakeLists.txt 完整配置
cmake_minimum_required(VERSION 3.16)
project(ImGuiDemo)
set(CMAKE_CXX_STANDARD 17)
# 源码直接编译
add_executable(imgui_demo
main.cpp
imgui/imgui.cpp
imgui/imgui_draw.cpp
imgui/imgui_tables.cpp
imgui/imgui_widgets.cpp
imgui/backends/imgui_impl_glfw.cpp
imgui/backends/imgui_impl_opengl3.cpp
)
target_include_directories(imgui_demo PRIVATE imgui imgui/backends)
target_link_libraries(imgui_demo PRIVATE glfw3 opengl32)
6. 核心 API 详解
6.1 窗口系统
cpp
// 基本窗口
ImGui::Begin("窗口标题", &open, flags);
ImGui::Text("内容");
ImGui::End();
// flags 常用组合
ImGuiWindowFlags_NoMove // 不可移动
ImGuiWindowFlags_NoResize // 不可调整大小
ImGuiWindowFlags_NoCollapse // 无折叠按钮
ImGuiWindowFlags_NoTitleBar // 无标题栏
ImGuiWindowFlags_AlwaysAutoResize // 自动适配内容
ImGuiWindowFlags_NoScrollbar // 无滚动条
6.2 常用控件
cpp
// 文本
ImGui::Text("普通文本");
ImGui::TextColored(ImVec4(1,0,0,1), "红色文本");
ImGui::BulletText("项目符号文本");
// 按钮
ImGui::Button("普通按钮");
ImGui::SmallButton("小按钮");
ImGui::ArrowButton("arrow", ImGuiDir_Right);
bool clicked = ImGui::Button("带返回值按钮");
if (clicked) { /* 处理点击 */ }
// 输入
static char input[128] = "";
ImGui::InputText("标签", input, sizeof(input));
ImGui::InputInt("整数", &i);
ImGui::InputFloat("浮点数", &f, 0.1f, 1.0f, "%.3f");
// 滑块
static float v = 0.5f;
ImGui::SliderFloat("滑块", &v, 0.0f, 1.0f);
ImGui::SliderInt("整数滑块", &iv, 0, 100);
ImGui::SliderAngle("角度滑块", &angle);
ImGui::VSliderFloat("垂直滑块", ImVec2(18, 160), &vv, 0, 1);
// 选择框
static bool checked = false;
ImGui::Checkbox("复选框", &checked);
ImGui::RadioButton("单选A", &selected, 0);
ImGui::RadioButton("单选B", &selected, 1);
// 组合框
static int item = 0;
const char* items[] = {"选项A", "选项B", "选项C"};
ImGui::Combo("下拉框", &item, items, IM_ARRAYSIZE(items));
// 颜色
static ImVec4 color = ImVec4(1,1,1,1);
ImGui::ColorEdit3("RGB颜色", (float*)&color);
ImGui::ColorPicker4("拾色器", (float*)&color);
// 进度条
static float progress = 0.0f;
ImGui::ProgressBar(progress, ImVec2(-1, 0), "进度");
6.3 布局控制
cpp
// 分组
ImGui::BeginGroup();
ImGui::Button("A");
ImGui::Button("B");
ImGui::EndGroup();
// 间距
ImGui::Spacing();
ImGui::Separator(); // 横线分隔
ImGui::SameLine(); // 放在同一行
ImGui::Indent(20.0f); // 缩进
ImGui::Unindent(20.0f);
// 可折叠区域
if (ImGui::CollapsingHeader("高级设置")) {
ImGui::Text("折叠内容");
}
// 树节点
if (ImGui::TreeNode("节点1")) {
ImGui::Text("子内容");
ImGui::TreePop();
}
6.4 表格
cpp
if (ImGui::BeginTable("table", 3, ImGuiTableFlags_Borders)) {
ImGui::TableSetupColumn("名称");
ImGui::TableSetupColumn("类型");
ImGui::TableSetupColumn("值");
ImGui::TableHeadersRow();
for (int row = 0; row < 10; row++) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::Text("item_%d", row);
ImGui::TableSetColumnIndex(1);
ImGui::Text("float");
ImGui::TableSetColumnIndex(2);
ImGui::Text("%.2f", row * 0.5f);
}
ImGui::EndTable();
}
6.5 绘图 API(ImDrawList)
cpp
ImDrawList* draw = ImGui::GetWindowDrawList();
ImVec2 p = ImGui::GetCursorScreenPos();
// 画线
draw->AddLine(p, ImVec2(p.x+100, p.y), IM_COL32(255,0,0,255), 2.0f);
// 画矩形
draw->AddRect(ImVec2(p.x, p.y+20), ImVec2(p.x+100, p.y+60),
IM_COL32(0,255,0,255), 5.0f);
// 画圆
draw->AddCircle(ImVec2(p.x+50, p.y+100), 30.0f,
IM_COL32(0,0,255,255), 32, 2.0f);
// 画文本
draw->AddText(ImVec2(p.x, p.y+150), IM_COL32_WHITE, "Hello DrawList!");
// 画图片
draw->AddImage((ImTextureID)texture_id,
ImVec2(p.x, p.y), ImVec2(p.x+100, p.y+100));
7. Docking + Viewport 多窗口系统
7.1 启用 Docking
cpp
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
启停靠后,窗口可以拖拽到任意位置停靠、合并为 Tab 页、分栏排列。
7.2 Docking 布局初始化
cpp
ImGui::DockSpaceOverViewport(ImGui::GetMainViewport());
配合 ImGuiDockNodeFlags_PassthruCentralNode 可创建类似 Unity/Unreal 的编辑器布局。
7.3 Viewport 多窗口
cpp
io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
启用后,ImGui 窗口可以拖出主窗口成为独立 OS 窗口。需要 Platform 后端支持(GLFW/SDL 原生支持)。每个窗口有自己的 OS 标题栏、任务栏入口。
7.4 持久化布局
cpp
// 保存
ImGui::SaveIniSettingsToDisk("layout.ini");
// 加载
ImGui::LoadIniSettingsFromDisk("layout.ini");
布局文件为 INI 格式,包含窗口位置、大小、停靠状态、折叠状态等。
8. 自定义控件与绘图
8.1 自定义控件示例:颜色条
cpp
void ColorBar(const char* id, float* values, int count, float min_v, float max_v) {
ImGui::PushID(id);
ImDrawList* draw = ImGui::GetWindowDrawList();
ImVec2 pos = ImGui::GetCursorScreenPos();
float width = ImGui::GetContentRegionAvail().x;
float bar_height = 20.0f;
// 绘制背景
draw->AddRectFilled(pos, ImVec2(pos.x + width, pos.y + bar_height),
IM_COL32(30, 30, 30, 255));
// 绘制每个色块
float seg_w = width / count;
for (int i = 0; i < count; i++) {
float t = (values[i] - min_v) / (max_v - min_v);
ImU32 color = ImGui::GetColorU32(ImVec4(t, 0.2f, 1.0f - t, 1.0f));
draw->AddRectFilled(
ImVec2(pos.x + i * seg_w, pos.y),
ImVec2(pos.x + (i + 1) * seg_w, pos.y + bar_height),
color
);
}
ImGui::Dummy(ImVec2(width, bar_height));
ImGui::PopID();
}
8.2 自定义控件示例:可拖拽节点
cpp
struct DraggableNode {
ImVec2 pos;
ImVec2 size;
const char* label;
};
bool DraggableNode(DraggableNode& node) {
ImDrawList* draw = ImGui::GetWindowDrawList();
ImVec2 cursor = ImGui::GetCursorScreenPos();
// 绘制节点背景
ImRect rect(node.pos, ImVec2(node.pos.x + node.size.x, node.pos.y + node.size.y));
draw->AddRectFilled(rect.Min, rect.Max, IM_COL32(60, 60, 80, 255), 4.0f);
draw->AddRect(rect.Min, rect.Max, IM_COL32(100, 100, 200, 255), 4.0f);
// 绘制文本
draw->AddText(ImVec2(rect.Min.x + 5, rect.Min.y + 5), IM_COL32_WHITE, node.label);
// 拖拽交互
ImGui::SetCursorScreenPos(rect.Min);
ImGui::InvisibleButton(node.label, node.size);
if (ImGui::IsItemActive() && ImGui::IsMouseDragging(0)) {
node.pos.x += ImGui::GetIO().MouseDelta.x;
node.pos.y += ImGui::GetIO().MouseDelta.y;
return true;
}
return false;
}
9. 性能优化指南
9.1 核心性能指标
ImGui 的帧开销主要来自三个方面:
| 指标 | 含义 | 优化目标 |
|---|---|---|
| 顶点数 | 每帧生成的三角形顶点 | 控制 < 500k |
| Draw Call 数 | 每帧提交的绘制批次 | 控制 < 200 |
| 控件数 | 每帧声明的控件数量 | 控制 < 10k |
9.2 优化技巧
1. 使用 ImGuiListClipper 虚拟化列表
cpp
// 不要这样
for (int i = 0; i < 100000; i++) {
ImGui::Text("item %d", i);
}
// 要这样
ImGuiListClipper clipper;
clipper.Begin(100000);
while (clipper.Step()) {
for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) {
ImGui::Text("item %d", i);
}
}
2. 启用 ImDrawList 合并
cpp
io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange; // 减少字体纹理切换
3. 避免每帧重新创建大量字符串
cpp
// 差
for (int i = 0; i < items.size(); i++) {
ImGui::Text("Item %d: %s", i, items[i].c_str());
}
// 好:使用 PushID + 预格式化
static char buf[256];
for (int i = 0; i < items.size(); i++) {
ImGui::PushID(i);
snprintf(buf, sizeof(buf), "Item %d: %s", i, items[i].c_str());
ImGui::TextUnformatted(buf);
ImGui::PopID();
}
4. 使用 Begin 的 open 参数控制窗口创建
cpp
static bool show_advanced = false;
if (show_advanced) {
ImGui::Begin("Advanced", &show_advanced);
// 大量控件
ImGui::End();
}
5. 减少纹理切换
将多个小图标打包到一张图集中,使用 AddImage 时指定 UV 坐标。
9.3 性能分析工具
cpp
// 内置性能监控
ImGui::ShowMetricsWindow(); // 显示控件/顶点/DrawCall 统计
ImGui::ShowDebugLogWindow(); // 调试日志
ImGui::ShowStyleEditor(); // 样式编辑器
ImGui::ShowAboutWindow(); // 版本信息
10. 与 Qt/WPF/Retained Mode 对比
| 对比维度 | Dear ImGui | Qt 6 | WPF (.NET) |
|---|---|---|---|
| 模式 | 即时模式 | 保留模式 | 保留模式 |
| 布局方式 | 声明式(ImGui::Begin/End) | QML / 布局管理器 | XAML + 布局面板 |
| 事件模型 | 每帧轮询 | 信号/槽 + 事件循环 | 路由事件 + 命令 |
| 自定义控件难度 | 低(直接 DrawList 绘图) | 中(QPainter 或子类化) | 中(ControlTemplate) |
| 数据绑定 | 手动同步 | QProperty/QML 绑定 | INotifyPropertyChanged |
| 多语言支持 | 无内建,需手动处理 UTF-8 | 完备(Qt Linguist) | 完备(RESX + 本地化) |
| 无障碍支持 | 无 | 完备(QAccessible) | 完备(UIAutomation) |
| 动画系统 | 无(手动帧间 lerp) | QPropertyAnimation | Storyboard + DoubleAnimation |
| 外部依赖 | 0(仅后端依赖图形 API) | 大量(QtCore/QtGui/QtWidgets) | .NET Framework |
| 编译时间 | < 1s | 数分钟 | N/A |
| 二进制体积 | ~500KB | ~20MB+ | 数十 MB |
| 适用场景 | 工具/调试/游戏内嵌 | 桌面应用/嵌入式 | Windows 企业应用 |
11. FAQ 速查表
Q: ImGui 能做正式产品 UI 吗?
A : 可以,但不推荐。ImGui 的设计目标就是工具和调试,不是面向最终消费者的精美 UI。缺乏无障碍、国际化、主题切换、复杂动画等成熟框架的标准能力。但许多商业产品(Unity Editor、Unreal Editor、RenderDoc、Blender 插件)确实使用 ImGui 做内部工具面板。
Q: ImGui 支持中文吗?
A: 支持。ImGui 使用 UTF-8 编码,只需确保字体文件包含中文字形。在 ImFontAtlas 中加载中文字体即可:
cpp
ImGuiIO& io = ImGui::GetIO();
io.Fonts->AddFontFromFileTTF("C:/Windows/Fonts/msyh.ttc", 16.0f, nullptr,
io.Fonts->GetGlyphRangesChineseFull());
Q: ImGui 支持触摸交互吗?
A: 有限支持。可通过 io.MouseDown 和 io.MousePos 模拟触摸事件,但无原生多点触控、手势识别。嵌入式场景可通过 ImGuiNavInput_ 映射实现触摸导航。
Q: ImGui 线程安全吗?
A : 不。ImGui 的所有 API 必须在主渲染线程中调用。如果需要多线程,只能在一个线程中调用 ImGui,其他线程通过队列传递数据。
Q: ImGui 的字体纹理太大怎么办?
A: 可以使用 ImFontAtlas::AddFontFromFileTTF 的 size_pixels 参数控制字号,或使用 GlyphRanges 限制字符集(如只加载 ASCII 或常用中文字符)。
Q: ImGui 的 Draw Call 太多怎么办?
A: 1) 启用 ImGuiConfigFlags_NoMouseCursorChange 减少纹理切换;2) 使用 ImDrawList::ChannelsSplit 分层合并;3) 减少 Alpha 渐变(透明区域会打断批次合并)。
Q: 如何让 ImGui 窗口透明?
cpp
ImGui::SetNextWindowBgAlpha(0.5f); // 半透明背景
ImGui::Begin("Transparent Window");
// ...
ImGui::End();
Q: ImGui 的字体模糊怎么办?
A: 1) 确保 ImGui::GetIO().FontGlobalScale 匹配 DPI 缩放;2) 使用 ImGui::GetStyle().ScaleAllSizes(scale) 缩放控件尺寸;3) 在 HiDPI 屏幕上,让后端使用高分辨率帧缓冲,ImGui 自动适配。
总结 :Dear ImGui 的核心价值在于极低的集成成本和极高的迭代速度 。它不是 Qt 的替代品,而是在"需要快速出一个可交互的工具面板"这个场景下最趁手的工具。使用 ImGui 的正确姿势是:把它当作可嵌入的调试控制台,而不是桌面应用框架。