多subpass渲染
· 阅读需 4 分钟
多 Subpass 的 Vulkan 渲染流程,非常适合用于 G-buffer + 光照、先画再后处理等场景。
🎯 示例目标
我们构建一个简单的 双 Subpass Render Pass:
- Subpass 0:绘制场景,输出到颜色附件(Color Attachment 0)
- Subpass 1:对前面的输出做一个后处理,读取 Subpass 0 的颜色作为输入 attachment,再输出到屏幕(Color Attachment 1)
✅ 涉及的 Attachment(总共 2 个)
Attachment Index | 类型 | 用途 |
---|---|---|
0 | Color Attachment | 用于 Subpass 0 输出 |
1 | Color Attachment | 用于 Subpass 1 输出最终画面 |
🧩 步骤总览
- 定义两个附件(Attachment 0 和 1)
- 创建两个 Subpass:
- Subpass 0:写入 Attachment 0
- Subpass 1:读取 Attachment 0,写入 Attachment 1
- 设置 Subpass Dependency 同步两个阶段
- 用
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 layout | Subpass 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 | 切换阶段 |