【OSG学习笔记】Day 45: osg::Camera::DrawCallback (抓取图片)

osg::Camera::DrawCallback (抓取图片)

在OpenSceneGraph(OSG)三维渲染开发中,相机(Camera) 是场景渲染的核心入口,控制着三维场景到二维屏幕的投影、绘制流程等关键逻辑。

osg::Camera::DrawCallback作为OSG提供的相机绘制阶段回调机制,是实现帧缓存读取、屏幕截图、渲染后处理、自定义绘制等高级功能的核心技术。

本文将结合OSG屏幕截图实战代码 ,深度解析osg::Camera::DrawCallback的继承关系、工作原理、使用方法,帮你彻底掌握这一OSG核心组件。

基础认知

1. 核心定义

osg::Camera::DrawCallback是OSG中专门用于监听相机渲染流程 的抽象回调类,它允许开发者在相机渲染的前置阶段(PreDraw)后置阶段(PostDraw) 插入自定义逻辑,无需修改OSG底层渲染源码。

简单来说:它是相机渲染的「钩子函数」,相机渲染到指定阶段时,会自动执行我们重写的回调逻辑。

2. 核心应用场景

结合本文截图代码,该回调最常用的场景包括:

  1. 屏幕截图/帧缓存读取(PostDraw阶段,渲染完成后读取像素)
  2. 渲染后处理(滤镜、灰度、模糊等效果)
  3. 自定义绘制(叠加UI、标记、辅助图形)
  4. 渲染状态监控/性能统计

类的继承关系(核心图谱)

osg::Camera::DrawCallback并非孤立存在,它遵循OSG的引用计数+继承架构,完整继承关系如下:

复制代码
osg::Referenced (OSG所有智能指针管理类的基类)
        ↓ 继承
osg::Camera::DrawCallback (抽象回调基类)
        ↓ 继承
自定义回调类(如本文的 CaptureDrawCallback)

关键说明:

  1. osg::Referenced

    OSG所有需要自动内存管理的类的顶级基类,提供引用计数 功能,配合osg::ref_ptr智能指针使用,避免内存泄漏。

    ✅ 本文中CaptureDrawCallback无需手动释放,由OSG自动管理生命周期。

  2. osg::Camera::DrawCallback

    纯虚抽象类,核心接口:

    cpp 复制代码
    virtual void operator()(const osg::Camera& camera) const = 0;

    这是必须重写的纯虚函数,自定义逻辑全部写在该函数内。

  3. 自定义子类(CaptureDrawCallback)

    继承DrawCallback,重写operator()实现具体功能(本文中为读取帧缓存、截图数据采集)。


相机渲染流程与回调执行时机

OSG相机的渲染分为三个核心阶段,回调的执行时机直接决定功能是否生效

回调类型 执行时机 适用场景 本文使用情况
PreDrawCallback 相机开始渲染之前 渲染前修改状态、清空缓存 不适用
DrawCallback 相机渲染过程中 中间层自定义绘制 不适用
PostDrawCallback 相机渲染完成后 读取像素、截图、后处理 ✅ 核心使用

关键结论:

屏幕截图必须使用 PostDrawCallback

因为只有渲染完成后,GPU帧缓存才有完整的场景像素数据,此时调用readPixels才能拿到有效画面,否则会出现黑屏、残缺等问题。


结合截图代码:DrawCallback 实战解析

全部代码:

c 复制代码
#include <osgViewer/Viewer>
#include <osgViewer/GraphicsWindow>

#include <osg/Node>
#include <osg/Geode>
#include <osg/Group>
#include <osg/Camera>
#include <osg/Image>
#include <osg/BufferObject>

#include <osgDB/ReadFile>
#include <osgDB/WriteFile>

#include <osgUtil/Optimizer>

#include <iostream>

// 定义全局变量,用于在回调和事件处理器之间传递图像数据
osg::ref_ptr<osg::Image> image_c = new osg::Image();

/***********************************************************************
 * 相机绘制回调:在绘制完成后读取帧缓存
 * 继承自 osg::Camera::DrawCallback,在相机绘制流程中被调用
 ***********************************************************************/
