现在的位置: 首页 > 综合 > 正文

【Ogre编程入门与进阶】第十章 Ogre场景管理

2014年10月25日 ⁄ 综合 ⁄ 共 16593字 ⁄ 字号 评论关闭

10.1    Ogre场景管理总览及场景结构体系

10.1.1  场景管理总览

在前面的章节,我们已经多次提到过场景管理器(SceneManager)的概念,它是Ogre中相当重要的概念。当我们进行Ogre程序开发的时候,场景管理器也是最经常用到的对象。场景管理器控制着场景中的所有内容,它还负责组织创建管理所有的摄像机(Camera)、可移动的实体(Entity)、光(Light)和材质(Material),并且载入和布置世界地图等。

那么,到底什么是场景呢?在三维游戏、动画或应用程序中,场景就像是一个大的容器,它用来盛放我们需要在这个三维“虚拟世界”中要显示的物体。这些物体既包括非活动物体,即一旦放入场景就不能被移动的物体,也包括一些活动物体,例如一些模型、建筑物,人物,树,椅子以及灯光和摄像机等等。另外,场景包括很多不同的类型,例如有室内场景和室外场景。在一幅室内场景中,可能有楼梯、走廊,也可能有沙发、家具和墙壁等等;室外场景可能包括高山、流水、草地,也可能包括蔚蓝的天空和飘在上面的白云等等。

       为了管理这些场景,Ogre为我们提供了很多不同类型的场景管理器,不同的场景管理器用来管理不同类型的场景。场景管理器主要负责以下事情:1 创建放置在场景中的可移动对象,例如模型、灯光、摄像机等等,并可以有效地访问这些对象。2 加载世界地图,这个世界地图是不可移动的,而且它通常很大。3 场景查询,知道场景中都包含哪些对象。4 将不可见对象卸载,把可见对象放入渲染队列进行渲染。5 根据当前和渲染物体的透视图,按由近到远的顺序对无方向的光源(Nondirectional Light)进行组织和排序。6
设置并渲染场景中的所有阴影。7 设置并渲染场景中的其他对象(如背景,天空盒)。8 将组织好的内容传递到渲染系统进行渲染。

    总之,Ogre场景管理器功能丰富,它可以做很多事情,它有许多函数都以create,destroy,get,set开头。例如:setScale()、setposition()函数等。其中,场景管理器最常用的功能就是用来管理场景中的对象,这些对象可以是摄像机(Camera)、可移动的实体(Entity)、光(Light)等。例如,当我想在场景中添加一台摄像机、或是删除某个光源的时候,都得经由场景管理器。

   

笔者注:

1.当我们需要绘制场景时,场景管理器会将场景送到渲染系统(RenderSystem)对象中去,而且,我们无需调用SceneManager::_ renderScene函数来操作这个绘制过程,场景管理器会帮助我们完成函数的调用工作,这样也就大大减少了程序的编写量,提高了程序员的编程效率。

2.场景管理器不仅仅管理场景中的对象,而且它负责对整个场景进行管理。例如计算场景中对象的具体位置、对整个场景进行优化等。

 

10.1.2   场景结构体系

Ogre中的场景管理器设计独特,它实现了场景节点和场景内容的分离。Ogre中有一个实体的概念,我们可以把场景中的3D模型都看做实体(例如,我们前几章添加进createScene函数里的OgreHead.mesh和Ninja.mesh等)。但是,实体不能直接被放置到场景中,它需要与场景节点绑定后放入场景,这样,这个实体才能够被渲染;同样,一个场景节点也不能单独的在屏幕上显示出来,只有与一个实体绑定后才能在屏幕上显示,场景节点记录了绑定在上面的实体的信息。

 

正如上图所示,根节点下绑定场景节点,场景节点上可以再绑定场景节点,实体绑定在场景节点上,而且,一个场景节点上可以绑定多个实体。在场景中,被渲染的对象是实体,而不是场景节点,场景节点中只包含了绑定在上面的实体的方位等信息。例如,当我们移动节点2,实体3就跟着移动;当我们移动节点1,实体1和2就跟着移动。这就好比场景节点是实体的“代言人”,对实体的移动、旋转、缩放等操作,其实是通过实体绑定着的场景节点完成的。另外,实体是场景中被渲染的对象,它包含了纹理、材质等信息,而场景节点却不必包含这些信息。这也就意味着,Ogre实现了场景节点与场景内容(即实体)的分离。

