天天看點

OSG學習:紋理映射(四)——三維紋理映射

以下内容來自: 

1、《OpenSceneGraph三維渲染引擎程式設計指南》肖鵬 劉更代 徐明亮 清華大學出版社 

2、《OpenSceneGraph三維渲染引擎設計與實踐》王銳 錢學雷 清華大學出版社

3、自己的總結

下載下傳完整工程OSG_12_Texture3D

建立C++項目後,首先需要配置OSG環境,具體步驟看OSG學習:WIN10系統下OSG+VS2017編譯及運作第六步:建立OSG項目測試。

三維紋理映射是一大類應用範疇的一部分,稱為體渲染。它雖然能達到很好的效果,但當場景較大時,渲染負擔極大,占用的記憶體資源極多,是以應用于實際的項目極少。

三維紋理可以看成是一層層的二維紋理圖像矩陣所構成的一個有多層深度的紋理圖像。

三維紋理osg::Texture3D類繼承自osg::Texture類,但它不支援立方圖紋理。使用三維紋理的方法同使用二維紋理基本類似,三維紋理圖像的二維紋理子圖的大小和像素格式需要一緻,使用三維紋理時需要初始化一個圖形環境。

具體來看例子,這個例子是将多張二維紋理資料壓入一個渲染體:

// stdafx.h

#include <osg/Node>
#include <osg/Geode>  
#include <osg/Group>

#include <osg/Image> 
#include <osg/StateSet>

#include <osg/TexGen> //指定用于自動生成紋理坐标的函數,可以設定紋理的計算方式是以物體坐标空間還是相機坐标空間來進行不同的計算
#include <osg/TexEnv>
#include <osg/Texture3D>  //繼承自osg::Texture類,但它不支援立方圖紋理

#include <osgViewer/Viewer>

#include <osgDB/ReadFile>
#include <osgDB/WriteFile>

#include <osgUtil/Optimizer>
           
//.cpp

/*
 *三維紋理映射
 *使用三維紋理的方法同使用二維紋理基本類似
 *三維紋理圖像的二維紋理子圖的大小和像素格式需要一緻
 *使用三維紋理時需要初始化一個圖形環境
 *
 *這個例子是将多張二維紋理資料壓入一個渲染體
*/


//建立一個四邊形節點
osg::ref_ptr<osg::Node> createNode()
{
	//設定頂點
	osg::ref_ptr<osg::Vec3Array> vc = new osg::Vec3Array;
	//添加資料  
	vc->push_back(osg::Vec3(0.0, 0.0, 0.0));
	vc->push_back(osg::Vec3(1.0, 0.0, 0.0));
	vc->push_back(osg::Vec3(1.0, 0.0, 1.0));
	vc->push_back(osg::Vec3(0.0, 0.0, 1.0));

	//設定紋理坐标
	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));

	//設定法線
	osg::ref_ptr<osg::Vec3Array> nc = new osg::Vec3Array();
	//添加法線
	nc->push_back(osg::Vec3(0.0f, -1.0f, 0.0f));	

	osg::ref_ptr<osg::Geometry> geom = new osg::Geometry();
	geom->setVertexArray(vc.get());
	//設定紋理坐标數組setTexCoordArray(unsigned int unit, Array *)參數紋理單元/紋理坐标數組
	geom->setTexCoordArray(0, vt.get());
	//設定法線數組setNormalArray(Array *array)
	geom->setNormalArray(nc.get());
	//設定法線的綁定方式setNormalBinding(AttributeBinding ab)為全部頂點
	geom->setNormalBinding(osg::Geometry::BIND_OVERALL);

	//添加圖元,繪制基元為四邊形
	//資料解析,即指定向量資料和綁定方式後,指定渲染幾何體的方式,不同方式渲染出的圖形不同,即時效果相同,可能面數或内部機制等也有差別,函數為:
	//bool addPrimitiveSet(PrimitiveSet *primitiveset)參數說明:osg::PrimitiveSet是無法初始化的虛基類,是以主要調用它的子類指定資料渲染,最常用為osg::DrawArrays
	//osg::DrawArrays(GLenum mode, GLint first, GLsizei count)參數為指定的繪圖基元、繪制幾何體的第一個頂點數在指定頂點的位置數、使用的頂點的總數
	//PrimitiveSet類繼承自osg::Object虛基類,但不具備一般一般場景中的特性,PrimitiveSet類主要封裝了OpenGL的繪圖基元,常見繪圖基元如下
	//POINTS點/LINES線/LINE_STRIP多線段/LINE_LOOP封閉線
	//TRIANGLES一系列三角形(不共頂點)/TRIANGLE_STRIP一系列三角形(共用後面兩個頂點)/TRIANGLE_FAN一系列三角形,頂點順序與上一條語句繪制的三角形不同
	//QUADS四邊形/QUAD_STRIP一系列四邊形/POLYGON多邊形

	//繪制
	geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, 4));

	//添加到葉節點
	osg::ref_ptr<osg::Geode> geode = new osg::Geode();
	geode->addDrawable(geom.get());

	return geode.release();
}

