场景管理器 -----OGRE 3D 1.7 Beginner‘s Guide中文版 第六章

原文来源:http://www.cnblogs.com/oneDouble/articles/2628540.html

Ogre提供了很多很多的功能。在这章,我们将会接触一些我们之前没有用过的技术,但是有一些在创建复杂的3D场景中很有帮助,比如SceneManagers,创建我们自己的模型,加速我们的程序和有效率的处理大量的3D数据。

   在这章,我们将会:

※学习如何改变现有的场景管理器。

※学习什么是Octree。

※学习用代码创建自己的实体。

※学习使用静态集合加速我们的程序。

 

【 以空的程序作为起点 】

  这次我们将会使用几乎空白的程序并以这个程序作为起点。

  1. 首先我们需要引入 ExampleApplication 头文件:

#include "Ogre\ExampleApplication.h" 

2. 创建一个从ExampleApplication继承而来的新类并创建一个空的createScene() 函数:

class Example41 : public ExampleApplication 

{

public: 

void createScene() 

{ 

} 

};

 3.最后,我们需要一个main函数来创建程序的实例并运行它。

int main (void) 

{ 

Example41 app; 

app.go(); 

return 0; 

} 

   4. 以同样的头文件和之前使用库目录来编译项目。你将会得到一个可以按escape建关闭的黑色窗口。

 

  刚刚我们创建了一个从ExampleApplication继承的类。它有一个空的createScene函数在基类中它是一个纯虚函数,而且如果我们不重写它,我们不能建立类的实例。我们程序的最有趣的部分将会从现在开始。

 

 

【 获取场景管理器的类型 】

  我们使用之前的代码。一如既往的,也需要添加后面的代码:

 

  1. 使用createScene() 函数打印出场景管理器的名字:

std::cout << mSceneMgr->getTypeName() << "::" << mSceneMgr->getName() << std::endl;

  2. 编译运行程序。当程序开始在控制台运行时,你可以看到下面一行:

OctreeSceneManager::ExampleSMInstance

 

  刚刚我们添加了一行可以打印出场景管理器名字和类型的代码。在当前情况下,场景管理器的名字是ExampleSMInstance,这清楚的表示了在示例程序的场景中使用的管理器的名称。SM代表Scene Manager(场景管理器)。最有趣的部分是类型,在当前情况下是八叉树场景管理器(OctreeSceneManager).在我们详细讲解什么是OctreeSceneManager之前,让我们讨论一下在Ogre 3D中 场景管理器一般做什么。

 

 

【 场景管理器是用来干什么的?】

 

  场景管理器做着很多的事情,当去查看Ogre的文档时,我们就会清楚的发现这一点。管理器中有大量关于创建,销毁,获取,设置和赋值的函数。我们已经使用了一些函数,比如createEntity()createLight() 和 getRootSceneNode()函数。场景管理器的重要任务之一就是实现对象的管理。这些对象可能是场景结点,实体,光源或者其他一些Ogre 3D的对象。场景管理器就扮演一个制造和摧毁对象的工厂。Ogre 3D 工作的原则是——谁创建,谁摧毁。每次我们想要删除一个实体或场景结点的时候,我们必须使用场景管理器。否则Ogre 3D 可能会尝试稍后自动释放内存,这可能会导致程序的崩溃。

 

  场景管理器除了管理对象,也如它名字所暗示的那样管理场景。这可能包含优化场景和计算每个对象是在场景中渲染的位置。它会实现高效的裁剪运算。每次我们移动一个场景结点,它会标志为已移动。当场景被渲染的时候,移动结点和其子结点的位置将会被计算出来。至于其它,我们使用上一帧的位置。这就节省了大量的计算时间而且这是场景管理器的重要任务之一。  

  为方便拣选的目地,我们需要一个快速的方法来丢弃摄像机渲染过程中不可见的部分。这意味着,我们需要一个简便的办法来遍历场景和测试结点的可见性。有不同的算法来实现这个目地,Ogre3D带有不同的场景管理器,并且用多种算法来实现多种场景类型。

 

  我们一直使用的是OctreeSceneManager。这个场景管理器名字是由来是使用一个八叉树来存储场景。那么什么又是Octree呢?

 

 

 

 

