虛擬仿真引擎與游戲引擎極其相似,但又有其不同的地方。游戲引擎重在游戲體驗,所以60⑴20HZ的畫面刷新率和事件刷新率仍然足夠。但虛擬仿真引擎不但需要仿真體驗,更需要更高速度的消息機制。試想在動作捕捉的利用中,動作捕捉裝備的刷新率的典型值是120HZ,或是需要記錄1個裝備的運動軌跡,也需要更高的刷新率。但是在中等以上復雜度的渲染中根本沒法到達120HZ,同時如果需要進行立體渲染,可以預感渲染速率是不會超過60HZ的,由于超過60HZ的立體渲染已超過了顯示裝備的極限。
所以虛擬仿真系統需要更加高速的消息機制,這也就是為何VRPN的標準http://www.vxbq.cn/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關系分析