RTT(Render to Texture)即渲染到纹理。在普通的图形渲染流程中,最终结果是渲染到帧缓存中,然后才会显示到屏幕上。而RTT则是将场景渲染到一张纹理上,并且在之后进行使用。
1)创建纹理对象
要进行RTT,首先要创建一个纹理对象,需要注意的是,这里只是创建了一个纹理对象,并没有提供数据,因此data设置成了null。
// 创建渲染对象
const targetTextureWidth = 256; // 纹理的长度与宽度
const targetTextureHeight = 256;
const targetTexture = gl.createTexture(); // 创建纹理对象
gl.bindTexture(gl.TEXTURE_2D, targetTexture); // 进行bind,以下操作针对的是当前bind对象
{
// 定义 0 级的大小和格式,不需要mipmap只需定义level 0即可
const level = 0;
const internalFormat = gl.RGBA; // 每个像素使用RGBA表示
const border = 0; // 纹理的border,必须为0
const format = gl.RGBA;
const type = gl.UNSIGNED_BYTE; // 像素每个通道的存储格式
const data = null; // 设置成null
gl.texImage2D(gl.TEXTURE_2D, level, internalFormat,
targetTextureWidth, targetTextureHeight, border,
format, type, data);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
}
2)创建帧缓冲
接下来,需要创建一个帧缓冲(framebuffer),帧缓冲是一个附件集,附件是纹理或renderbuffer。Renderbuffer与纹理很像,但支持一些纹理不支持的格式和可选项。不过,不能像纹理那样直接将renderbuffer提供给着色器。
// 创建并绑定帧缓冲
const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
// 附加纹理为第一个颜色附件
const attachmentPoint = gl.COLOR_ATTACHMENT0;
gl.framebufferTexture2D(
gl.FRAMEBUFFER, attachmentPoint, gl.TEXTURE_2D, targetTexture, level);
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
3) 创建深度附件
在渲染到纹理时,可以选择不创建深度附件,这样就没有深度检测,渲染出来的纹理遮挡关系可能不正确。如果需要创建深度附件,则可以按照以下步骤进行:
// 创建一个深度纹理
const depthTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, depthTexture);
// 设置深度缓冲的大小和targetTexture相同
{
// 定义 0 级的大小和格式
const level = 0;
const internalFormat = gl.DEPTH_COMPONENT24;
const border = 0;
const format = gl.DEPTH_COMPONENT;
const type = gl.UNSIGNED_INT;
const data = null;
gl.texImage2D(gl.TEXTURE_2D, level, internalFormat,
targetTextureWidth, targetTextureHeight, border,
format, type, data);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
// 将深度纹理附加到缓冲帧
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, depthTexture, level);
}
4)进行渲染,生成纹理
在渲染之前需要先绑定帧缓冲以及设置视口。以下是进行渲染的代码:
// 绑定帧缓冲
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
// 设置视口大小
gl.viewport(0, 0, targetTextureWidth, targetTextureHeight);
// 清空画布颜色缓冲区和深度缓冲区
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// 设置相机
// ...
// 设置光源
// ...
// 绘制场景
// …
渲染到纹理,目前我知道的使用场景有以下几种:
1)直接交给着色器使用
生成的纹理可以直接传递给着色器,在着色器中使用它进行渲染。在进行渲染之前,需要将Framebuffer的绑定解除,并使用纹理对象进行绘制:
// 将帧缓冲的绑定解除
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
// 使用生成的纹理进行绘制
gl.bindTexture(gl.TEXTURE_2D, targetTexture);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.useProgram(program);
gl.drawArrays(gl.TRIANGLES, 0, 6);
2)读取纹理到数组,然后进一步使用, 可用于拾取对象、生成纹理、调试Shader等
// 读取纹理到数组
const pixels = new Uint8Array(targetTextureWidth * targetTextureHeight * 4);
gl.readPixels(0, 0, targetTextureWidth, targetTextureHeight, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
在这里,我们使用gl.readPixels()函数将帧缓冲区中的像素数据读取到一个数组中。这个数组就是我们最终生成的纹理数据,可以用来在之后的渲染中使用。如果想用这个数组重新生成纹理,可按以下代码:
// 创建纹理对象
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
// 设置纹理参数
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
// 将像素数据绑定到纹理对象上
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, targetTextureWidth, targetTextureHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
有些开发者将Shader的中间值渲染到纹理用于调试。有些软件将场景物体的ID渲染到纹理,用于物体的拾取。
3)Shadowmap
在 Shadow Mapping 中,首先需要渲染一次场景,生成深度贴图(Depth Map),这个深度贴图实际上就是一张纹理,存储了从光源视角看到的场景的深度信息。然后,再将深度贴图应用到场景中进行阴影计算。渲染到纹理是实现 Shadow Mapping 的一种基础技术。
在 OSG 中主要有以下 4 种 buffer:
FrameBuffer。相当于一个本地缓存集合,包括颜色缓存、深度缓存、模板缓存和累积缓存,用于存放每帧的渲染数据。
Frame Buffer Object。是 OpenGL 的一个高级扩展,提供了一种渲染到目标对象的新的机制。
Pixel Buffer Objects。是窗口系统的一个扩展,实现离屏渲染,与一个不可见的窗口图形环境相似。
Vertex Buffer Object。是 OpenGL 的一个高级扩展,用于将顶点数据提交给显卡的高速缓存区,使显卡能够快速存取,以提高渲染速度。
渲染到纹理就是将当前的渲染结果(framebuffer)通过纹理的方式直接读取,这样可以在很大程度上提高渲染的性能,避免从 framebuffer 里面拷贝纹理对象,从而节省很大的内存。渲染到纹理主要用于生成动态纹理、反射效果和图像模糊等。下面通过示例程序向读者演示如何渲染到纹理。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include //事件监听
#include //事件响应类,对渲染状态进行控制
#include //简化几何体
#include
#include
#include
#include
#pragma comment(lib, "OpenThreadsd.lib")
#pragma comment(lib, "osgd.lib")
#pragma comment(lib, "osgDBd.lib")
#pragma comment(lib, "osgUtild.lib")
#pragma comment(lib, "osgGAd.lib")
#pragma comment(lib, "osgViewerd.lib")
#pragma comment(lib, "osgTextd.lib")
//定义相机绘制回调
struct MyCameraPostDrawCallback : public osg::Camera::DrawCallback
{
public:
MyCameraPostDrawCallback(osg::ref_ptr<osg::Image> image) :
_image(image)
{
}
virtual void operator () (const osg::Camera& /*camera*/) const
{
if (_image && _image->getPixelFormat() == GL_RGBA && _image->getDataType() == GL_UNSIGNED_BYTE)
{
//获得Image的中心
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)
{
unsigned char* data = _image->data(column_start, r);
for (int c = column_start; c < column_end; ++c)
{
(*data) = 255 - (*data); ++data;
(*data) = 255 - (*data); ++data;
(*data) = 255 - (*data); ++data;
(*data) = 255; ++data;
}
}
_image->dirty();
}
else if (_image && _image->getPixelFormat() == GL_RGBA && _image->getDataType() == GL_FLOAT)
{
//获得Image的中心
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;
(*data) = 1.0f - (*data); ++data;
(*data) = 1.0f - (*data); ++data;
(*data) = 1.0f; ++data;
}
}
_image->dirty();
}
}
public:
osg::ref_ptr<osg::Image> _image;
};
//创建预渲染场景
osg::ref_ptr<osg::Node> createPreRenderSubGraph(osg::ref_ptr<osg::Node> subgraph,
unsigned tex_width,
unsigned tex_height,
osg::Camera::RenderTargetImplementation renderImplementation,
bool useImage)
{
if (!subgraph) return 0;
//创建一个包含预渲camera的 Group 节点
osg::ref_ptr<osg::Group> parent = new osg::Group;
//创建纹理,用来绑定相机渲染的结果
osg::ref_ptr<osg::Texture> texture = 0;
{
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;
}
//创建一个用来浏览的四边形几何体
{
osg::ref_ptr<osg::Geometry> polyGeom = new osg::Geometry();
//设置该几何体不使用显示列表
polyGeom->setSupportsDisplayList(false);
float height = 100.0f;
float width = 200.0f;
//创建顶点数组,并添加数据
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));
//创建纹理数组,并添加数据
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->setVertexArray(vertices.get());
//使用vbo扩展
{
osg::ref_ptr<osg::VertexBufferObject> vbObject = new osg::VertexBufferObject;
vertices->setVertexBufferObject(vbObject);
polyGeom->setUseVertexBufferObjects(true);
}
polyGeom->setTexCoordArray(0, texcoords.get());
//创建颜色数组,并设置绑定方式及添加数据
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);
polyGeom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, vertices->size()));
//现在我们需要将纹理附加到该几何体上,我们创建一个包含该纹理的StateSet
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
stateset->setTextureAttributeAndModes(0, texture, osg::StateAttribute::ON);
polyGeom->setStateSet(stateset);
osg::ref_ptr<osg::Geode> geode = new osg::Geode();
geode->addDrawable(polyGeom.get());
parent->addChild(geode.get());
}
// 需要创建一个相机节点,用来渲染到该纹理(RTT)
{
osg::ref_ptr<osg::Camera> camera = new osg::Camera;
//设置背景色及清除颜色和深度缓存
camera->setClearColor(osg::Vec4(0.1f, 0.1f, 0.3f, 1.0f));
camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//获得该节点的范围盒
const osg::BoundingSphere& bs = subgraph->getBound();
if (!bs.valid())
{
return subgraph.get();
}
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;
//设置投影矩阵.
camera->setProjectionMatrixAsFrustum(-proj_right, proj_right, -proj_top, proj_top, znear, zfar);
//将相机对准该子场景
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));
//设置视口
camera->setViewport(0, 0, tex_width, tex_height);
//设置相机的渲染序列
camera->setRenderOrder(osg::Camera::PRE_RENDER);
//设置相机渲染通过 OpenGL frame buffer object 实现
camera->setRenderTargetImplementation(renderImplementation);
if (useImage)
{
osg::ref_ptr<osg::Image> image = new osg::Image;
//image->allocateImage(tex_width, tex_height, 1, GL_RGBA, GL_UNSIGNED_BYTE);
image->allocateImage(tex_width, tex_height, 1, GL_RGBA, GL_FLOAT);
//将Image附加到相机的COLOR_BUFFER
camera->attach(osg::Camera::COLOR_BUFFER, image.get());
//添加相机的绘制后回调,修改images数据
camera->setPostDrawCallback(new MyCameraPostDrawCallback(image.get()));
//这里我们不直接将相机的COLOR_BUFFER附加到该纹理上,是为了修改渲染后的图像数据
texture->setImage(0, image.get());
}
else
{
//直接将该纹理附加到相机的颜色缓存.
camera->attach(osg::Camera::COLOR_BUFFER, texture.get());
}
//添加要绘制的子场景
camera->addChild(subgraph.get());
parent->addChild(camera.get());
}
return parent.get();
}
int main()
{
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();
unsigned tex_width = 1024;
unsigned tex_height = 512;
osg::Camera::RenderTargetImplementation renderImplementation = osg::Camera::FRAME_BUFFER_OBJECT;
bool useImage = false;
//读取模型
osg::ref_ptr<osg::Node> loadedModel = osgDB::readNodeFile("cessna.osg");
//创建一个transform节点,用来选装该子场景
osg::ref_ptr<osg::MatrixTransform> loadedModelTransform = new osg::MatrixTransform;
loadedModelTransform->addChild(loadedModel.get());
//设置更新回调
osg::ref_ptr<osg::NodeCallback> nc = new osg::AnimationPathCallback(loadedModelTransform->getBound().center(), osg::Vec3(0.0f, 0.0f, 1.0f), osg::inDegrees(45.0f));
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 optimzer;
optimzer.optimize(rootNode.get());
//方便查看在多边形之间切换,以查看三角网
viewer->addEventHandler(new osgGA::StateSetManipulator(viewer->getCamera()->getOrCreateStateSet()));
viewer->addEventHandler(new osgViewer::StatsHandler());
viewer->addEventHandler(new osgViewer::WindowSizeHandler());
viewer->setSceneData(rootNode.get());
viewer->setUpViewInWindow(600, 600, 1000, 800);
return viewer->run();
}
三维纹理(3D texture),即体纹理(volume texture),是传统二维纹理(2D texture)在逻辑上的扩展。二维纹理是一张简单的位图图片,用于为三维模型提供表面点的颜色值;而一个三维纹理,可以被认为由很多张 2D 纹理组成,用于描述三维空间数据的图片。三维纹理通过三维纹理坐标进行访问 。
优势:使用体纹理,可以跳过为三维网格确定良好二维参数的复杂过程,因为三维位置可以直接用作纹理坐标,从而避免了二维参数化中通常会发生的变形和接缝问题。体纹理也可用于表示诸如木材或大理石的材料的体积结构。使用三维纹理实现出的这些模型,看起来会很逼真,浑然天成。
劣势:更高的储存要求,并且滤波成本更高。使用体纹理作为表面纹理会非常低效,因为三维纹理中的绝大多数样本都没起到作用。
三维纹理映射(osg::Texture3D)是一大类应用范畴的一部分,称为体纹理。三维纹理主要应用于医学领域和科学领域,笔者也未对其做深入研究,只限于简单的了解,这里也只简单介绍一下。在OsgChina 中国官方成员中,hesicong 目前主要研究这个方向。在医学领域应用程序中,三维纹理主要应用于断层计算成像(CT)和核磁共振(MRT)图像,在网上有很多相关的演示视频。当然,这里也不会向读者演示一个精妙的虚拟手术,只是作一些简单的介绍。
在实际的虚拟现实项目中,三维纹理的应用不多,它虽然能达到很好的效果,但当面对一个很大的场景时,渲染的负担是非常大的,三维纹理可能也会非常之大,它占用的内存资源也会非常多,有时即使是一个非常粗糙的三维纹理,它占用的内存也可能是普通二维纹理的 16 倍或 32 倍。如果渲染一个大的场景,就需要一个高配置的机器。
osg::Texture3D 类继承自 osg::Texture 类,封装了 OpenGL 的二维纹理函数的一些功能,但它不支持立方图纹理。在它的父类中同样有 osg::StateAttribute。因此,它同样可以通过设置渲染属性来启用三维纹理属性。到目前为止,我们已经讲解了基本的几种纹理映射,从它们的类的继承关系可以看出,它们都继承自 osg::Texture。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include //事件监听
#include //事件响应类,对渲染状态进行控制
#include //简化几何体
#include
#include
#include
#include
#pragma comment(lib, "OpenThreadsd.lib")
#pragma comment(lib, "osgd.lib")
#pragma comment(lib, "osgDBd.lib")
#pragma comment(lib, "osgUtild.lib")
#pragma comment(lib, "osgGAd.lib")
#pragma comment(lib, "osgViewerd.lib")
#pragma comment(lib, "osgTextd.lib")
//创建一个四边形节点
osg::ref_ptr<osg::Node> createNode()
{
osg::ref_ptr<osg::Geode> geode = new osg::Geode();
osg::ref_ptr<osg::Geometry> geom = new osg::Geometry();
//设置顶点
osg::ref_ptr<osg::Vec3Array> vc = new osg::Vec3Array();
vc->push_back(osg::Vec3(0.0f, 0.0f, 0.0f));
vc->push_back(osg::Vec3(1.0f, 0.0f, 0.0f));
vc->push_back(osg::Vec3(1.0f, 0.0f, 1.0f));
vc->push_back(osg::Vec3(0.0f, 0.0f, 1.0f));
geom->setVertexArray(vc.get());
//设置纹理坐标
osg::ref_ptr<osg::Vec2Array> vt = new osg::Vec2Array();
vt->push_back(osg::Vec2(0.0f, 0.0f));
vt->push_back(osg::Vec2(1.0f, 0.0f));
vt->push_back(osg::Vec2(1.0f, 1.0f));
vt->push_back(osg::Vec2(0.0f, 1.0f));
geom->setTexCoordArray(0, vt.get());
//设置法线
osg::ref_ptr<osg::Vec3Array> nc = new osg::Vec3Array();
nc->push_back(osg::Vec3(0.0f, -1.0f, 0.0f));
geom->setNormalArray(nc.get());
geom->setNormalBinding(osg::Geometry::BIND_OVERALL);
//添加图元
geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, 4));
//绘制
geode->addDrawable(geom.get());
return geode.get();
}
//初始化一个图形环境
class MyGraphicsContext
{
public:
MyGraphicsContext()
{
//设置图形环境特性
osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
//设置左上角坐标
traits->x = 0;
traits->y = 0;
//设置宽度和高度
traits->width = 1;
traits->height = 1;
//设置窗口扩展
traits->windowDecoration = false;
//设置双缓冲
traits->doubleBuffer = false;
traits->sharedContext = 0;
//设置pbuffer
traits->pbuffer = true;
//创建图形环境
_gc = osg::GraphicsContext::createGraphicsContext(traits.get());
//如果创建失败
if (!_gc)
{
//设置pbuffer为false
traits->pbuffer = false;
//重新创建图形环境
_gc = osg::GraphicsContext::createGraphicsContext(traits.get());
}
//是否初始化
if (_gc.valid())
{
//初始化
_gc->realize();
_gc->makeCurrent();
}
}
bool valid() const { return _gc.valid() && _gc->isRealized(); }
private:
osg::ref_ptr<osg::GraphicsContext> _gc;
};
//创建三维纹理属性
osg::ref_ptr<osg::StateSet> createState()
{
//创建图形环境
MyGraphicsContext gc;
if (!gc.valid())
{
//如果创建失败,则返回
osg::notify(osg::NOTICE) << "Unable to create the graphics context required to build 3d image." << std::endl;
return 0;
}
//读取四张二维纹理图像
osg::ref_ptr<osg::Image> image_0 = osgDB::readImageFile("Images/lz.rgb");
osg::ref_ptr<osg::Image> image_1 = osgDB::readImageFile("Images/reflect.rgb");
osg::ref_ptr<osg::Image> image_2 = osgDB::readImageFile("Images/tank.rgb");
osg::ref_ptr<osg::Image> image_3 = osgDB::readImageFile("Images/skymap.jpg");
//判断是否正确读取
if (!image_0 || !image_1 || !image_2 || !image_3)
{
std::cout << "Warning: could not open files." << std::endl;
return new osg::StateSet();
}
//判断纹理格式是否一致
if (image_0->getPixelFormat() != image_1->getPixelFormat() || image_0->getPixelFormat() != image_2->getPixelFormat() || image_0->getPixelFormat() != image_3->getPixelFormat())
{
std::cout << "Warning: image pixel formats not compatible." << std::endl;
return new osg::StateSet();
}
//得到支持的最大的三维纹理单元的大小
/*GLint textureSize = osg::Texture3D::getExtensions(0, true)->maxTexture3DSize();
if (textureSize > 256)
{
textureSize = 256;
}*/
GLint textureSize = 256;
//对四张二维纹理图像缩放,以达到相同的大小
image_0->scaleImage(textureSize, textureSize, 1);
image_1->scaleImage(textureSize, textureSize, 1);
image_2->scaleImage(textureSize, textureSize, 1);
image_3->scaleImage(textureSize, textureSize, 1);
//创建一个三维纹理数据图像,注意深度为4
osg::ref_ptr<osg::Image> image_3d = new osg::Image;
//第一个和第二个参数是纹理的大小,第三个参数指的是三维纹理数据图像的深度
image_3d->allocateImage(textureSize, textureSize, 4, image_0->getPixelFormat(), image_0->getDataType());
//把四张二维纹理图像压入三维纹理数据图像
//第1-3个参数分别是s,t,r上的偏移,当然这里只是r上的偏移
//第四个参数是子二维纹理图像数据
image_3d->copySubImage(0, 0, 0, image_0.get());
image_3d->copySubImage(0, 0, 1, image_1.get());
image_3d->copySubImage(0, 0, 2, image_2.get());
image_3d->copySubImage(0, 0, 3, image_3.get());
//设置纹理格式
image_3d->setInternalTextureFormat(image_0->getInternalTextureFormat());
//创建三维纹理对象
osg::ref_ptr<osg::Texture3D> texture3D = new osg::Texture3D;
//设置滤波,不支持mip map滤波
texture3D->setFilter(osg::Texture3D::MIN_FILTER, osg::Texture3D::LINEAR);
texture3D->setFilter(osg::Texture3D::MAG_FILTER, osg::Texture3D::LINEAR);
//设置环绕模式
texture3D->setWrap(osg::Texture3D::WRAP_R, osg::Texture3D::REPEAT);
//关联三维纹理图像数据
texture3D->setImage(image_3d.get());
//设置自动生成纹理坐标
osg::ref_ptr<osg::TexGen> texgen = new osg::TexGen;
//设置自动生成纹理坐标为视觉线性
texgen->setMode(osg::TexGen::OBJECT_LINEAR);
//指定参考平面
texgen->setPlane(osg::TexGen::R, osg::Plane(1.0f, 0.0f, 0.0f, 0.2f));
//创建状态属性对象
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
//设置在R上自动生成纹理坐标
stateset->setTextureMode(0, GL_TEXTURE_GEN_R, osg::StateAttribute::ON);
//启用自动生成纹理坐标
stateset->setTextureAttribute(0, texgen.get());
//启用三维纹理对象
stateset->setTextureAttributeAndModes(0, texture3D.get(), osg::StateAttribute::ON);
return stateset.get();
}
int main()
{
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();
osg::ref_ptr<osg::Group> root = new osg::Group();
osg::ref_ptr<osg::Node> node = createNode();
//创建状态属性对象
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();
stateset = createState();
//使用三维纹理
node->setStateSet(stateset.get());
root->addChild(node.get());
//优化场景数据
osgUtil::Optimizer optimzer;
optimzer.optimize(root.get());
//方便查看在多边形之间切换,以查看三角网
viewer->addEventHandler(new osgGA::StateSetManipulator(viewer->getCamera()->getOrCreateStateSet()));
viewer->addEventHandler(new osgViewer::StatsHandler());
viewer->addEventHandler(new osgViewer::WindowSizeHandler());
viewer->setSceneData(root.get());
viewer->setUpViewInWindow(600, 600, 1000, 800);
return viewer->run();
}