ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

VulkanSceneGraph学习教程(十三)

VulkanSceneGraph学习教程(十三) 第 13 章 加载 3D 模型摘要本章系统介绍了在 VulkanSceneGraph (VSG) 中加载外部 3D 模型的核心流程。重点阐述了如何使用统一的vsg::read()函数加载 glTF/OBJ 等格式的模型强调了vsgXchange组件库在提供格式支持中的关键作用并详细说明了 CMake 的链接配置方法。此外本章还解释了加载后得到的Node子树结构、如何通过vsg::Options控制加载行为如坐标轴转换和资源路径并提供了一个完整的模型查看器示例。最后总结了常见问题的排查思路为后续的模型变换、动画和纹理学习奠定了基础。本章定位实际项目很少手搓几何体——vsgXchange组件库让vsg::read()直接吃 glTF / OBJ / 图像 / 字体。本章讲清「一行读取、挂入场景图」。13.1 本章目标用vsg::read()加载 glTF / OBJ 模型了解vsgXchange在背后提供的格式支持正确链接vsgXchange并配置CMake知道加载进来的Node已经自带几何体、状态与描述符。13.2 前置准备第 3 章已装好 VSG并安装了vsgXchange第 6 章理解场景图节点。13.3 统一入口vsg::read()VSG 的 IO 系统有一个统一读取函数位于vsg/io/read.href_ptrObject read(const Path filename, ref_ptrconst Options options {});它根据文件扩展名挑选已注册的ReaderWriter。vsgXchange在链接后自动注册对 glTF/OBJ/KTX2/PNG/字体等的支持。// 读取 glTFvsgXchange 提供支持 auto object vsg::read(models/duck.gltf); if (!object) { std::cerr 加载失败请确认文件存在且已链接 vsgXchange std::endl; return 1; } auto model vsg::ref_ptrvsg::Node(object); // 转换为 Node 并挂入场景图 root-addChild(model);⚠️必须链接vsgXchange核心库vsg只认自家的.vsgt/.vsgb等格式glTF/OBJ/PNG 全靠vsgXchange。没装它vsg::read(x.gltf)会返回 null。13.4 CMake链接 vsgXchangefind_package(vsg REQUIRED) find_package(vsgXchange REQUIRED) # 额外引入模型/图像支持 target_link_libraries(my_app PRIVATE vsg::vsg vsgXchange::vsgXchange)vsgXchange会按需拉入tinygltf、libpng、ktx等底层依赖多数通过 FetchContent 自动处理。13.5 加载进来的节点长什么样vsgXchange的 glTF 读取器会把文件解析成一棵完整的Node子树网格 →vsg::Geometry顶点/索引/绘制命令见第 7 章材质/纹理 → 自动生成DescriptorImageSampler挂到StateGroup见第 10 章变换层级 →vsg::MatrixTransform动画 →vsg::AnimationTransformSampler见第 14 章。也就是说你拿到的已经是「可渲染」的场景图通常只需套一层MatrixTransform调整位置/缩放即可。13.6 加载选项vsg::Options需要坐标系转换glTF 是 Y-upVSG 常 Z-up、缩放、或指定字体/图像搜索路径时传Optionsauto options vsg::Options::create(); options-paths vsg::Paths{models/, textures/}; // 资源搜索路径 options-fileCache vsg::getFileCache(); // 共享已加载对象 auto model vsg::read(duck.gltf, options);glTF 与 VSG 的坐标轴差异通常由vsgXchange在读取时按options处理遇到模型「躺平」多半是 up-axis 没设对检查Options的轴向/缩放设置。13.7 完整示例加载并查看一个模型下面是一个完整的、可直接编译运行的 VSG 程序示例演示如何加载 glTF 模型并创建一个简单的查看器。#include vsg/all.h #include iostream int main(int argc, char** argv) { // 1. 解析命令行参数模型文件路径 vsg::CommandLine arguments(argc, argv); auto modelFile arguments.value(std::string(models/duck.gltf), --model); auto width arguments.value(1024, --width); auto height arguments.value(768, --height); // 2. 创建窗口和查看器 auto traits vsg::WindowTraits::create(); traits-width width; traits-height height; traits-title VSG Model Viewer; auto window vsg::Window::create(traits); if (!window) { std::cerr 无法创建窗口 std::endl; return 1; } auto viewer vsg::Viewer::create(); viewer-addWindow(window); // 3. 加载模型需要已链接 vsgXchange auto options vsg::Options::create(); options-paths vsg::getEnvPaths(VSG_FILE_PATH); // 从环境变量获取搜索路径 options-paths.push_back(models/); // 添加本地模型目录 options-paths.push_back(textures/); // 添加纹理目录 auto model vsg::readvsg::Node(modelFile, options); if (!model) { std::cerr 模型加载失败: modelFile std::endl; std::cerr 请确认 std::endl; std::cerr 1. 文件路径正确 std::endl; std::cerr 2. 已安装并链接 vsgXchange std::endl; std::cerr 3. 依赖的纹理文件在搜索路径中 std::endl; return 1; } // 4. 创建场景图根节点 auto root vsg::Group::create(); // 5. 添加变换节点调整模型位置和大小 auto transform vsg::MatrixTransform::create(); // 将模型居中并适当缩放 transform-matrix vsg::translate(0.0, 0.0, 0.0) * vsg::scale(0.5, 0.5, 0.5); transform-addChild(model); root-addChild(transform); // 6. 设置相机 auto camera vsg::Camera::create(); // 透视投影 double aspect static_castdouble(width) / static_castdouble(height); camera-projectionMatrix vsg::Perspective::create(60.0, aspect, 0.1, 100.0); // 观察矩阵相机在 (0, 2, 5)看向原点上方向为 Y 轴 camera-viewMatrix vsg::LookAt::create( vsg::dvec3(0.0, 2.0, 5.0), // 相机位置 vsg::dvec3(0.0, 0.0, 0.0), // 观察目标 vsg::dvec3(0.0, 1.0, 0.0) // 上方向 ); // 视口与窗口大小一致 camera-viewportState vsg::ViewportState::create(window-extent2D()); // 7. 创建渲染图和命令图 auto renderGraph vsg::createRenderGraphForView(window, camera, root); auto commandGraph vsg::createCommandGraphForView(window, camera, root); // 8. 配置查看器 viewer-assignRecordAndSubmitTaskAndPresentation({commandGraph}); // 9. 编译场景图 auto compileResult viewer-compile(); if (!compileResult) { std::cerr 场景图编译失败 std::endl; return 1; } // 10. 添加轨道相机控制器方便交互查看 auto trackball vsg::Trackball::create(camera, window); viewer-addEventHandler(trackball); // 11. 主渲染循环 std::cout 成功加载模型: modelFile std::endl; std::cout 按 ESC 键退出程序 std::endl; while (viewer-advanceToNextFrame()) { // 处理窗口事件鼠标、键盘等 viewer-handleEvents(); // 更新场景动画、相机等 viewer-update(); // 记录命令缓冲区并提交 viewer-recordAndSubmit(); // 呈现到屏幕 viewer-present(); } return 0; }CMakeLists.txt 配置确保你的CMakeLists.txt正确链接了vsgXchangecmake_minimum_required(VERSION 3.12) project(model_viewer) find_package(vsg REQUIRED) find_package(vsgXchange REQUIRED) # 关键引入模型/图像支持 add_executable(model_viewer main.cpp) target_link_libraries(model_viewer PRIVATE vsg::vsg vsgXchange::vsgXchange # 关键链接 vsgXchange )运行说明准备模型文件将 glTF 模型如duck.gltf放在models/目录下编译mkdir build cd build cmake .. -DCMAKE_PREFIX_PATH/path/to/vsg/install make运行./model_viewer --model models/duck.gltf --width 1280 --height 720交互鼠标左键拖动旋转模型鼠标右键拖动平移模型鼠标滚轮缩放ESC 键退出程序提示如果模型显示异常如颠倒、大小不合适可以调整transform-matrix中的变换参数或在Options中设置upAxis和scale。13.8 常见问题现象原因解决方案vsg::read返回 null未链接vsgXchange或文件/路径错误确认target_link_libraries(... vsgXchange::vsgXchange)检查路径模型颠倒/躺平up-axis / 缩放设置不正确在Options中设置轴向与缩放或使用外层MatrixTransform旋转纹理丢失纹理文件不在搜索路径中使用Options::paths指定资源目录编译报找不到 glTF 符号只安装了核心库安装并链接vsgXchange13.9 性能优化与高级用法在掌握了基础加载流程后了解一些高级用法和性能优化技巧能让你在复杂项目中更高效地使用 VSG 的模型加载功能。1. 资源复用避免重复加载当多个场景需要加载同一模型或纹理时重复从磁盘读取会浪费 I/O 和内存。VSG 提供了两种资源复用机制使用vsg::SharedObjects全局共享// 创建全局共享对象管理器 auto sharedObjects vsg::SharedObjects::create(); // 第一次加载时注册到共享管理器 auto options vsg::Options::create(); options-sharedObjects sharedObjects; auto model1 vsg::read(models/duck.gltf, options); // 后续加载相同文件时直接从共享管理器获取 auto model2 vsg::read(models/duck.gltf, options); // 复用已加载的资源使用Options::fileCache文件级缓存// 创建带文件缓存的 Options auto options vsg::Options::create(); options-fileCache vsg::getFileCache(); // 获取全局文件缓存 // 多次读取同一文件只有第一次会真正从磁盘加载 auto texture1 vsg::read(textures/diffuse.png, options); auto texture2 vsg::read(textures/diffuse.png, options); // 从缓存获取适用场景SharedObjects适用于运行时需要多次创建相同模型的场景如游戏中的 NPC、道具fileCache适用于纹理、字体等静态资源的重复加载2. 类型安全读取vsg::read_castT()标准vsg::read()返回ref_ptrObject需要手动类型转换。使用read_cast可以在读取时直接进行类型检查// 传统方式需要手动转换和检查 auto object vsg::read(models/duck.gltf); if (object) { auto node vsg::ref_ptrvsg::Node(object); // 手动转换 if (node) root-addChild(node); } // 类型安全方式直接获取指定类型 auto node vsg::read_castvsg::Node(models/duck.gltf); if (node) root-addChild(node); // node 已经是正确的类型 // 也可以用于读取特定类型的资源 auto image vsg::read_castvsg::Data(textures/diffuse.png); auto font vsg::read_castvsg::Font(fonts/arial.ttf);优势编译时类型安全避免运行时类型错误代码更简洁减少手动类型转换清晰的意图表达提高代码可读性3. 异步加载提升启动和切换性能对于大型模型或网络资源同步加载会阻塞主线程。VSG 提供了异步加载机制#include vsg/threading/OperationThreads.h #include vsg/io/read.h // 1. 创建操作线程池建议在程序初始化时创建 auto operationThreads vsg::OperationThreads::create(2); // 2个工作线程 // 2. 创建支持异步的 Options auto options vsg::Options::create(); options-operationThreads operationThreads; // 3. 异步读取模型 auto future vsg::readAsyncvsg::Node(models/large_scene.gltf, options); // 主线程可以继续做其他事情... setupCamera(); setupLighting(); // 4. 需要模型时检查是否加载完成 if (future.valid() future.wait_for(std::chrono::seconds(0)) std::future_status::ready) { auto model future.get(); if (model) root-addChild(model); } else { // 可以显示加载进度条或占位模型 showLoadingIndicator(); // 阻塞等待如果必须立即使用 // auto model future.get(); }适用场景大型场景加载游戏关卡切换、CAD 模型查看器网络资源从网络加载模型或纹理后台预加载预加载下一场景的资源响应式 UI避免加载卡住用户界面注意事项异步加载的资源在添加到场景图前不能访问需要合理管理线程池大小避免创建过多线程异步加载失败时future.get()可能抛出异常需要适当处理4. 组合使用示例在实际项目中通常会组合使用这些技术class ResourceManager { public: ResourceManager() { // 创建共享对象和线程池 sharedObjects vsg::SharedObjects::create(); operationThreads vsg::OperationThreads::create(4); options vsg::Options::create(); options-sharedObjects sharedObjects; options-operationThreads operationThreads; options-fileCache vsg::getFileCache(); } vsg::ref_ptrvsg::Node loadModelAsync(const std::string path) { // 检查是否已缓存 if (auto cached sharedObjects-getvsg::Node(path)) return cached; // 异步加载 auto future vsg::readAsyncvsg::Node(path, options); pendingFutures[path] std::move(future); return nullptr; } bool tryGetModel(const std::string path, vsg::ref_ptrvsg::Node outModel) { if (auto it pendingFutures.find(path); it ! pendingFutures.end()) { if (it-second.wait_for(std::chrono::seconds(0)) std::future_status::ready) { outModel it-second.get(); pendingFutures.erase(it); return true; } } return false; } private: vsg::ref_ptrvsg::SharedObjects sharedObjects; vsg::ref_ptrvsg::OperationThreads operationThreads; vsg::ref_ptrvsg::Options options; std::unordered_mapstd::string, std::futurevsg::ref_ptrvsg::Node pendingFutures; };性能建议小文件 10MB使用同步加载 缓存即可中等文件10-100MB考虑使用异步加载超大文件 100MB需要分块加载或使用流式加载如果格式支持移动设备上适当减少线程池大小1-2个线程13.9 小结vsg::read(path)是统一入口按扩展名选ReaderWriterglTF/OBJ/图像/字体支持来自组件库vsgXchange务必find_package 链接加载结果已是完整可渲染的Node子树直接挂场景图即可用vsg::Options控制路径、轴向与缓存。13.10 延伸阅读与下一章预告第 14 章《变换与动画》让加载的模型动起来AnimationTransformSampler第 15 章《纹理与材质》理解 glTF 里的纹理/材质是怎么变成DescriptorImage的第 24 章《简易 3D 模型查看器》把本章读模型 轨道相机 光照整合成一个完整程序。
返回列表