基于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
相关产品推荐
相关产品推荐

