相信每一位读者朋友一直会有一个这样的疑问,虽然我们通过前面章节的学习可以做出一些相对比较复杂的应用程序了,但是总是感觉不能完全把握整个Ogre3D的流程,而且我们前面都是在继承Ogre3D为我们提供的模板类的基础上构建的应用程序,虽然这样减少了我们不少工作,但是对于刚接触Ogre3D的我们来说或许并不是什么好事,是的,现在是时候和大家一起学习一下Ogre3D框架是怎样一步一步搭建起来,是怎样一步一步启动起来的了。
这一节我们不再继承Ogre3D为我们提供的ExampleApplication类,也不再使用我们一直用的那个模板代码,我们自己从头编写所有代码来完成所有工作,以此让大家来跟好的理解Ogre3D的启动序列。
手动建立第一个应用
第一步,我们建立一个空的Win32项目;
第二步,添加OgreMain_d.lib OIS_d.lib,并修改工作目录(如果你对这里有任何疑问,请参阅《Ogre初步》一章);
第二步,新建一个Main.cpp文件(名字任意),并添加如下代码:
#include <windows.h> #include "Ogre.h"
INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT ) { return 0; } |
第三步,创建一个Ogre3D的Root对象,在WinMain函数中加入如下代码:
Ogre::Root*mRoot = new Ogre::Root("plugins_d.cfg"); |
第四步,添加Ogre配置对话框,继续在WinMain函数中加入如下代码:
if (!mRoot->showConfigDialog()) { return -1; } |
第五步,创建渲染窗口:
Ogre::RenderWindow*mWindow = mRoot->initialise(true,"IMedia Project"); |
第六步,创建场景管理器:
Ogre::SceneManager*mSceneMgr = mRoot->createSceneManager(Ogre::ST_GENERIC); |
第七步,创建相机:
Ogre::Camera*mCamera = mSceneMgr->createCamera("MyCamera"); mCamera->setPosition(Ogre::Vector3(0,0,80)); mCamera->lookAt(Ogre::Vector3(0,0,-300)); mCamera->setNearClipDistance(5); |
第八步,创建视口,并设置背景色以及纵横比:
Ogre::Viewport*vp = mWindow->addViewport(mCamera); vp->setBackgroundColour(Ogre::ColourValue(0,0,0)); mCamera->setAspectRatio(Ogre::Real(vp->getActualWidth())/Ogre::Real(vp->getActualHeight())); |
第九步,调用跟对象的startRendering函数开始渲染场景:
mRoot->startRendering(); |
下面列出我们添加的所有代码:
#include <windows.h> #include "Ogre.h"
INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT ) { Ogre::Root*mRoot = new Ogre::Root("plugins_d.cfg"); if (!mRoot->showConfigDialog()) { return -1; } Ogre::RenderWindow*mWindow = mRoot->initialise(true,"IMedia Project"); Ogre::SceneManager*mSceneMgr = mRoot->createSceneManager(Ogre::ST_GENERIC); Ogre::Camera*mCamera = mSceneMgr->createCamera("MyCamera"); mCamera->setPosition(Ogre::Vector3(0,0,80)); mCamera->lookAt(Ogre::Vector3(0,0,-300)); mCamera->setNearClipDistance(5);
Ogre::Viewport*vp = mWindow->addViewport(mCamera); vp->setBackgroundColour(Ogre::ColourValue(0,0,0)); mCamera->setAspectRatio(Ogre::Real(vp->getActualWidth())/Ogre::Real(vp->getActualHeight()));
mRoot->startRendering(); return 0; } |
编译并运行程序,如果读者能看到一个黑屏窗口,那么说明我们的应用程序成功了第一步。但是当我们点击了窗口的关闭按钮后或许会在发现我们的应用程序并没有真正退出,这里的原因和我们在前面《帧监听与用户输入》一章中分析的情况一样,不用担心我们很快就会添加键盘响应事件,是应用程序能正常退出,现在我们可以通过下图所示,在任务管理器中终止我们的应用程序:
如果读者朋友是在debug调试模式下(或者直接按F5键)运行的程序,可以到Visual Studio中选择 菜单——调试——停止调试(或者直接同时按下SHIFT+F5键),即可终止应用程序,如下所示:
代码分析:
通过上面的代码,我们没有使用Ogre3D提供的ExampleApplication类的帮助,创建了我们的第一个Ogre3D程序。在前面代码第三步中,我们创建了一个Root对象的实例,这是因为根(Root)对象是Ogre系统的入口,我们每一个Ogre3D应用必须有一个根对象,因此,使用Ogre3D程序所需要做的第一件事就是实例化一个Root对象,如果没有这个对象,你就无法调用(除了日志管理以外)的任何一个功能。Root类的构造函数接受一些字符串对象的参数,这些字符串代表着不同作用的文件名称。
查阅Ogre3D的帮助文档我们可以发现Root构造函数的声明如下所示:
Root (const String &pluginFileName="plugins.cfg", constString &configFileName="ogre.cfg", const String &logFileName="Ogre.log") |
plugins.cfg文件
它代表了Ogre中所需的插件列表,Ogre中所谓的插件就是符合Ogre插件接口的代码模块(在Windows下面是DLL文件,在Linux下是.so文件),比如场景管理(SceneManager插件)和渲染系统(RenderSystem)插件等。在启动Ogre的时候,它会载入plugins.cfg(Release模式下)配置文件来查看有哪些插件可以被使用,下面Ogre中的Release发行版中提供的"plugins.cfg"文件的内容
# Defines plugins to load
# Define plugin folder PluginFolder=.
# Define plugins Plugin=RenderSystem_Direct3D9 # Plugin=RenderSystem_Direct3D10 # Plugin=RenderSystem_Direct3D11 Plugin=RenderSystem_GL # Plugin=RenderSystem_GLES Plugin=Plugin_ParticleFX Plugin=Plugin_BSPSceneManager Plugin=Plugin_CgProgramManager Plugin=Plugin_PCZSceneManager Plugin=Plugin_OctreeZone Plugin=Plugin_OctreeSceneManager |
我们简要分析一下这个配置文件,前面带有#号的代表了注释语句,PluginFolder标签的值告诉了Ogre要到哪个目录下找下面使用的插件,这里是当前目录(一般情况下是程序的执行目录),Plugin标签值就是对应的所需插件。
你可以通过改变这个文件的内容来达到配置自己程序中使用的插件的目的,当然,没必要非得用"plugins.cfg"作为文件名,可以自己随意取名,然后在构建Root实例的时候作为参数传递给系统就好了,当没有配置这个参数的时候程序就会采用默认的"plugins.cfg"文件来配置插件,这是在Release模式下默认的文件名,而我们是在Debug模式下运行的程序,因此这里我们需要把第一个参数修改为"plugins_d.cfg",如果大家对这个不太理解,可以到你的Ogre根目录下查看,有个bin文件夹,打开之后有debug文件夹和release文件夹,在这两个文件夹中分别对应着plugins_d.cfg文件和plugins.cfg文件,而后面两个参数对应的文件并不像plugins_d.cfg一样,它们如果不存在Ogre会为我们自动生成,因此这里无需修改名称;
前面代码第四步中,我们通过根对象的showConfigDialog函数显示了Ogre配置系统窗口,通过提供给我们的这个对话框,我们可以对Ogre绘制系统的一些选项(如色彩深度(Colour Depth)、是否全屏显示等)进行修改,系统会根据用户选项进行初始化。而这里的这个配置对话框和我们将要讲到的Ogre.cfg文件有着紧密的关系;
Ogre.cfg文件
下面是ogre.cfg文件中的内容:
Render System=OpenGL Rendering Subsystem
[Direct3D9 Rendering Subsystem] Allow NVPerfHUD=No FSAA=0 Floating-point mode=Fastest Full Screen=Yes Rendering Device=Monitor-1-NVIDIA GeForce 8400M G Resource Creation Policy=Create on all devices VSync=No VSync Interval=1 Video Mode=800 x 600 @ 32-bit colour sRGB Gamma Conversion=No
[OpenGL Rendering Subsystem] Colour Depth=32 Display Frequency=N/A FSAA=0 Full Screen=No RTT Preferred Mode=FBO VSync=No VSync Interval=1 Video Mode=1024 x 768 sRGB Gamma Conversion=No |
细心的读者会发现上面所列出来的条目和对话框中设置选项中的条目是对应的。有的时候我们希望在应用程序运行的时候不弹出这个对话框配置窗口,那么为了达到这个目的,我们可以修改一下前面的部分代码,把第四步中的代码替换为如下代码:
if (!mRoot->restoreConfig()) { if (!mRoot->showConfigDialog()) { return -1; } } |
编译并运行程序,你会发现原来每次运行程序时弹出的那个配置对话框不再弹出了,而这里的修改很简单,上面代码的意思就是如果restoreConfig函数调用失败,也就是说没有一个合法的Ogre.cfg文件存在的时候,程序会提供一个配置窗口用于初始化(当你点击OK之后会帮你吧新生成的Ogre.cfg存储),通过这种方法,你可以保证在任何情况下都能正确运行你的程序,大家可以尝试一下把debug目录下的那个Ogre.cfg文件删除掉,然后再次运行程序,就会发现那个配置对话框又重新显示出来了。
Ogre.log文件
Ogre提供了日志管理对象作为记录系统诊断和异常信息的日志工具。当用户程序出现问题的时候,开发者可以通过查询日志信息了解具体情况,而没有必要去询问用户技术细节和程序的具体设置。在日志的输出中包括以Ogre为基础的程序的所有事件、初始值、状态以及性能信息等。默认的情况下这些信息被输出到磁盘上。 |
回过来我们再看前面第五步创建渲染窗口的代码,前面我们已经解除过RenderWindow这个类,当时我们只是使用它的一个实例对象,而现在我们这里的代码就是通过Root实例化渲染窗口的一种方法,initialise函数的第一个参数是告诉Ogre系统是否自动建立一个渲染窗口来给用户使用,这里我们传递了true,意思是让Ogre给我们提供渲染窗口,当然我们也可以通过手动自定义渲染窗口,我们会在之后使用多窗口的时候再来学习。第二个参数是设置窗口的标题,这里如果你没有提供你自己的标题,那程序就会使用默认的"OGRE Render Window"。
Root对象还可以帮助你获得系统中其它对象的指针,比如:场景管理器(SceneManager),绘制系统(RenderSystem),还有其它的资源管理器(Resource Managers)等等。我们在第六步的代码中就是通过Root获得场景管理器的指针。之后在第七和第八步中的代码我们已经做过介绍,这里不再重复。
最后,如果你的OGRE运行在连续绘制模式(continuous rendering mode)下时,也就是说,你想尽可能的不断刷新所有需要绘制的对象时(这个场景常常运用于游戏(Games)或演示(Demos)中,而不是一般的窗口工具(windowed utilities)),根对象有一个函数叫做"startRendering",调用该函数后,程序将不断地重绘直到所有的绘制窗口关闭为止,或者,你可以通过帧监听器(FrameListener)对象来告诉绘制窗口停止重绘。
添加模型
完成了上面的准备工作,我们现在开始向我们的应用中添加模型,在mRoot->startRendering();这行代码之前继续添加如下代码:
Ogre::ResourceGroupManager::getSingleton().addResourceLocation("../../Samples/Media/packs/Sinbad.zip","Zip"); Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups(); Ogre::Entity*ent = mSceneMgr->createEntity("Sinbad.mesh"); mSceneMgr->getRootSceneNode()->attachObject(ent); |
编译并运行程序,你将会发现窗口中和以前一样显示这Sinbad的人物模型:
通过任务管理器结束此程序,或者停止调试(SHIFT+F5)。
我们来分析一下上面的代码:第一行代码,我们通过ResourceGroupManager的addResourceLocation函数增加资源位置,也就是告诉资源管理器要到哪儿去找你想要加载的资源,第一个参数代表了一个文件夹或ZIP压缩包的路径,这里就是我们将要使用的Sinbad模型及其纹理的位置,第二个参数表示它是什么类型的,通常它可以是ZIP文件类型,或者文件夹类型,然后调用ResourceGroupManager的initialiseAllResourceGroups函数初始化所有先前定义的资源组,之后我们就可以通过createEntity函数加载使用我们的模型了。但是我们或许可以发现如果我们每个资源都通过上面这种方式增加其资源位置未免显得有些冗余,我们可以借鉴ExampleApplication中的那种方式,通过使用加载一个叫做resources.cfg的配置文件统一加载我们所需的资源位置列表。
Resources.cfg文件
下面列出了resources.cfg文件中的部分内容
# Resources required by the sample browser and most samples. [Essential] Zip=../../Samples/media/packs/SdkTrays.zip FileSystem=../../Samples/media/thumbnails
# Common sample resources needed by many of the samples. # Rarely used resources should be separately loaded by the # samples which require them. [Popular] FileSystem=../../Samples/media/fonts FileSystem=../../Samples/media/materials/programs FileSystem=../../Samples/media/materials/scripts FileSystem=../../Samples/media/materials/textures ………… ………… |
通过上面的片段,我们可以看到,类似[Essential]样式的代表了一个片段,它是资源组的名称,其中"General"代表了默认的资源组名称,而FileSystem和Zip指示了所使用“档案”类型,其中FileSystem表示一个文件系统为基础的资源“档案”,而Zip表示了资源路径是一个Zip压缩文件的资源“档案”,这和前面我们提到的addResourceLocation的参数是对应的。如果你实现了自定义的“档案”类型,可以在这里调用。
下面就让我们一起修改我们的代码使之能够从resources.cfg加载不同资源组资源位置列表,注意由于在debug模式下,Ogre给我们提供的文件名为resources_d.cfg,所以下面的代码我们加载的文件名为resources_d.cfg,而release模式下提供给我们的文件名是resources.cfg,如果我们感觉不方便可以把二者改为一样的名称。
第一步,删除通过addResourceLocation 函数加载Zip文件的那一行代码,替换为如下代码:
// Load resource paths from config file Ogre::ConfigFilecf; cf.load("resources_d.cfg"); // Go through all sections & settings in the file Ogre::ConfigFile::SectionIteratorseci = cf.getSectionIterator(); Ogre::StringsecName, typeName,archName; while (seci.hasMoreElements()) { secName = seci.peekNextKey(); Ogre::ConfigFile::SettingsMultiMap *settings =seci.getNext(); Ogre::ConfigFile::SettingsMultiMap::iteratori; for (i =settings->begin();i != settings->end(); ++i) { typeName = i->first; archName = i->second; Ogre::ResourceGroupManager::getSingleton().addResourceLocation(archName,typeName, secName); } } |
编译并运行程序,你将会发现同样能正常显示人物模型。
通过任务管理器结束此程序,或者停止调试(SHIFT+F5)。
代码分析:
ConfigFile类是Ogre中用来读取和解析脚本使用的格式的,我们这里正好可以用来解析读取我们需要资源的配置文件resources.cfg。它在外层迭代资源配置文件中的每一个片段(每一个片段也是一个组名,如"General"和"Popular"),然后对每个片段再遍历其中每一个名字/值对,最后对每个名字/值对调用ResourceGroupManager的addResourceLocation()方法来设置资源定位。这段代码的核心也就是addResourceLocation处的代码,这里的循环其实就相当如多次调用addResourceLocation函数添加各个资源位置。另外,大家或许可以看到我们这里的资源路径都是使用的相对路径,同样,我们也可以使用绝对路径,但是笔者建议大家使用相对路径,这样当改变程序安装位置的时候不易出错。
重构代码
有时候或许我们想当然的认为程序只要实现了我们所需要的功能,就算达到了我们的目的,其实不然,笔者认为真正优秀的代码不仅仅是能达到所需的功能,更应该是易维护的,应是能让更多的人看懂的,如果我们把所有代码都堆叠在一起,或许我们刚写完代码时自己是能看懂的,但是当别人来读你的代码的时候如果很吃力这就不算是较好的代码,一段时间后如果我们自己都读不懂我们自己曾经写过的代码,那么可以说我们写的代码是很糟糕的,因此,我们现在需要把我们前面写的代码重构一下,让其不仅实用,而且美观大方。
下面是重构后的所有代码,大家可以仔细和原来的代码比较一下:
#include <windows.h> #include "Ogre.h"
class MyApplication { public: MyApplication(); ~MyApplication(); void go(); protected: bool setup(); void setupResources(); bool configure(); void chooseSceneManager(); void createCamera(); void createViewports(); void loadResources(); void createScene(); private: Ogre::Root*mRoot; Ogre::RenderWindow*mWindow; Ogre::SceneManager*mSceneMgr; Ogre::Camera*mCamera; }; MyApplication::MyApplication() { mRoot = NULL; mWindow = NULL; mSceneMgr = NULL; mCamera = NULL; mWindow = NULL; } MyApplication::~MyApplication() { if (mRoot) { delete mRoot; } } bool MyApplication::setup() { mRoot = new Ogre::Root("plugins_d.cfg"); setupResources(); if (!configure()) { return false; } chooseSceneManager(); createCamera(); createViewports(); loadResources(); createScene(); return true; } void MyApplication::setupResources() { // Load resource paths from config file Ogre::ConfigFilecf; cf.load("resources_d.cfg"); // Go through all sections & settings in the file Ogre::ConfigFile::SectionIteratorseci = cf.getSectionIterator(); Ogre::StringsecName, typeName,archName; while (seci.hasMoreElements()) { secName = seci.peekNextKey(); Ogre::ConfigFile::SettingsMultiMap *settings =seci.getNext(); Ogre::ConfigFile::SettingsMultiMap::iteratori; for (i =settings->begin();i != settings->end(); ++i) { typeName = i->first; archName = i->second; Ogre::ResourceGroupManager::getSingleton().addResourceLocation(archName,typeName, secName); } } } bool MyApplication::configure() { if (!mRoot->showConfigDialog()) { return false; } mWindow = mRoot->initialise(true,"IMedia Project"); return true; } void MyApplication::chooseSceneManager() { mSceneMgr = mRoot->createSceneManager(Ogre::ST_GENERIC); } void MyApplication::createCamera() { mCamera = mSceneMgr->createCamera("MyCamera"); mCamera->setPosition(Ogre::Vector3(0,0,80)); mCamera->lookAt(Ogre::Vector3(0,0,-300)); mCamera->setNearClipDistance(5); } void MyApplication::createViewports() { Ogre::Viewport*vp = mWindow->addViewport(mCamera); vp->setBackgroundColour(Ogre::ColourValue(0,0,0)); mCamera->setAspectRatio(Ogre::Real(vp->getActualWidth())/Ogre::Real(vp->getActualHeight())); } void MyApplication::loadResources() { Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups(); } void MyApplication::createScene() { Ogre::Entity*ent = mSceneMgr->createEntity("Sinbad.mesh"); mSceneMgr->getRootSceneNode()->attachObject(ent); } void MyApplication::go() { if (!setup()) return; mRoot->startRendering(); }
INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT ) { MyApplication app; app.go(); return 0; } |
通过比较我们似乎发现现在的代码量好像比原来把代码全部放在WinMain函数中显得比较多了,但是我们现在只是搭建基础的框架,随着我们工程的逐渐变大,相信你会深刻体会到这样封装的好处,我们这里把大部分功能都分离出来,基本上接近了Ogre给我们提供的模板代码,只是我们还有一些功能没添加进去,如帧监听等功能,这些我们马上就会涉及到。我们可以通过跟踪的方式,查看一下每个函数调用的时机,这样我们对Ogre应用的启动序列现在应该就有了一个初步的了解。
笔者提示:我们在写代码的时候最好加上清晰的注释,这样才能让别人更好的读懂每处的功能,我们这里为了代码的简洁因此省去了大部分注释的步伐,请读者朋友见谅。另外,我们还可以把类的声明放在一个头文件中,把各个成员函数的实现放在另一个cpp文件中,这样模块的划分会更好些。 |
添加帧监听和用户输入功能
为了尽快让我们的应用程序能够正常退出,我们是时候添加帧监听和用户输入的功能了,这里我们的添加方式和前面《帧监听与用户输入》章节的方式基本类似,因此如果大家有任何疑惑的地方可以查阅相关章节内容。
第一步,让我们的MyApplication类继承Ogre中进行帧监听所需的类Ogre::FrameListener以及我们用户输入功能中OIS提供的OIS::KeyListener和OIS::MouseListener类,如下所示:
class MyApplication:public Ogre::FrameListener, public OIS::KeyListener, public OIS::MouseListener |
第二步,添加头文件#include"OIS\OIS.h";
第三步,给MyApplication类添加private类型的成员变量:
bool mShutDown; //OIS Input devices OIS::InputManager* mInputManager; OIS::Mouse* mMouse; OIS::Keyboard* mKeyboard; |
第四步,在MyApplication类的构造函数中初始化新添加的成员变量:
mShutDown = false; mInputManager = NULL; mMouse = NULL; mKeyboard = NULL; |
第四步,为MyApplication类添加protected类型的成员函数:
void createFrameListener(void); |
实现代码如下:
void MyApplication::createFrameListener(void) { OIS::ParamList pl; size_t windowHnd = 0; std::ostringstream windowHndStr; mWindow->getCustomAttribute("WINDOW", &windowHnd); windowHndStr << windowHnd; pl.insert(std::make_pair(std::string("WINDOW"), windowHndStr.str())); mInputManager = OIS::InputManager::createInputSystem( pl ); mKeyboard=static_cast<OIS::Keyboard*>(mInputManager->createInputObject( OIS::OISKeyboard, true )); mMouse = static_cast<OIS::Mouse*>(mInputManager->createInputObject( OIS::OISMouse, true )); mMouse->setEventCallback(this); mKeyboard->setEventCallback(this); mRoot->addFrameListener(this); } |
第五步,为MyApplication类添加以下一些protected类型的成员函数:
// Ogre::FrameListener virtual bool frameRenderingQueued(const Ogre::FrameEvent& evt); // OIS::KeyListener virtual bool keyPressed( const OIS::KeyEvent &arg ); virtual bool keyReleased( const OIS::KeyEvent &arg ); // OIS::MouseListener virtual bool mouseMoved( const OIS::MouseEvent &arg ); virtual bool mousePressed( const OIS::MouseEvent &arg, OIS::MouseButtonID id ); virtual bool mouseReleased( const OIS::MouseEvent &arg, OIS::MouseButtonID id ); |
实现代码如下所示:
bool MyApplication::frameRenderingQueued(const Ogre::FrameEvent& evt) { if(mWindow->isClosed()) return false; if(mShutDown) return false; //Need to capture/update each device mKeyboard->capture(); mMouse->capture(); return true; } bool MyApplication::keyPressed( const OIS::KeyEvent &arg ) { if (arg.key == OIS::KC_ESCAPE) { mShutDown = true; } return true; } bool MyApplication::keyReleased( const OIS::KeyEvent &arg ) { return true; } bool MyApplication::mouseMoved( const OIS::MouseEvent &arg ) { return true; } bool MyApplication::mousePressed( const OIS::MouseEvent &arg, OIS::MouseButtonID id ) { return true; } bool MyApplication::mouseReleased( const OIS::MouseEvent &arg, OIS::MouseButtonID id ) { return true; } |
第六步,在MyApplication类的setup()函数的最后添加如下代码(return true;行代码之前):
createFrameListener(); |
第七步,在MyApplication类的析构函数中添加如下代码(if (mRoot)行代码之前):
if( mInputManager ) { mInputManager->destroyInputObject(mMouse ); mInputManager->destroyInputObject(mKeyboard ); OIS::InputManager::destroyInputSystem(mInputManager); mInputManager = 0; } |
编译并运行程序,我们会发现现在程序可以正常退出了,这里的代码和前面《帧监听与用户输入》章节添加的代码没什么不同,因此这里不再详细解释,到此为止,我们基本上完成了Ogre启动序列的一个基本流程,如果读者朋友感兴趣,还可以把前面我们添加的用键盘和鼠标控制模型和相机位置的代码添加进去,这样就和Ogre为我们提供的ExampleApplication或者通过我们介绍的那个OgreApplication插件达到的效果一样了。
使用我们自己的渲染循环
查看我们的代码,我们会发现,程序真正的启动渲染循环是在mRoot->startRendering();这一行代码初,但是仅仅通过这一行代码我们还是无法洞悉到底Ogre为我们做了怎样的工作,我们有两种方式可以解决这个问题:第一种方式,查看Ogre的源代码,这种方式也是笔者极力向读者朋友推荐的,笔者建议读者朋友多多学习Ogre的源代码,Ogre是开源的,通过查看其源代码我们可以学到更多的东西,我们也会在相关章节带领着大家一起学习到底如何有效的跟踪查看Ogre的源代码,这里我们暂且不讨论这个话题,如果读者朋友想;第二种方式,我们自己手动模拟创建我们自己的主循环,通过这样的方式我们会对Ogre的启动序列有更深入的理解。
第一步,为MyApplication类添加一个private类型的成员变量:
bool _keepRunning; |
第二步,在MyApplication类的构造函数中初始化新添加的成员变量:
_keepRunning = true; |
第三步,为MyApplication类添加一个protected类型的成员函数,代码如下:
void renderOneFrame() { Ogre::WindowEventUtilities::messagePump(); _keepRunning = mRoot->renderOneFrame(); } |
第四步,替换go函数中的mRoot->startRendering();这行代码为如下代码:
while(_keepRunning) { renderOneFrame(); } |
编译并运行程序,你会发现和原来的程序具有同样的运行效果。
代码分析:
通过上面的代码我们现在就有了自己的主渲染循环,我们通过一个bool型标志变量指示着循环是否终止,在第三步中的renderOneFrame函数中,我们首先调用Ogre3D的一个帮助函数messagePump,它可以处理所有的窗口事件,之后我们调用Ogre提供的renderOneFrame函数,正如其名,它渲染一帧,它会再调用那些注册过的FrameListener的frameStarted, frameRenderingQueued和 frameEnded 进行事件处理,如果这其中任何一个返回了false,renderOneFrame函数就会返回false,这样我们的渲染循环就结束了,由此我们的程序也会终止。这样我们就完成了我们自定义的渲染循环的所有操作,如果读者朋友查看源代码的话,你会发现Ogre3D后台所做的工作其实和我们现在做的是一样的,只是当我们手动控制的时候,我们对程序的课操控性就会很灵活了。
PS:很久以前就打算把学习Ogre中遇到的知识分享给大家(虽然只是一些皮毛),但是说来惭愧,一直很懒,直到最近才抽出点时间写一些自己的理解(Ogre入门级的东西),所以难免会有很多不足之处,分享是一种快乐,同时也希望和大家多多交流!
(由于在Word中写好的东西发布到CSDN页面需要重新排版(特别是有很多图片时),所以以后更新进度可能会比较慢,同时每章节发布的时间可能不一样(比如说我首选发布的是第二章,其实第一章就是介绍下Ogre的前世今生神马的,相信读者早就了解过~~~),但是我会尽量做到不影响大家阅读,还望大家谅解。)
上述内容很多引用了网上现有的翻译或者内容,在此一并谢过(个人感觉自己有些地方写得或者翻译的不好),还望见谅,转载请注明此项!