【 八叉树 】

 

  顾名思义,八叉树是一种树形结构。想每个树形结构一样,它有一个根而且每个子结点都有一个父节点。和普通树形结构不同是的每个结点最多有八个子结点,故名Octree(八叉树)。下面的图示就展示出了一个Octree(八叉树)。

资料来源:http://commons.wikimedia.org/wiki/File:Octree2.svg 

 

 

  但为什么Ogre 3D 使用一个八叉树来存储3D场景?一个八叉树有一些极其有用的属性来存储3D 场景。其中之一就是它有多达8个的子结点。比如,如果我们拥有一个有两个对象的3D 场景,我们可以用一立方体来封闭场景。

 

  如果我们把这个立方体按长,宽,高的一半来分来,我们可以得到8个新的立方体,每个封闭了1/8的场景。这八个立方体可以别认为是原立方体的八个子结点。

 

  现在两个场景的对象在立方里的右上前端了。其他七个立方体都是空的,因此现在省略他们。我们将会把含有两个对象的立方体再次分割。

  现在每个立方体封闭这一个或者零个对象,他们都是叶子结点。八叉树的这个树形使得拣选变得简单而快速。当渲染一个场景的时候,我们在场景中有一个摄像机,视野为一视椎的面积。我们以八叉树的根结点作为开始来决定渲染哪个对象。如果视椎与立方体相交叉,我们继续遍历子结点。以上情况总是发生,因为开始时摄像机拍摄整个封闭场景的时候,八叉树从0开始索引。然后接着索引八叉树到下一层,也就是根结点的子结点。我们每次对子结点进行视椎体拣选测试,如果它们相交,我们继续索引立方体的子立方体。同样,如果立方里完全在视椎体内,我们不需要进行深度的遍历,因为我们知道立方里内的所有子立方体也都在视椎体内。一直这样做直到遍历到叶子为止,然后继续遍历另一个子结点,也直到叶子为止。通过这种算法,我们每步都可以去掉大部分的渲染场景,并且在几步之后,我们或者空的叶子树或只有一个叶子的对象。然后就可以知道那些对象是可视的渲染对象。这种算法的妙处就是可以在开始几步就可以去掉大部分的场景。假如在我们的例子中,我们整个视椎体看到两个对象在深度为1的立方体中,我们就可以在第一步去掉8分之7的场景。

  这种处理方式类似于二叉树。它们之间的不同就是前者可有八个子结点,而后者只可有两个子结点。 

 

【 另一种场景管理器类型 】

  我看已简单研究第一种场景管理器了。现在让我们来看下另一种。 

 

    实践时刻 —— 使用另一种场景管理器  

  我们再次使用之前example 中的代码: 

 

   1. 删去createScene() 函数中所有的代码。 

 

   2. 添加一个名为chooseSceneManager()的新函数到程序的类中:

virtual void chooseSceneManager(void)
{

}

   3. 现在添加代码到新的函数中以加载一个包含我们想要的地图的文件。

ResourceGroupManager::getSingleton().addResourceLocation("http://www.cnblogs.com/media/packs/chiropteraDM.pk3", "Zip", ResourceGroupManager::getSingleton().getWorldResourceGroupName(), true);

  4. 在添加完地图之后,我们需要把完整地图的信息它加载进去:

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

  5. 然后我们需要使用createSceneManager() 函数:

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

 6. 现在告诉场景管理器我们想要显示之前加载的地图:

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

 7. 编译运行程序。你会看到一个取自一个著名游戏的地图并且你可以操控方向穿越地图。但是因为这个游戏使用了不同up向量,所以旋转有些不同并且浏览起来也感觉有点怪异。

 

 

   刚刚我们使用了chooseSceneManager() 函数来创建一个不同于默认的场景管理器。在这种情况下,我们创建了BspSceneManager(BSP场景管理器)。BSP代表二叉空间分割,它是一种被多年前的老式射击游戏用来存储信息的技术。BSP分割层为多个凸部分并把它们按树形结构储存起来。在老式的显卡上,这使渲染和别的图形任务的执行变得更快。而今,BSP场景已经没有多年前使用的频繁了。

 

 

