天天看點

XNA下的自然場景實時渲染

課程完成,把試驗報告放上來留個紀念。 實時動态自然景物模拟實驗報告 1、            選題背景 自然景物模拟是計算機圖形學中一個充滿活力和挑戰的研究課題。它研究的對象都是模拟出大家日常生活中見到大自然場景,如茂盛的植被、綿延的山丘、廣闊的天空、蕩漾的水波等等。然而如何模拟出如此複雜的大自然,進而把它動态的實時的繪制的出來缺失一個巨大的挑戰。可是經典的計算機圖形學的方法在這一領域并不适用。首先自然界中的許多現象是難以用幾何模型有效的表示,例如山體等地形和植被的構造;而且許多自然現象的模拟總是與一些複雜的實體模型相關聯,其運動無法用一個的簡單模型表示,例如雲的模拟和水波的模拟;再有自然界中的存在的物體數目極為巨大,其渲染也是一個很複雜的過程,傳統的渲染算法和光照模型也難以适用。 其實,完全真實的仿真各種大規模的自然現象對于虛拟現實等自然景物模拟的主要應用來說是完全不必要,也是不可能的。基本上我們需要的隻是一個能夠生成這些現在的近似圖像的算法,真實性并不重要,隻要最終的圖像看起來像就可以了。為了模拟自然界中的各種現象,幾十年來許多研究者采用各種方法研究出了許多特殊的方法。而近年圖形處理器(GPU)性能的飛速提升和通用圖形處理程式設計(GPGPU)的發展完善極大的提高了個人計算機對三維圖形算法的處理速度。本程式就主要利用了GPU的處理能力,生成并實時顯示地形、水面、天空、草地和樹木。 2、            開發與測試環境 本程式使用Visual Studio C# express開發,三維顯示采用Microsoft XNA Game Studio express函數庫,運作時需要支援Shader Model 3.0的顯示卡,.Net2.0運作庫和XNA framework運作庫。目前在測試系統(AMD Sempron 2500+,1G記憶體,Geforce 6800GS)上本程式在預設設定上能在800×600的分辯率下以10幀左右的速度運作。(主要是草地和數目比較耗資源,如果減少其數目可以很大的提高幀率。) 3、            地形生成和顯示部分 l         地形的資料結構 本程式中地形是根據高度圖生成的。高度圖是一個記錄高度資訊的數組,它的長寬是我們所需要地形大小的長度加以1所得出的值,它的單元是一個16位無符号整數,足以滿足地形細度的要求。 細節分級地形的最基本單元就是地形方塊(Patch),它是我們實作地形的最基本骨架。地形方塊将擁有不同細度層次的子模型,子模型根據與之相對應的LOD層次的低細度縮放标尺來生成。最高層次的子模型的低細度縮放标尺為1,也就是不簡化。我們的地形子產品定義的低細度縮放标尺的規範是:0~2LOD層次分别對應低細度縮放标尺為。 地形模型生成中比較常用的地形網格編織方式是三角形扇,其優點是友善生成,原理簡單,但是其覆寫面積小,容易造成繪制指令調用過多的情況。為了避免這些缺點,我們采用了三角形條帶的編織方式。其優點是可以編織出覆寫範圍大的網格模型,而且比三角形序列要節省資源(它使用到的頂點索引數目少)。我們的地形子產品所采用的編織順序為:左右循環。在每一行的末尾需要重複一個頂點作為換行之用。為了解決細節層次切換時高度突變的問題,我們采用了這種用于可程式設計管線的編織方式,如下圖所示。                                                         圖:可程式設計管線模式 可程式設計管線下,不需要包含貼圖坐标,因為這可以通過着色器程式即時運算出來。本程式中仍然保留一層貼圖坐标的資料,但它并不是用來儲存貼圖坐标,而是用來儲存不同LOD級别下頂點的高度資訊,是以除去0層高度資訊儲存在位置資料裡,其餘兩個高度資訊将被儲存在這一層貼圖資料中,而0層的LOD下所有相應頂點的位置儲存在頂點緩沖中。這樣可以在着色器中進行LOD中的頂點高度混合操作。這樣就可以很友善的解決不同LOD層次間的裂縫問題。 l         地形生成算法 本程式采用了采用菱形-方形網算法。 首先為要生成三維地形配置設定數組來存儲各點的高程,将四個角點的高度值初始化,然後進行以下兩步的遞歸細分: 4、      步驟:取四個點的正方形,在正方形中點生成一個随機值,中點為兩對角線交點。中點高度值是四個角點高度值的平均再加上一個随機位移量計算得到的。這樣就得到了一個棱形網格。設四個角為a、b、c、d,則中點e的高度值則由式子diamond 5、      步驟:取每個四點形成的棱形,在棱形的中心生成一個随機位移值。平均角點高程(或屬性)值再加上與 diamond 步相同的随機量,計算出每條邊中點值。這樣又得到一個正方形網格。square 這個步驟在代碼的注釋中也有詳細說明。其中random項為每步所加的偏移量,是一個具有高斯分布的随機變量。但是一方面由于C#函數庫沒有提供高斯函數,另一方面這裡為了計算的速度,我們使用了普通的均勻随機變量以代替。事實從效果上看真實度的差别并不大。最後,為了保證最終得到地形的效果,程式中還手工對地形進行了一些處理,把地形的中心降低,四角升高,以保證在最終顯示時不會看到地形的邊緣影響效果。 l         可見性檢測 本程式中地形塊的可見性檢測包括距離檢測和視錐檢測兩個方面。距離檢測,就是檢測錄影機與方塊中心的距離,以确定此地形方塊是否處于有效視覺範圍之内。視錐檢測是将每個地形方塊的包圍盒與目前的視錐體進行相交檢測。 經過這兩個步驟的地形塊剔除,使得需要顯示的面片數量大大減少,提高了顯示的效率。同時,由于草地和樹木也依附于地形塊,此步驟也可以直接判斷草地和樹木的可見性。 l         層次細節顯示 本程式中采用了3層的層次細節來實作地形的層次細節簡化,地形的顯示采用了GeoMipMap的方法。 對每個地形塊所顯示的細節層次的選擇。這裡使用的是距離選擇,即根據每個地形方塊的中心位置與錄影機之間的距離差别來決定地形方塊目前的細節層次。對于不同細節層次之間的過渡,在可程式設計管線上采用了漸進式細節層次過渡,它可以有效防止地形撕裂和層次切換突變的出現。本程式的做法是在頂點資訊中儲存不同細節層次下該頂點的高度資訊,在頂點着色器中根據頂點與視點距離來插值計算該頂點目前應該處于的位置,使得不同LOD層次的地形塊在相同位置的頂點有相同的高度值。(此算法的主要問題是在移動視點時同一位置的高度會有一定變換,影響視覺效果。)地形的渲染采用了8個細節紋理逐次混合得到(目前沒有計算地形的法向,也沒有計算地形的光照。),各紋理的坐标都根據此點的世界坐标計算生成,同時根據象素點到視點的距離計算一個表示霧的混合項。地形渲染的網格如下:

