曹 莉
目前,在虛擬現(xiàn)實領(lǐng)域中,人們多數(shù)采用OpenGL的開發(fā)接口進行3D應(yīng)用程序開發(fā)。這種開發(fā)接口仍然停留在幾何體級別的操作層面,是一種明顯帶有面向過程特征的API,這使得系統(tǒng)在開發(fā)后期變得龐大而復(fù)雜,從而給系統(tǒng)維護和修改帶來難度。
OSG(Open Scene Graph)是一個跨平臺的開源的圖形開發(fā)包,它基于場景圖的概念,提供一個構(gòu)建于OpenGL函數(shù)庫之上的面向?qū)ο蟮目蚣?,為圖形程序的開發(fā)提供場景管理和渲染優(yōu)化功能,從而使系統(tǒng)開發(fā)脫離幾何體操作,轉(zhuǎn)而處理具體的場景。
三維引擎作為一個名詞已存在了很多年,但即使是一些專業(yè)的引擎設(shè)計師,也很難就它的定義達成一個共識。通常來說,三維引擎可以把它看成是對3D API的封裝,并包含有對一些圖形通用算法以及底層工具的封裝。
從功能的角度來定義三維引擎,更能確切的表達出一個三維引擎的真實含義。一個三維引擎最基本的功能應(yīng)該包括:
(l)對三維場景的數(shù)據(jù)管理:這里的數(shù)據(jù)管理是一個比較廣泛的定義,不同的三維引擎也許會擁有其中一個或多個功能。這些功能包括:場景管理,對象系統(tǒng),序列化,數(shù)據(jù)與外部工具的交互,底層三維數(shù)據(jù)的組織和表示。
(2)功能合理的渲染器:由于一個引擎的渲染能力是由多方面決定的,因此在設(shè)計初期需考慮到不同的開發(fā)項目有著不同的需求,在渲染器的功能選擇上也應(yīng)有所側(cè)重。比如一款以實時游戲作為目標(biāo)的游戲,會選擇基于光柵化的渲染算法。
(3)與虛擬世界的交互能力:交互性是虛擬現(xiàn)實也是三維引擎的一個必需的要素,只有通過與虛擬世界的交互,才能使虛擬現(xiàn)實看上去更逼真,使人們產(chǎn)生沉浸感。
任何包含了上面三種功能的引擎,就可以稱為三維引擎。而開發(fā)功能強大的引擎,則需要實現(xiàn)更多更龐大的功能。
OSG采用場景圖結(jié)構(gòu),如圖1所示:
圖1 場景圖結(jié)構(gòu)
來管理場景,它是一種自頂向下的,分層的樹狀數(shù)據(jù)結(jié)構(gòu)。該結(jié)構(gòu)以根節(jié)點表示整個三維場景;以組節(jié)點(osg::Group)表示物體屬性信息,如矩陣變換、狀態(tài)切換、細節(jié)層次等,在實現(xiàn)過程中,又以組節(jié)點為基類派生出變換節(jié)點(osg::Transform),開關(guān)節(jié)點(osg::Switch),細節(jié)層次節(jié)點(osg::LOD)等類,對屬性信息進行分別管理;而葉子節(jié)點(osg::Geode)則代表物理對象本身或幾何模型。這種結(jié)構(gòu)既反映了場景的空間結(jié)構(gòu),也反映了對象的狀態(tài),便于提取數(shù)據(jù)節(jié)點之間的共有行為和屬性。
場景圖是一種中間件(middleware),它構(gòu)建于底層API函數(shù)之上,提供了高性能3D程序所需的空間數(shù)據(jù)組織能力及其它特性,表現(xiàn)了一個典型的OSG程序?qū)哟谓Y(jié)構(gòu),如圖2所示:
圖2 3D程序?qū)哟谓Y(jié)構(gòu)
一個場景圖系統(tǒng)執(zhí)行繪圖遍歷時,所有幾何體是以O(shè)penGL指令的形式發(fā)送到硬件設(shè)備上。這種機制無法實現(xiàn)諸如細節(jié)層次、渲染特效等高級特性。為了實現(xiàn)動態(tài)的幾何體更新,揀選,排序和高效渲染,OSG場景圖形提供3個遍歷過程:
更新:更新遍歷允許修改幾何體、渲染狀態(tài),或者節(jié)點參數(shù),保證場景圖形的更新對應(yīng)當(dāng)前幀;
揀選:在揀選遍歷中檢查可見性,將幾何體和狀態(tài)量置入新的結(jié)構(gòu)(在OSG中稱為渲染圖形,render graph)之中。
繪制:在繪制遍歷中(有時也稱作渲染遍歷),場景圖形將遍歷由揀選遍歷過程生成的幾何體列表,并調(diào)用底層API,實現(xiàn)幾何體的渲染。
一般地,這3種遍歷操作在每一個渲染幀中只執(zhí)行一次,但有時一些渲染特例需要將同一個場景(不同或相同部分)在多個視口中進行同步顯示,這樣更新遍歷仍然只執(zhí)行一次,但揀選和繪制遍歷則需要在每個視口內(nèi)各執(zhí)行一次,以保證多處理器和多顯卡的系統(tǒng)實現(xiàn)并行場景圖形處理。
在OSG中,智能指針(Smart pointer)的概念指的是一種類的模板,它針對某一特定類型的對象(即Referenced類及其派生類)構(gòu)建,提供了了一種內(nèi)存自動釋放的機制,即,場景圖形中的每一個節(jié)點均關(guān)聯(lián)一個內(nèi)存計數(shù)器,當(dāng)計數(shù)器的計數(shù)減到零時,該對象將被自動釋放。
由于OSG中與場景圖形有關(guān)的大多數(shù)類均派生自Referenced類,因此OSG大量使用了智能指針來實現(xiàn)場景圖形節(jié)點的管理。當(dāng)用戶希望釋放整個場景圖形的節(jié)點時,只需要刪除根節(jié)點,則根節(jié)點以下的所有分支節(jié)點均會被自動刪除,從而避免內(nèi)存泄漏錯誤。
OSG不能直接嵌入MFC窗口,要用攝像機類來支持。OSG使用第三方的Producer接口來實現(xiàn)MFC方面的GUI。
Producer接口提供有Camera類,它的原理,如圖3所示:
圖3 osgProducer接口實現(xiàn)嵌入圖
Render Surface是Camera的一個成員對象,它相當(dāng)于計算機的屏幕。攝像機拍到的影像就投放在該面的投影矩陣上。要將OSG窗口嵌入到MFC中,也同樣用Render Surface來設(shè)置。使用Camera對象的getRenderSurface()方法可以獲取到Render Surface,并返回一個RenderSurface*的對象指針。在OSG中如果該對象是引用類派生出來,則可以使用osg::ref_ptr<>模版類來創(chuàng)建一個智能指針對象。
新版的OSG中,已經(jīng)取消了關(guān)于Producer接口的應(yīng)用,但在實現(xiàn)將OSG窗口嵌入到MFC過程中,用到的原理是相似的,只是更加簡單的通過一個自己定義的類cOSG來實現(xiàn)。實現(xiàn)過程如下:
(1)初始化操作器
由void cOSG::InitManipulators(void)實現(xiàn),具體過程是首先創(chuàng)建一個trackball的控制器驅(qū)動,并設(shè)置它的高度。再創(chuàng)建一個控制器轉(zhuǎn)換器,之后將trackball的控制器添加到轉(zhuǎn)換控制器中,初始化轉(zhuǎn)換器,在這個程序中選擇用第一個模式的控制器,值為0。
(2)初始化場景圖
由void cOSG::InitSceneGraph(void)實現(xiàn),具體過程是首先初始化主要的節(jié)點,組,之后完善模型,再將模型添加到場景中。
(3)初始化攝像機
由void cOSG::InitCameraConfig(void)實現(xiàn),具體過程是首先獲取本地的窗口大小矩陣,創(chuàng)建windows的視圖,之后在視圖中添加一個事件處理控制。然后獲取對話框的寬高,利用智能指針初始化GraphicsContext Traits。再引用窗口句柄獲取MFC窗口,并設(shè)置窗口大小。利用Traits設(shè)置視圖矩陣,設(shè)置窗口場景,把OSG窗口綁定到viewer中。創(chuàng)建場景上下文,設(shè)置畫圖屬性。
(4)配置攝像機
初始化一個新的攝像機對象,為攝像機定義場景上下文,設(shè)置視口接口。然后將攝像機添加到視圖中,將攝像機的控制器加到視圖中。
OSG的攝像機操作,由osgGA::MatrixManipulator的派生類來完成。要實現(xiàn)攝像機控制,關(guān)鍵是實現(xiàn)osgGA::MatrixManipulator類的以下5個純虛函數(shù):
(1)virtual void setByMatrix(const osg::Matrixd&matrix);
這個函數(shù)在從一個攝像機切換到另一個攝像機時調(diào)用,用來把上一個攝像機的視圖矩陣傳過來,這樣就可依此設(shè)定自己的初始位置了。重載setByMatrix函數(shù)實現(xiàn)過程:
void CMyManipulator::setByMatrix(const osg::Matrixd&matrix)
{//設(shè)置攝像機初始位置
_eye=matrix.getTrans();
_rotation=matrix.getRotate();
}
(2)virtual osg::Matrixd getMatrix()const;
SetByMatrix方法需要的矩陣需要用這個方法得到,用來向下一個攝像機傳遞矩陣。重載getMatrix函數(shù)實現(xiàn)過程:
osg::Matrixd CMyManipulator::getMatrix()const
{
return
osg::Matrixd::rotate(_pitch,1.0,0.0,0.0)*osg::Matrixd::rotate(_r otation)*osg::Matrixd::translate(_eye);
}
(3)virtual osg::Matrixd getInverseMatrix()const;
這個是最重要的方法,在每幀中都會被調(diào)用,它返回當(dāng)前的視圖矩陣。重載getInverseMatrix函數(shù)實現(xiàn)過程:
osg::Matrixd CMyManipulator::
getInverseMatrix()const
{
return
osg::Matrixd::translate(-_eye)*osg::Matrixd::rotate(_rotation.in verse())*osg::Matrixd::rotate(-_pitch,1.0,0.0,0.0);}
其中osg::Matrixd::translate(-_eye)表示平移矩陣,Matrixd::rotate(_rotation.inverse())表示水平旋轉(zhuǎn)矩陣,Matrixd::rotate(-_pitch,1.0,0.0,0.0)表示豎直旋轉(zhuǎn)矩陣。
(4)virtual void setByInverseMatrix(const osg::Matrixd&matrix);
這個方法是當(dāng)在外部直接調(diào)用Viewer的setViewByMatrix方法時,把設(shè)置的矩陣傳過來,讓攝像機記錄新更改的位置,本系統(tǒng)中不需要實現(xiàn)。
(5)virtual bool handle(const osg-GA::GUIEventAdapter&ea,osgGA::GUIActionAdapter&us)
這個方法是進行事件的處理,方法中第一個參數(shù)是GUI事件的供給者,第二個參數(shù)用來使handle方法對GUI進行反饋,它可以讓GUIEventHandler根據(jù)輸入事件讓GUI執(zhí)行一些動作。如果要進行事件處理,可以從GUIEventHandler派生出自己的類,然后覆蓋handle方法,在其中進行事件處理。osgProducer::Viewer類維護一個GUIEventHandler隊列,事件在這個隊列里依次傳遞,handle的返回值決定這個事件是否繼續(xù)讓后面的GUIEventHandler處理,如果返回true,則停止處理,如果返回false,后面的GUIEventHandler還有機會繼續(xù)對這個事件進行響應(yīng)。重載handle函數(shù)實現(xiàn)鍵盤上的方向鍵控制攝像機視口,效果如圖4所示:
圖4 方向鍵控制攝像機視口的效果
bool handle(
const osgGA::GUIEventAdapter&ea,
osgGA::GUIActionAdapter&us)
{
case(GUIEventAdapter::KEYDOWN/KEYUP):
{
if(ea.getKey()=GUIEventAdapter::KEY_Space)
{//返回初始位置
flushMouseEventStack();
home(ea,us);
return true;}
//實現(xiàn)上,下,左,右方向鍵控制視口
else if
(ea.getKey()=osgGA::GUIEventAdapter::KEY_Up)
{return true;}
else if
(ea.getKey()=osgGA::GUIEventAdapter::KEY_Down)
{return true;}
else if
(ea.getKey()=osgGA::GUIEventAdapter::KEY_Left)
{return true;}
else if
(ea.getKey()=osgGA::GUIEventAdapter::KEY_Right)
{return true;}}
在虛擬環(huán)境中,由于用戶的交互和物體的運動,不可避免會發(fā)生碰撞,為避免穿透現(xiàn)象的發(fā)生,系統(tǒng)必須進行碰撞檢測,并計算相應(yīng)的碰撞反應(yīng),更新繪制結(jié)果。OSG中提供了osg::LineSegmen,用于表示一個包含一個起點和一個終點的線段類;osgUtil::Int ersectVisiotr是一個接受線段的類,用于判別與節(jié)點的交集,其中的函數(shù)addLineSegment(line.get())用來添加一條線段到列表當(dāng)中;osgUt il::IntersectVisitor::HitList可以得到相交點的具體位置,從而計算出距離。本系統(tǒng)中采用基于視點向前線段探測的碰撞檢測方法,用OSG實現(xiàn)算法的代碼:
//定義一個交集訪問器對象
osgUtil::IntersectVisitor iv;
iv.setTraversalMask(_intersectTraversalMask);
//創(chuàng)建線段
osg::ref_ptr
//設(shè)置線段起點和終點
segForward->set(_eye,_eye+lv*(signedBuffer+distanceToMove));
//將線段添加到交集訪問器中
iv.addLineSegment(segForward.get());
//將場景中節(jié)點與交集訪問器綁定
_node->accept(iv);
//線段與節(jié)點如果相交,則發(fā)生碰撞,同時創(chuàng)建碰撞列表
if(iv.hits())
{//創(chuàng)建碰撞列表
osgUtil::IntersectVisitor::HitList&hitList=iv.getHitList(segForward.get());
}
一般地,OSG導(dǎo)入的三維場景是不帶天空的,本系統(tǒng)實現(xiàn)天空的顯示,是將要顯示的場景,放到一個正六方體的盒子中,在盒子的內(nèi)壁貼上天空貼圖,如圖5所示:
圖5 天空貼圖
來模擬天空,當(dāng)攝像機運動的時候,能夠看見場景遠處的天空,實現(xiàn)天空盒的過程,如圖6所示:
圖6 加載天空盒的三維場景
(1)創(chuàng)建一個正六方體的盒子。
(2)加載6張連續(xù)的無縫拼接天空照片。
(3)將6張照片按一定的順序,貼在正六方體的內(nèi)壁。
(4)動態(tài)更新貼圖,創(chuàng)建一個貼圖回調(diào)對象,該對象由osg循環(huán)自動調(diào)用。
(5)創(chuàng)建一個動態(tài)更新的控制器,用來動態(tài)的移動天空。
在綜合考慮開發(fā)成本和開發(fā)周期的基礎(chǔ)上,采用開源的圖形開發(fā)包OSG開發(fā)了一個簡單的三維引擎,通過分析OSG的場景圖結(jié)構(gòu)及渲染過程,實現(xiàn)了三維場景的交互功能,并研究了將OSG嵌入MFC的原理及技巧,碰撞檢測的實現(xiàn)方法以及天空盒特效。
OSG本身提供眾多高效能的渲染特性,本系統(tǒng)僅實現(xiàn)了幾個基本的功能,在功能的改進和擴充方面還有很大的提升空間。隨著研究的不斷深入,今后還需在輔助工具的開發(fā)方面加大力度。
[1]申閆春,朱幼虹,曹莉,等.基于OSG的三維仿真平臺的設(shè)計與實現(xiàn)[J].計算機仿真,2007,24(06):207—211
[2]熊磊.一種用于虛擬旅游體驗的三維引擎的研究[D].武漢:華中師范大學(xué),2007.
[3]曹莉,李紹彬,申閆春.基于OSG的鏡面反射特效的實現(xiàn)[J].計算機仿真,2009,26(08):208-211.
[4]徐凌.基于OpenSceneGraph引擎的漫游系統(tǒng)的研究與實現(xiàn)[D].武漢:武漢理工大學(xué),2008.
[5]溫轉(zhuǎn)萍,申閆春.基于OSG的虛擬校園漫游系統(tǒng)的設(shè)計與實現(xiàn)[J].計算機技術(shù)與發(fā)展,2009,19(01):217-220.
[6]王銳,錢學(xué)雷.OpenSceneGraph三維渲染引擎設(shè)計與實踐[M].北京:清華大學(xué)出版社,2009.