【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. 核心应用场景
结合本文截图代码,该回调最常用的场景包括:
- 屏幕截图/帧缓存读取(PostDraw阶段,渲染完成后读取像素)
- 渲染后处理(滤镜、灰度、模糊等效果)
- 自定义绘制(叠加UI、标记、辅助图形)
- 渲染状态监控/性能统计
类的继承关系(核心图谱)
osg::Camera::DrawCallback并非孤立存在,它遵循OSG的引用计数+继承架构,完整继承关系如下:
osg::Referenced (OSG所有智能指针管理类的基类) ↓ 继承 osg::Camera::DrawCallback (抽象回调基类) ↓ 继承 自定义回调类(如本文的 CaptureDrawCallback)关键说明:
osg::Referenced
OSG所有需要自动内存管理的类的顶级基类,提供引用计数功能,配合osg::ref_ptr智能指针使用,避免内存泄漏。
✅ 本文中CaptureDrawCallback无需手动释放,由OSG自动管理生命周期。osg::Camera::DrawCallback
纯虚抽象类,核心接口:virtualvoidoperator()(constosg::Camera&camera)const=0;这是必须重写的纯虚函数,自定义逻辑全部写在该函数内。
自定义子类(CaptureDrawCallback)
继承DrawCallback,重写operator()实现具体功能(本文中为读取帧缓存、截图数据采集)。
相机渲染流程与回调执行时机
OSG相机的渲染分为三个核心阶段,回调的执行时机直接决定功能是否生效:
| 回调类型 | 执行时机 | 适用场景 | 本文使用情况 |
|---|---|---|---|
| PreDrawCallback | 相机开始渲染之前 | 渲染前修改状态、清空缓存 | 不适用 |
| DrawCallback | 相机渲染过程中 | 中间层自定义绘制 | 不适用 |
| PostDrawCallback | 相机渲染完成后 | 读取像素、截图、后处理 | ✅ 核心使用 |
关键结论:
屏幕截图必须使用 PostDrawCallback!
因为只有渲染完成后,GPU帧缓存才有完整的场景像素数据,此时调用readPixels才能拿到有效画面,否则会出现黑屏、残缺等问题。
结合截图代码:DrawCallback 实战解析
全部代码:
#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,在相机绘制流程中被调用 ***********************************************************************/structCaptureDrawCallback:public osg::Camera::DrawCallback{public:// 构造函数:接收外部传入的osg::Image对象CaptureDrawCallback(osg::ref_ptr<osg::Image>image){_image=image;}~CaptureDrawCallback(){}// 重载operator():绘制回调的核心执行函数virtualvoidoperator()(constosg::Camera&camera)const{// 1. 获取窗口系统接口(跨平台抽象层,适配不同操作系统的窗口API)osg::ref_ptr<osg::GraphicsContext::WindowingSystemInterface>wsi=osg::GraphicsContext::getWindowingSystemInterface();unsignedintwidth,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 boolhandle(constosgGA::GUIEventAdapter&ea,osgGA::GUIActionAdapter&aa){// 1. 将ActionAdapter转换为Viewer指针,获取Viewer实例osg::ref_ptr<osgViewer::Viewer>viewer=dynamic_cast<osgViewer::Viewer*>(&aa);if(viewer==nullptr)returnfalse;// 静态变量:记录截图序号,实现连续截图(ScreenShot000.bmp, 001.bmp...)staticint_screenCaptureSequence=0;// 2. 判断事件类型:键盘按下事件switch(ea.getEventType()){caseosgGA::GUIEventAdapter::KEYDOWN:{// 按下'c'或'C'键时触发截图if(ea.getKey()=='c'||ea.getKey()=='C'){charfilename[128];// 格式化文件名,生成带序号的BMP文件sprintf(filename,"ScreenShot%04d.bmp",_screenCaptureSequence++);// 写入文件:使用osgDB工具类,将Image数据保存为BMP格式osgDB::writeImageFile(*(image_c.get()),filename);}break;}default:// 其他事件不处理,返回false表示事件未被消费returnfalse;}// 返回true表示事件已被处理returntrue;}};/*********************************************************************** * 主函数:程序入口 ***********************************************************************/intmain(){// 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(newCaptureDrawCallback(image_c.get()));// 6. 设置场景数据:将根节点传入Viewerviewer->setSceneData(root.get());// 7. 添加事件处理器:注册截图按键事件viewer->addEventHandler(newImageHandler());// 8. 初始化窗口并启动渲染主循环viewer->realize();viewer->run();return0;}1. 自定义回调类定义(继承与重写)
// 1. 继承 osg::Camera::DrawCallbackstructCaptureDrawCallback:publicosg::Camera::DrawCallback{public:// 构造函数:接收图像对象,用于存储截图数据CaptureDrawCallback(osg::ref_ptr<osg::Image>image){_image=image;}// 2. 重写纯虚函数 operator() → 核心逻辑入口virtualvoidoperator()(constosg::Camera&camera)const{// 获取屏幕/窗口尺寸unsignedintwidth,height;autowsi=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阶段:
// 注册后置绘制回调 → 渲染完成后自动执行viewer->getCamera()->setPostDrawCallback(newCaptureDrawCallback(image_c.get()));这一步是回调生效的关键:告诉OSG相机,渲染完成后执行CaptureDrawCallback的逻辑。
3. 配合事件处理器完成截图
DrawCallback负责采集数据,键盘事件处理器负责保存数据:
// 按下c键,将Image数据写入文件osgDB::writeImageFile(*(image_c.get()),filename);使用核心规范
必须继承+重写
自定义类必须公有继承osg::Camera::DrawCallback,且必须实现operator()纯虚函数。const 约束不可忽略
operator()函数是const修饰的,函数内不能修改类的非mutable成员变量。执行时机选对
- 截图/后处理 →
setPostDrawCallback - 渲染前初始化 →
setPreDrawCallback
- 截图/后处理 →
内存自动管理
基于osg::Referenced,回调对象用new创建即可,无需手动delete。
总结
osg::Camera::DrawCallback是OSG相机渲染的核心扩展接口,它通过继承抽象类+回调钩子的设计,让开发者可以灵活介入相机渲染流程。
结合本文截图代码,我们可以总结其核心价值:
- 继承关系:
osg::Referenced→osg::Camera::DrawCallback→ 自定义回调; - 核心作用:在相机渲染的指定阶段执行自定义逻辑;
- 实战用法:PostDraw回调读取帧缓存 + 事件处理器保存图像,实现稳定截图;
- 设计优势:无侵入式扩展,不修改OSG源码,满足个性化渲染需求。
