You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Vulkan的ImGui弹窗背景模糊Shader实现求助

基于ImGui GLFW Vulkan实现弹窗背景模糊的解决方案

1. 适配Vulkan离屏渲染资源

基于现有example_glfw_vulkan框架,新增两个与交换链同尺寸的离屏帧缓冲(分别用于水平/垂直模糊),配套创建ImageView和兼容的RenderPass:

  • 帧缓冲颜色附件采用VK_FORMAT_R8G8B8A8_UNORM,与ImGui渲染格式一致
  • RenderPass配置:单个颜色附件,加载操作为VK_ATTACHMENT_LOAD_OP_LOAD(用于读取上一步渲染结果),存储操作为VK_ATTACHMENT_STORE_OP_STORE

2. 编译加载模糊Shader

将你编写的水平/垂直模糊Shader编译为SPIR-V字节码,在初始化阶段创建Vulkan ShaderModule:

  • 水平模糊Shader示例(可替换为你的实现):
#version 450
layout(set = 0, binding = 0) uniform sampler2D src_tex;
layout(set = 0, binding = 1) uniform BlurData { float radius; vec2 texel_size; };
layout(location = 0) in vec2 uv;
layout(location = 0) out vec4 out_color;

void main() {
    vec4 total = vec4(0.0);
    float weight_sum = 0.0;
    for (int i = -int(radius); i <= int(radius); i++) {
        float w = exp(-float(i*i)/(2.0*radius*radius));
        total += texture(src_tex, uv + vec2(float(i)*texel_size.x, 0.0)) * w;
        weight_sum += w;
    }
    out_color = total / weight_sum;
}
  • 垂直模糊Shader仅需将采样方向改为Y轴,其余逻辑一致
  • 用vkCreateShaderModule加载SPIR-V字节码,分别创建水平/垂直模糊的ShaderModule

3. 创建模糊专用Pipeline

基于ImGui的管线基础,构建两个模糊Pipeline:

  • 管线布局包含两个绑定:sampler2D(输入纹理)、uniform buffer(模糊半径+纹理像素尺寸)
  • 关闭深度测试,开启混合(与ImGui渲染状态匹配),顶点输入采用全屏三角形(仅需3个顶点,无需顶点缓冲)

4. 实现ApplyBlurToPopupBackground函数

该函数需在弹窗绘制前执行,完成两次模糊渲染,并将结果提供给ImGui绘制:

void ApplyBlurToPopupBackground(VkCommandBuffer cmd_buf, VkImage swap_img, VkImageView swap_img_view, float blur_radius, const VkExtent2D& swap_extent) {
    // 图像布局转换:交换链图像转为可读状态
    VkImageMemoryBarrier swap_barrier = {};
    swap_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
    swap_barrier.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
    swap_barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
    swap_barrier.image = swap_img;
    swap_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    swap_barrier.subresourceRange.levelCount = 1;
    swap_barrier.subresourceRange.layerCount = 1;
    swap_barrier.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
    swap_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
    vkCmdPipelineBarrier(cmd_buf, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &swap_barrier);

    // 执行水平模糊:将交换链内容渲染到水平缓冲
    VkRenderPassBeginInfo rp_info = {};
    rp_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
    rp_info.renderPass = g_BlurRenderPass;
    rp_info.framebuffer = g_BlurFramebufferHorizontal;
    rp_info.renderArea.extent = swap_extent;
    VkClearValue clear_val = {.color = {0.0f, 0.0f, 0.0f, 0.0f}};
    rp_info.clearValueCount = 1;
    rp_info.pClearValues = &clear_val;

    vkCmdBeginRenderPass(cmd_buf, &rp_info, VK_SUBPASS_CONTENTS_INLINE);
    vkCmdBindPipeline(cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, g_BlurPipelineHorizontal);
    
    // 更新Uniform:模糊半径、像素尺寸
    BlurData ubo = {blur_radius, {1.0f/swap_extent.width, 1.0f/swap_extent.height}};
    UpdateUniformBuffer(cmd_buf, g_BlurUBO, &ubo);
    
    // 绑定交换链纹理的描述符集
    vkCmdBindDescriptorSets(cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, g_BlurPipelineLayout, 0, 1, &g_BlurDescSetSwapchain, 0, nullptr);
    // 绘制全屏三角形
    vkCmdDraw(cmd_buf, 3, 1, 0, 0);
    vkCmdEndRenderPass(cmd_buf);

    // 执行垂直模糊:将水平缓冲结果渲染到垂直缓冲
    rp_info.framebuffer = g_BlurFramebufferVertical;
    vkCmdBeginRenderPass(cmd_buf, &rp_info, VK_SUBPASS_CONTENTS_INLINE);
    vkCmdBindPipeline(cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, g_BlurPipelineVertical);
    // 绑定水平缓冲纹理的描述符集
    vkCmdBindDescriptorSets(cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, g_BlurPipelineLayout, 0, 1, &g_BlurDescSetHorizontal, 0, nullptr);
    vkCmdDraw(cmd_buf, 3, 1, 0, 0);
    vkCmdEndRenderPass(cmd_buf);

    // 垂直缓冲转为可读状态,供ImGui采样
    VkImageMemoryBarrier blur_barrier = {};
    blur_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
    blur_barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
    blur_barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
    blur_barrier.image = g_BlurImageVertical;
    blur_barrier.subresourceRange = swap_barrier.subresourceRange;
    blur_barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
    blur_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
    vkCmdPipelineBarrier(cmd_buf, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &blur_barrier);
}

弹窗绘制时使用模糊背景

在弹窗Begin之后,用ImGui的DrawList绘制覆盖弹窗区域的模糊纹理:

if (ImGui::BeginPopup("MyPopup")) {
    ImVec2 pos = ImGui::GetWindowPos();
    ImVec2 size = ImGui::GetWindowSize();
    ImDrawList* draw_list = ImGui::GetWindowDrawList();
    // 提前将垂直模糊缓冲的ImageView注册为ImGui纹理ID
    draw_list->AddImage(g_BlurTextureID, pos, ImVec2(pos.x+size.x, pos.y+size.y), ImVec2(0,0), ImVec2(1,1), IM_COL32(255,255,255,220));
    
    // 弹窗内容
    ImGui::Text("Blurred background popup");
    ImGui::EndPopup();
}

5. 核心注意事项

  • 资源同步:必须用vkCmdPipelineBarrier处理图像布局转换,确保Shader能正确采样目标图像
  • ImGui纹理注册:通过ImGui_ImplVulkan_AddTexture将模糊缓冲的ImageView包装为ImGui可识别的ImTextureID
  • 性能优化:可缩小模糊缓冲分辨率(如1/2尺寸)后再放大绘制,或仅模糊弹窗所在区域,减少计算开销

内容的提问来源于stack exchange,提问作者Seleran

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 16:40:56