XNA下的自然場景實時渲染

最終生成的地形效果如下(沒有顯示草地和樹木):

XNA下的自然場景實時渲染

6、            水面渲染 水面渲染的網格仍然采用繪制地形的網格。這樣可以節省系統資源,同時也很能夠利用地形網格本身的層次細節能力,而且可以在着色器上友善的計算出水面的深度,但是這樣使得水面的網格的粒度比較大,對于波浪的位移表現的不精确細緻。 水面的網格在着色器中統一設定到固定的高度,然後在計算兩個正弦波合成其在高度方向上的位移。(本來準備使用頂點紋理通路(Vertex Texture Fetch)來從多個高度圖中獲得頂點的高度位移,但似乎準備的高度圖有點問題,效果不明顯,是以隻有使用簡單的正弦波混合。)而水面的法向在象素着色器上通過對兩張凹凸紋理(Bump map)的混合來确定。這樣就可以在粒度較大的網格上很好的表現水面的細節。(缺點是難以與通過正弦波混合成的高度配合。)取得此點的法向後,再根據此點到視點的方向就可以算出此處的水面高光項。同時也可以計算出此點的視線反射方向,以此方向通路環境紋理貼圖,就可以得到此點的反射項。而再根據此點的深度以法向方向擾動目前點的世界坐标值,再以此計算地形的顔色值,作為此點的反射項。最終跟據此點的深度值混合反射項、折射項,再加上高光項就得到最終的渲染顔色。  