【 资源管理器 】

 

  代码中的第一行使用我们从未使用的称为ResourceGroupManager(资源管理器)的管理器。这种管理器在我们程序的生命周期中一直加载着各种资源。在启动期间,资源管理器获取到资源的目录列表和我们想要加载的zip压缩包。这个目录可以从文件中读取,比如resources.cfg,或者也可以把文件写进程序代码。在这之后,我们就可以仅仅使用文件名来创建实体,而不需要文件的整个路径,因为管理器已经索引过了。只有当我们创建索引文件的实例的时候它才会真正的被加载进来。索引帮助我们避免了加载同样模型的两次的检查。但我们使用模型两次,管理器仅加载模型一次,并且当需要两个同样模型的实例的时候,管理器使用已经加载过的模型并且不会再次加载。 

 

    addResourceLocation() 函数获取一个文件夹或zip压缩包的路径,第二个参数定义它的类型,通常它可以是zip压缩包或者一个文件夹。如果需要,我们可以添加自己的资源的类型。当我们想要加载自己定义的数据包类型时,这会变得非常有用。

 

   第三个参数是我们想要加载文件到所在资源组的名称。资源组就好像C++的命令空间一样;因为我们加载的是一张游戏地图的一部分,是加载预先定义好的被WorldResourceGroup 返回的资源组名称。最后一个函数告诉Ogre 3D ,我们加载的路径是否相被递归调用。如果设置为false,只有在目录中的文件被加载,在子文件夹的文件不被加载。如果设置为trueOgre 3D 也会加载子文件的文件。默认的设置为false

 

   通过调用initialiseResourceGroup()函数,我们告诉Ogre 3D 去索引在ResourceGroup没有索引到的文件。当然,我们必须添加想要索引的资源组。在这个调用完之后,我们可以使用关联到此资源组的所有文件。

 

【 setWorldGeometry() 】

 

  setWorldGeometry()是个特殊的函数告诉BspSceneManager去加载保存在bsp文件类型中的地图。对于一个地图,我们使用保存在 .pk3 为后缀的BSP文件——这就是为什么我们需要在第一步中加载压缩包的原因。

 

 

【 创建我们自己的模型 】

 

  我们已经看到如何使用不同的场景管理器和如何使用一个场景管理器加载地图。现在我们将会研究如何只在plane类的帮助下用代码创建一个mesh。这次,我们将会自己做所有的事情。我们将会创建一个在地面上有草地的模型。】

 

  这次,我们需要使用OctreeSceneManager,所以我们不需要使用chooseSceneManager()函数了:

 

   1. 我们需要一个空的程序:

 

class Example43 : public ExampleApplication
{
private:

public:
  void createScene()
  { 

  }
};

  2. 我们需要在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);

  3. 然后实例化我们刚刚创建的平面并给它设置一种材质。我们将会使用从expamles中加载的GrassFloor 材质:

 

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

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

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

  4. 然后添加方向光到场景。否则,场景过暗就什么都看不到了:

Ogre::Light* light = mSceneMgr->createLight("Light1");

light->setType(Ogre::Light::LT_DIRECTIONAL);

light->setDirection(Ogre::Vector3(1,-1,0));

  5. 现在创建新的ManualObject 类型并在调用begin() 方法:

 

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

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

  6. 为第一个多边形的顶点添加位置坐标和纹理坐标:

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

manual->textureCoord(1,1);

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

manual->textureCoord(0,0);

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

manual->textureCoord(0,1);

7. 我们也需要第二个三角形拼成一完整的四边形:

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

manual->textureCoord(1,1);

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

manual->textureCoord(1,0);

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

manual->textureCoord(0,0);

  8. 我们已经结束了四边形的定义;让我们通知 manual 对象:

 

manual->end();

  9. 最后,创建一个新的场景结点并关联manual object到它上面:

 

Ogre::SceneNode* grassNode = mSceneMgr->getRootSceneNode()->create

ChildSceneNode("GrassNode2");

grassNode->attachObject(manual);

  10. 编译运行程序。这个平面上将会有草坪的纹理,并且会有一片草在平面上。

 

  刚刚我们用不同的颜色绘制了平面,这次是草绿色;同样,我们创建了四边形并且放置一片草在平面上。第一步和第四步应该理解起来比较简单,我们已经学习过这些。唯一的不同是我们使用了比之前的石头更为适用于应用程序的不同材质。我们将会在下一章好好的讲下关于材质的一些知识。

  在第五步,我们见到了新的类型。我们创建了一个ManualObject 对象。

 

 

