
NodeVisitor(顶点访问器)
在 OSG(OpenSceneGraph)三维渲染引擎中,顶点访问器(Vertex Visitor) 是操作场景几何数据最核心、最常用的工具。
它基于访问者设计模式 实现,完美实现了场景结构与操作逻辑的解耦,让开发者无需修改引擎源码,就能遍历、提取、修改场景中所有模型的顶点数据。
本文将从设计思想、继承关系、核心API、实战代码、使用场景五个维度,带你彻底掌握 OSG 顶点访问器。
什么是顶点访问器?
顶点访问器是专门用于遍历、处理场景中几何顶点数据的访问器。
OSG 场景是树形结构:
根节点 -> 组节点(Group) -> 几何节点(Geode) -> 几何体(Geometry) -> 顶点数组
顶点数据存储在最底层的 Geometry 中,顶点访问器的使命就是自动遍历整棵树,找到所有顶点并对其操作。
核心设计思想
- 访问者模式:将"遍历逻辑"与"业务逻辑"分离
- 双重分发:运行时动态匹配节点类型,自动调用对应处理函数
- 非侵入式:不修改 Node、Geode、Geometry 源码
- 自动递归:无需手写递归遍历,引擎自动完成全场景遍历
类继承关系
理解继承关系,才能真正掌握访问器的运行机制。
1. 顶层基类:osg::NodeVisitor
所有访问器的祖宗类,提供遍历框架。
osg::Referenced
↓
osg::NodeVisitor <-- 所有访问器必须继承它
2. 顶点访问器继承链
你自定义的顶点访问器继承结构:
osg::NodeVisitor
↓
你的顶点访问器(VertexVisitor)
3. 被访问节点继承链(顶点数据所在)
osg::Node
↓
osg::Geode <-- 几何节点,存放模型
↓
osg::Geometry <-- 真正存储顶点、法线、纹理坐标
4. 顶点数据继承链
osg::Array
↓
osg::Vec3Array <-- 存储三维顶点坐标
四大核心 API
这是顶点访问器能工作的四大支柱:
1. accept() ------ 入口函数(节点提供)
-
位置 :
osg::Node -
作用:启动访问,允许访问器进入节点
-
代码 :
cppnode->accept(visitor);
2. apply() ------ 业务逻辑(访问器重写)
-
位置 :
osg::NodeVisitor -
作用 :处理具体节点,顶点访问器必须重写
apply(Geode&) -
代码 :
cppvoid apply(osg::Geode& geode) override;
3. traverse() ------ 递归遍历
-
作用 :继续遍历子节点,必须调用,否则遍历中断
-
代码 :
cpptraverse(geode);
4. validNodeMask() ------ 节点过滤
- 作用:跳过不需要的节点,提升性能
完整实战代码
这是工业级、可直接编译运行的顶点提取访问器,逐行解析。
完整代码
cpp
#include <osgViewer/Viewer>
#include <osg/Node>
#include <osg/Geode>
#include <osg/Geometry>
#include <osgDB/readFile>
#include <fstream>
#include <iostream>
// ==============================================
// 顶点访问器:继承自 osg::NodeVisitor
// ==============================================
class VertexExtractor : public osg::NodeVisitor
{
public:
// 存储提取的所有顶点
osg::ref_ptr<osg::Vec3Array> vertices;
// 构造:设置遍历所有子节点
VertexExtractor()
: osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
{
vertices = new osg::Vec3Array;
}
// ==========================================
// 核心:重写 apply,只处理 Geode 模型节点
// ==========================================
void apply(osg::Geode& geode) override
{
// 遍历 Geode 下的所有几何体
for (unsigned i = 0; i < geode.getNumDrawables(); ++i)
{
// 转为 Geometry(只有它有顶点)
osg::Geometry* geom = dynamic_cast<osg::Geometry*>(geode.getDrawable(i));
if (!geom) continue;
// 获取顶点数组
osg::Vec3Array* verts = dynamic_cast<osg::Vec3Array*>(geom->getVertexArray());
if (!verts) continue;
// 把顶点追加到结果中
vertices->insert(vertices->end(), verts->begin(), verts->end());
}
// 必须遍历子节点
traverse(geode);
}
};
// ==============================================
// 主函数:加载模型 + 使用访问器
// ==============================================
int main()
{
// 1. 加载模型
osg::ref_ptr<osg::Node> model = osgDB::readNodeFile("glider.osg");
// 2. 创建顶点访问器
VertexExtractor extractor;
// 3. 启动访问(核心:accept)
model->accept(extractor);
// 4. 输出顶点数量
std::cout << "提取顶点数:" << extractor.vertices->size() << std::endl;
// 5. 保存到文件
std::ofstream out("vertices.txt");
for (auto& v : *extractor.vertices)
out << v.x() << " " << v.y() << " " << v.z() << "\n";
out.close();
// 显示
osgViewer::Viewer viewer;
viewer.setSceneData(model);
return viewer.run();
}

代码执行流程
model->accept(extractor)
↓
extractor.apply(geode) 处理模型
↓
提取 Geometry 顶点
↓
traverse(geode) 继续遍历子节点
↓
遍历完成 → 得到所有顶点
应用场景
顶点访问器是 OSG 开发必备工具,覆盖 90% 几何操作:
1. 数据导出与格式转换
- 导出顶点、法线、UV 坐标
- 转成 JSON/CSV/自定义格式
- 模型轻量化、数据清洗
2. 模型修改与实时形变
- 水面波浪动画
- 模型破碎、拉伸、扭曲
- 顶点位移、缩放
3. 碰撞检测与拾取
- 鼠标点击获取顶点
- 检测交点所在三角面
- 物理仿真碰撞计算
4. 网格优化与处理
- 生成法线
- 简化模型、减少三角面
- 计算包围盒、中心点
5. 分析与统计
- 统计三角面数量
- 检查模型错误
- 计算模型体积、面积
总结
- 解耦:场景结构 ↔ 操作逻辑
- 自动遍历:不用手写递归
- 非侵入:不修改引擎源码
- 灵活扩展:一个访问器实现一种功能
- 工业必备:仿真、车载、数字孪生必用
