虛擬仿真引擎與游戲引擎極其相似,但又有其不同的地方。游戲引擎重在游戲體驗,所以60⑴20HZ的畫面刷新率和事件刷新率仍然足夠。但虛擬仿真引擎不但需要仿真體驗,更需要更高速度的消息機制。試想在動作捕捉的利用中,動作捕捉裝備的刷新率的典型值是120HZ,或是需要記錄1個裝備的運動軌跡,也需要更高的刷新率。但是在中等以上復(fù)雜度的渲染中根本沒法到達120HZ,同時如果需要進行立體渲染,可以預(yù)感渲染速率是不會超過60HZ的,由于超過60HZ的立體渲染已超過了顯示裝備的極限。
所以虛擬仿真系統(tǒng)需要更加高速的消息機制,這也就是為何VRPN的標準http://www.jyygyx.com/server/的刷新率是1000HZ。典型的渲染循環(huán)刷新率在30⑹0HZ之間,這就造成了1個巨大的不平衡:渲染的低速和操作的高速。正確的方式應(yīng)當是操作消息能夠以1000HZ以上的刷新率在引擎中存在,因此必須為操作消息提供1個新的線程。而消息管理器之所以定義為1000HZ是由于可以簡單的使用Sleep(1)來節(jié)省大量的CPU開消,這樣消息線程的CPU消耗率會下降到單核的1%之內(nèi)。如果需要更高的速度,只需要取消等待,或使用其他的等待方式,則幾萬、幾10萬的刷新率都是可實現(xiàn)的。
其實不是所有的模塊都必須用到Delta3D的數(shù)據(jù)類型和各種對象,如果為我們的每個模塊都加入這么1個限制是不好的。比如我需要我的矩陣能進行1個特別的變換,Delta3D中的矩陣其實不具有這個功能,這個時候,我不應(yīng)當去重寫Delta3D
的矩陣類,由于這樣會造成很大的改變。和需要傳遞的消息,不應(yīng)當是復(fù)雜的已定義的類型,而應(yīng)當是簡單的每一個模塊都能解析的數(shù)據(jù)。這樣,每個模塊才能更簡單的取得所需要的數(shù)據(jù)。
另外一方面,在渲染1個場景時,如果已顯示了場景內(nèi)的UI,則場景內(nèi)的其他操作都應(yīng)當被停止。這時候,如果沒有消息管理器,就必須更改場景的狀態(tài),更新各個節(jié)點或相機管理器告知他們,你們暫時不要動。而如果消息管理用具有優(yōu)先級的概念,UI
當前獲得了這個消息,并且已處理,返回該消息已被處理,則該消息不再繼續(xù)傳遞,這樣就避免了很多無謂的狀態(tài)更改。
看到消息管理器有那末多的功能,必定會想到使用消息管理器會不會很復(fù)雜,需要很多的步驟。實際上使用消息管理器取得數(shù)據(jù)是很簡單的事情,只需要繼承1個接口,并實現(xiàn)兩個函數(shù)便可。
需要繼承的接口是IEventListener,它具有兩個方法需要被實現(xiàn):OnEvent和GetListenerOption。下面是IEventListener的代碼:
enum EPriority
{
RealTime,
High,
AboveNormal,
Normal,
BelowNormal,
Idle
};
struct SListenerOpt
{
int mRegMessages; // 偵聽消息的類型
EPriority mPriority; // 消息優(yōu)先級
};
struct IEventListener
{
IEventListener()
{ // 向消息管理器注冊此偵聽器的代碼 }
virtual SListenerOptGetListenerOption() = 0;
virtual boolOnEvent(EEventType event, void* eventDat) = 0;
// 多線程支持 內(nèi)部在OnEvent之前會檢查是不是被Lock 如果Lock則跳過
void Lock(); // 當你需要讀取消息相干數(shù)據(jù)時調(diào)用,則消息管理器暫不更新當前偵聽器的數(shù)據(jù)
void Unlock(); // 當數(shù)據(jù)讀取終了時調(diào)用,消息管理器可以更新其數(shù)據(jù)。
};
EEventType是返回的消息類型,將在下1節(jié)中實現(xiàn)。典型的實現(xiàn)方式為:
class CCameraManager : public IEventListener
{
//Members
//Functions
SListenerOptmListenerOpt; //在構(gòu)造函數(shù)中定義或?qū)崟r修改獲得消息的優(yōu)先級和獲得的消息類型
virtual SListenerOptGetListenerOption() { return mListenerOpt; }
virtual boolOnEvent(EEventType event, void* eventDat)
{
switch(event)
//將數(shù)據(jù)放入正確的位置
return //如果消息不再傳遞返回true;
//接收事件的實質(zhì)是禁止此事件的繼續(xù)發(fā)送,需配合優(yōu)先級謹慎使用
}
}
消息管理器管理各個消息偵聽者所能取得的消息和其權(quán)限。消息管理器中消息可以分為多類,典型的有:輸入消息、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
如果需要接受特定類型的數(shù)據(jù)則在消息偵聽器的mListenerOpt中的mRegMessages中使用這些定義如:mListenerOpt. mRegMessages= MESSAGE_TYPE_INPUT | MESSAGE_TYPE_UI;這樣,這個偵聽器就能夠偵聽輸入消息和UI的消息了。一樣,如果修改了mListenerOpt. mPriority則消息的接收權(quán)限會更改。這兩個偵聽器的屬性可以配置為固定或?qū)崟r更改,可以根據(jù)需要進行配置。
消息管理器是1個引擎中非常重要的部份,它就像1個交通關(guān)鍵,讓各個部份能夠快速的交換數(shù)據(jù)。下面就來描寫1下消息管理器的實現(xiàn)和使用。它需要保存2個列表:
所有已注冊的偵聽器的列表
每一個優(yōu)先級的偵聽器的列表
已注冊的偵聽器列表可以方便的便利所有的偵聽器查看他們的狀態(tài)改變,優(yōu)先級列表可以保證高優(yōu)先級的偵聽器率先被賦予數(shù)據(jù)。
下面是消息管理器的簡單實現(xiàn):
class CMessageManager
{
public:
CMessageManager() { // 初始化并啟動消息管理器線程 }
~ CMessageManager() { // 燒毀列表并結(jié)束消息管理線程 }
void MainLoop(); // 檢查輸入裝備狀態(tài) 發(fā)送消息
void CheckOpt(); // 檢查每一個偵聽器的狀態(tài) 優(yōu)先級改變后改變所在的列表
voidPostMessage(EEventType event, void* eventDat); // 其他模塊發(fā)送的消息,多線程支持
}
這樣就實現(xiàn)了1個簡單的消息管理器,只需要在引擎初始化的最開始建立1個它的新實例,并且在引擎結(jié)束時燒毀它就能夠了。偵聽器會在被創(chuàng)建時自動注冊到這個消息管理器,消息管理器內(nèi)部以1000HZ或更高的速度刷新數(shù)據(jù),并發(fā)送到各個需要這些消息的模塊。典型的消息處理流程將在第7節(jié)進行描寫。
下面說1下消息的類型,1個引擎中有很多種的消息類型,但可以預(yù)感的是總消息類型基本不會超過1個32位整數(shù)所表達的最大數(shù)值,消息的分類應(yīng)當不會超過1024種,單種類型的消息其細份量不會大于32 * 65536 = 2097152種,所以消息類型的定義以下:
#define EVENT_TYPE_BASE_INPUT 0x00000000
#define EVENT_TYPE_BASE_UI 0x00200000
生活不易,碼農(nóng)辛苦
如果您覺得本網(wǎng)站對您的學習有所幫助,可以手機掃描二維碼進行捐贈