Vulkan 通过 CMake 集成 Dear ImGUI

一、 目录与文件部署

从官网获取 IMGUI 代码库,在项目 extern 目录下新建 imgui 目录,将相关文件复制进去,构建出如下目录结构:

.
├── build
├── extern
│   ├── glfw
│   ├── glm
│   └── imgui
│       ├── backends
│       │   ├── imgui_impl_glfw.cpp
│       │   ├── imgui_impl_glfw.h
│       │   ├── imgui_impl_vulkan.cpp
│       │   └── imgui_impl_vulkan.h
│       ├── imconfig.h
│       ├── imgui_demo.cpp
│       ├── imgui_draw.cpp
│       ├── imgui_internal.h
│       ├── imgui_tables.cpp
│       ├── imgui_widgets.cpp
│       ├── imgui.cpp
│       ├── imgui.h
│       ├── imstb_rectpack.h
│       ├── imstb_textedit.h
│       ├── imstb_truetype.h
│       └── LICENSE.txt
└── stb

二、CMakeLists 配置 - 接入 ImGUI 依赖

(一)路径与文件设置

CMakeLists.txt 中,添加 ImGUI 相关配置:

# 设置外部库目录及各库路径
...
set(IMGUI_ROOT ${EXTERNAL_LIB_DIR}/imgui) 