//初始化一個圖形環境
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;
	}

	//讀取4張二維紋理圖像
	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;

	//對4張二維紋理圖像縮放,以達到相同的大小
	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());

	//把4張二維紋理圖像壓入三維紋理資料圖像
	//前三個參數分别是s/t/r上的偏移,這裡隻是r上的偏移
	//第四個參數是子二維紋理圖像資料
	image_3d->copySubImage(0, 0, 0, image_0.get());
	image_3d->copySubImage(0, 0, 1, image_0.get());
	image_3d->copySubImage(0, 0, 2, image_0.get());
	image_3d->copySubImage(0, 0, 3, image_0.get());

	//設定紋理格式
	image_3d->setInternalTextureFormat(image_0->getInternalTextureFormat());

	//建立三維紋理對象
	osg::ref_ptr<osg::Texture3D> texture3D = new osg::Texture3D();
	//設定濾波,不支援Mipmap濾波
	/*設定紋理的過濾方法/過濾處理
	enum FilterParameter
	{
		MIN_FILTER, //縮小
		MAG_FILTER //放大
	};
	enum FilterMode
	{
		LINEAR, //以周圍4個像素的平均值作為紋理
		LINEAR_MIPMAP_LINEAR, //使用線性均和計算兩個紋理的值
		LINEAR_MIPMAP_NEAREST, //線性地改寫臨近的紋理單元值
		NEAREST, //取比較接近的像素作為紋理
		NEAREST_MIPMAP_LINEAR, //在兩個紋理中選擇最臨近的紋理,并取它們之間的線性均和值
		NEAREST_MIPMAP_NEAREST //選擇最臨近的紋理單元值
	};
	*/
	texture3D->setFilter(osg::Texture3D::MIN_FILTER, osg::Texture3D::LINEAR);
	texture3D->setFilter(osg::Texture3D::MAG_FILTER, osg::Texture3D::LINEAR);
	//設定環繞模式
	/*設定紋理的坐标/包裝模式
	enum WrapParmeter
	{
		WRAP_S, //x軸
		WRAP_T, //y軸
		WRAP_T //z軸
	};
	enum WrapMode
	{
		CLAMP, //截取
		CLAMP_TO_EDGE, //邊框始終被忽略
		CLAMP_TO_BORDER, //它使用的紋理取自圖像的邊框,沒有邊框就使用常量邊框的顔色
		REPEAT, //紋理的重複映射
		MIRROR //紋理鏡像的重複映射
	};
	*/
	texture3D->setWrap(osg::Texture3D::WRAP_R, osg::Texture3D::REPEAT);
	//關聯三維紋理圖像資料
	texture3D->setImage(image_3d.get());

	//設定自動生成紋理坐标
	/*設定紋理坐标的自動生成模式
	enum Mode
	{
		OBJECT_LINEAR, //物體線性,紋理貼圖與移動物體保持固定
		EYE_LINEAR, //産生移動物體的動态輪廓線
		SPHERE_MAP, //球體貼圖
		NORMAL_MAP, //法線貼圖,用于立方圖紋理
		REFLECTION_MAP //反射貼圖
	};
	*/
	osg::ref_ptr<osg::TexGen> texgen = new osg::TexGen();
	//設定自動生成紋理坐标為視覺線性
	texgen->setMode(osg::TexGen::OBJECT_LINEAR);
	//指定參考平面
	texgen->setPlane(osg::TexGen::R, osg::Plane(0.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.release();
}

int main()
{
	osg::ref_ptr<osg::Node> node = createNode();
	osg::ref_ptr<osg::StateSet> stateset = createState();
	node->setStateSet(stateset.get());

	osg::ref_ptr<osg::Group> root = new osg::Group();
	root->addChild(node.get());

	//優化場景資料
	osgUtil::Optimizer optimizer;
	optimizer.optimize(root.get());

	osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();
	viewer->setSceneData(root.get());
	viewer->realize();

	return viewer->run();
}