教程1——使用Open Scene Graph几何
摘要
本节涵盖了生成基本几何形状的一些方法。生成几何物体的方法有这么几种:在最底层对OpenGL基本几何进行松散的包装,中级是使用Open Scene Graph的基本形状,以及更高级一些的从文件读取。这篇教程涵盖的是最低层的。这种方法弹性最大但最费力。通常在Scene Graph级别,几何形状是从文件加载的。文件加载器完成了跟踪顶点的大部分工作。
背景
对一下几个类的简单解释:
Geode类:
geode类继承自node类。在一个Scene Graph中,node(当然包含geode)可以作为叶子节点。Geode实例可以有多个相关的drawable。
Drawable类层次
基类drawable是一个有六个具体子类的抽象类。
geometry类可以直接有vertex和vertex数据,或者任意个primitiveSet实例。
vertex和vertex属性数据(颜色、法线、纹理坐标)存放在数组中。既然多个顶点可以共享相同的颜色、法线或纹理坐标,那么数组索引就可以用来将顶点数组映射到颜色、法线、或纹理坐标数组。
PrimitiveSet类:
这个类松散的包装了OpenGL的基本图形-POINTS,LINES,LINE_STRIP,LINE_LOOP,...,POLYGON.
代码
以下这节代码安装了一个viewer来观察我们创建的场景,一个‘group’实例作为scene graph的根节点,一个几何节点(geode)来收集drawable,和一个geometry实例来关联顶点和顶点数据。(这个例子中渲染的形状是一个四面体)
...
int main()
{
...
osgProducer::Viewer viewer;
osg::Group* root = new osg::Group();
osg::Geode* pyramidGeode = new osg::Geode();
osg::Geometry* pyramidGeometry = new osg::Geometry();
下一步,需要将锥体geometry和锥体geode关联起来,并将pyramid geode加到scene graph的根节点上。
pyramidGeode->addDrawable(pyramidGeometry);
root->addChild(pyramidGeode);
声明一个顶点数组。每个顶点由一个三元组表示——vec3类的实例。这些三元组用osg::Vec3Array类的实例存贮。既然osg::Vec3Array继承自STL的vector类,那么我们就可以使用push_back方法来添加数组成员。push_back将元素加到向量的尾端,因此第一个元素的索引是0,第二个是1,依此类推。
使用‘z’轴向上的右手坐标系系统,下面的0...4数组元素代表着产生一个简单锥体所需的5个点。
osg::Vec3Array* pyramidVertices = new osg::Vec3Array;
pyramidVertices->push_back( osg::Vec3( 0, 0, 0) ); // front left
pyramidVertices->push_back( osg::Vec3(10, 0, 0) ); // front right
pyramidVertices->push_back( osg::Vec3(10,10, 0) ); // back right
pyramidVertices->push_back( osg::Vec3( 0,10, 0) ); // back left
pyramidVertices->push_back( osg::Vec3( 5, 5,10) ); // peak
将这个顶点集合和与我们加到场景中的geode相关的geometry关联起来。
pyramidGeometry->setVertexArray( pyramidVertices );
下一步,产生一个基本集合并将其加入到pyramid geometry中。使用pyramid的前四个点通过DrawElementsUint类的实例来定义基座。这个类也继承自STL的vector,所以push_back方法会顺序添加元素。为了保证合适的背面剔除,顶点的顺序应当是逆时针方向的。构造器的参数是基本的枚举类型(和opengl的基本枚举类型一致),和起始的顶点数组索引。
osg::DrawElementsUInt* pyramidBase =
new osg::DrawElementsUInt(osg::PrimitiveSet::QUADS, 0);
pyramidBase->push_back(3);
pyramidBase->push_back(2);
pyramidBase->push_back(1);
pyramidBase->push_back(0);
pyramidGeometry->addPrimitiveSet(pyramidBase);
对每个面重复相同的动作。顶点仍要按逆时针方向指定。
osg::DrawElementsUInt* pyramidFaceOne =
new osg::DrawElementsUInt(osg::PrimitiveSet::TRIANGLES, 0);
pyramidFaceOne->push_back(0);
pyramidFaceOne->push_back(1);
pyramidFaceOne->push_back(4);
pyramidGeometry->addPrimitiveSet(pyramidFaceOne);
osg::DrawElementsUInt* pyramidFaceTwo =
new osg::DrawElementsUInt(osg::PrimitiveSet::TRIANGLES, 0);
pyramidFaceTwo->push_back(1);
pyramidFaceTwo->push_back(2);
pyramidFaceTwo->push_back(4);
pyramidGeometry->addPrimitiveSet(pyramidFaceTwo);
osg::DrawElementsUInt* pyramidFaceThree =
new osg::DrawElementsUInt(osg::PrimitiveSet::TRIANGLES, 0);
pyramidFaceThree->push_back(2);
pyramidFaceThree->push_back(3);
pyramidFaceThree->push_back(4);
pyramidGeometry->addPrimitiveSet(pyramidFaceThree);
osg::DrawElementsUInt* pyramidFaceFour =
new osg::DrawElementsUInt(osg::PrimitiveSet::TRIANGLES, 0);
pyramidFaceFour->push_back(3);
pyramidFaceFour->push_back(0);
pyramidFaceFour->push_back(4);
pyramidGeometry->addPrimitiveSet(pyramidFaceFour)
声明并加载一个vec4为元素的数组来存储颜色。
osg::Vec4Array* colors = new osg::Vec4Array;
colors->push_back(osg::Vec4(1.0f, 0.0f, 0.0f, 1.0f) ); //index 0 red
colors->push_back(osg::Vec4(0.0f, 1.0f, 0.0f, 1.0f) ); //index 1 green
colors->push_back(osg::Vec4(0.0f, 0.0f, 1.0f, 1.0f) ); //index 2 blue
colors->push_back(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f) ); //index 3 white
声明的这个变量可以将顶点数组元素和颜色数组元素匹配起来。这个容器的元素数应当和顶点数一致。这个容器是顶点数组和颜色数组的连接。这个索引数组中的条目就对应着顶点数组中的元素。他们的值就是颜色数组中的索引。顶点数组元素与normal和纹理坐标数组的匹配也是遵循这种模式。
注意,这种情况下,我们将5个顶点指定4种颜色。顶点数组的0和4元素都被指定为颜色数组的0元素。
osg::TemplateIndexArray
<unsigned int, osg::Array::UIntArrayType,4,4> *colorIndexArray;
colorIndexArray =
new osg::TemplateIndexArray<unsigned int, osg::Array::UIntArrayType,4,4>;
colorIndexArray->push_back(0); // vertex 0 assigned color array element 0
colorIndexArray->push_back(1); // vertex 1 assigned color array element 1
colorIndexArray->push_back(2); // vertex 2 assigned color array element 2
colorIndexArray->push_back(3); // vertex 3 assigned color array element 3
colorIndexArray->push_back(0); // vertex 4 assigned color array element 0
下一步,将颜色数组和geometry关联起来,将上面产生的颜色索引指定给geometry,设定绑定模式为_PER_VERTEX。
// Declare and initialize a transform node.
osg::PositionAttitudeTransform* pyramidTwoXForm =
new osg::PositionAttitudeTransform();
// Use the 'addChild' method of the osg::Group class to
// add the transform as a child of the root node and the
// pyramid node as a child of the transform.
root->addChild(pyramidTwoXForm);
pyramidTwoXForm->addChild(pyramidGeode);
// Declare and initialize a Vec3 instance to change the
// position of the tank model in the scene
osg::Vec3 pyramidTwoPosition(15,0,0);
pyramidTwoXForm->setPosition( pyramidTwoPosition );
既然我们生成了一个geometry节点并将它加到了场景中,我们就可以重用这个geometry。例如,如果我们想让另一个pyramid在第一个的右侧15个单位处,我们就可以在我们的scene graph中将这个geode加到transform节点的子节点上。
最后一步,建立并进入一个仿真循环。
viewer.setUpViewer(osgProducer::Viewer::STANDARD_SETTINGS);
viewer.setSceneData( root );
viewer.realize();
while( !viewer.done() )
{
viewer.sync();
viewer.update();
viewer.frame();
}
好运气!
pyramidGeometry->setColorArray(colors);
pyramidGeometry->setColorIndices(colorIndexArray);
pyramidGeometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
osg::Vec2Array* texcoords = new osg::Vec2Array(5);
(*texcoords)[0].set(0.00f,0.0f);
(*texcoords)[1].set(0.25f,0.0f);
(*texcoords)[2].set(0.50f,0.0f);
(*texcoords)[3].set(0.75f,0.0f);
(*texcoords)[4].set(0.50f,1.0f);
pyramidGeometry->setTexCoordArray(0,texcoords);
目标:
为教程1中介绍的由OpenGL基本绘制单位定义的几何体添加纹理。
背景:
前一节教程介绍了包含由OpenGL基本单位产生的基本形状的视景。本节讲解如何为这些形状添加纹理。为了使代码更方便使用,我们将pyramid的代码放到一个函数中,产生geode并返回它的指针。下面的代码来自教程1。
osg::Geode* createPyramid()
{
osg::Geode* pyramidGeode = new osg::Geode();
osg::Geometry* pyramidGeometry = new osg::Geometry();
pyramidGeode->addDrawable(pyramidGeometry);
// Specify the vertices:
osg::Vec3Array* pyramidVertices = new osg::Vec3Array;
pyramidVertices->push_back( osg::Vec3(0, 0, 0) ); // front left
pyramidVertices->push_back( osg::Vec3(2, 0, 0) ); // front right
pyramidVertices->push_back( osg::Vec3(2, 2, 0) ); // back right
pyramidVertices->push_back( osg::Vec3( 0,2, 0) ); // back left
pyramidVertices->push_back( osg::Vec3( 1, 1,2) ); // peak
// Associate this set of vertices with the geometry associated with the
// geode we added to the scene.
pyramidGeometry->setVertexArray( pyramidVertices );
// Create a QUAD primitive for the base by specifying the
// vertices from our vertex list that make up this QUAD:
osg::DrawElementsUInt* pyramidBase =
new osg::DrawElementsUInt(osg::PrimitiveSet::QUADS, 0);
pyramidBase->push_back(3);
pyramidBase->push_back(2);
pyramidBase->push_back(1);
pyramidBase->push_back(0);
//Add this primitive to the geometry:
pyramidGeometry->addPrimitiveSet(pyramidBase);
// code to create other faces goes here!
// (removed to save space, see tutorial two)
osg::Vec4Array* colors = new osg::Vec4Array;
colors->push_back(osg::Vec4(1.0f, 0.0f, 0.0f, 1.0f) ); //index 0 red
colors->push_back(osg::Vec4(0.0f, 1.0f, 0.0f, 1.0f) ); //index 1 green
colors->push_back(osg::Vec4(0.0f, 0.0f, 1.0f, 1.0f) ); //index 2 blue
colors->push_back(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f) ); //index 3 white
osg::TemplateIndexArray
<unsigned int, osg::Array::UIntArrayType,4,4> *colorIndexArray;
colorIndexArray =
new osg::TemplateIndexArray<unsigned int, osg::Array::UIntArrayType,4,4>;
colorIndexArray->push_back(0); // vertex 0 assigned color array element 0
colorIndexArray->push_back(1); // vertex 1 assigned color array element 1
colorIndexArray->push_back(2); // vertex 2 assigned color array element 2
colorIndexArray->push_back(3); // vertex 3 assigned color array element 3
colorIndexArray->push_back(0); // vertex 4 assigned color array element 0
pyramidGeometry->setColorArray(colors);
pyramidGeometry->setColorIndices(colorIndexArray);
pyramidGeometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
// Since the mapping from vertices to texture coordinates is 1:1,
// we don't need to use an index array to map vertices to texture
// coordinates. We can do it directly with the 'setTexCoordArray'
// method of the Geometry class.
// This method takes a variable that is an array of two dimensional
// vectors (osg::Vec2). This variable needs to have the same
// number of elements as our Geometry has vertices. Each array element
// defines the texture coordinate for the cooresponding vertex in the
// vertex array.
osg::Vec2Array* texcoords = new osg::Vec2Array(5);
(*texcoords)[0].set(0.00f,0.0f); // tex coord for vertex 0
(*texcoords)[1].set(0.25f,0.0f); // tex coord for vertex 1
(*texcoords)[2].set(0.50f,0.0f); // ""
(*texcoords)[3].set(0.75f,0.0f); // ""
(*texcoords)[4].set(0.50f,1.0f); // ""
pyramidGeometry->setTexCoordArray(0,texcoords);
return pyramidGeode;
}
加载纹理,生成状态集合并将他们附加到节点上
渲染基本单位的方法是使用StateSet。这节代码演示了怎样从文件中加载纹理,产生此纹理起作用的一个StateSet,并将这个StateSet附加到场景中的一个节点上。前面开始的代码和上一节教程中的一样,初始化一个viewer并建立有一个pyramid的场景。
int main()
{
osgProducer::Viewer viewer;
// Declare a group to act as root node of a scene:
osg::Group* root = new osg::Group();
osg::Geode* pyramidGeode = createPyramid();
root->addChild(pyramidGeode);
现在,准备加纹理。这里我们会声明一个纹理实例并将它的数据不一致性设为'DYNAMIC'。(如果不把纹理声明为dynamic,osg的一些优化程序会删除它。)这个texture类包装了OpenGL纹理模式(wrap,filter,等等)和一个osg::Image。下面的代码说明了如何从文件里读取osg::Image实例并把这个图像和纹理关联起来。
osg::Texture2D* KLN89FaceTexture = new osg::Texture2D;
// protect from being optimized away as static state:
KLN89FaceTexture->setDataVariance(osg::Object::DYNAMIC);
// load an image by reading a file:
osg::Image* klnFace = osgDB::readImageFile("KLN89FaceB.tga");
if (!klnFace)
{
std::cout << " couldn't find texture, quiting." << std::endl;
return -1;
}
// Assign the texture to the image we read from file:
KLN89FaceTexture->setImage(klnFace);
纹理 可以和渲染StateSet关联起来。下一步就产生一个StateSet,关联并启动我们的纹理,并将这个StateSet附加到我们的geometry上。
// Create a new StateSet with default settings:
osg::StateSet* stateOne = new osg::StateSet();
// Assign texture unit 0 of our new StateSet to the texture
// we just created and enable the texture.
stateOne->setTextureAttributeAndModes
(0,KLN89FaceTexture,osg::StateAttribute::ON);
// Associate this state set with the Geode that contains
// the pyramid:
pyramidGeode->setStateSet(stateOne);
最后一步是仿真循环:
//The final step is to set up and enter a simulation loop.
viewer.setUpViewer(osgProducer::Viewer::STANDARD_SETTINGS);
viewer.setSceneData( root );
viewer.realize();
while( !viewer.done() )
{
viewer.sync();
viewer.update();
viewer.frame();
}
return 0;
}
编码愉快!
教程3——使用Shape,改变state
目标:
用osg::Shape实例构建场景。使用osg::StateSet控制shape的渲染。
使用Shape类:
Shape类是所有形状类别的基类。Shape既可用于剪裁和碰撞检测也可用于定义程序性地产生几何体的那些基本形状。下面的类继承自Shape类:
TriangleMesh
Sphere
InfinitePlane
HeightField
Cylinder
Cone
CompositeShape
Box
为了使这些形状可以被渲染,我们需要把他们和Drawable类的实例关联起来。ShapeDrawable类提供了这样的功能。这个类继承自Drawable并允许我们把Shape实例附加到可以被渲染的东西上。既然ShapeDrawable类继承自Drawable,ShapDrawable实例就可以被加到Geode类实例上。下面的步骤演示了将一个单位立方体加到空场景中时是如何做到这些的。
// Declare a group to act as root node of a scene:
osg::Group* root = new osg::Group();
// Declare a box class (derived from shape class) instance
// This constructor takes an osg::Vec3 to define the center
// and a float to define the height, width and depth.
// (an overloaded constructor allows you to specify unique
// height, width and height values.)
osg::Box* unitCube = new osg::Box( osg::Vec3(0,0,0), 1.0f);
// Declare an instance of the shape drawable class and initialize
// it with the unitCube shape we created above.
// This class is derived from 'drawable' so instances of this
// class can be added to Geode instances.
osg::ShapeDrawable* unitCubeDrawable = new osg::ShapeDrawable(unitCube);
// Declare a instance of the geode class:
osg::Geode* basicShapesGeode = new osg::Geode();
// Add the unit cube drawable to the geode:
basicShapesGeode->addDrawable(unitCubeDrawable);
// Add the goede to the scene:
root->addChild(basicShapesGeode);
产生一个球体和上面的代码基本相似。没有太多的注释的代码看起来是这个样子:
// Create a sphere centered at the origin, unit radius:
osg::Sphere* unitSphere = new osg::Sphere( osg::Vec3(0,0,0), 1.0);
osg::ShapeDrawable* unitSphereDrawable
= new osg::ShapeDrawable(unitSphere);
现在,我们可以使用transform节点将这个球体加到场景中,以便让它离开原点。unitSphereDrawable不能直接添加到场景中(因为它不是继承自node类),所以我们需要一个新的geode以便添加它。
osg::PositionAttitudeTransform* sphereXForm =
new osg::PositionAttitudeTransform();
sphereXForm->setPosition(osg::Vec3(2.5,0,0));
osg::Geode* unitSphereGeode = new osg::Geode();
root->addChild(sphereXForm);
sphereXForm->addChild(unitSphereGeode);
unitSphereGeode->addDrawable(unitSphereDrawable);
设置状态
前面的教程讲解了如何生成纹理,将其指定为从文件加载的图像,生成一个带纹理的StateSet。下面的代码建立了两个状态集合——一个是BLEND纹理模式,另一个是DECAL纹理模式。BLEND模式:
// Declare a state set for 'BLEND' texture mode
osg::StateSet* blendStateSet = new osg::StateSet();
// Declare a TexEnv instance, set the mode to 'BLEND'
osg::TexEnv* blendTexEnv = new osg::TexEnv;
blendTexEnv->setMode(osg::TexEnv::BLEND);
// Turn the attribute of texture 0 - the texture we loaded above - 'ON'
blendStateSet->setTextureAttributeAndModes
(0,KLN89FaceTexture,osg::StateAttribute::ON);
// Set the texture texture environment for texture 0 to the
// texture envirnoment we declared above:
blendStateSet->setTextureAttribute(0,blendTexEnv);
重复这些步骤,产生DECAL纹理模式的状态集合。
osg::StateSet* decalStateSet = new osg::StateSet();
osg::TexEnv* decalTexEnv = new osg::TexEnv();
decalTexEnv->setMode(osg::TexEnv::DECAL);
decalStateSet->setTextureAttributeAndModes
(0,KLN89FaceTexture,osg::StateAttribute::ON);
decalStateSet->setTextureAttribute(0,decalTexEnv);
产生了状态集合后我们就可以把它们应用在场景中的节点上。在scene graph的绘制遍历(root->leaf)中状态是积累的。除非这个节点有一个它自己的状态,否则它会继承其父节点的状态。(如果一个节点有一个以上的父节点,它会使用一个以上的状态渲染。)
root->setStateSet(blendStateSet);
unitSphereGeode->setStateSet(decalStateSet);
最后一步是进入仿真循环。
viewer.setUpViewer(osgProducer::Viewer::STANDARD_SETTINGS);
viewer.setSceneData( root );
viewer.realize();
while( !viewer.done() )
{
viewer.sync();
viewer.update();
viewer.frame();
}
return 0;
教程4——更多的StateSet
// For state five, change the texture associated with texture unit 0
// Set the mode of an osg::TexEnv instance to DECAL
// For stateTwo, turn FOG OFF and set to OVERRIDE.
// For stateFour, set the mode to PROTECTED, thus overriding the parent setting
// apply the StateSets above to appropriates nodes in the scene graph.
加载几何模型并加入到场景中,调整其中一个模型在场景中的位置并通过安装仿真循环观察场景。
如果你下载了当前版本的Open Scene Graph,那么你就可以将在有相应插件的任何文件格式。包括以下的几何文件格式:3dc,3ds,flt,geo,iv,ive,lwo,md2,obj,osg和以下这些图像文件格式:bmp,gif,jpeg,rgb,tga,tif。
Open Scene Graph安装包里包含了很多open scene graph格式(.osg)的几何文件。我们会加载其中一个,还有一个MPI Open Flight(.flt)文件。为了便于找到模型,建立一个models文件夹,并用OSG_DATA_PATH系统变量指向它。(通常为C:/Projects/OpenSceneGraph/OpenSceneGraph-Data/)。解压此文件到那个文件夹下。
几何模型使用scene graph的节点表示。因此,为了加载并操作一个几何模型文件,我们需要声明一个句柄(或指针)指向osg::Node类型实例。(在一些要求的#include后)。
#include <osg/Node> #include <osgDB/ReadFile> ... osg::Node* cessnaNode = NULL; osg::Node* tankNode = NULL; ... cessnaNode = osgDB::readNodeFile("cessna.osg"); tankNode = osgDB::readNodeFile("Models/T72-tank/t72-tank_des.flt"); |
这就是加载数据库需要做的事。下一步我们把它作为scene graph的一部分加入。将模型加载到transform节点的子节点上,这样我们就可以重新定位它了。
// Declare a node which will serve as the root node // for the scene graph. Since we will be adding nodes // as 'children' of this node we need to make it a 'group' // instance. // The 'node' class represents the most generic version of nodes. // This includes nodes that do not have children (leaf nodes.) // The 'group' class is a specialized version of the node class. // It adds functions associated with adding and manipulating // children.
osg::Group* root = new osg::Group(); root->addChild(cessnaNode);
// Declare transform, initialize with defaults.
osg::PositionAttitudeTransform* tankXform = new osg::PositionAttitudeTransform();
// Use the 'addChild' method of the osg::Group class to // add the transform as a child of the root node and the // tank node as a child of the transform.
root->addChild(tankXform);
tankXform->addChild(tankNode);
// Declare and initialize a Vec3 instance to change the // position of the tank model in the scene osg::Vec3 tankPosit(5,0,0); tankXform->setPosition( tankPosit ); |
现在,我们的scene graph由一个根节点和两个子节点组成。一个是cessna的几何模型,另一个是一个右子树,由一个仅有一个tank的几何模型的transform节点组成。为了观察场景,需要建立一个viewer和一个仿真循环。就像这样做的:
#include <osgProducer/Viewer>
// Declare a 'viewer'
osgProducer::Viewer viewer;
// For now, we can initialize with 'standard settings' // Standard settings include a standard keyboard mouse // interface as well as default drive, fly and trackball // motion models for updating the scene.
viewer.setUpViewer(osgProducer::Viewer::STANDARD_SETTINGS);
// Next we will need to assign the scene graph we created // above to this viewer:
viewer.setSceneData( root );
// create the windows and start the required threads.
viewer.realize();
// Enter the simulation loop. viewer.done() returns false // until the user presses the 'esc' key. // (This can be changed by adding your own keyboard/mouse // event handler or by changing the settings of the default // keyboard/mouse event handler)
while( !viewer.done() ) { // wait for all cull and draw threads to complete. viewer.sync();
// Initiate scene graph traversal to update nodes. // Animation nodes will require update. Additionally, // any node for which an 'update' callback has been // set up will also be updated. More information on // settting up callbacks to follow.
viewer.update(); // initiate the cull and draw traversals of the scene.
viewer.frame(); }
|
目标
添加文本到场景中——包括HUD风格的文本和作为场景一部分的文本。
摘要
文本类继承自Drawable类。也就是说文本实例可以加到Geode类实例上并且可以和其它几何体一样被渲染。与文本类相关的方法的全部列在*这里*。‘osgExample Text’工程示范了许多方法。这个教程提供了文本类的几个有限的函数。绘制一个HUD牵扯到下面两个概念:
1、生成一个子树,它的根节点有合适的投影及观察矩阵...
2、将HUD子树中的几何体指定到合适的RenderBin上,这样HUD几何体就会在场景的其他部分之后按正确地状态设置绘制。
渲染HUD的子树涉及到一个投影矩阵和一个观察矩阵。对于投影矩阵,我们会使用相当于屏幕维数水平和垂直扩展的正投影。根据这种模式,坐标相当于象素坐标。为了简单起见,观察矩阵使用单位矩阵。
为了渲染HUD,我们把它里面的几何体附加到一个指定的RenderBin上。RenderBin允许用户在几何体绘制过程中指定顺序。这对于HUD几何体需要最后绘制来说很有用。
代码
首先,声明我们需要的变量-osg::Text和osg::Projection。
osg::Group* root = NULL;
osg::Node* tankNode = NULL;
osg::Node* terrainNode = NULL;
osg::PositionAttitudeTransform* tankXform;
osgProducer::Viewer viewer;
// A geometry node for our HUD:
osg::Geode* HUDGeode = new osg::Geode();
// Text instance that wil show up in the HUD:
osgText::Text* textOne = new osgText::Text();
// Text instance for a label that will follow the tank:
osgText::Text* tankLabel = new osgText::Text();
// Projection node for defining view frustrum for HUD:
osg::Projection* HUDProjectionMatrix = new osg::Projection;
从文件里加载模型,和前面的教程一样建立scene graph(这里没什么新东东)。
// Initialize root of scene:
root = new osg::Group();
osgDB::FilePathList pathList = osgDB::getDataFilePathList();
pathList.push_back
("C://Projects//OpenSceneGraph//OpenSceneGraph-Data//NPSData//Models//T72-Tank//");
pathList.push_back
("C://Projects//OpenSceneGraph//OpenSceneGraph-Data//NPSData//Models//JoeDirt//");
pathList.push_back
("C://Projects//OpenSceneGraph//OpenSceneGraph-Data//NPSData//Textures//");
osgDB::setDataFilePathList(pathList);
// Load models from files and assign to nodes:
tankNode = osgDB::readNodeFile("t72-tank_des.flt");
terrainNode = osgDB::readNodeFile("JoeDirt.flt");
// Initialize transform to be used for positioning the tank
tankXform = new osg::PositionAttitudeTransform());
tankXform->setPosition( osg::Vec3d(5,5,8) );
// Build the scene - add the terrain node directly to the root,
// connect the tank node to the root via the transform node:
root->addChild(terrainNode);
root->addChild(tankXform);
tankXform->addChild(tankNode);
下一步,建立场景来显示HUD组件。添加一个子树,它的根节点有一个投影和观察矩阵。
// Initialize the projection matrix for viewing everything we
// will add as descendants of this node. Use screen coordinates
// to define the horizontal and vertical extent of the projection
// matrix. Positions described under this node will equate to
// pixel coordinates.
HUDProjectionMatrix->setMatrix(osg::Matrix::ortho2D(0,1024,0,768));
// For the HUD model view matrix use an identity matrix:
osg::MatrixTransform* HUDModelViewMatrix = new osg::MatrixTransform;
HUDModelViewMatrix->setMatrix(osg::Matrix::identity());
// Make sure the model view matrix is not affected by any transforms
// above it in the scene graph:
HUDModelViewMatrix->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
// Add the HUD projection matrix as a child of the root node
// and the HUD model view matrix as a child of the projection matrix
// Anything under this node will be viewed using this projection matrix
// and positioned with this model view matrix.
root->addChild(HUDProjectionMatrix);
HUDProjectionMatrix->addChild(HUDModelViewMatrix);
现在建立几何体。我们根据屏幕坐标建立一个四边形,并设置颜色和纹理坐标。
// Add the Geometry node to contain HUD geometry as a child of the
// HUD model view matrix.
HUDModelViewMatrix->addChild( HUDGeode );
// Set up geometry for the HUD and add it to the HUD
osg::Geometry* HUDBackgroundGeometry = new osg::Geometry();
osg::Vec3Array* HUDBackgroundVertices = new osg::Vec3Array;
HUDBackgroundVertices->push_back( osg::Vec3( 0, 0,-1) );
HUDBackgroundVertices->push_back( osg::Vec3(1024, 0,-1) );
HUDBackgroundVertices->push_back( osg::Vec3(1024,200,-1) );
HUDBackgroundVertices->push_back( osg::Vec3( 0,200,-1) );
osg::DrawElementsUInt* HUDBackgroundIndices =
new osg::DrawElementsUInt(osg::PrimitiveSet::POLYGON, 0);
HUDBackgroundIndices->push_back(0);
HUDBackgroundIndices->push_back(1);
HUDBackgroundIndices->push_back(2);
HUDBackgroundIndices->push_back(3);
osg::Vec4Array* HUDcolors = new osg::Vec4Array;
HUDcolors->push_back(osg::Vec4(0.8f,0.8f,0.8f,0.8f));
osg::Vec2Array* texcoords = new osg::Vec2Array(4);
(*texcoords)[0].set(0.0f,0.0f);
(*texcoords)[1].set(1.0f,0.0f);
(*texcoords)[2].set(1.0f,1.0f);
(*texcoords)[3].set(0.0f,1.0f);
HUDBackgroundGeometry->setTexCoordArray(0,texcoords);
osg::Texture2D* HUDTexture = new osg::Texture2D;
HUDTexture->setDataVariance(osg::Object::DYNAMIC);
osg::Image* hudImage;
hudImage = osgDB::readImageFile("HUDBack2.tga");
HUDTexture->setImage(hudImage);
osg::Vec3Array* HUDnormals = new osg::Vec3Array;
HUDnormals->push_back(osg::Vec3(0.0f,0.0f,1.0f));
HUDBackgroundGeometry->setNormalArray(HUDnormals);
HUDBackgroundGeometry->setNormalBinding(osg::Geometry::BIND_OVERALL);
HUDBackgroundGeometry->addPrimitiveSet(HUDBackgroundIndices);
HUDBackgroundGeometry->setVertexArray(HUDBackgroundVertices);
HUDBackgroundGeometry->setColorArray(HUDcolors);
HUDBackgroundGeometry->setColorBinding(osg::Geometry::BIND_OVERALL);
HUDGeode->addDrawable(HUDBackgroundGeometry);
为了正确的渲染HUD,我们建立带有深度检测和透明度混合的osg::stateSet。我们也要保证HUD几何体最后绘制。几何体在裁剪遍历时通过指定一个已编号的渲染箱可以控制渲染顺序。最后一行演示了这些:
// Create and set up a state set using the texture from above:
osg::StateSet* HUDStateSet = new osg::StateSet();
HUDGeode->setStateSet(HUDStateSet);
HUDStateSet->
setTextureAttributeAndModes(0,HUDTexture,osg::StateAttribute::ON);
// For this state set, turn blending on (so alpha texture looks right)
HUDStateSet->setMode(GL_BLEND,osg::StateAttribute::ON);
// Disable depth testing so geometry is draw regardless of depth values
// of geometry already draw.
HUDStateSet->setMode(GL_DEPTH_TEST,osg::StateAttribute::OFF);
HUDStateSet->setRenderingHint( osg::StateSet::TRANSPARENT_BIN );
// Need to make sure this geometry is draw last. RenderBins are handled
// in numerical order so set bin number to 11
HUDStateSet->setRenderBinDetails( 11, "RenderBin");
最后,使用文本时,由于osg::Text是继承自osg::Drawable的,osg::Text实例可以作为孩子加到osg::Geode类实例上。
// Add the text (Text class is derived from drawable) to the geode:
HUDGeode->addDrawable( textOne );
// Set up the parameters for the text we'll add to the HUD:
textOne->setCharacterSize(25);
textOne->setFont("C:/WINDOWS/Fonts/impact.ttf");
textOne->setText("Not so good");
textOne->setAxisAlignment(osgText::Text::SCREEN);
textOne->setPosition( osg::Vec3(360,165,-1.5) );
textOne->setColor( osg::Vec4(199, 77, 15, 1) );
// Declare a geode to contain the tank's text label:
osg::Geode* tankLabelGeode = new osg::Geode();
// Add the tank label to the scene:
tankLabelGeode->addDrawable(tankLabel);
tankXform->addChild(tankLabelGeode);
// Set up the parameters for the text label for the tank
// align text with tank's SCREEN.
// (for Onder: use XZ_PLANE to align text with tank's XZ plane.)
tankLabel->setCharacterSize(5);
tankLabel->setFont("/fonts/arial.ttf");
tankLabel->setText("Tank #1");
tankLabel->setAxisAlignment(osgText::Text::XZ_PLANE);
// Set the text to render with alignment anchor and bounding box around it:
tankLabel->setDrawMode(osgText::Text::TEXT |
osgText::Text::ALIGNMENT |
osgText::Text::BOUNDINGBOX);
tankLabel->setAlignment(osgText::Text::CENTER_TOP);
tankLabel->setPosition( osg::Vec3(0,0,8) );
tankLabel->setColor( osg::Vec4(1.0f, 0.0f, 0.0f, 1.0f) );
最后,建立viewer并进入仿真循环:
viewer.setUpViewer(osgProducer::Viewer::STANDARD_SETTINGS);
viewer.setSceneData( root );
viewer.realize();
while( !viewer.done() )
{
viewer.sync();
viewer.update();
viewer.frame();
}
好运!