Ogre3D 基础教程1-7


文档:教程:基础教程:基础教程一 出自 Ogre3D 开放资源地带 跳转到: 导航, 搜索 目录 · 1 简介 · 2 从这里开始 · 3 OGRE 是怎样工作的 o 3.1 场景管理器基础 o 3.2 实体基础 o 3.3 场景节点基础 · 4 第一个 OGRE 程序 · 5 坐标和向量 · 6 添加其它的对象 · 7 实体深入了解 · 8 场景节点深入了解 · 9 额外尝试 o 9.1 缩放 o 9.2 旋转 · 10 小结 · 11 Ogre 环境配置 o 11.1 动态链接库(DLLs) 与 插件(Plugins) o 11.2 配置文件 o 11.3 一个更好的调试程序的方法 简介 在这篇教程里,我会向您介绍 OGRE 最基础的构架:场景管理器,场景节点和实 体。由于我需要在这篇教程里把 OGRE 的基本概念介绍给你,所以我们不会接触 太多的代码。在您阅读这篇教程的同时,您应该自己一点一点的添加代码来体 会代码的作用,只有这样才可以真正理解这些概念。 从这里开始 在这篇教程里,我们将使用已经写好的代码作为模版。除了我们将要在 createScene 函数里面添加的代码之外,您可以暂时忽略其他的东西。在后面 的教程里我会深入讲解 OGRE 程序是如何工作的,现在我们只需要从最简单的地 方学起就行了。在您的编译器里创建一个新的工程,然后把下面的代码添加进 去: #include "ExampleApplication.h" class TutorialApplication : public ExampleApplication { protected: public: TutorialApplication() { } ~TutorialApplication() { } protected: void createScene(void) { } }; #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 #define WIN32_LEAN_AND_MEAN #include "windows.h" INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT ) #else int main(int argc, char **argv) #endif { // Create application object TutorialApplication app; try { app.go(); } catch( Exception& e ) { #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 MessageBox( NULL, e.getFullDescription().c_str(), "An exception has occured!", MB_OK | MB_ICONERROR | MB_TASKMODAL); #else fprintf(stderr, "An exception has occured: %s\n", e.getFullDescription().c_str()); #endif } return 0; } 假如您能够成功编译这段代码,您在运行的时候可以用 WASD 键移动,鼠标来转 镜头,ESC 键来退出程序。 OGRE 是怎样工作的 这是一个很广的题目,我们将从场景管理器开始然后进一步了解场景节点和实 体。这三个类是所有 OGRE 程序的基石。 场景管理器基础 在屏幕上显示的所有东西都是由场景管理器来管理。当您在场景中添加物体 时,场景管理器会记录这些物体的位置。当您添加摄像机来观看某个场景时, 场景管理器会记录摄像机的位置。当您添加平面、广告牌、灯光时,场景管理 器同样会管理他们。 OGRE 里有很多种场景管理器。有的场景管理器渲染地 面,有的场景管理器渲染 BSP 表等等。在后面,我们将进一步了解场景管理 器。 实体基础 一个实体是可以在场景中渲染的物体之一。您可以把实体理解为任何一个 3D 模 型。一个机器人可以是一个实体,一条鱼可以是一个实体,大地草原可以是一 个非常大的实体。灯光,摄像机,粒子,广告牌等不能成为实体。 但在 Ogre 中你不能够直接将一个实体放入到场景中,而是将实体与场景节点 绑在一起,这个场景节点则包括了实体的方位信息。 场景节点基础 场景节点将持续跟踪与它绑在一起的实体的方位。当你创建了一个实体时,它 直到与一个场景节点绑定后才会被渲染。同样,一个场景节点也不能单独的在 屏幕上显示出来,只有与一个实体绑定后才能在屏幕上显示。 场景节点可以绑定多个实体。例如在屏幕上有在行走的一个人物对象,并且希 望这个对象产生发光效果,要实现这些,首先你需要创建一个场景节点,然后 再创建一个人物对象的实体并与场景节点绑定在一起,之后你还需要创建一个 光照模型也与这个场景节点绑定在一起。场景节点同样可以与其它场景节点绑 定以描述更完整的对象。我们在后续的章节中介绍场景节点更多的用法。 在场景中,场景节点的位置总是与它的父节点相关。每一个场景管理器都包含 一个根节点。 第一个 OGRE 程序 返回到我们刚才创建的代码,找到 TutorialApplication::createScene 成员 函数。首先需要为整个场景设置环境光,这样才可以看到要显示的内容,通过 调用 setAmbientLight 函数并指定环境光的颜色就可以做到这些。指定的颜色 由红、绿、蓝三种颜色组成,且每种色数值范围在 0 到 1 之间。将下一句添 加到 createScene 中: mSceneMgr->setAmbientLight( ColourValue( 1, 1, 1 ) ); 下一步创建一个 Entity ,通过调用 SceneManager 的 createEntity 方法来 创建: Entity *ent1 = mSceneMgr->createEntity( "Robot", "robot.mesh" ); 变量 mSceneMgr 是当前场景管理器的一个对象,createEntity 方法的第一个 参数是为所创建的实体指定一个唯一的标识,第二个参数 "robot.mesh" 指明 要使用的网格实体,"robot.mesh" 网格实体在 ExampleApplication 类中被装 载。 这样,就已经创建了一个实体,但还需要创建一个场景节点来与它绑定在一 起。既然每个场景管理器都有一个根节点,那我们就在根节点下创建一个场景 节点。 SceneNode *node1 = mSceneMgr->getRootSceneNode()- >createChildSceneNode( "RobotNode" ); 这句代码首先调用场景管理器的 getRootSceneNode 方法来获取根节点,再使 用根节点的 createChildSceneNode 方法创建一个名为 "RobotNode" 的场景节 点。与实体一样,场景节点的名字也是唯一的。 最后,将实体与场景节点绑定在一起,这样机器人(Robot)就会在指定的位置被 渲染: node1->attachObject( ent1 ); 编译并运行你的程序,在屏幕上你将看到一个机器人。 坐标和向量 在我们继续之前,需要先了解一下 OGRE 的坐标和向量。与其它图形引擎一 样,OGRE 也使用 XZ 面作为其水平面,Y 轴作为纵轴。当你面对着屏幕时,从 左至右的方向为 X 轴正方向,从下至上为 Y 轴正方向,从里至外为 Z 轴的正 方向。 你现在可能注意到机器人正面朝 X 轴正方向,这也许是模型(mesh)设计时就预 定好的方向。OGRE 并不会假定你的模型的方位,你所载入的每一个模型都可能 有不同的“标准”方向。 OGRE 使用 Vectors 类来描述方向和位置,定义了 2 维、3 维及 4 维向量,其 中 3 维向量使用得最多。如果你对向量的知识不熟悉,那建议你在正式使用 OGRE 之前先学习一些向量的知识,因为在复杂的程序设计中向量是非常重要 的。 添加其它的对象 现在我们已经了解了 OGRE 中的方位了,让我们再次回到代码部分你就会发现 在代码中并没有指定机器人的方向。实际上在 OGRE 中大量的函数都有自己默 认的参数值,例如 SceneNode::createChildSceneNode 成员函数有三个参数: 场景节点的名称、位置及方向,该成员的位置默认值为(0,0,0)。让我们创建另 一个场景节点,这次我们指定它的方位信息: Entity *ent2 = mSceneMgr->createEntity( "Robot2", "robot.mesh" ); SceneNode *node2 = mSceneMgr->getRootSceneNode()- >createChildSceneNode("RobotNode2", Vector3( 50, 0, 0 ) ); node2->attachObject( ent2 ); 这些代码看起来应该很熟悉,除了两个地方不同之外,其它部分与以前的代码 都一样。首先我们创建的实体及场景节点的名称有细微的不同,其次创建场景 节点时指定了节点的方位信息,我们让实体向 X 的正方向偏移了 50 个单位 (这是相对与场景中的根节点而言,而所有的节点的位置都是相对于其父节点 而言的)。编译并运行,你会发现屏幕上现在有两个并排着的机器人。 实体深入了解 实体类包含的内容相当的广,这里我不会向你介绍实体类的所有内容,只不过 使你能够开始使用 OGRE 而已。不过我会向你介绍几个实体类有用的成员函数。 第一个是 Entity::setVisible 和 Entity::isVisible。利用这个函数,你可以 设置任何一个实体为可见或不可见。当你想暂时隐藏一个实体时,与其销毁这 个实体然后再在用的时候重新创建,不如简单的调用一下这个函数。注意你不 需要“省着”用这些实体,任何一个对象的材质和贴图只会被载入到内存里一 次,所以当你省系统资源的时候,你并没有真正节省多少。你唯一省掉的只不 过是实体创建和毁灭的时间。 getName 函数返回实体的名称,getParentSceneNode 函数返回这个实体绑定的 节点。 场景节点深入了解 场景节点类同样相当复杂。场景节点能实现很多事情,所以我们只了解一些常 用的。 你可以利用 getPosition 或 setPosition 得到或者设定场景节点的位置 (总是 和父节点相对的)。你可以利用 translate 函数来移动对象。 场景节点不仅仅决定一个对象的位置,它还可以控制一个对象的缩放比例和旋 转角度。你可以用 scale 函数来设置一个对象的缩放比例,还可以用 yaw, roll, pitch 函数来旋转对象。你可以用 resetOrientation 来还原你对对象进 行的所有旋转。你还可以用 setOrientation, getOrientation 和 rotate 函数 对对象进行高级旋转。四元数会在后面的教程里对大家讲解。 你现在已经见过 attachObject 函数了。当你想对绑定到场景节点上的对象进行 操作时,这些函数会很有帮助:numAttachedObjects, getAttachedObject(这 个函数有很多版本), detachObject(同样很多版本), detachAllObjects。还有 一大堆函数是来处理父节点和子节点的。 由于所有的移动都是相对于父节点的,我们可以很容易的使两个节点一起移 动。我们现在已经有这些代码在我们的程序里: Entity *ent1 = mSceneMgr->createEntity( "Robot", "robot.mesh" ); SceneNode *node1 = mSceneMgr->getRootSceneNode()- >createChildSceneNode( "RobotNode" ); node1->attachObject( ent1 ); Entity *ent2 = mSceneMgr->createEntity( "Robot2", "robot.mesh" ); SceneNode *node2 = mSceneMgr->getRootSceneNode()- >createChildSceneNode( "RobotNode2", Vector3( 50, 0, 0 ) ); node2->attachObject( ent2 ); 假如我们将第 5 行从: SceneNode *node2 = mSceneMgr->getRootSceneNode()- >createChildSceneNode( "RobotNode2", Vector3( 50, 0, 0 ) ); 改成: SceneNode *node2 = node1->createChildSceneNode( "RobotNode2", Vector3( 50, 0, 0 ) ); 这样一来 RobotNode2 就变成了 RobotNode 的子节点。移动 node1 会使 node2 跟 着移动,移动 node2 却不会影响 node1。下面的代码就只会移动 RobotNode2: node2->translate( Vector3( 10, 0, 10 ) ); 下面的代码会移动 RobotNode,既然 RobotNode2 是 RobotNode 的子节点, RobotNode2 会跟着移动: node1->translate( Vector3( 25, 0, 0 ) ); 最后说一下,你可以使用场景管理器的成员函数 getSceneNode 和 getEntity 来 获取场景节点和实体,这样你就不用保留你创建他们时使用的指针了。但你仍 然应该保留很常用的实体的指针。 额外尝试 到目前为止你应该已经掌握了实体,场景节点和场景管理器的基础了。现在你 可以试验一下下面的代码段: 缩放 你可以用 scale 这个成员函数来缩放网格。试一下变换 scale 里面的值看看什 么效果: Entity *ent = mSceneMgr->createEntity( "Robot", "robot.mesh" ); SceneNode *node = mSceneMgr->getRootSceneNode()- >createChildSceneNode( "RobotNode" ); node->attachObject( ent ); node->scale( .5, 1, 2 ); ent = mSceneMgr->createEntity( "Robot2", "robot.mesh" ); node = mSceneMgr->getRootSceneNode()- >createChildSceneNode( "RobotNode2", Vector3( 50, 0, 0 ) ); node->attachObject( ent ); node->scale( 1, 2, 1 ); 旋转 你可以用 yaw, pitch, roll 来旋转对象。Yaw 是 Y 轴的旋转,Pitch 是 X 轴, Roll 是 Z 轴。试一下变换角度(Degree)和合并多种旋转方式: Entity *ent = mSceneMgr->createEntity( "Robot", "robot.mesh" ); SceneNode *node = mSceneMgr->getRootSceneNode()- >createChildSceneNode( "RobotNode", Vector3( -100, 0, 0 ) ); node->attachObject( ent ); node->yaw( Degree( -90 ) ); ent = mSceneMgr->createEntity( "Robot2", "robot.mesh" ); node = mSceneMgr->getRootSceneNode()- >createChildSceneNode( "RobotNode2"); node->attachObject( ent ); node->pitch( Degree( -90 ) ); ent = mSceneMgr->createEntity( "Robot3", "robot.mesh" ); node = mSceneMgr->getRootSceneNode()- >createChildSceneNode( "RobotNode3", Vector3( 100, 0, 0 ) ); node->attachObject( ent ); node->roll( Degree( -90 ) ); 小结 恭喜你,现在已经掌握了 OGRE 的基石——实体,场景节点,场景管理器。你并 不需要对上面所讲的那些函数都了如指掌。由于他们是很基础的东西,我们会 经常使用。随着对 OGRE 了解的深入,你应该可以自然而然的掌握他们。 Ogre 环境配置 本课(甚至整个教程)涉及到的大部分文件(dll 和 cfg)都可以在 ${OgreSDK}/bin/debug 或 release 目录里找到。你创建的 debug 程序应该使用 debug 目录里的文件,而 release 程序应当使用 release 目录里的文件。 注意,在这里多数情况是指在 Windows 环境下的。在 Linux 中,相同的内容基 本上是适用的,但共享库是以.so 作后缀,且存在于不同的位置,部分内容可 能存在细微的差异。如果你遇到任何问题,请在 OGRE help 论坛求助。 动态链接库(DLLs) 与 插件(Plugins) 好,我们已经稍微玩转了一下 Ogre 的开发环境,我想进一步说明在一般情况下 OGRE 库是如何工作的,以及告诉您如何更轻松的使用它。 Ogre 被分为若干类共享库。 第一类是原始库,以及它的依赖库。Ogre 库全都包含在 OgreMain.dll 里。这 个 dll 还需要其它的一些库,比如 cg.dll。这些 dll 必须毫无例外地包含在每 一个 ogre 应用中。 第二类库是插件。Ogre 将很大一部分功能抽到这些库里面,以方便能根据具体 需要来开启或关闭这些功效。Ogre 包含的基本插件拥有以"Plugin_"开头的文 件名。如果需要,你能亲自写一个插件,但我们的教程将不涉及这些内容。 Ogre 还为渲染系统提供插件(如 OpenGL、DirectX 等)。这些插件以 "RenderSystem_"为前缀。有了这些插件,你就可以方便地在应用程序中添加或 者移除渲染系统。这是非常有用的,如果你正在写一个 OpenGL 专门的着色程 序,并希望当运行在 DirectX 上时不执行它,你则只要移除这个渲染系统,它 就不存在了。另外,如果你致力于一个非标准的平台,你可以写你自己的渲染 系统插件,但我们的教程不涉及。我们将在下一课告诉你如何移除插件。 第三类库是第三方库和帮助库。Ogre 本身只是一个图形渲染引擎,它并不包含 像 GUI 系统、输入控制、物理引擎这类东西,你需要其它第三方帮助库。CEGUI 库就是一个能轻松整合进 Ogre 的 GUI 系统,以"CEGUI*"开头的 dll 文件和 "OgreGUIRenderer.dll"就是它的一部分。我们将在以后的教程里介绍 CEGUI。 鼠标键盘输入则通过 OIS 来实现(一个输入系统),它包含在 OIS.dll 里。还有 其它很多能够提供更多功能的库(不包含在 SDK),比如音效和物理。你能在 Wiki 或论坛里找到更多的信息。 总之准则就是,当你在本地调试你的程序时,你可以将所有的功能都“打开” (也就是说,不移除任何东西)。当你准备发布你的程序时,你应用 Release 模式构建它,包括所有你用到了的 dll 文件。而且你应该移动那些你没有使用 的 dll。如果你的程序没有用到 CEGUI,而是使用了 OIS,就不要包含 CEGUI 进 来,但你必须包含 OIS 的 dll,否则的你程序跑不起来。 配置文件 Ogre 的运行需要几个配置文件。它们用来控制哪些插件需要加载,程序的资源 在哪里定位等等。我们来简单地看一下每个配置文件是用来做什么的。若你有 更特殊的问题,请直接来 Ogre help 论坛询问。 plugins.cfg 这个文件指定应用程序使用的插件。如果你要在程序中添加或移 除某个插件,就要修改这个文件。要去掉某个插件,只需要删除某一行,或用# 直接注释掉它。你想要添加一个插件,就要添加像"Plugin=[PluginName]"这样 的一行。注意,你不要在插件名后面加上.dll 后缀。你的插件同样不必以 "RenderSystem_" 或 "Plugin_" 开头。 通过修改"PluginFolder"变量,你还 可以定义 Ogre 搜索插件的目录。你可以使用绝对或相对路径,但不能使用像 $(SomeVariable)这样的环境变量。 resources.cfg 这个文件含有 Ogre 搜索资源的目录列表。资源包括:脚本、模 型、纹理等等。你同样可以使用绝对或相对路径,但你不能使用如 $(SomeVariable)环境变量。注意,Ogre 不会搜索子目录,所以你如果有多层 目录必须手工输入它们。比如,你有一个目录树,像"res\meshes" and "res\meshes\small",你就要为这些路径添加两个入口。 media.cfg 这个文件告诉 Ogre 更多关于某些资源的细节。目前你不太可能改动 这个文件,所以我们跳过去。你能在手册或 Ogre 论坛里找到更多的信息。 ogre.cfg 这个文件是 Ogre 的配置窗口生成的。这个文件与你的个人电脑和显 示配置相关。当你把应用程序发布给别人时,不必配提供这个文件,因为他们 的设置很可能不同。注意,你不能直接编辑这个文件,而要使用那个配置窗 口。 quake3settings.cfg 这个文件是与 BSPSceneManager 一起使用的。除非你使用 这个场景管理器(目前你不会使用),否则你不需要这个文件,我们忽略它。同 样地,除非你正在使用 BSPSceneManager,你不要把这个文件与你的程序一起 发布,就算那样,它也可能会完全不一样,这要看你程序的需求。 以上是所有 Ogre 需要操作的文件。Ogre 要能找到"plugins.cfg", "resources.cfg", 和 "media.cfg",它才能正确运行。在后续的教程里,我们 将会深入介绍这些文件,以及如何改变它们的地址,用它们来做更高级的事 情。 一个更好的调试程序的方法 注意,这个部分是 Windows 和 Visual C++相关的。 如很多地方讲述的,Ogre 必须要能找到配置文件、dll 文件以及程序所用到的 media 资源文件。为了解决这个问题,大多数人手工地把 bin 目录从 OgreSDK 拷到他们的工程目录,来保证正确的 dll 文件与可执行文件在相同的目录。如 果你正在创建一个游戏并准备发布给别人,这可能是最好的办法,因为你毫无 疑问地会修改这些配置文件,从标准内容中添加删除插件。而在其它一些情况 里,持贝所有的 dll 文件到每一个 Ogre 工程里浪费了空间和时间。其实你可以 有另一种办法。 另一种方法就是把 OgreSDK 里的 dll 文件拷贝到 c:\Windows\System 目录。这 样做的优点就是,不管理你的可执行文件在哪,Windows 总能找到合适的 dll。 要让这样做生效,你还要相应地修改 resources.cfg 和 plugins.cfg 来包含 media 目录和插件的绝对路径。现在,无论何时你创建了一个工程,你只要把 修改过的配置文件拷贝到你的 bin\debug 和 bin\release 目录就行了。我个人 不太使用这种办法,因为这有可能让人分不清在 windows 目录里哪些是 OgreSDK 的 dll 文件。Ogre 新版本出得很快,所以实际上手工地去用这种方法 去安装是非常烦琐的。 一个更好的选择就是,让那些 OgreSDK 文件原地不动,而把每个 Ogre 应用的工 作目录(working directory)设置成 OgreSDK 的 bin\release 或 bin\debug 目 录。要这样做的话,找到你的工程属性,把工作目录(working directory)改成 "C:\OgreSDK\bin\$(ConfigurationName)"。你要把 "C:\OgreSDK" 改成你自己 的 Ogre 安装目录。好了,不需要拷贝任何文件,你的 Ogre 程序就能运行了。 反正我是用这个方法的。唯一的缺点就是,若你修改了配置文件,你所有的 Ogre 工程都会受到影响,这显然是糟糕的。如果你使用了这个方法,而又想为 你的工程修改配置文件的话,你最好还是照以前那样把文件复制到工程里,然 后把工作目录给改回来。 取自 "http://ogre3d.cn/wiki/index.php?title=%E6%96%87%E6%A1%A3:%E6%95%99%E 7%A8%8B:%E5%9F%BA%E7%A1%80%E6%95%99%E7%A8%8B:%E5%9F%BA%E7%A1%80%E6%95 %99%E7%A8%8B%E4%B8%80" 查看 · 页面 · 讨论 · 源码 · 历史 个人工具 · 登录/创建账户 导航 · 首页 · 社区 · 当前事件 · 最近更改 · 随机页面 · 帮助 搜索 进入 搜索 Google 搜索 Enter your search terms Submit search form earch 工具箱 · 链入页面 · 链出更改 · 特殊页面 · 可打印版 · 永久链接 Google Adsense · 这页的最后修订在 2009 年 5 月 11 日 (星期一) 07:30。 · 本页面已经被浏览 3,996 次。 · 隐私政策 · 关于 Ogre3D 开放资源地带 · 沪 ICP 备 09049564 号 文档:教程:基础教程:基础教程二 出自 Ogre3D 开放资源地带 跳转到: 导航, 搜索 本章翻译由收费打工仔完成 目录 [隐藏]  1 先决条件  2 简介  3 从这里开始  4 摄像机 o 4.1 Ogre 的摄像机 o 4.2 创建一个摄像机  5 视口 o 5.1 Ogre 的视口 o 5.2 建立视口  6 光照和阴影 o 6.1 Ogre 支持的阴影类型 o 6.2 在 Ogre 中使用阴影 o 6.3 故障排除 o 6.4 光源种类 o 6.5 建立光源  7 尝试要做的事情 o 7.1 不同的阴影类型 o 7.2 光源衰减 o 7.3 SceneManager::setAmbientLight o 7.4 视口背景色 o 7.5 Camera::setFarClipDistance o 7.6 平面  8 完整来源 先决条件 本教程假定你已经拥有了 c++程序设计的知识,并且已经安装和编译了一个 Ogre 的应用程序(如果你在设置你的应用程序中有困难,请参考 this guide 获得更 详细的编译步骤)。这个教程同时也是建立在上一章基础上的,因此默认你已经 了解了上个教程的内容。 简介 在这篇教程里,我会向您介绍几个新的构架,同时还会补充一些过去的内容。这 篇教程主要介绍如何使用灯光对象以及如何产生阴影。我们还会稍微了解一下摄 像机的用法。当你看完这篇教程以后,你应该试着慢慢的添加一些代码到你自己 的工程里,然后看看结果。你可以参考这个教程里最终状态的源代码。如果你在 写代码时遇到了困难,也可以和最终工程里的源文件对比一下。 从这里开始 像上一个教程一样,我们将使用一个先前建立的代码作为我们出发的起点。我们 将增加两个方法到 TutorialApplication class 中:createViewport 和 createCamera。这两个方法我们已经在基类 ExampleApplication 中定义了,在 这个教程中我们将看到摄像机和视口具体建立和使用。 为这个项目在编译器中创建一个工程,添加源文件包含这些代码: #include "ExampleApplication.h" class TutorialApplication : public ExampleApplication { protected: public: TutorialApplication() { } ~TutorialApplication() { } protected: virtual void createCamera(void) { } virtual void createViewports(void) { } void createScene(void) { Entity *ent; Light *light; } }; #if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32 #define WIN32_LEAN_AND_MEAN #include "windows.h" INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT ) #else int main(int argc, char **argv) #endif { // Create application object TutorialApplication app; try { app.go(); } catch( Exception& e ) { #if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32 MessageBox( NULL, e.getFullDescription().c_str(), "An exception has occurred!", MB_OK | MB_ICONERROR | MB_TASKMODAL); #else fprintf(stderr, "An exception has occurred: %s\n", e.getFullDescription().c_str()); #endif } return 0; } 如果你是在 Windows 下使用 OgreSDK 的,请确定添加 “"[OgreSDK_DIRECTORY]\samples\include”目录到这个工程 (ExampleApplication.h 文件所在的位置)除了标准包含以外。如果使用的是 Ogre 的源代码包,这个文件应该在 “"[OgreSource_DIRECTORY]\Samples\Common\include”目录中。请确保你可以 编译这段代码在进入下一阶段之前(我们并没有运行,因为他是会崩溃,直到随 后给他添加更多的东西)。我们稍后将添加代码来完成这项工作。如果你有问题, 请查看这个 Wiki 页获得设置你编译器的信息,如果仍然有问题请试着去看看帮 助栏。 程序控制: 用键盘上 WASD 来移动,用鼠标观察四周。Ese 键退出。 摄像机 Ogre 的摄像机 摄像机是用来观察我们所创建的场景的。摄像机是一个特殊的物体,她的工作方 式有点类似于场景节点。摄像机可以设置位置进行偏移,滚动,倾斜操作和绑定 到任意场景节点上。就像场景节点一样,摄像机的位置跟父节点有关(就好像尊 重长辈一样)。对所有的移动和旋转来说,你可以把摄像机考虑成场景节点。 对于 Ogre 摄像机来说,有一点跟你想象不一样的是同一个时间内你只能使用一 台摄像机(当前)。这就是说,我们不能创建其中一个摄像机去观察场景的一部 分,而用第二个去观察场景的另一部分,并且仅通过打开或关闭某一个摄像机就 可以来呈现我们想要的一部分场景。不过要完成这一步有一个替代方式就是通过 一些创建场景节点来扮演“摄像机句柄”。这些场景节点简单的参加到场景中指 向摄像机可能要捕捉的位置。当只需要展示一部分场景时,摄像机只需要简单的 把自己绑定到适当的节点上就可以了。我们会在帧监听教程里重温这项技术。 创建一个摄像机 我们将通过替换默认的 ExampleApplication 方法来创建摄像机。 找到 TutorialApplication::createCamera 方法。首先,我们将创建摄像机。 场景管理器里有自带的摄像机。我们就以此来创建我们的摄像机。添加下面的代 码到创建摄像机方法里: // 创建摄像机 mCamera = mSceneMgr->createCamera("PlayerCam"); 这将创建一个叫"PlayerCam"的摄像机。如果你不打算保留它的指针,那么你可 以调用场景管理器的方法 getCamera 通过传递它的名字来获取它。 接下来,我们继续设置摄像机的初始位置和朝向。我们将设置摄像机朝向坐标原 点,所以我们需要设置一个恰当的 Z 轴距离。添加下面的代码到刚才的后面: //设置摄像机位置和方向 mCamera->setPosition(Vector3(0,10,500)); mCamera->lookAt(Vector3(0,0,0)); lookAt 方法是灵活的。你能够设置它面向任何你想要的角度。场景节点也有同 样的方法,能够通过此方法在任何情况下,很容易的达到你想要的方向。最后, 我设置一个 5 单位距离的近距离裁剪。通过设置摄像机的裁剪距离,来规定该距 离内的事物是你看不到的。设置近距离裁剪后,你能够在离实体很近的时候,很 容易的透过实体看到场景。还有一种很微小的情况就是,当你和某个物体很近时, 这个物体将填满你的整个视野。同样,你也可以设置远距离裁剪。这个值的设置 能够让引擎不去渲染远于它的物体。这主要在你需要远距离渲染大场景的时候, 用来增加帧速。添加如下代码,以实现近距离裁剪: mCamera->setNearClipDistance(5); 设置远距离裁剪和上面一样,只是方法名是这个:setFarClipDistance。当然, 在这个例子里,你没必要设置远距离裁剪。 视口 Ogre 的视口 当你开始处理多个摄像机时,视口类的概念就变得对你很有用了。我提出这个主 题的原因是,我认为了解当渲染一个场景时 Ogre 是如何决定使用哪个摄像机的 是非常重要的。在同一个时间内 Ogre 中可能有多个场景管理器在运行。也有可 能把场景分为多个区域,然后用分开的摄像机去渲染场景中不同的区域(例如: 思考一下控制台游戏中两个人的观察)。然而我们可能去做这些事情的时候不用 考虑他们是如何被做到的,这是高级教程的内容。 为了了解 Ogre 是怎样渲染场景的,考虑一下 Ogre 中建立的这些东西:摄像机, 场景管理器,和渲染窗口。渲染窗口我们还没有涉及到,但是她是所有物体呈现 的最基本的窗口。场景管理器实体创建摄像机去观察场景。你必须告诉渲染窗口 哪些摄像机去呈现场景,哪部分窗口被渲染进来。你告诉渲染窗口的摄像机所呈 现的区域就是视口,并且这里只有一个视口实体。 在这篇指南中我们将介绍怎样注册摄像机来创建视口。我们可以通过运用这个视 口实体来设置正在渲染的场景的背景色。 建立视口 我们将重点关注 ExampleApplication 里视口的建立,因此找到 TutorialApplication::createViewports 的成员函数。建立视口的方法通常是 简单的调用渲染窗口中 addViewport 函数,把她提供给正在使用的摄像机。 ExampleApplication 类已经通过我们的渲染窗口移到 mWindow 类里面了,因此 添加这些代码: // Create one viewport, entire window Viewport* vp = mWindow->addViewport(mCamera); 现在我们拥有自己的视口了,我们可以用他做什么呢?答案是:不多。最重要的 事情我们可以做的是调用 setBackgroundColour 方法给背景设置任意我们选择 的颜色。因为在这章中我们要处理光照因此我们把背景设置为黑色。 vp->setBackgroundColour(ColourValue(0,0,0)); 注意颜色值分别是红绿蓝三种颜色的值他们的参数在 0 和 1 之间。最后也是最重 要的事情是我们需要设置摄像机的纵宽比。如果你用的是一些非标准全窗口视口, 而未能设置这个的话结果会出现一个非常奇怪的场景图。我们立刻设置她尽管我 们使用的是默认的纵宽比: // Alter the camera aspect ratio to match the viewport mCamera->setAspectRatio(Real(vp->getActualWidth()) / Real(vp->getActualHeight())); 这就是所有的我们对视口的简单运用。 这时你应该首先去编译一下然后运行这个应用程序,尽管什么也呈现不了只是黑 的场景(用 Esc 来退出)。确保你可以运行这个程序不会崩溃再继续。 光照和阴影 Ogre 支持的阴影类型 Ogre currently supports three types of Shadows: Ogre 目前支持 3 种类型 的阴影(当前版本 1.4.x 目前已经支持四种阴影类型): 1. 调制纹理阴影 (SHADOWTYPE_TEXTURE_MODULATIVE) - 是这 3 种中最节省 资源的。她创建一个阴影投射者的黑与白渲染到纹理,然后用于场景中。 2. 调制模板阴影 (SHADOWTYPE_STENCIL_MODULATIVE) - 这项技术是在所有 的非透明体被渲染到场景以后再渲染所有的阴影体来调制阴影,她耗费资 源没有加成模板阴影强,但是也不是非常精确。 3. 加成模板阴影 (SHADOWTYPE_STENCIL_ADDITIVE) - 这项技术是渲染每一 个光源作为分离的部分附加到场景中。 对显卡来说这是比较麻烦的,因 为在场景中每一个附加的光源需要一个附加渲染通路。 Ogre 不支持软阴影作为引擎的一部分。如果你想用软阴影,你需要写你自己的 顶点和片段语言。注意这只是一个简单的介绍。Ogre 手册完整描述了阴影以及 他们的用法。 在 Ogre 中使用阴影 用阴影在 Ogre 中相对来说是比较简单的。SceneManager 类中有一个 setShadowTechnique 成员函数,我们可以使用她设置我们想要的阴影类型。这 样只要你创建了一个实体,调用 setCastShadows 方法来设置这个实体是否投射 阴影。我们现在将设置环境光全变暗,然后设置阴影类型。找到 TutorialApplication::createScene 成员函数添加如下代码: mSceneMgr->setAmbientLight(ColourValue(0, 0, 0)); mSceneMgr->setShadowTechnique(SHADOWTYPE_STENCIL_ADDITIVE); 现在场景管理器运用的是加成模板阴影。让我们创建一个实体然后让他投射阴影。 ent = mSceneMgr->createEntity("Ninja", "ninja.mesh"); ent->setCastShadows(true); mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(e nt); 另外,ninja.mesh 已经通过 ExampleApplication 为我们预载入了。我们还需要 一些东西来安放 Ninja(就是需要一些东西来接收她投射的阴影)。为了做到这 个我们需要为此创建一个平面。这并不意味着我们需要用到 MeshManager 指南, 但是我将复习一些更基础的东西因为我们要用她来创建一个平面。一开始我们需 要定义平面实体本身,她需要提供一个法线和一个到原点的距离。(举个例子) 我们可以用平面来组织世界几何体的一部分,在这种情况下我们需要指定一些东 西与原点之间的距离不为 0. 现在我们仅仅想要一个平面把 y 正半轴作为她的法 线(这意味着我想让她面朝上),与原点的距离为 0: Plane plane(Vector3::UNIT_Y, 0); 现在我们需要注册这个平面以便于我们可以把她运用到我们的应用程序中。 MeshManager 类了解所有读到我们应用程序中的模型(例如:她了解我们用到的 robot.mesh 和 ninja.mesh)。createPlane 成员函数接收一个平面定义通过参 数制造的一个模型。这些是注册平面所用到的: MeshManager::getSingleton().createPlane("ground", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, plane, 1500,1500,20,20,true,1,5,5,Vector3::UNIT_Z); 另外,我现在不想探究怎样用 MeshManager 的细节(如果你想准确的了解每个参 数的作用,请参考 API)。基本上我们已经注册了我们的平面尺寸为 1500×1500, 新模型名字叫“ground”。现在,我们可以通过模型创建一个实体把她放置到场 景中: ent = mSceneMgr->createEntity("GroundEntity", "ground"); mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(e nt); 漂亮吧?哈!对 ground 来说在结束之前我们还有两件事情要做。首先是告诉场 景管理器我们不想把她设置成投射阴影体直到她被用做阴影体。第二件事是我们 需要在上面贴纹理。我们的 robot 和 ninja 模型已经拥有了为他们定义的材质脚 本。当我们手动去创建 ground 模型的时候,我们不需要指定用什么纹理。我们 将用"Examples/Rockwall""Examples/Rockwall"材质脚本,Ogre 包含以下这个 例子: ent->setMaterialName("Examples/Rockwall"); ent->setCastShadows(false); 现在场景中我已经有一个 Ninja 和 ground,让我们来编译和运行这个程序。我 们等待...什么也没有!!发生什么事了?上一个指南中我们明明添加了 Robots 而且她也被呈现出来了。原因是 Ninja 没有露出来,因为场景中的环境光设置的 是全黑。因此让我们添加光源看看会发生什么。 故障排除 一些人报道在用 GCC 和 code::blocks 时他们的程序片段中遇到一些问题。如果 收到类似这样的错误: variable 'vtable for Ogre::MeshPtr' can't be auto-imported. Please read the documentation for ld's --enable-auto-import for details. You'll need to add the following to your linker options: 你需要添加如 下的东西添加到你连接器选项中: -Wl,--enable-runtime-pseudo-reloc 如果用了 codeblocks::ide 点击 project name->build options->linker options,在那设置一下。 光源种类 Ogre 提供了三种光源类型。 1. 点光源 (LT_POINT) - 从一个点发出光放射到四周。 2. 聚光源 (LT_SPOTLIGHT) - 严格来说聚光灯工作有点像手电筒。 你在某 个地方设置了灯光的起点,然后灯光射向一个方向。你可以告诉光源内锥 角度大小和外锥角度大小(你知道手电筒内部会明亮一些。) 3. 有向光 (LT_DIRECTIONAL) - 有向光模拟非常遥远地方的灯光,在场景中 从某个方向照射物体。如果你想要一个夜晚的场景你就需要模拟月光。你 可以通过为场景设置环境光来做到这个,但是这并非是真实场景的模拟因 为月光并不平等地照亮所有物体(日光也不是)。一种方法解决这个是设 置一个有向的光源然后指向一个方向,月光就会显示出来。 光源有一个范围属性描述光看上去的样子。两个更重要的属性是光的漫反射颜色 和镜面反射颜色(diffuse and specular color)。每一个材质脚本定义有多少漫 反射和镜面反射光线由材质反射来,稍后我们将学习如果控制她。 建立光源 在 Ogre 中创建一个光源需要调用场景管理器中的 createLight 方法然后提供光 源的名称,就跟我们创建实体和摄像机一样,光源仅仅有两个方法 setPosition 和 setDirection(而且并不是像旋转,偏移,滚动一样有全套的移动函数)。 如果你需要移动光源(例如创建一个光源跟随角色),你需要把光源绑定到场景 节点上。 因此,让我们从基础的点光源开始吧。首先我们要创建光源设置她的类型和她的 位置: Light* light = mSceneMgr->createLight("Light1"); light->setType(Light::LT_POINT); light->setPosition(Vector3(0, 150, 250)); 现在我们已经创建好了光源,我们可以设置她的漫射色和镜面色了。让我们设置 为红色: light->setDiffuseColour(1.0, 0.0, 0.0); light->setSpecularColour(1.0, 0.0, 0.0); 好的现在编译和运行这个应用程序。成功了!我们现在可以看见 Ninja 和她投射 的影子了。确定你也可以从前面,侧面都可以看到她。有个要注意的事是你看不 见光源。你看见产生的光事实上不是光照实体本身。许多 Ogre 指南里都添加了 一个实体展示光是从哪发射出来的。如果你在应用程序中使用灯光时遇到了困难, 你应该考虑创建这些东西这样你可以准确知道光源的位置。 接着让我们试试有向光。注意为什么 ninja 前面有一些倾斜的黑色?让我们添加 一些黄色的有向光射向她的前面。设置光源和颜色的步骤和设置点光源是一样的: light = mSceneMgr->createLight("Light3"); light->setType(Light::LT_DIRECTIONAL); light->setDiffuseColour(ColourValue(.25, .25, 0)); light->setSpecularColour(ColourValue(.25, .25, 0)); 因为有向光是从无穷远的距离射来的,因此我们不需要设置她的位置,仅仅是设 置她的方向。我们设置光的方向是 z 正半轴和 y 负半轴(类似从 ninja 的 45 度 角正前方射过来): light->setDirection(Vector3( 0, -1, 1 )); 编译并运行这个应用程序。在场景中我们已经有两个影子了,因为有向光很微弱, 因此影子也比较弱。最后让我们来试试聚光灯。我们将创建一个蓝聚光灯: light = mSceneMgr->createLight("Light2"); light->setType(Light::LT_SPOTLIGHT); light->setDiffuseColour(0, 0, 1.0); light->setSpecularColour(0, 0, 1.0); 我们仍然需要设置位置和方向来确定聚光源。我们将创建一个聚光源盘旋在 Ninja 的右侧,然后直射下来: light->setDirection(-1, -1, 0); light->setPosition(Vector3(300, 300, 0)); 聚光源还允许我们指定光束的宽度。考虑一下手电筒的光束。中心的内光束比周 围光束明亮一些。我们可以设置这两种宽度通过调用 setSpotlightRange 方法。 light->setSpotlightRange(Degree(35), Degree(50)); 编译运行她,紫色的 Ninja„„好恐怖! 尝试要做的事情 不同的阴影类型 这个 demo 里我们设置的阴影类型仅仅是 SHADOWTYPE_STENCIL_ADDITIVE(加成 模板阴影)。尝试设置成其他两种阴影看看会发生什么。还有一些其他的与阴影 相关的方法在场景管理器类中。试试他们然后看看你能得到什么。 光源衰减 当离光源越来越远的时候,光源定义了一个 setAttenuation 方法允许你控制定 义光怎样渐弱。添加一个函数来调用点光源设置衰减为不同的值,看看会怎样影 响光? SceneManager::setAmbientLight 实验一下这个方法 setAmbientLight,在 mSceneMgr 中。 视口背景色 在 createViewports 方法中改变默认的 ColourValue 值。在这里改变颜色也许并 不是合适的,但是可以让你了解一下如何改变她。 Camera::setFarClipDistance 在 createCamera 中我们设置的摄像机距离很近。添加一个函数调用 setFarClipDistance,设置参数为 500,分别设置模板阴影开启和关闭并移动摄 像机来观察 Ninja。注意到速度变慢没? 注意:你需要设置 mSceneMgr->setShadowUseInfiniteFarPlane(false),否则 你将得到一些奇怪的影子(看看这里) 平面 在本指南中我们并没有涉及太多关于平面的内容(她不是本文的重点)。在后续 章节中我们会重新提到她,但是现在你应该了解了解 createPlane 方法试着去用 用这些函数。 完整来源 如果有困难,可以看看源代码 文档:教程:基础教程:基础教程三 出自 Ogre3D 开放资源地带 跳转到: 导航, 搜索 目录 · 1 先决条件 · 2 简介 · 3 从这里开始 · 4 根对象和场景管理器的创建 o 4.1 根对象 o 4.2 场景管理器的创建 · 5 地形 o 5.1 在场景中添加地面 o 5.2 "terrain.cfg" 文件 o 5.3 照亮地面 · 6 天空 o 6.1 天空盒 o 6.2 天空穹 o 6.3 天空面 o 6.4 用哪个好? · 7 雾化效果 o 7.1 入门 o 7.2 雾化类型 o 7.3 雾与天空面 o 7.4 黑暗中的雾 先决条件 简介 在这篇教程里,我们将会一起探索 OGRE 中的天空,地面和雾化处理。通过这篇 教程,您应该明白天空盒(Skybox),天空穹(Skydome)和天空面(Skyplane)的用 法和区别。您还会了解不同种类的雾化效果,以及它们的使用方法。 从这里开始 和以往一样,我们将使用已经写好的代码作为模版。在您的编译器里创建一个 新的工程,然后把下面的代码添加进去: #include "ExampleApplication.h" class TutorialApplication : public ExampleApplication { protected: public: TutorialApplication() { } ~TutorialApplication() { } protected: void chooseSceneManager(void) { } void createScene(void) { } }; #if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32 #define WIN32_LEAN_AND_MEAN #include "windows.h" INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT ) #else int main(int argc, char **argv) #endif { // Create application object TutorialApplication app; try { app.go(); } catch( Exception& e ) { #if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32 MessageBox( NULL, e.getFullDescription().c_str(), "An exception has occured!", MB_OK | MB_ICONERROR | MB_TASKMODAL); #else fprintf(stderr, "An exception has occured: %s\n", e.getFullDescription().c_str()); #endif } return 0; } 假如您能够成功编译这段代码,您在运行的时候可以用 WASD 键移动,鼠标来转 镜头,ESC 键来退出程序。 根对象和场景管理器的创建 根对象 首先我们需要让 OGRE 创建一个地面。我们必须将场景管理器设为地面的场景管 理器,而不是 ExampleApplication 里默认的那个。将下面的代码添加到 chooseSceneManager 函数里: mSceneMgr = mRoot->createSceneManager(ST_EXTERIOR_CLOSE); 根对象是 OGRE 的核心对象,您可以在这里看到 OGRE 对象的 UML 图。到目前为 止, 您已经见到过除了 RenderSystem 以外大部分的对象了。在上面的代码中, 我们告诉根节点我们需要一个 ST_EXTERIOR_CLOSE 类型的场景管理器。紧接 着,根对象会要求场景枚举器查找您要的场景管理器然后将它返回。 等你的程序设置好了之后,你几乎不会再接触根对象了,你也不会直接调用场 景枚举器。 场景管理器的创建 我想先讲一下场景管理器的创建和管理,以免您在后面产生误会。场景管理器 不是单例,您想创建多少就创建多少。与场景节点/灯光等不同的是,您可以直 接使用 "new SceneManager()" 这种语句来直接创建它(而不必使用 Root 的 createSceneManager 方法,但你的确应该这么用)。你可以有多个场景管理 器,来同时装载多个独立的几何体和实体。你可以通过重建视口的方式在任何 时间交换这些场景管理器(这部分内容详见中级教程 4)或者使用多视口方法 同时显示多个场景管理器(这会在高级教程中有所涉及)。 我们为什么使用 createSceneManager 函数,而不是手动地创建我们的场景管理 器对象呢?当我们使用单一场景管理器时,Ogre 的插件系统给我们以巨大的灵 活性。在 SceneType 枚举类型中仅定义了几种场景类型。直到现在, ExampleApplication 例程还一直使用 ST_GENERIC 作为我们的场景管理器。你 也许认为这里用的是作为基础的 SceneManager 类,但是只要你没有修改 plugins.config 文件,那么你用的就不是它。如果你使用了 OctreeSceneManager 插件的话,OctreeSceneManager 会将自身注册为 ST_GENERIC 类型,并且覆盖作为基础的 SceneManager 类。 OctreeSceneManager 使用了拣选不可见对象的系统,因此它比标准的 SceneManager 要快)。如果你从 plugins.cfg 文件中去除了 OctreeSceneManager 插件,那么当你请求 ST_GENERIC 类型时,你才可能会使 用基础的 SceneManager,也许会根据你的插件设置使用更好的场景管理器,这 就是 Ogre 系统的优美所在。 迄今为止我们只用了 Root 对象最基本的一些方法来创建场景管理器。实际上, 我们可以使用一个字符串而不是 SceneType 枚举来请求一个场景管理器,Ogre 灵活的 SceneManager 工厂允许你你定义任意数量的场景管理器类型并在需要的 时候创建和销毁它们。例如我们使用了一个插件,允许你创建叫做 "FooSceneManager"类型的场景管理器,我们这样来创建一个: // do not add this to the project mSceneMgr = mRoot->createSceneManager("FooSceneManager"); 这会创建这样一个场景管理器并给它一个默认名称,虽然我在这个教程里不会 这么用,你始终应该使用 createSceneManager 的第二个参数来給场景管理器命 名。例如我们要创建两个不同名称的场景管理器可以这么做: // do not add this to the project mSceneMgr1 = mRoot->createSceneManager("FooSceneManager", "foo"); mSceneMgr2 = mRoot->createSceneManager("FooSceneManager", "bar"); 通过给场景管理器命名,我们就不再需要再保留指针来跟踪它们。Root 对象会 替我们搞定,之后你要使"foo"和"bar"场景管理器的时候,这样做就行了: // do not add this to the project SceneManager *foo = mRoot->getSceneManager("foo"); SceneManager *bar = mRoot->getSceneManager("bar"); 当你彻底用不再使用一个场景管理器时,使用 Root 的 destroySceneManager 来 删除它释放内存。 尽管我们不会通过教程来讲解,你同样可以通过继承 SceneManagerFactory 类 定义一个自己的 SceneManager 工厂。如果你创建了自己的场景管理器,或者想 在你的程序使用一个标准的场景管理器之前对它做一些修改(比如创建摄像 机,创建光照,加载几何体等等),这将会非常有用。 地形 在场景中添加地面 现在我们已经搞清楚了根对象和场景管理器,是时间来真正创建一个地面了 (当然是在 OGRE 里)。场景管理器的基类定义了一个叫做 setWorldGeometry 的方法,使派生类便于创建场景。当使用地面场景管理器 (TerrainSceneManager)时,它需要一个文件名来加载地面属性。在 createScene 函数中添加这行代码: mSceneMgr->setWorldGeometry( "terrain.cfg" ); 编译运行你的程序,稍微移动一下镜头就可以看到 OGRE 生成的地面了,简单 吧? "terrain.cfg" 文件 terrain.cfg 文件包含了许多生成地面的选项,和以往一样,我只会讲一些基 础的东西,更详细的内容可以在/*这里*/找到。需要注意的是,地面场景管理 器设计的时候包含了分页的功能,只不过还没有被实现。不过 OGRE 有一个插件 目前可以实现这个功能:Paging Scene Manager。 地面场景管理器使用高度图(Heightmap)来生成地面。你可以通过更改 Heightmap.image 的参数来改变高度图。你可以通过更改 WorldTexture 来更改 地面的帖图。你还可以通过更改 DetailTexture 来使地面看得更逼真。你可以 在/Media/materials/textures 里找到 terrain.cfg 默认的几个图片。 照亮地面 在上篇教程里我们刚学过如何使用灯光和阴影,不幸的是他们很难用在地面 上。使用已经处理过光照效果的帖图会比真的光照容易许多。在雾化处理那一 节,我们还会讨论如何使用“伪黑暗”。假如你想用真正的光照效果,你应该 考虑一下 Paging Scene Manager,他有很好的光照支持。 天空 OGRE 提供三种天空:天空盒,天空穹和天空面。我们会逐个学习他们,现在你 必须在 chooseSceneManager 函数中添加以下代码: ResourceGroupManager::getSingleton().initialiseAllResourceGroups(); 天空盒 天空盒实际上是一个包含了场景里所有对象的巨型立方体。耳闻不如眼见,将 下面代码添加到 createScene 里,自己体会什么是天空盒吧: mSceneMgr->setSkyBox( true, "Examples/SpaceSkyBox" ); 编译运行你的程序。怎么样,效果不错吧?(你可以更改高像素的贴图使它看起 来更逼真。) 当我们调用 setSkyBox 的时候我们可以设置几个有用的参数。第 一个代表是否启用天空盒。假如你什么时候想要取消天空盒你只须调用 mSceneMgr->setSkyBox( false, "" ); 第二个参数是天空盒使用的材质脚本。 setSkyBox 的第三个和第四个参数并不是十分重要。第三个参数设定了天空盒 与摄像机的距离,第四个参数决定了天空盒是在其他对象之前渲染还是其他对 象之后。我们来试试将第三个参数从默认的 5000 改为一个很小的值然后看看有 什么效果: mSceneMgr->setSkyBox( true, "Examples/SpaceSkyBox", 10 ); 什么都没改变!这是因为第四个参数的默认值是 true,也就是说天空盒会最先 渲染,其它的东西会渲染到天空盒之上,从而使天空盒看起来像是在背后。 (注意你不应该让第三个参数小于摄像机的近剪切面距离,否则他会不可 见!)其实天空盒并不应该最先渲染,因为你会渲染他的全部。假如你最后渲 染他,OGRE 只会渲染可见的部分,从而提高一定的运行速度。所以,我们来试 一下最后渲染天空盒: mSceneMgr->setSkyBox( true, "Examples/SpaceSkyBox", 5000, false ); 现在它看起来好像一样没有任何改变,但是天空盒的不可见的部分不会被渲 染。有一点要注意,当你把天空盒设置得很近时,部分的几何会被裁剪掉,试 一下这个: mSceneMgr->setSkyBox( true, "Examples/SpaceSkyBox", 100, false ); 看到了么?地面看上去穿过了天空。这肯定不是你想要的结果(假如你还正常 的话)。假如你要在你的程序中使用天空盒你必须决定如何使用它。当你最后 渲染天空盒是,程序的运行速度会提高一些,但是你必须小心它不要遮住你的 几何。总的来说,将第二个参数之后的所有东西都设为默认是最安全的。 天空穹 天空穹和天空盒很相像,你需要使用 setSkyDome 来创建天空穹。他会创建一个 包含了场景中所有对象的巨型立方体,最大的区别是贴图被用球体的方法投影 到立方体上。你看到的其实还是一个立方体,只不过它的贴图看上去像贴到了 一个球体上一样。这种天空有一个很大的缺陷,就是立方体的下面没有任何贴 图,所有你必须要有一个类似地面的东西来遮盖下面。 OGRE 的示例贴图将让你很明显地看到这个缺陷。删掉 setSkyBox 然后在 createScene 中添加下面代码: mSceneMgr->setSkyDome( true, "Examples/CloudySky", 5, 8 ); 当你运行时,将镜头移动到地面的中间,然后是镜头几乎和地面平行。按 R 键 切换到网格状态,你会发现你看到的仍然是一个立方体(没有底的),但是那 些云彩看起来却像覆盖在球体上一样。(注意云彩的变换是在 "Examples/CloudySky"脚本里定义的,并不是所有的天空穹都有这样的效 果。) 天空穹的前两个参数和天空盒一样,第三个参数是天空穹的弯度。OGRE 的 API 建议使用 2 到 65 之间的数值,降低第三个参数会有更好的距离感,增加第三个 参数会降低扭曲度从而达到更平滑的效果。试着将它设为 2 然后再设为 65 来观 察它们的区别,下面截图可以很好的展示天空的弯曲效果,这是当第三个参数 为 2 时的效果: http://www.idleengineer.net/images/beginner03_2.png 这是当第三个参数为 64 时的效果: http://www.idleengineer.net/images/beginner03_64.png 第四个参数是贴图重复的次数,你需要根据你贴图的大小来设置它。但是注意 这个参数是 Real 类型(浮点数)的而不是整形。第五第六个参数分别是距离和渲 染顺序,与天空盒的相同。 天空面 天空面与前两种天空有相当大的区别。我们用一个平面来替代正方体。(注意 下面所有的天空面的属性都是尽量靠地面中间)删掉 createScene 里所有天空 穹的代码。我们要先创建一个平面,然后是他朝下。setSkyPlane 与前两个函 数不同的是他没有距离参数。相应的,我们在 Plane 的 d 变量里设置它的距 离。 Plane plane; plane.d = 1000; plane.normal = Vector3::NEGATIVE_UNIT_Y; 现在我们定义了平面,接下来就可以创建天空面了。注意第四个参数是天空面 的大小(在这里是 1500x1500 个单位)第五个参数是他重复的次数: mSceneMgr->setSkyPlane( true, plane, "Examples/SpaceSkyPlane", 1500, 75 ); 编译运行你的程序。咱们创建的天空面有两个缺陷。第一个是由于贴图像素 低,重复的时候不好看。你自己画一个高像素边缘处理好的贴图就行了。最主 要的缺陷是当你注视水平线的时候,你能看到天空面的结尾。即使你有一个好 的贴图,他看起来并不会很逼真假如你能看到天空的结尾。天空面的这种用法 只能用在盆地或者四周都是墙的场景。不过他对显卡的要求会比天空盒和天空 穹要低。 幸运的是天空面的功能还不仅是这些。第六个参数跟天空盒和天空穹的渲染顺 序很相似。第七个参数允许你设置天空面的弯曲度,这样一来平面就变成弧形 的了。同时我们还需要设置 x 和 y 的线段数量(天空面是一个巨大的正方形, 但是假如我们想让他弯曲那么它就要变成许多小正方形)。第八个和第九个参 数就是 x 和 y 的线段数量了: mSceneMgr->setSkyPlane( true, plane, "Examples/SpaceSkyPlane", 1500, 50, true, 1.5f, 150, 150 ); 编译运行你的程序。现在这个天空面好看多了,你还可以用云彩的那个材质: mSceneMgr->setSkyPlane( true, plane, "Examples/CloudySky", 1500, 40, true, 1.5f, 150, 150 ); 编译运行你的程序。云彩的移动以及他重复的方法比天空穹还差一点,尤其是 当你接近地面的边缘往水平线的方向看去时。 还有一点,你可以通过 mSceneMgr->setSkyPlane( false, Plane(), "" ); 来 取消天空面。 用哪个好? 究竟用哪种天空完全取决于你的程序。假如你需要看到周围所有的东西,包括 负 y 的方向,那你可能只有天空盒这个选择了。假如你有一个地面或者类似地 板的东西可以遮住负 y 方向,那么使用天空穹会有一个更真实的效果。假如你 看不到海平线(比如一个峡谷,一个监狱,或者城堡中间的院子),天空面会 有一个很好的效果同时只有很低的 GPU 使用量。我们会在下一节中来解释用天 空面的最重要的一个原因,是因为它能和云雾很好地结合在一起。 这些只是建议。当你自己写程序的时候应该把这几种都试试然后看看哪种最好 再来决定最终用哪个。 雾化效果 入门 雾化效果在 OGRE 里非常简单。但是在你使用它之前要注意,当你使用地面管理 器(TerrainSceneManager)的时候,一定要在调用 setWorldGeometry 函数之前 调用 setFog 函数。(别的场景管理器里没关系的)。根据你调用这两个函数的顺 序,不同的顶点程序会被调用。(注意在 OGRE 1.0.0 里有一个 Bug,当你使用 正确顺序调用这些函数的时候,指数雾化不会被渲染。这个 Bug 在 1.0.1 的时 候就已经被修复了)。 在我们开始之前,清除掉 createScene 函数里除了 setWorldGeometry 之外的所 有内容。 你需要知道关于设置雾化效果的很重要的一点是他并不像你想象的那样在空的 地方创建“雾”的实体。实际上雾只是当你观看物体时的一个滤镜。当你面前 没有任何物体时,你是看不见雾的。事实上,你只会看到视口(viewport)的背 景色。所以想要使雾逼真,我们必须将视口的背景色设成雾的颜色。 雾分两种:线性的和指数的。线性雾“线性的”增浓,而指数雾“指数的”增 浓(每个距离单位雾的浓度都会比上一个单位增加得更多)。你自己看他们效 果的差距比这样讲更容易理解,所以我们来看几个例子。 雾化类型 我们将看到的第一种雾的类型是线性的,这也是最容易理解的类型。在调用 setWorldGeometry 后首先要设置视口的背景颜色。我们可以用 createViewport 方法(象用在上一个指南)一样,但是有时我们并不需要调用视口。代码如 下: ColourValue fadeColour(0.9, 0.9, 0.9); mWindow->getViewport(0)->setBackgroundColour(fadeColour); 如果视口不唯一的话,你可以用 getNumViewports 元函数来得到视口的数量然 后不断的操作她,但是通常这种情况是很少见的(目前为止我们知道我们仅用 了一个视口),我们可以直接得到视口。设置完背景色以后我们就可以创建雾 了。记住,这段代码必须出现在 setWorldGeometry 被调用之前。 mSceneMgr->setFog(FOG_LINEAR, fadeColour, 0.0, 50, 500); 第一个参数设置雾的类型(这里我们设的是线性的)。第二个参数是我们用到 的雾的颜色。第三个参数在线性雾里面是不用设置的。第四个和第五个参数是 设置雾慢慢变浓的范围。这里我们从 50 开始结束于 500。这就意味着在摄像机 的 0 到 50 个单位内是没有雾的。从 50 到 500 单位内雾慢慢线性变浓。在 500 单位以外全是雾了。编译运行这个应用程序。 另一种雾的类型是指数雾。和设置雾的起点和终点不一样的是,我们要设置雾 的密度(第四个和第五个参数不需要设置)。代码如下: mSceneMgr->setFog(FOG_EXP, fadeColour, 0.005); 编译运行这个应用程序。如果你用的是 DirectX 渲染器你会发现完全在雾的外 面,在 setWorldGeometry 之前调用 setFog 来修正它。OpenGL 渲染器会按照文 档里说的那样做,这能产生另一种生成雾的效果。这里还有一种更厉害的指数 雾函数(与前一种比较起来,离摄像机越远它的雾更浓)。注意,如果你使用 FOG_EXP2 会得到更加浓密的雾。将上面的代码替换成下面的: mSceneMgr->setFog(FOG_EXP2, fadeColour, 0.003); 再一次编译和运行这个程序。基本上 Ogre 提供的这三种雾函数是可以互换的。 你应该都实验一下这些函数,来看看哪一种在你的应用程序里效果更好。 雾与天空面 当你试图将雾与天空盒/天空穹一起使用时,会遇到些有趣的问题。因为天空盒 /天空穹是方形的,而雾效是以球形的方式工作的。清空 createScene 方法里的 内容,如果巧妙地调整天空穹和雾的参数,我们就能明显地发现这个问题。 ColourValue fadeColour(0.9, 0.9, 0.9); mSceneMgr->setFog(FOG_LINEAR, fadeColour, 0.0, 50, 515); mWindow->getViewport(0)->setBackgroundColour(fadeColour); mSceneMgr->setWorldGeometry("terrain.cfg"); mSceneMgr->setSkyDome(true, "Examples/CloudySky", 5, 8, 500); 编译运行程序。如果你转动摄像机,你会发现天空穹的某些部分会探出来(蓝 色部分从边缘探出来,而不是中央): http://www.idleengineer.net/images/beginner03_fogbox.png 这显然不是我们想要的。另一种选择就是使用天空面。在 createScene 里用以 下代码替换: ColourValue fadeColour(0.9, 0.9, 0.9); mSceneMgr->setFog(FOG_LINEAR, fadeColour, 0.0, 0, 130); mWindow->getViewport(0)->setBackgroundColour(fadeColour); Plane plane; plane.d = 100; plane.normal = Vector3::NEGATIVE_UNIT_Y; mSceneMgr->setWorldGeometry("terrain.cfg"); mSceneMgr->setSkyPlane(true, plane, "Examples/CloudySky", 500, 20, true, 0.5, 150, 150); 这样看起来就对了。如果我们往上看,就能看见天空(真实情况也是这样), 而不会滑稽地从别处探出来。不管你是否使用弯曲,这都能解决当你往地平线 上看的时候出现的不正常现象。 还有一种办法能让雾效不涉及整个天空,但需要对材质脚本进行修改。这已不 是本课的内容,为了今后参考,它就是材质里禁用雾效的那个参数。 黑暗中的雾 当你设置雾的时候,可能不想使用天空,因为雾已经浓到你无法看见天空了。 上面讲的雾效的一些技巧在某些场景非常有用。不把雾设置成明亮的颜色,而 是设成非常暗,看看有什么效果。(注意,我们把天空面设置成离摄像机只有 10 个单位距离,在设置雾之前): ColourValue fadeColour(0.1, 0.1, 0.1); mWindow->getViewport(0)->setBackgroundColour(fadeColour); mSceneMgr->setFog(FOG_LINEAR, fadeColour, 0.0, 10, 150); mSceneMgr->setWorldGeometry("terrain.cfg"); Plane plane; plane.d = 10; plane.normal = Vector3::NEGATIVE_UNIT_Y; mSceneMgr->setSkyPlane(true, plane, "Examples/SpaceSkyPlane", 100, 45, true, 0.5, 150, 150); 编译运行程序。这就是我们得到的: http://www.idleengineer.net/images/beginner03_darkness.png 不算太糟糕。当然,一旦你会使用光照,你就不必用这种技巧了。但这的确展 示了雾效的灵活性,以及用这个引擎你能做一些有趣的事情。如果你正写一个 第一人称游戏,使用黑色雾能够制造“失明”或“符咒”这样的特效。 原文 上一章节:基础教程二 下一章节:基础教程四 目录 取自 "http://ogre3d.cn/wiki/index.php?title=%E6%96%87%E6%A1%A3:%E6%95%99%E 7%A8%8B:%E5%9F%BA%E7%A1%80%E6%95%99%E7%A8%8B:%E5%9F%BA%E7%A1%80%E6%95 %99%E7%A8%8B%E4%B8%89" 查看 · 页面 · 讨论 · 源码 · 历史 个人工具 · 登录/创建账户 导航 · 首页 · 社区 · 当前事件 · 最近更改 · 随机页面 · 帮助 搜索 进入 搜索 Google 搜索 Enter your search terms Submit search form earch 工具箱 · 链入页面 · 链出更改 · 特殊页面 · 可打印版 · 永久链接 Google Adsense · 这页的最后修订在 2009 年 4 月 3 日 (星期五) 09:25。 · 本页面已经被浏览 1,743 次。 · 隐私政策 · 关于 Ogre3D 开放资源地带 · 沪 ICP 备 09049564 号 文档:教程:基础教程:基础教程四 出自 Ogre3D 开放资源地带 跳转到: 导航, 搜索 目录 · 1 先决条件 · 2 介绍 · 3 从这开始 · 4 帧监听 o 4.1 介绍 o 4.2 注册一个帧监听 · 5 建立场景 o 5.1 介绍 o 5.2 代码 · 6 帧监听指南 o 6.1 变量 o 6.2 构造函数 o 6.3 帧启动方法 先决条件 本教程假定你已经拥有了 c++程序设计的知识,并且已经安装和编译了一个 Ogre 的应用程序(如果你在设置你的应用程序中有困难,请参考 this guide 获得更详细的编译步骤)。这个教程同时也是建立在上一章基础上的,因此默 认你已经了解了上个教程的内容。 介绍 这一章我们将介绍 Ogre 中最有用的构造:帧监听(FrameListener)。在本指南 最后你将了解帧监听,怎样运用帧监听去实现一些要求每一帧更新的东西,怎 样去用 Ogre 的无缓冲输入系统。 代码你都可以在这篇指南中找到。当你看完这篇教程以后,你应该试着慢慢的 添加一些代码到你自己的工程里,然后看看结果。 从这开始 像上一个教程一样,我们将使用一个先前建立的代码作为我们出发的起点。在 编译器中创建一个工程,添加如下的源代码: #include "ExampleApplication.h" class TutorialFrameListener : public ExampleFrameListener { public: TutorialFrameListener(RenderWindow* win, Camera* cam, SceneManager *sceneMgr) : ExampleFrameListener(win, cam, false, false) { } bool frameStarted(const FrameEvent &evt) { return ExampleFrameListener::frameStarted(evt); } protected: bool mMouseDown; // Whether or not the left mouse button was down last frame Real mToggle; // The time left until next toggle Real mRotate; // The rotate constant Real mMove; // The movement constant SceneManager *mSceneMgr; // The current SceneManager SceneNode *mCamNode; // The SceneNode the camera is currently attached to }; class TutorialApplication : public ExampleApplication { public: TutorialApplication() { } ~TutorialApplication() { } protected: void createCamera(void) { } void createScene(void) { } void createFrameListener(void) { } }; #if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32 #define WIN32_LEAN_AND_MEAN #include "windows.h" INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT) #else int main(int argc, char **argv) #endif { // Create application object TutorialApplication app; try { app.go(); } catch(Exception& e) { #if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32 MessageBox(NULL, e.getFullDescription().c_str(), "An exception has occurred!", MB_OK | MB_ICONERROR | MB_TASKMODAL); #else fprintf(stderr, "An exception has occurred: %s\n", e.getFullDescription().c_str()); #endif } return 0; } 如果你是在 Windows 下使用 OgreSDK 的,请确定添加 “"[OgreSDK_DIRECTORY]\samples\include”目录到这个工程 (ExampleApplication.h 文件所在的位置)除了标准包含以外。如果使用的是 Ogre 的源代码包,这个文件应该在 “"[OgreSource_DIRECTORY]\Samples\Common\include”目录中。不要试着去 运行程序,因为我们还没有定义键盘的行为。如果你有问题,请查看这个 Wiki 页获得设置你编译器的信息,如果仍然有问题请试着去看看帮助栏。 我们将在这章中定义程序控制器。 帧监听 介绍 在前面的指南中当我们添加代码到创建场景方法时候,我们仅仅考虑我们可以 做什么。在 Ogre 中我们可以注册一个类去接收消息当一帧被渲染到屏幕之前和 之后。FrameListener 接口定义两个函数: bool frameStarted(const FrameEvent& evt) bool frameEnded(const FrameEvent& evt) Ogre 的主循环(Root::startRendering)类似这样: 1. Root object 调用 frameStarted 方法在所有已经注册的 FrameListeners 中。 2. Root object 渲染一帧。 3. Root object 调用 frameEnded 方法在所有已经注册的 FrameListeners 中。 这个循环直到有任意一个 FrameListener 的 frameStarted 或者 frameEnded 函 数返回 false 时终止。这些函数返回值的主要意思是“保持渲染”。如果你返 回否,这个程序将退出。FrameEvent 实体包含两个参数,但是只有 timeSinceLastFrame 在帧监听中是有用的。这个变量了解 frameStarted 或者 frameEnded 最近被激活时间。注意在 frameStarted 方法中, FrameEvent::timeSinceLastFrame 将包含 frameStarted 时间最近被激活的时 间(而不是最近被激活的 frameEnded 方法)。 一个重要的概念你不能决定那个 FrameListener 被调用的次序。如果你需要确 定 FrameListeners 被调用的准确顺序,你应该只注册一个 FrameListener,然 后用适当的顺序来调用所有的物体。 你应该也会注意到主循环事实上做了三件事,在 frameEnded 方法和 frameStarted 方法被调用中间什么都没有发生,你甚至可以换着用。你的代码 也可以随你放哪。你可以放到一个大的 frameStarted 或者 frameStarted 方法 中,也可以插到两者之间。 注册一个帧监听 现在上面的代码是可以编译通过的,但是因为我们没有考虑在 ExampleApplication 和 createCamera 中创建帧监听,因此如果你运行这个程 序你就无法结束她。在继续本指南其他内容之前我们将先解决这个问题。 找到 TutorialApplication::createCamera 方法添加如下代码: // create camera, but leave at default position mCamera = mSceneMgr->createCamera("PlayerCam"); mCamera->setNearClipDistance(5); 除了最基本的之外我们什么都没有做。之所以我们要用 ExampleApplication 中 的 createCamera 方法是因为 createCamera 方法可以用来移动相机和改变相机 位置,这里我们详细讨论这个。 因为 Root 类是要每帧更新的,他也了解 FrameListeners。我们首先要创建 TutorialFrameListener 的实例,然后把他注册到 Root 对象中。代码如下: // Create the FrameListener mFrameListener = new TutorialFrameListener(mWindow, mCamera, mSceneMgr); mRoot->addFrameListener(mFrameListener); mRoot 和 mFrameListener 变量是在 ExampleApplication 类中定义的。 addFrameListener 方法添加一个帧监听者,removeFrameListener 方法移除帧 监听者(也就是说 FrameListener 就不用在更新了)。注意 add|removeFrameListener 方法仅仅是得到一个指向 FrameListener 的指针 (也就是说 FrameListener 并没有一个名字你可以移除她)。这就意味着你需 要对每一个 FrameListener 持有一个指针,让你随后可以移除他们。 这个 ExampleFrameListener(我们的 TutorialFrameListener 是从它继承来 的),还提供了一个 showDebugOverlay(bool)方法,用来告诉 ExampleApplication 是否要在左下角显示帧率的提示框。我们会把它打开: // Show the frame stats overlay mFrameListener->showDebugOverlay(true); 确定你可以编译和运行这个应用程序再继续。 建立场景 介绍 在我们对代码的深入研究之前,我将简单介绍一下我将做什么,这样你可以了 解当我们创建和添加一些东西到场景中去的目的。 我们将把一个物体(一个忍者)放到屏幕中,并在场景中加上点光源。当你点击 鼠标左键,灯光会打开或关闭。按住鼠标右键,则开启“鼠标观察”模式(也 就是你用摄像机四处观望)。我们将在场景里放置场景节点,来让作用于不同 视口的摄像机附在上面。按下 1、2 键,以选择从哪一个视口来观看场景。 代码 找到 TutorialApplication::createScene 方法。首先我们需要把场景的环境光 设置的很低。我们想要场景中物体在灯光关闭情况下仍然可见,而且我们仍然 想做到在灯光开关时场景有所变化: mSceneMgr->setAmbientLight(ColourValue(0.25, 0.25, 0.25)); 现在把一个忍者加到屏幕中去: Entity *ent = mSceneMgr->createEntity("Ninja", "ninja.mesh"); SceneNode *node = mSceneMgr->getRootSceneNode()- >createChildSceneNode("NinjaNode"); node->attachObject(ent); 现在我们创建一个白色灯让后放到场景中,和忍者有一个相对小距离: Light *light = mSceneMgr->createLight("Light1"); light->setType(Light::LT_POINT); light->setPosition(Vector3(250, 150, 250)); light->setDiffuseColour(ColourValue::White); light->setSpecularColour(ColourValue::White); 现在需要创建场景节点供摄像机绑定: // Create the scene node node = mSceneMgr->getRootSceneNode()- >createChildSceneNode("CamNode1", Vector3(-400, 200, 400)); node->yaw(Degree(-45)); node->attachObject(mCamera); // create the second camera node node = mSceneMgr->getRootSceneNode()- >createChildSceneNode("CamNode2", Vector3(0, 200, 400)); 现在我们在 TutorialApplication 类里做得差不多了。再到 TutorialFrameListener 里去... 帧监听指南 变量 在我们走得更远之前,还是先介绍一下 TutorialFrameListener 类里的一些变 量: bool mMouseDown; // 鼠标左键是否在上一帧被按下 Real mToggle; // 直到下一次触发的时间 Real mRotate; // 滚动常量 Real mMove; // 移动常量 SceneManager *mSceneMgr; // 当前场景管理器 SceneNode *mCamNode; // 当前摄像机所附在的场景节点 mSceneMgr 拥有一个目前 SceneManager 的指针,mCamNode 拥有目前 SceneNode 并且绑定着摄像机。mRotate 和 mMove 是旋转和移动的变量。如果你想移动或 旋转的更快或更慢,改变这两个变量的大小就可以了。 另外两个变量 (mToggle 和 mMouseDown)控制我们的输入。我们将在本章中使用“非缓冲” (unbuffered)鼠标和键盘输入(“缓冲”(buffered)输入将是下一章的议题。这 意味着我们将在帧监听器查询鼠标和键盘状态时调用方法。 当我们试图用键盘来改变屏幕中物体的状态时,会遇到一些有趣的问题。如果 知道一个键正被按下,我们会去处理它,但到了下一帧怎么办?我们还会看见 它被按下,然后再重复地做操作吗?在一些情况(比如用方向键来移动),我 们是这样做的。然而,我们想通过“T”键来控制灯的开关时,在第一帧里按下 T 键,拨动了灯的开关,但在下一帧 T 键仍然被按着,所以灯又打开了...如此 返复,直到按键被释放。我们必须在帧与帧之间追踪这些按键状态,才能避免 这个问题。我将介绍两种不同的方法来解决它。 mMouseDown 用来追踪鼠标在上一帧里是否也被按下(所以如果 mMouseDown 为 真,在鼠标释放之前我们不会做同样的操作)。mToggle 指定了直到我们可以 执行下一个操作的时间。即,当一个按键被按下去了,在 mToggle 指明的这段 时间里不允许有其它动作发生。 构造函数 关于这个构造函数首先需要注意的是,我们要调用 ExampleFrameListener 的构 造函数: 首先我们要继承 ExampleFrameListener 的构造函数。 : ExampleFrameListener(win, cam, false, false) 一个需要注意的是,第三和第四个参数要置为 false。第三个参数指明是否使 用带缓冲的键盘输入,第四个参数指明是否使用带缓冲的鼠标输入(我们在本 课都不使用)。 在 TutorialFrameListener 的构造函数中, 我们把所有的变量设置为默认值: // 键盘和鼠标状态追踪 mMouseDown = false; mToggle = 0.0; // Populate the camera and scene manager containers mCamNode = cam->getParentSceneNode(); mSceneMgr = sceneMgr; // 设置旋转和移动速度 mRotate = 0.13; mMove = 250; 好了。mCamNode 变量被初始化成摄像机的任何当前父节点。 帧启动方法 现在,我们到了教程最核心的部分:在每一帧里执行动作。目前我们的 frameStarted 方法里有如下代码: return ExampleFrameListener::frameStarted(evt); 这块代码非常重要。ExampleFrameListener::frameStarted 方法定义了许多行 为(像所有的按键绑定、所有的摄像机移动等)。清空 TutorialFrameListener::frameStarted 方法。 开放输入系统(OIS)提供了三个主要的类来获得输入:Keyboard, Mouse, 和 Joystick 。在教程里,我们只有涉及如何使用 Keyboard 和 Mouse 对象。倘若 你对在 Ogre 里使用摇杆感兴趣,请查阅 Joystick 类。 我们使用无缓冲输入时,要做的第一件事情就是获取当前鼠标键盘的状态。为 了达到目的,我们调用 Mouse 和 Keyboard 对象的 capture 方法。示例框架已经 帮我们创建了这些对象,分别在 mMouse 和 mKeyboard 变量里。将以下代码添加 到目前还是空的 TutorialFrameListener::frameStarted 成员方法里。 mMouse->capture(); mKeyboard->capture(); 接下来,我们要保证当 Escape 键被按下时程序退出。我们通过调用 InputReader 的 isKeyDown 方法并指定一个 KeyCode,来检查一个按钮是否被按 下。当 Escape 键被按下时,我们只要返回 false 到程序末尾。 if(mKeyboard->isKeyDown(OIS::KC_ESCAPE)) return false; 为了能继续渲染,frameStarted 方法必须返回一个正的布尔值。我们将在方法 最后加上这么一行。 return true; 所有我们将要讨论的代码都在最后的这一行"return true"之上。 我们使用 FrameListener 来做的第一个事情就是,让鼠标左键来控制灯的开 关。通过调用 InputReader 的 getMouseButton 方法,并传入我们要查询的按 钮,我们能够检查这个按钮是否被按下。通常 0 是鼠标左键,1 是右键,2 是中 键。在某些系统里 1 是中键、2 是右键。如果按键不能很好的工作,可尝试这 样设置: bool currMouse = mMouse- >getMouseState().buttonDown(OIS::MB_Left); 如果鼠标被按下,currMouse 变量设为 true。现在我们拨动灯开关,依赖于 currMouse 是否为 true,同时在上一帧中鼠标没有被按下(因为我们只想每次按 下鼠标时,只让灯开关被拨动一次)。同时注意 Light 类里的 setVisible 方法 决定了这个对象实际上是否发光: if (currMouse && ! mMouseDown) { Light *light = mSceneMgr->getLight("Light1"); light->setVisible(! light->isVisible()); } // if 现在,我们把 mMouseDown 的值设置成与 currMouse 变量相同。下一帧里,它会 告诉我们上次是否按下了鼠标按键。 mMouseDown = currMouse; 编译运行程序。现在可以左击鼠标控制灯的开关!注意,因为我们不再调用 ExampleFrameListener 的 frameStarted 方法,我们不能转动摄像头(目 前)。 这种保存上一次鼠标状态的方法非常好用,因为我们知道我们已经对为鼠标状 态做了动作。 这样做的缺点就是你要为每一个被绑定动作的按键设置一个布尔变量。一种能 避免这种情况的方法是,跟踪上一次点击任何按键的时刻,然后只允许经过了 一段时间之后才触发事件。我们用 mToggle 变量来跟踪。如果 mToggle 大于 0,我们就不执行任何动作,如果 mToggle 小于 0,我们才执行动作。我们将用 这个方法来进行下面两个按键的绑定。 首先我们要做的是拿 mToggle 变量减去从上一帧到现在所经历的时间。 mToggle -= evt.timeSinceLastFrame; 现在我们更新了 mToggle,我们能操作它了。接下来我们将“1”键绑定为摄像 机附在第一个场景节点上。在这之前,我们检查 mToggle 变量以保证它是小于 0 的: if ((mToggle < 0.0f ) && mKeyboard->isKeyDown(OIS::KC_1)) { 现在我们需要设置 mToggle 变量,使得在下一个动作发生时至少相隔一秒钟: mToggle = 0.5f; 接下来,我们将摄像机从当前附属的节点取下来,然后让 mCamNode 变量含有 "CamNode1"节点,再将摄像机放上去。 mCamera->getParentSceneNode()->detachObject(mCamera); mCamNode = mSceneMgr->getSceneNode("CamNode1"); mCamNode->attachObject(mCamera); } 当按下 2 键时,对于 CamNode2 我们也是这么干的。除了把 1 换成 2,if 改成 else if,其它代码都是相同的: else if ((mToggle < 0.0f) && mKeyboard->isKeyDown(OIS::KC_2)) { mToggle = 0.5f; mCamera->getParentSceneNode()->detachObject(mCamera); mCamNode = mSceneMgr->getSceneNode("CamNode2"); mCamNode->attachObject(mCamera); } 编译并运行。我们通过按 1、2 键,能够转换摄像机的视口。 我们的下一个目标就是,当用户按下方向键或 W、A、S、D 键时,进行 mCamNode 的平移。 与上面的代码不同,我们不必追踪上一次移动摄像机的时刻,因为在每一帧按 键被按下时我们都要进行平移。这样一来我们的代码会相对简单,首先我们来 创建一个 Vector3 用于保存平移的方位: Vector3 transVector = Vector3::ZERO; 好的,当按下 W 键或者上箭头时,我们希望前后移动(也就是 Z 轴,记住 Z 轴 负的方向是指向屏幕里面的): if (mKeyboard->isKeyDown(OIS::KC_UP) || mKeyboard- >isKeyDown(OIS::KC_W)) transVector.z -= mMove; 对于 S 键和下箭头键,我们基本上也是这么做的,只不过方向是往正 Z 轴: if (mKeyboard->isKeyDown(OIS::KC_DOWN) || mKeyboard- >isKeyDown(OIS::KC_S)) transVector.z += mMove; 对于左右方向移动的,我们是在 X 的正负方向进行的: if (mKeyboard->isKeyDown(OIS::KC_LEFT) || mKeyboard- >isKeyDown(OIS::KC_A)) transVector.x -= mMove; if (mKeyboard->isKeyDown(OIS::KC_RIGHT) || mKeyboard- >isKeyDown(OIS::KC_D)) transVector.x += mMove; 最后,我们还想沿着 Y 轴进行上下移动。我个人喜欢用 E 键或者 PageDown 键进 行向下移动,用 Q 键或者 PageUp 键进行向上移动: if (mKeyboard->isKeyDown(OIS::KC_PGUP) || mKeyboard- >isKeyDown(OIS::KC_Q)) transVector.y += mMove; if (mKeyboard->isKeyDown(OIS::KC_PGDOWN) || mKeyboard- >isKeyDown(OIS::KC_E)) transVector.y -= mMove; 好了,我们的 transVector 变量保存了作用于摄像机场景节点的平移。这样做 的第一个缺点是如果你旋转了场景节点,再作平移的时候我们的 x、y、z 坐标 就不对了。为了修正它,我们必须把作用在场景节点的旋转,也作用于我们的 平移。这实际上比听起来更简单。 为了表示旋转,Ogre 不是像一些图形引擎那样使用变换矩阵,而是使用四元 组。四元组的数学意义是一个较难理解的四维线性代数。幸好,你不必去了解 它的数字知识,了解如何使用就行了。非常简单,用四元组旋转一个向量只要 将它们两个相乘即可。现在,我们希望将所有作用于场景节点的旋转,也作用 在平移向量上。通过调用 SceneNode::getOrientation(),我们能得到这些旋 转的四元组表示,然后用乘法让它作用在平移节点。 还有一个缺陷要引起注意的是,我们必须根据从上一帧到现在的时间,来对平 移的大小进行缩放。否则,你的移动速度取决于应用程序的帧率。这确实不是 我们想要的。这里有一个函数供我们来调用,可以避免平移摄像机时带来的问 题: mCamNode- >translate(transVector*evt.timeSinceLastFrame,Node::TS_LOCAL); 好了,我们来介绍一些新的东西。当你对一个节点进行平移,或者是绕某个轴 进行旋转,你都能指定使用哪一个“变换空间”来移动它。一般你移动一个对 象时,不需要指定这个参数。它默认是 TS_PARENT,意思是这个对象使用的是 父节点所在的变换空间。在这里,父节点是场景的根节点。当我们按下 W 键时 (向前移动),我们走向 Z 轴负的方向。如果我们不在代码前面指明 TS_LOCAL,摄像机都会向全局负 Z 轴移动。然而,既然我们希望按下 W 键时摄 像机往前走,我们就需要它往节点所面朝的方向走。所以,我们使用“本地” 变换空间。 还有另一种方式我们也能实现(尽管不是怎么直接)。我们获取节点的朝向, 一个四元组,用方向向量乘以它,能得到相同的结果。这完全是合法的: // Do not add this to the program mCamNode->translate(mCamNode- >getOrientation()*transVector*evt.timeSinceLastFrame, Node::TS_WORLD); 这同样也在本地空间里进行移动。在这里,实际上没这样做的必要。Ogre 定义 了三种变换空间:TS_LOCAL, TS_PARENT, 和 TS_WORLD。也许存在其它的场 合,你需要使用另外的向量空间来进行平移或旋转。若真是这样,你可以像上 面的代码那样做。找到一个表示向量空间的四元组(或者你要操作的物体的朝 向),用平移向量去乘它,得到一个正确的平移向量,然后用它在 TS_WORLD 空 间里移动。目前应该不会遇到这样的情况,我们后面的教程也不会涉及这些内 容。 好了,我们有了键盘控制的移动。我们还想实现一个鼠标效果,来控制我们观 察的方向,但这只有在鼠标右键被按住的情况下。为了达到目的,我们首先要 检查右键是否被按下去: if (mMouse->getMouseState().buttonDown(OIS::MB_Right)) { 如果是,我们根据从上一帧开始鼠标移动的距离来控制摄像机的俯仰偏斜。为 了达到目的,我们取得 X、Y 的相对改变,传到 pitch 和 yaw 函数里去: mCamNode->yaw(Degree(-mRotate * mMouse- >getMouseState().X.rel), Node::TS_WORLD); mCamNode->pitch(Degree(-mRotate * mMouse- >getMouseState().Y.rel), Node::TS_LOCAL); } 注意,我们使用 TS_WORLD 向量坐标来进行偏斜(如果不指明的话,旋转函数总 是用 TS_LOCAL 作为默认值)。我们要保证物体随意的俯仰不影响它的偏斜,我 们希望总是绕着固定的轴进行偏斜转动。如果我们把 yaw 设置成 TS_LOCAL,我 们就能得到这样的: http://www.idleengineer.net/images/beginner04_rot.png 编译程序并运行。 本课不是关于旋转和四元组的完整教程(那样的话会构成另一系列的教程)。 下一课里,我们将使用带缓冲的输入,而不用在每一帧里都检查按键是否被按 下。 原文 上一章节:基础教程三 下一章节:基础教程五 目录 取自 "http://ogre3d.cn/wiki/index.php?title=%E6%96%87%E6%A1%A3:%E6%95%99%E 7%A8%8B:%E5%9F%BA%E7%A1%80%E6%95%99%E7%A8%8B:%E5%9F%BA%E7%A1%80%E6%95 %99%E7%A8%8B%E5%9B%9B" 查看 · 页面 · 讨论 · 源码 · 历史 个人工具 · 登录/创建账户 导航 · 首页 · 社区 · 当前事件 · 最近更改 · 随机页面 · 帮助 搜索 进入 搜索 Google 搜索 Enter your search terms Submit search form earch 工具箱 · 链入页面 · 链出更改 · 特殊页面 · 可打印版 · 永久链接 Google Adsense · 这页的最后修订在 2009 年 5 月 14 日 (星期四) 05:33。 · 本页面已经被浏览 1,724 次。 · 隐私政策 · 关于 Ogre3D 开放资源地带 · 沪 ICP 备 09049564 号 文档:教程:基础教程:基础教程五 出自 Ogre3D 开放资源地带 跳转到: 导航, 搜索 目录 · 1 先决条件 · 2 介绍 · 3 从这开始 · 4 简单的缓冲输入 o 4.1 介绍 o 4.2 键盘监听界面 o 4.3 鼠标监听界面 · 5 代码 o 5.1 TutorialFrameListener 的构造函数 o 5.2 变量 o 5.3 TutorialFrameListener 构造函数 o 5.4 键盘绑定 o 5.5 鼠标绑定 · 6 其他输入系统 先决条件 本教程假定你已经拥有了 c++程序设计的知识,并且已经安装和编译了一个 Ogre 的应用程序(如果你在设置你的应用程序中有困难,请参考 this guide 获得更详细的编译步骤)。这个教程同时也是建立在上一章基础上的,因此默 认你已经了解了上个教程的内容。 介绍 在这一课里,你将学会 OIS 的带缓冲的输入, 这不同于上节课中无缓冲的输入。 而且在这节课中,当鼠标键盘的事件发生时,我们会立即处理它,不会像上节 课那样每一帧只处理一次。请注意这里只是对缓冲输入的一个简单介绍,而不 是完整的如何使用 OIS 的教程。若想了解更多内容,请查阅相关的 OIS 使用教 程。 你可以找到这节课的源代码。在你学习本课的时候,你应该逐个地将这些代码 添加到你自己的工程里去,然后构建并观察结果。 从这开始 本课是基于上一课的,但我们将改变输入的方式。既然功能基本上是相同的, 我们就使用上次的 TutorialApplication 类,但我们会从 TutorialFrameListener 开始。用你喜欢的编译器创建一个工程,再添加一个 包含如下代码的源文件: #include "ExampleApplication.h" class TutorialFrameListener : public ExampleFrameListener, public OIS::MouseListener, public OIS::KeyListener { public: TutorialFrameListener(RenderWindow* win, Camera* cam, SceneManager *sceneMgr) : ExampleFrameListener(win, cam, true, true) { } bool frameStarted(const FrameEvent &evt) { if(mMouse) mMouse->capture(); if(mKeyboard) mKeyboard->capture(); return mContinue; } // MouseListener bool mouseMoved(const OIS::MouseEvent &e) { return true; } bool mousePressed(const OIS::MouseEvent &e, OIS::MouseButtonID id) { return true; } bool mouseReleased(const OIS::MouseEvent &e, OIS::MouseButtonID id) { return true; } // KeyListener bool keyPressed(const OIS::KeyEvent &e) { return true; } bool keyReleased(const OIS::KeyEvent &e) { return true; } protected: Real mRotate; // The rotate constant Real mMove; // The movement constant SceneManager *mSceneMgr; // The current SceneManager SceneNode *mCamNode; // The SceneNode the camera is currently attached to bool mContinue; // Whether to continue rendering or not Vector3 mDirection; // Value to move in the correct direction }; class TutorialApplication : public ExampleApplication { public: void createCamera(void) { // create camera, but leave at default position mCamera = mSceneMgr->createCamera("PlayerCam"); mCamera->setNearClipDistance(5); } void createScene(void) { mSceneMgr->setAmbientLight(ColourValue(0.25, 0.25, 0.25)); // add the ninja Entity *ent = mSceneMgr->createEntity("Ninja", "ninja.mesh"); SceneNode *node = mSceneMgr->getRootSceneNode()- >createChildSceneNode("NinjaNode"); node->attachObject(ent); // create the light Light *light = mSceneMgr->createLight("Light1"); light->setType(Light::LT_POINT); light->setPosition(Vector3(250, 150, 250)); light->setDiffuseColour(ColourValue::White); light->setSpecularColour(ColourValue::White); // Create the scene node node = mSceneMgr->getRootSceneNode()- >createChildSceneNode("CamNode1", Vector3(-400, 200, 400)); // Make it look towards the ninja node->yaw(Degree(-45)); // Create the pitch node node = node->createChildSceneNode("PitchNode1"); node->attachObject(mCamera); // create the second camera node/pitch node node = mSceneMgr->getRootSceneNode()- >createChildSceneNode("CamNode2", Vector3(0, 200, 400)); node = node->createChildSceneNode("PitchNode2"); } void createFrameListener(void) { // Create the FrameListener mFrameListener = new TutorialFrameListener(mWindow, mCamera, mSceneMgr); mRoot->addFrameListener(mFrameListener); // Show the frame stats overlay mFrameListener->showDebugOverlay(true); } }; #if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32 #define WIN32_LEAN_AND_MEAN #include "windows.h" INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT) #else int main(int argc, char **argv) #endif { // Create application object TutorialApplication app; try { app.go(); } catch(Exception& e) { #if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32 MessageBox(NULL, e.getFullDescription().c_str(), "An exception has occurred!", MB_OK | MB_ICONERROR | MB_TASKMODAL); #else fprintf(stderr, "An exception has occurred: %s\n", e.getFullDescription().c_str()); #endif } return 0; } 如果你是在 Windows 下使用 OgreSDK 的,请确定添加 “"[OgreSDK_DIRECTORY]\samples\include”目录到这个工程 (ExampleApplication.h 文件所在的位置)除了标准包含以外。如果使用的是 Ogre 的源代码包,这个文件应该在 “"[OgreSource_DIRECTORY]\Samples\Common\include”目录中。不要试着去 运行程序,因为我们还没有定义键盘的行为。如果你有问题,请查看这个 Wiki 页获得设置你编译器的信息,如果仍然有问题请试着去看看帮助栏。 注意:如果你没有把你的工程设置成使用 Ansi C++ ,并收到关于 MessageBox 的错误信息,你可能需要把对 MessageBox 的引用改成 MessageBoxA。 程序的控制与上一课的相同。 简单的缓冲输入 介绍 在上一次课里,我们使用的是无缓冲的输入,也就是说,在每一帧里我们查询 OIS::Keyboard 和 OIS::Mouse 实例的状态,以判断它们是否被按下。而缓冲输 入使用了一个 listener 接口,以便在事件发生时通知你的程序。比如,当一个 键被按下时,会触发一个 KeyListener::keyPressed 事件,而当这个键被释放 (不再按下)时,KeyListener::keyReleased 事件被触发给所有已注册的 KeyListener 类。这些能用在追踪按键的时间,或判断按键在上一帧中是否没 有被按下。 通过 OIS::JoystickListener 接口,OIS 也支持无缓冲的操纵杆事件,但在本 课我们不会涉及到。 关于 OIS 的监听系统有一点要注意的是,对于每一个 Keyboard,Mouse,Joystick 对象只能有一个监听器。这样是为了简单(也为了 速度)。多次调用 setEventCallback 函数(后面会讲到)的结果是只有最后一 次注册的监听器才得到事件消息。如果你有多个对象需要获得 Key,Mouse,或 Joystick 事件,你只有自己写一个消息分发。还有,千万记得在 frameStarted 方法里调用 Keyboard::capture 和 Mouse::capture。OIS 不会使用线程(或其 它玩意儿)来确定键盘鼠标的状态,所以你必须指明什么时候去获取输入。 键盘监听界面 OIS 的 KeyListener 接口提供了两个纯虚函数。第一个是 keyPressed 函数(每 次按下某个键时调用它),还一个是 keyReleased(每次离开某个键时调用 它)。传入这些函数的参数是一个 KeyEvent,它包含被按下/释放的按键的键 码。 鼠标监听界面 MouseListener 接口比 KeyListener 接口要稍微复杂一些。它包含查看何时鼠 标键被按下/释放的函数: MouseListener::mousePressed 和 MouseListener::mouseReleased. 它还包含一个 mouseMoved 函数,当鼠标移动 时调用它。这些函数都接收一个 MouseEvent 对象,在"state"变量里保存着当 前鼠标的状态。 需要注意的是,MouseState 对象即包含了鼠标移动的相对 XY 坐标(即,从上 一次调用 MouseListener::mouseMoved 开始,它所移动的距离),还包含了绝 对 XY 坐标(即,屏幕上的准确位置)。 代码 TutorialFrameListener 的构造函数 在我们开始修改 TutorialFrameListener 之前,请注意我们对 TutorialFrameListener 类做了两处大的改变: class TutorialFrameListener : public ExampleFrameListener, public OIS::MouseListener, public OIS::KeyListener 我们继承了 OIS 的 MouseListener 和 KeyListener 类,这样我们才能从它们那里接收事件。注 意,OIS 的 MouseListener 处理鼠标按钮事件,也处理鼠标移动事件。 同样,ExampleFrameListener 的构造函数也有变化: : ExampleFrameListener(win, cam, true, true) 这个"true, true"参数指明了我们将要使用带缓冲的键盘鼠标输入。我们接下 面会深入介绍如何手动地设置 OIS,以及 Ogre 的其它部分。 变量 上一课的变量在这里有些改变。我移除了 mToggle 和 mMouseDown 变量(再也用 不到了)。我还添加了一些其它的: Real mRotate; // 旋转常量 Real mMove; // 运动常量 SceneManager *mSceneMgr; // 当前的场景管理器 SceneNode *mCamNode; // 当前摄像机附着的场景节点 bool mContinue; // 是否要继续渲染 Vector3 mDirection; // 指向正确的移动方向 mRotate, mMove, mSceneMgr, 以及 mCamNode 与上一课的相同(虽然我们会改 变 mRotate 的值,因为它有不同的用处)。 mContinue 变量是 frameStarted 方法的返回值。当 mContinue 为 false 的时候,程序退出。mDirection 变量指 定了在每一个帧里我们如何移动摄像机节点。 TutorialFrameListener 构造函数 在构造函数里,我们像在上一课那样初始化一些变量,并把 mContinue 设成 true。添加如下代码到 TutorialFrameListener 的构造函数里: // Populate the camera and scene manager containers mCamNode = cam->getParentSceneNode(); mSceneMgr = sceneMgr; // 设置旋转和移动速度 mRotate = 0.13; mMove = 250; // 继续渲染 mContinue = true; 在 ExampleFrameListener 的构造函数里已经取得了 OIS 的 mMouse 和 mKeyboard 对象。我们调用这些输入对象的 setEventCallback 方法,把 TutorialFrameListener 注册成一个监听器。 mMouse->setEventCallback(this); mKeyboard->setEventCallback(this); 最后,我们还要把 mDirection 初始化成零向量(因为我们最开始不需要它 动): mDirection = Vector3::ZERO; 键盘绑定 在我们深入之前,我们应该设置 Escape 键用来退出程序。找到 TutorialFrameListener::keyPressed 方法,每当键盘上一个键被按下时,都 会调用这个方法并传入一个 KeyEvent 对象。我们能够通过这个对象的"key"变 量来获取按键的键码(KC_*)。基于这个值,我们构造一个 switch,为绑定所有 程序里用到的按钮。 bool keyPressed(const OIS::KeyEvent &e) { switch (e.key) { case OIS::KC_ESCAPE: mContinue = false; break; } return true; } 我们继续之前请保证以上都能编译和运行。 我们需要在 switch 语句里为其它按钮做绑定。首先我们要让用户按 1、2 键进 行视口的切换。 代码基本上与上节课相同(需要包括进 switch 语句),除了不需要处理 mToggle 变量: case OIS::KC_1: mCamera->getParentSceneNode()->detachObject(mCamera); mCamNode = mSceneMgr->getSceneNode("CamNode1"); mCamNode->attachObject(mCamera); break; case OIS::KC_2: mCamera->getParentSceneNode()->detachObject(mCamera); mCamNode = mSceneMgr->getSceneNode("CamNode2"); mCamNode->attachObject(mCamera); break; 瞧!这比用临时变量来保存按键时间要干净多了。 接下来我们要添加键盘移动。每次用户按下移动按键,我们都要朝正确的方向 加上或者减去 mMove: case OIS::KC_UP: case OIS::KC_W: mDirection.z -= mMove; break; case OIS::KC_DOWN: case OIS::KC_S: mDirection.z += mMove; break; case OIS::KC_LEFT: case OIS::KC_A: mDirection.x -= mMove; break; case OIS::KC_RIGHT: case OIS::KC_D: mDirection.x += mMove; break; case OIS::KC_PGDOWN: case OIS::KC_E: mDirection.y -= mMove; break; case OIS::KC_PGUP: case OIS::KC_Q: mDirection.y += mMove; break; 当按键被释放时,我们要立即取消 mDirection 向量上的移动。找到 keyReleased 方法,添加如下代码: switch (e.key) { case OIS::KC_UP: case OIS::KC_W: mDirection.z += mMove; break; case OIS::KC_DOWN: case OIS::KC_S: mDirection.z -= mMove; break; case OIS::KC_LEFT: case OIS::KC_A: mDirection.x += mMove; break; case OIS::KC_RIGHT: case OIS::KC_D: mDirection.x -= mMove; break; case OIS::KC_PGDOWN: case OIS::KC_E: mDirection.y += mMove; break; case OIS::KC_PGUP: case OIS::KC_Q: mDirection.y -= mMove; break; } // switch return true; 好了,我们能根据按键输入对 mDirection 进行更新了,我们还需要让它真正移 动起来。下面的代码与上节课是一样的,添加到 frameStarted 函数里: mCamNode->translate(mDirection * evt.timeSinceLastFrame, Node::TS_LOCAL); 编译并运行程序。我们现在已经有了带缓冲的输入来控制运动! 鼠标绑定 现在我们已经完成了键盘绑定,接下来轮到鼠标了。我们从点击鼠标左键来控 制灯的开关开始。找到 mousePressed 函数并看看它的参数。用 OIS,我们可以 访问 MouseEvent 和 MouseButtonID。我们用 MouseButtonID 作为 switch 条 件,来确定按下的是哪个按钮。用下面的代码替换掉 mousePressed 函数里的: Light *light = mSceneMgr->getLight("Light1"); switch (id) { case OIS::MB_Left: light->setVisible(! light->isVisible()); break; } return true; 编译并运行。 噢耶! 成功了,剩下来的事情就是绑定鼠标右键来进入鼠标观察 模式。每当鼠标移动时我们都检查右键是否按下。如果是,我们基于相对运动 来转动摄像机。通过传入函数的 MouseEvent 对象,我们能获取相对运动。它包 含一个"state"变量,里面有鼠标的状态(是关于鼠标的详细信息)。 MouseState::buttonDown 告诉我们是否一个特定的按钮被按下,而“X”和 “Y”变量告诉我们鼠标的相对运动。找到 mouseMoved 方法,用以下代码替换 掉原来的: if (e.state.buttonDown(OIS::MB_Right)) { mCamNode->yaw(Degree(-mRotate * e.state.X.rel), Node::TS_WORLD); mCamNode->pitch(Degree(-mRotate * e.state.Y.rel), Node::TS_LOCAL); } return true; 编译并运行程序。当鼠标右键被按下时,摄像机就以自由视角的模式工作。 其他输入系统 OIS 是非常棒的,应该满足你的应用程序的大部分需求。话说回来,你也有其 它的选择。有的窗口系统像 wxWidgets 也许正是你想要的,人们已经成功地把 它整合进了 Ogre。如果你不介意你的应用程序是特定平台的,你也可以使用标 准的 Windows 消息系统,或者从 Linux GUI 工具集中选择一个。 你也可以试一试 SDL,它是一个跨平台的带窗口输入系统,并支持摇杆/手柄输 入。我不能指导你用 Ogre 如何设置 wx/gtk/qt 这些(因为我自己也没搞过-_- ),但我有许多将 SDL 的摇杆/手柄输入整合进 Ogre 的成功经验。为了使 SDL 的操纵杆系统生效,通过调用 SDL_Init 和 SDL_Quit,把你的应用程序包裹起 来(放在你的程序 main 函数里,或放在你的应用对象里): SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_NOPARACHUTE); SDL_JoystickEventState(SDL_ENABLE); app.go(); SDL_Quit(); 为了设置 Joystick,调用 SDL_JoystickOpen 并传入操纵杆编号(你能通过 0、 1、2...来指定多个操纵杆): SDL_Joystick* mJoystick; mJoystick = SDL_JoystickOpen(0); if ( mJoystick == NULL ) ; // error handling 如果 JoystickOpen 返回 NULL,表示打开 joystick 有问题。这基本上意味着你 请求的摇杆不存在。使用 SDL_NumJoysticks 来确定连接在系统上的摇杆数量。 当你结束之后,需要关闭摇杆: SDL_JoystickClose(mJoystick); 为了使用摇杆,调用 JoystickGetButton 和 JoystickGetAxis。我个人使用的 是 PS2 的手柄,所以我有四个轴向和 12 个按钮来玩。这是我的操纵代码: SDL_JoystickUpdate(); mTrans.z += evt.timeSinceLastFrame * mMoveAmount * SDL_JoystickGetAxis(mJoystick, 1) / 32767; mTrans.x += evt.timeSinceLastFrame * mMoveAmount * SDL_JoystickGetAxis(mJoystick, 0) / 32767; xRot -= evt.timeSinceLastFrame * mRotAmount * SDL_JoystickGetAxis(mJoystick, 3) / 32767; yRot -= evt.timeSinceLastFrame * mRotAmount * SDL_JoystickGetAxis(mJoystick, 2) / 32767; mTrans 在后面要传入到摄像机的 SceneNode::translate 方法,xRot 传入到 SceneNode::yaw,而 yRot 传入到 SceneNode::pitch。注意, SDL_JoystickGetAxis 返回一个从-32767 到 32767 的值,所以我得把它缩小到- 1 到 1 的范围。这应该可以让你开始使用 SDL 摇杆输入了。若你想查看更多关 于这个领域的信息,SDL joystick 的头文件是最好的文档。 如果你想要认真地在你的程序时使用 SDL,你应该查阅标准 SDL 文档。 原文 上一章节:基础教程四 下一章节:基础教程六 目录 取自 "http://ogre3d.cn/wiki/index.php?title=%E6%96%87%E6%A1%A3:%E6%95%99%E 7%A8%8B:%E5%9F%BA%E7%A1%80%E6%95%99%E7%A8%8B:%E5%9F%BA%E7%A1%80%E6%95 %99%E7%A8%8B%E4%BA%94" 查看 · 页面 · 讨论 · 源码 · 历史 个人工具 · 登录/创建账户 导航 · 首页 · 社区 · 当前事件 · 最近更改 · 随机页面 · 帮助 搜索 进入 搜索 Google 搜索 Enter your search terms Submit search form earch 工具箱 · 链入页面 · 链出更改 · 特殊页面 · 可打印版 · 永久链接 Google Adsense · 这页的最后修订在 2009 年 5 月 14 日 (星期四) 07:52。 · 本页面已经被浏览 1,344 次。 · 隐私政策 · 关于 Ogre3D 开放资源地带 · 沪 ICP 备 09049564 号 文档:教程:基础教程:基础教程六 出自 Ogre3D 开放资源地带 跳转到: 导航, 搜索 目录 · 1 必备知识 · 2 工程设置 · 3 介绍 · 4 开始 · 5 CEGUI 是如何工作的 · 6 添加退出按钮 · 7 响应事件 · 8 加载设置(Layout) · 9 尝试 · 10 如何在两个 GUI(用户界面)之间转换(使用透明度) · 11 结论 必备知识 本教程是在假设你已经拥有 c++编程基础并能够成功建立和编译 Ogre 程序(如 果你设置程序方面还存在问题,请参阅 SettingUpAnApplication 来获取详细信 息)。本教程建立在之前的初学者教程的基础上,并且假设你已经学习了它 们。 工程设置 下面的适用于下载源代码的用户: 添加 include 文件夹: $(OGRE_HOME)\Dependencies\include, $(OGRE_HOME)\Dependencies\include\CEGUI 添加 lib 库路径: $(OGRE_HOME)\OgreMain\Dependencies\Lib\Debug 确信已经链接 'CEGUIBase' 和 'OgreGUIRender' 库,也就是说将下面一行添 加进你的 Makefile 文件或 g++命令行: -L/usr/local/lib -lCEGUIBase -lCEGUIOgreRenderer 下面的适用于 SDK 的用户: 添加 include 文件夹:$(OGRE_HOME)\include\CEGUI 确信已经在 debug 配置的中添加 'CEGUIBase_d.lib' 和 'OgreGUIRenderer_d.lib' 库( 'CEGUIBase.lib' 和 'OgreGUIRenderer.lib' 在 release 配置中)。在 Visual C++中添加依赖,依次点击:项目 -> 属性 - > 配置属性 -> 链接。 CEGUIRender 源程序现在是从 Ogre CVS 下载代码中的一部分,一个示例工程, 因此你必须将包含 OgreGUIRenderer 头文件和 lib 文件的文件夹路径添加到属 性配置中。 另外,下面两个目录是必需的。尽管你在你的安装路径中的文件夹找不到。将 其作为约定它就会起作用: 添加 Include 文件夹: $(OGRE_HOME)\Samples\Common\CEGUIRenderer\include 添加 Lib 路径: $(OGRE_HOME)\Samples\Common\CEGUIRenderer\lib 介绍 Crazy Eddies GUI 系统是一个为不具备或缺乏用户界面制作功能的图形 API 或 引擎提供免费用户界面支持的开源的库。这个使用 c++编写的库是针对那些想 制作优秀的游戏却又没有 GUI(图形用户界面)子系统的专业游戏开发者。 开始 首先,你需要架构(skeleton)代码来创建具有 CEGUI 组件的 Ogre 程序。注 意:如果你使用,你必须在之前添加#define NOMINMAX。 //mem probs without this next one #include #include #include #include #include #include #include #include "OgreCEGUIRenderer.h" #include "OgreCEGUIResourceProvider.h" //regular mem handler #include #include "ExampleApplication.h" class GuiFrameListener : public ExampleFrameListener { private: CEGUI::Renderer* mGUIRenderer; public: GuiFrameListener(RenderWindow* win, Camera* cam, CEGUI::Renderer* renderer) : ExampleFrameListener(win, cam, false, false), mGUIRenderer(renderer) { } }; 仅仅是一个不做任何动作的空帧监听器,但在你按下“Esc”之前会一直循环。 class TutorialApplication : public ExampleApplication { private: CEGUI::OgreCEGUIRenderer* mGUIRenderer; CEGUI::System* mGUISystem; CEGUI::Window* mEditorGuiSheet; 这些是包含所有 CEGUI 数据的数据成员。我喜欢显式的调用 CEGUI 成员,一但 你开始对 Ogre 成员添加调用,这将会明确的说明它们是来自 CEGUI。 public: TutorialApplication() : mGUIRenderer(0), mGUISystem(0), mEditorGuiSheet(0) { } ~TutorialApplication() { if(mEditorGuiSheet) { CEGUI::WindowManager::getSingleton().destroyWindow(mEditorGuiSheet); } if(mGUISystem) { delete mGUISystem; mGUISystem = 0; } if(mGUIRenderer) { delete mGUIRenderer; mGUIRenderer = 0; } } 下面是你可以设置任意 Ogre 场景的地方,使用你在前五章教程学到的方法。在 这个 Ogre 场景中,你仍要为其添加一个独立的相机(camera)和视窗 (viewport)。 protected: void createScene(void) { // Set ambient light mSceneMgr->setAmbientLight(ColourValue(0.5, 0.5, 0.5)); 下面是创建 CEGUI 日志的地方,一般都设置为 Informative 模式的。其具有四 种模式:Standard, Errors, Informative 和 Insane。 // Set up GUI system mGUIRenderer = new CEGUI::OgreCEGUIRenderer(mWindow, Ogre::RENDER_QUEUE_OVERLAY, false, 3000, mSceneMgr); mGUISystem = new CEGUI::System(mGUIRenderer); CEGUI::Logger::getSingleton().setLoggingLevel(CEGUI::Informative); 创建一个新的 CEGUI 系统,使用“TaharezLook”来设置图(sheme)与鼠标指 针,使用“BlueHighway-12”来设置字体。 CEGUI::SchemeManager::getSingleton().loadScheme((CEGUI::utf8*)"Tahare zLookSkin.scheme"); mGUISystem- >setDefaultMouseCursor((CEGUI::utf8*)"TaharezLook",(CEGUI::utf8*)"Mou seArrow"); CEGUI::MouseCursor::getSingleton().setImage("TaharezLook","MouseMoveC ursor"); mGUISystem->setDefaultFont((CEGUI::utf8*)"BlueHighway-12"); mEditorGuiSheet=CEGUI::WindowManager::getSingleton().createWindow((CE GUI::utf8*)"DefaultWindow", (CEGUI::utf8*)"Sheet"); mGUISystem->setGUISheet(mEditorGuiSheet); } 调用自定义的帧监听器,这样我们可以在需要时访问“mGUIRender”。 void createFrameListener(void) { mFrameListener = new GuiFrameListener(mWindow, mCamera, mGUIRenderer); mRoot->addFrameListener(mFrameListener); } }; 下面是主函数也是程序的主循环,在本教程并不需要你修改这段代码。 #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 #define WIN32_LEAN_AND_MEAN #include "windows.h" INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT ) #else int main(int argc, char **argv) #endif { // Create application object TutorialApplication app; try { app.go(); } catch( Exception& e ) { #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 MessageBox( NULL, e.getFullDescription().c_str(), "An exception has occured!", MB_OK | MB_ICONERROR | MB_TASKMODAL); #else fprintf(stderr, "An exception has occured: %s\n",e.getFullDescription().c_str()); #endif } return 0; } 完成后,编译程序能得到一个空的窗口。请关掉程序,继续我们的学习。注 意:如果实际操作中出现问题,你可以在应用程序所在文件夹中找到 “CEGUI.log”文件分析查找错误 CEGUI 是如何工作的 本质上 CEGUI 是通过向窗口添加第二个场景,这个场景是在 Ogre 的基本渲染队 列完成后才渲染的。这个场景仅仅是由一系列 3D 矩形对象组成的。(也就是两 个多边形沿着其边压制到一起)。渲染矩阵是为消除矩形的突兀与歪斜而根据 他们的位置建立的。使用这些矩形,添加材质和响应就构成了用户界面 (GUI)。一般情况下这是很不错的,因为一个 3D 的用户界面将会自动的缩放 其元素来适应屏幕,并且使用硬件材质过滤。其将会比 C++标准的 2D 用户界面 更加快速和漂亮。 “So in one sentence: CEGUI renders a 2D gui using 3D methods and hardware so you don't have to.”——zeroskill 添加退出按钮 首先,我们需要为应用程序添加下面的头文件。本例中是“Push Button” #include 我们要在场景底部添加退出按钮。 CEGUI::PushButton* quitButton = (CEGUI::PushButton*)CEGUI::WindowManager::getSingleton().createWindow ("TaharezLook/Button", (CEGUI::utf8*)"Quit"); mEditorGuiSheet->addChildWindow(quitButton); quitButton->setPosition(CEGUI::Point(0.35f, 0.45f)); quitButton->setSize(CEGUI::Size(0.3f, 0.1f)); quitButton->setText("Quit"); 完成后执行程序,一个漂亮的按钮将会出现在屏幕中。但请注意,程序此时仍 然不做任何事情,因为我们并没有为其添加响应时间。 如果你编译时遇到了 setPosition()和 setSize()的调用错误: 'CEGUI::Window::setPosition' : cannot convert parameter 1 from 'CEGUI::Vector2' to 'const CEGUI::UVector2 &' 将 setPosition()和 setSize()所在行分别用下面的代码替换: quitButton->setPosition(CEGUI::UVector2(cegui_reldim(0.35f), cegui_reldim( 0.45f)) ); quitButton->setSize(CEGUI::UVector2(cegui_reldim(0.35f), cegui_reldim( 0.1f)) ); 响应事件 将下面函数添加到 TutorialApplication 的 public:中 void setupEventHandlers(void) { CEGUI::WindowManager& wmgr = CEGUI::WindowManager::getSingleton(); wmgr.getWindow((CEGUI::utf8*)"Quit")- >subscribeEvent(CEGUI::PushButton::EventClicked, CEGUI::Event::Subscriber (&TutorialApplication::handleQuit, this)); } bool handleQuit(const CEGUI::EventArgs& e) { static_cast(mFrameListener)- >requestShutdown(); return true; } 重写 GuiFrameListener 类来响应键盘和鼠标输入 class GuiFrameListener : public ExampleFrameListener, public MouseMotionListener, public MouseListener { private: CEGUI::Renderer* mGUIRenderer; bool mShutdownRequested; public: // NB using buffered input GuiFrameListener(RenderWindow* win, Camera* cam, CEGUI::Renderer* renderer) : ExampleFrameListener(win, cam, true, true), mGUIRenderer(renderer), mShutdownRequested(false) { mEventProcessor->addMouseMotionListener(this); mEventProcessor->addMouseListener(this); mEventProcessor->addKeyListener(this); } // Tell the frame listener to exit at the end of the next frame void requestShutdown(void) { mShutdownRequested = true; } bool frameEnded(const FrameEvent& evt) { if (mShutdownRequested) return false; else return ExampleFrameListener::frameEnded(evt); } void mouseMoved (MouseEvent *e) { CEGUI::System::getSingleton().injectMouseMove( e->getRelX() * mGUIRenderer->getWidth(), e->getRelY() * mGUIRenderer->getHeight()); e->consume(); } void mouseDragged (MouseEvent *e) { mouseMoved(e); } void mousePressed (MouseEvent *e) { CEGUI::System::getSingleton().injectMouseButtonDown( convertOgreButtonToCegui(e->getButtonID())); e->consume(); } void mouseReleased (MouseEvent *e) { CEGUI::System::getSingleton().injectMouseButtonUp( convertOgreButtonToCegui(e->getButtonID())); e->consume(); } void mouseClicked(MouseEvent* e) {} void mouseEntered(MouseEvent* e) {} void mouseExited(MouseEvent* e) {} void keyPressed(KeyEvent* e) { if(e->getKey() == KC_ESCAPE) { mShutdownRequested = true; e->consume(); return; } CEGUI::System::getSingleton().injectKeyDown(e->getKey()); CEGUI::System::getSingleton().injectChar(e->getKeyChar()); e->consume(); } void keyReleased(KeyEvent* e) { CEGUI::System::getSingleton().injectKeyUp(e->getKey()); e->consume(); } void keyClicked(KeyEvent* e) { // Do nothing e->consume(); } }; Ogre 1.4.0 如果你使用的是 Ogre 1.4.0 你将会要使用 OIS。在 Ogre3d 中使用 OIS 的更多 细节,请参阅使用 OIS 并且再看一看基础教程 5: class GuiFrameListener : public ExampleFrameListener, public OIS::MouseListener, public OIS::KeyListener { private: CEGUI::Renderer* mGUIRenderer; bool mShutdownRequested; public: // NB using buffered input GuiFrameListener(RenderWindow* win, Camera* cam, CEGUI::Renderer* renderer) : ExampleFrameListener(win, cam, true, true), mGUIRenderer(renderer), mShutdownRequested(false) { mMouse->setEventCallback( this ); mKeyboard->setEventCallback( this ); } // Tell the frame listener to exit at the end of the next frame void requestShutdown(void) { mShutdownRequested = true; } bool frameEnded(const FrameEvent& evt) { if (mShutdownRequested) return false; else return ExampleFrameListener::frameEnded(evt); } bool mouseMoved( const OIS::MouseEvent &e ) { using namespace OIS; CEGUI::System::getSingleton().injectMouseMove(e.state.X.rel,e.state.Y .rel); return true; } bool mousePressed (const OIS::MouseEvent &e, OIS::MouseButtonID id) { CEGUI::System::getSingleton().injectMouseButtonDown(convertOgreButton ToCegui(id)); return true; } bool mouseReleased( const OIS::MouseEvent &e, OIS::MouseButtonID id ) { CEGUI::System::getSingleton().injectMouseButtonUp(convertOgreButtonTo Cegui(id)); return true; } bool keyPressed( const OIS::KeyEvent &e ) { if(e.key == OIS::KC_ESCAPE) { mShutdownRequested = true; return true; } CEGUI::System::getSingleton().injectKeyDown(e.key); CEGUI::System::getSingleton().injectChar(e.text); return true; } bool keyReleased( const OIS::KeyEvent &e ) { CEGUI::System::getSingleton().injectKeyUp(e.key); return true; } }; 在 include 语句后 GuiFrameListener 声明前添加下面代码 CEGUI::MouseButton convertOgreButtonToCegui(int buttonID) { switch (buttonID) { case MouseEvent::BUTTON0_MASK: return CEGUI::LeftButton; case MouseEvent::BUTTON1_MASK: return CEGUI::RightButton; case MouseEvent::BUTTON2_MASK: return CEGUI::MiddleButton; case MouseEvent::BUTTON3_MASK: return CEGUI::X1Button; default: return CEGUI::LeftButton; } } Ogre 1.4.0 CEGUI::MouseButton convertOgreButtonToCegui(int buttonID) { using namespace OIS; switch (buttonID) { case OIS::MB_Left: return CEGUI::LeftButton; case OIS::MB_Right: return CEGUI::RightButton; case OIS::MB_Middle: return CEGUI::MiddleButton; default: return CEGUI::LeftButton; } } 将下面语句添加到创建场景方法(createscene)的末尾。 setupEventHandlers(); 现在你可以编译并执行程序了。实现效果是点击按钮后退出。 加载设置(Layout) CEGUI 使用 XML 格式来加载图形用户界面样式设置。复制下面 xml 代码到记事 本,并将其以“Tutoral Gui.xml”命名另存在“\media\gui”文件夹下。 Ogre 1.4.0 ( 补充说明:在复制上面的.xml 代码保存时,请手动删除行前的空格.否则会编 译出错. editBy 自由骑士笃志 2008-04-25 ) 现在将程序中下列代码段注释掉 mEditorGuiSheet= CEGUI::WindowManager::getSingleton().createWindow((CEGUI::utf8*)"Defa ultWindow", (CEGUI::utf8*)"Sheet"); mGUISystem->setGUISheet(mEditorGuiSheet); CEGUI::PushButton* quitButton = (CEGUI::PushButton*)CEGUI::WindowManager::getSingleton().createWindow ("TaharezLook/Button", (CEGUI::utf8*)"Quit"); mEditorGuiSheet->addChildWindow(quitButton); quitButton->setPosition(CEGUI::Point(0.35f, 0.45f)); quitButton->setSize(CEGUI::Size(0.3f, 0.1f)); quitButton->setText("Quit"); 在同样位置添加下列代码 mEditorGuiSheet = CEGUI::WindowManager::getSingleton().loadWindowLayout((CEGUI::utf8*)" Tutorial Gui.xml"); mGUISystem->setGUISheet(mEditorGuiSheet); CEGUI::PushButton* quitButton=(CEGUI::PushButton*)CEGUI:: WindowManager::getSingleton().getWindow((CEGUI::utf8*)"Quit"); 最后一行多余的,因为我们没有在之后使用指针,但是其说明了如何通过加载 文件来进行访问。注意:我们在创建 xml 文件时要根据实际窗口进行设计。 完成后编译并执行,程序在外观上并没有变化。 尝试 •视线相交和选取 Ogre mesh—当鼠标没有从 GUI 元素上移过。 •定义一个在 GUI 根菜单上的鼠标点击动作。当你鼠标点击一个不在根窗口中的 GUI 元素 时,它将会响应你的鼠标点击。如果你的鼠标并没有从一个 GUI 元素上滑过 (也就是说当你的鼠标指针还在我们的 3D 场景中)则根窗口响应鼠标点击。 •将鼠标点击转换到世界坐标系和视线相交((Camera::getCamera)到 ViewportRay(mouseX, mouseY)) // Start a new ray query Ogre::Ray cameraRay = root::getSingleton( ). getCamera( )->getCameraToViewportRay( mouseX, mouseY ); Ogre::RaySceneQuery *raySceneQuery = root::getSingleton( ). getSceneManager( )->createRayQuery( cameraRay ); raySceneQuery->execute( ); Ogre::RaySceneQueryResult result = raySceneQuery- >getLastResults( ); Ogre::MovableObject *closestObject = NULL; real closestDistance = LONG_MAX; std::list< Ogre::RaySceneQueryResultEntry >::iterator rayIterator; for ( rayIterator = result.begin( ); rayIterator != result.end( ); rayIterator++ ) { if ( ( *rayIterator ).movable->getUserObject( ) != NULL ) { if ( ( *rayIterator ).distance < closestDistance ) { closestObject = ( *rayIterator ).movable; closestDistance = ( *rayIterator ).distance; } } } // No object clicked if ( closestObject == NULL ) { clickedObject = NULL; ---- clickedObject is a class scoped variable } else { clickedObject = static_cast< object* >( closestObject- >getUserObject( ) ); } raySceneQuery->clearResults( ); root::getSingleton( ).getSceneManager( )- >destroyQuery( raySceneQuery ) 如何在两个 GUI(用户界面)之间转换(使用透明 度) 例如:如果你有一个登陆界面,在成功登陆后,进入了你的用户主界面。你将 会想在这两个界面间切换。 •第一步,加载登陆用户界面。 //First loading with this mGUIRenderer = new CEGUI::OgreCEGUIRenderer(mWindow, Ogre::RENDER_QUEUE_OVERLAY, false, 3000,mSceneMgr); mGUISystem = new CEGUI::System(mGUIRenderer); CEGUI::Logger::getSingleton().setLoggingLevel(CEGUI::Informative); CEGUI::SchemeManager::getSingleton().loadScheme((CEGUI::utf8*)"Window sLook.scheme"); mGUISystem->setDefaultMouseCursor((CEGUI::utf8*)"WindowsLook", (CEGUI::utf8*)"MouseArrow"); CEGUI::Font *f = CEGUI::FontManager::getSingleton().createFont("Commonwealth-10.font"); mGUISystem->setDefaultFont(f); //End "first loading with this" //Load a XML file mEditorGuiSheet = CEGUI::WindowManager::getSingleton().loadWindowLayout((CEGUI::utf8*)" Presentation.xml"); mGUISystem->setGUISheet(mEditorGuiSheet); •第二步,如果你想删除并重建一个 GUI,你需要做到以下: if(mEditorGuiSheet) CEGUI::WindowManager::getSingleton().destroyWindow(mEditorGuiSheet); •最后一步,加载其他的 GUI mEditorGuiSheet = CEGUI::WindowManager::getSingleton().loadWindowLayout((CEGUI::utf8*)" Futura.xml"); mGUISystem->setGUISheet(mEditorGuiSheet); 重做第二步和最后一步来加载其他 GUI。 结论 这个教程为你展示了在 Ogre3D 下使用 CEGUI 的一些基本方法,你可以感受下使 用 CUEGUI 编程的乐趣:) 原文 上一章节:基础教程五 下一章节:基础教程七 目录 取自 "http://ogre3d.cn/wiki/index.php?title=%E6%96%87%E6%A1%A3:%E6%95%99%E 7%A8%8B:%E5%9F%BA%E7%A1%80%E6%95%99%E7%A8%8B:%E5%9F%BA%E7%A1%80%E6%95 %99%E7%A8%8B%E5%85%AD" 查看 · 页面 · 讨论 · 源码 · 历史 个人工具 · 登录/创建账户 导航 · 首页 · 社区 · 当前事件 · 最近更改 · 随机页面 · 帮助 搜索 进入 搜索 Google 搜索 Enter your search terms Submit search form earch 工具箱 · 链入页面 · 链出更改 · 特殊页面 · 可打印版 · 永久链接 Google Adsense · 这页的最后修订在 2009 年 4 月 3 日 (星期五) 09:27。 · 本页面已经被浏览 1,592 次。 · 隐私政策 · 关于 Ogre3D 开放资源地带 · 沪 ICP 备 09049564 号 文档:教程:基础教程:基础教程七 出自 Ogre3D 开放资源地带 跳转到: 导航, 搜索 目录 · 1 先决条件 · 2 介绍 · 3 从这开始 o 3.1 最初的代码 o 3.2 简单介绍 · 4 与 Ogre 结合 o 4.1 初始化 CEGUI o 4.2 注入键盘事件 o 4.3 转变和注入鼠标事件 · 5 窗口、表单、组件 o 5.1 介绍 o 5.2 载入表单 o 5.3 手动创建部件 · 6 事件 · 7 渲染到纹理 · 8 结论 o 8.1 其它的选择 o 8.2 更多的信息 先决条件 本教程是在假设你已经拥有 c++编程基础并能够成功建立和编译 Ogre 程序(如 果你设置程序方面还存在问题,请参阅 SettingUpAnApplication 来获取详细信 息)。本教程建立在之前的初学者教程的基础上,并且假设你已经学习了它 们。 介绍 在这一课里,我们将一起来探索如何在 Ogre 里使用 CEGUI(一个嵌入的 GUI 系 统)。学完本课以后,你应该能够往你的 Ogre 应用程序里添加基本的 CEGUI 功 能了。注意:本课并不会全面地教你如何使用 CEGUI,本课只是一个入门。若 有更深入的问题需要帮助,请直接去它们的主页。 你能在这里找到本课的代码。在你学习本课时,你应该逐个地往你的工程里添 加代码,编译后观察它的效果。 从这开始 最初的代码 在这节课里,你将用预定义的基础代码作为起点。只要你学过之前的课程,你 应该对它们已经很熟悉了。用你喜欢的编译器创建一个工程,然后新建一个源 文件包含如下代码: #include "ExampleApplication.h" #include #include #include class TutorialListener : public ExampleFrameListener, public OIS::MouseListener, public OIS::KeyListener { public: TutorialListener(RenderWindow* win, Camera* cam) : ExampleFrameListener(win, cam, true, true) { mContinue=true; mMouse->setEventCallback(this); mKeyboard->setEventCallback(this); } // CEGUIDemoListener bool frameStarted(const FrameEvent &evt) { mKeyboard->capture(); mMouse->capture(); return mContinue && !mKeyboard->isKeyDown(OIS::KC_ESCAPE); } bool quit(const CEGUI::EventArgs &e) { mContinue = false; return true; } // MouseListener bool mouseMoved(const OIS::MouseEvent &arg) { return true; } bool mousePressed(const OIS::MouseEvent &arg, OIS::MouseButtonID id) { return true; } bool mouseReleased(const OIS::MouseEvent &arg, OIS::MouseButtonID id) { return true; } // KeyListener bool keyPressed(const OIS::KeyEvent &arg) { return true; } bool keyReleased(const OIS::KeyEvent &arg) { return true; } private: bool mContinue; }; class CEGUIDemoApplication : public ExampleApplication { public: CEGUIDemoApplication() : mSystem(0), mRenderer(0) { } ~CEGUIDemoApplication() { if (mSystem) delete mSystem; if (mRenderer) delete mRenderer; } protected: CEGUI::System *mSystem; CEGUI::OgreCEGUIRenderer *mRenderer; void createScene(void) { } void createFrameListener(void) { mFrameListener= new TutorialListener(mWindow, mCamera); mFrameListener->showDebugOverlay(true); mRoot->addFrameListener(mFrameListener); } }; #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 #define WIN32_LEAN_AND_MEAN #include "windows.h" INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT) #else int main(int argc, char **argv) #endif { // Create application object CEGUIDemoApplication app; try { app.go(); } catch(Exception& e) { #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 MessageBoxA(NULL, e.getFullDescription().c_str(), "An exception has occurred!", MB_OK | MB_ICONERROR | MB_TASKMODAL); #else fprintf(stderr, "An exception has occurred: %s\n", e.getFullDescription().c_str()); #endif } return 0; } 确保你能编译并运行这些代码。目前这个程序除了显示一个黑屏(按 ESC 退 出)什么都不能做。如果你在编译时遇到连接器错误,请保证 CEGUIBase_d.lib、OgreGUIRenderer_d.lib 已经添加到了连接器的输入里 (debug 模式的,如果是 release 模式,去掉_d 后缀)。 简单介绍 CEGUI 是一个功能全面的 GUI 库,它能够植入像 Ogre 这样的 3D 应用程序(当 然也支持纯 DirectX 和 OpenGL)。就像 Ogre 只是一个图像库一样(不做声 音、物理等其它的事情),CEGUI 只是一个 GUI 库,意味着既不自己做渲染, 也不与任何鼠标键盘事件挂钩。实际上,为了让 CEGUI 能渲染,你必须提供一 个渲染器给它(包含在 SDK 的 OgreGUIRenderer 库)。而为了让它能够理解鼠 标键盘事件,你必须手工地把它们注入系统。这也许一开始看起来觉得痛苦, 但其实只需要一丁点代码就能实现。这样,你就可以对渲染和输入进行全面控 制,CEGUI 根本不会防碍你。 CEGUI 有很多的方面,有很多你不熟悉的诡异的地方(就算你以前使用过 GUI 系统)。在接下来的学习中,我将给你一一道来。 与 Ogre 结合 初始化 CEGUI 在前面的教程里,我们已经学习了如何启动 CEGUI,所以在这第一个部分我们 不会深究。找到 createScene 函数并添加如下代码: mRenderer = new CEGUI::OgreCEGUIRenderer(mWindow, Ogre::RENDER_QUEUE_OVERLAY, false, 3000, mSceneMgr); mSystem = new CEGUI::System(mRenderer); 好,CEGUI 已经初始化了,我们选择它将使用的皮肤。CEGUI 是高度自定义的, 它允许你通过更换皮肤来定义应用程序的"look and feel"。我们将不涉及如何 给这个库换皮肤,所以如果你想了解得更多,咨询 CEGUI 的网站。以下的代码 选择了一个皮肤: CEGUI::SchemeManager::getSingleton().loadScheme((CEGUI::utf8*)"Tahare zLookSkin.scheme"); 默认的 Ogre 安装不提供其它的皮肤给你,但如果你是从 CEGUI 网址上下载的, 你就能找到几个其它的选择(或者你自己就可以制作)。接下来我们要做的事 件就是设置默认鼠标指针图像和默认字体: mSystem->setDefaultMouseCursor((CEGUI::utf8*)"TaharezLook", (CEGUI::utf8*)"MouseArrow"); mSystem->setDefaultFont((CEGUI::utf8*)"BlueHighway-12"); 在整个教程里,我们都会使用 CEGUI 来显示鼠标指针,就算我们没有使用这个 GUI 库的其它功能。用其它的 GUI 库来渲染鼠标,或者直接用 Ogre 来创建你自 己的鼠标,也是可以的(后者要麻烦一点)。如果你只为了鼠标指针而使用 CEGUI,并担心内存的使用以及游戏所占的硬盘空间,你可以从这些选择中找一 个来代替 CEGUI。 最后请注意,在最后一段代码里我们设置了默认的鼠标指针,但我们并没有像 后面的教程那样直接使用 MouseCursor::setImage 函数来设置。这是因为在这 一课里,我们总是在 CEGUI 窗口上面(虽然它可能是不可见的),而设置默认 鼠标指针将会使得它是我们选择的图像。如果我们直接设置鼠标指针,而不设 置默认的,则每当经过当 CEGUI 窗口时,鼠标指针都是不可见的(在本课里总 是这种情况 )。另一方面,如果你不显示任何 CEGUI 窗口,设置默认的鼠标指 标将不产生任何效果,后面的教程就是这种情况。那样的话,调用 MouseCursor::setImage 才能在应用程序里显示指针。 注入键盘事件 CEGUI 不会自己处理输入,它不读取鼠标的移动和键盘的输入。相反地,它依 赖用户把按键鼠标事件注入到系统。接下来我们要做的就是处理这些事件。若 你正使用 CEGUI,你需要以缓冲模式运行鼠标键盘,这样你就能直接获取事件 并在它们发生时注入。找到 keyPressed 函数,并添加代码: CEGUI::System *sys = CEGUI::System::getSingletonPtr(); sys->injectKeyDown(arg.key); sys->injectChar(arg.text); 得到系统对象之后,我们做两件事。第一件事是把 Key Down 事件注入到 CEGUI。第二件是注入一个实际按下的字符。正确地注入字符是非常重要的,因 为如果使用的是非英语键盘,只凭注入 KeyDown 事件并不能总是取到想要的结 果。记住 injectChar 是支持 Unicode 的。 现在我们给系统注入 KeyUp 事件。找到 keyReleased 函数,添加以下代码: CEGUI::System::getSingleton().injectKeyUp(arg.key); 注意,我们这里不需要注入字符 up 事件,KeyUp 使用就可以了。 转变和注入鼠标事件 现在我们完成了键盘输入的处理,下面我们来看看鼠标输入。然而我们有个小 问题需要说一下。当我们注入 KepUp 和 KepDown 事件到 CEGUI 里时,我们不必 转换键码。OIS 和 CEGUI 为键盘输入使用相同的键码。但鼠标按钮却不是这 样。在按下鼠标按钮注入到 CEGUI 时,我们需要写一个函数来把 OIS 的按钮 ID 转换为 CEGUI 的按钮 ID。在你代码顶部附近,TutorialListener 类前面,添加 以下函数: CEGUI::MouseButton convertButton(OIS::MouseButtonID buttonID) { switch (buttonID) { case OIS::MB_Left: return CEGUI::LeftButton; case OIS::MB_Right: return CEGUI::RightButton; case OIS::MB_Middle: return CEGUI::MiddleButton; default: return CEGUI::LeftButton; } } 现在我们准备注入鼠标事件。找到 mousePressed 函数,并添加以下代码: CEGUI::System::getSingleton().injectMouseButtonDown(convertButton(id) ); 这代码的意思应该比较清楚了。我们转换传入的 ID,再把结果传给 CEGUI。找 到 mouseReleased 函数,并添加这么一行: CEGUI::System::getSingleton().injectMouseButtonUp(convertButton(id)); 最后,我们往 CEGUI 里注入鼠标移动。The CEGUI::System 对象有一个 injectMouseMove 方法,它需要鼠标的相对运行作为参数。OIS::mouseMoved 处 理者在 state.X.rel 和 the state.Y.rel 变量里给我们提供了这些相对运动。 找到 mouseMoved 方法,并添加以下代码: CEGUI::System::getSingleton().injectMouseMove(arg.state.X.rel, arg.state.Y.rel); 好了!CEGUI 设置完成了,可以接收鼠标和键盘事件了。 窗口、表单、组件 介绍 CEGUI 与其它多数 GUI 系统有所不同。在 CEGUI 里,所有能显示出来的东西都 是 CEGUI::Window 类的一个子类,而且一个 window 可以有任意数量的子 window。这样会导致一些奇怪的事情发生。你能在一个按钮里面放置另外一个 按钮,虽然实际上不会这么干。我提这些的原因是,当你正寻找放在应用程序 里的某个特殊的小部件时,它们都被称作 Windows,而且可以通过访问 Windows 的函数来访问。 CEGUI 最常用的用法是,你不必在代码里创建每一个单独的对象。而你可以通 过一个像 CEGUI Layout Editor 这样的编辑器,来为你的程序创建一个 GUI 布 局。根据你的喜好,放置你的窗口、按钮以及其它部件到屏幕上之后,编辑器 会把布局保存到一个文本文件里。你就可以之后加载这个布局到 GUI sheet 里 面(它也是 CEGUI::Window 的一个子类)。 最后,要知道 CEGUI 包含大量的小部件供你的程序使用。我们在这里不去涉 及,所以如果你决定使用 CEGUI,最好还是去它们的网站了解更多的信息。 载入表单 在 CEGUI 里载入一个表单(sheet)是非常容易的事。WindowManager 类提供了一 个"loadWindowLayout"函数来加载表单,并把它放入 CEGUI::Window 对象。然 后你可以通过 CEGUI::System::setGUISheet 来显示它。我们在本课不会使用 它,但如果不给你介绍一下使用它的例子,我觉得我很失职。别把这些添加到 代码里(如果添加了,看见结果后删除掉): // Do not add this to the program CEGUI::Window* sheet = CEGUI::WindowManager::getSingleton().loadWindowLayout((CEGUI::utf8*)" ogregui.layout"); mSystem->setGUISheet(sheet); 这样就把表单设置成显示了。你能在后面取回这个表单,用 System::getGUISheet 方法。通过调用 setGUISheet,你能够无缝地交换 GUI 表 单(但你必须要保证拥有指向当前表单的指针,如果你打算再换回来)。 手动创建部件 就像我前面所说的,大多数你使用 CEGUI 的时候,你使用的是由编辑器生成的 GUI 表单。然而有时候,你需要手动地创建一个部件以呈现在屏幕上。在这个 例子中,我们将添加一个 Quit 按钮,我们稍后会给它添加功能。因为当本课结 束时,我们不只会添加一个 Quit 按钮到屏幕上,我们要创建一个默认的 CEGUI::Window 来包含所有我们要创建的部件。将以下代码添加到 createScene 函数末尾: CEGUI::WindowManager *win = CEGUI::WindowManager::getSingletonPtr(); CEGUI::Window *sheet = win->createWindow("DefaultGUISheet", "CEGUIDemo/Sheet"); 这里使用 WindowManager 创建了一个被称为"CEGUIDemo/Sheet"的 "DefaultGUISheet"。尽管我们可能为表单取任何名称,但用层次结构的方法来 命名它是非常常见的(推荐),比如 “SomeApp/MainMenu/Submenu3/CancelButton”。然后我们要做的事情就是, 创建一个 Quit 按钮,并设置它的大小: CEGUI::Window *quit = win->createWindow("TaharezLook/Button", "CEGUIDemo/QuitButton"); quit->setText("Quit"); quit->setSize(CEGUI::UVector2(CEGUI::UDim(0.15, 0), CEGUI::UDim(0.05, 0))); 这个的含义比较隐晦。CEGUI 里的大小和方位使用了一种“统一化的尺度”。 当设置大小的时候,你必须创建一个 UDim 对象来告诉它到底是什么尺寸。第一 个参数是这个对象相比于其父亲的相对大小。第二个参数才是对象的绝对大小 (像素表示)。必须注意的是,你只能给 UDim 设置两种参数的一种,另一种必 须是 0。所以在这里,我们创建了一个宽度为父亲的 15%且高度为 5%的按钮。 如果我们想要把它设置成 20×5 像素,就要两个 UDim 的第二参数分别设置为 20 和 5。 我们要做的最后一件事情是,把 Quit 按钮粘合到我们创建的表单上,并把它设 成当前系统的 GUI 表单: sheet->addChildWindow(quit); mSystem->setGUISheet(sheet); 现在如果你编译并运行了你的程序,你将会在屏幕左上角看到一个 Quit 按钮, 但当你点击它时不会有任何反应。 事件 CEGUI 里的事件是非常灵活的。它不是实现一个接收事件的接口,而是使用一 种回调机制,把任何公共函数绑定成一个事件处理器(通过适当的方法签名)。 不幸的是,这也意味着注册事件会比 Ogre 更麻烦。我们现在要为处理 Quit 按 钮的点击事件进行注册。这样的话,我们首先需要一个指向 Quit 按钮的指针。 找到 TutorialListener 的构造器,并添加以下代码: CEGUI::WindowManager *wmgr = CEGUI::WindowManager::getSingletonPtr(); CEGUI::Window *quit = wmgr- >getWindow((CEGUI::utf8*)"CEGUIDemo/QuitButton"); 现在我们取得了指向按钮的指针,我们来订阅这个点击事件。CEGUI 里的所有 部件都有它们所支持的事件集,而且都是以“Event”开头。这里是订阅事件的 代码: quit->subscribeEvent(CEGUI::PushButton::EventClicked, CEGUI::Event::Subscriber(&TutorialListener::quit, this)); subscribeEvent 的第一参数是事件本身。第二个参数是一个 Event::Subscriber 对象。当创建 Subcriber 对象时,我们传入的第一个东西 是指向处理事件函数的指针(注意&符号给出了函数的指针)。我们传经 subscriber 的第二个参数是处理这个事件的 TutorialListener 对象(就是 “this”对象)。好了!我们的 TutorialListener::quit 函数(已经定义了)将会 处理鼠标点击并终止程序。 编译并运行你的程序,调试一下看看。 注意,我们可以创建任意数量的函数来为 CEGUI 处理事件。对它们的唯一约束 是,返回值必须是一个布尔型,而且必须带有类型为"const CEGUI::EventArgs &"的唯一参数。关于事件的更多信息(以及如何退订事件),请查阅 CEGUI 的网 站。 渲染到纹理 我们能用 CEGUI 来做更有趣的事,其中之一就是创建一个纹理窗口的渲染器。 这让我们能够创建一个可以直接渲染到 CEGUI 部件的视口。这样的话,我们首 先要创建一个让我们看见的场景。在 createScene 函数底部添加如下代码: mSceneMgr->setAmbientLight(ColourValue(1, 1, 1)); mSceneMgr->setSkyDome(true, "Examples/CloudySky", 5, 8); Entity* ogreHead = mSceneMgr->createEntity("Head", "ogrehead.mesh"); SceneNode* headNode = mSceneMgr->getRootSceneNode()- >createChildSceneNode(Vector3(0, 0, -300)); headNode->attachObject(ogreHead); 现在我们创建好了一个渲染纹理。RenderSystem 对象提供了一种渲染到纹理的 功能。我们用 RenderSystem::createRenderTexture 函数创建一个纹理。在这 个程序里,我们创建一个 512 x 512 的纹理: RenderTexture *tex = mRoot->getRenderSystem()- >createRenderTexture("RttTex", 512, 512, TEX_TYPE_2D, PF_R8G8B8); UPDATE: The createRenderTexture() method appears to have been deprecated in the trunk. So instead of the above line use this: RenderTexture *tex= mRoot->getTextureManager()->createManual ("RttTex", "Default", TEX_TYPE_2D, 512, 512, 0, PF_R8G8B8, TU_RENDERTARGET) ->getBuffer()->getRenderTarget(); 关于这个函数,请查阅 API 参考来获取更多的信息。接下来我们还要创建一个 摄像机和一个视口,以便察看我们创建的场景。注意,我们改变了一些视口设 置,包括关闭 Overlays。这是非常重要的,否则你会看见 CEGUI 与 Ogre 在小 窗口里面重叠在一起。 Camera *cam = mSceneMgr->createCamera("RttCam"); cam->setPosition(100, -100, -400); cam->lookAt(0, 0, -300); Viewport *v = tex->addViewport(cam); v->setOverlaysEnabled(false); v->setClearEveryFrame(true); v->setBackgroundColour(ColourValue::Black); 注意,我们把视口添加给了纹理(而我们通常是把视口添加到渲染窗口)。好 了,我们已经创建了我们的场景和纹理,现在我们需要把它们植入 CEGUI。你 能用任何 Ogre 纹理来创建 CEGUI::Texture,通过调用 OgreCEGUIRenderer::createTexture 函数: CEGUI::Texture *cTex = mRenderer- >createTexture((CEGUI::utf8*)"RttTex"); 不幸的是,这里比较复杂。在 CEGUI 里,你从来不处理单个的纹理或单个的图 像。CEGUI 处理的是图像集,而非单个的图像。当你试着定义皮肤的 look and feel 时,处理一整套图像是很有用的(比如,看一看 SDK 里 media 目录 TaharezLook.tga 的图像是什么样子)。然后,就算你只要定义单个图像,你 必须为它创建一个图像集。下面我们就是做这个事情: CEGUI::Imageset *imageSet = CEGUI::ImagesetManager::getSingleton().createImageset((CEGUI::utf8*)" RttImageset", cTex); imageSet->defineImage((CEGUI::utf8*)"RttImage", CEGUI::Point(0.0f, 0.0f), CEGUI::Size(cTex->getWidth(), cTex->getHeight()), CEGUI::Point(0.0f,0.0f)); 第一行用我们提供给它的纹理,创建了一个图像集(称为"RttImageset")。第 二行(调用 defineImage),指定了第一个也是唯一一个图像,它被称为 "RttImage",而且它的大小与我们提供的 cTex 纹理大小一样。最后我们还要创 建一个 StaticImage 小部件,来收容这个渲染纹理。第一部分与创建其它窗口 没有什么不同: CEGUI::Window *si = win- >createWindow((CEGUI::utf8*)"TaharezLook/StaticImage", "RTTWindow"); si->setSize(CEGUI::UVector2(CEGUI::UDim(0.5f, 0), CEGUI::UDim(0.4f, 0))); si->setPosition(CEGUI::UVector2(CEGUI::UDim(0.5f, 0), CEGUI::UDim(0, 0))); 现在我们要指定 StaticImage 部件将要显示的图像。再一次,由于 CEGUI 总是 处理图像集而不是单独的图像,我们必须从图像集里得到图像名称,并显示 它: si->setProperty("Image", CEGUI::PropertyHelper::imageToString(&imageSet- >getImage((CEGUI::utf8*)"RttImage"))); 这似乎是我们把一个纹理包装进了图像集,然后再解包出来,我们实际上就是 这么做的。在 CEGUI 里操作图像并不是这个库中最简单最直接的事件。最后, 我们把 StaticImage 部件添加到先前创建的 GUI 表单里去: sheet->addChildWindow(si); 现在我们完成了。编译并运行这个应用程序。 结论 其它的选择 CEGUI 并不是完美无缺的,它确定不是适用于每一个人。除了 CEGUI,这里还有 一些其它的选择: Navi Right Brain Games GUI QuickGUI BetaGUI 更多的信息 你还能够在其它的地方找到关于 CEGUI 的更多信息。 Practical Application - Something With A Bit More Meat - A more in- depth tutorial than the one here. CEGUI's Official Tutorials The CEGUI Website --Xiao7cn 11:13 2008 年 3 月 1 日 (CST)Xiao7cn(FriedChicken)翻译 原文 上一章节:基础教程六 下一章节:基础教程八 目录 取自 "http://ogre3d.cn/wiki/index.php?title=%E6%96%87%E6%A1%A3:%E6%95%99%E 7%A8%8B:%E5%9F%BA%E7%A1%80%E6%95%99%E7%A8%8B:%E5%9F%BA%E7%A1%80%E6%95 %99%E7%A8%8B%E4%B8%83" 查看 · 页面 · 讨论 · 源码 · 历史 个人工具 · 登录/创建账户 导航 · 首页 · 社区 · 当前事件 · 最近更改 · 随机页面 · 帮助 搜索 进入 搜索 Google 搜索 Enter your search terms Submit search form earch 工具箱 · 链入页面 · 链出更改 · 特殊页面 · 可打印版 · 永久链接 Google Adsense · 这页的最后修订在 2009 年 4 月 3 日 (星期五) 09:27。 · 本页面已经被浏览 1,227 次。 · 隐私政策 · 关于 Ogre3D 开放资源地带 · 沪 ICP 备 09049564 号
还剩93页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 8 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

愤怒的螃蟹

贡献于2015-05-23

下载需要 8 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf