Bootstrap]
Zip=../../Media/packs/OgreCore.zip
# Resource locations to be added to the default path
[General]
FileSystem=../../Media
FileSystem=../../Media/fonts
FileSystem=../../Media/materials/programs
FileSystem=../../Media/materials/scripts
FileSystem=../../Media/materials/textures
FileSystem=../../Media/models
FileSystem=../../Media/overlays
FileSystem=../../Media/particle
FileSystem=../../Media/datafiles/fonts
FileSystem=../../Media/datafiles/imagesets
FileSystem=../../Media/datafiles/layouts
FileSystem=../../Media/datafiles/looknfeel
FileSystem=../../Media/datafiles/schemes
FileSystem=../../Media/datafiles/lua_scripts
FileSystem=../../Media/DeferredShadingMedia
Zip=../../Media/packs/cubemap.zip
Zip=../../Media/packs/cubemapsJS.zip
Zip=../../Media/packs/dragon.zip
Zip=../../Media/packs/fresneldemo.zip
Zip=../../Media/packs/ogretestmap.zip
Zip=../../Media/packs/skybox.zip
代碼7-1相當的清晰明了。檔案中出現的兩個資源群組分別是“Bootstrap”與“General”。其中“General”是系統預設的組並且始終存在,當你把資源添加到這個組,並不會建立新的資源群組。而例子中的Bootstrap組意味著只針對Ogre示範架構ExampleApplication的資來源物件;它是一個可以隨意起的名字(你也可在你的實現中任意使用),它並不會像General一樣要被特殊處理。
FileSystem和Zip指示了所使用“檔案”的類型:其中FileSystem表示一個檔案系統為基礎的資源“檔案”;而Zip表示了資源路徑是一個ZIP壓縮檔的資源“檔案”。如果你實現了自訂的“檔案”類型,可以在這裡調用。
資源定位
一個經常被提出的問題是,“如果沒有resource.cfg我該如何設定資源定位?”讓我們先撇開這個話題來看看ExampleApplication.h中的代碼。代碼7-2中,展示了如何讀取resources.cfg檔案。
代碼7-2:ExampleApplication架構中用於解析resources.cfg檔案的代碼
// Load resource paths from config file
ConfigFile cf;
cf.load("resources.cfg");
// Go through all sections & settings in the file
ConfigFile::SectionIterator seci = cf.getSectionIterator();
String secName, typeName, archName;
while (seci.hasMoreElements())
{
secName = seci.peekNextKey();
ConfigFile::SettingsMultiMap *settings = seci.getNext();
ConfigFile::SettingsMultiMap::iterator i;
for (i = settings->begin(); i != settings->end(); ++i)
{
typeName = i->first;
archName = i->second;
ResourceGroupManager::getSingleton().addResourceLocation(
archName, typeName, secName);
}
}
注意:resources.cfg只是為了方便示範程式提供的檔案;在Ogre的API中你找不到任何處理resources.cfg檔案的方法。ConfigFile類(和它內部的類)是Ogre的一部分;它用來讀取和解析指令碼使用的格式。你可以藉助這個類來處理你自己的設定檔,可以如同處理Ogre自身定義指令碼一樣簡單。
上面這段代碼(代碼7-2)被Ogre所有的示範程式調用,以用來解析讀取資源設定檔resources.cfg。它在外層迭代檔案中的每一個片段(每一個片段也是一個組名,Bootstrap和General),然後對每個片斷再曆遍其中每一個名字/值對。最後對每個名字/值對調用ResourceGroupManager的addResourceLocation()方法來設定資源定位。
代碼7-1中指令碼所依賴的核心其實只有addResourceLocation()。下面列出通過這個方法實現的與上面設定檔代碼相同效果的的寫入程式碼樣本(代碼7-3)。
代碼7-3:直接的,寫入程式碼方式實現與代碼7-2一樣的效果
ResourceGroupManager *rgm = ResourceGroupManager:;getSingletonPtr();
rgm->addResourceLocation(“../../media/packs/OgreCore.zip”,”Zip”,”Bootstrap”);
rgm->addResourceLocation(“../../media/”,”FileSystem”,”General”);
rgm->addResourceLocation(“../../media/fonts”,”FileSystem”,”General”);
// 你可以在後面再添加其他需要的資源定位
在這裡可以留意一下上面代碼中對資源定位的路徑設定都是系統相對路徑。這並不代表Ogre的資源定位無法支援絕對路徑,但是如果你決定使用絕對路徑,當程式安裝到其他使用者的機器上時候,就可能會失效。所以配製各種目錄時候盡量使用相對路徑是一種很好的習慣。
資源初始化
資源初始化需要一定的順序支援。在正式添加資源前,你需要首先初始化相應的資源群組。而在初始化資源本身之前,你需要建立所需的渲染視窗,這是因為對資源指令碼的解析可能會依賴GPU的配置。
警告:請認真閱讀上面的一段文字,因為再Ogre論壇中最常出現的問題都是因為上面所提的原因引起的。
代碼:7-4 初始化資源定位
// 初始化所有先前定義的資源群組
ResourceGroupManager::getSingleton().initialiseAllResourceGroup();
// 或者每次交替初始化一個資源群組
ResourceGroupManager::getSingleton().intialiseResourceGroup(“General”);
ResourceGroupManager::getSingleton().intialiseResourceGroup(“Bootstrap”);
在代碼7-4中其實包含了兩種不同的初始化方式,第一種是提供給懶人用來一次初始化所有未初始化資源群組的方法。下面的是提供給希望更精確控制初始化過程的人使用的每次初始化一個資源群組的方法。
資源卸載
你可以在任何希望的時候從記憶體中卸載資源(不論是單獨的或者一組)。Ogre會負責協助你維護代碼的安全性,當你在其他地方再次使用資源,Ogre會協助你重新載入。如果當你卸載的時候仍然有系統在使用資源,Ogre會協助你禁止這個卸載動作(至少是對3D渲染相關的資源)。
下面的代碼展示了資源群組管理器ResourceGroupManager的全部卸載資源的介面:
ResourceGroupManager::getSingleton().unloadResourceGroup(“Bootstrap”, true);
ResourceGroupManager::getSingleton().unloadUnreferencedResourcesInGroup(“Bootstrap”,true);
第一行代碼提供了卸載一整個資源群組的方法,而第二行的方法只會卸載當前沒有引用的資源。當資源被卸載之後,其資料被移出記憶體,但相應的執行個體和聲明資訊都還存在(當再次使用時候系統會負責重新載入工作)。
在方法中的true參數告知系統卸載我們允許卸載的資源;換句話說這種調用方法不會卸載我們標記為“不可卸載”的資源。在預設的情況下,方法會使用預設參數false來卸載全部資源。
清理或者銷毀資源群組
在字面上看來清理和銷毀資源群組差別並不大,但是在執行中要特別注意這兩種操作的細微差別。
清理資源群組只是負責卸載和刪除資源群組裡面的資源:
ResourceGroupManager::getSingleton().clearResourceGroup(“Bootstrap”);
而銷毀資源群組不單單“清理”了裡面的資源,還“銷毀”資源群組本身:
ResourceGroupManager::getSingleton().destroyResourceGroup(“Bootstrap”);
卸載單獨的資源
如果你希望仔細的管理每一個資源的生存周期,你可以通過調用資源本身的unload方法執行卸載。
// 假設pEntity是一個Entity執行個體的指標
MeshPtr meshPtr = pEntity->getMesh();
meshPtr->unload();
載入和重新載入資源群組
這裡有一個可以載入同一資源群組所有資源的方法:
ResourceGroupManager::getSingleton().loadResourceGroup(“Bootstrap”);
你也可以通過額外的參數來定義載入那部分資源,其中包括“世界地圖”和其他“普通”的資源。參看下面兩個方法的調用:
//例如只載入或者重新載入世界地圖,將使用下面的方法調用
ResourceGroupManager::getSingleton().loadResourceGroup(“Bootstrap”, false, true);
//只載入或者重新載入“普通”組中的資源:
ResourceGroupManager::getSingleton().loadResourceGroup(“Bootstrap”, true, false);
資源群組載入的回調事件
如果在資源管理其中註冊了相應的監聽對象,Ogre會載入資源時候通知你各種載入時間。這種功能最通常的應用是UI介面的載入條等很有趣的功能。
代碼7-5:展示了一個通過實現資源監聽介面ResourceGroupListene來實現的UI視窗空近代碼(這裡虛構了一個被稱為ProgressMeter的類型)
// 注意:例子中使用的ProgressMeter是個虛構的類型
class LoadingProgresslistener : public ResourceGroupListener
{
public:
LoadingProgressListener(ProgressMeter& meter) :
m_progressMeter(meter){ m_currentResource = 0;}
// 資源群組ResourceGroupListener事件方法
// 當資源群組開始解析指令碼的時候被調用
void resourceGroupScriptingStarted(const String& groupName,
size”_tscriptCount){}
// 當一個指令碼開始被解析的時候調用
void scriptParseStarted(const String& scriptName){}
// 當一個指令碼已經被解析完的時候調用
void scriptParseEnded(){}
// 當資源群組中指令碼被解析完畢的時候被調用
void resourceGroupScriptingEnded(const String& groupName){}
// 當開始載入資源群組中資源的時候被調用
void resourceGroupLoadStarted(const String& groupName,size_t resourceCount)
{ m_resCount = resourceCount;}
// 當開始載入一個資源的時候被調用
void resourceLoadStarted(const ResourcePtr& resource){}
// 當一個資源載入結束的時候被調用
void resourceLoadEnded();
// 當世界幾何體開始載入的時候被調用
void worldGeometryStageStarted(const String& description){}
// 當世界地圖載入結束的時候被調用
void worldGeometryStageEnded();
// 當資源群組的資源全部載入結束的時候被調用
void resourceGroupLoadEnded(const String& groupName){}
private:
int m_resCount;
int m_currentResource;
ProgressMeter &m_progressMeter;
};
void LoadingProgressListener:worldGeometryStageEnded()
{
// 增加當前資源數量
m_currentResource++;
// 根據百分比更新進度條尺寸
m_progressMeter.updateProgress((float)m_currentResource /(float)m_resCount);
}
void LoadingProgressListener::resourceLoadEnded()
{
// 增加當前資源數量
m_currentResource++;
// 根據百分比更新進度條尺寸
m_progressMeter.updateProgress((float)m_currentResource /(float)m_resCount);
}
// 使用ProgressMeter類的執行個體初始化一個listener
LoadingProgressListener listener(m_progressMeter);
// 在你代碼中的ResourceGroupManager註冊監聽者listener
ResourceGroupManager::getSingleton().addResourceGroupListener(&listener);
在上面代碼7-5中,我們展示了一個UI中 “載入進度條”的實現代碼。在代碼中,我們只關心載入每個資源之後更新進度條的動作,而不關心具體是世界地圖還是單獨的資源本身。對於資源管理員回調的其他方法,我們只負責實現了相應的虛函數,並沒有做具體工作(因為是對虛介面的實現,它們除了單純的“存在”之外並沒有其他用處)。
我們可以注意到範例程式碼中的ProgressMeter類,它並不是Ogre架構中的成員。我們假設它是你在自己的應用程式中實現的類型。
Ogre中的“檔案(Archive)”
對Ogre中“檔案”的概念最基本的描述是:把一系列檔案儲存在一起的檔案夾。“檔案”本身和裡面的檔案都可以使用壓縮格式。而“檔案”本身存在的價值是:為Ogre提供一個檔案訪問的入口。
Ogre對檔案中的檔案操作是通過Archive介面實現的。你可以在自己的應用中根據需要來實現不同的檔案形勢(只要繼承Archive介面就能很好的被系統支援)。預設的情況下磁碟目錄是一種基本的檔案實現方式。在Ogre的發行版本中,你會找到基於ZIP檔案實現的檔案類型,可以直接對PKZIP壓縮格式的檔案進行操作。
這兩種不同的檔案實現方式都有自己的優點,在構建和測試應用程式的時候,更適合直接在硬碟操作,因為這樣就允許你在測試錯誤的時候方便快速的改變和轉換檔,而不需要進行繁瑣的壓縮和解壓縮操作。而在發行版本中,你可能更需要一個高壓縮比例的ZIP檔案來在發布的程式中壓縮資源。
像在本書前面所提到的一樣,各種檔案系統也是作為外掛程式“插入”Ogre架構中來的。進而你可以很方面的擴充自己需要的檔案實現方式。雖然在大部分情況下操作磁碟檔案的FilerSystemArchive和操作壓縮格式檔案的ZipArchive已經足夠使用,但這並不代表你不能擴充,比如實現一個直接在區域網路操作傳輸資料的“網路檔案”。
檔案管理器(ArchiveManager)
下面介紹實現一個新的“檔案”類型的方法。其中我們的檔案類型MyArchive繼承於Archive介面。然後實現相應的Factory 方法MyArchiveFactory,註冊到文件管理器ArchiveManager中。
class MyArchive : public Archive
{
// 在這省略了具體的實現
}
class MyArchiveFactory : public ArchiveFactory
{
//在這省略了具體的實現ArchiveFactory的一些方法
Archive* createInstance(const String& name){
return new MyArchive(name, “MyArchiveType”);
}
};
void function()
{
MyArchiveFactory *factory = new MyArchiveFactory;
ArchiveManager::getSingleton().addArchiveFactory(factory);
}
代碼7-6:註冊一個新的Archive類型到Ogre中
當你向檔案管理器註冊了相應的Factory 方法之後,就可以放心大膽的在程式中或者指令碼中使用新的檔案類型了。例如在前面的例子中,系統通過解析resources.cfg檔案中的“ZIP”關鍵字來調用ZipArchive對象處理相應的壓縮檔。如果你把其中的“ZIP”改成“MyArchiveType”,就可以通過自訂的檔案格式MyArchive來處理檔案系統了。
通過自訂“檔案”實現自定製資源載入
在這裡可以回想一下我們前面提到過手動建立資源載入的概念。在“檔案”中我們也可以實現相同的工作。這是因為Ogre只是通過Archive介面來操作資源檔,而並不在意所謂的“資源檔”是一直儲存在硬碟上還是剛剛通過程式在記憶體區中建立。根據這個特性,我們可以通過Archive動態在記憶體中產生我們想要的“資源檔”。這裡有一個比較實用的例子,在很多遊戲中是通過一種被稱為“wad”的檔案[1]儲存遊戲資料的。你可以在程式運行時候把需要的wad檔案拷貝到記憶體中來,當應用程式需要具體的檔案時候。通過拷貝wad檔案對應記憶體中的所需部分動態建立出一個檔案交給應用程式使用。這種處理方法能很好的封裝各種不同資料檔案格式的區別,進而通過Ogre資源管理系統處理非Ogre支援格式的檔案,以達到擴充Ogre的目的。
結語
在3D應用程式中的資源管理員一般都不是一個很複雜的主題,但是它卻相當重要。糟糕的資源管理員會導致執行效率下降和使用者的抱怨。所以根據這章節所學到的知識,在你的應用程式中應該儘可能靈活的運用資源管理員來維護資源的使用,在必要的時候也可以實現自己的資源管理原則。Ogre提供給你一組通用且靈活的資源管理介面來協助你的工作。