struct CaptureDrawCallback : public osg::Camera::DrawCallback
{
public:
    // 构造函数:接收外部传入的osg::Image对象
    CaptureDrawCallback(osg::ref_ptr<osg::Image> image)
    {
        _image = image;
    }

    ~CaptureDrawCallback() {}

    // 重载operator():绘制回调的核心执行函数
    virtual void operator()(const osg::Camera& camera) const
    {
        // 1. 获取窗口系统接口(跨平台抽象层,适配不同操作系统的窗口API)
        osg::ref_ptr<osg::GraphicsContext::WindowingSystemInterface> wsi =
            osg::GraphicsContext::getWindowingSystemInterface();

        unsigned int width, height;
        // 2. 获取主屏幕分辨率(也可以根据窗口句柄获取窗口大小)
        wsi->getScreenResolution(osg::GraphicsContext::ScreenIdentifier(0), width, height);

        // 3. 为Image分配内存:宽、高、像素格式、数据类型
        _image->allocateImage(width, height, 1, GL_RGB, GL_UNSIGNED_BYTE);

        // 4. 读取帧缓存数据:从(0,0)开始,读取整个窗口的RGB像素数据
        _image->readPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE);
    }

    // 成员变量:存储要写入数据的Image对象
    osg::ref_ptr<osg::Image> _image;
};

/***********************************************************************
 * 事件处理器:处理键盘输入,触发截图操作
 * 继承自 osgGA::GUIEventHandler,用于捕获用户交互事件
 ***********************************************************************/
class ImageHandler : public osgGA::GUIEventHandler
{
public:
    ImageHandler() {}
    ~ImageHandler() {}

    // 重载handle()函数:事件处理的核心逻辑
    virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
    {
        // 1. 将ActionAdapter转换为Viewer指针,获取Viewer实例
        osg::ref_ptr<osgViewer::Viewer> viewer = dynamic_cast<osgViewer::Viewer*>(&aa);
        if (viewer == nullptr)
            return false;

        // 静态变量:记录截图序号,实现连续截图(ScreenShot000.bmp, 001.bmp...)
        static int _screenCaptureSequence = 0;

        // 2. 判断事件类型:键盘按下事件
        switch (ea.getEventType())
        {
            case osgGA::GUIEventAdapter::KEYDOWN:
            {
                // 按下'c'或'C'键时触发截图
                if (ea.getKey() == 'c' || ea.getKey() == 'C')
                {
                    char filename[128];
                    // 格式化文件名,生成带序号的BMP文件
                    sprintf(filename, "ScreenShot%04d.bmp", _screenCaptureSequence++);

                    // 写入文件:使用osgDB工具类,将Image数据保存为BMP格式
                    osgDB::writeImageFile(*(image_c.get()), filename);
                }
                break;
            }
            default:
                // 其他事件不处理,返回false表示事件未被消费
                return false;
        }

        // 返回true表示事件已被处理
        return true;
    }
};

/***********************************************************************
 * 主函数:程序入口
 ***********************************************************************/
int main()
{
    // 1. 创建Viewer实例(OSG渲染主循环控制器)
    osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();

    // 2. 创建场景根节点
    osg::ref_ptr<osg::Group> root = new osg::Group();

    // 3. 读取外部模型(示例为cow.osg,可替换为其他OSG支持的模型格式)
    osg::ref_ptr<osg::Node> node = osgDB::readNodeFile("cow.osg");
    if (node == nullptr)
    {
        std::cerr << "Error: 无法读取模型文件 cow.osg,请确保文件在程序目录下!" << std::endl;
        return -1;
    }
    root->addChild(node.get());

    // 4. 场景优化:使用OSG内置优化器,提升渲染性能
    osgUtil::Optimizer optimizer;
    optimizer.optimize(root.get());

    // 5. 设置相机绘制回调:在绘制完成后读取帧缓存
    viewer->getCamera()->setPostDrawCallback(new CaptureDrawCallback(image_c.get()));

    // 6. 设置场景数据:将根节点传入Viewer
    viewer->setSceneData(root.get());

    // 7. 添加事件处理器:注册截图按键事件
    viewer->addEventHandler(new ImageHandler());

    // 8. 初始化窗口并启动渲染主循环
    viewer->realize();
    viewer->run();

    return 0;
}

