• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            基于四叉樹(shù)空間劃分的地形實(shí)時(shí)渲染方法

            地形是計(jì)算機(jī)圖形的一個(gè)重要組成部分,而它又具有特殊的形態(tài)。地形往往覆蓋面積極廣,且精度要求很高,使得我們必須用許多多邊形來(lái)描述。這樣的特點(diǎn)使得我們不能像對(duì)待其他普通模型那樣對(duì)待地形。要想實(shí)時(shí)地渲染地形,我們需要一些特殊的方法。

                地形渲染一直以來(lái)都是計(jì)算機(jī)圖形學(xué)中一個(gè)重要的研究領(lǐng)域。并且在這一方面已經(jīng)誕生了許多優(yōu)秀的算法。其中包括基于體素的渲染方法,也有基于多邊形的渲染方法。早期的游戲,如三角洲特種部隊(duì)就是采用體素渲染法的成功例子。體素法類似光線追蹤渲染,它從屏幕空間出發(fā),找到地形與屏幕像素發(fā)出的射線交點(diǎn),然后確定該像素的顏色。這種方法不依賴具體的圖形硬件,整個(gè)渲染過(guò)程完全使用CPU處理,因此它不能使用現(xiàn)代硬件來(lái)加速,并且對(duì)于一個(gè)場(chǎng)景來(lái)說(shuō),往往不只是地形,還有其他使用多邊形描述的物體,體素法渲染的圖像很難與硬件渲染的多邊形進(jìn)行混合,因此這種方法現(xiàn)在用得極少。而多邊形渲染方法則成為一種主流。選擇多邊形來(lái)描述和渲染地形有很多的理由和優(yōu)點(diǎn)。最重要的是它能夠很好地使用硬件加速,并且能夠和其他多邊形對(duì)象一起統(tǒng)一管理。

                已有大量?jī)?yōu)秀的基于多邊形的地形渲染算法。比較經(jīng)典的算法有M. Duchaineau等人提出ROAM算法。這個(gè)算法采用一棵三角二叉樹(shù)來(lái)描述整個(gè)地形。一個(gè)地形在最初的層次上由兩個(gè)較大的等腰直角三角形組成,這兩個(gè)等腰直角三角形可以被不斷地細(xì)分來(lái)展現(xiàn)地形的更多細(xì)節(jié)。每一次細(xì)分過(guò)程都向直角三角形的斜邊的中點(diǎn)處增加一個(gè)由高程數(shù)據(jù)所描述的頂點(diǎn),該點(diǎn)將所在的直角三角形一分為二,同時(shí)該算法也定義了一些規(guī)則來(lái)保證地形中不會(huì)因相鄰兩個(gè)三角形細(xì)節(jié)層次的不同而出現(xiàn)裂縫。這個(gè)算法已被許多游戲所采用。還有一類算法,通過(guò)將地形在X-Z投影面上不斷地規(guī)則細(xì)分來(lái)得到不同的細(xì)節(jié),這就是本文要介紹的四叉樹(shù)空間劃分算法。另外,最新提出的一個(gè)地形算法也不得不提,Hugues Hoppe在2004年提出的幾何裁剪圖方法(Geometry Clipmaps),算法使用了最新硬件所支持的頂點(diǎn)紋理來(lái)定義地形的外觀,并且對(duì)于距離攝影機(jī)不同遠(yuǎn)近的地方采用不同的紋理層,最大限度地使用硬件加速了地形渲染的過(guò)程。這個(gè)方法聽(tīng)起來(lái)非常美妙,但它目前只被較少的硬件支持。因?yàn)轫旤c(diǎn)紋理是Shader Model 3.0才支持的功能,也就是說(shuō)只有DirectX 9.0c級(jí)別的顯卡才能支持這種算法。這對(duì)于某些有普及性要求的圖形應(yīng)用程序,尤其是對(duì)游戲來(lái)講不是一件好的事情。因此大多數(shù)人現(xiàn)在還在使用經(jīng)典的地形渲染方法。

                首先,基于四叉樹(shù)的地形渲染方法使用高程數(shù)據(jù)作為數(shù)據(jù)源。且算法要求高程數(shù)據(jù)的大小必須為2n+1的正方形。所謂高程數(shù)據(jù),即色彩范圍在0-255的灰度圖片,不同的灰度代表了不同的高度值。如果某高程數(shù)據(jù)指出這個(gè)高程數(shù)據(jù)最高處的Y坐標(biāo)值是4000,那么在高程數(shù)據(jù)中一個(gè)值為255的像素點(diǎn)就表示這個(gè)點(diǎn)所代表的地形區(qū)域的高度是4000,同理如果該像素值是127那么就表示這個(gè)點(diǎn)所代表的地形區(qū)域的高度是4000×(127/255)=2000。高程數(shù)據(jù)的每個(gè)像素都對(duì)應(yīng)所渲染網(wǎng)格中的一個(gè)頂點(diǎn)。另外還有一個(gè)參數(shù)描述頂點(diǎn)與頂點(diǎn)之間的水平距離,以及一個(gè)描述最大高度的參數(shù)。因此地形的基本數(shù)據(jù)結(jié)構(gòu)如下:

                struct Terrain
                {
                    char **DEM; //一個(gè)描述高程數(shù)據(jù)的二維數(shù)組
                    float CellSpace;
                    float HeightScale; 
                }

                其中,各變量的具體意義如下圖所示:

                有了這些參數(shù),我們可以很容易地由高程數(shù)據(jù)的參數(shù)值得到它所表述的多邊形網(wǎng)格。得到這個(gè)網(wǎng)格之后,可以簡(jiǎn)單地把它放入頂點(diǎn)數(shù)組,并為之建立一個(gè)頂點(diǎn)索引,就可以傳入硬件進(jìn)行渲染了。然而,事情并不是這么簡(jiǎn)單。對(duì)于較小尺寸的高程數(shù)據(jù)(如129×129),這樣做確實(shí)可行,但隨著高程數(shù)據(jù)規(guī)模的增大,所需的頂點(diǎn)數(shù)和描述網(wǎng)格的三角形數(shù)會(huì)急劇膨脹。這個(gè)數(shù)值很快就會(huì)大到最新的顯卡也無(wú)法接受。比如一個(gè)1025×1025的高程數(shù)據(jù),我們需要1025×1025=1050625個(gè)頂點(diǎn),以及1050625×2=2101250個(gè)三角形。就算你的顯卡每秒能夠渲染1000萬(wàn)個(gè)三角形,你也只能得到不到5fps的渲染速度,況且你的場(chǎng)景可能還不只包括地形。因此我們必須想辦法在不影響視覺(jué)效果的情況下縮減所渲染的三角形數(shù)量,另外還應(yīng)該注意一次性將最多的數(shù)據(jù)預(yù)先傳給硬件以節(jié)約帶寬。

                這里要講解的算法,目的就是在不影響或在視覺(jué)可以接受的范圍內(nèi)縮減所渲染三角形的數(shù)量,以達(dá)到實(shí)時(shí)渲染的要求。根據(jù)測(cè)試,本算法在漫游大小為1025*1025的地形時(shí)速度穩(wěn)定在150fps以上(在nVidia Geforce 6200 + P4 1.6GHz的硬件上得到)。

                由于地形覆蓋范圍廣,但它的投影在XZ平面上均勻分布(以下采用OpenGL中的右手坐標(biāo)系,Y軸為豎直向上的坐標(biāo)軸),因此我們有必要考慮對(duì)地形進(jìn)行空間劃分。正是由于這樣的均勻分布,給我們的劃分過(guò)程帶來(lái)了便利。我們不需要具體地去分割某個(gè)三角形,只要選擇那些過(guò)頂點(diǎn)且和X或Z軸垂直的平面作為劃分面即可。例如對(duì)于一個(gè)高程數(shù)據(jù),我們可以以坐標(biāo)原點(diǎn)作為地形的中心點(diǎn),然后沿著X軸和Z軸依次展開(kāi)來(lái)分布各個(gè)頂點(diǎn)。如下如所示。

                首先,我們可以選擇X=0和Z=0這兩個(gè)平面,將地形劃分為等大的四個(gè)區(qū)域,然后對(duì)劃分出來(lái)的四個(gè)子區(qū)域進(jìn)行遞歸劃分,每次劃分都選擇交于區(qū)域中心點(diǎn)并且互相垂直的兩個(gè)平面作為劃分面,直到每個(gè)子區(qū)域都只包含一個(gè)地形單元塊(即兩個(gè)三角形)而不能再劃分為止。例如對(duì)于上圖所示9*9大小的地形塊,經(jīng)過(guò)劃分之后如下圖所示:

                由圖可知,只有高程數(shù)據(jù)滿足大小2n+1的正方形這個(gè)條件,我們才可能對(duì)地形進(jìn)行均勻劃分。我們可以把劃分結(jié)果用一棵樹(shù)來(lái)表述,由于每次劃分之后產(chǎn)生四個(gè)子節(jié)點(diǎn),因此這棵樹(shù)叫四叉樹(shù)。那么,這棵樹(shù)中應(yīng)該存儲(chǔ)那些信息呢?首先對(duì)于每個(gè)節(jié)點(diǎn),應(yīng)該指定這個(gè)節(jié)點(diǎn)所代表的地形的區(qū)域范圍。并不是把地形網(wǎng)格中實(shí)際的頂點(diǎn)放入樹(shù)中,而是要在樹(shù)中說(shuō)明這個(gè)節(jié)點(diǎn)覆蓋了地形的那些區(qū)域。比如一個(gè)子節(jié)點(diǎn)應(yīng)該有一個(gè)Center(X,Y)變量,指定這個(gè)節(jié)點(diǎn)的中心點(diǎn)所對(duì)應(yīng)的頂點(diǎn)索引,或編號(hào)。為了方便起見(jiàn),可以把地形中心點(diǎn)編號(hào)為(0,0)然后沿著坐標(biāo)軸遞增。此外還要有個(gè)變量指定這個(gè)節(jié)點(diǎn)到底覆蓋了地形的多少個(gè)頂點(diǎn)。如下圖所示。

                我們目前的四叉樹(shù)的數(shù)據(jù)結(jié)構(gòu)如下:

                struct QuadTreeNode
                {
                    QuadTreeNode *Children[4];
                    int CenterX,CenterY;
                    int HalfRange;
                }

                有了四叉樹(shù)之后,如何利用它的優(yōu)勢(shì)呢?首先我們考慮簡(jiǎn)單的視見(jiàn)體裁剪(View Frustum Culling,以下簡(jiǎn)稱VFC)。相信很多接觸過(guò)基本圖形優(yōu)化的人都應(yīng)該熟悉VFC,VFC的作用既是對(duì)那些明顯位于可見(jiàn)平截頭體之外的多邊形在把它們傳給顯卡之前剔除掉。這個(gè)過(guò)程由CPU來(lái)完成。雖然簡(jiǎn)單,但它卻非常有效。VFC過(guò)程如下:

                1.為每個(gè)節(jié)點(diǎn)計(jì)算包圍球。包圍球可以簡(jiǎn)單的以中心頂點(diǎn)為球心,最大坐標(biāo)值點(diǎn)(節(jié)點(diǎn)所覆蓋的所有頂點(diǎn)的最大X、Y、Z值作為此點(diǎn)的坐標(biāo)值)到球心的距離為半徑。

                2.根據(jù)當(dāng)前的投影和變換矩陣計(jì)算此時(shí)可視平截頭體的六個(gè)平面方程。這一步可以參考Azure的Blog上的一篇文章,這篇文章給出了VFC的具體代碼。單擊這里

                3.從樹(shù)的根結(jié)點(diǎn)以深度優(yōu)先的順序遍歷樹(shù)。每次訪問(wèn)節(jié)點(diǎn)時(shí),測(cè)試該節(jié)點(diǎn)包圍球與視見(jiàn)體的相交情況。在下面的情況下,包圍球與視見(jiàn)體相交:

                    1) 球心在六個(gè)平面所包圍的凸?fàn)顓^(qū)域內(nèi)部。
                    2) 球心在六個(gè)平面所包圍的凸?fàn)顓^(qū)域外部,但球心到某個(gè)平面的距離小于半徑。

                4.如果相交測(cè)試顯示包圍球和視見(jiàn)體存在交集,繼續(xù)遞歸遍歷此節(jié)點(diǎn)的4個(gè)子節(jié)點(diǎn),如果此節(jié)點(diǎn)已經(jīng)是葉節(jié)點(diǎn),則這個(gè)節(jié)點(diǎn)應(yīng)被繪制。如果不存在交集,放棄這個(gè)節(jié)點(diǎn),對(duì)于這個(gè)節(jié)點(diǎn)的所有子節(jié)點(diǎn)不再遞歸檢查。因?yàn)槿绻粋€(gè)節(jié)點(diǎn)不可見(jiàn),那么其子節(jié)點(diǎn)一定不可見(jiàn)。

                這樣,我們剔除了那些不在視見(jiàn)體內(nèi)的地形區(qū)域,節(jié)約了一些資源。但這還不夠。在某些情況下,VFC可能還會(huì)指出整個(gè)地形都可見(jiàn),在這種情況下,將這么多三角形都畫出顯然是不可取的。

                因此還要考慮地形的細(xì)節(jié)層次(LOD)。我們應(yīng)該考慮到,地形不可能所有部分都一樣平坦或陡峭。對(duì)于平坦的部分,我們用過(guò)多的三角形去描述是沒(méi)有意義的。而對(duì)于起伏程度較大的區(qū)域,只有較多的三角形數(shù)量才不讓人感到尖銳的棱角。再者,無(wú)論地形起伏程度如何,那些距離視點(diǎn)很遠(yuǎn)的區(qū)域,也沒(méi)有必要花費(fèi)太多的資源去渲染,畢竟它們投影到屏幕上的面積很小,對(duì)其進(jìn)行簡(jiǎn)化也是必要的。

                既然我們要對(duì)起伏程度不同的區(qū)域采用不同的細(xì)節(jié)級(jí)別,我們首先必須找到一種描述地形起伏程度的量。與其說(shuō)起伏程度,不如說(shuō)是地形的某個(gè)頂點(diǎn)因?yàn)楸缓?jiǎn)化后而產(chǎn)生的誤差。要計(jì)算這個(gè)誤差,我們先要了解地形是如何被簡(jiǎn)化的。

                考慮下圖所示的地形塊,它的渲染結(jié)果如下圖右圖所示。

               現(xiàn)在如果要對(duì)所需渲染的三角形進(jìn)行簡(jiǎn)化,我們可以考慮這個(gè)地形塊每條邊中間的頂點(diǎn)(下圖左側(cè)紅色點(diǎn)):

               如果將這些紅色的頂點(diǎn)剔除,我們可以得到上圖右邊所示的簡(jiǎn)化后的網(wǎng)格。誤差就在這一步產(chǎn)生。由于紅色的頂點(diǎn)被剔除后,原本由紅色頂點(diǎn)所表示的地形高度現(xiàn)在變成了兩側(cè)黑色頂點(diǎn)插值后的高度。這個(gè)高度就是誤差。如下圖。

                因此,對(duì)于每個(gè)節(jié)點(diǎn),我們先計(jì)算這個(gè)節(jié)點(diǎn)所有邊中點(diǎn)被刪除后所造成的誤差,分別記為ΔH1, ΔH2, ΔH3, ΔH4。如果這個(gè)節(jié)點(diǎn)包含子節(jié)點(diǎn),遞歸計(jì)算子節(jié)點(diǎn)的誤差,并把四個(gè)子節(jié)點(diǎn)的誤差記為ΔHs1, ΔHs2, ΔHs3, ΔHs4。這個(gè)節(jié)點(diǎn)的誤差就是這八個(gè)誤差值中的最大值。由于這是一個(gè)遞歸的過(guò)程,因此應(yīng)該把這個(gè)過(guò)程加到四叉樹(shù)的生成過(guò)程中,并向四叉樹(shù)的數(shù)據(jù)結(jié)構(gòu)中加入一個(gè)誤差變量。如下。

                struct QuadTreeNode
                {
                    QuadTreeNode *Children;
                    int CenterX,CenterY;
                    int HalfRange;
                    float DeltaH;  //節(jié)點(diǎn)誤差值
                }

                下面來(lái)看一下地形的具體渲染過(guò)程。

                首先,我們位于四叉樹(shù)的根結(jié)點(diǎn)。我們此時(shí)考慮根結(jié)點(diǎn)的誤差,如果這個(gè)誤差小于一個(gè)閾值,直接使用根結(jié)點(diǎn)的中心點(diǎn)以及此節(jié)點(diǎn)的四個(gè)邊角點(diǎn)作為頂點(diǎn)渲染一個(gè)三角扇形,這個(gè)三角扇形就是渲染出來(lái)的地形。但是更經(jīng)常的情況下,根結(jié)點(diǎn)的誤差值是很大的,因此算法認(rèn)為要對(duì)根結(jié)點(diǎn)進(jìn)行細(xì)分,以展現(xiàn)更多細(xì)節(jié)。于是對(duì)于根結(jié)點(diǎn)的每個(gè)子節(jié)點(diǎn),重復(fù)這個(gè)步驟,即檢查它的誤差值是否大于閾值,如果大于,直接渲染這個(gè)節(jié)點(diǎn),如果小于,遞歸細(xì)分節(jié)點(diǎn)。目前我們的算法偽代碼如下。

                procedure DrawTerrain(QuadTreeNode *node)
                {
                  if (node->DeltaH > k)
                  {
                       for (i=0;i<4;i++)
                       {
                            DrawTerrain(node->Children[i]);//遞歸劃分
                       }
                  }
                  else
                  {
                       GraphicsAPI->DrawPrimitive(node);//以節(jié)點(diǎn)的中心點(diǎn)和四個(gè)邊角點(diǎn)繪制三角扇形
                  }   
                }

                這個(gè)偽代碼在一個(gè)較高的層次上表述了算法的基本思想。然而我們還有許多問(wèn)題要考慮。其一是目前我們僅僅考慮了地形的細(xì)節(jié)層次和地形表面起伏程度的關(guān)系,但還應(yīng)該考慮地形塊距離視點(diǎn)遠(yuǎn)近跟地形細(xì)節(jié)層次的關(guān)系。解決這個(gè)問(wèn)題很簡(jiǎn)單,我們只需在偽代碼的條件中加入距離這一因素即可。即把

                    if (node->DeltaH > k)
                    {
                        ...
                    }
                    else ...

                改為:

                    if (node->DeltaH / d > k)
                    {
                        ...
                    }
                    else ...

                其中d為節(jié)點(diǎn)中心點(diǎn)與視點(diǎn)之間的距離。而事實(shí)上,當(dāng)細(xì)節(jié)程度與距離的平方成反比時(shí),能夠減少更多的三角形,而且視覺(jué)效果更好,只要閾值k設(shè)置得當(dāng),根本感覺(jué)不出地形因?yàn)橐朁c(diǎn)的移動(dòng)而發(fā)生幾何形變。因此,我們最終的條件式為:

                node->DeltaH / d2 > k

                還有一個(gè)很重要的問(wèn)題,就是這個(gè)算法所產(chǎn)生的地形會(huì)因?yàn)楣?jié)點(diǎn)之間細(xì)節(jié)層次的不同而產(chǎn)生裂縫。下圖說(shuō)明了裂縫的產(chǎn)生原因。

                有兩個(gè)方法可以解決這個(gè)問(wèn)題,一個(gè)方法是刪除左側(cè)節(jié)點(diǎn)中產(chǎn)生裂縫的頂點(diǎn),使兩條邊能夠重合。另一種方法是人為地在右側(cè)地形塊中插入一條邊,這條邊連接中心點(diǎn)和造成裂縫的頂點(diǎn),從而消除裂縫。在渲染地形時(shí),可以采取下面的辦法避免裂縫的產(chǎn)生:

                1.在預(yù)處理階段,為所有頂點(diǎn)創(chuàng)建一個(gè)標(biāo)記數(shù)組,標(biāo)記以該頂點(diǎn)為中心點(diǎn)的節(jié)點(diǎn)在某一幀是否被細(xì)分。如果被細(xì)分則標(biāo)記為1,否則標(biāo)記0。

                2.從根節(jié)點(diǎn)開(kāi)始,以廣度優(yōu)先的順序遍歷四叉樹(shù),使用之前提出的條件式判斷節(jié)點(diǎn)是否需要分割。如果公式表明需要分割,并且與節(jié)點(diǎn)相鄰的四個(gè)節(jié)點(diǎn)的中心點(diǎn)都被標(biāo)記為1,那么把這個(gè)節(jié)點(diǎn)及其四個(gè)子節(jié)點(diǎn)的標(biāo)記設(shè)為1,并遞歸細(xì)分這個(gè)節(jié)點(diǎn)。否則,將這個(gè)節(jié)點(diǎn)的標(biāo)記設(shè)為1,把這個(gè)節(jié)點(diǎn)的四個(gè)子節(jié)點(diǎn)的標(biāo)記設(shè)為0,然后采用下面的方法繪制這個(gè)地形塊:

                    1)將節(jié)點(diǎn)的中心頂點(diǎn)和四個(gè)邊角點(diǎn)添加到即將繪制的三角扇形列表中。
                    2)依次檢查與四條邊相鄰的節(jié)點(diǎn)的標(biāo)記數(shù)組,如果相應(yīng)的標(biāo)記為1,那么將該點(diǎn)添加到三角扇形的頂點(diǎn)列表中,否則跳過(guò)該點(diǎn)。
                    3)繪制三角扇形。

                我們最終的偽代碼如下。

            bool IsNodeInFrustum(QuadTreeNode *node)

            {

               return (node->BoudingSphere in frustum);

            }

            bool NeighbourIsValid(QuadTreeNode *node)

            {

               return (all four neighbours of node are identified as 1)

            }

            void RenderTerrain()

            {

               list<QuadTreeNode *>next,current,draw;

               int level =0;
               current.push_back(root);
               while (current.size()!=0)

               {

                  for each thisNode in current

                  {
                     if (!IsNodeInFrustum(thisNode))
                        continue;
                     if (level == MaxResolution)
                        draw.push_back(thisNode);
                     else

                     if (thisNode->DeltaH/(distance*distance) > k

                         && NeighbourIsValid(thisNode) )

                     {

                         SetFlag(thisNode,1);

                         for j= 1 to 4

                         {

                            next.push_back(thisNode->Children[j]);

                            SetFlag(thisNode->Children[j],1)

                         }

                     }

                     else

                     {

                        SetFlag(thisNode,1); 

                        for j= 1 to 4

                         {

                            draw.push_back(thisNode->Children[j]);

                            SetFlag(thisNode->Children[j],0);

                         }

                     } 

                  }

                  SwapList(current,next);
                  next.clear();

                  level++;

               }

               GraphicsAPI->DrawPrimitives(draw);  

            }

                另外,一個(gè)重要的優(yōu)化是利用硬件的緩沖區(qū)或頂點(diǎn)數(shù)組(對(duì)于不支持頂點(diǎn)緩沖的硬件而言)。因?yàn)榈匦螣o(wú)論怎樣簡(jiǎn)化,頂點(diǎn)數(shù)據(jù)總是固定不變的。我們?cè)诿恳粠瑒?dòng)態(tài)產(chǎn)生的僅僅是頂點(diǎn)索引,因此我們有必要實(shí)現(xiàn)將地形的所有頂點(diǎn)數(shù)據(jù)輸入到頂點(diǎn)緩沖中,然后在渲染時(shí)一次性將所有的索引傳給顯卡,以提高速度。實(shí)驗(yàn)表明,使用頂點(diǎn)緩沖比直接使用glBegin/glEnd繪制圖形要快5倍以上。

                以上講述了如何做到實(shí)時(shí)地渲染大型地形。主要應(yīng)用了LOD和VFC兩種手段來(lái)精簡(jiǎn)三角形數(shù)量。然而VFC只能剔除不在視見(jiàn)體內(nèi)的圖形,而對(duì)于在視見(jiàn)體內(nèi)但被其他更近的物體遮擋的情況卻無(wú)能為力。如果要實(shí)現(xiàn)地形的自遮擋剔除,地平線算法是一個(gè)好的選擇。然而當(dāng)你的場(chǎng)景不僅僅是包含地形時(shí),地平線算法也只能處理地形的自遮擋情況。因?yàn)榈仄骄€算法只對(duì)2.5D的地圖(即在XZ平面上無(wú)重合投影的場(chǎng)景)有效。對(duì)于完全3D場(chǎng)景,地平線并不能很好的工作。所以當(dāng)你在引擎中使用地形時(shí),可以考慮將地形分塊后放入場(chǎng)景的管理樹(shù)中,如BSP或Octree等。然后根據(jù)引擎的性質(zhì)使用入口(Portal)、PVS或者遮擋測(cè)試(Occlusion Culling)等方法進(jìn)行遮擋剔除。值得強(qiáng)調(diào)的是,遮擋測(cè)試是一個(gè)非常靈活的實(shí)時(shí)的剔除算法,且無(wú)需任何預(yù)計(jì)算過(guò)程。但要想有效的實(shí)現(xiàn)它并不是一件容易的事。我曾將地形分塊后使用遮擋剔除來(lái)完成地形的自遮擋,但是渲染速度不但沒(méi)有提升,反而有輕微的下降。因此如果要使用遮擋剔除的話必須和引擎結(jié)合起來(lái)統(tǒng)一進(jìn)行遮擋測(cè)試,才有可能提高效率。

                現(xiàn)在你應(yīng)該了解了基本的地形實(shí)時(shí)渲染方法。要想讓地形的外觀更加真實(shí),我們還需要更多的工作。我們需要為地形加上紋理貼圖和光照。首先考慮地形的光照。由于地形的多邊形網(wǎng)格是實(shí)時(shí)產(chǎn)生的,它會(huì)隨著視點(diǎn)的移動(dòng)而變化,因此如果你直接使用OpenGL內(nèi)置的頂點(diǎn)光照,你會(huì)得到極度不穩(wěn)定的光照效果。你會(huì)看到地形表面會(huì)因?yàn)槟愕囊苿?dòng)而不斷跳動(dòng)。因此我們必須使用其他的光照方法來(lái)避免這個(gè)問(wèn)題。我們想到了光照貼圖。光照貼圖是一個(gè)游戲中常用的光照技術(shù)。它是一個(gè)覆蓋了場(chǎng)景中所有多邊形的貼圖。通過(guò)給貼圖賦值,我們可以得到多邊形表面復(fù)雜的光照效果。使用好的算法計(jì)算出來(lái)的光照貼圖可以模擬極度逼真的光影效果。它給我們帶來(lái)的視覺(jué)享受遠(yuǎn)遠(yuǎn)地超過(guò)了OpenGL的內(nèi)置光照。有關(guān)光照貼圖的計(jì)算可以參考我翻譯的一篇文章:輻射度算法(Radiosity)

               

               你可以簡(jiǎn)單地為地形覆蓋上單一的紋理,這看起來(lái)些許增加了地形的真實(shí)性:

                在上圖中,我們創(chuàng)建了一個(gè)地形,并運(yùn)用了一個(gè)重復(fù)的紋理。這個(gè)過(guò)程讓地形的無(wú)論哪一個(gè)區(qū)域看起來(lái)都是一樣的(例如都是草地)。這顯然不太真實(shí),也過(guò)于乏味。或許你會(huì)創(chuàng)建了一幅超大的圖片,以拉伸覆蓋的方式映射到地形表面。這樣做的后果是內(nèi)存開(kāi)銷過(guò)于龐大,這樣做也很會(huì)受到硬件的限制。因此我們應(yīng)該使用一種更好的紋理貼圖方式,紋理索引貼圖。

                紋理索引貼圖對(duì)三個(gè)可重復(fù)的紋理進(jìn)行索引貼圖。所謂索引貼圖,就是對(duì)三個(gè)可重復(fù)紋理進(jìn)行索引,以決定地形的哪些區(qū)域需要使用哪些紋理的混合來(lái)貼圖。因?yàn)閷?duì)于任意的貼圖,都由一組包含3個(gè)顏色通道(即R、G、B)的像素組成。用于索引的貼圖的像素并不表示地形的某個(gè)區(qū)域的具體顏色,而是表示地形的某個(gè)區(qū)域用何種具體的紋理貼圖。因?yàn)榫唧w的紋理細(xì)節(jié)存儲(chǔ)在這三個(gè)可重復(fù)的紋理中,因此索引貼圖的貼圖方式也為拉伸到地形表面,但它的分辨率可以大大降低。

                紋理索引貼圖的工作方式如下:對(duì)于地形投影到屏幕上的像素,查找該像素所映射到索引貼圖上的像素。然后根據(jù)這一像素R、G、B分量的不同,決定R、G、B分量所代表的具體紋理貼圖的混合因子。根據(jù)這個(gè)混合因子混合三個(gè)可重復(fù)貼圖后,將混合得到的最終顏色值輸出到屏幕上。

                例如,令索引貼圖的R分量代表沙灘的紋理,G分量代表草地,B分量代表巖石。如果索引貼圖上一個(gè)像素的值是(0,255,0),即綠色,則這個(gè)像素所對(duì)應(yīng)的地形區(qū)域的具體紋理就為草地。如果該像素顏色值是(127,127,0),即黃色,則該像素所對(duì)應(yīng)的地形區(qū)域的紋理為草地和沙灘的混合,看起來(lái)既有草,又有沙。又如下圖顯示了一個(gè)樣本索引貼圖,以及使用該貼圖索引紋理之后的渲染效果。

            索引貼圖(R=沙灘,G=草地,B=巖石)

            渲染效果

                原理很簡(jiǎn)單,下面講解一下具體的實(shí)現(xiàn)過(guò)程。首先,我們準(zhǔn)備4個(gè)紋理,其中1個(gè)紋理索引貼圖,它將被拉伸覆蓋整個(gè)地形,然后3張細(xì)節(jié)貼圖,并將它們綁定到相應(yīng)的紋理通道上。然后使用Vertex Shader為每個(gè)頂點(diǎn)自動(dòng)計(jì)算索引貼圖的紋理坐標(biāo),在Fragment Shader里,對(duì)索引貼圖進(jìn)行紋理查找,使用查找得到的顏色值的RGB顏色信息混合3張細(xì)節(jié)貼圖,得到當(dāng)前像素的顏色。最后還應(yīng)該把這個(gè)顏色和光照貼圖中的值相乘,得到最終的結(jié)果。下面是相關(guān)的Shader代碼,使用GLSL編寫。

            Vertex Shader:

            uniform float TexInc;   //紋理縮放值,用于查找索引紋理
            void main()
            {
              gl_TexCoord[6] = gl_Vertex;
              gl_TexCoord[0] = gl_MultiTexCoord0;
              gl_TexCoord[2] = TexInc*vec4(gl_Vertex.xz,0.0,0.0);
              gl_Position = ftransform();
            }

            Fragment Shader:

            uniform sampler2D IndexMap;
            uniform sampler2D LightMap;
            uniform sampler2D texR,texG,texB,texA;
            void main()
            {
              vec4 idx,lm,r,g,b,color;
              idx = texture2D(IndexMap,gl_TexCoord[0].xy); //索引值
              lm = texture2D(LightMap,gl_TexCoord[0].xy);  //光照度
              r = texture2D(texR,gl_TexCoord[2].xy);   //R通道紋理
              g = texture2D(texG,gl_TexCoord[2].xy);   //G通道紋理
              b = texture2D(texB,gl_TexCoord[2].xy);   //B通道紋理
              color = lm*(idx.x*r + idx.y*g+idx.z*b);  //混合顏色
              gl_FragColor = color;
            }

                最后,如果你對(duì)本文有不解之處,歡迎和我共同討論。

            posted on 2008-05-11 13:54 RedLight 閱讀(1344) 評(píng)論(1)  編輯 收藏 引用 所屬分類: 3D渲染技術(shù)

            評(píng)論

            # re: 基于四叉樹(shù)空間劃分的地形實(shí)時(shí)渲染方法[未登錄](méi) 2010-07-02 09:32 KK

            暈,轉(zhuǎn)載也不注明...  回復(fù)  更多評(píng)論   

            <2008年5月>
            27282930123
            45678910
            11121314151617
            18192021222324
            25262728293031
            1234567

            導(dǎo)航

            統(tǒng)計(jì)

            公告


            Name: Galen
            QQ: 88104725

            常用鏈接

            留言簿(3)

            隨筆分類

            隨筆檔案

            相冊(cè)

            My Friend

            搜索

            最新評(píng)論

            閱讀排行榜

            評(píng)論排行榜

            无遮挡粉嫩小泬久久久久久久| 久久久噜噜噜久久熟女AA片| 精品水蜜桃久久久久久久| 开心久久婷婷综合中文字幕| 少妇久久久久久久久久| 国产精品伊人久久伊人电影| 少妇熟女久久综合网色欲| 久久99中文字幕久久| 久久无码专区国产精品发布| 99久久99久久精品国产片| 精品国产青草久久久久福利| 99久久精品免费| 久久综合亚洲欧美成人| 日韩一区二区三区视频久久| 国产精品视频久久久| 久久亚洲精品成人无码网站| 品成人欧美大片久久国产欧美...| 亚洲AV无码久久| 午夜精品久久久久久久无码| 久久噜噜电影你懂的| 亚洲中文字幕无码一久久区 | 久久综合色老色| 国产激情久久久久影院小草| 国产精品久久影院| 欧美黑人激情性久久| 99精品久久久久久久婷婷| 日韩AV毛片精品久久久| 久久久久久久国产免费看| 日本久久久精品中文字幕| 久久er99热精品一区二区| 亚洲精品乱码久久久久久自慰| 久久99热这里只频精品6| 久久人人爽人人澡人人高潮AV| 色综合色天天久久婷婷基地| 久久国产高潮流白浆免费观看| 色狠狠久久AV五月综合| 久久人人爽人人爽人人爽| 国产精品久久久久蜜芽| 久久亚洲AV无码精品色午夜 | 亚洲人成无码网站久久99热国产| 久久99国产一区二区三区|