
ReadFileCallback 与 WriteFileCallback 自定义文件
在 OpenSceneGraph(OSG)开发中,osgDB 模块承担着模型、图片、场景文件的核心读写工作,是连接外部资源与 OSG 场景图的关键桥梁。
但原生的读写逻辑仅能完成基础的文件加载与保存,无法满足开发中的耗时统计、日志打印、权限校验、加密解密、格式预处理等定制化需求。
为此,OSG 提供了回调机制 ------ReadFileCallback(文件读取回调)和 WriteFileCallback(文件写入回调),允许开发者在不修改 OSG 底层源码的前提下,无缝扩展文件读写的前后逻辑,实现灵活的功能增强。
本文将结合完整可运行代码,深度解析两个回调类的继承关系、核心原理、使用方法与实战应用。
核心类继承关系(底层架构)
理解 OSG 回调的第一步,是明确其类继承体系,这决定了回调的触发逻辑与重载规则:
1. 完整继承链
osg::Referenced(基类:智能指针内存管理)
↓
osgDB::Registry::ReadFileCallback (读取回调抽象类)
↓(自定义继承)
MyReadFileCallback(开发者实现类)
osg::Referenced(基类:智能指针内存管理)
↓
osgDB::Registry::WriteFileCallback(写入回调抽象类)
↓(自定义继承)
MyWriteFileCallback(开发者实现类)
2. 关键类作用解析
-
osg::ReferencedOSG 所有动态对象的顶层基类 ,提供引用计数智能指针 (
osg::ref_ptr)管理内存,无需手动释放回调对象,避免内存泄漏。 -
osgDB::Registry::ReadFileCallback- 抽象类,定义文件读取的回调接口;
- 包含纯虚函数
readNode(),必须由子类重载实现; - 全局唯一,注册后所有
osgDB::readNodeFile()都会触发该回调。
-
osgDB::Registry::WriteFileCallback- 抽象类,定义文件写入的回调接口;
- 包含纯虚函数
writeNode(),必须由子类重载实现; - 全局唯一,注册后所有
osgDB::writeNodeFile()都会触发该回调。
-
osgDB::RegistryOSG 读写插件的全局管理器,负责:
- 注册/注销读写回调;
- 调用底层插件完成实际文件读写(
readNodeImplementation/writeNodeImplementation); - 管理所有支持的文件格式(.osg/.obj/.fbx/.png 等)。
3. 核心虚函数(必须重载)
| 回调类 | 核心虚函数 | 触发时机 |
|---|---|---|
ReadFileCallback |
readNode(文件名, 配置参数) |
调用 osgDB::readNodeFile() 读取模型时 |
WriteFileCallback |
writeNode(节点对象, 文件名, 配置参数) |
调用 osgDB::writeNodeFile() 保存模型时 |
注意:重载函数中必须调用 OSG 底层的实现方法 (
readNodeImplementation/writeNodeImplementation),否则文件无法真正读写。
核心原理
OSG 采用钩子(Hook)设计模式,回调的执行流程如下:
- 开发者自定义类继承回调抽象类,重载核心虚函数;
- 向
osgDB::Registry全局注册自定义回调; - 调用
readNodeFile/writeNodeFile时,OSG 优先触发回调函数; - 回调中执行自定义逻辑(计时、日志、校验等);
- 回调调用 OSG 底层方法完成实际文件读写;
- 读写完成后,回调执行后续自定义逻辑,返回结果。
优势:无侵入式扩展,不修改 OSG 源码,不影响原有读写逻辑,可随时启用/关闭回调。
实战代码实现(完整可运行)
结合继承关系与核心原理,我们实现带耗时统计、日志打印的自定义读写回调:
1. 完整代码
cpp
#include <osgViewer/Viewer>
#include <osg/Node>
#include <osg/Group>
#include <osgDB/ReadFile>
#include <osgDB/WriteFile>
#include <osgDB/Registry>
#include <osgUtil/Optimizer>
#include <osg/Timer>
#include <iostream>
// -------------------------- 读取回调实现 --------------------------
// 继承:osg::Referenced → osgDB::Registry::ReadFileCallback
class MyReadFileCallback : public osgDB::Registry::ReadFileCallback
{
public:
// 重载核心虚函数:读取节点
virtual osgDB::ReaderWriter::ReadResult readNode(
const std::string& fileName,
const osgDB::ReaderWriter::Options* options
) override
{
// 自定义逻辑1:打印开始日志
std::cout << "[回调] 开始读取文件:" << fileName << std::endl;
// 计时开始
osg::Timer_t start = osg::Timer::instance()->tick();
// 核心:调用OSG底层实现,完成实际文件读取
osgDB::ReaderWriter::ReadResult result =
osgDB::Registry::instance()->readNodeImplementation(fileName, options);
// 自定义逻辑2:计算耗时、打印结束日志
double elapsed = osg::Timer::instance()->delta_s(start, osg::Timer::instance()->tick());
std::cout << "[回调] 读取完成,耗时:" << elapsed << "s" << std::endl << std::endl;
return result;
}
};
// -------------------------- 写入回调实现 --------------------------
// 继承:osg::Referenced → osgDB::Registry::WriteFileCallback
class MyWriteFileCallback : public osgDB::Registry::WriteFileCallback
{
public:
// 重载核心虚函数:写入节点
virtual osgDB::ReaderWriter::WriteResult writeNode(
const osg::Node& obj,
const std::string& fileName,
const osgDB::ReaderWriter::Options* options
) override
{
// 自定义逻辑1:打印开始日志
std::cout << "[回调] 开始写入文件:" << fileName << std::endl;
// 计时开始
osg::Timer_t start = osg::Timer::instance()->tick();
// 核心:调用OSG底层实现,完成实际文件写入
osgDB::ReaderWriter::WriteResult result =
osgDB::Registry::instance()->writeNodeImplementation(obj, fileName, options);
// 自定义逻辑2:计算耗时、打印结束日志
std::cout << "[回调] 写入完成,耗时:"
<< osg::Timer::instance()->delta_s(start, osg::Timer::instance()->tick())
<< "s" << std::endl << std::endl;
return result;
}
};
// -------------------------- 主函数 --------------------------
int main()
{
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();
osg::ref_ptr<osg::Group> root = new osg::Group();
// 全局注册回调:注册后所有读写操作自动触发
osgDB::Registry::instance()->setReadFileCallback(new MyReadFileCallback());
osgDB::Registry::instance()->setWriteFileCallback(new MyWriteFileCallback());
// 1. 读取模型(触发ReadFileCallback)
osg::ref_ptr<osg::Node> model = osgDB::readNodeFile("spaceship.osg");
if (!model.valid()) {
std::cerr << "模型读取失败!" << std::endl;
return -1;
}
root->addChild(model);
// 2. 保存模型(触发WriteFileCallback)
osgDB::writeNodeFile(*root, "output_model.osg");
// 优化场景并渲染
osgUtil::Optimizer optimizer;
optimizer.optimize(root);
viewer->setSceneData(root);
viewer->realize();
return viewer->run();
}