【 Manual 对象 】

 

  一个 manual 对象就好像一个新的代码文件。在开始的时候他是空的,但是好多不同的事情可以使用它来创建。使用一个 manual 对象,我们可以创建3D模型。为了创建一模型,我们需要给单个顶点来描述三角形。我们已经讨论过我们在3D场景中使用的所有对象是由三角形组成。但是对于四边形,我们需要两个三角形,我们将会稍后看到。

 

  在第五步创建了一个新的空的manual object并简单的命名为grass。我们然后调用了begin() 方法,这个方法准备接收manual object的顶点信息;一个顶点用3D坐标来表示。begin()方法需要一个顶点使用的材质的名字,指定一个我们要输出的创建顶点信息。这里有六种方法来指定输出的manual object的信息。有三种不同东西可以使用manual object创建,即为——点,线和三角形。

 

 

  点被简单的存储为列。每次我们添加一个新的位置,我们创建一个新的点。这种模式被称为OT_POINT_LIST。对于线,有两种不同的方法来创建它们。一个比较易懂的方法就是使用第一和第二的点的连线作为第一条线,第三和第四个位置的连线作为第二条线,等等。这被称为OT_LINE_LIST.另一种方式使用开始两个点作为第一条线,但然后每个新定义的点作为新线的最后一个点并使用最后一个点作为这条线的另一个端点;这是OT_LINE_STRIP方式。

  他们可以用三种方式定义三角形。第一种是最简单的方式是triangle list:前三个点作为一三角形,然后三个作为第二个三角形,如此类推。这被称为OT_TRIANGLE_LIST。然后我们可以使用前点三个作为第一个三角形并且每个新点使用前两个点定义了的点组成下一个三角形。这被称为OT_TRIANGLE_STRIP方式。最后一种方式是使用前三个点作为第一个三角形,然后第一个点,上次最后使用点和新创建的点作为下一个三角形。

 

  我们可以看到,依赖于输入模式,它需要更多的点来描述3D图形。用triangle list 模式,对于每个三角形我们需要三个点。用stripfan,前三个点为一新三角形,后来每个新的点构成一新三角形。

 

  在begin()函数调用期间,我们定义使用triangle list来描述四边形。我们想要四边形宽为10个单位并高为10个单位.以下四边形有四个点,并且每个点的旁边有坐标标识。

 

  第一个三角形需要点 12和 3。第二个三角形需要点124。在第六步中用position()函数定义第一个三角形的各顶点,在第七步调用position()函数来定义了第二个三角形。你可能注意到在每个position() 后面调用了textureCoord() 函数。

 

 

【 纹理映射 】

 

  对于Ogre 3D 可以输出草叶的图像到我们的四边形上,每个顶点除了需要它的位置坐标还需要纹理坐标。纹理坐标由二元组来 (u,v)组成。(u,v) 描述它们在图片中的位置,u是用于x轴,v是用于y轴。(0,0)表示纹理图片的左上角,(1,1)表示纹理图片的右下角。

 

  如果我们使用大于1的值,有很多种情况会发生,这取决于材质的设置。如果我们使用wrap模式,那么纹理将会重复。如果使用clamp 模式,每个大于1的值将会减到1.0。如果小于0也是用同样的方式—— 坐标会被设为0。在mirror模式中,1变为了02变作了1,在这种情况下反射了纹理,如果它们的值大于2,原始的图片将会再次使用,翻转之后,又是原图,以此类推。最后的模式定义了边框颜色,在[0,1]之外的所有东西将会被渲染称为边框的颜色。

 

  把纹理坐标应用于原坐标系,我们将会看到下图关于四边形的信息:

 

  让我们看一下第六步和第七步。把代码和上面的图片对比。坐标和纹理坐标的位置将会匹配在一起。 

  第八步,我们完成了manual 对象。在第九步,我们创建了一个场景节点来关联我们新创建的对象,这样它就可以被渲染了。

 

 

【 让英雄动起来 —— 使用 manual object

 

  使用manual object尝试不同的描述对象的方式。同样,尝试线和点的方式。为使这个过程更加简单,使用BaseWhiteNoLighting来代替草地的材质。通过这种材质,你不需要纹理坐标,你只需要使用position()函数并测试。你创建的所有东西将会被渲染为白色。

 

 