XNA下的自然場景實時渲染
XNA下的自然場景實時渲染
XNA下的自然場景實時渲染

7、            天空的渲染 天空體的網格模型采用了一個半球面和一個圓柱面合成。為了得到比較平滑的效果,在高度次元上把它壓縮到1/4,整體貼上一個天空的紋理。雲層通過不同的紋理偏移量從兩個紋理貼圖中混合産生,并渲染到天空體的半球上。太陽采用一張貼圖渲染再用Alpha混合繪制到天空上。通過不斷的更新雲層的紋理偏移量就可以實作雲層漂移的效果。

XNA下的自然場景實時渲染
XNA下的自然場景實時渲染

8、            草地渲染 為了得到更好的效果,本程式使用了簡單的草模型+硬體Instancing技術在一個渲染批次内繪制大量的草。所謂Instancing,就是把提供給頂點渲染器的頂點資料分為模型資料和屬性資料兩個部分。在模型資料中提高草模型的局部坐标值、法向等直接從模型檔案中提供的資訊。在屬性部分中提供每一棵草的世界坐标系坐标、方向、大小、擺動起點等資訊。 在模拟草的搖擺時,對每個頂點分别做一定的位移(直接跟據其局部坐标系的高度值乘上一個正弦函數值再乘上位移方向來計算)。下圖是本程式實際渲染出的草地效果:

XNA下的自然場景實時渲染
XNA下的自然場景實時渲染