10.1.3   场景管理器的类型

       首先,我们要向大家介绍一个createSceneManager()函数,因为Ogre正是使用了createSceneManager()函数来创建场景管理器,代码如下:

SceneManager* sceneMgr = root->createSceneManager(ST_GENERIC, “MyOgreSManagemer");

通过这一句代码,我们创建了一个ST_GENERIC类型(它的具体含义将在下文介绍)的场景管理器,它的名字是“MyOgreSManagemer”。 createSceneManager()函数的第一个参数用来设置场景管理器的“类型”,第二个参数用来设置我们所创建的场景管理器的名称。其中,第一个参数所有取值有如下几种:

(1)ST_GENERIC:这是最简单的场景管理器的类型,这个类型的场景管理器没有对场景内容和结构做任何优化。如果一个程序的场景结构非常简单,我们才会考虑使用这个类型的场景管理器。

(2)ST_INTERIOR:这种类型的场景管理器,优化了室内近距离的渲染,比较适合高密度的场景。

(3)ST_EXTERIOR_CLOSE:这种场景管理器,优化了室外场景里面的中近距离的渲染,比较适合用一个简单模型或者高度场的场景地图。

(4)ST_EXTERIOR_FAR:在目前的Ogre版本中,这种类型的场景管理器现在已经不再使用了。在需要的时候会用ST_EXTERIOR_CLOSE和ST_EXTERIOR_REAL_FAR来代替这个选项。

(5)ST_EXTERIOR_REAL_FAR:这种类型的场景管理器特别适合那种需要动态加载内容的场景。需要动态加载的场景通常都非常大,可能要加载的内容也非常大,在这种情况下,我们一般会使用这种ST_EXTERIOR_REAL_FAR类型的场景管理器。

      

笔者注:

在创建场景管理器的时候可以指定上述参数值的一种或是几种,即我们创建的场景管理器可以具备上述特性的一种或几种。默认情况下,系统会选择具有ST_GENERIC特性的场景管理器。

 

除了这种用参数值来选择场景管理器类型的方法,我们还可以通过场景管理器的名字来选择场景管理器。Ogre官方一共提供了四种可供选择的场景管理器:

(1)GenericSceneManager(默认类型)

(2)OctreeSceneManager

(3)BspSceneManager

(4)Portal Connected Zone Scene Manager (PCZ)SceneManager

其中,GenericSceneManager 类型的场景管理器具有ST_GENERIC 特性;OctreeSceneManager和PCZSceneManager 具有全部的5个特性(ST_GENERIC、ST_INTERIOR、ST_EXTERIOR_CLOSE、ST_EXTERIOR_FAR、ST_EXTERIOR_REAL_FAR),BspSceneManager管理器具有ST_INTERIOR特性。

 

笔者注:

如果我们用第二种“名字”的方式来选择场景管理器,那么我们必须要将我们要选择的场景管理器的名字写入plugins.cfg文件中,如果我们在plugins.cfg文件中,依次写入了三个场景管理器的名字,它们是:BSP, PCZ and OctreeSceneManager,那么最终只有最后一个OctreeSceneManager会被系统选中作为我们的场景管理器。事实上,默认情况下plugins.cfg文件中的场景管理器的名称的顺序就是上述的BSP, PCZ and OctreeSceneManager ,所以OctreeSceneManager就成为了系统默认的场景管理器。

 

1.  Generic Scene Manager

Generic Scene Manager是默认的场景管理系统,但是它的功能非常的简单而且缺点非常多,我们一般不使用它。

2.  Octree Scene Manager

Octree Scene Manager简单、通用,并使用了八叉树(Octree)的场景结构,这种类型的场景管理器是目前使用的最多的。在前面的章节中,我们使用的例子,一直使用的场景管理类型是八叉树场景管理(OctreeSceneManager),这个场景管理器使用八叉树的方式来存储场景,为了让大家更好的理解,我们首先来了解一下什么是八叉树。

八叉树(Octree)

正如其名字表示的那样,八叉树也是一种树,它有根,每一个节点也有父(根节点除外)。它和其它树的区别在于每个节点最大可以有八个孩子,下面的图表展示了八叉树的结构:

Ogre3D使用八叉树存储3D场景是由于八叉树具有一些存储3D场景非常有用的属性,其中一个就是它能达到八个孩子。例如,如果我们有两个对象在一个3D场景中,我们能用一个立方体来装入整个这个场景。

如果我们用这立方体的一半长宽高,我们就得到了八个新的立方体,每一个就可以装入八分之一的场景。这八个立方体可以认为是最初的那个立方体的八个孩子。

现在场景中的两个对象就存在于右上角的那个立方体内,其它的七个立方体是空的,因此是叶子节点。我们现在只需再划分包含两个对象的那个立方体。

现在,每个立方体或者有一个对象,或者没有对象已经是叶子节点了。八叉树的这一属性使得“剔除”实现的很容易并且很快速,当我们渲染一个场景的时候,我们相机的视锥体就会在场景中,我们需要决定哪些对象需要被渲染,哪些不需要渲染,因此,我们从八叉树的根节点开始,如果视锥体和这个立方体相交,我们就继续,通常情况一开始都会是这样,因为这个八叉树在第0层次装入了整个的场景,我们然后继续查看八叉树的第1层,也就是根节点的孩子,我们再次用视锥体和每个孩子节点测试,如果他们相交,我们就继续测试这个立方体的孩子,但是如果这个立方体完全的在视锥体内,我们就不需要再进行深层次的测试,因为我们已经知道了这个立方体的所有孩子也都在这个视锥体内了,我们一直这样继续做,直到我们到达了叶子节点,然后我们再继续测试其它的孩子,直到我们仅仅只剩下叶子节点了,没有其它的可以测试的立方体了。

       通过这个算法,我们在每一步丢弃了大部分的场景,一般情况,仅仅只需几步,我们就对场景剔除到只剩下空叶子节点了或者含有对象的叶子节点了,这样我们就会知道哪些对象是可见的,是需要渲染的,而哪些是不需要渲染的。这种方法和二叉树很类似,不同的是它用八个孩子代替了二叉树的两个孩子。

3.  BSP Scene Manager

BSP(binary-space partitioning) Scene Manager(二叉空间分割)场景管理器比较适合用于室内场景。

笔者注:

BSP(binary-space partitioning)二叉空间分割类型的场景管理器也是一个比较常用的场景管理类型,但是对于现在的应用程序而言,更倾向于使用BSP做空间快速碰撞检测而不是几何体分割。Ogre中为我们提供了一个简单得BSP支持,这里我们只是为了让大家多了解一种场景管理类型,实际上这里的这种场景管理类型以及很少使用了,大家可以了解即可。

 

4.  Portal Connected Zone Scene Manager (PCZSM)

Portal Connected Zone Scene Manager场景管理器也非常适用于室内的场景,而且它兼容大量的第三方工具。

 

10.1.4 通过实例了解Ogre场景管理

 

实例一:

首先,我们还是使用我们最初定义的模板代码,在此基础上添加新的内容,模板代码如下:

#include
<windows.h>

#include
"ExampleApplication.h"

class
Example1
: public
ExampleApplication

{

public:

     void
createScene
()

     {

     }

protected:

private:

};

INT
WINAPI
WinMain(
HINSTANCE
hInst,
HINSTANCE
, LPSTR
strCmdLine
, INT )

{

     Example1
app
;

     app.go();

     return 0;

}

第一步:重写函数createSceneManager,使之创建一个与原来默认场景管理不同的场景管理类型BspSceneManager在Example1类的定义中增加如下代码:

 void chooseSceneManager(void)

{

     ResourceGroupManager::getSingleton().addResourceLocation("../../Samples/media/packs/chiropteraDM.pk3""Zip"

     ResourceGroupManager::getSingleton().getWorldResourceGroupName(),true);

     ResourceGroupManager::getSingleton().initialiseResourceGroup(ResourceGroupManager::getSingleton().getWorldResourceGroupName());

     mSceneMgr =
mRoot
->createSceneManager("BspSceneManager");

     mSceneMgr->setWorldGeometry("maps/chiropteradm.bsp");

}

第二步,在createScene函数中增加如下代码调整相机的观察角度:

void
createScene
()

{

    //Quake 使用z轴做为向上的轴,因此我们这里需要做一些调整

     mCamera->setFixedYawAxis(true,Vector3::UNIT_Z);

     mCamera->pitch(Degree(90));

}

编译并运行程序,你将会发现一个比较复杂的场景:

 

实例二:

1.手动创建自己的模型

       前面的代码我们都是加载已经存在的模型,现在我们来学习一下,怎么通过自己定义顶点的方式来生成我们所需要的模型,这里我们自己手动生成“草”的模型。

首先,我们还是使用我们最初定义的模板代码,在此基础上添加新的内容。

第一步,我们定义一个面板用来当做地面,这里为了方便我们还是使用Plane类来定义,当然,当我们学习完这节之后我们同样也可以手动定义自己的面板。在createScene函数中增加如下代码:

Ogre::Plane plane(Vector3::UNIT_Y, -10);

Ogre::MeshManager::getSingleton().createPlane("plane",

ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,plane,1500,1500,200,200,true,1,5,5,Vector3::UNIT_Z);

第二步,为新创建的面板设置一个材质(关于材质的介绍我们会在材质一章介绍,这里暂时不要求掌握),继续在createScene函数中添加如下代码:

Ogre::Entity*ent =
mSceneMgr->createEntity("GrassPlane","plane");

mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(ent);

ent->setMaterialName("Examples/GrassFloor");

第三步,手动创建我们的新ManualObject:

Ogre::ManualObject*manualObj =
mSceneMgr->createManualObject("grass");

manualObj->begin("Examples/GrassBlades",RenderOperation::OT_TRIANGLE_LIST);//创建开始

//创建第一个三角形

manualObj->position(5.0, 0.0, 0.0);

manualObj->textureCoord(1,1);

manualObj->position(-5.0, 10.0, 0.0);

manualObj->textureCoord(0,0);

manualObj->position(-5.0, 0.0, 0.0);

manualObj->textureCoord(0,1);

//创建第二个三角形

manualObj->position(5.0, 0.0, 0.0);

manualObj->textureCoord(1,1);

manualObj->position(5.0, 10.0, 0.0);

manualObj->textureCoord(1,0);

manualObj->position(-5.0, 10.0, 0.0);

manualObj->textureCoord(0,0);

 

manualObj->end();//创建结束

Ogre::SceneNode*grassNode =
mSceneMgr->getRootSceneNode()->createChildSceneNode("GrassNode",Ogre::Vector3(0,-10,0));

grassNode->attachObject(manualObj);

编译并运行程序,你将会看到以下效果:

 

代码分析:

       第一步和第二步中的代码相信大家如果看过前面章节应该不难理解,如果哪里不懂请查阅前面章节的介绍,我们这里主要分析第三步中的代码,现在我们先来了解一个新的概念Manual object:

       一个手动对象(Manual Object)就像是一个新的代码文件,在开始的时候它是空的,但是通过它我们可以做很多事,我们可以通过Manual Object创建一个3D模型,我们如果想创建就需要给出它的三角形的顶点描述,我们知道Ogre中用到的3D模型是通过很多的三角形面片组成的,我们可以定义多个三角形面片的描述来最终生成新的3D模型,比如说我们想生成一个四边形,我们就需要两个三角形来构成。在前面第三步中的代码中,我们首先通过场景管理器类创建了一个新的手动对象,并给它命名为"grass",然后我们调用ManualObject的begin函数,表示我们可是准备对它的顶点信息进行描述,我们这里给这个函数传入了两个参数,第一个表示我们新生成的这个对象需要赋予什么样的材质,同样我们这里只需要知道这个材质是我们事先已经定义好的即可;如果我们接触过OpenGL或者DirectX我们应该不难知道第二个参数代表了我们所要创建的几何图元的渲染操作类型,Ogre中定义了如下几种几何图元渲染操作的类型:

渲染操作类型

含义

OT_POINT_LIST

OT_LINE_LIST

OT_LINE_STRIP

OT_TRIANGLE_LIST

OT_TRIANGLE_STRIP

OT_TRIANGLE_FAN

一系列点的列表,每个点由一个顶点构成

一系列线的列表,每条线由两个顶点构成

一系列的连接的直线

一系列的三角形列表,每个三角形由三个顶点组成

三角形的连接串

连接成扇形的三角形系列

在前面的代码中,我们定义草的模型时,实际上是定义了一个四边形的面片,由两个三角形组成,如下图所示,对应了两个三角形的各个顶点的坐标:

其中,第一个三角形需要顶点1,2,3,第二个三角形需要顶点1,2,4,。如代码中所示,position函数的三个参数即代表顶点的位置,textureCoord的两个参数代表的是每个顶点对应的纹理坐标。

2.纹理映射

前面的章节我们已经简单介绍过纹理坐标的概念,Ogre3D中,为了让我们准备的一张图片映射到一个图形(如四边形)上,我们除了定义这个图形的位置,我们同样需要提供这个图形每个顶点的纹理坐标,它是一个两元组(u,v),它描述了在图片中对应的位置,其中u代表了x轴的坐标值,v代表了y轴的坐标值,(0,0)指的是图片的左上角,(1,1)指的是图片的右下角,如下图所示:

如果我们使用大于1的纹理坐标值,依赖于我们在材质中的不同设置,会出现不同的情况。如果我们使用wrap模式,纹理就会重复显示;如果我们使用clamp模型,每一个大于1的值都会减到1,同理,如果小于0的值会被设置到0;如果我们使用mirror模式,这就是一种镜像模式,这种模式下,1会变成0,2会变成1,如此类推,如果值大于2,那么再从起初的图像开始使用;还有一种模式是定义边缘颜色,超出0~1范围的都会被渲染成边缘颜色。

       了解了这些,我们就可以知道前面begin和end函数之间的代码实际上是完成了下面类似的工作:

分析到这里,我们再来看一下前面最后两行的代码,很简单,我们的目的就是把新生成的这个对象挂接到一个场景节点上使之最终能够被渲染出来。

3. 2D到3D的转换

如果大家仔细观察肯定会发现,我们前面代码最终生成的草模型其实只是一张2D的四边形面片,如果我们转动一下摄像机视角就会发现我们创建的模型其实并不是真正的3D模型,因此下面我们通过一种简单的方法让其真正成为3D的模型效果。

第一步,继续前面创建第二个三角形的代码后面创建四个三角形(在manualObj->end();这行代码之前添加):

//创建第三个三角形

manualObj->position(2.5, 0.0, 4.3);

manualObj->textureCoord(1,1);

manualObj->position(-2.5, 10.0, -4.3);

manualObj->textureCoord(0,0);

manualObj->position(-2.0, 0.0, -4.3);

manualObj->textureCoord(0,1);

//创建第四个三角形

manualObj->position(2.5, 0.0, 4.3);

manualObj->textureCoord(1,1);

manualObj->position(2.5, 10.0, 4.3);

manualObj->textureCoord(1,0);

manualObj->position(-2.5, 10.0, -4.3);

manualObj->textureCoord(0,0);

//创建第五个三角形

manualObj->position(2.5, 0.0, -4.3);

manualObj->textureCoord(1,1);

manualObj->position(-2.5, 10.0, 4.3);

manualObj->textureCoord(0,0);

manualObj->position(-2.0, 0.0, 4.3);

manualObj->textureCoord(0,1);

//创建第六个三角形

manualObj->position(2.5, 0.0, -4.3);

manualObj->textureCoord(1,1);

manualObj->position(2.5, 10.0, -4.3);

manualObj->textureCoord(1,0);

manualObj->position(-2.5, 10.0, 4.3);

manualObj->textureCoord(0,0);

编译并运行程序,你将会发现无论我们怎么转动摄像机的角度,都会看到一个3D的草模型:

代码分析:

这里的代码其实我们定义了三个四边形面片,每两个之间的夹角为60度,三者正好旋转构成了360度,因此无论我们从哪个方向观看都不会只看到只有一个2D面片的效果了,这样三个四边形结合起来就构成了一个立体的3D模型。

4.大批量植被的种植:

既然我们实现了一颗植被的显示,或许读者朋友会想那么大批量植被的种植,下面我们就来实现这一效果:

很简单,我们第一个想法或许应该就是通过一个for循环来种植大量创建植被。

在createScene函数体的最后继续添加如下代码:

manualObj->convertToMesh("BladesOfGrass");

 

for(inti=0;i<50;i++)

{

     for(intj=0;j<50;j++)

    {

         Ogre::Entity *entTemp =
mSceneMgr->createEntity("BladesOfGrass");//创建匿名Entity

         Ogre::SceneNode*node =
mSceneMgr->getRootSceneNode()->createChildSceneNode(Ogre::Vector3(i*3,-10,j*3));

         node->attachObject(entTemp);

    }

}

编译并运行程序,将会看到以下效果:

代码分析:

       效果如我们想象的那样显示出来了,但是问题又来了,读者朋友或许会问问什么运行起来这么卡呢,或许你的电脑性能足够好,但我相信大部分读者朋友的电脑运行起来这么一个简单的程序肯定会太卡,这到底是什么原因呢,怎么解决这个问题?那么,我们首先来分析一下前面的这些代码。

       manualObj->convertToMesh("BladesOfGrass");这行代码的功能是把我们创建的ManualObject对象转换成Mesh对象,其名称是"BladesOfGrass"。当我们想要去创建它的多个实例的时候,就需要这么做,接着我们通过两个for循环创建了2500个它的实例,现在大家应该知道了吗,虽然我们创建的模型很简单,但是当数量级足够大的时候,这么多的模型显示出来的确是一件十分费时的操作。

10.2 静态几何体(Static geometry)

     为了解决前面我们遇到的程序运行缓慢的问题,我们这里需要修改一下原来的代码:

     void
createScene
()

     {

         Ogre::Planeplane(Vector3::UNIT_Y, -10);

         Ogre::MeshManager::getSingleton().createPlane("plane",

         ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,plane,1500,1500,200,200,true,1,5,5,Vector3::UNIT_Z);

         Ogre::Entity*ent =
mSceneMgr->createEntity("GrassPlane","plane");

         mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(ent);

         ent->setMaterialName("Examples/GrassFloor");

        

         Ogre::ManualObject*manualObj =
mSceneMgr->createManualObject("grass");

         manualObj->begin("Examples/GrassBlades",RenderOperation::OT_TRIANGLE_LIST);

        

         manualObj->position(5.0, 0.0, 0.0);

         manualObj->textureCoord(1,1);

         manualObj->position(-5.0, 10.0, 0.0);

         manualObj->textureCoord(0,0);

         manualObj->position(-5.0, 0.0, 0.0);

         manualObj->textureCoord(0,1);

         manualObj->position(5.0, 10.0, 0.0);

         manualObj->textureCoord(1,0);

         manualObj->position(2.5, 0.0, 4.3);

         manualObj->textureCoord(1,1);

         manualObj->position(-2.5, 10.0, -4.3);

         manualObj->textureCoord(0,0);

         manualObj->position(-2.0, 0.0, -4.3);

         manualObj->textureCoord(0,1);

         manualObj->position(2.5, 10.0, 4.3);

         manualObj->textureCoord(1,0);

         manualObj->position(2.5, 0.0, -4.3);

         manualObj->textureCoord(1,1);

         manualObj->position(-2.5, 10.0, 4.3);

         manualObj->textureCoord(0,0);

         manualObj->position(-2.0, 0.0, 4.3);

         manualObj->textureCoord(0,1);

         manualObj->position(2.5, 10.0, -4.3);

         manualObj->textureCoord(1,0);

        

         //创建第一个三角形

         manualObj->index(0);

         manualObj->index(1);

         manualObj->index(2);

         //创建第二个三角形

         manualObj->index(0);

         manualObj->index(3);

         manualObj->index(1);

        //创建第三个三角形

         manualObj->index(4);

         manualObj->index(5);

         manualObj->index(6);

         //创建第四个三角形

         manualObj->index(4);

         manualObj->index(7);

         manualObj->index(5);

         //创建第五个三角形

         manualObj->index(8);

         manualObj->index(9);

         manualObj->index(10);

         //创建第六个三角形

         manualObj->index(8);

         manualObj->index(11);

         manualObj->index(9);

 

         manualObj->end();

         Ogre::StaticGeometry*field =
mSceneMgr->createStaticGeometry("FieldOfGrass");

         manualObj->convertToMesh("BladesOfGrass");

         for(inti=0;i<50;i++)

         {

              for(intj=0;j<50;j++)

              {

                   Ogre::Entity *entTemp =
mSceneMgr->createEntity("BladesOfGrass");

                   field->addEntity(entTemp,Ogre::Vector3(i*3,-10,j*3));

              }

         }

         field->build();

     }

编译并运行程序,你将会发现效果和原来一样,但和原来不同的是现在的速度比原来快多了。

10.3 渲染管线

     我们知道每次我们渲染一个场景的时候,Ogre3D和图形卡都会执行一系列的操作,如我们前面提到的剔除(culling),我们前面已经提到过不同坐标空间的概念,当我们需要去渲染一个对象的时候,我们需要把它们从局部空间转换到世界空间中,从局部空间到世界空间的转换是一系列简单数学操作的组合,但是它们同样需要花费一些计算时间,当我们渲染前面定义的2500个植被模型的实体的时候,Ogre3D就不得不计算每个实体在每一帧的世界位置,每一帧都有许多操作。更糟糕的是,每一个植被实体是被分离的发送到GPU做渲染的,这就是为什么我们前面的应用程序运行起来比较缓慢的原因了。

而我们通过静态几何体(Static geometry)就解决了这一问题,我们可以这样来理解,Ogre中有很多非活动物体,非活动物体指那些一旦放置进场景就不能移动的物体。非活动物体通常被放置到一个静态几何体(StaticGeometry)中,或者可以这么理解,静态几何体就是一个包含很多个非活动物体的集合。有些设计者习惯于将3D游戏或动画中非常复杂的场景放入一个静态集合体中,从而形成一个非活动物体,这样一来,这个复杂的场景就不能被移动和修改了,这种操作方便了场景的构造,因为我们不会因为改变场景中其它物体的位置而影响到这个大的场景。不过,这样做也有一个非常大的缺点,那就是降低了程序的灵活性,因为如果一旦我们想改变这个大的场景的内容,就变得非常麻烦。而且,还需要注意一点:我们可能在渲染窗口中只能看到静态几何体重的某一部分场景(这个静态几何体包含的场景通常比较大),但是即便如此,Ogre也会将整个几何体都渲染,而不会只渲染我们可以看到的那一小部分,这会大大影响程序的效率。另外,静态几何体中包含了很多不同的物体,这些物体可能有不同的材质,虽然静态几何体会按照材质将这些物体分类,但是,真正渲染的时候,Ogre仍然会按静态几何体中物体的不同的材质进行渲染,即不会以静态几何体为单位进行渲染,这也会增加系统的负担。

在代码中,我们通过场景管理类的createStaticGeometry函数创建了一个静态几何体的实例,接着在后面的for循环代码中,我们把新创建的实体直接增加到静态几何体中,完成这些之后,我们必须调用静态几何体的build函数,这个函数会处理我们前面增加的所有实体对象,并计算它们的的世界坐标位置。使用静态几何体也是有缺点的,其中之一就是剔除操作对它有很少的作用,因为当静态几何体的一部分在视锥体中时,它的所有部分都会被渲染。

10.4 索引顶点

       读者朋友应该会发现我们前面的代码定义顶点的方式和最初的方式不太一样,我们这里用到了索引定义各个三角形顶点的方式,原因是因为这里我们定义的静态几何体实例仅仅能增加使用索引顶点的实体。那么什么是索引呢,下面我们就来学习一下。

       首先,我们结合下面的图示来分析一下我们最初定义顶点的一部分代码:

//创建第一个三角形

manualObj->position(5.0, 0.0, 0.0);

manualObj->textureCoord(1,1);

manualObj->position(-5.0, 10.0, 0.0);

manualObj->textureCoord(0,0);

manualObj->position(-5.0, 0.0, 0.0);

manualObj->textureCoord(0,1);

//创建第二个三角形

manualObj->position(5.0, 0.0, 0.0);

manualObj->textureCoord(1,1);

manualObj->position(5.0, 10.0, 0.0);

manualObj->textureCoord(1,0);

manualObj->position(-5.0, 10.0, 0.0);

manualObj->textureCoord(0,0);

通过上面的对比我们可以发现,一个四边形面片的定义其实只需要四个顶点,但是我们在代码的定义中有两个点的定义重复了两次。

我们再来看一下我们修改后的代码:

manualObj->position(5.0, 0.0, 0.0);

manualObj->textureCoord(1,1);

manualObj->position(-5.0, 10.0, 0.0);

manualObj->textureCoord(0,0);

manualObj->position(-5.0, 0.0, 0.0);

manualObj->textureCoord(0,1);

manualObj->position(5.0, 10.0, 0.0);

manualObj->textureCoord(1,0);

//创建第一个三角形

manualObj->index(0);

manualObj->index(1);

manualObj->index(2);

//创建第二个三角形

manualObj->index(0);

manualObj->index(3);

manualObj->index(1);

我们事先我需要的顶点定义之后,在定义三角形的时候只需要指定对应顶点的索引即可,或许读者朋友认为这样的代码量更多,其实不然,当我们一个模型的面片数足够多的时候,我们就会发现这样的方法可以节省大量重复点的定义,这也是我们这里使用索引定义顶点的另一个原因。

10.5 世界地图

前面几节中,我们介绍了场景中的活动物体和非活动物体,但是场景中只有这些物体当然是不够的,还要为这些物体提供它们活动的平台。世界地图(World Geometry)就扮演了这样的角色,世界地图通常包括了如高度场地图,网格地图,以及在地图上面的建筑物,甚至包括建筑物里面的门和窗户,以及建筑物外面的地面上的草坪等等。我们可以使用一个3D建模软件来构建世界地图,所构建的地图通常是由网格模型为基础构成的,其中包括诸如一些简单的室内场景和室外场景,比如公路、楼房、也包括草坪,树林等植被模型。这里要说明一点,为了提高室外场景的工作性能,Ogre将它们“分割”成一些较小的部分,当它们被系统载入之后,会由系统将它们“组合”在一起。这样做的目的是因为就算整个网格模型只有一小部分需要显示,Ogre也会将整个模型都渲染一遍,这样会大大影响程序的渲染效率。

笔者注:

Ogre中的场景管理器提供了两种不同的读取场景的方式:(1)直接从磁盘文件载入数据;(2)使用数据流的方式载入数据。目前使用的最多的方式,是使用从磁盘中读取地图的方法,这种方法读入的世界地图文件通常是磁盘上已有的文件,可以直接打开并载入。

 

 

笔者注:

为了便于场景的管理和操作,以及对场景进行优化,Ogre通常会把场景中的大的物体分割成一些小的部分来进行控制和管理,这个过程称为空间分割(Spatial Partitioning)。目前,有3种不同的分割策略:(1)通用的空间分割策略;(2)四叉树(Quadtree)和八叉树(Octree)的空间分割策略;(3)二叉树(Binary-Space Partitioning)的空间分割策略。有兴趣的读者可以参阅相关的资料对上面的三种策略进行学习,而且我们前面也已经有所提到了,这里就不再详述了。

 

PS:很久以前就打算把学习Ogre中遇到的知识分享给大家(虽然只是一些皮毛),但是说来惭愧,一直很懒,直到最近才抽出点时间写一些自己的理解(Ogre入门级的东西),所以难免会有很多不足之处,分享是一种快乐,同时也希望和大家多多交流!

(由于在Word中写好的东西发布到CSDN页面需要重新排版(特别是有很多图片时),所以以后更新进度可能会比较慢,同时每章节发布的时间可能不一样(比如说我首选发布的是第二章,其实第一章就是介绍下Ogre的前世今生神马的,相信读者早就了解过~~~),但是我会尽量做到不影响大家阅读,还望大家谅解。)


 上述内容很多引用了网上现有的翻译或者内容,在此一并谢过(个人感觉自己有些地方写得或者翻译的不好),还望见谅,转载请注明此项!

 

抱歉!评论已关闭.