第 6 章 场景图基础本章定位场景图是 VSG 的骨架。本章讲清Node / Group / MatrixTransform / Switch / LOD / StateGroup的层次关系与遍历机制是后续所有实战章的前提。6.1 本章目标理解Node与Group的父子层次掌握MatrixTransform的模型变换如何搭建「爷爷→父亲→儿子」的变换链会用Switch按掩码切换子节点、LOD做细节层次理解 VSG 的 Visitor 遍历尤其是录制阶段专用的RecordTraversal。6.2 前置准备已完成第 5 章理解一个最小 VSG 程序里StateGroup → Geometry的挂载关系。6.3 节点层次Node与Groupvsg::Node是所有场景图节点的基类InheritObject, Node。vsg::Group是最常用的容器节点持有一个children列表auto root vsg::Group::create(); auto child vsg::Group::create(); root-addChild(child); // 把 child 挂到 root 之下 // children 是 std::vectorref_ptrNode节点通过accept(Visitor)被遍历Group的traverse()会依次访问自己的每个子节点。这就是「场景图」——一棵树渲染时从根向下遍历、录制。6.4MatrixTransform模型变换vsg::MatrixTransform持有一个dmat4 matrix在RecordTraversal中把它乘到当前的模型视图矩阵modelview栈上作用在子树上离开子树时再弹出不影响兄弟节点。// 平移 1 个单位vsg::translate 等辅助函数在 vsg/maths 中 auto xform vsg::MatrixTransform::create(vsg::translate(1.0, 0.0, 0.0)); // 也可手动构造 4x4 矩阵 vsg::dmat4 m; m.set_translate(vsg::dvec3(0.0, 0.0, -5.0)); auto xform2 vsg::MatrixTransform::create(m);变换顺序子节点的世界矩阵 父矩阵 × 本节点matrix。即「先局部、后父级」地左乘。多个嵌套MatrixTransform自然形成变换链。6.5Switch按掩码切换子节点Switch用**掩码Mask**决定哪些子节点参与遍历。每个子节点是一个Child{ Mask mask; ref_ptrNode node }auto sw vsg::Switch::create(); sw-addChild(true, modelA); // 默认显示 A sw-addChild(false, modelB); // 默认隐藏 B // 运行时切换 sw-setSingleChildOn(1); // 只显示索引 1B sw-setAllChildren(true); // 全部显示遍历时节点的mask与traversalMask / overrideMask组合运算MASK_ALL表示「始终参与」MASK_OFF表示「关闭」。Switch常用于 LOD 切换、调试显隐、动画状态机。6.6LOD细节层次LODLevel of Detail根据子图在屏幕上的占屏比自动选择不同精度的子节点auto lod vsg::LOD::create(); lod-bound vsg::dsphere(vsg::dvec3(0.0, 0.0, 0.0), 1.0); // 包围球中心半径用于视锥剔除 // 子节点按精度从高到低排列minimumScreenHeightRatio 为该子节点可见的最小屏幕高度占比 lod-addChild({0.0, highResModel}); // 始终可见最高精度优先 lod-addChild({0.2, midResModel}); // 占屏比 0.2 时降级 lod-addChild({0.05, lowResModel}); // 占屏比 0.05 时再用最低精度LOD在裁剪遍历中已经结合了视锥剔除无需额外挂CullGroup。注意一旦某个子节点通过测试后续更低精度的子节点不再被遍历同一时刻至多一个 LOD 子节点可见。6.7StateGroup状态分组预览StateGroup继承自Group额外持有stateCommands状态命令列表。它对子树统一「压入」这些状态离开时「弹出」auto sg vsg::StateGroup::create(); sg-add(pipelineBindCommand); // 压入管线状态push sg-addChild(geometry); // 子树在「已绑定管线」下录制 // 离开 StateGroup 时状态自动弹出popStateGroup是连接「场景图」与「Vulkan 状态」的桥梁第 8 章会专门展开。6.8 Visitor 遍历机制VSG 用访问者模式遍历场景图vsg::Visitor/vsg::ConstVisitor通用遍历用于统计节点数、查找某类节点、导出场景等vsg::RecordTraversal录制阶段专用。它一边遍历一边把Command写到 Vulkan 的CommandBuffer并维护 modelview / projection 等状态栈这正是第 12 章要讲的「编译成命令图」。Node的accept()把自身交给访问者容器节点的traverse()递归访问子节点。理解这一点就能写出自己的Visitor来后处理场景图例如收集所有MatrixTransform。6.9 综合示例搭一棵小场景树// root // └─ MatrixTransform(平移) // └─ Switch(在「带纹理」与「纯色」间切换) // ├─ StateGroup(纹理管线) → Geometry // └─ StateGroup(纯色管线) → Geometry auto root vsg::Group::create(); auto xform vsg::MatrixTransform::create(vsg::translate(0.0, 0.0, -5.0)); root-addChild(xform); auto sw vsg::Switch::create(); xform-addChild(sw); auto sgTextured vsg::StateGroup::create(); // 第 8/10 章补充管线与纹理 auto sgSolid vsg::StateGroup::create(); sw-addChild(true, sgTextured); sw-addChild(false, sgSolid); // sgTextured-addChild(geometryTextured); // sgSolid-addChild(geometrySolid);6.10 常见问题现象原因解决节点「消失」忘记addChild或父节点根本没挂到 root检查层次链是否从root一路连到该节点变换方向反了误以为右乘或顺序颠倒记住 世界 父 × 本节点matrix左乘链Switch 全黑所有子节点mask MASK_OFF用addChild(true, ...)或setSingleChildOn(i)开启LOD 永远显示最低精度minimumScreenHeightRatio设得过大最高精度子节点用0.0始终可见逐级递减6.11 小结场景图是一棵以Node为根的树Group是通用容器MatrixTransform用dmat4做模型变换并进入 modelview 栈Switch按掩码、LOD按屏幕占比控制子图可见性StateGroup对子树统一施加状态遍历由 Visitor尤其RecordTraversal驱动。6.12 延伸阅读与下一章预告第 7 章《几何体与顶点数据》把「顶点/索引」装进Geometry理解顶点布局第 8 章《命令与状态管理》StateGroup/StateCommand如何把 Vulkan 状态挂到场景图第 12 章《RenderGraph 与 CommandGraph》RecordTraversal如何把整棵树编译成命令缓冲。