1. 程式人生 > >OGRE啟動順序--以及官方示例程式分析

OGRE啟動順序--以及官方示例程式分析

下面內容第一部分可見sina部落格:

一、不建立類,直接在main函式中實現的過程

首先新增標頭檔案:#include "Ogre\Ogre.h" (使用Ogre名稱空間)

第一步:建立一個空窗體要的物件

總結成:R-WMAV(WMV)共5個物件

R-root物件(外觀模式),W-視窗window物件,M-場景管理器manager,A-camera物件,V-視口view物件

具體:

Ogre::Root* root = new Ogre::Root("plugins_d.cfg");

//此函式另外兩個引數預設值是ogre.cfg和ogre.log。之所以用第一個引數是因為它預設是plugins.cfg,我們需要除錯版本的。

if(!root->showConfigDialog())

{

  return -1;

}

Ogre::RenderWindow* window = root->initialise(true,"Ogre3D Beginners Guide");

Ogre::SceneManager* sceneManager = root->createSceneManager(Ogre::ST_GENERIC);

Ogre::Camera* camera = sceneManager->createCamera("Camera");

camera->setPosition(Ogre::Vector3(0,0,50));

camera->lookAt(Ogre::Vector3(0,0,0));

camera->setNearClipDistance(5);

camera->setAspectRatio(Ogre::Real(viewport->getActualWidth())/ Ogre::Real(viewport->getActualHeight()));

//上面一句就好像把攝像機變成和視口一樣的,只不過可能是視口的縮小版。這樣可以保證攝像機照出來是什麼樣,在視口顯示成什麼樣。

完畢!

第二步:新增資源

//新增資源位置,Eample中是用迴圈的方法從resource.cfg中把字串用下面一句載入的

Ogre::ResourceGroupManager::getSingleton().addResourceLocation("../../Media/packs/Sinbad.zip","Zip");

//載入資源到程式【自動解析上面位置中的所有檔案,並把如材質資源載入解析進來並以材質名稱標記】

Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups();

如果資源比較多,不建議用上面方法一句一句的新增資源位置,建議像下面一樣用resource.cfg檔案:

//---------------------使用resource.cfg載入資源過程--開始--------------------------------------

Ogre::ConfigFile cf; //Ogre 3D的一個助手類,用於解析resource.cfg檔案(鍵值對組成)

cf.load(“resources_d.cfg”);

Ogre::ConfigFile::SectionIterator sectionIter = cf.getSectionIterator(); //得到區塊的迭代器

Ogre::String sectionName, typeName, dataname;

while (sectionIter.hasMoreElements()) //如果當前區塊裡面有東西

