OSG开发概览
1?OSG基礎(chǔ)知識(shí)
??OSG是Open?Scene?Graphic?的縮寫,OSG于1997年誕生于以為滑翔機(jī)愛好者之手,Don?burns??為了對滑翔機(jī)的飛行進(jìn)行模擬,對openGL的庫進(jìn)行了封裝,osg的雛形就這樣誕生了,1998年Don?burns?遇到了同樣喜歡滑翔機(jī)和計(jì)算機(jī)圖形學(xué)的Robert?Osfield?,從此Robert?Osfield加入了osg小組的開發(fā)并一直擔(dān)任開發(fā)小組的組長。
??OSG不但有openGL的跨平臺(tái)的特性和較高的渲染性能,還提供了一系列可供3D程序開發(fā)者使用的功能接口,包括2D和3D數(shù)據(jù)文件的加載、紋理字體支持、細(xì)節(jié)層次(LOD)控制、多線程數(shù)據(jù)分頁處理等。OSG廣泛應(yīng)用于飛行仿真等領(lǐng)域,包括Flightgear,及美國軍方投資的仿真項(xiàng)目Delta3d等
1.1?計(jì)算機(jī)繪圖的基本知識(shí)
??首先要先回顧一下,在顯示世界中,我們是如何作畫的。
??在現(xiàn)實(shí)世界中,繪制一副畫,我們需要的東西就是彩筆、白紙。通過選擇不同顏色的彩筆,在白紙上移動(dòng),就可以將白紙上的不同的點(diǎn)描繪上不同的顏色,而所有這些點(diǎn)連接起來,從人的宏觀視野看來,就構(gòu)成了一副對人有意義的畫作。
??類比到計(jì)算機(jī)的實(shí)際中來。在計(jì)算機(jī)的世界里。作畫的過程又是怎樣的呢?
??同樣,繪制虛擬的圖像,也需要“彩筆”和“白紙”。在計(jì)算機(jī)的世界里,“彩筆”就是Direct3D之類的繪圖API函數(shù),而“白紙”就是存儲(chǔ)數(shù)據(jù)的內(nèi)存。我們在內(nèi)存中劃分出一塊區(qū)域,其中的數(shù)據(jù)就是對一個(gè)真實(shí)世界的模擬。一個(gè)數(shù)據(jù)就描述真實(shí)世界中一個(gè)點(diǎn)的屬性。在我們作畫前,他們都只有一個(gè)初始值,就像白紙?jiān)谧鳟嬊爸挥邪咨粯印6谧鳟嫼?#xff0c;每一個(gè)數(shù)據(jù)都有了獨(dú)特的意義,將整片數(shù)據(jù)連接在一起看,就是一副有意義的圖景。作畫的過程就是對內(nèi)存中的每一個(gè)數(shù)據(jù)進(jìn)行賦值的過程,相當(dāng)于用彩筆給白紙上的一個(gè)點(diǎn)進(jìn)行著色。選擇不同的API函數(shù),可以畫出不同的形狀。
1.2?OSG程序框架
??一個(gè)最簡單的OSG程序如下所示,當(dāng)然在如果是在VS下面進(jìn)行編輯的話要進(jìn)行一些設(shè)置,要設(shè)置OSG的lib和include目錄。
osgViewer::Viewer?viewer 申請了一個(gè)viewer,可以理解為申請一個(gè)觀察器,該觀察可以查看模型
viewer.setSceneData(osgDB::readNodeFile("glider.osg")) 這里是設(shè)置觀察器Viewer中的數(shù)據(jù),換句話說,有了觀察器,就可以添加模型了
viewer.realize() 這個(gè)語句表達(dá)的意思非常多,事實(shí)上可以定位到Viewer.cpp的realize函數(shù),會(huì)發(fā)現(xiàn)里面的操作非常多,可以理解為這是在渲染前的最后一步,會(huì)檢查和設(shè)置圖形上下文,屏幕啊什么的,會(huì)讓你以前的設(shè)置,對Viewer的設(shè)置都生效。
viewer.run(); 這一句的意思就是渲染了,如果要解釋它的意思的話,可以用下面的幾個(gè)語句來替代:
while(!viewer.done()){viewer.frame();}.意思也就是說,只要viewer沒有結(jié)束,那么就繪制它的每一個(gè)幀[frame]。
1.3?OSG簡單模型控制
1.3.1?添加模型
在OSG當(dāng)中模型是使用osg::Group和osg::Node來裝載在一起的,比如同時(shí)需要加入兩個(gè)模型,模型A了模型B,AB各自是一個(gè)NODE,那么可以使用以下語句來做到,首先使用一個(gè)Group,然后Group->addChild(A),同樣,之后要Group->addChild(B)。然后再把Group添加到viewer當(dāng)中就可以了。如圖3.1所示AB之間的關(guān)系。在這里要申明的是NODE是Group的父類,在類中都有相應(yīng)的方法可以轉(zhuǎn)到對方,故Node與Group
是通用的,Node也可以被當(dāng)作Group來用。
?
?
圖?31?AB都加入到Group當(dāng)中
簡單示例代碼如下:
則運(yùn)行結(jié)果為:
?
圖?32示例運(yùn)行結(jié)果
1.3.2?刪除結(jié)點(diǎn)
如果我們不需要某個(gè)結(jié)點(diǎn)了,比如圖3.2我們看那個(gè)小飛機(jī)很不爽,我們想把它從場景中刪除掉。不知道于某種目的,反正現(xiàn)在要?jiǎng)h除掉,可能是開始想看見它現(xiàn)在不想看見它了。可以通過removeChild方法,除多個(gè)孩子也可以通過removeChildren方法,里面的參數(shù)有些需要索引值,有些需要結(jié)點(diǎn)本身的指針,讀者可以自己嘗試。這里要注意的是,如果要?jiǎng)h除一個(gè)結(jié)點(diǎn),那么該結(jié)點(diǎn)下的所有結(jié)點(diǎn)都會(huì)被刪除。如果一個(gè)結(jié)點(diǎn)被加入到一個(gè)組中兩次,那么這兩次是分別存在的,刪除一次還有另一次。刪除操作不能說不是個(gè)危險(xiǎn)的操作,有些時(shí)候,尤其在有移動(dòng)結(jié)點(diǎn)等等混在一起時(shí),刪除操作有時(shí)候會(huì)發(fā)生一些比較奇怪的現(xiàn)象。在內(nèi)存映象當(dāng)中,如果一個(gè)模型被讀取一次,而用了多次,那么所占用的空間是不會(huì)改變的。
1.3.3?隱藏模型與結(jié)點(diǎn)開關(guān)
??隱藏模型
???隱藏模型其實(shí)模型仍在渲染當(dāng)中,因此損耗并未減少,只不過隱藏了而已,隱藏的確不是個(gè)什么好操作,但是有時(shí)候?qū)π∧P痛_實(shí)也很實(shí)用。node->setNodeMask可以設(shè)置隱藏與顯示。
??節(jié)點(diǎn)開關(guān)
在OSG當(dāng)中,專門有一個(gè)類來負(fù)責(zé)打開與關(guān)閉結(jié)點(diǎn),該類名為osg::Switch,里面有相應(yīng)的方法來控制它所管理的結(jié)點(diǎn)的打開與關(guān)閉。
兩個(gè)方法都能控制模型的顯示和隱藏,區(qū)別在于隱藏模型方法不會(huì)讓模型在內(nèi)存中消失,這樣對于小的物體頻繁的調(diào)用會(huì)節(jié)省一些時(shí)間,而對于有些大的模塊在用一次以后可能很久再用第二次,這個(gè)時(shí)候用節(jié)點(diǎn)開關(guān)可以將模型銷毀,再次使用再調(diào)入內(nèi)存,以防止占用更多的資源。
1.3.4?超級指針
超級指針的機(jī)制,其實(shí)就是引用一個(gè)計(jì)數(shù)器,這個(gè)計(jì)數(shù)器會(huì)計(jì)算這個(gè)箱子被引用的次數(shù),被別人引用一次這個(gè)計(jì)數(shù)器增加一,別人不用一次,即:釋放一次,則計(jì)數(shù)器減一。當(dāng)減至0時(shí),內(nèi)存放掉不用。
們來看使用一個(gè)Node的三種方法,對比一下:
??//方法一,最好的方法,十分安全,也是OSG中最常用的方法,多少版本它都沒變
osg::ref_ptr<osg::Node>aNode(new?osg::Node());
group->addChild(aNode.get());
??//方法二,也是非常好的方法,有時(shí)候不適用,但也十分安全
group->addChild(new?osg::Node());
??//方法三,非常危險(xiǎn),但是令許多人無故鋌而走險(xiǎn)的方法
osg::Node*anotherNode=new?osg::Node();
group->addChild(anotherNode);
方法一:在new::Node()時(shí)申請了一個(gè)Node的資源,這時(shí)在堆內(nèi)引用該Node的計(jì)算器會(huì)被置1。在group->addChild(aNode.get())時(shí)又引用了一次,會(huì)再加1。在這兩次引用都結(jié)束時(shí),Node的資源就會(huì)被釋放。
方法二:這個(gè)方法也是很實(shí)用的,但是無法引出Node的指針,也許在別處可以用到,事實(shí)上會(huì)經(jīng)常用到。如果已經(jīng)這樣做了,得到Node指針也不是不可以的,可以使用NodeVisitor來得到Node的指針,也可以使用findChild方法來做這件事。
方法三:這個(gè)應(yīng)該是最常用,但是最爛的方法了,原因在于如果在osg::Node*antherode=new?osg::Node()之后發(fā)生了錯(cuò)誤,拋出了異常,誰來釋放Node所占用的資源呢。而這個(gè)異常在后面被捕獲,程序正常的走下去,而內(nèi)存卻沒有被正常的放掉。
1.3.5?移動(dòng)/旋轉(zhuǎn)/縮放模型
移動(dòng)/旋轉(zhuǎn)/縮放其實(shí)都是對矩陣進(jìn)行操作,在OSG當(dāng)中,矩陣可以當(dāng)作一個(gè)特殊的結(jié)點(diǎn)加入到root當(dāng)中,而矩陣下也可以另入結(jié)點(diǎn),而加入的結(jié)點(diǎn)就會(huì)被這個(gè)矩陣處理過,比如移動(dòng)過/旋轉(zhuǎn)過/縮放過。在OSG中控制矩陣的類為osg::MatrixTransform。
??移動(dòng)
osg::Matrix::translate
??旋轉(zhuǎn)
osg::Matrix::rotate
??縮放
osg::Matrix::scale
1.4?基本幾何圖元
1.4.1?基本繪制方法
首先來看一些OSG中的最基本的繪制路數(shù)。如果我們要繪制一個(gè)正方形,繪制有色彩,未貼圖。首先我們必須要申請一個(gè)osg::Geometry,把這個(gè)Geometry加入到Geode就可以了。在這個(gè)Geometry中要設(shè)置一些元素,最基本的是頂點(diǎn)Vertex,顏色color,以及頂點(diǎn)的關(guān)聯(lián)方式和法線normal.就可以了。如圖3.3所示。
?
?
圖?33幾何體繪制過程
1.4.2?可繪制的圖元
所有可繪制的圖元包括:
??POINTS[點(diǎn)]
??LINES[線]
??LINE_STRIP[線帶]
??LINE_LOOP[閉合線段]
??TRIANGLES[三角形]
??TRIANGLE_STRIP[三角帶]
??TRIANGLE_FAN[三角扇]
??QUADS[四方塊]
??QUAD_STRIP[四方塊帶]
??POLYGON[多邊形]
在OSG中設(shè)置直線線寬的專門有一個(gè)函數(shù)來管理,叫做LineWidth,它本身屬于狀態(tài)與屬性類別中的類。事實(shí)上也是從那里派生而來。所有設(shè)置狀態(tài)的操作都與此類似。
1.4.3?內(nèi)置幾何類型
如同OpenGL一樣,OSG同樣有一套內(nèi)置幾何類型,這些幾何類型都在類osg::Shape中,這些shape本身都可以本當(dāng)成一個(gè)Draw結(jié)點(diǎn)加入到geode中,然后再人geode中添加到root里進(jìn)行渲染。形狀共有九種,分別為:osg::Box[盒子],osg::Capsule[膠囊形],osg::CompositeShape[組合型],osg::Cone[圓錐形],osg::Cylinder[圓柱形],osg::HeightField[高程形],osg::InfinitePlane[有限面],osg::Sphere[球形],osg::TriangleMesh[三角蒙皮]。
內(nèi)置幾何類型的渲染過程,如圖4.5所示
?
?
圖?34基本幾何圖元的添加過程
這里要注意的是,一般的形狀態(tài)都有特定的因素,比如Box有長寬,圓有半徑,以及各個(gè)圖形所畫的精細(xì)度都需要指明,這些精細(xì)度在球這樣的形狀上意義還是十分巨大的。在OSG中有專門指明精細(xì)度的類,名為:osg::TessellationHints。以球?yàn)槔?#xff0c;只需要規(guī)定,圓心,半徑和精細(xì)度就可以畫出該球。
1.5?交互
1.5.1?交互過程
viewer的主要的功能是控制場景,它是場景的核心類,如果能響應(yīng)鍵盤時(shí)得到viewer,那么也可以從鍵盤的響應(yīng)中控制整個(gè)場景。viewer中有一個(gè)方法,名為addEventHandler就是專門做這件事情的。他會(huì)加入一個(gè)事件處理器。于是我們就想,一定要自己寫一個(gè)事件處理器才行,這就必須要了解事件處理器的格式,只要有一個(gè)接口就可以了解它的格式,這個(gè)接口就是:osgGA::GUIEventHandler,于是我們可以寫一個(gè)類A從該類公有派生出來,即:class?A:public?osgGA::GUIEventHandler,在里面處理好各種操作然后加入到viewer當(dāng)中,即:viewer.addEventHadler(new?A(里面可以有參數(shù)));這樣就可以完成操作。
假如類A是一個(gè)事件處理類,那么加入類A可以這樣理解,如圖3.5:
?
圖?35事件A控制場景過程
?
1.5.2?事件類型與響應(yīng)
代碼?????????????? ? 值????????? 事件類型
NONE??????????? ? ?0????????????? 無事件。
PUSH??????????? ??? ?1 鼠標(biāo)某鍵按下,在上面代碼28行有用到。
RELEASE????? ?2 鼠標(biāo)某鍵彈起。
DOUBLECLICK?? 4 鼠標(biāo)某鍵雙擊。
DRAG??? ? 8 鼠標(biāo)某鍵拖動(dòng)。
MOVE 16 鼠標(biāo)移動(dòng)。
KEYDOWN? 32 鍵盤上某鍵按下。
KEYUP? 64 鍵盤上某鍵彈起。
FRAME? 128 應(yīng)該是鼠標(biāo)每幀。沒用過。
RESIZE? 256 窗口大小改變時(shí)會(huì)有的事件。
SCROLL?? 512 鼠標(biāo)輪滾動(dòng)。
PEN_PRESSURE? 1024 手寫板的某事件?
PEN_PROXIMITY_ENTER? 2048 手寫板的某事件?
PEN_PROXIMITY_LEAVE ? 4096 手寫板的某事件?
CLOSE_WINDOWS? 8192 關(guān)閉窗口。
QUIT_APPLICATION? 16384 退出程序。
USER? 32768 用戶定義。
至于為什么都用2的N次方,主要是因?yàn)樗亩M(jìn)制編碼只有一位是一,判斷事件時(shí)很好判斷,只要年哪位是一就可以了。
1.5.3?PICK
pick主要是通過鼠標(biāo)的點(diǎn)擊來拾取一些物體,或者判斷鼠標(biāo)所點(diǎn)擊的位置在哪里。Pick實(shí)現(xiàn)的思路如下圖所示:
?
?
圖?36pick事件流程
判斷射線與viewer中物體相交的方法為發(fā)出射線并相交。在OSG中有庫函數(shù),osgViewer::View::computeIntersections他共有三個(gè)參數(shù):第一個(gè)是x屏幕坐標(biāo),第二個(gè)是Y屏幕坐標(biāo),第三個(gè)是存放被交的結(jié)點(diǎn)以及相交的坐標(biāo)結(jié)點(diǎn)路徑等等相關(guān)信息。
判斷相交結(jié)點(diǎn)為我想要的那個(gè)結(jié)點(diǎn):只需要判斷存放相交射線交場景的結(jié)果集中有沒有要用的結(jié)點(diǎn)就可以了。
?
1.6?漫游
1.6.1?MatrixManipulator
場景的核心管理器是viewer,而漫游必須響應(yīng)事件,比如鼠標(biāo)動(dòng)了,場景也在動(dòng)。響應(yīng)事件的類是osgGA::GUIEventHandler。我們想把響應(yīng)事件的類派生一個(gè)新類出來,這個(gè)類專門用來根據(jù)響應(yīng)控制viewer。這個(gè)類就是osgGA::MatrixManipulator,這個(gè)類有一些設(shè)置矩陣的公共接口,有了這些接口就可以有效的控制viewer了,根據(jù)不同的習(xí)慣,大家還會(huì)設(shè)置不同的控制方式,如同OSG自帶的幾個(gè)操作器,操作都不盡相同。來看一下漫游的主要流程如圖6.1:
?
圖?37一般的場景操作器
操作器必須從osgGA::MatrixManipulator派生而來。osgGA::MatrixManipulator有四個(gè)可以控制場景的重要接口:
四個(gè)矩陣接口可以有效的向viewer來傳遞矩陣的相關(guān)信息。
1.6.2?碰撞檢測
最簡單的碰撞檢測如下圖所示:
?
圖?38簡單的碰撞檢測原理圖
TravelManipulator.dll中用到的就是如圖所示的原理,黑三角形代表沒有移動(dòng)之的位置,控制移動(dòng)的函數(shù)是ChangePosition(osg::Vec3&delta),參數(shù)意思是要移動(dòng)的相對于當(dāng)前點(diǎn)的增量,在黑三角形沒有移動(dòng)時(shí)該函數(shù)在計(jì)算時(shí)先假設(shè)一點(diǎn)newPosition為移動(dòng)后的點(diǎn),而后通過連接這兩個(gè)點(diǎn),而后通過判斷與場景的模型是否有交點(diǎn)來判定這個(gè)移動(dòng)可不可以執(zhí)行,如圖所示,兩者之間有個(gè)大盒子,是穿不過去的,所以只有保持在原地。就算沒有這個(gè)盒子,移動(dòng)后的新點(diǎn)又與地面在某種程序上有一個(gè)交點(diǎn),這證明移動(dòng)是不可行的。這可以防止用戶穿過地板到達(dá)地下去。
1.6.3?路徑漫游
使用path文件的方法如下面示例
我們可以用路徑編輯器編輯path文件,或者可以控制程序中的某個(gè)物體的運(yùn)動(dòng)軌跡然后保存為path文件。
1.7?更新&回調(diào)
回調(diào)的意思就是說,你可以規(guī)定在某件事情發(fā)生時(shí)啟動(dòng)一個(gè)函數(shù),這個(gè)函數(shù)可能做一些事情。這個(gè)函數(shù)就叫做回調(diào)函數(shù),我們可以使用已有回調(diào)函數(shù)或者自定義回調(diào)函數(shù)。
??使用已有回調(diào)
已有的回調(diào)的類型有很多種,一般很容易就想到的是UpdateCallBack,或者EventCallBack等
??自定義回調(diào)
自定義回調(diào)為從一個(gè)回調(diào)類型派出生自己的回調(diào),然后具有該種回調(diào)的特點(diǎn)等等。
NodeVisitor是一個(gè)極有用的類,可以訪問結(jié)點(diǎn)序列,使用的方法大同小異,NodeVisitor的工作流程如下圖所示:
?
圖?39NodeVisitor工作流程
在主結(jié)點(diǎn)accept之后,結(jié)點(diǎn)數(shù)據(jù)立即傳至NodeVisitor中去,應(yīng)用apply函數(shù),可以將數(shù)據(jù)定任一些操作,更多的操作還是需要硬性的制做與調(diào)用。
1.8?粒子系統(tǒng)初步
在OSG中提供有專門的粒子系統(tǒng)工具,名字空間為osgParticle,OSG對經(jīng)常使用的粒子模擬都做了專門的類,如:ExplosionEffect用于暴炸的模擬,FireEffect用于火的模擬,ExplosionDebrisEffect用于爆炸后四散的顆粒模擬等等。
在OSG中使用粒子系統(tǒng)一般要經(jīng)歷以下幾個(gè)步驟:
第一步:確定意圖(包括粒子的運(yùn)動(dòng)方式等等諸多方面)。第二步:建立粒子模版,按所需要的類型確定粒子的角度(該角度一經(jīng)確定,由于粒子默認(rèn)使用有Billboard所以站在任何角度看都是一樣的),形狀(圓形,多邊形等等),生命周期等。第三步:建立粒子系統(tǒng),設(shè)置總的屬性,第四步:設(shè)置發(fā)射器(發(fā)射器形狀,發(fā)射粒子的數(shù)目變化等),第五步:設(shè)置操作(旋轉(zhuǎn)度,風(fēng)力等等因素)。第六步:加入結(jié)點(diǎn),更新。下圖描述了各個(gè)部分是協(xié)調(diào)工作的方式:
?
圖?310粒子系統(tǒng)各個(gè)部分是協(xié)調(diào)工作的方式
上圖中各個(gè)部分所對應(yīng)的類如下圖所示
?
圖?311粒子系統(tǒng)各部分對應(yīng)的類
?
1.9?視口&LOD&Imposter
1.9.1?多視口
多視口的原理是自己創(chuàng)建所有的相機(jī),包括主相機(jī),這樣我們可以隨意的添加相機(jī)。
首先我們要?jiǎng)?chuàng)建視口必須有以下幾件東西,第一,了解整個(gè)屏幕的分辯率有多大,這樣可以分辯視口的大小,好分割開來。第二,上下文。我們必須自己手動(dòng)的打開設(shè)置上下文。每個(gè)視口的數(shù)據(jù)也不一定非要與主視口的相同。但是矩陣一般是同步的。也就是說:主視口里有棟樓,從視口里可以是平面圖什么的。了解整個(gè)屏幕的分辯率可以用這個(gè)類:osg::GraphicsContext::WindowingSystemInterface意思是說系統(tǒng)接口,可以獲得當(dāng)前環(huán)境的各種信息。有一方法叫getScreenResolution,可以得到分辯率。之后上下文了,osg::GraphicsContext里面可以設(shè)置窗口大小,緩存什么的,大多數(shù)的東西都在這里面設(shè)置。
1.9.2?LOD
LOD即level?of?details
LOD比起PagedLOD而言并非十分的常用,有個(gè)地方用的特別多,那就是把一個(gè)好好的模型加一個(gè)視矩壓成一個(gè)模型,這個(gè)模型比以前的看來就是多了個(gè)視矩的控制,遠(yuǎn)了看不見,近了能看見。
在模型中加LOD頭結(jié)點(diǎn)的方式如下所示:
1.9.3?Imposter
用動(dòng)態(tài)圖片來替換場景的實(shí)用技術(shù):imposter.可以把它法做LOD一樣使用,只不過它
不是變模型變沒有,而是使它換成一張圖
示例代碼如下:設(shè)置一個(gè)視矩,超過這個(gè)視距模型會(huì)變?yōu)橐粡垊?dòng)態(tài)圖
1.10?文字&模型陰影
1.10.1?HUD?
HUD即head?up?display
文字在3D場景中顯示往往要經(jīng)歷以下幾步:讀取字體點(diǎn)陣信息->轉(zhuǎn)化為圖像->反走樣->最終圖像。在反走樣期間可以處理可種模糊效果,在最終圖像形成時(shí)可以設(shè)置如何擺放。OSG中有一個(gè)TEXT類,提供可很多文字顯示的方法,比如
等等,可以很方便的調(diào)用
1.10.2?陰影
OSG對陰影的支持也相當(dāng)?shù)暮?#xff0c;可以很容易的寫出簡單的陰影效果,可以參考例子osgShadow
OSG有一個(gè)專門的shadow類來支持陰影效果,提供了很多接口,如:
等等
?
PS:本文為幾個(gè)月前整理,參考書:FreeSouth的《QpenSceneGraph程序設(shè)計(jì)》
總結(jié)
- 上一篇: 在桌面上创建路径
- 下一篇: COGS182 [USACO Jan07