跳到主要内容

多subpass渲染

· 阅读需 4 分钟
苏明才
Vulkan驱动填坑狮

多 Subpass 的 Vulkan 渲染流程,非常适合用于 G-buffer + 光照、先画再后处理等场景。


🎯 示例目标

我们构建一个简单的 双 Subpass Render Pass

  1. Subpass 0:绘制场景,输出到颜色附件(Color Attachment 0)
  2. Subpass 1:对前面的输出做一个后处理,读取 Subpass 0 的颜色作为输入 attachment,再输出到屏幕(Color Attachment 1)

✅ 涉及的 Attachment(总共 2 个)

Attachment Index类型用途
0Color Attachment用于 Subpass 0 输出
1Color Attachment用于 Subpass 1 输出最终画面

🧩 步骤总览

  1. 定义两个附件(Attachment 0 和 1)
  2. 创建两个 Subpass:
    • Subpass 0:写入 Attachment 0
    • Subpass 1:读取 Attachment 0,写入 Attachment 1
  3. 设置 Subpass Dependency 同步两个阶段
  4. vkCmdNextSubpass() 过渡到下一个 subpass

📜 代码示例

下面是核心结构代码(C++ 伪代码 + Vulkan):

1. 定义 Attachments

cpp复制编辑VkAttachmentDescription attachments[2] = {};

// Attachment 0: Intermediate color output
attachments[0].format = VK_FORMAT_R8G8B8A8_UNORM;
attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
attachments[0].finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;

// Attachment 1: Final output (present)
attachments[1].format = swapchainImageFormat;
attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
attachments[1].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;

2. 定义 Subpasses

Subpass 0:写入 Attachment 0

cpp复制编辑VkAttachmentReference colorRef0 = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };

VkSubpassDescription subpass0 = {};
subpass0.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass0.colorAttachmentCount = 1;
subpass0.pColorAttachments = &colorRef0;

Subpass 1:读取 Attachment 0 作为输入,写入 Attachment 1

cpp复制编辑VkAttachmentReference inputRef = { 0, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL };
VkAttachmentReference colorRef1 = { 1, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };

VkSubpassDescription subpass1 = {};
subpass1.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass1.inputAttachmentCount = 1;
subpass1.pInputAttachments = &inputRef;
subpass1.colorAttachmentCount = 1;
subpass1.pColorAttachments = &colorRef1;

3. 添加 Subpass 依赖(同步两个 Subpass)

cpp复制编辑VkSubpassDependency dependency = {};
dependency.srcSubpass = 0;
dependency.dstSubpass = 1;
dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependency.dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
dependency.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
dependency.dstAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT;

4. 创建 Render Pass

cpp复制编辑VkSubpassDescription subpasses[] = { subpass0, subpass1 };

VkRenderPassCreateInfo renderPassInfo{};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderPassInfo.attachmentCount = 2;
renderPassInfo.pAttachments = attachments;
renderPassInfo.subpassCount = 2;
renderPassInfo.pSubpasses = subpasses;
renderPassInfo.dependencyCount = 1;
renderPassInfo.pDependencies = &dependency;

VkRenderPass renderPass;
vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass);

5. 在 Command Buffer 中使用

cpp复制编辑vkCmdBeginRenderPass(cmdBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);

// --- Subpass 0 ---
vkCmdBindPipeline(cmdBuffer, ..., pipeline0);
vkCmdDraw(cmdBuffer, ...);

// 切换到 Subpass 1
vkCmdNextSubpass(cmdBuffer, VK_SUBPASS_CONTENTS_INLINE);

// --- Subpass 1 ---
vkCmdBindPipeline(cmdBuffer, ..., pipeline1);
vkCmdDraw(cmdBuffer, ...);

vkCmdEndRenderPass(cmdBuffer);

📦 附加说明

名称说明
vkCmdNextSubpass()明确切换到下一个 Subpass
input attachment只允许当前 framebuffer 的 image 作只读使用
Pipeline layoutSubpass 1 的 fragment shader 中用 inputAttachment 读取 Subpass 0 的结果

🧠 Shader 中如何读取上一个 Subpass 的结果?

在 Subpass 1 的 Fragment Shader:

glsl复制编辑layout (input_attachment_index = 0, set = 0, binding = 0) uniform subpassInput inputColor;

void main() {
vec4 color = subpassLoad(inputColor);
// 做一些后处理操作
}

✅ 总结

步骤动作
定义多个 attachment描述 color / depth 输出
创建多个 subpass指定谁写、谁读
设置 dependency明确读写先后顺序
vkCmdNextSubpass切换阶段