资源管理实践
代码7-1提供了Ogre演示程序中用来初始化资源的resource.cfg文件,你可以在Ogre的SDK中找到相应的文件。
代码7-1:resource.cfg文件中对资源管理器的配置信息
# Resource locations to be added to the 'boostrap' path
# This also contains the minimum you need to use the Ogre example framework
[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接口。然后实现相应的工厂方法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中
当你向档案管理器注册了相应的工厂方法之后,就可以放心大胆的在程序中或者脚本中使用新的档案类型了。例如在前面的例子中,系统通过解析resources.cfg文件中的“ZIP”关键字来调用ZipArchive对象处理相应的压缩文件。如果你把其中的“ZIP”改成“MyArchiveType”,就可以通过自定义的档案格式MyArchive来处理文件系统了。
通过自定义“档案”实现自定制资源载入
在这里可以回想一下我们前面提到过手动创建资源载入的概念。在“档案”中我们也可以实现相同的工作。这是因为Ogre只是通过Archive接口来操作资源文件,而并不在意所谓的“资源文件”是一直保存在硬盘上还是刚刚通过程序在内存区中创建。根据这个特性,我们可以通过Archive动态的在内存中生成我们想要的“资源文件”。这里有一个比较实用的例子,在很多游戏中是通过一种被称为“wad”的文件[1]保存游戏数据的。你可以在程序运行时候把需要的wad文件拷贝到内存中来,当应用程序需要具体的文件时候。通过拷贝wad文件映射内存中的所需部分动态创建出一个文件交给应用程序使用。这种处理方法能很好的封装各种不同数据文件格式的区别,进而通过Ogre资源管理系统处理非Ogre支持格式的文件,以达到扩展Ogre的目的。
结语
在3D应用程序中的资源管理器一般都不是一个很复杂的主题,但是它却相当重要。糟糕的资源管理器会导致执行效率下降和用户的抱怨。所以根据这章节所学到的知识,在你的应用程序中应该尽可能灵活的运用资源管理器来维护资源的使用,在必要的时候也可以实现自己的资源管理策略。Ogre提供给你一组通用且灵活的资源管理接口来帮助你的工作。
ogre 添加外部资源管理器
最新推荐文章于 2020-05-23 10:50:00 发布