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