【 给草叶添加体积 】

 

  我们已经成功渲染了一些草,但是当我们移动摄像机。我们就可以看得清清楚楚那些叶子只是2D图形,根本没有什么体积。如果不用我们自己的3D模型渲染每片叶子,这个问题不可能简单解决。如果我们做到了,会提升视觉效果,但是这不太容易,会因为大的草地增加渲染的复杂程度,而降低交互的程度。但是有若干技术使这个问题不是太棘手。我们现在可以看一种解决方式。

  现在我们将会使用之前的代码并将添加两个新的死表型到我们的草叶上。 

 

  1. 在添加过的前两个三角形之后,添加第三个和第四个三角形来创建第二个四边形:

//third triangle
    manual->position(2.5, 0.0, 4.3);
    manual->textureCoord(1,1);
    manual->position(-2.5, 10.0, -4.3);
    manual->textureCoord(0,0);
    manual->position(-2.0, 0.0, -4.3);
    manual->textureCoord(0,1);

    //fourth triangle
    manual->position(2.5, 0.0, 4.3);
    manual->textureCoord(1,1);
    manual->position(2.5, 10.0, 4.3);
    manual->textureCoord(1,0);
    manual->position(-2.5, 10.0, -4.3);
    manual->textureCoord(0,0);

  2. 添加第五和第六个三角形创建第三个四边形。

 

//fifth triangle
manual->position(2.5, 0.0, -4.3);
manual->textureCoord(1,1);
manual->position(-2.5, 10.0, 4.3);
manual->textureCoord(0,0);
manual->position(-2.0, 0.0, 4.3);
manual->textureCoord(0,1);
//sixth triangle
manual->position(2.5, 0.0, -4.3);
manual->textureCoord(1,1);
manual->position(2.5, 10.0, -4.3);
manual->textureCoord(1,0);
manual->position(-2.5, 10.0, 4.3);
manual->textureCoord(0,0);

 

  3. 编译运行程序,然后在草的周围操控摄像机。在之前的例子中,我们从侧面看只能看到草叶只有一条线的宽度,但现在这种情况不再发生了。

 

  刚刚我们解决了草叶看起来只像一幅图片投射到四边形上的问题。为了解决这个问题,我们简单的创建了两个新的四边形,位置上有旋转的关系,并把它们相互卡在一起。就好像下面的图示一样:

 

billboarding.

 

  每个四边形有同样的长,就好像之前的图片一样,我们可以认为它是一个圆分为6份。两个四边形之间有60°的夹角。三个四边形在中心两两相交,这样我们就有660度的夹角,最终合成360度。这个图示也回答了之前代码引发的有趣的问题。我们如何计算另外两个四边形各点新坐标?这就是简单的三角形计算。为计算出y的值,我们使用了正弦值,对于x使用了余弦值。我们使用这种方式创建了一个平面并给它渲染一个纹理,使模型更具真实感。这种名为billboarding的技术,在电子游戏中这种技术被广泛使用。

 

 

【 创建一块草坪】

 

  现在我们有一片草,让我们来创建一块完整的草坪吧。

 

   1. 我们需要多个草叶的实例,所以转换 manual object 为 mesh

 

manual->convertToMesh("BladesOfGrass");

  2. 我们想要一块草坪包含50 * 50 的草叶实例。所以我们需要两个循环。 

 

for(int i=0;i<50;i++) 
{
  for(int j=0;j<50;j++) 
  {

  3. 在循环内部,创建一个无名实体和一个无名场景结点:

 

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

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

node->attachObject(ent);

  4. 不要忘记关闭循环:

 

   }

}

 

  5. 编译运行程序,你将会看到一块草坪。看你的电脑的配置,这块草坪可能非常慢才会渲染完成。 

 

 

  刚刚在第一步,我们使用了一个新的 manual object 的成员函数。这个函数把manual object 转变一个mesh,这样我们就可以使用场景管理器的createEntity()函数来创建mesh的实例。为能使用新的实体,我们需要准备一个稍后在createEntity()函数中使用的参数名。这次,我们使用BladesOfGrass作为描述mesh的名字。我们想要若干草的实例,所以我们在第二步创建了两个循环,每个50次。在第三步中添加了循环体。在循环体中,我们首先使用刚创建的参数名新建了一个实体。有心的读者可能注意到了我们没有用两个参数传入createEntity()函数,即一个实体类型和一个想要创建的参数名。这次,我们只给了实体的类型作为实参,而非名字。但是否是因为每个实体都需要一个独一无二的名字,所以每次必须给它一个名字呢?这个想法是正确的,我们调用的函数仅是一个辅助函数,它只需要一个实体类型的名字,因为它会自己生成一个唯一的名字,然后调用我们经常用的函数。这为我们节省了为循环附加如BladesOfGrassEntity一般的变量名字的而带来的麻烦。我们使用同类型的函数来创建场景结点。

 

