介紹
在這一課裡,你將學習如何啟動OGRE,而不用樣本架構。學習完之後,你將會建立你自己的Ogre應用程式,且不使用ExampleApplication或ExampleFrameListener。
你能在這裡找到本課的代碼。當你學習本課程時,你應該逐個地往你的工程裡添加代碼,編譯並觀察結果。
準備開始
初始代碼
#include #include #include #include using namespace Ogre; class ExitListener : public FrameListener { public: ExitListener(OIS::Keyboard *keyboard) : mKeyboard(keyboard) { } bool frameStarted(const FrameEvent& evt) { mKeyboard->capture(); return !mKeyboard->isKeyDown(OIS::KC_ESCAPE); } private: OIS::Keyboard *mKeyboard; }; class Application { public: void go() { createRoot(); defineResources(); setupRenderSystem(); createRenderWindow(); initializeResourceGroups(); setupScene(); setupInputSystem(); setupCEGUI(); createFrameListener(); startRenderLoop(); } ~Application() { } private: Root *mRoot; OIS::Keyboard *mKeyboard; OIS::InputManager *mInputManager; CEGUI::OgreCEGUIRenderer *mRenderer; CEGUI::System *mSystem; ExitListener *mListener; void createRoot() { } void defineResources() { } void setupRenderSystem() { } void createRenderWindow() { } void initializeResourceGroups() { } void setupScene() { } void setupInputSystem() { } void setupCEGUI() { } void createFrameListener() { } void startRenderLoop() { } }; #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 { try { Application app; app.go(); } catch(Exception& e) { #if OGRE_PLATFORM == PLATFORM_WIN32 || 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; }
從零開始
開始之前,讓我們站在更高的層次上看一下啟動過程是如何工作的:
- 建立Root對象。
- 定義Ogre將要使用的資源。
- 選擇並設定渲染系統(即DirectX, OpenGL等)。
- 建立渲染視窗(Ogre所處的視窗)。
- 初始化你要使用的資源。
- 用這些資源來建立一個情境。
- 設定第三方庫或外掛程式。
- 建立一些幀監聽器。
- 啟動渲染迴圈
本課將逐一介紹這些步驟。請注意步驟1-4必須嚴格按順序進行,而5(初始化資源)和6(建立情境)可以放在更後一點,如果你喜歡的話。但我這裡不推薦,除非你真的很瞭解你的工作順序。
啟動Ogre
建立Root對象
這個很簡單,Root對象是Ogre庫裡最核心的,在你用這個引擎做其它事情之前,你必須先建立它。找到Application::createRoot方法,添加如下代碼:
mRoot = new Root();
Root的建構函式需要三個參數。第一個是外掛程式設定檔的名稱和路徑。第二個是Ogre設定檔的路徑(它告訴Ogre關於顯卡、顯示設定等資訊)。最後一個是記錄檔的名稱和路徑。因為我們不需要修改任何一個屬性,所以用預設的就好了。
資源
注意: 你最好在SDK的bin/release目錄裡找到resources.cfg,看看它的內容是很有協助的。
下面我們要定義程式將要使用的資源,包括紋理、模型、指令碼等等。請記住,你必須預先定義好你的資源,在Ogre能使用它們之前,你還必須對它進行初始化。在這一步裡,我們來定義所有程式可能使用的資源。為止,我們把每一個資源所在的檔案夾添加到ResourceGroupManager。找到defineResources方法,並添加如下代碼:
String secName, typeName, archName; ConfigFile cf; cf.load("resources.cfg");
這裡使用了一個Ogre的ConfigFile類來解析"resources.cfg"裡的所有的資源,但並不是把它們裝入到Ogre(你必須手工添加)。記住,你可以自由地使用你自己的檔案格式和解析器,只需要用你自己的解析器來替換ConfigFile的。裝入資源的方式並不十分重要,只要你能把資源添加到ResourceGroupManager。現在我們解析好了cfg檔案,還需要把各部分添加到ResourceGroupManager中。以下代碼啟動一個迴圈:
ConfigFile::SectionIterator seci = cf.getSectionIterator(); while (seci.hasMoreElements()) {
每次迴圈裡,我們再迴圈一次,提取它裡面所有的內容:
secName = seci.peekNextKey(); ConfigFile::SettingsMultiMap *settings = seci.getNext(); ConfigFile::SettingsMultiMap::iterator i;
最後,我們添加組件名稱(那一組資源的),資源類型(zip,檔案夾等等),以及資源本身的檔案名稱,給ResourceGroupManager:
for (i = settings->begin(); i != settings->end(); ++i) { typeName = i->first; archName = i->second; ResourceGroupManager::getSingleton().addResourceLocation(archName, typeName, secName); } }
這個方法從config檔案添加所有的資源,但它只告訴Ogre它們在哪。但如果你打算使用它們,你還必須初始化它們。我們在後面“初始化資源”一節來介紹。
建立渲染系統
接下來,我們需要選擇一個渲染系統(在Windows機器上通常是DirectX或者OpenGL),然後配置它。大多數Demo程式使用的是一個Ogre配置對話方塊,這是一個很好的一個東東。Ogre提供了一種儲存使用者佈建的方法,意味著除了第一次需要設定外,以後都不需要了。找用setupRenderSystem方法,並添加以下代碼:
if (!mRoot->restoreConfig() && !mRoot->showConfigDialog()) throw Exception(52, "User canceled the config dialog!", "Application::setupRenderSystem()");
在if語句裡的第一部分,我們嘗試恢複這個config檔案。如果函數返回false,意味著檔案不存在,則我們應該顯示配置對話方塊,也就是if語句的第二部分。如果仍然返回false,意味著使用者取消了配置對話方塊(也就是他們想退出程式)。在這個例子裡,我們拋出了一個異常,但實際上簡單地返回false且關閉應用程式,這樣可能更好。由於 restoreConfig 和 showConfigDialog 也可能拋出異常,儲存這些真正的異常可能更好。然而,這將增加了教程的不必要的複雜度,我在這裡只使用了一個異常。
我還建議,如果你在Ogre的啟動過程中捕獲了一個異常,你最好在catch裡刪除這個ogre.cfg檔案。因為他們在配置對話方塊裡進行的設定可能導致了問題的發生,所以他們需要更改它。你也可以不使用它,關閉配置對話方塊可以節省開發時間,因為你不必每次程式運行時確認這些顯示設定。
如果你不打算使用Ogre的配置對話方塊,你需要手動地設定渲染系統。以下是一個基本的例子:
// 不要把這些添加到程式中 RenderSystem *rs = mRoot->getRenderSystemByName("Direct3D9 Rendering Subsystem"); mRoot->setRenderSystem(rs); rs->setConfigOption("Full Screen", "No"); rs->setConfigOption("Video Mode", "800 x 600 @ 32-bit colour");
你可以使用Root::getAvailableRenderers來瞭解有哪些渲染系統可供你的程式使用。一旦你得到了一個渲染系統,你能夠使用RenderSystem::getConfigOptions來查看有哪些選項可以提供給使用者。利用這兩個方法,你可以建立你自己的配置對話方塊。
建立渲染視窗
目前我們選擇了一個渲染系統,我們還需要一個渲染Ogre的視窗。實際上有許多種方式來實現,但這裡只介紹兩種。
如果你希望Ogre為你建立一個渲染視窗,這是相當容易的事情。找到createRenderWindow方法,這麼寫:
mRoot->initialise(true, "Tutorial Render Window");
第一個參數表示是否讓Ogre為你建立一個渲染視窗。否則,你可以自己建立一個渲染視窗,通過使用win32 API、wxWidgets或其它Windows/Linux的GUI系統。關於在Windows下的一個簡單例子是這樣:
// 別把這些添加到程式裡 mRoot->initialise(false); HWND hWnd = 0; // Get the hWnd of the application! NameValuePairList misc; misc["externalWindowHandle"] = StringConverter::toString((int)hWnd); RenderWindow *win = mRoot->createRenderWindow("Main RenderWindow", 800, 600, false, &misc);
在這裡你仍然使用Root::initialise,第一個參數設定成了false。然後,你必須擷取你希望Ogre渲染的視窗的控制代碼。你如何取得它,完全決定於你用來建立視窗的GUI工具箱(在Linux下我估計這有一點區別)。你擁有了它之後,你通過NameValuePairList handle把這個控制代碼賦予"externalWindowHandle"。Root::createRenderWindow方法被用來從你建立的視窗來建立RenderWindow對象。想瞭解更多,參考這個方法的API文檔。
初始化資源
現在我們建立了Root對象、渲染系統、以及渲染視窗,繼續。接下來是初始化我們將要使用的資源。從mesh到指令碼,所有的東西,在某一時刻,我們只用到這些資源其中的一小部分。為了減少記憶體消耗,我們可以只載入正在使用的資源。為止,我們把資源分解成各種部分,只在運行時初始化它們。在本課裡,我們不將詳細介紹。其它的地方有一個專門介紹資源的教程。初始化資源之前,我們應該設定紋理mipmap的預設值。找到initializeResourceGroups方法,添加如下代碼:
TextureManager::getSingleton().setDefaultNumMipmaps(5); ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
這樣程式就擁有了所有已經被初始化的資源群組以供使用。
建立情境
你應該瞭解在把各種東西添加到情境之前,你要做三件事:建立情境管理器(SceneManager)、建立攝像機(Camera)、建立視口(Viewport)。在setupScene方法裡添加如下代碼:
SceneManager *mgr = mRoot->createSceneManager(ST_GENERIC, "Default SceneManager"); Camera *cam = mgr->createCamera("Camera"); Viewport *vp = mRoot->getAutoCreatedWindow()->addViewport(cam);
如果你需要的話,可以建立多個情境管理器、多個攝像機,但當你真正打算使用攝像機把事物渲染到螢幕上時,請確保你已經為它添加了視口中。為止,你要藉助RenderWindow類,它在“建立渲染視窗”一節裡被建立。由於我們沒有這個對象的指標,所以我們通過Root::getAutoCreatedWindow方法來擷取它。
這三件事完了以後,你可以盡情地往你的情境裡添加物體了。
設定第三方庫
OIS
雖然在OGRE裡,OIS不是唯一的選擇,但它是最好的之一。我來簡單介紹一下OIS如何在程式裡啟動。若真想要使用這個庫,請參考這個教程,以及OIS自身的文檔。
設定無緩衝輸入
OIS使用一個統一的InputManager,它比較難配置,但一旦正確地建立之後,非常好使用。實際上,它只是需要Ogre渲染視窗的控制代碼。幸好,由於我們使用的是自動建立的視窗,Ogre使之簡化了。找到setupInputSystem方法,添加如下代碼:
size_t windowHnd = 0; std::ostringstream windowHndStr; OIS::ParamList pl; RenderWindow *win = mRoot->getAutoCreatedWindow();
win->getCustomAttribute("WINDOW", &windowHnd); windowHndStr << windowHnd; pl.insert(std::make_pair(std::string("WINDOW"), windowHndStr.str())); mInputManager = OIS::InputManager::createInputSystem(pl, false);
這樣InputManager就建好了,但為了從鍵盤、滑鼠、或是手柄中獲得輸入,你還必須建立這些對象:
try { mKeyboard = static_cast(mInputManager->createInputObject(OIS::OISKeyboard, false)); //mMouse = static_cast(mInputManager->createInputObject(OIS::OISMouse, false)); //mJoy = static_cast(mInputManager->createInputObject(OIS::OISJoyStick, false)); } catch (const OIS::Exception &e) { throw Exception(42, e.eText, "Application::setupInputSystem"); }
我把Mouse和Joystick對象注釋掉了,因為這裡我們不使用,但它們就是這樣建立的。InputManager::createInputObject的第二個參數是指是否使用帶緩衝輸入(在以前的教程裡介紹過)。把第二個參數設定成false,建立了一個無緩衝的輸入對象,我們這裡就使用這個。
建立幀監聽器
不論你是否使用帶緩衝的輸入,每一幀之間你必須調用鍵盤、滑鼠或者手柄對象的捕獲方法。在本課的起始程式碼裡已經做了這些。對於無緩衝的輸入,你只需要做這些。每一幀之間,你都可以調用滑鼠鍵盤的各種方法來查詢這些對象的狀態。而對於帶緩衝的輸入,我們還需稍微地做一些事情。
為了使用帶緩衝的輸入,你要添加一個類,以處理輸入。兩件事情要做,一是實現適合的監聽介面,二是把你建立的這個類註冊成為這個事件的回調。在前面的課程裡,你能找到帶緩衝國輸入的例子,下面是這個類的代碼:
// 這別它添加到工程 class BufferedInputHandler : public OIS::KeyListener, public OIS::MouseListener, public OIS::JoyStickListener { public: BufferedInputHandler(OIS::Keyboard *keyboard = 0, OIS::Mouse *mouse = 0, OIS::JoyStick *joystick = 0) { if (keyboard) keyboard->setEventCallback(this);
if (mouse) mouse->setEventCallback(this);
if (joystick) joystick->setEventCallback(this); }
// KeyListener virtual bool keyPressed(const OIS::KeyEvent &arg) { return true; } virtual bool keyReleased(const OIS::KeyEvent &arg) { return true; }
// MouseListener virtual bool mouseMoved(const OIS::MouseEvent &arg) { return true; } virtual bool mousePressed(const OIS::MouseEvent &arg, OIS::MouseButtonID id) { return true; } virtual bool mouseReleased(const OIS::MouseEvent &arg, OIS::MouseButtonID id) { return true; }
// JoystickListener virtual bool buttonPressed(const OIS::JoyStickEvent &arg, int button) { return true; } virtual bool buttonReleased(const OIS::JoyStickEvent &arg, int button) { return true; } virtual bool axisMoved(const OIS::JoyStickEvent &arg, int axis) { return true; }
};
我建議您僅實現自己之後實際需要的監聽函數.
帶緩衝輸入的疑難雜症
如果你沒有如自己所希望那樣在程式中獲得輸入緩衝,那麼你需要檢查以下這些事情:
- 你在調用InputManager::createInputObject()函數建立輸入裝置時是否設定第二個參數(開啟輸入緩衝)為true?
- 你是否為每個輸入緩衝對象設定了一個setEventCallback()函數?
- 是否有別的類調用了setEventCallback()函數?(注意:OIS僅僅允許一個事件回調給一個對象,你不能把一個事件回調給兩個類處理.)
如果你檢查了這三個問題依然有錯誤的話,請將您的問題發表在Ogre協助論壇.
CEGUI
CEGUI是直接整合到Ogre裡的一個非常靈活的GUI庫。雖然在這一課裡,我們不使用CEGUI的任何功能,但我還是來簡單介紹一下它的設定。CEGUI需要RenderWindow和SceneManager以供渲染。
SceneManager *mgr = mRoot->getSceneManager("Default SceneManager"); RenderWindow *win = mRoot->getAutoCreatedWindow();
// CEGUI setup mRenderer = new CEGUI::OgreCEGUIRenderer(win, Ogre::RENDER_QUEUE_OVERLAY, false, 3000, mgr); mSystem = new CEGUI::System(mRenderer);
就這樣,你就能使用CEGUI了。如果你程式中途你改變了SceneManager,你必須通知CEGUI應該渲染到一個新的SceneManager。為止,使用OgreCEGUIRenderer::setTargetSceneManager就行了。
渲染迴圈以及最後的工作
幀監聽
在我們開始渲染迴圈,並讓程式運行之前,我們還需要添加幀監聽器。請注意我已經建立一個非常簡單的幀監聽器,名為ExitListener,它等待ESC鍵被按下,以退出程式。在你的程式裡,我可能需要更多的幀監聽器,來做更複雜的事情。看一下這個ExitListener,確保瞭解它的流程。然後把如下代碼添加到createFrameListener方法裡:
mListener = new ExitListener(mKeyboard); mRoot->addFrameListener(mListener);
渲染迴圈
最後我們要做的是啟動Ogre的渲染迴圈。非常簡單,找到startRenderLoop方法,並添加如下代碼:
mRoot->startRendering();
這樣程式就開始渲染,直到FrameListener返回false。你也可以提取單個幀,並在每幀之間做一些事情。Root::renderOneFrame渲染一幀,如何任何一個FrameListener返回false,它也返回false:
// 別把這段加到工程裡 while (mRoot->renderOneFrame()) { // Do some things here, like sleep for x milliseconds or perform other actions. }
然而,在我看來,你應該把所有while迴圈裡的代碼轉移到FrameListener。我能想到的這種模式的唯一用處就是,中途睡眠某些毫秒,從而人為地降低幀率到某一個值。一般在FrameListener裡不會這樣做,因為它會與FrameEvent::timeSinceLastFrame變數搞混淆。
清理
最後一件事是,當程式終止時,我們要對所建立的所有對象進行清理。為止,我們將按照與建立時相反的順序來刪除或銷毀這些對象。我們從OIS開始下手,它有一個專門的方法來銷毀它的對象。找到~Application方法,並添加如下代碼:
mInputManager->destroyInputObject(mKeyboard); OIS::InputManager::destroyInputSystem(mInputManager);
現在我們來清理CEGUI,只要刪除對象即可:
delete mRenderer; delete mSystem;
最後,我們要刪除Root和FrameListener對象。當刪除Root對象時,我們建立的其它對象(SceneManager, the RenderWindow等)也會一併刪除。
delete mListener; delete mRoot;
好了! 你現在可以編譯並運行你的程式了,雖然你將只能看見一個黑屏,因為我們並沒有往情境添加任何東西。如果在編譯裡遇到連結問題,確保CEGUIBase_d.lib和OgreGUIRenderer_d.lib添加到了連結器的輸入裡(這是debug模式的,如果是release模式,去掉_d)。現在你應該對Ogre的啟動過程比較熟悉了,可以拋開樣本架構而使用其它的了。然而,為了簡單化,後面的課程將仍然使用那個樣本架構。
如果你對樣本架構中的其它部分感興趣,請參考更深入的文章。