【OSG学习笔记】Day 41: ReadFileCallback 与 WriteFileCallback(自定义文件读取)

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. 关键类作用解析

  1. osg::Referenced

    OSG 所有动态对象的顶层基类 ,提供引用计数智能指针osg::ref_ptr)管理内存,无需手动释放回调对象,避免内存泄漏。

  2. osgDB::Registry::ReadFileCallback

    • 抽象类,定义文件读取的回调接口
    • 包含纯虚函数 readNode(),必须由子类重载实现;
    • 全局唯一,注册后所有 osgDB::readNodeFile() 都会触发该回调。
  3. osgDB::Registry::WriteFileCallback

    • 抽象类,定义文件写入的回调接口
    • 包含纯虚函数 writeNode(),必须由子类重载实现;
    • 全局唯一,注册后所有 osgDB::writeNodeFile() 都会触发该回调。
  4. osgDB::Registry

    OSG 读写插件的全局管理器,负责:

    • 注册/注销读写回调;
    • 调用底层插件完成实际文件读写(readNodeImplementation/writeNodeImplementation);
    • 管理所有支持的文件格式(.osg/.obj/.fbx/.png 等)。

3. 核心虚函数(必须重载)

回调类 核心虚函数 触发时机
ReadFileCallback readNode(文件名, 配置参数) 调用 osgDB::readNodeFile() 读取模型时
WriteFileCallback writeNode(节点对象, 文件名, 配置参数) 调用 osgDB::writeNodeFile() 保存模型时

注意:重载函数中必须调用 OSG 底层的实现方法readNodeImplementation/writeNodeImplementation),否则文件无法真正读写。

核心原理

OSG 采用钩子(Hook)设计模式,回调的执行流程如下:

  1. 开发者自定义类继承回调抽象类,重载核心虚函数;
  2. osgDB::Registry 全局注册自定义回调;
  3. 调用 readNodeFile/writeNodeFile 时,OSG 优先触发回调函数;
  4. 回调中执行自定义逻辑(计时、日志、校验等);
  5. 回调调用 OSG 底层方法完成实际文件读写;
  6. 读写完成后,回调执行后续自定义逻辑,返回结果。

优势:无侵入式扩展,不修改 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. 代码核心说明

  1. 继承与重载 :自定义类严格继承 ReadFileCallback/WriteFileCallback,使用 override 关键字确保虚函数重载正确;
  2. 必须调用底层方法readNodeImplementation/writeNodeImplementation 是 OSG 实际读写文件的逻辑,不可省略;
  3. 全局注册 :通过 osgDB::Registry 单例注册回调,一次注册,全局生效;
  4. 内存安全 :回调对象由 osg::Referenced 管理,无需手动 delete

进阶扩展

基于 ReadFileCallbackWriteFileCallback,可实现大量企业级需求:

  1. 权限校验:读取前校验文件权限,拒绝非法文件访问;
  2. 加密解密:读取时解密加密模型,写入时加密保存,保护资源安全;
  3. 格式自动转换:将 .obj/.fbx 自动转为 .osg 缓存,提升加载速度;
  4. 全链路日志:记录所有文件读写的文件名、耗时、结果,用于性能监控;
  5. 异常处理:读写失败时自动重试、上报错误,提升程序稳定性。

总结

  1. 继承关系 :回调类最终继承自 osg::Referenced,依托 osgDB::Registry 实现全局管理;
  2. 核心价值:无侵入式扩展 OSG 读写逻辑,满足定制化开发需求;
  3. 使用关键:继承抽象类 → 重载虚函数 → 调用底层实现 → 全局注册;
  4. 实战价值 :广泛应用于三维仿真、数字孪生、游戏开发等 OSG 项目中,是进阶开发必备技能。
相关推荐
悠哉悠哉愿意2 小时前
【单片机复习笔记】十三届国赛复盘2
笔记·单片机·嵌入式硬件
每天吃饭的羊2 小时前
nest,java对比
java·开发语言
组合缺一2 小时前
SolonCode CLI v2026.4.11 发布(中文驱动的编码智能体)
java·ai编程·agents·solon-ai·claudecode·opencode·soloncode
大佐不会说日语~2 小时前
Spring AI Alibaba 的 Function Calling 使用 @Tool 调用中,无法获取用户ID踩坑记录
java·人工智能·spring boot·spring·alibaba·function
知识分享小能手2 小时前
MongoDB入门学习教程,从入门到精通,MongoDB 持久性完全指南(20)
数据库·学习·mongodb
Java基基2 小时前
Maven 4要来了:15年后,Java构建工具迎来“彻底重构”
java·开发语言·重构
zhanghongbin012 小时前
Remote Write:高效数据推送
java·人工智能
老鱼说AI2 小时前
长文预警!大模型面试:关于大模型微调的进阶与工程部署讲解
人工智能·深度学习·神经网络·学习·自然语言处理·面试·职场和发展
2301_822703202 小时前
鸿蒙flutter三方库适配——笔记与知识管理应用:Flutter Markdown实战
笔记·算法·flutter·华为·图形渲染·harmonyos·鸿蒙