OSG编程指南<十六>:OSG渲染到纹理RTT及三维纹理体渲染技术简介

1、渲染到纹理(RTT)

1.1 RTT介绍

  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.2 RTT应用场景

  渲染到纹理,目前我知道的使用场景有以下几种:

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 的一种基础技术。

1.3 OSG中的RTT

  在 OSG 中主要有以下 4 种 buffer:
FrameBuffer。相当于一个本地缓存集合,包括颜色缓存、深度缓存、模板缓存和累积缓存,用于存放每帧的渲染数据。
Frame Buffer Object。是 OpenGL 的一个高级扩展,提供了一种渲染到目标对象的新的机制。
Pixel Buffer Objects。是窗口系统的一个扩展,实现离屏渲染,与一个不可见的窗口图形环境相似。
Vertex Buffer Object。是 OpenGL 的一个高级扩展,用于将顶点数据提交给显卡的高速缓存区,使显卡能够快速存取,以提高渲染速度。

  渲染到纹理就是将当前的渲染结果(framebuffer)通过纹理的方式直接读取,这样可以在很大程度上提高渲染的性能,避免从 framebuffer 里面拷贝纹理对象,从而节省很大的内存。渲染到纹理主要用于生成动态纹理、反射效果和图像模糊等。下面通过示例程序向读者演示如何渲染到纹理。

1.4 OSG中的RTT示例

OSG编程指南<十六>:OSG渲染到纹理RTT及三维纹理体渲染技术简介_第1张图片

1.5 源码

#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();
}

2、三维纹理映射

2.1 三维纹理映射介绍

  三维纹理(3D texture),即体纹理(volume texture),是传统二维纹理(2D texture)在逻辑上的扩展。二维纹理是一张简单的位图图片,用于为三维模型提供表面点的颜色值;而一个三维纹理,可以被认为由很多张 2D 纹理组成,用于描述三维空间数据的图片。三维纹理通过三维纹理坐标进行访问 。

  优势:使用体纹理,可以跳过为三维网格确定良好二维参数的复杂过程,因为三维位置可以直接用作纹理坐标,从而避免了二维参数化中通常会发生的变形和接缝问题。体纹理也可用于表示诸如木材或大理石的材料的体积结构。使用三维纹理实现出的这些模型,看起来会很逼真,浑然天成。

  劣势:更高的储存要求,并且滤波成本更高。使用体纹理作为表面纹理会非常低效,因为三维纹理中的绝大多数样本都没起到作用。

  三维纹理映射(osg::Texture3D)是一大类应用范畴的一部分,称为体纹理。三维纹理主要应用于医学领域和科学领域,笔者也未对其做深入研究,只限于简单的了解,这里也只简单介绍一下。在OsgChina 中国官方成员中,hesicong 目前主要研究这个方向。在医学领域应用程序中,三维纹理主要应用于断层计算成像(CT)和核磁共振(MRT)图像,在网上有很多相关的演示视频。当然,这里也不会向读者演示一个精妙的虚拟手术,只是作一些简单的介绍。

  在实际的虚拟现实项目中,三维纹理的应用不多,它虽然能达到很好的效果,但当面对一个很大的场景时,渲染的负担是非常大的,三维纹理可能也会非常之大,它占用的内存资源也会非常多,有时即使是一个非常粗糙的三维纹理,它占用的内存也可能是普通二维纹理的 16 倍或 32 倍。如果渲染一个大的场景,就需要一个高配置的机器。

  osg::Texture3D 类继承自 osg::Texture 类,封装了 OpenGL 的二维纹理函数的一些功能,但它不支持立方图纹理。在它的父类中同样有 osg::StateAttribute。因此,它同样可以通过设置渲染属性来启用三维纹理属性。到目前为止,我们已经讲解了基本的几种纹理映射,从它们的类的继承关系可以看出,它们都继承自 osg::Texture。

2.2 示例

OSG编程指南<十六>:OSG渲染到纹理RTT及三维纹理体渲染技术简介_第2张图片

2.3 源码

#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();
}

你可能感兴趣的:(OSG三维引擎入门及进阶,OSG,渲染到纹理RTT,体渲染)