2. 代码核心说明
- 继承与重载 :自定义类严格继承
ReadFileCallback/WriteFileCallback,使用override关键字确保虚函数重载正确; - 必须调用底层方法 :
readNodeImplementation/writeNodeImplementation是 OSG 实际读写文件的逻辑,不可省略; - 全局注册 :通过
osgDB::Registry单例注册回调,一次注册,全局生效; - 内存安全 :回调对象由
osg::Referenced管理,无需手动delete。
、
进阶扩展
基于 ReadFileCallback 和 WriteFileCallback,可实现大量企业级需求:
- 权限校验:读取前校验文件权限,拒绝非法文件访问;
- 加密解密:读取时解密加密模型,写入时加密保存,保护资源安全;
- 格式自动转换:将 .obj/.fbx 自动转为 .osg 缓存,提升加载速度;
- 全链路日志:记录所有文件读写的文件名、耗时、结果,用于性能监控;
- 异常处理:读写失败时自动重试、上报错误,提升程序稳定性。
总结
- 继承关系 :回调类最终继承自
osg::Referenced,依托osgDB::Registry实现全局管理; - 核心价值:无侵入式扩展 OSG 读写逻辑,满足定制化开发需求;
- 使用关键:继承抽象类 → 重载虚函数 → 调用底层实现 → 全局注册;
- 实战价值 :广泛应用于三维仿真、数字孪生、游戏开发等 OSG 项目中,是进阶开发必备技能。
