
渲染到纹理(RTT)
在OpenSceneGraph(OSG)开发中,渲染到纹理(Render to Texture,简称RTT)是一项核心高级技术,其核心思想是将3D场景的渲染结果不直接输出到屏幕窗口,而是先渲染到一张纹理(Texture)中,再通过纹理映射将其显示在场景物体上,同时支持基于像素的后处理效果。
本文将结合完整可运行的OSG代码,详细解析RTT技术的原理、核心类继承关系、代码实现细节及完整执行流程,帮助开发者快速掌握这项实用技术。
核心原理
传统的3D渲染流程中,相机将场景渲染后直接输出到屏幕的帧缓冲区,最终呈现给用户;而RTT技术则通过"离屏相机"将场景渲染到内存中的纹理对象,再将该纹理贴到几何体(如四边形)上,实现场景的二次显示。
这种技术广泛应用于镜面反射、动态纹理生成、场景预览、像素级后处理(如颜色反转、模糊、边缘检测)等场景。
结合本文提供的完整代码,RTT的核心流程可概括为5步:
-
创建离屏相机(不直接显示在屏幕),配置其渲染参数;
-
创建纹理对象,用于接收离屏相机的渲染结果;
-
将3D模型挂载到离屏相机,由相机将模型渲染到纹理;
-
通过回调函数对纹理进行像素级后处理(本文实现颜色反转);
-
创建四边形几何体,将处理后的纹理映射到四边形上,最终在屏幕显示。
OSG RTT核心类继承关系
OSG的RTT技术依赖于多个核心类的协同工作,这些类均继承自OSG的基础类,形成清晰的继承体系。结合本文代码中用到的所有类,其完整继承关系如下(从顶层基类到具体实现类):
核心继承体系图
核心类功能解析(对应代码实现)
本文代码中,每一个核心类都承担着RTT流程的关键角色,其功能与代码中的具体应用一一对应:
-
osg::Camera:RTT的核心载体,通过配置为"离屏模式",将场景渲染到纹理而非屏幕。代码中通过设置渲染顺序、渲染目标实现方式、绑定纹理/图像等参数,完成离屏渲染配置。
-
osg::Texture2D:用于接收离屏相机的渲染结果,相当于"虚拟画布"。代码中设置纹理尺寸、内部格式(GL_RGBA)和过滤方式,确保渲染结果清晰。
-
osg::Image:作为像素数据的中间载体,连接相机渲染结果与纹理,支持像素级修改。代码中通过Image接收相机渲染数据,再将其绑定到Texture2D,同时供回调函数修改像素。
-
MyCameraPostDrawCallback:自定义回调类,继承自osg::Camera::DrawCallback,在相机完成渲染后自动执行,实现像素颜色反转的后处理效果。
-
osg::Geometry:创建显示纹理的四边形,通过设置顶点、纹理坐标、颜色等参数,将纹理映射到几何体表面,最终在屏幕显示。
-
osg::MatrixTransform:用于实现模型的自动旋转动画,通过添加更新回调,让飞机模型绕Y轴持续旋转,丰富RTT渲染效果。
完整代码实现
以下是完整可编译运行的OSG RTT代码(包含离屏渲染、像素后处理、模型动画),结合代码逐模块解析其核心逻辑,明确每一段代码在RTT流程中的作用。
c
#include <osg/Notify>
#include <osg/MatrixTransform>
#include <osg/Texture2D>
#include <osg/Stencil>
#include <osg/ColorMask>
#include <osg/GLExtensions>
#include <osg/Depth>
#include <osg/AnimationPath>
#include <osgDB/WriteFile>
#include <osgDB/ReadFile>
#include <osgUtil/Optimizer>
#include <iostream>
// 定义相机绘制后回调:实现像素颜色反转(仅处理图像中心区域)
struct MyCameraPostDrawCallback : public osg::Camera::DrawCallback
{
public:
MyCameraPostDrawCallback(osg::ref_ptr<osg::Image> image)
: _image(image)
{
}
virtual void operator()(const osg::Camera* /*camera*/) const
{
// 仅处理GL_RGBA格式、对应数据类型的图像
if (_image && _image->getPixelFormat() == GL_RGBA && _image->getDataType() == GL_UNSIGNED_BYTE)
{
// 计算图像中心区域(取中间1/2宽高的矩形)
int column_start = _image->s() / 4;
int column_end = 3 * column_start;
int row_start = _image->t() / 4;
int row_end = 3 * row_start;
// 遍历中心区域像素,执行颜色反转(255 - 原值)
for (int r = row_start; r < row_end; ++r)
{
unsigned char* data = _image->data(column_start, r);
for (int c = column_start; c < column_end; ++c)
{
// RGBA四个通道分别反转
*data = 255 - *data; ++data; // R
*data = 255 - *data; ++data; // G
*data = 255 - *data; ++data; // B
*data = 255 - *data; ++data; // A
}
}
// 标记图像数据已修改,通知OSG更新纹理
_image->dirty();
}
else if (_image && _image->getPixelFormat() == GL_RGBA && _image->getDataType() == GL_FLOAT)
{
// 浮点型像素的反转逻辑(1.0f - 原值)
int column_start = _image->s() / 4;
int column_end = 3 * column_start;
int row_start = _image->t() / 4;
int row_end = 3 * row_start;
for (int r = row_start; r < row_end; ++r)
{
float* data = (float*)_image->data(column_start, r);
for (int c = column_start; c < column_end; ++c)
{
*data = 1.0f - *data; ++data; // R
*data = 1.0f - *data; ++data; // G
*data = 1.0f - *data; ++data; // B
*data = 1.0f - *data; ++data; // A
}
}
_image->dirty();
}
}
osg::ref_ptr<osg::Image> _image;
};
// 创建预渲染子场景:将子场景渲染到纹理,支持后处理
osg::ref_ptr<osg::Node> createPreRenderSubGraph(
osg::ref_ptr<osg::Node> subgraph,
unsigned int tex_width,
unsigned int tex_height,
osg::Camera::RenderTargetImplementation renderImplementation,
bool useImage)
{
if (!subgraph) return nullptr;
// 1. 创建父Group节点,承载预渲染相机和显示用四边形
osg::ref_ptr<osg::Group> parent = new osg::Group;
// 2. 创建纹理,用于绑定相机渲染结果
osg::ref_ptr<osg::Texture> texture = nullptr;
{
osg::ref_ptr<osg::Texture2D> texture2D = new osg::Texture2D;
texture2D->setTextureSize(tex_width, tex_height);
texture2D->setInternalFormat(GL_RGBA);
// 设置纹理过滤:线性插值,避免锯齿
texture2D->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::LINEAR);
texture2D->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::LINEAR);
texture = texture2D;
}
// 3. 创建显示用四边形,用于展示预渲染的纹理
{
osg::ref_ptr<osg::Geometry> polyGeom = new osg::Geometry;
// 禁用显示列表,确保动态纹理实时更新
polyGeom->setSupportsDisplayList(false);
// 四边形尺寸(可根据需求调整)
float height = 100.0f;
float width = 200.0f;
// 3.1 顶点数组
osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;
vertices->push_back(osg::Vec3(0.0f, 0.0f, 0.0f));
vertices->push_back(osg::Vec3(width, 0.0f, 0.0f));
vertices->push_back(osg::Vec3(width, 0.0f, height));
vertices->push_back(osg::Vec3(0.0f, 0.0f, height));
polyGeom->setVertexArray(vertices.get());
// 3.2 纹理坐标数组(对应纹理UV)
osg::ref_ptr<osg::Vec2Array> texcoords = new osg::Vec2Array;
texcoords->push_back(osg::Vec2(0.0f, 0.0f));
texcoords->push_back(osg::Vec2(1.0f, 0.0f));
texcoords->push_back(osg::Vec2(1.0f, 1.0f));
texcoords->push_back(osg::Vec2(0.0f, 1.0f));
polyGeom->setTexCoordArray(0, texcoords.get());
// 3.3 颜色数组(白色,确保纹理颜色正常显示)
osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array;
colors->push_back(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
polyGeom->setColorArray(colors.get());
polyGeom->setColorBinding(osg::Geometry::BIND_OVERALL);
// 3.4 启用VBO,提升渲染性能
{
osg::ref_ptr<osg::VertexBufferObject> vbObject = new osg::VertexBufferObject;
vertices->setVertexBufferObject(vbObject);
polyGeom->setUseVertexBufferObjects(true);
}
// 3.5 添加图元(四边形)
polyGeom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, vertices->size()));
// 3.6 绑定纹理到四边形的StateSet
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
stateset->setTextureAttributeAndModes(0, texture.get(), osg::StateAttribute::ON);
polyGeom->setStateSet(stateset.get());
// 3.7 将四边形添加到Geode,再挂到父Group
osg::ref_ptr<osg::Geode> geode = new osg::Geode;
geode->addDrawable(polyGeom.get());
parent->addChild(geode.get());
}
// 4. 创建预渲染相机(RTT核心)
{
osg::ref_ptr<osg::Camera> camera = new osg::Camera;
// 4.1 设置相机清除属性
camera->setClearColor(osg::Vec4(0.1f, 0.1f, 0.3f, 1.0f)); // 背景色
camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 4.2 获取子场景包围盒,自动适配相机视锥
const osg::BoundingSphere& bs = subgraph->getBound();
if (!bs.valid())
return subgraph.get();
// 4.3 计算近/远裁剪面、投影参数
float znear = 1.0f * bs.radius();
float zfar = 3.0f * bs.radius();
float proj_top = 0.25f * znear;
float proj_right = 0.5f * znear;
znear *= 0.9f;
zfar *= 1.1f;
// 4.4 设置投影矩阵(正交投影,适配子场景)
camera->setProjectionMatrixAsFrustum(-proj_right, proj_right, -proj_top, proj_top, znear, zfar);
// 4.5 设置视图矩阵:相机对准子场景中心
camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
camera->setViewMatrixAsLookAt(
bs.center() - osg::Vec3(0.0f, 2.0f, 0.0f) * bs.radius(), // 相机位置
bs.center(), // 目标点
osg::Vec3(0.0f, 0.0f, 1.0f) // 上方向
);
// 4.6 设置视口大小(和纹理尺寸一致)
camera->setViewport(0, 0, tex_width, tex_height);
// 4.7 设置渲染顺序:PRE_RENDER,确保先渲染相机,再渲染主场景
camera->setRenderOrder(osg::Camera::PRE_RENDER);
// 4.8 设置渲染目标实现方式(FBO)
camera->setRenderTargetImplementation(renderImplementation);
// 4.9 绑定纹理/图像到相机颜色缓冲区
if (useImage)
{
// 方式1:通过Image中转,支持后处理回调修改像素
osg::ref_ptr<osg::Image> image = new osg::Image;
// 分配图像内存(这里用FLOAT类型,对应回调的浮点分支)
image->allocateImage(tex_width, tex_height, 1, GL_RGBA, GL_FLOAT);
// 将Image附加到相机COLOR_BUFFER
camera->attach(osg::Camera::COLOR_BUFFER, image.get());
// 添加后绘制回调,执行像素反转
camera->setPostDrawCallback(new MyCameraPostDrawCallback(image.get()));
// 将Image绑定到纹理
texture->setImage(0, image.get());
}
else
{
// 方式2:直接将纹理附加到相机,无后处理
camera->attach(osg::Camera::COLOR_BUFFER, texture.get());
}
// 4.10 挂载子场景到相机,相机将渲染该子场景到纹理
camera->addChild(subgraph.get());
// 4.11 将相机挂载到父Group
parent->addChild(camera.get());
}
return parent.get();
}
int main()
{
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer;
// 纹理尺寸
unsigned int tex_width = 1024;
unsigned int tex_height = 512;
// 渲染目标实现方式:使用FBO(推荐)
osg::Camera::RenderTargetImplementation renderImplementation = osg::Camera::FRAME_BUFFER_OBJECT;
// 是否启用Image后处理(true=开启像素反转,false=直接渲染)
bool useImage = true;
// 读取模型(替换为你的模型路径,支持.osg/.ive/.obj等格式)
osg::ref_ptr<osg::Node> loadedModel = osgDB::readNodeFile("cessna.osg");
if (!loadedModel)
{
std::cerr << "Error: Failed to load model cessna.osg!" << std::endl;
return 1;
}
// 创建变换节点,用于模型自动旋转
osg::ref_ptr<osg::MatrixTransform> loadedModelTransform = new osg::MatrixTransform;
loadedModelTransform->addChild(loadedModel.get());
// 添加更新回调:让模型绕Y轴以45°/秒的速度旋转
osg::ref_ptr<osg::NodeCallback> nc = new osg::AnimationPathCallback(
loadedModelTransform->getBound().center(),
osg::Vec3(0.0f, 0.0f, 1.0f), // 旋转轴(Y轴)
osg::inDegrees(45.0f) // 旋转速度(45°/秒)
);
loadedModelTransform->setUpdateCallback(nc);
// 创建根节点
osg::ref_ptr<osg::Group> rootNode = new osg::Group;
// 挂载预渲染子场景
rootNode->addChild(createPreRenderSubGraph(
loadedModelTransform.get(),
tex_width,
tex_height,
renderImplementation,
useImage
));
// 优化场景数据(合并状态、剔除冗余节点等)
osgUtil::Optimizer optimizer;
optimizer.optimize(rootNode.get());
// 设置场景数据到Viewer
viewer->setSceneData(rootNode.get());
// 初始化Viewer
viewer->realize();
// 运行渲染循环
return viewer->run();
}
模块1:自定义后处理回调类(MyCameraPostDrawCallback)
该类继承自osg::Camera::DrawCallback,是RTT后处理的核心。其作用是在离屏相机完成渲染后,自动对渲染结果(存储在osg::Image中)进行像素级修改。
核心逻辑:
-
构造函数接收osg::Image对象,用于获取相机渲染的像素数据;
-
重载operator()方法(回调执行入口),OSG引擎会在相机渲染完成后自动调用;
-
判断图像格式(GL_RGBA)和数据类型(GL_UNSIGNED_BYTE或GL_FLOAT),避免异常;
-
计算图像中心区域(中间1/2宽高),遍历该区域的每一个像素,对RGBA四个通道分别执行反转操作(8位像素用255-原值,浮点型像素用1.0f-原值);
-
调用_image->dirty(),通知OSG引擎图像数据已修改,确保纹理同步更新。
模块2:RTT核心函数(createPreRenderSubGraph)
该函数是RTT流程的核心实现,负责创建离屏相机、纹理、显示用四边形,并将三者关联,完成"渲染→存储→显示"的链路搭建。
核心步骤解析:
-
创建父Group节点:作为容器,承载离屏相机和显示用四边形,统一管理场景节点;
-
创建Texture2D纹理:设置纹理尺寸(与相机视口一致)、内部格式(GL_RGBA,支持透明)和过滤方式(线性插值,避免锯齿),用于接收相机渲染结果;
-
创建显示用四边形:
-
创建osg::Geometry对象,定义四边形的顶点、纹理坐标、颜色;
-
设置纹理坐标(0.0f~1.0f),确保纹理完整映射到四边形;
-
将纹理绑定到四边形的StateSet,让四边形显示纹理内容;
-
禁用显示列表、启用VBO,确保动态纹理实时更新,提升渲染性能。
-
-
创建离屏相机(RTT核心):
-
设置清除属性:背景色(深蓝色)、清除掩码(清除颜色和深度缓冲区),确保每次渲染都是干净的画面;
-
适配相机视锥:通过模型的包围球(osg::BoundingSphere)自动计算近裁剪面(znear)、远裁剪面(zfar)和视野范围(proj_top、proj_right),确保模型完整显示,不被切割;
-
设置投影矩阵和视图矩阵:采用正交投影,相机位置对准模型中心,确保模型在视锥内;
-
配置RTT关键参数:设置渲染顺序(PRE_RENDER,先渲染到纹理)、渲染目标实现方式(FBO,离屏渲染),绑定Image到相机颜色缓冲区,让相机渲染结果写入Image;
-
挂载模型和回调:将旋转的飞机模型挂载到相机,设置后处理回调,完成相机配置。
-
模块3:主函数(main)
主函数是程序的入口,负责初始化OSG窗口、加载模型、创建RTT场景、启动渲染循环。
核心逻辑:
-
创建osgViewer::Viewer对象,用于显示场景窗口;
-
配置RTT参数:纹理尺寸、渲染目标实现方式(FBO)、是否启用后处理;
-
加载模型:读取cessna.osg飞机模型,添加错误处理,避免模型加载失败导致程序崩溃;
-
实现模型动画:通过osg::MatrixTransform和osg::AnimationPathCallback,让模型绕Y轴以45°/秒的速度旋转;
-
创建场景根节点,挂载RTT子场景,优化场景数据,启动渲染循环,完成RTT效果的展示。
RTT完整执行流程(按帧渲染顺序)
结合本文代码,OSG RTT的每帧执行流程清晰可追溯,具体步骤如下:
-
OSG引擎启动渲染循环,开始一帧的渲染;
-
预渲染相机(osg::Camera)按PRE_RENDER顺序优先执行渲染;
-
离屏相机渲染挂载在其下的旋转飞机模型,渲染结果写入绑定的osg::Image对象;
-
相机渲染完成后,自动调用MyCameraPostDrawCallback回调函数,对Image中的中心区域像素执行颜色反转,并标记Image数据已修改;
-
osg::Texture2D同步Image中的修改后数据,获取最终的后处理纹理;
-
显示用四边形读取Texture2D中的纹理数据,通过纹理映射将其显示在屏幕上;
-
引擎完成一帧渲染,重复上述流程,实现动态的RTT后处理效果(旋转的飞机+中心反色)。
技术要点与注意事项
5核心技术要点
-
离屏相机配置:必须设置渲染顺序(PRE_RENDER)、渲染目标实现方式(FBO)、视口尺寸(与纹理一致),否则无法实现RTT;
-
纹理与Image绑定:通过camera->attach()将Image绑定到相机颜色缓冲区,再通过texture->setImage()将Image数据同步到纹理,实现渲染结果的传递;
-
像素后处理:回调函数中必须调用image->dirty(),否则OSG引擎不会更新纹理,后处理效果无法显示;
-
相机视锥适配:通过模型包围球自动计算视锥参数,避免模型被裁剪,确保渲染完整性。
5常见问题与解决方案
-
模型加载失败:确保cessna.osg模型文件放在可执行文件同目录,或修改代码中的模型路径为绝对路径;
-
纹理不显示:检查纹理尺寸与相机视口是否一致,纹理过滤方式是否正确,是否调用image->dirty();
-
模型被切割:调整znear、zfar、proj_top、proj_right参数,扩大相机视锥范围,或微调相机位置;
-
后处理效果不生效:检查回调类是否正确挂载到相机,图像格式与数据类型是否匹配。
总结
渲染到纹理(RTT)是OSG中一项基础且实用的高级技术,其核心是通过离屏相机将场景渲染到纹理,再结合纹理映射和像素后处理,实现丰富的视觉效果。本文结合完整可运行的代码,详细解析了RTT的原理、核心类继承关系、代码实现细节及执行流程,涵盖了离屏相机配置、纹理创建、后处理回调、模型动画等关键知识点。
