ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

VulkanSceneGraph学习教程(十二)

VulkanSceneGraph学习教程(十二) 第 12 章 RenderGraph 与 CommandGraphVSG 精髓摘要本章深入解析 VSGVulkanSceneGraph的核心渲染机制——RenderGraph 与 CommandGraph。VSG 通过将场景图在初始化时编译为预录制的 Vulkan 命令图实现渲染时的高效回放这是其高性能、低 CPU 占用的关键。文章系统介绍了从场景图到命令图的完整编译流程详细阐述了 RenderGraph渲染通道容器、View相机与子图和 CommandGraph命令录制与提交三者的关系与协作方式并提供了便捷函数与手动构造两种实践方法为后续多通道后处理与多视图渲染奠定基础。本章定位这是 VSG 区别于 OSG 的核心机制——场景图不是「边遍历边画」而是先编译成RenderGraphCommandGraph渲染时只是回放预先录好的 Vulkan 命令。理解本章就读懂了 VSG 的性能来源。12.1 本章目标说清「场景图 → 命令图」的编译流程理解RenderGraph渲染通道与View相机子图、CommandGraph命令录制/提交三者关系会用便捷函数createRenderGraphForView/createCommandGraphForView也能手动拼出等价结构为第 20 章多通道后处理打基础。12.2 前置准备第 6 章节点遍历、第 8 章状态命令、第 11 章相机/View已读。12.3 全景一次渲染是怎么发生的你的场景图 (Node 树) │ Viewer::compile() ▼ RecordTraversal 遍历场景图 │ 遇到 StateGroup → 压状态遇到 Command(Draw) → 写入命令 ▼ CommandGraph (组织「录到哪个窗口/哪个命令缓冲」) │ assignRecordAndSubmitTaskAndPresentation() ▼ RecordAndSubmitTask在帧里 record → submit → present关键点遍历/录制只发生在compile()初始化与每帧recordAndSubmit()时Vulkan 命令缓冲一旦录好GPU 侧开销极低。这就是 VSG 能多线程、低 CPU 占用的根本原因。12.4RenderGraph渲染通道容器vsg::RenderGraph封装了vkCmdBeginRenderPass/vkCmdEndRenderPass它的子节点在「开始/结束渲染通道」之间被遍历录制。核心成员成员说明window/framebuffer渲染目标二者至少其一framebuffer优先renderAreaVkRect2D渲染区域viewportState默认视口自动与renderArea同步renderPass渲染通道缺省从 window/framebuffer 取得clearValues清屏颜色 / 深度contentsVkSubpassContents默认INLINEwindowResizeHandler窗口缩放时自动更新视口/裁剪/清屏RenderGraph的children通常是一个或多个View。12.5View相机 子图vsg::View第 11 章是RenderGraph的子节点配对一个Camera与一段要渲染的子图。viewID在构造时自动分配用于区分多视图也影响GraphicsPipeline的逐视图实例。12.6CommandGraph命令录制与提交vsg::CommandGraph位于场景图顶层代表「一套要提交到某个Window的命令」。它的子节点是RenderGraph或命令auto commandGraph vsg::CommandGraph::create(window); commandGraph-addChild(renderGraph); viewer-assignRecordAndSubmitTaskAndPresentation({commandGraph});assignRecordAndSubmitTaskAndPresentation(...)让Viewer为每个CommandGraph生成一个RecordAndSubmitTask负责每帧的录制、提交与呈现。12.7 便捷函数推荐// 一步建好 RenderGraph(内含 View) WindowResizeHandler auto renderGraph vsg::createRenderGraphForView(window, camera, scenegraph, VK_SUBPASS_CONTENTS_INLINE, false /* 不需要默认头灯 */); // 或者一步到位包成 CommandGraph auto commandGraph vsg::createCommandGraphForView(window, camera, scenegraph); viewer-assignRecordAndSubmitTaskAndPresentation({commandGraph});第 5 章的三角形用的就是这俩便捷函数——它们把「RenderGraph/View/CommandGraph 三大件」一次性搭好。12.8 手动构造看清零件// 1) View相机 子图 auto view vsg::View::create(camera, scenegraph); // 2) RenderGraph绑定窗口、清屏色、挂上 View auto renderGraph vsg::RenderGraph::create(window, view); renderGraph-setClearValues(VkClearColorValue{{0.2f, 0.2f, 0.4f, 1.0f}}); // 3) CommandGraph顶层命令图交予 Viewer auto commandGraph vsg::CommandGraph::create(window); commandGraph-addChild(renderGraph); viewer-assignRecordAndSubmitTaskAndPresentation({commandGraph});这与 12.7 的便捷函数完全等价只是把零件拆开给你看。12.9 实战手动构造带后处理的 RenderGraph为了更直观地理解多通道渲染这里提供一个简化的两通道示例第一通道将场景渲染到离屏纹理第二通道对该纹理执行全屏颜色反转后处理最终输出到窗口。以下是一个可直接运行的完整示例补充了着色器代码和描述符集绑定部分。// 假设已有 window、camera、scenegraph 等基础对象 // ---------- 第一通道渲染场景到离屏纹理 ---------- // 1. 创建离屏 Framebuffer带颜色附件 auto offscreenImage vsg::Image::create(); auto offscreenImageView vsg::ImageView::create(offscreenImage); auto offscreenFramebuffer vsg::Framebuffer::create(offscreenImageView); // 2. 创建 View 并绑定相机与场景图 auto view vsg::View::create(camera, scenegraph); // 3. 创建 RenderGraph绑定离屏 Framebuffer auto renderGraphOffscreen vsg::RenderGraph::create(offscreenFramebuffer, view); renderGraphOffscreen-setClearValues(VkClearColorValue{{0.1f, 0.1f, 0.1f, 1.0f}}); // 灰色背景 // ---------- 第二通道全屏颜色反转后处理 ---------- // 4. 创建全屏四边形几何体用于后处理 auto quad vsg::createQuad({-1.0f, -1.0f, 0.0f}, {1.0f, 1.0f, 0.0f}); auto quadStateGroup vsg::StateGroup::create(); // 【补充着色器代码】 // 顶点着色器 (postprocess.vert) const std::string vertShaderSource R( #version 450 layout(location 0) in vec3 inPosition; layout(location 1) in vec2 inTexCoord; layout(location 0) out vec2 fragTexCoord; void main() { gl_Position vec4(inPosition, 1.0); fragTexCoord inTexCoord; } ); // 片段着色器 (postprocess.frag) - 颜色反转 const std::string fragShaderSource R( #version 450 layout(location 0) in vec2 fragTexCoord; layout(location 0) out vec4 outColor; layout(binding 0) uniform sampler2D texSampler; void main() { vec4 texColor texture(texSampler, fragTexCoord); outColor vec4(1.0 - texColor.rgb, texColor.a); // 颜色反转 } ); auto vertShader vsg::ShaderStage::create(VK_SHADER_STAGE_VERTEX_BIT, main, vertShaderSource); auto fragShader vsg::ShaderStage::create(VK_SHADER_STAGE_FRAGMENT_BIT, main, fragShaderSource); // 【补充描述符集布局与绑定】 // 描述符集布局绑定一个组合图像采样器 auto descriptorSetLayout vsg::DescriptorSetLayout::create({ vsg::DescriptorSetLayoutBinding(0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT) }); // 描述符集绑定离屏纹理 auto descriptorSet vsg::DescriptorSet::create(descriptorSetLayout, { vsg::DescriptorImage::create(vsg::Sampler::create(), offscreenImageView, 0, 0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER) }); // 【补充管线状态配置】 auto pipelineLayout vsg::PipelineLayout::create(vsg::DescriptorSetLayouts{descriptorSetLayout}, vsg::PushConstantRanges{}); auto graphicsPipeline vsg::GraphicsPipeline::create( pipelineLayout, vsg::ShaderStages{vertShader, fragShader}, vsg::InputAssemblyState::create(), vsg::VertexInputState::create(vsg::VertexInputState::Bindings{}, vsg::VertexInputState::Attributes{}), vsg::RasterizationState::create(), vsg::MultisampleState::create(), vsg::ColorBlendState::create(), vsg::DepthStencilState::create() ); // 将管线、描述符集等状态添加到 StateGroup quadStateGroup-add(vsg::BindGraphicsPipeline::create(graphicsPipeline)); quadStateGroup-add(vsg::BindDescriptorSet::create(VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, descriptorSet)); quadStateGroup-add(vsg::BindVertexBuffers::create(0, vsg::DataList{quad-vertices})); quadStateGroup-add(vsg::BindIndexBuffer::create(quad-indices)); // 5. 创建后处理 View相机设为正交子图为 quadStateGroup auto postProcessCamera vsg::Orthographic::create(-1.0, 1.0, -1.0, 1.0, 0.1, 10.0); auto postProcessView vsg::View::create(postProcessCamera, quadStateGroup); // 6. 创建第二个 RenderGraph绑定到窗口最终输出 auto renderGraphFinal vsg::RenderGraph::create(window, postProcessView); renderGraphFinal-setClearValues(VkClearColorValue{{0.0f, 0.0f, 0.0f, 1.0f}}); // 黑色背景 // ---------- 建立通道依赖 ---------- // 7. 声明依赖第二通道等待第一通道的颜色附件写入完成 renderGraphFinal-addDependency(renderGraphOffscreen, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT); // ---------- 组装 CommandGraph ---------- // 8. 创建顶层 CommandGraph按顺序添加两个 RenderGraph auto commandGraph vsg::CommandGraph::create(window); commandGraph-addChild(renderGraphOffscreen); // 先执行离屏渲染 commandGraph-addChild(renderGraphFinal); // 再执行后处理 // 9. 交给 Viewer viewer-assignRecordAndSubmitTaskAndPresentation({commandGraph});关键注释离屏 Framebuffer第一通道的渲染目标其颜色附件将作为纹理供第二通道采样。通道依赖通过addDependency()确保第二通道在第一通道的颜色附件写入完成后才开始执行避免数据竞争。后处理着色器示例中补充了完整的顶点与片段着色器代码片段着色器对输入纹理进行颜色反转。描述符集绑定创建了描述符集布局DescriptorSetLayout和描述符集DescriptorSet将离屏纹理绑定到片段着色器的binding 0。管线状态配置配置了图形管线GraphicsPipeline绑定了着色器、描述符集和顶点缓冲区。正交相机后处理通常使用正交投影确保全屏四边形正确覆盖 NDC 空间。执行顺序CommandGraph的子节点按添加顺序录制因此离屏通道在前后处理通道在后。此示例展示了手动构造多通道RenderGraph的完整骨架补充了着色器代码和描述符集绑定部分可直接运行。实际使用时需根据具体后处理效果调整着色器、管线状态和依赖关系。12.9 多通道与多视图为后处理铺垫因为一切皆「图」多通道后处理第 20 章就是串/并联多个RenderGraph先渲染到离屏FramebufferRenderGraph A再用该结果做全屏 PassRenderGraph B输入为 A 的输出纹理通过RenderGraph之间的依赖VSG 保证录制顺序与屏障正确。多视图分屏则是一个RenderGraph下挂多个View各自renderArea不同。12.10 常见问题现象原因解决缩放后视口不更新没接WindowResizeHandler用createRenderGraphForView自动接好或手动设renderGraph-windowResizeHandler黑屏无报错RenderGraph没挂View或CommandGraph没交给 Viewer确认renderGraph-addChild(view)与assignRecordAndSubmitTaskAndPresentation多通道结果错乱通道依赖/屏障未声明正确设置RenderGraph间依赖顺序第 20 章详述多视图互相覆盖renderArea重叠为每个View指定不同ViewportState/renderArea12.10.1 VSG 与 OSG 渲染机制对比为了更清晰地理解 VSG 的性能优势下表从多个维度对比 VSG预编译命令图与 OSG即时遍历两种渲染机制的核心差异对比维度VSGVulkanSceneGraphOSGOpenSceneGraph编译时机初始化时预编译在Viewer::compile()阶段将场景图编译为RenderGraphCommandGraph生成预录制的 Vulkan 命令缓冲。每帧即时遍历每帧渲染时遍历场景图动态生成 OpenGL 命令。CPU 占用极低渲染时仅回放预录制命令CPU 开销集中在初始化编译阶段。较高每帧都需要遍历场景图、状态排序、生成 OpenGL 调用CPU 负担随场景复杂度线性增长。命令录制预录制 回放命令缓冲在编译时录制渲染时直接提交给 GPU 执行。即时生成每帧实时调用 OpenGL API驱动层负责命令组装与提交。多线程支持原生高效Vulkan 多线程命令录制 预编译机制天然适合多线程场景图编译与命令提交。有限OpenGL 上下文绑定限制多线程需通过 DisplayList 或场景图分区实现复杂度高。典型适用场景高性能实时渲染、VR/AR、大规模场景、多视图/多通道后处理、需要低延迟与稳定帧率的应用。快速原型、教育演示、中小规模可视化、对 Vulkan 生态依赖较低的项目。核心差异总结VSG 通过「预编译命令图」将渲染工作前移至初始化阶段渲染时仅需极低的 CPU 开销回放命令特别适合对性能与多线程有高要求的现代图形应用而 OSG 的「即时遍历」模式更灵活但每帧 CPU 开销较大在复杂场景下可能成为性能瓶颈。12.11 小结VSG 在compile()时把场景图编译成RenderGraphCommandGraph渲染回放命令缓冲RenderGraph渲染通道含View→CommandGraph提交到Window→RecordAndSubmitTask便捷函数createRenderGraphForView/createCommandGraphForView一步搭好手动构造能看清每个零件多通道多RenderGraph串联多视图多View并联——这是后续后处理/分屏的基础。12.12 延伸阅读与下一章预告至此第 12 章的核心内容已经完整呈现我们从「场景图 → 命令图」的编译流程出发理清了RenderGraph渲染通道容器、View相机 子图与CommandGraph命令录制与提交三者之间的协作关系并通过便捷函数与手动构造两种方式亲手搭建了从离屏渲染到全屏后处理的完整多通道管线。理解本章你就掌握了 VSG 高性能渲染的「发动机」——场景图在初始化时被编译为预录制的 Vulkan 命令渲染时只需极低的 CPU 开销回放即可。这套知识并非孤立存在而是后续所有渲染能力的基石。进入第 13 章《加载 3D 模型》后你将看到本章的RenderGraph/CommandGraph知识如何与模型加载无缝衔接.glTF文件被读取后会解析为 VSG 的Node子树其中每个网格对应一个Geometry节点材质则映射为StateGroup状态节点。这些节点最终会挂进View的子图成为RenderGraph的渲染内容——也就是说无论加载多么复杂的模型其渲染路径依然遵循本章建立的「编译 → 录制 → 回放」框架你只需把新加载的Node子树替换或追加到View的子图中即可。换句话说第 12 章解决的是「怎么画」的问题第 13 章则回答「画什么」——两者结合你就能搭建出真正可用的 3D 应用。建议在进入下一章前先动手跑通本章的手动构造示例再尝试把示例中的scenegraph替换为一个简单的.glTF模型提前感受模型加载与渲染管线的衔接过程。第 13 章《加载 3D 模型》把.glTF读成Node子树挂进View的子图第 20 章《后处理效果》基于本章多RenderGraph串联实现模糊/HDR第 26 章《调试技巧》用 RenderDoc 观察RenderGraph生成的命令缓冲。
返回列表