译者:whistleofmysong@gmail.com或者博客 www.singmelody.com ,其实我还有他QQ
【 第二章 Ogre之场景绘图 】
【 这章将会介绍给我们场景绘图的一些概念和如何使用函数创造一个复杂的场景。 】
在这章,我们将会:
l 学习在3D空间中三个基本的操作。
l 一个场景绘图是如何被组织的。
l 我们可以操作的不同的3D空间。
那么,就让我们开始吧。
【 创建一个场景结点。 】
在上一章中(第一章 创建Ogre 3D),我们加载了一个3D模型并且把它绑定到我们的场景上。现在我们将会学习如何创建一个新的场景结点并如何把3D模型绑定到结点上。
【 实践时刻 — 用Ogre3D创建一个场景结点 】
我们将会使用第一章的代码,修改第一章的代码以创建一个新的场景结点,并且把他绑定到一个场景结点上。我们将会做以下步骤:
1在我们老版本的代码中,我们在createScene() 函数中存在以下两行。
Ogre::Entity* ent = mSceneMgr->createEntity("MyEntity","Sinbad.mesh"); mSceneMgr->getRootSceneNode()->attachObject(ent);
2. 用以下代码替换函数中的最后一行。
Ogre::SceneNode* node = mSceneMgr->createSceneNode("Node1");
3. 然后添加以下两行;这两行代码的添加顺序对场景的效果没有影响。
mSceneMgr->getRootSceneNode()->addChild(node);
node->attachObject(ent);
4. 编译并且运行应用程序。
5. 当你运行应用程序,你应会看到和你第一章一样的场景。
【 刚刚发生了什么?】
我们创建了一个新的命名为Node1的场景结点。然后我们添加这个场景结点到根结点。在这之后,我们绑定之前的3D模型到我们新创建的结点上面,这样就可以看到效果了。
【 如何使用场景根结点 】
调用mSceneMgr->getRootSceneNode() 函数将会返回场景的根结点。场景结点是场景管理器的一个成员变量。当我们想要什么显示的时候,我们需要以一种方式把它绑定到场景根结点上或者一个派生类或子类的结点上。简而言之,子结点需要和根结点保持结点与结点之间的联系,否则的话,子结点上的模型就得不到渲染。就像变量名所暗示的那样,场景根结点是场景的根。因此整个场景将会以某种方式绑定到场景的根结点上。Ogre 3D使用了所谓的场景绘图的方式来组织场景。
这个绘图的方式就好像一棵树一样。它只有一个根(也就是场景根结点),并且每个结点都可以有子结点。我们在mSceneMgr->getRootSceneNode()->addChild(node)中使用了这一特性。这里把创建好的场景结点当做场景根结点的子结点。在这之后,我们使用node->attachObject(ent) 使另一个子节点添加到场景结点。这里,我们添加一个实体到场景结点。我们现在有两种不同的可以添加到场景结点的对象。首先,可以被添加为子结点或者可为自己添加子结点。然后,我们我们创建我们想要渲染的实体。实体不是子结点,它们也不可能有子结点。这些数据对象可以当作是被关联上的结点也可以被认为是树上的叶子。我们稍晚将会学习它们到底是什么和如何使用它们。现在,我们只需要实体。我们当前的场景绘图方式就好像下图一样。
首先我们需要理解的是什么是场景结点和他们是干什么的。一个场景绘图就是用来代替在3D空间中不同部分的场景是如何相互关联在一起的。
3D space
Ogre 3D是一个3D渲染引擎,所以我们需要一些基本的3D概念。在3D上最基础的结构就是向量,这个向量中有次序的包含着(x,y,z)。
在3D空间的每个位置都可以被欧几里德三维坐标系的一个三维坐标所表示。我们要强调一下在3D表示方面有不同的坐标体系。而这些体系之间的不同就是主轴的方向和旋转的正方向。现在主流的有两种体系,左手坐标系和右手坐标系。在下面的图中,我们看到两种体系— 在左边我们看到的是左手坐标系;在右边我们看到的是右手坐标系。
Source: http://en.wikipedia.org/wiki/File:Cartesian_coordinate_system_handedness.svg
左手和右手坐标系名字的由来是基于他们主轴方向的不同,方向是左手和右手来决定创建的。大拇指是X轴,食指是Y轴,而中指是Z轴。我们需要张开手使大拇指,食指和中指之间保持90度角。当使用右手时,我们就可以得到一个右手坐标系。当使用左手时,我们就可以得到一个左手坐标系。
Ogre 使用的是右手坐标系,但是旋转坐标系使X轴的正半轴指向右并且X轴的负半轴指向左。Y轴(正半轴)指向上,Z轴(正半轴)垂直于屏幕向外,这被称为y-up convention。这开始会使我们很不适应。但是我们不久将会在这样的坐标系下学习研究Ogre。这个网址http://viz.aset.psu.edu/gho/sem_notes/3d_fundamentals/html/3d_coordinates.html 有能更好展示不同坐标系之间的不同和联系的图片解释。
【场景绘图】(Scene graph)
场景绘图是在图形化编程领域最被广泛使用的概念之一。简单的说,这是存储场景信息的一种方式。我们已经讨论过场景绘图必须有一个根结点而且是树形结构的。但是我们还没有涉及场景绘图中最重要的函数。每个场景结点既有子结点也有可以在3D空间变换的函数。这些变换可以说由三方面组成,就是—— 位置变换(position),旋转变换(rotation)和缩放(scale)变换。坐标点的(x,y,z),明确的描述了结点在场景中的位置。旋转是使用四元数来储存的,四元数是3D空间存贮旋转的一个数学概念,但我们可以认为旋转就是每个坐标轴一个的浮点数,描述了结点以弧度为单位的旋转程度。缩放就比较简单了,同样的,就是它使用了一个(x,y,z)的数组,并且数组每一部分是对应着坐标轴的缩放比例。
关于场景绘图很重要的一件事是相对于父结点的变换。如果我们修改了父结点的方向,子结点也会受其影响发生改变。当我们把父结点沿X轴移动十个单位,所有的子结点也将会沿X轴移动十个单位。最后子结点的方向会根据所有父结点的方向而计算出来。这个概念将会在下面的图标中描述的更加清楚。
MyEntity的在场景中的位置将会是(10,0,0)并且MyEntity2将会在位置(10,10,20)。然后让我们在Ogre 3D中尝试一下这个实验。
【 简单测试——找到场景结点的位置】
1.观察下面的树形结构并且判定MyEntity和MyEntity2的最终位置
a. MyEntity(60,60,60) and MyEntity2(0,0,0)
b. MyEntity(70,50,60) and MyEntity2(10,-10,0)
c. MyEntity(60,60,60) and MyEntity2(10,10,10)
【 设置场景结点的位置 】
现在,对比上幅图片我们将会尝试创建如图表中描述的场景。
【 实践时刻 —— 设置场景结点的位置 】
在创建场景结点后添加以下一行代码:
node->setPosition(10,0,0);
在createScene()函数结尾中添加下面一行代码以创建第二个实体。
Ogre::Entity* ent2 = mSceneMgr->createEntity("MyEntity2","Sinbad.mesh");
然后创建第二个场景结点。
Ogre::SceneNode* node2 = mSceneMgr->createSceneNode("Node2");
把第二个结点添加到第二个结点上
node->addChild(node2);
设置第二个结点的位置
node2->setPosition(0,10,20);
把第二个实体关联到第二个结点上面:
node2->attachObject(ent2);
编译这个程序然后你就会看到两个Sinbad 实例了.
【 刚刚发生了什么?】
我们创建了一个和之前图解中相匹配的场景。我们在第一步使用的首个新函数。能够很容易的猜到,setPosition(x,y,z)函数是根据数组来设置结点位置的。记住这个位置是相对于父结点的位置。我们想要MyEntity2 在位置(10,10,20),因为我们添加了关联MyEntity2的node2结点,并且node2的父结点已经在位置(10,0,0)了。我们只需要设置node2的位置到(0,10,20)。当两个位置结合到一起,MyEntity2就在(10,10,20)了。
【 简单测试 —— 使用场景结点 】
我们现在有场景结点node1在(0,20,0)并且我们有个场景子结点在node2并且已经有一个提示关联上去了。如果我们想要实体在(10,10,10)位置被渲染,那么我们应该把node2设置在什么位置?
a. (10,10,10)
b. (10,-10,10)
c. (-10,10,-10)
【 让英雄动起来 —— 添加一个Sinbad】
添加Sinbad的第三个实例并且让他在位置(10,10,30)处渲染
【 旋转一个场景结点】
我们已经知道如何设置一个场景结点的位置。现在,我们将会学习如何去旋转一个场景结点并且如何以另一种方式去修改一个场景结点。
【 实践时刻 —— 旋转一个场景结点】
我们将会使用之前的代码,但是会为createScene()函数写全新的代码。
1. 移走createScene()函数中的所有代码。
2. 首先创建一个Sinbad.mesh 的实例并且然后创建一个新的场景结点。设置场景结点的位置到(10,10,0),在最后关联实体到结点,并且添加一个结点为场景根结点的子结点。
Ogre::Entity* ent = mSceneMgr->createEntity("MyEntity","Sinbad. mesh"); Ogre::SceneNode* node = mSceneMgr->createSceneNode("Node1"); node->setPosition(10,10,0); mSceneMgr->getRootSceneNode()->addChild(node); node->attachObject(ent);
3. 同样的,创建一个新的实例模型,然后是一个新的场景结点,并且设置点到(10,0,0)
Ogre::Entity* ent2 = mSceneMgr->createEntity("MyEntity2","Sinbad. mesh"); Ogre::SceneNode* node2 = mSceneMgr->createSceneNode("Node2"); node->addChild(node2); node2->setPosition(10,0,0);
4. 现在添加以下两行到旋转模型并且关联实体到场景结点
node2->pitch(Ogre::Radian(Ogre::Math::HALF_PI));
node2->attachObject(ent2);
5. 同样的,但是这次使用yaw函数代替pitch函数并且使用translate函数代替setPosition函数
Ogre::Entity* ent3 = mSceneMgr->createEntity("MyEntity3","Sinbad. mesh"); Ogre::SceneNode* node3 = mSceneMgr->createSceneNode("Node3",); node->addChild(node3); node3->translate(20,0,0); node3->yaw(Ogre::Degree(90.0f)); node3->attachObject(ent3);
6.同样再次代替yaw()和pitch()函数使用roll()函数旋转
Ogre::Entity* ent4 = mSceneMgr->createEntity("MyEntity4","Sinbad. mesh"); Ogre::SceneNode* node4 = mSceneMgr->createSceneNode("Node4"); node->addChild(node4); node4->setPosition(30,0,0); node4->roll(Ogre::Radian(Ogre::Math::HALF_PI)); node4->attachObject(ent4);
【 编译并且运行程序,并且你将会看到下面的截图 】
【 刚刚发生了什么? 】
我们几乎重复了4遍我们的代码并且总是改变一些小细节。第一次的代码没有什么特别的。它仅是我们之前写过的一样的代码,但是这个实例会作为我们的参照物,用来对比其在他三个实例改变之后发生了什么。在第四步中,我们添加了下面一行代码:函数pitch (Ogre::Radian(Ogre::Math::HALF_PI)) 用来绕X轴旋转结点。就如我们之前所说的一样,这个函数接受一个弧度单位作为参数并且我们使用 π/2 ,也就是旋转90度。
在第五步中,我们代替setPosition(x,y,z) 函数调用了 translate(x,y,z) 函数。setPosition(x,y,z) 和translate(x,y,z)函数之间的不同setPosition()函数仅是设置点,没什么可说。translate() 以给定的值设置点的位置,但是它是相对于现在的位置变换。如果一个场景结点在位置(10,20,30)并我们调用setPosition(30,20,10),那个结点在就在世界空间的位置(30,20,10)。另一方面,如果我们调用translate(30,20,10),结点就会在位置(40,40,40)。这点区别虽小,但是相当重要。如果我们在正确的环境中使用这两个函数他们都是有效的,比如当我们想要设置结点在场景中的位置,我们将会使用setPosition(x,y,z)函数。然而,当我们想要移动一个已经在场景中设置好的结点,我们将会使用translate(x,y,z).
同样的,我们用yaw(Ogre::Degree(90.0f)) 代替 pitch(Ogre::Radian(Ogre::Math::HALF_PI))函数。yaw() 函数绕Y轴旋转场景结点。我们使用Ogre::Degree() 来代替Ogre::Radian(),当然,pitch和yaw仍然需要使用一个弧度参数。然而,Ogre 3D提供了一个可以使编译器自动转换角度到弧度的操作的Degree()类。因此,程序员可以随心所欲使用一个弧度或角度单位来旋转场景结点了。不同的类命令确保类的使用是清楚的,这样防止使用混淆或者可能出错的资源。
第六步介绍了三个不同结点旋转函数,其中最后一个函数—roll()函数。这个函数绕Z轴旋转场景结点。同样的,我们可以使用roll(Ogre::Degree(90.0f))来代替 roll(Ogre::Radian(Ogre::Math::HALF_PI))。当程序运行显示了可以一没有旋转的模型和三个已经旋转过了的模型。最左边的模型没有转动,左边模型的右边那个是绕X轴转动过的,中间的模型是绕Y轴旋转过的,最右边的模型是绕Z轴旋转过的。三个实例都显示了不同旋转函数的效果。简而言之,pitch()函数绕X轴旋转,yaw()函数绕Y轴旋转,roll()函数绕Z轴旋转。我们可以使用Ogre::Degree(degree) 或者Ogre::Radian(radian)其中一个来明确指明我们想要旋转的程度。
【 简单测试—— 旋转一个场景结点 】
1. 哪个是旋转结点的三个函数?
a. pitch, yawn, roll
b. pitch, yaw, roll
c. pitching, yaw, roll
【 让英雄动起来 —— 使用Ogre::Degree 】
修改我们之前输入的代码段,替换现有的Ogre::Radian为Ogre::Degree或反之亦然,旋转将会保持不变
【 缩放一个场景结点 】
我们已经涉及了三个中的两个可以改变绘图场景的场景。现在,是描述最后缩放操作的时刻了。
【 实践时刻 —— 缩放一个场景结点 】
再一次,以我们之前用过的程序段作为我们的开始
1. 移走createScene() 函数中所有代码并插入以下代码段
Ogre::Entity* ent = mSceneMgr->createEntity("MyEntity","Sinbad.mesh"); Ogre::SceneNode* node = mSceneMgr->createSceneNode("Node1"); node->setPosition(10,10,0); mSceneMgr->getRootSceneNode()->addChild(node); node->attachObject(ent);
2. 同样的,创建一个新实体:
Ogre::Entity* ent2 = mSceneMgr->createEntity("MyEntity2","Sinbad.
3. 现在我们使用一个函数创建一个场景结点并且把它添加为一个子结点。然后同样我们照原来所做。
Ogre::SceneNode* node2 = node->createChildSceneNode("node2"); node2->setPosition(10,0,0); node2->attachObject(ent2);
4. 现在,在setPosition() 函数之后,调用下面的一行来缩放模型:
node2->scale(2.0f,2.0f,2.0f);
5. 创建一个新的实体:
Ogre::Entity* ent3 = mSceneMgr->createEntity("MyEntity3","Sinbad.mesh");
6. 现在我们调用第三步中同样的函数,但是添加一个新增的参数
Ogre::SceneNode* node3 = node->createChildSceneNode("node3",Ogre:: Vector3(20,0,0));
7. 在调用完函数之后,插入这一行以缩放模型:
node3->scale(0.2f,0.2f,0.2f);
8. 编译程序并运行,然后就就看到下面的图片了:
【 刚刚发生了什么?】
我们创建一个有缩放模型的场景。在第三步之前没有什么特别的发生。然后我们使用了一个新函数,即是—— node->createChildSceneNode("node2")。这个函数是场景结点的一个成员函数并且用给定的名字创建新的场景结点,并且当调用函数时,直接添加新结点到指定的父结点。因此,node2被添加为node的子结点。
在第四步中,我们使用了场景结点的scale() 函数。函数使用数组(x,y,z)来表示场景结点是如何缩放的。想,x,y,z轴都是参数因子,如(0.5,1.0,2.0)表示场景结点应该在X轴上缩小一半,在Y轴保持不变,在Z轴放大一倍。当然,从严格意义上说,场景结点是不能缩放的,它只保存着不被渲染元数据。更严格的说每个渲染的对象将会代替原有结点造成缩放。所以说,结点只是一个关联子结点和渲染对象的容器或者说是参考框架。
在第六步中,我们又使用了createChildSceneNode()函数,但是这次有更多的参数。在这个函数中的第二个参数接收一个我们常用的数组(x,y,z)。Ogre3D也有自己调用(x,y,z)的类Ogre::Vector3。除了储存数组,这个类提供了实现基础操作的函数。使它们可以使用线代中的三维向量。这个向量描述了当场景结点被创建起来时,结点的变换。createChildSceneNode()函数使用代替了以下代码:
Ogre::SceneNode* node2 = mSceneMgr->createSceneNode("Node2"); node->addChild(node2);
或者甚至是
Ogre::SceneNode* node2 = mSceneMgr->createSceneNode("Node2"); node->addChild(node2); node2->setPosition(20,0,0);
在最后的一段代码可以被替换成
Ogre::SceneNode* node2 = node->createChildSceneNode("Node2",Ogre::Vector3(20,0,0));
如果我们省略Vector3参数,我们替换第一段代码。这个函数还有很多版本,我们将会稍后展示。如果您有点迫不及待了,请浏览Ogre3D的网上文档http://www.ogre3d.org/docs/api/html/index.html.。除了scale() 函数,也有一个setScale() 函数。这两个函数之间的不同就好像setPosition()和translate()函数一样。
【 简单测试 —— 创建一个场景子结点 】
1. 说出调用createChildSceneNode() 函数的两种不同方式
2 如果这行不用createChildSceneNode() 用何代码来代替?
Ogre::SceneNode* node2 = node->createChildSceneNode("node1",Ogre:: Vector3(10,20,30));
这行代码可以用三行代码来代替。第一行是创建场景结点,第二行是变换结点,第三行是把它绑定到node结点上。
Ogre::SceneNode* node2 = mSceneMgr->createSceneNode("Node2"); node2->translate(Ogre::Vector3(10,20,30)); node->addChild(node2);
【 让英雄动起来 —— 使用createChildSceneNode() 函数 】
使用createChildSceneNode() 函数来重构这章你写的所有代码。
【 用聪明的方式使用场景绘图 】
在这部分,我们将会学习如何使用场景绘图的一些特性使得场景绘图更加简单。这也将会扩展我们关于场景绘图的认识。
【 实践时刻 —— 使用场景结点创建树。 】
这次,我们将会使用除Sinbad的另一个模型。
1. 移去createScene() 函数中的所有代码。
2. 用我们之前的方法创建一个Sinbad。
Ogre::Entity* ent = mSceneMgr->createEntity("MyEntity","Sinbad. mesh"); Ogre::SceneNode* node = mSceneMgr->createSceneNode("Node1"); node->setPosition(10,10,0); mSceneMgr->getRootSceneNode()->addChild(node); node->attachObject(ent);
3.现在创建一个可以到处跟着Sinbad移动的ninja。(译者注:Sinbad 是天方夜谭中的水手辛巴达,ninja是日本忍者。)
Ogre::Entity* ent2 = mSceneMgr->createEntity("MyEntitysNinja","ninja.mesh"); Ogre::SceneNode* node2 = node->createChildSceneNode("node2"); node2->setPosition(10,0,0); node2->setScale(0.02f,0.02f,0.02f); node2->attachObject(ent2);
4. 编译并且运行应用程序。当你靠近看Sinbad的时候,你将会看到一个绿色的ninja的在他的左手边。
现在改变位置到(40,10,0)。
node->setPosition(40,10,0);
把模型绕X轴旋转180度。】
node->yaw(Ogre::Degree(180.0f));
编译并运行应用程序。
你将会看到ninja任然在Sinbad的左手边并且Sinbad被旋转了
【 刚刚发生了什么?】
我们创建了一个鬼鬼祟祟跟随Sinbad的ninja。我们可以实现是因为我们把ninja模型关联为Sinbad场景结点的一个子结点。当Sinbad移动的时候,我们使用了他的场景结点,所以每步他的变换也会给ninja,因为他的场景结点是我们设置的Sinbad的子结点,并且如我们之前所说,一个父结点的变换将会传递给它所有的子结点。关于场景这一点对创建附加模型和复杂场景极其有用。如是说,如果我们想要创建一个装有房子的卡车,我们会使用多种不同的模型和场景结点。最终,我们将会有一个房子的场景结点和房子内部的东西作为它的子结点。现在我们想要移动房子,我们只需要简单关联房子结点到卡车结点或者别的什么,并且如果卡车移动了,整个房子也将会一起移动。
箭头符号显示出场景绘图方向的变换是沿箭头方向一直传下去的。
【 简单测试 —— 关于场景绘图 】
在一个场景绘图中变换是如何传递的?
a 从叶子结点到根结点。
b 从根结点到叶子结点。
【 让英雄动起来 —— 添加一个跟随着的ninja 】
添加第二个跟随着第一个日本忍者的忍者到场景之中。
【 在场景中的不同空间。】
在这部分,我们将会学习场景中的不同空间并且如何使用这些空间。
【 实践时刻 —— 变换世界空间 】
我们将会以一种与往常不同的方式移动对象。
1. 同样,我们以一个空的createScene()函数开始;所以在使用前清空函数中的所有代码。
2.创建一个引用模型。
Ogre::Entity* ent = mSceneMgr->createEntity("MyEntity","Sinbad. mesh"); Ogre::SceneNode* node = mSceneMgr->createSceneNode("Node1"); node->setPosition(0,0,400); node->yaw(Ogre::Degree(180.0f)); mSceneMgr->getRootSceneNode()->addChild(node); node->attachObject(ent);
3.创建两个新的模型实例并且变换每个实例用相对位移(0,0,10)。
Ogre::Entity* ent2 = mSceneMgr->createEntity("MyEntity2","Sinbad. mesh"); Ogre::SceneNode* node2 = node->createChildSceneNode("node2"); node2->setPosition(10,0,0); node2->translate(0,0,10); node2->attachObject(ent2); Ogre::Entity* ent3 = mSceneMgr->createEntity("MyEntity3","Sinbad. mesh"); Ogre::SceneNode* node3 = node->createChildSceneNode("node3"); node3->setPosition(20,0,0); node3->translate(0,0,10); node3->attachObject(ent3);
4.编译并且运行应用程序,操纵你的摄像机直到你看到前面的模型得到下面的效果。
5.替换node3->translate(0,0,10)这行代码为
node3->translate(0,0,10,Ogre::Node::TS_WORLD);
同样的,编译并且运行应用程序并且像以前一样操控摄像机。
、
【 刚刚发生了什么?】
我们使用了translate() 函数的一个新的参数。这造成了场景中左边的模型相对与中间的模型移动了不同的方向。
【 在3D场景中不同的空间。 】
模型移动方向的不同的因为使用了Ogre::Node::TS_WORLD ,我们告诉translate() 函数变换是发生在世界空间的而非一般的父空间。我们在3D场景中有三种空间——世界空间,父空间和局部空间。局部空间是模型本身定义的。如一个立方体有8个点并且可以用以下的图来说明:
黑色的点是立方体的原点。立方体的每个点都是相对于原点来表示的。当场景被渲染的时候,这个立方体就需要在世界空间中。为了获得在世界空间的立方体的坐标,在场景绘图中立方体中所有结点的应变换适用于世界空间坐标。比方说立方体关联上一个已经关联到场景根结点的结点上面,并且使用变换了(10,0,0)。然后在世界空间中这个立方体就变为了这样:
两个立方体的不同之处在于原点变换了位置,或者更准确的说,这个立方体远离了原点。
当我们调用translate() 函数,如果没有定义使用的空间的话,立方体就相对于父空间移动,就如第五步所做的一样。当没有父结点的立方体被旋转,使用父空间或者局部空间的translate() 函数表现为和世界空间为同一种方式。这是因只有原点的位置改变了并且主轴的方向并没有改变。当我们说移动立方体(0,0,10),原点在哪无关紧要—— 只要坐标系的主轴的方向是正确的,变换后的结果不会改变。然而,当一个父结点被旋转后,这个观点就不再正确了。当父结点旋转的时候,父空间的原点的轴也会旋转,同时也会改变translate(0,0,10) 的意义。
左边的坐标系并没有旋转并且(0,0,10)表示移动立方体让观察者感觉到拉近了10个单位。这使因为z轴是代表着指向屏幕外的方向。当局部空间或父空间的坐标轴旋转180度,(0,0,10)就表示立方体远离观察者10个单位,因为Z轴已经指向了屏幕里面。
我们可以看到达到既定效果,translate() 函数是调用在哪个空间是十分重要的。世界空间主轴的方向总是相同的。更准确的说,世界空间使用了左边的坐标系。父空间的所有旋转使用了上一个父结点自身的坐标系。局部的坐标系包含了全部的旋转,不论是场景结点本身还是所有的父结点。translate()的默认设置是使用父空间。当结点移动使用translate() 函数,使我们可以旋转场景结点时不需要改变结点的方向。
但是在某些情况下当我们想要在不同的空间发生变换而非在一个父空间。在这些情况下,我们使用translate()函数的第二个参数。第二个参数定义了我们想要变换发生的空间。在我们代码中,我们使用Ogre::Node::TS_WORLD 使模型在世界空间中移动,这就变为模型反向旋转,因为我们的模型之前已经旋转过180度,这样,X轴和Z轴的方向都改变了。再一次的观察图片查看效果。
【 在局部空间中变换】
我们已经看到在父空间和时间空间的变换。现在我们将会在局部和父空间变换以区别两者的不同并且对空间之间的不同有个更深的认识。
【 实践时刻—— 在局部和父空间中变换 】
1. 再次清空createScene()函数
2. 插入一个参考模型。这次我们将会移动模型离我们的摄像器更近,所以我们就不用移动摄像机了。
Ogre::Entity* ent = mSceneMgr->createEntity("MyEntity","Sinbad. mesh"); Ogre::SceneNode* node = mSceneMgr->createSceneNode("Node1"); node->setPosition(0,0,400); node->yaw(Ogre::Degree(180.0f)); mSceneMgr->getRootSceneNode()->addChild(node); node->attachObject(ent);
3. 添加第二个模型并且绕Y轴旋转45度并且在父空间中移动(0,0,20)
Ogre::Entity* ent2 = mSceneMgr->createEntity("MyEntity2","Sinbad. mesh"); Ogre::SceneNode* node2 = node->createChildSceneNode("node2"); node2->yaw(Ogre::Degree(45)); node2->translate(0,0,20); node2->attachObject(ent2);
4. 添加第三个模型,同样绕Y轴旋转45度并且在局部空间中移动(0,0,20)
Ogre::Entity* ent3 = mSceneMgr->createEntity("MyEntity3","Sinbad. mesh"); Ogre::SceneNode* node3 = node->createChildSceneNode("node3"); node3->yaw(Ogre::Degree(45)); node3->translate(0,0,20,Ogre::Node::TS_LOCAL); node3->attachObject(ent3);
5.编译并运行应用程序。然后再次操控摄像机你就可以从上面看到模型了.
【 刚刚发生了什么? 】
我们创建了一个参考模型并且然后添加了两个绕Y轴旋转45度的模型。然后我们两个都移动(0,0,20),一个模型在默认的父空间,另一个在局部空间。在父空间的移动过的模型是直接朝Z轴移动的。但是因为我们绕Y轴旋转了模型,在局部空间中的移动模型,它将会以面向这个角度移动并且最终停在了途中的左上。让我们重复一下。
当我们移动的使用,默认的的设置是父空间,这意味着所有除了旋转过的场景结点都是使用父空间移动的。
当使用世界空间时,旋转是不被考虑进去的。当我们移动的时候,使用的就是就是世界坐标系。
当在局部空间移动的时候,每个旋转,甚至是我们移动过的结点的旋转,都会被用于移动变换。
【 简单测试——Ogre3D和空间】
指出在Ogre3D中不同的3个空间
【 让英雄移动起来—— 添加对称】
改变MyEntity2的旋转和移动使效果图对称。确定你使用了正确的空间。否则,创建一个对称的效果图是十分困难的。这就是之后的效果图。
【 实践时刻—— 在不同的空间中旋转 】
这次,我们将会使用不同的空间旋转,如下所述
1.同样的,我们将会以一个干干净净的createScene() 函数作为开始,所以删除这个函数内的所有代码。
2. 添加参照模型:
Ogre::Entity* ent = mSceneMgr->createEntity("MyEntity","sinbad. mesh"); Ogre::SceneNode* node = mSceneMgr->createSceneNode("Node1"); mSceneMgr->getRootSceneNode()->addChild(node); node->attachObject(ent);
3. 添加第二个模型并且以一般的方式旋转它:
Ogre::Entity* ent2 = mSceneMgr->createEntity("MyEntity2","sinbad. mesh"); Ogre::SceneNode* node2 = mSceneMgr->getRootSceneNode()- >createChildSceneNode("Node2"); node2->setPosition(10,0,0); node2->yaw(Ogre::Degree(90)); node2->roll(Ogre::Degree(90)); node2->attachObject(ent2);
4. 使用时间空间添加第三个模型:
Ogre::Entity* ent3 = mSceneMgr->createEntity("MyEntity3","Sinbad. mesh"); Ogre::SceneNode* node3 = node->createChildSceneNode("node3"); node3->setPosition(20,0,0); node3->yaw(Ogre::Degree(90),Ogre::Node::TS_WORLD); node3->roll(Ogre::Degree(90),Ogre::Node::TS_WORLD); node3->attachObject(ent3);
5.编译并运行程序
【 刚刚发生了什么? 】
如往常一样,我们创建我们的图中左侧的为参考模型。我们旋转第二个模型—— 首先绕Y轴旋转并且然后绕Z轴旋转。旋转使用默认的空间作为局部的空间。这意味着我们把第一个模型绕Y轴旋转90度,Z轴的方向就改变了。第二个模型使用了世界坐标系并且Z轴的方向总是保持不变,甚至当我们已经旋转过场景结点。
在一号模型中的坐标系是我们原始的坐标系。在二号中,我们看到经绕Y轴旋转90度之后的坐标系,在三号中,我们沿Z轴旋转90度。现在我们将会看代替局部空间使用世界空间后的变换。
虽然我们做同样的旋转,但是因为总是使用世界空间,我们不使用改变了的坐标系,那样会得到一个不同的结果。
【 在不同空间的缩放比例 】
缩放比例在模型建立之初就完成了,因此在不同空间的缩放是相同的。在每个空间设置缩放比例是没有必要的,因为我们没有必要去做这件事。
【 概要】
我们在这章学习了很多关于使用Ogre3D绘图和用它去创建复杂的场景的知识。
具体有以下几点:
- 为什么是场景绘图并且它的如何起作用的。
- 改变位置,方向和结点缩放比例的不同方法。
- 我们用于旋转和变换的不同坐标系我们如何灵活的使用场景绘图的特性去创建复杂的场景。
之后,在下一章我们将会创建更加复杂的场景,我们将会添加灯光,阴影,并且创建我们自己的摄像机。