{

    sectionName = sectionIter.peekNextKey();

   //上一句獲取區塊名稱,不用管Next,沒用,區塊指標並沒有移動【區塊中的一個指向key的指標移動了】

    Ogre::ConfigFile::SettingsMultiMap *settings = sectionIter.getNext();

    //上一句先get內容,再next,由於區塊.getNext了,所以區塊指標移動指向下一個區塊【getNext是慣用方法

    Ogre::ConfigFile::SettingsMultiMap::iterator i;

    //下面是在區塊中遍歷每個設定

   for (i = settings->begin(); i != settings->end(); ++i)

   {     

      typeName = i->first;

      dataname = i->second;

          Ogre::ResourceGroupManager::getSingleton().addResourceLocation(dataname,typeName, sectionName);

      }

關於以上程式碼指標太多,可以結合下圖理解:

Ogre啟動順序(總結)

上圖中MuliMap這個集合體就是區塊的內容,另外區塊還包含Key。實際上resource.cfg中以[General]作為一區段的開始,以另一個[sectionname]的出現作為結束

//---------------------使用resource.cfg載入資源過程--結束--------------------------------------

第三步:新增實體等場景內容,然後開始渲染

Ogre::Entity*ent=_sceneManager->createEntity("Sinbad.mesh"); 

_sceneManager->getRootSceneNode()->attachObject(ent); //繫結實體到場景節點

root->startRendering();  //開始渲染吧。

return 0;  //最後別忘了養成好習慣返回,當然這裡寫在main中,也可以無返回值

二、組裝成類的過程

組裝成類(2+3):因為上面的所有內容直接寫在main函式中,不好看,最後組裝成一個類MyApplication

(1)類MyApplication至少包含2個成員變數:_root和_sceneManager ,其它物件要在不同的函式成員中通過這兩個物件建立。

(2)類中至少包含3個函式成員:

void loadResources()  載入資源進入程式

void createScene()      建立各種實體,並繫結到場景節點

int startup() 

啟動函式,此函式裡面先建立上面提到的5個物件(R-WMAV),然後呼叫loadResources()和createScene(),最後啟動渲染迴圈和返回0。即:_root->startRendering();  return 0;

在main中只需要呼叫app.startup();即可。

注意:解構函式中只需要一句:delete _root;即可,ogre堅持誰建立,誰負責銷燬原則,所以只用顯示銷燬root物件,其它物件因為是從root建立來的,會自動銷燬。(還要注意,如果用第三方外掛不一定用這個原則)

三、新增監聽器

(1)建立一個監聽器類,必須繼承於Ogre::FrameListener(訪問者模式的一個公共介面)

class MyFrameListener : public Ogre::FrameListener

{

public:        

         bool frameStarted(const Ogre::FrameEvent& evt)

         {

              return false;

         }

         bool frameEnded(const Ogre::FrameEvent& evt)

        {

        return false;

        }

        bool frameRenderingQueued(const Ogre::FrameEvent& evt)

       {

             return false;

       }

}

解釋:我們直接從FrameListener介面繼承(訪問者模式中具體訪問者的父類介面)。這個介面包含了三個虛擬函式,這三個虛擬函式需要我們在自己的FrameListener中覆寫。

另外,呼叫完frameRenderingQueued,快取得到交換。且xxStarted()和xxEnded()是一般繪製的開始和介紹。

這三個函式呼叫如下圖:

Ogre啟動順序(總結)
(2)建立一個具體幀監聽器,並新增如被監聽者Root的列表中(用root->add之類的函式),如下:

_listener = new MyFrameListener();  //在MyApplication已經添加了成員MyFrameListener* _listener;

_root->addFrameListener(_listener); //把訪問者加入Root被監聽者物件的列表。

上面的語句中把_listener(指標)看成立MyApplication的一個成員了,為了以後用著方便,但別忘了在MyApplication的解構函式中新增delete _listener。

現在討論下,作為觀察者的幀監聽器是怎麼起作用的呢?即他是怎麼知道什麼時候我該其作用呢?

:_root->startRendering()中呼叫了_root->renderOneFrame()。【我估計,_root->renderOneFrame()事件中先呼叫被觀察者的NotifyObserver()之類的函式通知觀測者該更新狀態了,即呼叫監聽器的Update之類的函式。而Update之類的函式裡面又依次呼叫frameStartedframeRenderingQueuedframeEnded三個函式從而更新了變化矩陣等資料,然後再根據這些變化的資料如“世界觀察投影矩陣”開始真正畫一幀圖。

最後,提一下一個細節,上面所有的程式碼中的物件都使用Ogre名稱空間,所以只需要ogre.h標頭檔案即可,不用新增任何其它標頭檔案。

四、新增輸入

首先包含標頭檔案:#include "OIS\OIS.h"  使用OIS名稱空間

大致過程如下(只關心鍵盤用到的鍵盤滑鼠介面,不關心它們是怎麼來的):在frameStarted等函式中通過鍵盤介面Keyboard或滑鼠介面獲取值,根據這些值跟新一些資料或執行一些任務。如下程式碼所示

bool frameStarted(const Ogre::FrameEvent& evt)

{

  _Keyboard->capture();  //_Keyboard是監聽器類的一個成員,OIS::Keyboard* _Keyboard

  if(_Keyboard->isKeyDown(OIS::KC_ESCAPE))

  {

    return false;

  }

  return true;

}

但是上面的_Keyboard必須和我門的視窗window關聯以便知道操作的是那個視窗傳來的鍵盤資料,這些都是輸入管理器OIS::InputManager做的事情。這個管理其負責管理鍵盤和滑鼠介面。

以上就是粗略理解的過程。

具體實現如下:

在監聽器類中至少新增四個成員變數:

OIS::InputManager* _InputManager;

OIS::Mouse* _mouse;

OIS::Keyboard* _Keyboard;

Ogre::RenderWindow* win;  //這個win在new監聽器物件時提供。

注:如果需要操作攝像機,還有新增攝像機成員,移動速度控制成員、場景節點、實體、甚至是視口等,所有成員通過建構函式獲取值如下面的win,這裡為了簡化理解,就不加了。

下面是建構函式

MyFrameListener(Ogre::RenderWindow* win)

{

    OIS::ParamList parameters; //OIS使用引數列表來初始化(建立系統)

    unsigned int windowHandle = 0;

    std::ostringstream windowHandleString;

    //獲得RenderWindow的控制代碼並轉換它為一個字串

      win->getCustomAttribute("WINDOW", &windowHandle);

      windowHandleString << windowHandle;

     //使用鍵值”WINDOW,windowHandleString”來新增字串型別的控制代碼到引數表

   parameters.insert(std::make_pair("WINDOW", windowHandleString.str()));

   //使用引數列表來建立輸入管理器InputManager

     _InputManager = OIS::InputManager::createInputSystem(parameters);

     //用管理器來建立鍵盤介面keyboard,滑鼠介面Mouse

   _Keyboard = static_cast<OIS::Keyboard*>(_InputManager->createInputObject( OIS::OISKeyboard, false ));

    _mouse = static_cast<OIS::Mouse*>(_InputManager->createInputObject( OIS::OISMouse, false ));

別忘了解構函式:

~MyFrameListener()

{

  //輸入系統不是ogre的內容,不滿足誰建立誰負責銷燬原則,需要顯示的呼叫輸入管理器的銷燬函式

  _InputManager->destroyInputObject(_Keyboard);

  _InputManager->destroyInputObject(_mouse);

  //銷燬管理器

  OIS::InputManager::destroyInputSystem(_InputManager);

}

五、自己寫渲染迴圈(不使用_root->startRendering()

首先新增一個bool型的公有成員到MyApplication類的例項app中

bool _keepRunning;

然後在MyApplication類中新增一個函式成員:

void renderOneFrame() //MyApplication中的renderOneFrame,就兩句話

{

    Ogre::WindowEventUtilities::messagePump(); 

   //處理來自作業系統的視窗事件資訊【從應用程式訊息泵中去資訊,這些資訊會發送給註冊過的輸入管理器OIS::InputManager

   _keepRunning = _root->renderOneFrame();

   //【估計在_root->renderOneFrame()中先呼叫被觀察者的NotifyObserver()之類的函式通知觀測者更新狀態了,即呼叫監聽器的Update之類的函式。而Update之類的函式裡面又依次呼叫frameStarted、frameRenderingQueued、frameEnded三個函式從而更新了變化矩陣等資料,然後再根據這些變化的資料如“世界觀察投影矩陣”開始真正畫一幀圖。】

}

最後在main函式中新增一個自己的迴圈即可

while(app._keepRunning)

{

  app.renderOneFrame();

}

編譯並執行即可。 

基礎的學習可以從下面的basic tutorial開始:

-------------------------分割線-------------------------------
ORGE從一個root的構造初始化整個流程: mRoot = new Root(pluginsPath, mResourcePath + "ogre.cfg", mResourcePath + "Ogre.log"); ogre.cfg內容大致如下: Render System=OpenGL Rendering Subsystem [OpenGL Rendering Subsystem] FSAA=0 Full Screen=No RTT Preferred Mode=PBuffer Video Mode=800 x 600 可以看出,在Root建構函式中傳入的ogre.cfg檔案,可以決定用視窗模式還是全屏模式。 第一個引數是plugin.cfg的檔案,一個典型的plugin內容大致如下: # Defines plugins to load # Define plugin folder PluginFolder=/lib/OGRE/ # Define D3D rendering implementation plugin Plugin=RenderSystem_GL.so Plugin=Plugin_ParticleFX.so Plugin=Plugin_BSPSceneManager.so Plugin=Plugin_OctreeSceneManager.so Plugin=Plugin_CgProgramManager.so 可以看出,主要是OGRE的架構的特性,render等都是一個plugin,這個config檔案主要是指示載入的plugin的清單和路徑。 一個最簡單的流程: 程式碼可以通過Root->showConfigDialog()來讓使用者初始化render系統; 然後用Root->initialise(true)來初始化Root;此處會返回跟平臺相關的mWindow。 然後用Root->createSceneManager(ST_GENERIC, "ExampleSMInstance")來建立SceneManager; 然後開始相機的初始化,         mCamera = mSceneMgr->createCamera("PlayerCam");         // Position it at 500 in Z direction         mCamera->setPosition(Vector3(0,0,500));         // Look back along -Z         mCamera->lookAt(Vector3(0,0,-300));         mCamera->setNearClipDistance(5); 然後是viewPort, ViewPort* vp = mWindow->addViewPort(mCamera);這裡的mWindow, mCamera可以在上面看到來源; vp->setBackgroundColour(ColourValue(0,0,0)); //alter the camera aspect ratio to match the viewport mCamera->setAspectRatio(Real(vp->getActuralWidth()) / Real(vp->getActuralHeight())); 然後可以設定mipmap等級: TextureManager::getSingleton().setDefaultNumMipmaps(5); 最後給Root裝上frameListener(): 這一步也是OGRE的設計之一,一旦跑起來,OGRE會通過這個listener來給程式碼輸送資訊,如按鍵事件,frameStart, frameEnd等等: mFrameListener = new ExampleFrameListener (mWindow, mCamera); mFrameListener->showDebugOverlay(true); mRoot->addFrameListener(mFrameListener); ----------------------官方Sample的流程---------------------- WinMain()在sampleBrowser.cpp中,很簡單 OgreBites::SampleBrowser sb; sb.go(); 其中,sb.go()因為sampleBrowser雖然繼承了sampleContext,而且go()在sampleContext中是個virtual函式,但是sampleBrowser沒有自己的實現,所以呼叫的其實是sampleContext的go()。 go()中建立了root,傳入了plugin.cfg和orge.cfg(此檔案在MyDocument\OGRE下)。 在選中相應的sample後,如果使用者點選了start,那麼程式轉移到 runSample(Sample* s) [SampleBrowser.h],此函式最後呼叫到 SampleContext::runSample(s) [SampleContext.h],然後呼叫到 _setup(mWindow, mKeyboard, mMouse, mFSLayer) [SdkSample.h],這個函式的實現程式碼裡面,基本上就會呼叫到各個實際的sample的介面了。 因為各個sample的具體實現都是繼承了SdkSample.h,而後者又繼承自Sample.h,因此,Sample.h中的虛擬介面是可以被例項化的,而且,Sample.h中的virtual介面沒有純虛的,所以,可以根據需要選擇實現或不實現。 go()中的初始化好了後,必須呼叫到root->startRendering(),該函式中實現一個while迴圈,來實現每一幀的開始和結束 [sampleContext.h],這個實現如下程式碼:     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;         } 可以看出,迴圈的退出只有2個可能:queueEndRendering()這個介面函式被使用者呼叫到(這個函式是唯一一個會把mQueuedEnd設定成true的地方);或者,renderOneFrame()返回為false; -----------------典型的ogre流程----------------------- int main() { // load resources from resource.cfg bying using Root() construct // Ogre::ResourceGroupManager::getSingleton().addResourceLocation(); setupResources();  //config ogre rendering system root.showConfigDialog(); root.initialise(); //choose scenemanager mSceneMgr = mRoot->createSceneManager(Ogre::ST_GENERIC); //create camera mCamera = mSceneMgr->createCamera("PlayerCam"); mCamera->setPosition(Ogre::Vector3(0,0,80)); mCamera->lookAt(Ogre::Vector3(0,0,-300)); mCamera->setNearClipDistance(5); //create viewport Ogre::Viewport* vp = mWindow->addViewport(mCamera); vp->setBackgroundColour(Ogre::ColourValue(0,0,0)); mCamera->setAspectRatio(vp->getActualWidth()) / vp->getActualHeight()); //setup mipmap level TextureManager::getSingleton().setDefaultNumMipmaps(5); //NOW initialise the resource Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups(); //add frame listener mRoot->addFrameListener(this); //THEN this END-LESS LOOP INTERNAL function Root->startRendering(); //if comes here, means quit } -----------------ogre FrameListener.frameRenderingQueued----------------------- 這個功能是某個版本後加的,主要是為了能讓GPU在渲染的時候,同時CPU還能做些事情,實現效率化。 這裡是原文解釋: The usefulness of this event comes from the fact that rendering 
commands are queued for the GPU to process. These can take a little 
while to finish, and so while that is happening the CPU can be doing 
useful things. Once the request to 'flip buffers' happens, the thread 
requesting it will block until the GPU is ready, which can waste CPU 
cycles. Therefore, it is often a good idea to use this callback to 
perform per-frame processing. Of course because the frame's rendering 
commands have already been issued, any changes you make will only 
take effect from the next frame, but in most cases that's not noticeable.
bool Root::renderOneFrame(void)
{
//下面的程式碼使用者會因玩家操作更新所有的需要更新的邏輯、畫面等
if(!_fireFrameStarted())
returnfalse
;
//下面的功能詳細在後面的function中 if (!_updateAllRenderTargets())
returnfalse
;
return _fireFrameEnded();
}
bool Root::_updateAllRenderTargets(void)
{
//下面的程式碼可以通知到GPU開始為這一陣工作
mActiveRenderer->_updateAllRenderTargets(false);
//算定GPU應該在工作中,CPU把能幹的事情也幹些吧,不要閒著。
//如果沒有這句,可能的事情是GPU工作中,CPU直接執行下一句_swapxxx,但是GPU會block住CPU, //直到它做完渲染和swap bool ret = _fireFrameRenderingQueued();
mActiveRenderer->_swapAllRenderTargetBuffers(mActiveRenderer->getWaitForVerticalBlank());

return ret;
}

建立ROOT