# 递归收集源文件
file(GLOB_RECURSE SOURCE_FILES src/*.cpp src/*.c)

(二)构建与链接 ImGUI 库

# 构建 imgui 静态库,指定实现文件
add_library(imgui STATIC
    ${IMGUI_ROOT}/imgui.cpp
    ${IMGUI_ROOT}/imgui_demo.cpp
    ${IMGUI_ROOT}/imgui_draw.cpp
    ${IMGUI_ROOT}/imgui_widgets.cpp
    ${IMGUI_ROOT}/imgui_tables.cpp
    ${IMGUI_ROOT}/backends/imgui_impl_glfw.cpp
    ${IMGUI_ROOT}/backends/imgui_impl_vulkan.cpp
)

# 配置 imgui 头文件包含目录
target_include_directories(imgui PUBLIC
    ${IMGUI_ROOT}
    ${GLFW_ROOT}/include
    $<TARGET_PROPERTY:Vulkan::Vulkan,INTERFACE_INCLUDE_DIRECTORIES>
)

# 查找 Vulkan 库
find_package(Vulkan REQUIRED)

# 链接库到项目
target_link_libraries(${PROJECT_NAME} PRIVATE
    Vulkan::Vulkan
    imgui     
    ${GLFW_LIB}
)

三、主代码集成 - main.cpp 中 ImGUI 接入

(一)头文件引入

...
#include <imgui/imgui.h>
#include <imgui/backends/imgui_impl_glfw.h>
#include <imgui/backends/imgui_impl_vulkan.h>

#include "renderer/VkRenderer.h"
#include "scene/Camera.h"
#include "ObjModelLoader.h"
#include "GltfModelLoader.h"
#include "HelloRect.h"

(二)初始化与回调

GLFWwindow* window;
const uint32_t WIDTH = 1920;
const uint32_t HEIGHT = 1000;
Camera camera(glm::vec3(5.0f, 5.0f, 5.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f), 45.0f, 0.01f, 1000.0f);
VkContext vkcontext{};

// 帧缓冲大小变化回调
static void framebufferResizeCallback(GLFWwindow* window, int width, int height) {
    vkcontext.framebufferResized = true;
}

// 窗口初始化
void initWindow() {
    glfwInit();
    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
    window = glfwCreateWindow(WIDTH, HEIGHT, "Hello Vulkan", nullptr, nullptr);
    glfwSetFramebufferSizeCallback(window, framebufferResizeCallback);
}

(三)ImGUI 主题设置

// ImGUI 主题配置(含中文字体加载)
void setImguiTheme() {
    float baseFontSize = 16.0f;
    ImGuiIO& io = ImGui::GetIO();
    ImFont* font = io.Fonts->AddFontFromFileTTF
    (
        "assets/fonts/msyh.ttc",
        baseFontSize,
        nullptr,
        io.Fonts->GetGlyphRangesChineseFull()
    );
    IM_ASSERT(font != nullptr);
    ImGui::StyleColorsDark();
    ImGui::GetStyle().WindowRounding = 4.0f; 
}   

(四)主函数流程

int main() {
    initWindow();
    vkcontext.window = window;
    vkcontext.camera = &camera;

    // 加载模型并设置
    MeshInstance* objModel = ObjModelLoader::loadModel("assets/models/viking_room.obj", "assets/models/viking_room.png");
    objModel->modelMatrix = glm::rotate(glm::mat4(1.0f), glm::radians(45.0f), glm::vec3(0.0f, 0.0f, 1.0f));
    objModel->modelMatrix = glm::translate(glm::mat4(1.0f), glm::vec3(3.0f, 3.0f, 3.0f));
    vkcontext.meshInstances.push_back(objModel);

    // Vulkan 初始化
    if (!vkInit(&vkcontext)) {
        throw std::runtime_error("Vulkan 初始化失败!");
    }

    // ImGUI 上下文等初始化
    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGuiIO& io = ImGui::GetIO();
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;

    setImguiTheme();

    ImGui_ImplGlfw_InitForVulkan(window, true);
    ImGui_ImplVulkan_InitInfo init_info = {};
    // 填充 init_info 字段...
    ImGui_ImplVulkan_Init(&init_info);

    // 上传 ImGUI 字体(独立命令缓冲)
    VkCommandBufferAllocateInfo allocInfo{};
    // 配置 allocInfo...
    VkCommandBuffer font_cmd;
    if (vkAllocateCommandBuffers(vkcontext.device, &allocInfo, &font_cmd) != VK_SUCCESS) {
        throw std::runtime_error("无法为 ImGui 字体上传分配命令缓冲区!");
    }
    VkCommandBufferBeginInfo begin_info = {};
    begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
    begin_info.flags |= VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
    vkBeginCommandBuffer(font_cmd, &begin_info);
    ImGui_ImplVulkan_CreateFontsTexture();
    vkEndCommandBuffer(font_cmd);
    
    VkSubmitInfo submit_info = {};
    submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
    submit_info.commandBufferCount = 1;
    submit_info.pCommandBuffers = &font_cmd;
    vkQueueSubmit(vkcontext.graphicsQueue, 1, &submit_info, VK_NULL_HANDLE);
    vkDeviceWaitIdle(vkcontext.device);
    ImGui_ImplVulkan_DestroyFontsTexture();

    // 主循环
    try {
        while (!glfwWindowShouldClose(window)) {
            glfwPollEvents();
             // 1. ImGui 新帧
            ImGui_ImplVulkan_NewFrame();
            ImGui_ImplGlfw_NewFrame();
            ImGui::NewFrame();
            // 2. 构建UI
            ImGui::ShowDemoWindow();
            ImGui::Render();

            vkRender(&vkcontext, ImGui::GetDrawData());
        }
        // 资源清理
        vkDeviceWaitIdle(vkcontext.device);
        ImGui_ImplVulkan_Shutdown();
        ImGui_ImplGlfw_Shutdown();
        ImGui::DestroyContext();
        vkClean(&vkcontext);
        glfwDestroyWindow(window);
        glfwTerminate();

        for (MeshInstance* mesh: vkcontext.meshInstances) {
            delete mesh;
        }

    } catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

四、渲染器代码更新 - VkRenderer 调整

(一)函数声明修改(VkRenderer.h)

void vkRender(VkContext* vkcontext, ImDrawData* imguiDrawData); 

(二)描述符池与命令缓冲更新(VkRenderer.cpp)

1. 描述符池创建调整
// 创建描述符池,增大容量
{
    std::array<VkDescriptorPoolSize, 2> poolSizes{};
    size_t meshCount = ctx->meshInstances.size();
    poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
    poolSizes[0].descriptorCount = static_cast<uint32_t>(MAX_CONCURRENT_FRAMES * meshCount)+ 1;
    poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
    poolSizes[1].descriptorCount = static_cast<uint32_t>(MAX_CONCURRENT_FRAMES * meshCount) + 1;

    VkDescriptorPoolCreateInfo poolInfo{};
    poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
    poolInfo.poolSizeCount = static_cast<uint32_t>(poolSizes.size());
    poolInfo.pPoolSizes = poolSizes.data();
    poolInfo.maxSets = static_cast<uint32_t>(MAX_CONCURRENT_FRAMES * meshCount)+1;
    poolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;

    if (vkCreateDescriptorPool(ctx->device, &poolInfo, nullptr, &ctx->descriptorPool) != VK_SUCCESS) {
        throw std::runtime_error("创建描述符池失败!");
    }
}
2. 命令缓冲录制修改
void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex, ImDrawData* imguiDrawData, VkContext* ctx) {
    // 原有模型绘制循环...
    for (size_t i = 0; i < ctx->meshInstances.size(); ++i) {
        // 模型绘制逻辑
    }

    // 绘制 ImGUI 内容
    ImGui_ImplVulkan_RenderDrawData(imguiDrawData, commandBuffer);

    vkCmdEndRenderPass(commandBuffer);
    if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) {
        throw std::runtime_error("录制命令缓冲失败!");
    }
}

五、整合要点与总结

(一)核心流程

  1. Vulkan 基础准备:完成 VkInstanceVkDeviceSwapChain 等渲染器核心组件初始化,特别注意描述符池需预留足够空间给 ImGUI 与主渲染。
  2. ImGUI 初始化:通过 ImGui::CreateContext(); 创建上下文,配置 IO(如中文字体、交互支持),设置主题样式,再初始化 GLFW + Vulkan 后端。
  3. 字体上传:需用独立命令缓冲,上传后通过 vkDeviceWaitIdle 保证同步,避免渲染异常。
  4. 主循环集成:每帧调用 NewFrame、构建 UI(如 ShowDemoWindow )、Render,最后在 Vulkan 命令缓冲中调用 ImGui_ImplVulkan_RenderDrawData 绘制 UI。

(二)常见问题与解决

  • 描述符池报错:若出现 vkFreeDescriptorSets 报错,检查是否添加 VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT 标志;若报空间不足(如错误码 -1000069000 ),需增大 maxSetspoolSizes
  • 命令缓冲问题:字体上传必须用独立缓冲,否则易引发同步问题,导致渲染异常。
  • 样式定制:通过 ImGui::GetStyle() 调整圆角、配色,结合 ImGuiIO 加载自定义字体(如中文字体),灵活美化界面。

(三)关键总结

Vulkan 集成 ImGUI 需重点关注资源池管理(描述符池容量与标志)、命令缓冲同步(字体上传独立处理)、ImGUI 初始化流程。遇到验证层报错,优先排查描述符池配置;样式需求可通过 ImGUI 自身接口灵活实现,确保渲染流程与 UI 交互顺畅协同 。

在这里插入图片描述
当前代码分支:14_integrated_DearImGUI

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值