【 探索名称生成方案 】

 

  现在,让我们快速浏览一下Ogre 3D 为我们生成的名字。 

 

  1. 在循环体的结尾,添加下面的输出语句:

 

std::cout << node->getName() << "::" << ent->getName() << 

std::endl;

  2. 编译运行程序;将会有打印出来一长列的名字。更准确的说,是2500行,因为循环重复了50*50次。下面是最后几行的名字:

 

Unnamed_2488::Ogre/MO2487

Unnamed_2489::Ogre/MO2488

Unnamed_2490::Ogre/MO2489

Unnamed_2491::Ogre/MO2490

Unnamed_2492::Ogre/MO2491

Unnamed_2493::Ogre/MO2492

Unnamed_2494::Ogre/MO2493

Unnamed_2495::Ogre/MO2494

Unnamed_2496::Ogre/MO2495

Unnamed_2497::Ogre/MO2496

Unnamed_2498::Ogre/MO2497

Unnamed_2499::Ogre/MO2498

Unnamed_2500::Ogre/MO2499

  我们刚刚打印出场景结点的名字,并且当我们不赋一个名称作为参数,创建的实体成功的识别了Ogre 3D自动生成的名称。我们看到使用的场景结点使用了这种格式:Unnamed_Nr,Nr是一个计数器,每次我们创建一个无命名的场景时,它便会增加。实体使用了一个相似的方案,但使用的是MO格式;MOmovable object的简写。 moveable object Ogre 3D中,可作为多种不同类的基类。可借助场景结点移动的所有东西都是继承自一个movable object。如实体和光源都是继承自 Movable object 类,但还有更多的继承自movable object。下面是一幅来自于Ogre 3D 文档的图片显示了从MovableObject继承的所有类。

资料来源:http://www.ogre3d.org/docs/api/html/classOgre_1_1MovableObject.html 

 

  我们看到甚至摄像机是也是一个movable object;这是很必要,否则我们不能把它关联到场景结点上。只有MovableObject的子类可以可以被关联到场景结点上。名字不同的实体被关联到结点上,并且这步是用下面的代码来实现添加实体到无名结点上的。

 

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

 

【 静态几何 】

 

  我们创建了一块草坪,但是程序可能因你电脑的原因,运行的相当慢。你可以使用Ogre 3D中一个称为StaticGeometry的类来使程序运行的更快。

 

  我们将会修改上个例子中的代码,使程序渲染的更快: 

 

   1. 删除打印语句;我们下面不再需要它了。

 

  2. 现在回到manual object的话题,删除所有添加相同点的position()函数的调用。对于每个四边形,应有四或六个点。下面是删除多余实体后的代码:

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

manual->textureCoord(1,1);

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

manual->textureCoord(0,0);

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

manual->textureCoord(0,1);

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

manual->textureCoord(1,0);

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

manual->textureCoord(1,1);

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

manual->textureCoord(0,0);

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

manual->textureCoord(0,1);

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

manual->textureCoord(1,0);

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

manual->textureCoord(1,1);

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

manual->textureCoord(0,0);

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

manual->textureCoord(0,1);

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

manual->textureCoord(1,0);

  3. 现在我们使用称为索取的方法来描述想要创建的三角形。第一个三角形使用了头三个点,第二个三角形使用了第一,第二和第四个点。记住,和计算机中别的概念是一样,点是从 开始计数的:

manual->index(0);

manual->index(1);

manual->index(2);

manual->index(0);

manual->index(3);

manual->index(1);

   4. 用同样的方式添加另外两个四边形:

 

manual->index(4);

manual->index(5);

manual->index(6);

manual->index(4);

manual->index(7);

manual->index(5);

manual->index(8);

manual->index(9);

manual->index(10);

manual->index(8);

manual->index(11);

manual->index(9);

  5. 现在让SceneManager 创建一个新的静态实例:

 

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

  6. 现在在for循环中,创建草的实体。然而,这次添加它到静态几何的实例而不是场景结点:

 

