蔣 寧,徐濟惠
(寧波城市職業(yè)技術學院 信息與智能工程學院,寧波 315110)
通常虛擬現(xiàn)實(VR)應用的設計開發(fā)是需要開發(fā)人員具備一定的軟件編程能力,但是同時也限制了更多的沒有軟件編程技能但是卻對設計虛擬現(xiàn)實應用感興趣的這部分群體的創(chuàng)造能力[1],因此如何實現(xiàn)一個能夠使開發(fā)者無需掌握任何一門編程語言,僅僅通過鼠標拖拽等“傻瓜”式操作,在即看即所得場景中,方便完成各種專業(yè)級虛擬現(xiàn)實應用搭建的編輯器已經(jīng)成為當前虛擬現(xiàn)實市場的研究熱點之一,目前虛擬現(xiàn)實開發(fā)工具市場份額最大的是Unity3D 軟件工具,但是基于Unity3D 工具開發(fā)的應用也是需要寫大量代碼的,國外最為著名的可以通過鼠標拖拽就能完成一款虛擬現(xiàn)實應用的編輯器是PlayMaker 編輯器[2],但是它有幾個不足地方:(1)完全基于英文,沒有漢化版,不方便國人使用.(2)它本質上屬于插件,不能單獨使用,必須嵌入到Unity3D 里才能使用.國內目前也有幾家公司致力于可視化的編輯器設計,這幾款編輯器中,要么就是當用戶使用時,還是需要編寫腳本代碼的,要么就是實現(xiàn)的設計應用還是太簡單了,難以達到商業(yè)應用[3].本文研究的可視化編輯引擎是源自本文作者開發(fā)的一款基于可視化的編輯引擎,這款工具在一定程度上彌補了上述的不足.
本文研究的可視化編輯引擎的框架設計是基于有限狀態(tài)機的思想進行設計開發(fā).比如開、關、開啟、關閉、行走、空閑、攻擊、防御等諸多狀態(tài),每一個狀態(tài)(state)由一個或者多個行為組成,每一個行為(action)又包含了多個屬性(property),然后通過事件(event)驅動不同狀態(tài)之間的轉換[4].因此驅動一個通過編輯器引擎搭建的任務流是由狀態(tài)機、狀態(tài)、行為、事件、轉移、屬性以及條件等要素構成.任務流的數(shù)學模型可以表示成一個六元組(P,S,A,S0,δ,ω),其中P是前驅狀態(tài)集合,S是后繼狀態(tài)集合,A是行為的非空有限集合,S0 是初始狀態(tài),δ是狀態(tài)轉移函數(shù),可以表示為δ:P×S→P,ω是輸出函數(shù)[5].在這個引擎里,所有的狀態(tài)機、狀態(tài)和事件都是以可視化的方式通過拖拽等操作組合起來共同完成一個工作任務.這里狀態(tài)機(state machine)是3 種狀態(tài)的集合體,這3 種狀態(tài)分別是前驅狀態(tài)、當前狀態(tài)以及全局狀態(tài);而狀態(tài)的數(shù)據(jù)結構則包含了4 種列表:分別是只執(zhí)行一次的動作鏈表、在幀間不斷重復的動作鏈表、事件鏈表以及動作界面鏈表;事件包含4 種類型:自定義事件、系統(tǒng)事件、網(wǎng)絡事件以及VR 事件[6].上述幾種元素面向使用者則是封裝成可視化的矩陣節(jié)點(node)以方便用戶通過拖拽的方式進行操作,但是狀態(tài)之間的轉移則是通過一條有向線段標示,每個狀態(tài)可以包含多個動作,如圖1所示.
圖1 狀態(tài)可視化示意圖
圖1表示的是當鼠標左鍵點擊三維模型時,進入到下一狀態(tài),下一狀態(tài)包含特寫鏡頭、顯示按鈕、物理設置等行為.等下一狀態(tài)的這些行為全部執(zhí)行完畢后,觸發(fā)結束事件,通過轉移有向線段又返回到初始狀態(tài).其體現(xiàn)為一個狀態(tài)變遷矩陣如表1所示.
表1 狀態(tài)變遷矩陣
UI 是User Interface 的縮寫,就是用戶界面的意思.UI 在一個軟件系統(tǒng)中是人機交互的橋梁,UI 設計的優(yōu)劣直接決定用戶對于軟件使用的體驗感,好的UI 設計能夠讓用戶對這款軟件愛不釋手,反之如果UI 界面設計不到位,則即使你這款軟件功能很強大,也有可能不被用戶所接受.因此需要設計一套良好的UI 框架,在這款引擎中采用的是MVC 模式來進行UI 設計,其優(yōu)點包括3 個方面:(1)UI 界面的屬性只需要在Inspector面板中設置即可;(2)業(yè)務邏輯與UI 可以實現(xiàn)完全解耦,提高了需求功能的可擴充性;(3)可以很方便的從NGUI 模式切換到UGUI 模式.
本引擎中UI 基于MVC 的示意圖如圖2所示.
Model 的特點包括:(1)與UI 視圖數(shù)據(jù)完全解耦;(2)通過控制器或者其他Model 以引用的方式訪問;(3)可以通過委托(Delegate)方式來觸發(fā)外部事件.
本引擎中的Model 是繼承Unity3D 自帶的Monobehavior 類,完全基于C#編寫的.
View 的特點包括:(1)展示所有的UI 元素;(2)可以直接與用戶進行交互;(3)布局界面的設計;(4)對Models 層只擁有只讀權限[7].
Controller 的職責包括:Controller 負責定義和調用Model,它可以決定要顯示哪個View.控制器接受用戶的輸入并調用模型和視圖去完成用戶的需求.控制器本身不輸出任何東西和做任何處理.它只接受請求并決定調用哪個模型構件去處理請求,然后決定用哪個視圖來顯示模型處理返回的數(shù)據(jù).
圖2 MVC UI 結構圖
引擎中,我們加入了另外一個中間層來進一步解耦GUI 的View 實現(xiàn),稱之為:ControllerView,一個Controller View 位于View 和Controller 之間,它提供了若干接口,用來聲明與UI 具體實現(xiàn)無關的操作,無論View 本身是基于什么庫來實現(xiàn)的(NGUI,UGUI等),ControllerView 的實現(xiàn)是基于適配器模式(adapter pattern)的,這種類型的設計模式屬于結構型模式,它結合了兩個獨立接口的功能.此時是作為業(yè)務邏輯代碼和UI 代碼的橋梁,也是作為兩個不兼容的接口之間的橋梁.
在本引擎中各個狀態(tài)的轉換和遷移都是通過事件驅動,框架中定義了大量的事件,比如模型碰撞、鼠標事件、鍵盤事件、網(wǎng)絡事件等等,需要一種機制來合理組織、管理和執(zhí)行這些事件,這種機制能夠管理和執(zhí)行一幀里面的多個事件或者是幀里一個時間片上事件的多次觸發(fā).可以通過事件系統(tǒng)組件的方式來靈活的管理事件,同時也能提升應用的模塊化,提供更好的可擴展性.
事件系統(tǒng)組件包括標準輸入模塊、觸摸輸入模塊、物理射線投射模塊以及圖像投射模塊.
事件系統(tǒng)組件主要負責處理輸入、射線投射以及發(fā)送事件,它是一種將基于輸入的事件發(fā)送到應用程序中的對象,無論是鍵盤、鼠標、觸摸或自定義輸入.一個場景中只能有一個事件系統(tǒng)組件,但是可以擁有多個輸入類型組件,事件系統(tǒng)在初始化的時候會通過字典的形式將所屬對象下的輸入模塊類型依次添加進去,并且在每個Update 周期通過UpdateModules 接口調用這些基本輸入模塊的更新模塊接口,然后輸入模塊會在UpdateModule 接口中將自己的狀態(tài)修改成“已更新”狀態(tài),之后才可以調用輸入模塊的其他接口.
標準輸入模塊和觸摸輸入模塊都源自于一個基類:基礎輸入模塊,它是是一個基類模塊,負責發(fā)送輸入事件(點擊、拖拽、選中等)到具體對象.事件系統(tǒng)下的所有輸入模塊都必須繼承自基礎輸入模塊組件.
除了以上兩個組件,還有一個很重要的組件就是基礎射線投射組件,它也是一個基類,前面說的輸入模塊要檢測到鼠標事件必須有射線投射組件才能確定目標對象.系統(tǒng)實現(xiàn)的射線投射類組件有物理射線投射模塊以及圖像投射模塊.
總的來說,事件系統(tǒng)組件負責管理,基礎輸入模塊負責輸入,基礎射線投射組件負責確定目標對象,目標對象負責接收事件并處理,然后一個完整的事件系統(tǒng)就形成了.
由于本引擎的一個重要特征是可視化,因此用戶在操作本引擎時會涉及到與大量可見模型進行交互操作,這種與三維模型的交互需要附著在三維模型外面的碰撞檢測盒的支持,傳統(tǒng)的碰撞盒采用的方式主要有:AABB 方式、包圍球層次樹、OBB 層次樹等方式,其中AABB 方式特點是構造難度較低,但是包圍緊密度低;包圍球層次樹的特點是構造難度比AABB 方式低,但包圍緊密度不如AABB 方式;OBB 方式的特點是構造難度要大于前兩種方式,但是包圍緊密度要高于前兩者.這幾種方式的誤差度總體都較大,不能給出最優(yōu)范圍的界限且實時檢測性能不好.在本引擎中,采用了基于協(xié)方差矩陣方式優(yōu)化碰撞檢測盒以便提高準確度.在場景中的模型可以被認為是由n個點{D1,…,Dn}形成的一片點云集合,這里將每個點的x、y和z值的集合通過偏差和協(xié)方差來進行處理.偏差就是度量數(shù)據(jù)的點集和平均值的差異,它計算的是平均值差的平方和.其算式表示針對X 軸如下,針對Y 或者Z 軸也是類似的算式.
注意我們在估算中,是用n–1 代替n來糾正偏差的,這樣整體的平均值就是基于n個點集合的中心,即則在X,Y,Z 3 個方向上的平均值則分別是:
我們希望偏差盡量變小,這樣數(shù)據(jù)盡量是整體接近平均值的,在偏差的基礎上,可以進一步采用協(xié)方差的度量方式以便更加精確的提高檢測盒的精度,協(xié)方差度量的是數(shù)據(jù)集合的獨立性.在三維場景中用的最多的就是X 和Z 坐標集合之間的協(xié)方差,其算式如下表達:
x和y以及y和z之間的協(xié)方差的算式同上面是類似的,協(xié)方差的值越低說明數(shù)據(jù)集之間數(shù)據(jù)的獨立性越低,通過整合每個坐標對的偏差和協(xié)方差,可以確定一個協(xié)方差矩陣M,如式(4).
這種協(xié)方差矩陣可以在其他特殊方向上確定對偏差的度量,矩陣的結果向量如果偏向較小偏差的區(qū)域,則該方向就會偏短些,反之如果偏向較大偏差的區(qū)域,則該區(qū)域方向就會偏長些.對于一個單位圓,如果用協(xié)方差矩陣進行轉換,則會得到一個橢球體,這個橢球的長軸會沿著偏差值高的方向展開,基本上對齊橢球體的長軸.反之,橢球的短軸則會沿著較低偏差的方向展開,也就是點密集分布的地方,基本上對齊于橢球體的短軸.因此我們的策略就是用橢球的軸來代替標準軸創(chuàng)建場景中的包圍對象,這些軸就是已知的主軸,如圖3.
為了獲得這些主軸,需先對角化這個矩陣,即先對轉換到主軸空間的向量進行旋轉,并應用到協(xié)方差矩陣,最后旋轉回來,算式表達為:D=PTCP,這里P就是正交矩陣,所有列向量構成坐標系統(tǒng)的正交基.它們等同于轉換前的協(xié)方差矩陣的主軸,因此可以用它們來創(chuàng)建碰撞包圍盒.
圖3 基于協(xié)方差矩陣的帶主軸橢球體
為了得到這個旋轉矩陣,需要先獲得其特征向量和特征值,可以通過計算特征多項式,對于三維場景坐標而言,就是一個三次多項式,然后直接解出方程的根.矩陣D-λI的行列式為:
可以解出這個三項式的根來得到特征值,然后代入每個特征值來計算出矩陣Di=C?λiI.解出這個線性系統(tǒng)就可以獲得對應的3 個特征向量.這些特征向量和中心構成了點云的坐標基就可以計算三維對象的碰撞檢測盒了.
碰撞檢測盒算法設計思路:首先將構成碰撞檢測盒的每個點減去中心,得到差值向量,接著用每個歸一化的特征向量來做點積計算.相對于每個基向量可以給出差值向量投影的長度.計算出這些點擊的最大最小值,就會為點云創(chuàng)建對齊于新的基向量的包圍盒,經(jīng)測試通過基于協(xié)方差矩陣方式優(yōu)化包圍盒要比上述3 種方法在精確度和實時檢測速度上都有明顯的提高.
在引擎中,專門有一個資源列表面板,資源列表面板的作用是加載三維模型到場景中,目前資源面板里已經(jīng)包含了人物、動畫、植物、動物、交通、器材、建筑等類型的將近300 種三維模型,如圖4所示.
圖4 資源面板
每一種模型又分為高精度、中精度和低精度等3 個細節(jié)層次的模型,之所以采用這種方式,是因為當場景模型與攝像機的位置相聚很遠時,可以不需要顯示高精度的場景模型,從而加速渲染效果.反之當一個場景模型與攝像機的距離較近時,則可以顯示高精度的渲染效果以便改善視覺品質.為了實現(xiàn)這種動態(tài)渲染效果,需要使用LOD 技術,即細節(jié)層次技術,為了實現(xiàn)細節(jié)層次渲染效果,我們針對每個不同的細節(jié)層次分別創(chuàng)建三個,每幀要渲染的模型應基于與攝像機間的距離進行選擇的.比較簡單的規(guī)則是每個LOD 應該有大約2 倍于前一層次的多邊形的數(shù)目,但是這種規(guī)則比較粗略,很容易產(chǎn)生抖動,當最小化LOD 變換的數(shù)目時,需要解決何時改變LOD 以便獲得理想的性能和品質.選擇要渲染的LOD 最簡單方法是確定一個閾值,這個閾值對應攝像機到模型的距離,這種方法雖然容易計算,但是存在兩個問題:(1)這種方法沒有考慮攝像機的視野.如果攝像機的視野過于狹窄或者過于廣闊,則可能會導致LOD 渲染的頻繁抖動現(xiàn)象的發(fā)生.(2)如果對象與閾值很接近時,LOD 之間會有快速的反復轉換.為了解決上述兩個問題,我們使用“增大因子”技術,增大因子是物體的屏幕尺寸與其物理尺寸的比例,屏幕尺寸的確定是通過轉換和投影對象上的極大值和極小值,然后減去每一坐標的屏幕位置,增大因子通過將對象的位置變換到視域中然后進行計算而得到:
這里xscale是用于投影方程的換算參數(shù):
由于視點坐標只是一個世界坐標的旋轉或平移,zview 由世界單位度量.xscale相對于攝像機視野,以像素為單位;因此,增大因子M可以度量每個世界單位的像素.當M增加時,每世界單位有更多的像素,對象相對來說在屏幕上更大,因此應該使用一個更高的細節(jié)層次.這樣M既考慮了攝像機的距離也考慮了視野.而且給出了一個比簡單攝像機距離更好的確定細節(jié)層次的選擇依據(jù).但是如果僅僅對M應用一個簡單的閾值會產(chǎn)生頻繁的抖動現(xiàn)象.那么如何解決這種頻繁的抖動現(xiàn)象? 在此我們采用滯變閾值來解決這種頻繁抖動現(xiàn)象,滯變閾值對應于一個值的范圍的閾值,其使用一個較高的和一個較低的閾值并且記錄前一次的輸出值.如果輸入值是在較高的和較低的閾值之間,則輸出值不變.通過滯變閾值和放大率因子,我們可以創(chuàng)建一個細節(jié)層次選擇算法,針對3 個細節(jié)層次的低、中、高模型,用于在高細節(jié)層次和中細節(jié)層次之間移動的滯變閾值為Thupper和Thlower.在中細節(jié)層次和低細節(jié)層次之間移動的滯變閾值為Tmupper和Tmlower,通過不斷調試,可以設定一個介于較高和較低滯變閾值之間的輸入值,采用這種方式選擇細節(jié)層次意味著物體不會在一個點上進行反復的細節(jié)層次變換,這樣就確保我們所得到的是一個單一的移動而不是多個反復的變換行為,從而避免了快速抖動的問題.
綜上所述,我們從可視化角度分別在包圍盒碰撞檢測和模型細節(jié)層次上進行了算法優(yōu)化,比常規(guī)的算法提高了精確度和實時性.
本引擎完全采用C#開發(fā),所寫的C#語言腳本接口是以MonoBehaviour 這個類作為基礎的,主要使用的數(shù)據(jù)結構包括數(shù)組部分:List
在引擎中,我們大量采用的設計模式是觀察者模式,即定義了對象之間的一對多依賴,即當一個對象改變狀態(tài)時,它的所有依賴者都會收到通知并且自動更新.具體實現(xiàn)是通過委托(delegate)和事件(events)這兩種回調函數(shù)機制完成的,委托其實類似于C++中的函數(shù)指針,但它是以類的形式定義的,它定義了方法的類型,使得方法可以類似指針處理方式作為參數(shù)來進行傳遞.事件是一種特殊的委托.
多線程能夠在同一時間執(zhí)行多于一個線程,進而提升引擎整體處理性能,在本引擎中,通過某種機制也是可以實現(xiàn)類似多線程功能的效果的,這種機制就是協(xié)程(coroutine),協(xié)程可以暫停邏輯的執(zhí)行,并且將控制權移交給引擎,但是之后它還可以從暫停的位置繼續(xù)開始執(zhí)行余下的邏輯.同時在引擎中的很多延時效果也是使用協(xié)程來實現(xiàn)的,具體實現(xiàn)概括如下:調用StartCoroutine 方法開啟協(xié)程,協(xié)程的返回值必須是IEnumerator 類型,調用WaitForSeconds 類實現(xiàn)延時效果.
我們采用序列化和反序列化技術實現(xiàn)數(shù)據(jù)的存儲,所謂序列化就是將對象轉換為字節(jié)流的過程,反序列化則是指的將字節(jié)流轉換回對象的過程.具體實現(xiàn)概括如下:首先構造一個System.IO.MemoryStream 對象,提供了一個用來容納經(jīng)過序列化之后的字節(jié)塊的容器.接著創(chuàng)建一個格式化(formatters)對象,最后調用序列化(serialize)方法和非序列化方法(deserialize)方法分別實現(xiàn)存儲和加載數(shù)據(jù)的功能.
本文基于可視化的VR 編輯引擎設計和研究,圍繞可視化、可擴充、可優(yōu)化的設計目標,以系統(tǒng)框架、UI 界面、事件系統(tǒng)等方面的設計工作為基礎,研究了包圍碰撞盒技術和模型細節(jié)層次兩個關鍵技術并進行了優(yōu)化,實驗證明優(yōu)化后的算法比常規(guī)的算法提高了精確度和實時性.并解決了具體實現(xiàn)中的數(shù)據(jù)結構、多線程實現(xiàn)、設計模式和數(shù)據(jù)存儲等問題,最終實現(xiàn)了VR 編輯引擎,結果表明,VR 編輯引擎系統(tǒng)各項功能指標均符合實際要求,具有完全可視化、“拖拽”設計方式、操作簡單、可擴展性強、跨平臺等優(yōu)點,極大的降低了VR 仿真軟件的開發(fā)門檻和開發(fā)成本.下一步將繼續(xù)深入研究該引擎的在線網(wǎng)絡功能.