9、            樹木生成 這次大作業主要采用 95 年的文章 Creation and Rendering of Realistic Trees[1] 中提出的 3 維樹木生成、繪制辦法。 這篇文章中提出了一個生成樹木的模型,這個模型是基于樹木的整體幾何結構。這個模型能夠生成不同種類的樹木以及相關的植被。這個模型還能夠處理随機參數以保證一種特定的樹木也有許多不同的結構變化。 文章中的模型生成樹木主要基于兩種元素,樹幹和葉子。樹幹包括樹木的主軀幹和分支。樹幹單元模型主要是用一個窄的類似圓錐形的管子表示。而每一層上的樹幹都被分解成為一系列的類似圓柱的片斷,通過随機生成這一系列片斷的 z 坐标軸方向,可以使得樹幹變成簡單的 S 形。在模型種,樹幹的分裂可以通過随機的參數确定分支個數以及角度。同一層上的分支,可以采用複制的手段生成。僅僅通過複制的手段生成的分支,從分支的類型上說,顯然不是很豐富。是以在生成下一層次的分支時,主要是通過利用上一層分支參數計算出本層分支的參數,并在一定範圍内進行随機。通過這樣的方法即保證了分支在某些特征上的延續,又加入了随機的多樣性。同時,文章的模型中還提出了分支的範圍控制,除了主軀幹以外的分支的範圍都有其父分支的範圍的一個函數定義。 模型中葉子的生成。在模型中,分支的層數通常為 3 到 4 層。在分支遞歸的最後一層遞歸時采用葉子替代分支顯示。葉子的參數利用分支中的部分參數,主要是角度資訊。葉子的密度類似于分支也是由父分支的一些參數決定。關于葉子的形狀是根據預先定義好的形狀生成,具體參數根據樹木類型不同而設定。 文章提出的模型還提出剪枝用于保證生成的樹木能夠包裹在一個特定包圍中,以使得生成的樹木與真實樹木模型相似。在生成每個新的分支時,要求分支調整自己的長度以便落在包圍中。文章提出的模型可以通過修改參數來修改包圍的大小和形狀。 在真實場景中,樹木不是靜止不動的,會因為風的作用産生擺動,文章中也通過引入分支片段的曲率根據時間變化來達到模拟樹擺動的效果。通過随機産生的擺動參數,在動畫的每幀内重新計算分支片段的位置。 在文章的模型中為了考慮樹木向上生長的趨勢,引入了垂直方向上的吸引力來修正分支片段上的曲率。 最後,因為實際上,樹木的葉子的面朝向有一定要求,一般是向陽和向光的,是以要引入參數重新修正葉子的朝向。 程式中采用的樹的參數來源于《 Creation and Rendering of Realistic Trees 》論文附錄中的幾種樹的參數以及 sourceforge 上 arbaro project 上提供的一些樹的具體參數,程式實作了主要的 8 種樹的生成。 樹生成設計到的類具體如下: Tree 類:一顆樹的類,可以通過 Grow 方法生成整棵樹。 TStem 類:一顆樹上的一個枝幹的類,可以通過 TStem 的 substems 通路孩子節點。 TParameters 類:專門儲存一顆樹的具體參數。 TSectionFrame 類:專門儲存 Stem 上每個 Section 的旋轉矩陣和原點。 l        Tree 類具體實作: GetRandomValue 方法:獲得一個指定上界的随機浮點數,其中随機數的種子由 Tree 類的 mSeed 決定。 Grow 方法:由 Tree 中的主幹生出子節點的樹,以及樹葉。 CreateMesh 方法:獲得 Tree 中所有的枝幹節點以及樹葉節點,并且獲得枝幹和樹葉上的三角形。 l        TStem 類具體實作方法: CreateSection :生成每個 Stem 上某個小 Section 圓柱的頂點,根據傳入的 pSectionFrame 決定 Section 的位置, fSectionRadius 決定圓柱的半徑。 CreateStructure :生成指定 Stem 的子節點的結構,如果子節點為葉子,則根據指定參數生成葉子的數目;如果子節點為 stem ,則生成 stem 的子節點的結構,再遞規調用子節點的 CreateStructure 方法。 CalculateSectionRadius :根據實作指定的參數計算每個 stem 上每個小 section 圓柱的半徑,每個 section 的半徑是由 section 在 stem 上 z 方向的位置決定的。 CalculateVerticalAttraction :計算垂直引力 CreateLeaf :構造葉子頂點,根據 LeafShape 參數生成葉子的四邊形結構。 Grow :生成目前 Stem 上的所有的 section 圓柱,根據目前層上的 CurveRes 參數修改每個 Section 的 pSectionFrame 參數以達到在一個 Stem 上的 Section 有彎曲效果。生成完以後根據 Stem 的子節點是葉子還是枝幹調用 GrowLeaves 或 GrowSubStems 。 GrowSubStems :生成目前 Stem 的子枝幹的每個 SubStem 的朝向和位置,以及初始半徑等參數,然後調用 Grow 生成這些子 Stem 。 GrowLeaves :生成目前 Stem 上的葉子的位置和朝向,再調用 CreateLeaf 生成葉子。 AddLeavesVertices :将生成的葉子頂點組合成一個 ArrayList 。 AddMeshVertices :将生成的枝幹的定點組合成一個 ArrayList 。 AddMeshFace :生成枝幹的面片的頂點序 AddLeavesFace :生成葉子的面片的頂點序。 以下是本程式生成的各種類型的樹木的圖像:  

XNA下的自然場景實時渲染
XNA下的自然場景實時渲染
XNA下的自然場景實時渲染
XNA下的自然場景實時渲染
XNA下的自然場景實時渲染

  此算法生成的樹木在本程式中也使用Instance的方法來同時顯示多棵同樣的樹木。但由于生成的樹木面片樹木很多,對程式的性能有很大的影響,是以無法同時渲染太多的樹木。 10、      實驗結果    

XNA下的自然場景實時渲染
XNA下的自然場景實時渲染
XNA下的自然場景實時渲染
XNA下的自然場景實時渲染

繼續閱讀