1. 自定义回调类定义(继承与重写)

cpp 复制代码
// 1. 继承 osg::Camera::DrawCallback
struct CaptureDrawCallback : public osg::Camera::DrawCallback
{
public:
    // 构造函数:接收图像对象,用于存储截图数据
    CaptureDrawCallback(osg::ref_ptr<osg::Image> image)
    {
        _image = image;
    }

    // 2. 重写纯虚函数 operator() → 核心逻辑入口
    virtual void operator()(const osg::Camera& camera) const
    {
        // 获取屏幕/窗口尺寸
        unsigned int width, height;
        auto wsi = osg::GraphicsContext::getWindowingSystemInterface();
        wsi->getScreenResolution(osg::GraphicsContext::ScreenIdentifier(0), width, height);

        // 分配图像内存
        _image->allocateImage(width, height, 1, GL_RGB, GL_UNSIGNED_BYTE);
        
        // 3. 核心功能:读取GPU帧缓存像素数据(截图核心)
        _image->readPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE);
    }

    // 成员变量:存储像素数据
    osg::ref_ptr<osg::Image> _image;
};
代码关键点:
  • 严格继承osg::Camera::DrawCallback,遵循OSG继承规范;
  • 重写operator()纯虚函数,这是回调的唯一入口;
  • 函数内实现帧缓存读取 ,将GPU数据同步到CPU的osg::Image中。

2. 回调注册到相机

main函数中,将自定义回调绑定到相机的PostDraw阶段:

cpp 复制代码
// 注册后置绘制回调 → 渲染完成后自动执行
viewer->getCamera()->setPostDrawCallback(new CaptureDrawCallback(image_c.get()));

这一步是回调生效的关键 :告诉OSG相机,渲染完成后执行CaptureDrawCallback的逻辑。

3. 配合事件处理器完成截图

DrawCallback负责采集数据 ,键盘事件处理器负责保存数据

cpp 复制代码
// 按下c键,将Image数据写入文件
osgDB::writeImageFile(*(image_c.get()), filename);

使用核心规范

  1. 必须继承+重写

    自定义类必须公有继承osg::Camera::DrawCallback,且必须实现operator()纯虚函数。

  2. const 约束不可忽略
    operator()函数是const修饰的,函数内不能修改类的非mutable成员变量。

  3. 执行时机选对

    • 截图/后处理 → setPostDrawCallback
    • 渲染前初始化 → setPreDrawCallback
  4. 内存自动管理

    基于osg::Referenced,回调对象用new创建即可,无需手动delete


总结

osg::Camera::DrawCallback是OSG相机渲染的核心扩展接口 ,它通过继承抽象类+回调钩子的设计,让开发者可以灵活介入相机渲染流程。

结合本文截图代码,我们可以总结其核心价值:

  1. 继承关系osg::Referencedosg::Camera::DrawCallback → 自定义回调;
  2. 核心作用:在相机渲染的指定阶段执行自定义逻辑;
  3. 实战用法:PostDraw回调读取帧缓存 + 事件处理器保存图像,实现稳定截图;
  4. 设计优势:无侵入式扩展,不修改OSG源码,满足个性化渲染需求。
相关推荐
青苔猿猿4 小时前
OpenWebUI(20)源码学习-版本升级
人工智能·学习·ai·openwebui
不灭锦鲤4 小时前
网络安全学习第166天
学习
Java面试题总结4 小时前
2026年Java面试题最新整理,附白话答案
java·开发语言·jvm·笔记·spring·intellij-idea
Century_Dragon5 小时前
世纪龙-驶入未来课堂:新能源汽车故障诊断虚拟实训软件助力职教
学习
Westward-sun.5 小时前
OpenCV + dlib 人脸关键点检测学习笔记(68点)
人工智能·笔记·opencv·学习·计算机视觉
chushiyunen5 小时前
阿里云部署dify笔记
笔记·阿里云·云计算
大邳草民5 小时前
Python 对象模型与属性访问机制
开发语言·笔记·python
kinl20186 小时前
cs2385_note5 (lec18-lec19) Variational Inference & Control as Inference
笔记
red_redemption6 小时前
自由学习记录(165)
学习