ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

VulkanSceneGraph学习教程(九)

2026/8/13 10:01:12 拓冰建站 浏览量
VulkanSceneGraph学习教程(九) 第 9 章 着色器与管线摘要本章系统讲解 Vulkan 图形管线的构建过程涵盖从底层手动创建到便捷路径的完整流程。首先介绍如何使用vsg::ShaderModule和vsg::ShaderStage准备着色器阶段然后详细说明vsg::PipelineLayout的组成及其与描述符集布局的关系。接着逐一解析GraphicsPipelineState的各个子类顶点输入、光栅化、混合、深度测试等及其对应 Vulkan 结构。最后通过vsg::GraphicsPipeline和vsg::BindGraphicsPipeline组装并绑定管线并回顾第 5 章介绍的便捷路径ShaderSetGraphicsPipelineConfigurator。文章还提供了常见问题排查表帮助读者避免着色器编译、管线创建和绑定过程中的典型错误。本章定位把第 4 章的着色器与第 7 章的顶点布局真正组装成一条 Vulkan 图形管线。先讲「底层手动创建」看清每个零件再回顾第 5 章的「便捷路径」。9.1 本章目标用vsg::ShaderModule/vsg::ShaderStage准备着色器阶段理解vsg::PipelineLayout与vsg::GraphicsPipeline的组成认识GraphicsPipelineState各子类顶点输入、光栅化、混合、深度…会「手动」与「便捷」两种方式创建并绑定一条管线。9.2 前置准备第 4 章着色器准备与第 7 章顶点布局已读第 8 章命令与状态管理已读。9.3 着色器阶段ShaderModule与ShaderStagevsg::ShaderModule封装一个VkShaderModule接受GLSL 源码运行时编译成 SPIR-V或SPIR-V// 方式 A直接给 GLSL 源码VSG 内部用 glslang 编译成 SPIR-V auto vertModule vsg::ShaderModule::create(R( #version 450 layout(location 0) in vec3 inPosition; void main() { gl_Position vec4(inPosition, 1.0); } )); // 方式 B从 .spv 读取已编译的 SPIR-V见第 4 章 auto fragModule vsg::ShaderModule::read(shaders/triangle.frag.spv);ShaderModule的code成员是 SPIR-Vstd::vectoruint32_tvk(deviceID)取出VkShaderModule句柄。vsg::ShaderStage把一个ShaderModule包装成某个阶段vsg::ShaderStages stages { vsg::ShaderStage::create(VK_SHADER_STAGE_VERTEX_BIT, main, vertModule), vsg::ShaderStage::create(VK_SHADER_STAGE_FRAGMENT_BIT, main, fragModule) }; // 也支持直接给 GLSL 字符串 // vsg::ShaderStage::create(VK_SHADER_STAGE_VERTEX_BIT, main, vertSrc);下面给出一个从创建ShaderModule到绑定GraphicsPipeline的完整可运行示例覆盖顶点/片元着色器、管线布局、管线状态与最终绑定#include vsg/all.h int main() { // 1. 创建窗口与实例VSG 封装了 Vulkan 实例/设备/交换链 auto window vsg::Window::create(vsg::WindowTraits::create(800, 600, Pipeline Demo)); // 2. 准备着色器模块顶点着色器GLSL 源码运行时编译为 SPIR-V auto vertModule vsg::ShaderModule::create(R( #version 450 layout(location 0) in vec3 inPosition; layout(location 1) in vec3 inColor; layout(location 0) out vec3 fragColor; void main() { gl_Position vec4(inPosition, 1.0); fragColor inColor; } )); // 3. 准备片元着色器模块 auto fragModule vsg::ShaderModule::create(R( #version 450 layout(location 0) in vec3 fragColor; layout(location 0) out vec4 outColor; void main() { outColor vec4(fragColor, 1.0); } )); // 4. 把模块包装成着色器阶段顶点 片元 vsg::ShaderStages stages { vsg::ShaderStage::create(VK_SHADER_STAGE_VERTEX_BIT, main, vertModule), vsg::ShaderStage::create(VK_SHADER_STAGE_FRAGMENT_BIT, main, fragModule) }; // 5. 创建管线布局本例无描述符集仅空布局 auto pipelineLayout vsg::PipelineLayout::create( vsg::DescriptorSetLayouts{}, // setLayouts无描述符 vsg::PushConstantRanges{}, // pushConstantRanges无 0); // flags // 6. 顶点输入状态位置 颜色两个属性对应第 7 章 auto vertexInput vsg::VertexInputState::create(); vertexInput-bindings { {0, sizeof(vsg::vec3) * 2, VK_VERTEX_INPUT_RATE_VERTEX} }; vertexInput-attributes { {0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0}, {1, 0, VK_FORMAT_R32G32B32_SFLOAT, sizeof(vsg::vec3)} }; // 7. 组装其余管线状态使用默认值 vsg::GraphicsPipelineStates pipelineStates{ vertexInput, vsg::InputAssemblyState::create(), // 默认 TRIANGLE_LIST vsg::RasterizationState::create(), // 默认剔除背面、填充 vsg::ColorBlendState::create(), // 默认不混合 vsg::DepthStencilState::create(), // 默认开启深度测试 vsg::MultisampleState::create(), // 默认 1 样本 vsg::ViewportState::create(window-extent2D()) }; // 8. 创建图形管线subpass 0 auto pipeline vsg::GraphicsPipeline::create( pipelineLayout, // PipelineLayout* stages, // ShaderStages pipelineStates, // GraphicsPipelineStates 0); // subpass // 9. 把管线包装成绑定命令挂到 StateGroup auto bindPipeline vsg::BindGraphicsPipeline::create(pipeline); auto stateGroup vsg::StateGroup::create(); stateGroup-add(bindPipeline); // 10. 创建场景并渲染此处省略顶点数据与绘制命令见第 7/8 章 auto scene vsg::Group::create(); scene-addChild(stateGroup); auto viewer vsg::Viewer::create(); viewer-addWindow(window); viewer-setScene(scene); viewer-run(); return 0; }9.4 管线布局PipelineLayoutPipelineLayout描述管线用到的描述符集布局与push constant 范围auto descriptorSetLayout vsg::DescriptorSetLayout::create(); descriptorSetLayout-addBinding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT); auto pipelineLayout vsg::PipelineLayout::create( vsg::DescriptorSetLayouts{descriptorSetLayout}, // setLayouts vsg::PushConstantRanges{}, // pushConstantRanges 0); // flags9.5 管线状态GraphicsPipelineState子类一条图形管线由一组GraphicsPipelineState描述常见成员状态类对应 Vulkan 结构作用VertexInputStateVkPipelineVertexInputStateCreateInfo顶点布局第 7 章InputAssemblyStateVkPipelineInputAssemblyStateCreateInfo图元拓扑三角形/线/点RasterizationStateVkPipelineRasterizationStateCreateInfo光栅化剔除、多边形模式ColorBlendStateVkPipelineColorBlendStateCreateInfo颜色混合透明DepthStencilStateVkPipelineDepthStencilStateCreateInfo深度/模板测试ViewportState视口 裁剪视口区域MultisampleStateVkPipelineMultisampleStateCreateInfo多重采样MSAATessellationState/DynamicState——细分/动态状态它们都有::create()便捷构造常用默认值即可vsg::GraphicsPipelineStates pipelineStates{ vertexInput, // 第 7 章的 VertexInputState vsg::InputAssemblyState::create(), // 默认 TRIANGLE_LIST vsg::RasterizationState::create(), // 默认剔除背面、填充 vsg::ColorBlendState::create(), // 默认不混合 vsg::DepthStencilState::create(), // 默认开启深度测试 vsg::MultisampleState::create(), // 默认 1 样本 vsg::ViewportState::create(window-extent2D()) // 视口窗口尺寸 };9.6 组装管线GraphicsPipeline与BindGraphicsPipelineauto pipeline vsg::GraphicsPipeline::create( pipelineLayout, // PipelineLayout*ref_ptr 可隐式转换 stages, // ShaderStages pipelineStates, // GraphicsPipelineStates 0); // subpass 0 // 把管线做成「状态命令」挂到 StateGroup见第 8 章 auto bindPipeline vsg::BindGraphicsPipeline::create(pipeline); stateGroup-add(bindPipeline);GraphicsPipeline的vk(viewID)返回对应视图的VkPipelineBindGraphicsPipeline在录制时执行vkCmdBindPipeline。下面给出一个更完整的示例展示从创建ShaderModule到最终绑定GraphicsPipeline的完整流程并标注关键步骤#include vsg/all.h int main() { // ---------- 1. 窗口与实例 ---------- auto window vsg::Window::create(vsg::WindowTraits::create(1024, 768, Full Pipeline)); // ---------- 2. 创建 ShaderModuleGLSL 源码运行时编译为 SPIR-V ---------- auto vertModule vsg::ShaderModule::create(R( #version 450 layout(location 0) in vec3 inPosition; layout(location 1) in vec3 inColor; layout(location 0) out vec3 fragColor; void main() { gl_Position vec4(inPosition, 1.0); fragColor inColor; } )); auto fragModule vsg::ShaderModule::create(R( #version 450 layout(location 0) in vec3 fragColor; layout(location 0) out vec4 outColor; void main() { outColor vec4(fragColor, 1.0); } )); // ---------- 3. 包装成 ShaderStage ---------- vsg::ShaderStages stages { vsg::ShaderStage::create(VK_SHADER_STAGE_VERTEX_BIT, main, vertModule), vsg::ShaderStage::create(VK_SHADER_STAGE_FRAGMENT_BIT, main, fragModule) }; // ---------- 4. 创建 PipelineLayout无描述符集、无 push constant ---------- auto pipelineLayout vsg::PipelineLayout::create( vsg::DescriptorSetLayouts{}, // setLayouts vsg::PushConstantRanges{}, // pushConstantRanges 0); // flags // ---------- 5. 顶点输入状态位置 颜色 ---------- auto vertexInput vsg::VertexInputState::create(); vertexInput-bindings { {0, sizeof(vsg::vec3) * 2, VK_VERTEX_INPUT_RATE_VERTEX} }; vertexInput-attributes { {0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0}, {1, 0, VK_FORMAT_R32G32B32_SFLOAT, sizeof(vsg::vec3)} }; // ---------- 6. 其余管线状态默认值即可 ---------- vsg::GraphicsPipelineStates pipelineStates{ vertexInput, vsg::InputAssemblyState::create(), // TRIANGLE_LIST vsg::RasterizationState::create(), // 剔除背面、填充 vsg::ColorBlendState::create(), // 不混合 vsg::DepthStencilState::create(), // 深度测试 vsg::MultisampleState::create(), // 1 样本 vsg::ViewportState::create(window-extent2D()) }; // ---------- 7. 创建 GraphicsPipeline ---------- auto pipeline vsg::GraphicsPipeline::create( pipelineLayout, // PipelineLayout* stages, // ShaderStages pipelineStates, // GraphicsPipelineStates 0); // subpass 0 // ---------- 8. 绑定管线到 StateGroup ---------- auto bindPipeline vsg::BindGraphicsPipeline::create(pipeline); auto stateGroup vsg::StateGroup::create(); stateGroup-add(bindPipeline); // ---------- 9. 渲染顶点数据与绘制命令见第 7/8 章 ---------- auto scene vsg::Group::create(); scene-addChild(stateGroup); auto viewer vsg::Viewer::create(); viewer-addWindow(window); viewer-setScene(scene); viewer-run(); return 0; }9.7 便捷路径回顾ShaderSetGraphicsPipelineConfigurator第 5 章的写法会自动生成上面的PipelineLayout、VertexInputState、BindGraphicsPipelineauto shaderSet vsg::ShaderSet::create(); shaderSet-stages stages; shaderSet-addAttributeBinding(inPosition, , 0, VK_FORMAT_R32G32B32_SFLOAT, vsg::vec3Array::create({vsg::vec3()})); auto config vsg::GraphicsPipelineConfigurator::create(shaderSet); config-enableArray(inPosition, VK_VERTEX_INPUT_RATE_VERTEX, sizeof(vsg::vec3), VK_FORMAT_R32G32B32_SFLOAT); config-init(); // 必须真正创建 PipelineLayout/GraphicsPipeline config-copyTo(stateGroup); // 把 BindGraphicsPipeline 等拷入 stateCommands两条路径的关系手动路径让你完全掌控每个状态便捷路径用「声明式」自动推导适合绝大多数网格。请始终保证config-init()被调用漏掉会崩溃见第 5 章排错表。9.8 常见问题现象原因解决着色器编译失败GLSL#version不对或语法 Vulkan 不支持用#version 450避免桌面 GL 专用语法管线创建报VK_ERROR_UNKNOWN某个GraphicsPipelineState缺失/冲突至少提供 VertexInput InputAssembly Rasterization ViewportvkCmdBindPipeline后无输出BindGraphicsPipeline没add进StateGroup确认stateGroup-add(bindPipeline)段错误漏config-init()严格enableArray → init → copyTo每帧/每网格重复创建管线CPU 开销高、卡顿未复用PipelineLayout与GraphicsPipeline重量级对象被反复创建把PipelineLayout与GraphicsPipeline存入ref_ptr并挂到StateGroup多个网格共享同一管线按状态分组减少vkCmdBindPipeline切换窗口尺寸变化后视口/裁剪不更新视口与裁剪矩形被固定进管线未声明为动态状态用vsg::DynamicState声明VK_DYNAMIC_STATE_VIEWPORT/VK_DYNAMIC_STATE_SCISSOR录制时用SetViewport更新更换显卡/驱动后启动变慢或报缓存不匹配复用了与旧设备/驱动绑定的PipelineCache文件缓存文件与设备/驱动绑定更换后删除旧缓存重新生成缓存只加速创建不影响渲染结果9.9 性能与最佳实践图形管线的创建是 Vulkan 中开销较大的操作之一。合理复用对象、善用动态状态与管线缓存能显著降低 CPU 开销与启动延迟。1. 复用 PipelineLayout 与 GraphicsPipelinePipelineLayout与GraphicsPipeline都是重量级对象创建后应尽量复用避免在每帧或每个网格上重复创建共享 PipelineLayout只要描述符集布局与 push constant 范围一致多个管线可以共用同一个PipelineLayout。把布局对象保存在场景或资源管理器里按需引用。缓存 GraphicsPipeline把创建好的GraphicsPipeline存入ref_ptr并挂到StateGroup多个节点可共享同一管线不要为每个绘制调用新建。按状态分组把使用相同管线状态的网格归为一组减少vkCmdBindPipeline的切换次数。// 复用示例多个 StateGroup 共享同一个 pipeline auto pipeline vsg::GraphicsPipeline::create(pipelineLayout, stages, pipelineStates, 0); auto bindPipeline vsg::BindGraphicsPipeline::create(pipeline); // 网格 A 与网格 B 共用同一管线避免重复创建 auto stateGroupA vsg::StateGroup::create(); stateGroupA-add(bindPipeline); auto stateGroupB vsg::StateGroup::create(); stateGroupB-add(bindPipeline); // 复用同一个 BindGraphicsPipeline2. 动态状态DynamicState的使用场景与配置动态状态允许在录制命令时修改部分管线状态而无需重建管线。常见场景包括视口与裁剪矩形窗口尺寸变化时只需更新动态视口不必重建管线。线宽需要不同线宽时通过动态状态切换。深度偏移、混合常量等按绘制批次动态调整。在 VSG 中通过vsg::DynamicState声明需要动态化的状态并在录制时用对应命令设置// 声明动态视口管线创建时不再固定视口 auto dynamicState vsg::DynamicState::create(); dynamicState-dynamicStates { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; vsg::GraphicsPipelineStates pipelineStates{ vertexInput, vsg::InputAssemblyState::create(), vsg::RasterizationState::create(), vsg::ColorBlendState::create(), vsg::DepthStencilState::create(), vsg::MultisampleState::create(), dynamicState // 视口/裁剪改为动态 }; // 录制时设置视口见第 8 章命令录制 auto setViewport vsg::SetViewport::create(0, vsg::Viewport{0, 0, width, height}); commandGraph-add(setViewport);3. 管线缓存PipelineCache的 VSG 使用方式管线缓存把编译好的管线二进制数据保存下来下次启动时直接复用能明显缩短启动时间。VSG 通过vsg::PipelineCache封装// 创建管线缓存可指定磁盘文件实现跨进程持久化 auto pipelineCache vsg::PipelineCache::create(); pipelineCache-filename pipeline_cache.bin; // 可选持久化到磁盘 // 创建管线时传入缓存 auto pipeline vsg::GraphicsPipeline::create( pipelineLayout, stages, pipelineStates, 0, // subpass pipelineCache); // 传入 PipelineCache // 退出前写回磁盘VSG 会在析构时自动保存到 filename使用要点缓存文件与驱动/设备绑定更换显卡或驱动版本后应重新生成。缓存只加速创建过程不影响渲染结果。在便捷路径中GraphicsPipelineConfigurator也支持传入缓存见其构造参数。4. 实战示例复用管线 动态视口 管线缓存下面把前面三个要点整合到一个完整示例中复用同一个GraphicsPipeline渲染多个网格用动态视口适配窗口尺寸变化并通过PipelineCache加速后续启动。代码中的注释标注了每一步的作用。#include vsg/all.h int main() { // ---------- 1. 创建窗口与实例 ---------- // VSG 封装了 Vulkan 实例、物理设备、逻辑设备与交换链 auto window vsg::Window::create(vsg::WindowTraits::create(1024, 768, Pipeline Reuse Demo)); // ---------- 2. 创建 PipelineCache持久化到磁盘加速下次启动 ---------- // 缓存文件与设备/驱动绑定更换显卡或驱动后应删除重新生成 auto pipelineCache vsg::PipelineCache::create(); pipelineCache-filename pipeline_cache.bin; // 可选析构时自动写回磁盘 // ---------- 3. 准备着色器模块GLSL 源码运行时编译为 SPIR-V ---------- auto vertModule vsg::ShaderModule::create(R( #version 450 layout(location 0) in vec3 inPosition; layout(location 1) in vec3 inColor; layout(location 0) out vec3 fragColor; void main() { gl_Position vec4(inPosition, 1.0); fragColor inColor; } )); auto fragModule vsg::ShaderModule::create(R( #version 450 layout(location 0) in vec3 fragColor; layout(location 0) out vec4 outColor; void main() { outColor vec4(fragColor, 1.0); } )); // ---------- 4. 包装成 ShaderStage顶点 片元 ---------- vsg::ShaderStages stages { vsg::ShaderStage::create(VK_SHADER_STAGE_VERTEX_BIT, main, vertModule), vsg::ShaderStage::create(VK_SHADER_STAGE_FRAGMENT_BIT, main, fragModule) }; // ---------- 5. 创建 PipelineLayout本例无描述符集、无 push constant ---------- // 只要描述符集布局与 push constant 范围一致多个管线可共享同一个布局 auto pipelineLayout vsg::PipelineLayout::create( vsg::DescriptorSetLayouts{}, // setLayouts无描述符 vsg::PushConstantRanges{}, // pushConstantRanges无 0); // flags // ---------- 6. 顶点输入状态位置 颜色两个属性 ---------- auto vertexInput vsg::VertexInputState::create(); vertexInput-bindings { {0, sizeof(vsg::vec3) * 2, VK_VERTEX_INPUT_RATE_VERTEX} }; vertexInput-attributes { {0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0}, {1, 0, VK_FORMAT_R32G32B32_SFLOAT, sizeof(vsg::vec3)} }; // ---------- 7. 声明动态状态视口与裁剪矩形 ---------- // 窗口尺寸变化时只需更新动态视口无需重建管线 auto dynamicState vsg::DynamicState::create(); dynamicState-dynamicStates { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; // ---------- 8. 组装其余管线状态默认值即可 ---------- vsg::GraphicsPipelineStates pipelineStates{ vertexInput, vsg::InputAssemblyState::create(), // TRIANGLE_LIST vsg::RasterizationState::create(), // 剔除背面、填充 vsg::ColorBlendState::create(), // 不混合 vsg::DepthStencilState::create(), // 深度测试 vsg::MultisampleState::create(), // 1 样本 dynamicState // 视口/裁剪改为动态 }; // ---------- 9. 创建 GraphicsPipeline传入 PipelineCache ---------- // 重量级对象创建一次后复用避免每帧/每网格重复创建 auto pipeline vsg::GraphicsPipeline::create( pipelineLayout, // PipelineLayout* stages, // ShaderStages pipelineStates, // GraphicsPipelineStates 0, // subpass 0 pipelineCache); // 传入 PipelineCache 加速创建 // ---------- 10. 把管线包装成绑定命令 ---------- // BindGraphicsPipeline 在录制时执行 vkCmdBindPipeline auto bindPipeline vsg::BindGraphicsPipeline::create(pipeline); // ---------- 11. 多个 StateGroup 复用同一个管线 ---------- // 网格 A 与网格 B 共用同一管线避免重复创建 auto stateGroupA vsg::StateGroup::create(); stateGroupA-add(bindPipeline); auto stateGroupB vsg::StateGroup::create(); stateGroupB-add(bindPipeline); // 复用同一个 BindGraphicsPipeline // ---------- 12. 组装场景并渲染 ---------- auto scene vsg::Group::create(); scene-addChild(stateGroupA); scene-addChild(stateGroupB); auto viewer vsg::Viewer::create(); viewer-addWindow(window); viewer-setScene(scene); viewer-run(); return 0; }要点回顾GraphicsPipeline与PipelineLayout创建一次后即可被多个StateGroup共享动态视口让窗口尺寸变化时无需重建管线PipelineCache把编译结果持久化到磁盘显著缩短后续启动时间。9.10 小结着色器阶段 ShaderModuleGLSL/SPIR-VShaderStage管线 PipelineLayoutShaderStagesGraphicsPipelineStates封装成GraphicsPipeline各种GraphicsPipelineState子类对应 Vulkan 的管线状态结构BindGraphicsPipeline把管线作为状态挂到StateGroup便捷路径ShaderSetConfigurator等价但更省心。复用PipelineLayout与GraphicsPipeline、善用动态状态与管线缓存可显著提升性能。9.11 延伸阅读与下一章预告第 10 章《描述符与资源绑定》DescriptorSetLayout怎么填、BindDescriptorSet怎么挂第 11 章《相机与视图》视口ViewportState与投影/视图矩阵第 12 章《RenderGraph 与 CommandGraph》管线在哪里被录制进命令缓冲。