
NodeCallback 帧回调机制
在 OpenSceneGraph(OSG)开发中,NodeCallback 是实现模型动画、实时更新、交互控制的核心技术。它可以让我们在渲染的每一帧自动执行自定义逻辑,无需手动编写循环,是 OSG 场景树动态控制的基础。
本文结合模型旋转动画完整代码,深度讲解 NodeCallback 的原理、继承关系、使用方法,帮你彻底掌握 OSG 帧回调的核心用法。
前置知识
要理解 NodeCallback,首先要明确 OSG 中核心类的层级关系,这是所有用法的基础:
1. NodeCallback 继承体系
osg::Referenced // OSG 智能指针管理基类(自动内存管理)
└── osg::Callback // 回调基类
└── osg::NodeCallback // 节点回调(本文核心)
NodeCallback是专门为场景节点设计的回调类,用于节点的实时更新- 我们自定义回调必须继承自 NodeCallback ,并重写
operator()方法
2. MatrixTransform 节点继承体系
osg::Node // 所有场景节点的基类
└── osg::Group // 可挂载子节点的父节点
└── osg::Transform // 坐标变换基类
└── osg::MatrixTransform // 矩阵变换节点(控制旋转/平移/缩放)
MatrixTransform是功能型节点,专门给子节点施加坐标变换- 它是 NodeCallback 最常用的绑定对象(因为我们几乎都用它做动画)
核心原理
1. 什么是 NodeCallback?
NodeCallback 是 OSG 提供的节点帧回调接口:
- 绑定到任意
osg::Node(包括 MatrixTransform、Group、模型节点) - 渲染每一帧 ,OSG 遍历场景树时,会自动调用绑定的回调
- 重写
operator()方法,即可实现自定义逻辑(旋转、移动、交互等)
2. 调用时机(OSG 一帧渲染流程)
- OSG 启动渲染循环 → 遍历场景树
- 遇到绑定了 NodeCallback 的节点
- 自动执行回调的
operator()方法 - 执行完自定义逻辑后,继续遍历子节点
- 完成渲染,展示画面
核心特性:绑定在哪个节点,就为哪个节点每帧执行回调
3. 必须掌握的 2 个核心方法
cpp
// 1. 重写的核心方法:每一帧自动调用
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv);
// 2. 必须调用的方法:继续遍历子节点(不写会导致场景卡住/不渲染)
traverse(node, nv);
完整实战代码
cpp
#include <osgViewer/Viewer>
#include <osg/Node>
#include <osg/MatrixTransform>
#include <osgDB/ReadFile>
#include <osgUtil/Optimizer>
#include <iostream>
// ===================== 自定义回调类:继承 NodeCallback =====================
class RotateCallback : public osg::NodeCallback
{
public:
// 构造函数:初始化旋转角度为0
RotateCallback() : _rotateAngle(0.0) {}
// 核心重写方法:OSG 每一帧自动调用此函数
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) override
{
// 1. 将节点转换为 MatrixTransform(必须转换,才能修改矩阵)
osg::MatrixTransform* transform = dynamic_cast<osg::MatrixTransform*>(node);
if (transform)
{
// 2. 定义旋转矩阵:绕Z轴旋转(弧度制)
osg::Matrix rotateMatrix;
rotateMatrix.makeRotate(_rotateAngle, osg::Vec3(0.0f, 0.0f, 1.0f));
// 3. 为变换节点设置旋转矩阵
transform->setMatrix(rotateMatrix);
// 4. 角度递增,实现连续旋转
_rotateAngle += 0.01f;
}
// 关键:必须调用 traverse,继续遍历子节点,否则模型不渲染
traverse(node, nv);
}
private:
double _rotateAngle; // 记录旋转角度(成员变量,持久化存储)
};
// ===================== 主函数:场景搭建 =====================
int main()
{
// 1. 创建OSG查看器
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();
// 2. 创建根节点
osg::ref_ptr<osg::Group> root = new osg::Group();
// 3. 加载模型(OSG自带示例模型 cow.osg)
osg::ref_ptr<osg::Node> model = osgDB::readNodeFile("cow.osg");
if (!model)
{
std::cerr << "模型加载失败!" << std::endl;
return -1;
}
// 4. 创建矩阵变换节点(控制模型旋转)
osg::ref_ptr<osg::MatrixTransform> mtNode = new osg::MatrixTransform();
mtNode->addChild(model); // 模型作为子节点
// 5. 核心:为 MatrixTransform 绑定自定义 NodeCallback
mtNode->setUpdateCallback(new RotateCallback());
// 6. 节点添加到场景树
root->addChild(mtNode);
// 7. 优化场景
osgUtil::Optimizer optimizer;
optimizer.optimize(root);
// 8. 设置场景并运行
viewer->setSceneData(root);
viewer->realize();
return viewer->run();
}

代码核心解析(NodeCallback 重点)
1. 自定义回调类的编写规则
- 必须继承
osg::NodeCallback - 重写
operator()方法(这是帧回调的入口) - 用成员变量存储状态(如旋转角度,跨帧持久化)
- 必须调用
traverse(node, nv)
2. 为什么回调要绑定在 MatrixTransform 上?
- 我们要修改坐标变换,而不是修改模型本身
- 回调绑定到
MatrixTransform,才能在operator()中获取到该节点 - 模型节点本身没有矩阵变换功能,无法实现旋转
3. 内存管理说明
- OSG 所有回调、节点都继承自
osg::Referenced - 使用
osg::ref_ptr智能指针,无需手动 delete,自动释放内存
NodeCallback 常用扩展场景
掌握基础用法后,你可以用 NodeCallback 实现所有动态效果:
- 模型位移动画 :修改矩阵的
makeTranslate - 缩放动画 :修改矩阵的
makeScale - 交互控制:键盘/鼠标控制节点状态
- 骨骼动画:嵌套 MatrixTransform + 多个回调
- 性能优化:按需更新,而非每帧执行
总结
- NodeCallback 是 OSG 帧回调核心,每一帧自动执行
- 继承关系:
osg::Referenced → osg::Callback → osg::NodeCallback - 核心方法:重写
operator()+ 必须调用traverse() - 最佳实践:回调绑定 MatrixTransform 节点,实现模型动画
- 价值:解耦「动画逻辑」和「模型渲染」,是 OSG 开发必备技能