for(int i=0;i<50;i++) 
{
  for(int j=0;j<50;j++) 
  {
   Ogre::Entity * ent = mSceneMgr->createEntity("BladesOfGrass");

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

  7. 调用build函数,来结束静态几何:

 

field->build();

8. 编译运行程序,你将会看到同样的一片草地,但是这次,程序将会运行的快很多。

 

   刚刚我们创建了我们之前一样的场景,但是这次运行的更快了。为什么呢?唯一的原因是静态几何运行的更快。但静态几何和我们平时用的方法有什么不同?

 

 

 

【 渲染线管 】

 

  每次我们渲染一个场景,Ogre 3D和显卡都需要完成一些步骤。我们已经讲过一些,但是不是全部。我们讲过的一步是拣选;现在我们讨论一些我们没有遇到过的步骤。  

  我们知道对象可以在不同的空间中,如局部空间或世界空间。我们也知道对渲染一个对象,我们需要把它们从局部空间变为世界空间。从局部空间到世界空间的变换是简单数学运算的组和,Ogre 3D 需要每帧计算每个草实体的世界坐标。每帧需要很多的操作,但是更糟的是每个草实体被独立地送给GPU渲染。这花费了很多的时间,而且这就是为什么程序运行的很慢。

 

  我们可以使用静态几何来解决这个问题;在第五步,我们使用场景管理器创建了一个静态几何的实例。然而,在循环的内部,我们添加创建的实体,而不是像使用过的关联到场景结点上的那种方式。这里,我们直接添加它到静态几何,并给出想要实体的位置作为第二参数。

 

  在我们添加完实体到静态几何实例之后,我们需要调用build()函数。这个函数代入我们添加的所有实体,并计算出世界坐标,甚至做更多的事情。我们只需要使用索引列表添加模型,因为静态几何尝试使用同样的材质或索引和模型本身联系起来以更大的优化程序。我们付出的代价就是不能移动添加到静态几何的实体。在草地的那种情况下,这并不是什么损失;草是保持不动的。通常,静态几何被用于场景中不动的东西,因为它提供了一个几乎没有缺点的加速效果。但一个缺点就是当我们在场景中有大量的静态实例,当一部分静态几何在视锥范围之内,拣选就变得不太有效了,因为每个对象都必须渲染。

 

 

【 索引 】

  我们发现只可添加已给静态几何实例加过索引的实体。但首先我们没有讨论过什么是索取。为理解这个概念,让我们回到四边形的话题。

  四边形有四个点,是由两个三角形组成并定义的。当我们研究下过去使用创建四边形的代码,注意到我们直接使用添加6个点的方式来代替四个点两点被重复添加的方式。 

//First triangle

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

manual->textureCoord(1,1);

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

manual->textureCoord(0,0);

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

manual->textureCoord(0,1);

//Second triangle

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

manual->textureCoord(1,1);

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

manual->textureCoord(1,0);

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

manual->textureCoord(0,0);

  在图中点和点2,被添加了两次,因为它们被两个三角形所使用。一种防止信息重复的方式就是使用two-step system来描述三角形。首先,我们创建了一个想要使用的点列。第二,我们创建在点外定义了我们想要生成的三角形的列。

  这里,我们定义了四个点,然后告诉三角形使用点 123,并且第二个三角形使用点22,和4。这节省了我们添加一些点两次,或更多的时候,大于两次。这看起来只有小小的不同,但是当我们有几千个模型的时候,这就会产生重大的影响了。静态几何要求我们只使用添加过索引的实体,因为用这种方法,静态几何可以创建一个有所有点(也就是顶点)的列表和有所有索引的列表。如果我们使用同样的点添加实体,静态几何只需要添加一些索引而不是新的点。对于复杂的处理来说,这将会是一个很大的空间节省。 

 

 

【 概要总结 】

 

  我们已经改变了目前的场景管理器,创建我们自己的草坪,用静态几何加速程序。

   具体的说,我们学习了:

  1. 什么是ManualObject

  2. 为什么我们为3D模型使用索引。

  3.  如何和何时使用静态几何。

 

  在这章我们已经使用过材质。在下一章,我们将会创建自己的材质。


posted @ 2015-03-24 16:53  SunkingYang  阅读(471)  评论(0编辑  收藏  举报