


由于 osg::Image::readImageFromCurrentTexture 属于底层 OpenGL 操作的封装,它要求您在使用前手动绑定目标纹理。
下面是使用 readImageFromCurrentTexture 方法读取 colordepthTexture 数据的完整实现细节,包括所需的头文件、回调类定义,以及核心逻辑。
📝 完整实现代码:使用 readImageFromCurrentTexture
我们将修改您现有的 SaveImageCallback 类,以实现手动绑定和读取。
1. 所需头文件
除了常用的 OSG 头文件外,您需要引入 osg/State 和 osg/GLExtensions 来处理 OpenGL 状态和纹理绑定。
c++
#include <osg/Texture2D>
#include <osg/Image>
#include <osg/Camera>
#include <osg/RenderInfo>
#include <osg/State> // 用于获取 OpenGL 状态
#include <osg/GLExtensions> // 包含 OpenGL 上下文和绑定函数
#include <osgDB/WriteFile> // 用于保存文件
#include <iostream>
#include <string>
// 假设 Config 结构体和 SaveRequestHandler 已经定义
// struct Config {};
// class SaveRequestHandler : public osgGA::GUIEventHandler {...};
2. SaveImageCallback 类定义和实现
我们将使用 readImageFromCurrentTexture 方法替换掉之前的 copyImage 调用。
c++
class SaveImageCallback : public osg::Camera::PostDrawCallback
{
private:
osg::ref_ptr<osg::Texture2D> _sourceTexture;
osg::ref_ptr<osg::Image> _targetImage;
bool _saveRequested = false;
// 可以添加 Config 成员,这里为简化省略
public:
// 构造函数:接受源纹理和目标Image
SaveImageCallback(osg::Texture2D* sourceTexture, osg::Image* targetImage)
: _sourceTexture(sourceTexture), _targetImage(targetImage) {}
void requestSave() {
_saveRequested = true;
}
void operator()(osg::RenderInfo& renderInfo) const override
{
if (!_saveRequested) return;
osg::State* state = renderInfo.getState();
unsigned int contextID = renderInfo.getContextID();
if (!_targetImage->valid() || !_sourceTexture->valid()) {
std::cerr << "错误: 目标 Image 或源 Texture 无效,无法读取。" << std::endl;
_saveRequested = false;
return;
}
// --- 核心逻辑:手动绑定纹理并读取 ---
// 1. 获取 Texture2D 的底层 OpenGL ID
GLuint textureID = _sourceTexture->getTextureObject(contextID)->id();
// 2. 绑定纹理到 GL_TEXTURE_2D 目标
// 注意:这里需要确保 GL 函数被正确调用。在 OSG 环境中,可以直接使用 state->apply(Attribute) 或 gles(contextID) 来管理。
// 最直接的方式是调用 OpenGL 扩展函数,但为了跨平台和 OSG 兼容性,我们使用 OSG 封装的方法。
// 绑定纹理:这确保了 OpenGL 知道我们要从哪个纹理读取数据
state->get
(contextID)->glActiveTexture(GL_TEXTURE0 + _sourceTexture->getTextureUnit()); // 激活纹理单元
state->getExtensions(contextID)->glBindTexture(GL_TEXTURE_2D, textureID); // 绑定纹理 ID
std::cout << "DEBUG: 成功绑定 Texture ID " << textureID << " 到 GL_TEXTURE_2D。" << std::endl;
// 3. 调用 readImageFromCurrentTexture
// 参数:
// contextID: 渲染上下文 ID
// copyMipMapsIfAvailable: false (我们只关心 Level 0 的高程数据)
// type: GL_FLOAT (因为 colordepthTexture 的 SourceType 是 GL_FLOAT)
// face: 0 (不是立方体贴图)
// Image 在调用前必须分配好内存,这已在 main 函数中完成 (GL_R32F, GL_FLOAT)
_targetImage->readImageFromCurrentTexture(
contextID,
false, // 不拷贝 MipMaps
GL_FLOAT, // 期望读取的数据类型是 GL_FLOAT
0 // Texture Face 0 (默认)
);
// 4. (可选但推荐) 恢复状态:解除纹理绑定
state->getExtensions(contextID)->glBindTexture(GL_TEXTURE_2D, 0);
// ------------------------------------
// 5. 保存数据到文件
std::string filename = "ActualZ_HeightMap_ManualRead.tiff";
// 检查 Image 的格式是否正确 (应为 GL_RED, GL_FLOAT)
if (_targetImage->getDataType() == GL_FLOAT && _targetImage->getPixelFormat() == GL_RED)
{
std::cout << "INFO: Image 数据格式确认:GL_RED/GL_FLOAT。" << std::endl;
// 尝试保存为 TIFF 或其他支持浮点数的格式
if (osgDB::writeImageFile(*_targetImage, filename))
{
std::cout << "✅ 实际 Z 高度图已保存为: " << filename << std::endl;
}
else
{
std::cerr << "❌ 保存失败: OSG可能不支持直接保存 GL_FLOAT TIFF。请考虑手动写入二进制文件。" << std::endl;
// 如果需要,可以在这里添加手动写入浮点数数组的逻辑
}
}
else
{
std::cerr << "❌ 警告: readImageFromCurrentTexture 后的 Image 格式不匹配期望。" << std::endl;
}
_saveRequested = false;
}
};
3. 主程序调用修改
在主程序中,您只需要确保回调函数接收的参数顺序和类型与新的 SaveImageCallback 构造函数匹配。
原代码:
c++
osg::ref_ptr<SaveImageCallback> saveCallback = new SaveImageCallback(
// 传入最终的高程结果 Image
colordepthTexture.get(), elevationImage.get(), NULL,config // 这里的参数列表需要调整
);
修改后的(假设您只传入了 Texture 和 Image):
c++
// 调整为新的构造函数顺序 (Texture, Image)
osg::ref_ptr<SaveImageCallback> saveCallback = new SaveImageCallback(
colordepthTexture.get(), // Source Texture
elevationImage.get() // Target Image
);
为什么 copyImage 更常用?
使用 readImageFromCurrentTexture 需要您手动处理 OpenGL 状态(激活纹理单元、绑定纹理 ID),这使得代码与底层的 ContextID 和 GLExtensions 紧密耦合,增加了出错的可能性。
而 osg::Texture2D::copyImage(*image, ...) 内部已经包含了上述所有复杂的状态管理和底层调用,是 OSG 推荐和更健壮的纹理读回方式。
您提供的代码使用了 osg::Texture2D (colordepthTexture) 作为 RTT 目标,并且您希望使用 osg::Image::readImageFromCurrentTexture 方法从这个纹理中读取数据。
要使用 readImageFromCurrentTexture,您不能直接在 SaveImageCallback 中调用它,因为该方法需要先在 GPU 渲染上下文中显式地绑定 colordepthTexture。
由于 osg::Texture::copyImage() 方法已经为您封装了所有这些底层细节(绑定纹理、调用 glGetTexImage 等),强烈建议您优先使用 colordepthTexture->copyImage(...)。
替代方案:使用 readImageFromCurrentTexture(手动实现)
如果您坚持使用 osg::Image::readImageFromCurrentTexture,您必须手动处理纹理的绑定,这使得代码更复杂且容易出错。
在 SaveImageCallback::operator() 中实现此逻辑需要以下步骤:
- 获取当前 OpenGL 状态。
- 在当前上下文中使用
_sourceTexture->apply(state)显式绑定纹理。 - 调用
_targetImage->readImageFromCurrentTexture(...)。 - 解除纹理绑定(可选,但推荐)。
修改后的 SaveImageCallback 逻辑如下:
c++
#include <osg/Texture2D>
#include <osg/Image>
#include <osg/Camera>
#include <osg/RenderInfo>
#include <osg/State> // 需要 State 头文件
// ... (SaveImageCallback 结构与构造函数保持不变) ...
void operator()(osg::RenderInfo& renderInfo) const override
{
if (!_saveRequested) return;
osg::State* state = renderInfo.getState();
unsigned int contextID = renderInfo.getContextID();
// 1. **显式绑定纹理** 到 GL_TEXTURE_2D 目标
// (这一步是使用 readImageFromCurrentTexture 的关键)
_sourceTexture->apply(*state); // 自动绑定到 GL_TEXTURE_2D
// 2. 调用 readImageFromCurrentTexture
// 参数设置:
// contextID: 渲染上下文 ID
// copyMipMapsIfAvailable: false (通常 RTT 只需要 Level 0)
// type: GL_FLOAT (匹配 colordepthTexture 的 SourceType)
// face: 0 (不是 Cube Map)
_targetImage->readImageFromCurrentTexture(
contextID,
false, // 不拷贝 MipMaps
GL_FLOAT, // 期望读取的数据类型是 GL_FLOAT
0 // Texture Face 0
);
// **注意:** readImageFromCurrentTexture 内部会根据 Texture 的 InternalFormat (GL_R32F)
// 自动设置正确的 GL_RED 格式来调用 glGetTexImage。
// 3. (可选) 解除纹理绑定或恢复状态
// osg::Texture::unapplyTexture(state, GL_TEXTURE_2D);
// **保存数据到文件...** (与前一方案相同)
_saveRequested = false;
}
// ...
总结:
虽然 readImageFromCurrentTexture 可以工作,但由于您的 colordepthTexture 是一个 osg::Texture2D 实例,使用 _sourceTexture->copyImage(...) 是最简单、最安全、最推荐的方法。它避免了您手动管理 OpenGL 纹理绑定的复杂性。