本文介绍 OGRE 3D 1.9 程序的启动过程,即从程序启动到3D图形呈现,背后有哪些OGRE相关的代码被执行。会涉及的OGRE类包括:php
建议在阅读本文时参考OGRE API Reference,OGRE官方给的API Reference没有类的协做图,能够本身用Doxygen生成API文档,见:Bullet的学习资源(用Doxygen生成API文档)。html
关于如何安装OGRE和如何配置一个能够运行的OGRE HelloWorld程序见:OGRE 1.9 的第一个程序(OGRE HelloWorld程序)。node
本节全部代码以下,能够先迅速浏览,而后看后面详细解释,后面将用“启动代码”来指代这段代码:windows
1 #include<OgreRoot.h> 2 #include<OgreRenderSystem.h> 3 #include<OgreRenderWindow.h> 4 #include<OgreConfigFile.h> 5 #include<OgreResourceGroupManager.h> 6 #include<OgreLogManager.h> 7 #include<OgreViewport.h> 8 #include<OgreSceneManager.h> 9 #include<OgreCamera.h> 10 #include<OgreLight.h> 11 #include<OgreEntity.h> 12 13 int main(int argc, char *argv[]) 14 { 15 Ogre::Root* mRoot; 16 Ogre::RenderWindow* mWindow; 17 Ogre::SceneManager* mSceneMgr; 18 Ogre::Camera* mCamera; 19 20 // 建立Root,在调用OGRE任何功能以前必须已经建立了Root 21 mRoot = new Ogre::Root("plugins.cfg","ogre.cfg","Ogre.log"); 22 23 // 设定 RenderSystem 24 Ogre::RenderSystem *rs = 25 mRoot->getRenderSystemByName("OpenGL Rendering Subsystem"); 26 mRoot->setRenderSystem(rs); 27 rs->setConfigOption("Full Screen", "No"); 28 rs->setConfigOption("Video Mode", "800x600 @ 32-bit colour"); 29 // 另外一种方法是: if(!mRoot->showConfigDialog()) return false; 30 31 // 初始化 RenderSystem 32 mRoot->initialise(false); 33 34 // 建立 RenderWindow 35 int hWnd = 0; 36 Ogre::NameValuePairList misc; 37 misc["externalWindowHandle"] = Ogre::StringConverter::toString((int)hWnd); 38 mWindow = mRoot->createRenderWindow("Win Ogre", 800, 600, false, &misc); 39 // 上2步的另外一种实现是: mWindow = mRoot->initialise(true, "Win Ogre"); 40 41 // 建立SceneManager,将渲染目标绑定到RenderWindow 42 mSceneMgr = mRoot->createSceneManager(Ogre::ST_GENERIC); 43 // Create one camera 44 mCamera = mSceneMgr->createCamera("PlayerCam"); 45 mCamera->setNearClipDistance(5); 46 // Create one viewport, entire window 47 Ogre::Viewport* vp = mWindow->addViewport(mCamera); 48 vp->setBackgroundColour(Ogre::ColourValue(0,0,0)); 49 // Alter the camera aspect ratio to match the viewport 50 mCamera->setAspectRatio( 51 Ogre::Real(vp->getActualWidth()) / Ogre::Real(vp->getActualHeight())); 52 53 // 加载资源,该歩不能早于RenderSystem的初始化和RenderWindow的建立 54 // 若是使用OverlaySystem,该歩也不能早于OverlaySystem的建立 55 Ogre::ConfigFile cf; cf.load("resources.cfg"); 56 Ogre::ConfigFile::SectionIterator seci = cf.getSectionIterator(); 57 Ogre::String secName, typeName, archName; 58 while( seci.hasMoreElements() ){ 59 secName = seci.peekNextKey(); 60 Ogre::ConfigFile::SettingsMultiMap *settings = seci.getNext(); 61 Ogre::ConfigFile::SettingsMultiMap::iterator i; 62 for( i=settings->begin(); i!=settings->end(); ++i ){ 63 typeName = i->first; 64 archName = i->second; 65 Ogre::ResourceGroupManager::getSingleton(). 66 addResourceLocation(archName, typeName, secName); 67 } 68 } 69 Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups(); 70 71 // 构造及设置场景 72 mSceneMgr->setShadowTechnique(Ogre::SHADOWTYPE_STENCIL_ADDITIVE); 73 mSceneMgr->setAmbientLight(Ogre::ColourValue(0.2f, 0.2f, 0.2f)); 74 75 Ogre::Entity* entNinja = mSceneMgr->createEntity("entNinja", "ninja.mesh"); 76 Ogre::SceneNode* nodeNinja = mSceneMgr->createSceneNode("nodeNinja"); 77 mSceneMgr->getRootSceneNode()->addChild(nodeNinja); 78 nodeNinja->attachObject(entNinja); 79 Ogre::Entity* entSphere = mSceneMgr->createEntity("entSphere", "sphere.mesh"); 80 Ogre::SceneNode* nodeSphere = mSceneMgr->createSceneNode("nodeSphere"); 81 mSceneMgr->getRootSceneNode()->addChild(nodeSphere); 82 nodeSphere->attachObject(entSphere); 83 nodeNinja->setPosition(-50,-100,0); 84 nodeSphere->translate(50,0,100); 85 Ogre::Light* pointLight1 = mSceneMgr->createLight("pointLight1"); 86 pointLight1->setType(Ogre::Light::LT_POINT); 87 pointLight1->setDiffuseColour(Ogre::ColourValue::White); 88 pointLight1->setSpecularColour(Ogre::ColourValue::White); 89 pointLight1->setPosition(-400,200,-200); 90 91 mCamera->setPosition(Ogre::Vector3(0,0,-250)); 92 mCamera->lookAt(Ogre::Vector3(0,0,0)); 93 94 // 渲染循环 95 Ogre::LogManager::getSingleton().logMessage(">>Rendering"); 96 mRoot->startRendering(); 97 98 // 释放资源,目前只需释放Root 99 delete mRoot; 100 101 return 0; 102 }
运行结果截图:设计模式
1. 启动过程概览api
咱们概要地看OGRE的启动,OGRE WIKI Basic Tutorial 6: The Ogre Startup Sequence中摘出下面这段,注意它和上面的代码(“启动代码”)是有差异的,各步骤的顺序不一样:ide
The basic Ogre life cycle looks like this:函数
总的来讲,先是初始化,最后启动渲染循环。我将全部这些类的关系总结以下图(不是什么UML图,就大体理解吧):oop
看完后面的详细解释后能够回过头来看这段,那时你就会对OGRE的启动有个大体印象。学习
2. 建立Root
在调用OGRE任何功能以前,首先要实例化一个Root类,该Root实例将直接或间接指向全部其余类的实例。一个OGRE程序有且只有一个Root对象,所以Root类使用Singleton设计模式(单例模式,继承自Singleton<Root>)。说到Singleton,OGRE的不少类都是Singleton,后面还会讲的。
Root类的构造函数原型以下:
Root (const String &pluginFileName="plugins"OGRE_BUILD_SUFFIX".cfg", const String &configFileName="ogre.cfg", const String &logFileName="Ogre.log")
其中OGRE_BUILD_SUFFIX宏在Release下定义为空,Debug下定义为"_d"。三个参数是三个文件名。
pluginFileName是插件配置文件,该文件指示OGRE要加载哪些插件,一个plugins.cfg文件的例子以下,其中#表示注释:
# Defines plugins to load # Define plugin folder PluginFolder=. # Define plugins # Plugin=RenderSystem_Direct3D9 Plugin=RenderSystem_GL Plugin=Plugin_ParticleFX Plugin=Plugin_BSPSceneManager Plugin=Plugin_CgProgramManager Plugin=Plugin_PCZSceneManager Plugin=Plugin_OctreeZone Plugin=Plugin_OctreeSceneManager
configFileName文件设置渲染系统(OpenGL或者Direct3D)及其参数,如抗锯齿采样数(FSAA),一个针对OpenGL驱动的配置文件ogre.cfg例子以下:
Render System=OpenGL Rendering Subsystem [OpenGL Rendering Subsystem] Colour Depth=32 Display Frequency=N/A FSAA=8 Fixed Pipeline Enabled=Yes Full Screen=No RTT Preferred Mode=FBO VSync=No VSync Interval=1 Video Mode=1024 x 768 sRGB Gamma Conversion=No
logFileName文件是OGRE程序的日志文件,在OGRE程序能够插入写日志的代码,日志文件方便对OGRE程序的调试。向日志文件写入信息的代码的一个例子以下:
Ogre::LogManager::getSingleton().logMessage(">>Rendering");
这里的LogManager是另外一个使用Singleton设计模式的类,这种类使用静态方法getSingleton获取全局惟一的类实例。
“启动代码”中建立Root对象的代码在第21行:
mRoot = new Ogre::Root("plugins.cfg","ogre.cfg","Ogre.log");
3. 设定RenderSystem,初始化
RenderSystem类对渲染系统(底层的OpenGL或Direct3D)进行抽象,它至关因而执行渲染的设备。给 Root 添加一个RenderSystem实例的最简单方式是调用Ogre::Root:: showConfigDialog方法,运行时系统将弹出以下对话框,让用户选择要使用的图形驱动,以及相应的参数:
if(!mRoot->showConfigDialog()) return false;
咱们在这个对话框所作的设置被记录在ogre.cfg文件中(见上面第2节)。也能够不用对话框,而在程序中设置,也就是说在程序中设置咱们在对话框所选的项:
Ogre::RenderSystem *rs = mRoot->getRenderSystemByName("OpenGL Rendering Subsystem"); mRoot->setRenderSystem(rs); rs->setConfigOption("Full Screen", "No"); rs->setConfigOption("Video Mode", "800x600 @ 32-bit colour");
“启动代码”使用的是后者,代码在第24-27行。
若是不想每次都弹出对话框选择渲染系统,能够用以下代码:
if( !(mRoot->restoreConfig() || mRoot->showConfigDialog()) ) return false;
restoreConfig方法读入ogre.cfg文件来代替对话框设置,还记得C/C++逻辑运算表达式求值的短路性质吧,若是mRoot->restoreConfig()返回true(存在ogre.cfg文件),mRoot->showConfigDialog()将不被执行。
RenderSystem对象建立后须要初始化,Ogre::Root::initialise(bool, const String, const String)方法就是初始化root的RenderSystem的,若是第一个bool参数为true,将自动建立窗口,“启动代码”没有这样作,在第31行:
mRoot->initialise(false);
另外还要说的是,OGRE做为一个跨平台的高层3D图形库,对图形系统进行了高度抽象,这种抽象使用户不须要关心底层技术(如OpenGL或Direct3D、win32或Xwindow),但程序的执行必然会用到底层功能(如具体渲染任务必然是OpenGL或Direct3D执行)。OGRE(或者其余不少开源程序库)是这样作到这一点的:用户使用基类(如RenderSystem和RenderWindow)接口和OGRE进行交互,代码执行时程序自动根据系统配置调用相应子类的实现来执行命令(这得益于面向对象的继承性和多态性)。RenderSystem类的继承图以下:
对于咱们,使用的是OpenGL图形驱动,因此到程序执行时,实际使用的是GLRenderSystem的实现。其实RenderSystem压根就是个抽象类,不能被实例化。
4. 建立 RenderWindow
RenderWindow是对窗口的抽象,该窗口用来显示渲染结果(对于离线渲染或渲染到纹理则不须要窗口)。建立窗口最简单的方法是在调用Ogre::Root::initialise方法时传入true做为第一个参数:
mWindow = mRoot->initialise(true, "Win Ogre");
但“启动代码”为了代码的清晰,使用了手动建立RenderWindow的方法:
int hWnd = 0; Ogre::NameValuePairList misc; misc["externalWindowHandle"] = Ogre::StringConverter::toString((int)hWnd); mWindow = mRoot->createRenderWindow("Win Ogre", 800, 600, false, &misc);
注意上面使用的NameValuePairList类是用来构造参数的,你可能发现了,OGRE的不少参数都使用string数据类型。
5. 建立SceneManager,将渲染目标绑定到RenderWindow
SceneManager类管理OGRE的场景图形(Scene Graph),《Ogre 3D 1.7 Beginner's Guide》的Chapter 6中将SceneManager的功能总结为两个方面:
SceneManager不是Singleton,能够从Root建立一个(或多个)SceneManager,“启动代码”的第41行建立了一个普通类型的SceneManager:
mSceneMgr = mRoot->createSceneManager(Ogre::ST_GENERIC);
有了SceneManager,就能够从这个Factory建立场景的对象了,主要是Camera, SceneNode, Entity, Light,构建场景(构建场景树)留到后面说,这里说Camera和RenderWindow的对应关系。
Camera是场景到窗口输出的媒介,负责将3D场景映射到2D窗口,这个映射涉及到另外一个类Viewport,Viewport将Camera对场景的“拍摄”结果“贴”到窗口的所有可绘制区域的一个矩形部分。一个Root能够有多个SceneManager,一个SceneManager中也能够有多个Camera,但每一个Camera都须要一个Viewport对应到窗口的一个矩形区域。如今你应该知道怎样把一个场景的不一样视角,或者多个场景绘制到一个窗口的不一样区域了吧。
“启动代码”中建立Camera的代码在第43行:
mCamera = mSceneMgr->createCamera("PlayerCam"); mCamera->setNearClipDistance(5);
其中也设置了Camera的近裁剪面。“启动代码”中建立建Viewport的代码在随后的第46行:
// Create one viewport, entire window Ogre::Viewport* vp = mWindow->addViewport(mCamera); vp->setBackgroundColour(Ogre::ColourValue(0,0,0)); // Alter the camera aspect ratio to match the viewport mCamera->setAspectRatio( Ogre::Real(vp->getActualWidth()) / Ogre::Real(vp->getActualHeight()));
其中也设置了Viewport背景颜色和Camera长宽比例,addViewport是RenderWindow的方法,并以Camera为参数,这样就把RenderWindow和Camera联系起来了,正如咱们所分析的。另外addViewport方法还有其余参数用来指定Viewport在窗口的哪一区域,上述代码使用了缺省参数,即将Viewport对应到整个窗口。
同第4节最后说的,RenderWindow是抽象类,具体的和窗口相关的功能实际是由子类实现的,在windows上,这个子类是Win32Window。
6. 加载资源
OGRE的资源文件是OGRE的一个特点,最多见的资源文件莫过于Mesh(.mesh)和Material(.material)了,注意Mesh是一个可渲染物体,而不只仅是一个网格,Material定义可渲染物体除几何信息外的其余全部属性,能够是而不限于颜色、纹理、着色器什么的。
资源文件的一个好处就是当修改了物体的外观等信息以后,不须要从新编译程序,若是将物体的顶点数据什么的写在代码里那就固然要从新编译啦。资源文件的缺点,程序在启动时要对资源文件进行解析(分析脚本),这增长了程序启动时间,这也是HelloWorld程序须要好几秒以后才能看见图形的缘由。另外一个缺点,对于初学者来讲,最初可能就是想画一个长方体,但在OGRE里,你就须要建立Mesh资源。固然啦,OGRE做为面向而不限于3D游戏的3D引擎,强大的资源管理能力能够大大提升开发效率,应当说,正是资源文件的庞杂换来了程序代码的简洁。
有关OGRE对资源文件处理的细节见:Resources and ResourceManagers,要使用一个程序外定义(即脚本定义)的资源,须要:
上面的第一步在“启动代码”中对应代码以下,位于第54-67行:
Ogre::ConfigFile cf; cf.load("resources.cfg"); Ogre::ConfigFile::SectionIterator seci = cf.getSectionIterator(); Ogre::String secName, typeName, archName; while( seci.hasMoreElements() ){ secName = seci.peekNextKey(); Ogre::ConfigFile::SettingsMultiMap *settings = seci.getNext(); Ogre::ConfigFile::SettingsMultiMap::iterator i; for( i=settings->begin(); i!=settings->end(); ++i ){ typeName = i->first; archName = i->second; Ogre::ResourceGroupManager::getSingleton(). addResourceLocation(archName, typeName, secName); } }
其中"resources.cfg"是资源文件名字,文件内容以下(为了简洁,删减了一些):
# Resources required by the sample browser and most samples. [Essential] Zip=../../media/packs/SdkTrays.zip Zip=../../media/packs/profiler.zip FileSystem=../../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=../../media/fonts FileSystem=../../media/models Zip=../../media/packs/cubemap.zip Zip=../../media/packs/cubemapsJS.zip [General] FileSystem=../../media # Materials for visual tests [Tests] FileSystem=../../media/../../Tests/Media
Ogre::ConfigFile是一个资源配置文件解析的辅助类,相似于XML解析,和代码对应,Essential、Popular、Popular是secName,这是OGRE为方便对资源进行管理而分的组,每一个settings的格式为:typeName= archName(参数类型=参数值)。
注意下面这句代码:
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(archName, typeName, secName);
OGRE有不少xxxManager类,它们负责管理特定事物,如ResourceGroupManager提供对资源组的操做,LogManager提供写日志文件功能,SceneManager管理场景图形等等。这些Manager中的不少,好比LogManager和ResourceGroupManager使用Singleton设计模式,能够调用静态方法getSingleton获取全局惟一的实例。但SceneManager不是单例模式的,由于一个Root能够有多个场景图形(场景树)。
“启动代码”中declare资源的代码以下,在第68行:
Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
这里采起的是简单粗暴的方式,解析资源目录的全部脚本,怪不得程序启动后要等那么久了。
注意declare资源有时不能进行的太早,例如,不能早于RenderSystem的初始化和RenderWindow的建立,若是使用OverlaySystem,也不能早于OverlaySystem的建立。缘由同第3节最后分析的,由于资源的解析具体是由子类实现的,在没有肯定使用的是RenderSystem和RenderWindow的哪一个子类前,不能肯定使用哪一个解析资源的子类,例如,RenderSystem的不一样子类GLRenderSystem或D3D9RenderSystem,使用的纹理解析的类不一样,以下图:
截止目前你应该了解plugins.cfg、ogre.cfg、Ogre.log、resources.cfg文件的做用了吧。
7. 构造场景树
目前大多数3D图形库采用了场景图形(Scene Graph)技术,即用场景树来组织场景的全部物体,场景树的节点可分为两种:分支节点和叶节点。分支节点SceneNode(继承自Node)主要负责空间位置变化,叶节点能够为:Entity(可绘制实体),Light,Camera等。关于场景树,最须要了解的是,叶节点对象在世界坐标中的最终位置是由父节点级联决定的。一个典型的场景树以下图:
Entity3的世界坐标由Node五、Node四、Node2联合决定(世界坐标计算方式能够修改)。每一个Node都有一些空间变换方法:setPosition、setOrientation、setScale、translate、rotate、scale,其中前三个是覆盖式修改,后三个是增量式修改。用Ogre::Node::addChild方法链接两个Node,用Ogre::SceneNode::attachObject方法链接Node和叶节点。上图有一个容易混淆的地方:Light1是否是只做用于Node4子树呢,答案是否认的,Light1做用于整个场景树,Camera1也是相似的。Light和Camera老是做用于整个场景树,其上级Node只起到对其世界坐标进行变换的做用。
注意,一个可用的场景树不能有循环路径,以下图的场景树,OGRE程序运行时会抛出异常:
能够调用Ogre::SceneManager::setShadowTechnique方法设置阴影,Ogre::SceneManager::setSkyBox方法设置天空,Ogre::SceneManager::setFog方法设置雾效果。
“启动代码”构建了一个简单的场景,代码在第71-91行:
mSceneMgr->setShadowTechnique(Ogre::SHADOWTYPE_STENCIL_ADDITIVE); mSceneMgr->setAmbientLight(Ogre::ColourValue(0.2f, 0.2f, 0.2f)); Ogre::Entity* entNinja = mSceneMgr->createEntity("entNinja", "ninja.mesh"); Ogre::SceneNode* nodeNinja = mSceneMgr->createSceneNode("nodeNinja"); mSceneMgr->getRootSceneNode()->addChild(nodeNinja); nodeNinja->attachObject(entNinja); Ogre::Entity* entSphere = mSceneMgr->createEntity("entSphere", "sphere.mesh"); Ogre::SceneNode* nodeSphere = mSceneMgr->createSceneNode("nodeSphere"); mSceneMgr->getRootSceneNode()->addChild(nodeSphere); nodeSphere->attachObject(entSphere); nodeNinja->setPosition(-50,-100,0); nodeSphere->translate(50,0,100); Ogre::Light* pointLight1 = mSceneMgr->createLight("pointLight1"); pointLight1->setType(Ogre::Light::LT_POINT); pointLight1->setDiffuseColour(Ogre::ColourValue::White); pointLight1->setSpecularColour(Ogre::ColourValue::White); pointLight1->setPosition(-400,200,-200); mCamera->setPosition(Ogre::Vector3(0,0,-250)); mCamera->lookAt(Ogre::Vector3(0,0,0));
8. 渲染循环
调用Root::startRendering方法进入渲染循环,渲染结束释放Root:
Ogre::LogManager::getSingleton().logMessage(">>Rendering"); mRoot->startRendering(); // 释放资源,目前只需释放Root delete mRoot;
其中使用LogManager这个Singleton类的功能向日志文件中写入了信息。
startRendering函数实现以下:
void Root::startRendering(void) { assert(mActiveRenderer != 0); mActiveRenderer->_initRenderTargets(); // Clear event times clearEventTimes(); // Infinite loop, until broken out of by frame listeners // or break out by calling queueEndRendering() mQueuedEnd = false; while( !mQueuedEnd ) { //Pump messages in all registered RenderWindow windows WindowEventUtilities::messagePump(); if (!renderOneFrame()) break; } }
Root::renderOneFrame方法代码以下:
bool Root::renderOneFrame(void) { if(!_fireFrameStarted()) return false; if(!_updateAllRenderTargets()) return false; return _fireFrameEnded(); }
也能够自行构造渲染循环,这样就能够解决“启动代码”点击关闭窗口程序也不退出的问题了:
while(true) { // Pump window messages for nice behaviour Ogre::WindowEventUtilities::messagePump(); if(mWindow->isClosed()) { return false; } // Render a frame if(!mRoot->renderOneFrame()) return false; }
9. 总结
本文要点总结以下:
好了,关于OGRE的基本启动过程你应该了解的吧,本文并没涉及WindowEventListener、FrameListener等一些事件的处理,也没有涉及鼠标键盘输入,甚至,“启动代码”运行起来后关闭窗口都不能结束程序,这些留到之后再讲吧。
参考文献:
OGRE WIKI Basic Tutorial 6: The Ogre Startup Sequence
OGRE WIKI: Resources and ResourceManagers
Ogre 3D 1.7 Beginner's Guide (Felix Kerger, 2010)
OGRE API Reference(OGRE SDK下载包中有离线版本)