虚拟仿真引擎与游戏引擎极其相似,但又有其不同的地方。游戏引擎重在游戏体验,所以60⑴20HZ的画面刷新率和事件刷新率仍然足够。但虚拟仿真引擎不但需要仿真体验,更需要更高速度的消息机制。试想在动作捕捉的利用中,动作捕捉装备的刷新率的典型值是120HZ,或是需要记录1个装备的运动轨迹,也需要更高的刷新率。但是在中等以上复杂度的渲染中根本没法到达120HZ,同时如果需要进行立体渲染,可以预感渲染速率是不会超过60HZ的,由于超过60HZ的立体渲染已超过了显示装备的极限。
所以虚拟仿真系统需要更加高速的消息机制,这也就是为何VRPN的标准http://www.wfuyu.com/server/的刷新率是1000HZ。典型的渲染循环刷新率在30⑹0HZ之间,这就造成了1个巨大的不平衡:渲染的低速和操作的高速。正确的方式应当是操作消息能够以1000HZ以上的刷新率在引擎中存在,因此必须为操作消息提供1个新的线程。而消息管理器之所以定义为1000HZ是由于可以简单的使用Sleep(1)来节省大量的CPU开消,这样消息线程的CPU消耗率会下降到单核的1%之内。如果需要更高的速度,只需要取消等待,或使用其他的等待方式,则几万、几10万的刷新率都是可实现的。
其实不是所有的模块都必须用到Delta3D的数据类型和各种对象,如果为我们的每个模块都加入这么1个限制是不好的。比如我需要我的矩阵能进行1个特别的变换,Delta3D中的矩阵其实不具有这个功能,这个时候,我不应当去重写Delta3D
的矩阵类,由于这样会造成很大的改变。和需要传递的消息,不应当是复杂的已定义的类型,而应当是简单的每一个模块都能解析的数据。这样,每个模块才能更简单的取得所需要的数据。
另外一方面,在渲染1个场景时,如果已显示了场景内的UI,则场景内的其他操作都应当被停止。这时候,如果没有消息管理器,就必须更改场景的状态,更新各个节点或相机管理器告知他们,你们暂时不要动。而如果消息管理用具有优先级的概念,UI
当前获得了这个消息,并且已处理,返回该消息已被处理,则该消息不再继续传递,这样就避免了很多无谓的状态更改。
看到消息管理器有那末多的功能,必定会想到使用消息管理器会不会很复杂,需要很多的步骤。实际上使用消息管理器取得数据是很简单的事情,只需要继承1个接口,并实现两个函数便可。
需要继承的接口是IEventListener,它具有两个方法需要被实现:OnEvent和GetListenerOption。下面是IEventListener的代码:
enum EPriority
{
RealTime,
High,
AboveNormal,
Normal,
BelowNormal,
Idle
};
struct SListenerOpt
{
int mRegMessages; // 侦听消息的类型
EPriority mPriority; // 消息优先级
};
struct IEventListener
{
IEventListener()
{ // 向消息管理器注册此侦听器的代码 }
virtual SListenerOptGetListenerOption() = 0;
virtual boolOnEvent(EEventType event, void* eventDat) = 0;
// 多线程支持 内部在OnEvent之前会检查是不是被Lock 如果Lock则跳过
void Lock(); // 当你需要读取消息相干数据时调用,则消息管理器暂不更新当前侦听器的数据
void Unlock(); // 当数据读取终了时调用,消息管理器可以更新其数据。
};
EEventType是返回的消息类型,将在下1节中实现。典型的实现方式为:
class CCameraManager : public IEventListener
{
//Members
//Functions
SListenerOptmListenerOpt; //在构造函数中定义或实时修改获得消息的优先级和获得的消息类型
virtual SListenerOptGetListenerOption() { return mListenerOpt; }
virtual boolOnEvent(EEventType event, void* eventDat)
{
switch(event)
//将数据放入正确的位置
return //如果消息不再传递返回true;
//接收事件的实质是禁止此事件的继续发送,需配合优先级谨慎使用
}
}
消息管理器管理各个消息侦听者所能取得的消息和其权限。消息管理器中消息可以分为多类,典型的有:输入消息、UI消息、引擎消息和物理消息等。首先看1下消息类型的定义,消息类型以BitMask的情势定义:
#define MESSAGE_TYPE_INPUT 0x00000001
#define MESSAGE_TYPE_UI 0x00000002
#define MESSAGE_TYPE_ENGINE 0x00000004
#define MESSAGE_TYPE_PHYSICS 0x00000008
#define MESSAGE_TYPE_COUNT 0x00000004
如果需要接受特定类型的数据则在消息侦听器的mListenerOpt中的mRegMessages中使用这些定义如:mListenerOpt. mRegMessages= MESSAGE_TYPE_INPUT | MESSAGE_TYPE_UI;这样,这个侦听器就能够侦听输入消息和UI的消息了。一样,如果修改了mListenerOpt. mPriority则消息的接收权限会更改。这两个侦听器的属性可以配置为固定或实时更改,可以根据需要进行配置。
消息管理器是1个引擎中非常重要的部份,它就像1个交通关键,让各个部份能够快速的交换数据。下面就来描写1下消息管理器的实现和使用。它需要保存2个列表:
所有已注册的侦听器的列表
每一个优先级的侦听器的列表
已注册的侦听器列表可以方便的便利所有的侦听器查看他们的状态改变,优先级列表可以保证高优先级的侦听器率先被赋予数据。
下面是消息管理器的简单实现:
class CMessageManager
{
public:
CMessageManager() { // 初始化并启动消息管理器线程 }
~ CMessageManager() { // 烧毁列表并结束消息管理线程 }
void MainLoop(); // 检查输入装备状态 发送消息
void CheckOpt(); // 检查每一个侦听器的状态 优先级改变后改变所在的列表
voidPostMessage(EEventType event, void* eventDat); // 其他模块发送的消息,多线程支持
}
这样就实现了1个简单的消息管理器,只需要在引擎初始化的最开始建立1个它的新实例,并且在引擎结束时烧毁它就能够了。侦听器会在被创建时自动注册到这个消息管理器,消息管理器内部以1000HZ或更高的速度刷新数据,并发送到各个需要这些消息的模块。典型的消息处理流程将在第7节进行描写。
下面说1下消息的类型,1个引擎中有很多种的消息类型,但可以预感的是总消息类型基本不会超过1个32位整数所表达的最大数值,消息的分类应当不会超过1024种,单种类型的消息其细份量不会大于32 * 65536 = 2097152种,所以消息类型的定义以下:
#define EVENT_TYPE_BASE_INPUT 0x00000000
#define EVENT_TYPE_BASE_UI 0x00200000
下一篇 HTML与XML关系分析