本文介紹 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下載包中有離線版本)