Dynamic 2D Imposters: A Simple, Efficient DirectX 9 Implementation

Introduction

使用 2 Dimposters 的技術正在世界上電腦遊戲變成越來越流行。目標是在沒有降低細節的情況下采用將部分場景通過緩存的方式保存到顯存裏。場景中的三維物體被採集到一些分散的圖片中。然後場景中的真實三維物體在渲染的時候被緩存的Impostor替代。通過這種方式Impostor就成爲了三維物體簡化方式。因爲現代的遊戲變比較大而且要求遠比以往要多的細節,開發者正在逐漸地找尋新、創新的水平LOD算法。Imposters的理念正在得到認同,而且遊戲行業很受重視、很多的好文章都有簡單的提及,然而我還沒有見到關於Impostor系統實現的文章。這篇文章將會講到一些基本理論,但是大部分關心的內容正如標題所述:一個簡單高效的動態生成和渲染Impostor系統。

Impostor可以用在很多情況下,對於遠處擁有大量靜態物體作爲背景的複雜內容豐富的遊戲非常適合。在這些遊戲了用戶注意力大多集中在前景,不怎麼會留意背景中的Impostor。遊戲在處理和渲染集合體花的時間越少留給邏輯和AI的時間久就更充分。

Impostor的生成方式分爲兩大類:靜態或者離線的Impostor通常是通過美工離線生成的,靜態生成的Impostor已經在遊戲裏作爲Sprites,Particle或者Billboard用了很多年了,靜態的例子就是經常看到的根據角度生成Impostor構建的Billboard Tree。本文討論的是動態生成Impostor,它在概念上和靜態Impostor很類似,而且仍然是通過Billboard實現的,不同的是它是在運行時將三維物體的圖像繪製到貼圖上。



本文目標在於爲開發者提供一個實現Impostor系統的起點。本文提供的代碼都很簡單,可以適應不同的環境和需求。開發一個Impostor系統本身是一件很複雜的事情,事實上是一件R&D的任務,需要通過實驗來解決隨之而來的數學,視覺和性能問題。閱讀本文我假設你是有經驗的C/ C + +編程語言,並對3D圖形概念有一定的理解。你至少應該熟悉的DirectX9 API,你最好手上有SDK幫助。

理論

Impostor將三維物體渲染的圖像使用在Billboard上作爲對三維物體的監護。使用Impostor目的是爲了減少渲染三維場景所需的時間。它的工作原理是緩存三維物體的圖像然後將這些圖像替換真實的三維物體。使用Impostor降低了每幀的工作量,從而在繪製的時候花更少的時間。在DirectX中,這相當於在多邊形和紋理上傳的,DP,渲染狀態更改降低,更爲重要的是,更少的CPU處理時間。



Impostor的生成包括下面這些不住

  • 確定Impostor Billboard的頂點
  • 計算Render-To-Texture需要的View和Projection矩陣
  • 初始化相應的渲染狀態將Impostor繪製到貼圖裏

Impostor Billboard的頂點源於三維物體的包圍盒,Billboard在屏幕空間完全包含三維物體。Billboard和當前攝像機的位置被用來計算所需的視圖和投影矩陣。在生成一個Impostor的最後階段是初始化渲染狀態和渲染3D物體到紋理。Impostor的貼圖必須包含一個alpha通道。Alpha代表在紋理冒名頂替者是:不透明的(alpha =1.0),透明的(alpha =0.0)

 

 


當Impostor被緩存,它將會用於繪製的多個幀。Impostor在最終的3D場景中通過使用alpha test或alpha blend的方式進行繪製,並放置在所替代的3D對象的位置。Impostor帶來的性能提升是在儘可能多的幀儘可能地重用緩存的Impostor。因此Impostor應該只當和3D場景產生足夠的錯覺纔有必要進行再生。有一些特定的條件,使Impostor視差變得明顯。有全局條件,如Camera的可視角度和光源的顏色和方向。有局部條件,例如動畫,位置和三維目標的方位。當這些條件變得如此劇烈,Impostor明顯變得很二維,他們必須進行再生。Impostor和3D之間的外觀差異是我稱之爲“Impostor Error。”

Visual QualityConcerns

Impostor要有實用價值如果只是比三維物體渲染更高效是不夠的還需要看起來很不錯最好是可以以假亂真。通過實踐和實驗大部分視覺問題都是可以解決的。



最大的視覺問題包括:

  • 實用alpha
  • 三維物體切換到Impostor時的視覺突變
  • 渲染的貼圖分辨率不夠會是Impostor看起來像素感很嚴重
  • 環境或者視窗變化太大導致Imposter error國語明顯

常見的DirectX的alpha混合模式“源α-逆源阿爾法”不能在將要作爲Impostor的模型上使用。這很容易使用的低細節模型非alpha版本來解決。需要注意的是alpha test可以用於Impostor實現半透明。遊戲編程精粹2討論的預乘Alpha也可以使用。

具有良好的使用時間爲基礎的alpha混合時視覺突變幾乎無法察覺。可以在不同視角之間的Impostor進行漸變,這樣幾乎察覺不到Impostor正在更新,但是未在本文中實現的,因爲它需要一個更大的渲染紋理開銷。應該指出的是,當使用alpha blend時,Impostor必須深度排序,並按照從遠到近的順序繪製才能保證半透明正常工作。如果不需要的alpha漸變,可以使用alpha test來代替,這時就不需要進行深度排序。有很多可用的高性能的排序算法。

選擇合適的紋理分辨率是很重要的。紋理分辨率過低將意味着Impostor看起來是像素化。此外,如果相機太接近Impostor或Impostor過大則一個低分辨率的紋理變得非常明顯。在這篇文章中我曾憑經驗選擇的紋理分辨率的64由64對每個Impostor。用硬編碼的分辨率的問題是,它對不同大小的物體或物體的距離不一樣工作的不是很好。舉例來說,更大的和更近的Impostor替者將需要更高的分辨率。爲了簡單起見,本文中,我將一直採用硬編碼分辨率,深入討論部分有關於運行時動態選擇貼圖分辨率的相關擴展。

當Impostor error變得過於極端,Impostor需要在用戶覺察之前重新生成。造成Impostor error的情況或多或少取決於遊戲的類型,但通常它們都是基於攝像頭的可視角度的變化,對象動畫,移動和旋轉,以及光線變化。

在這篇文章中的條件通過以下方式處理:

•更改攝像頭的可視角度和物體的位置。與閾值角度相機向量之間的角度的簡單比較。如果角度大於所述閾值時,Impostor重新生成。

•照明和對象動畫的變化。每一個Impostor記錄上次重新生成的時間。 ”一旦這個時間已經超過x秒再重新生成。

請注意,改變物體朝向未在示例代碼測試。然而,這是一個簡單的條件來支持。相機向量之間的角度的測試也可以在模型空間而不是世界空間中進行,這樣它考慮物體的朝向。

應當注意的一點是使用的硬件灰霧可以幫助隱藏具有冒名頂替者相關聯的視覺問題。霧是你的朋友,只記得你只需要爲Impostor考慮霧,而不是3D對象,否則,你可能會得到雙倍的霧!


Runtime EfficiencyConcerns

是不是Impostor目的是使渲染更加高效?答案是肯定的,但是,如果一個使用一個原始的Impostor系統你可能會發現,它並沒有完全解決您的效率問題。

原始的實現是使用一個紋理渲染一個Impostor的簡單的方法。這個方法只能在實現Impostor原型的時候使用,因爲它會對性能產生不利影響。原始的實現需要一個渲染目標切換,所產生的每一個Impostor都需要DirectX再繪製一次來替代三維物體。改變渲染目標是一個昂貴的操作。執行一些繪製一個昂貴的操作。爲了最多數量的Impostor,多個Impostor紋理需要被打包到每個渲染紋理。越大渲染紋理和Impostor文理的越小越好,因爲我們可以在同一個渲染紋理保存更多Impostor。以這種方式打包Impostor紋理意味着只有一個渲染目標的變化並且繪製多個Impostor的時候只需要一個DP.。因此,越多的Impostor被擠壓成一個渲染紋理,會獲得更好的效率。

使用貼圖打包進行Impostor再生雖然很搞笑,但仍是該系統中最昂貴的部分。因此,我們希望Impostor重生儘可能少。重要的是要調整的閾值,這樣,當用戶明顯覺察到Impostor error時才需要對Impostor進行再生。

由於Impostor定期再生,動態頂點緩衝區是用來保存Impostor頂點。動態頂點緩存是生成時加了一個動態標記來告訴DirectX應該每幀更新頂點數據。

最後,應該提及的是,該Impostor渲染紋理不應該被鎖定。鎖定一個渲染紋理可能會導致圖形系統flush命令緩存並且同步CPU和GPU,這極大的影響了性能。

Step-by-stepImplementation

下面的章節詳細描述了高效Impostor生成和渲染代碼。所包含的代碼清單是從隨附本文的示例應用程序。示例代碼已經編譯了DirectX2005年10月9日的SDK,它使用了大量的DirectX的擴展庫(D3DX)的。

代碼清單按照一步一步的方式羅列了所有相關代碼,按順序介紹了Impostor中最重要的相關技術。第一個步驟涉及的渲染紋理的使用。然後我們看一下構建Impostor Billboard和所需的渲染到紋理矩陣。接下來,以原始的低效Impostor系統作爲演示。接着,對使用打包貼圖的高效的方法進行了討論。最後,還有一個部分,演示瞭如何確定何時冒名頂替需要再生。

1. Render TextureAllocation

Impostor系統的第一件事情是一個渲染紋理。清單1中的代碼通過D3DXCreateTexture和D3DXCreateRenderToSurface創建渲染紋理。 DirectX的代碼封裝在函數RenderTexture:: Initwhich被稱爲初始化渲染紋理。對於運行效率,當遊戲啓動時,這個功能纔會被調用。Init的參數用來指定紋理的分辨率。紋理採用D3DFMT_A8R8G8B8的格式,其中有8位紅,綠,藍,alpha通道。 Alpha通道是必需的,因爲它是定義Impostor的紋理區域的alpha mask。要創建一個渲染紋理,而不是一個正常的貼圖,D3DUSAGE_RENDERTARGET被指定爲Usage參數。對於Pool參數,D3DPOOL_DEFAULT的將渲染紋理放進顯存,這樣效率是最高的。

2. Render toTexture

渲染到紋理和渲染普通場景很相似。當渲染DirectX的場景中,渲染調用的IDirect3DDevice9功能需要在BeginScene和EndScene之間被執行。 BeginScene首先被調用。渲染然後進行使用DirectX API函數。當渲染完成EndScene被調用。當渲染到紋理, ID3DXRenderToSurface的BeginScene和EndScene被調用。在清單2中,BeginScene和EndScene功能在RenderTexture類的封裝函數中被調用。

現在我們已經覆蓋渲染紋理使用的基礎知識和創建RenderTexture類來簡化渲染紋理的使用。清單3展示了使用的渲染紋理類的一個簡單的例子。

3. GeneratingImposter Billboards

Impostor的第一階段是產生Impostor的幾何體Billboard。所需要的Billboard需要在屏幕空間中完全覆蓋當前視點的三維對象。



Billboard的幾何形狀是從三維對象的包圍盒得到的。首先,包圍盒被投影到屏幕空間。然後,在屏幕空間中生成一個包圍3D包圍盒的矩形框。這個矩形框是Impostor的幾何體在屏幕空間的投影。在確定所述2D邊界框時,對每個點的深度值進行比較,以確定所有點的最小深度值。最後,屏幕空間中的點和最小深度值反投影到世界空間和用作Impostor廣告牌幾何體。

清單4給出了ImposterVertex和Impostor結構。它們保存一個二維imposter需要的數據.ImposterVertex表示了Impostor Billboard的每個頂點。Impostor頂點具有位置,顏色和紋理座標。該顏色用於從3D對象到Impostor過渡時淡入淡出。Impostor的結構代表一個單獨的Impostor Billboard。除了其他有用的數據,它包含構成Billboard的頂點。

清單5包含CreateBillboard,它封裝了計算Impostor billboard的功能.D3DXVec3ProjectArray被調用來執行投影到屏幕空間的功能。隨後D3DXVec3UnprojectArray被稱爲從屏幕空間反投影到世界空間。還計算Billboard中心和距包圍盒的最近和最遠點的距離。這個數據被用於在接下來的部分,用於計算渲染到紋理所需的矩陣。從Billboard中心計算是方向矢量相機的位置。Impostor中心和攝像機方向是用來幫助確定何時Impostor需要再生。

4. Generation Viewand Projection Matrices

渲染到紋理需要視圖和投影矩陣。這些矩是通過清單6中的CreateMatrices計算得到的。使用D3DXMatrixLookAtLH andD3DXMatrixPerspectiveLH生成矩陣。視圖矩陣的計算與當前相機位置和相機重新定位,直接看Impostor Billboard的中心。投影矩陣是使用Billboard作爲投影平面,並用清單5中的計算出來的近和遠平面一起計算的。

5. Rendering aMesh to the Imposter Texture

有了Impostor Billboard和正確的矩陣,我們現在可以看一看如何渲染Impostor到紋理中。本節介紹了Impostor生成的原始方法。這裏介紹的方法對於Impostor原型系統已經足夠了,但它是低效的,因爲它爲每個Impostor都切換了渲染目標。

清單7給出瞭如何將一個模型渲染到Impostor貼圖裏。 開始渲染到渲染紋理時BeginScene被調用,然後將Impostor渲染到紋理中,接着EndScene被調用。要注意,這是低效的,因爲對每個Impostor都切換了一次渲染目標。當渲染Impostor到紋理時,首先通過調用CreateImposterBillboard構建Billboar,下一步CreateMatrices被調用來計算視圖​​和投影矩陣。這些矩陣通過調用一次SetTransform傳給DirectX 。在渲染之前,渲染狀態被初始化和背景被清除。要注意的是霧被禁用,因爲它是是Impostor的Billboard要被霧化而不是渲染到紋理的模型 。

6. Rendering anImposter Billboard

繼續原始的Impostor實現方法,清單8中給出呈現一個Billboard的代碼。 調用SetTexture綁定渲染紋理調用DrawPrimitiveUP渲染廣告牌。

在調用DrawPrimitveUP前渲染狀態被初始化。應注意的是,霧在此處啓用。被渲染到紋理的模型不計算霧,所以霧需要在繪製Impostor Billboard的時候被啓用。還要注意使用alpha測試,使渲染的紋理,其中存在Impostor(其中的α等於1.0)的部分和將被混合到場景中。 alpha混合用來作爲三維物體和二維Impostor的過渡。

同樣,這種方法是非常低效的,但對於演示和原型非常有用。這是低效的,不僅由於調用DrawPrimitiveUP,最大的開銷是有需每個Impostor需要一個DP。

7. Using TexturePacking for Efficient Imposter Generation and Rendering

到目前爲止,我們已經開發了一些有用的功能,生成和渲染Impostor。在前面兩節,展示了一個簡單和原始的技術。每個Impostor使用一個紋理是非常昂貴的。當每幀生成和渲染多個Impostor,性能成本迅速增加。本節介紹的高效實現將多個Impostor渲染到一個紋理中。紋理打包有降低渲染目標切換和減少DP的雙重效果。

渲染紋理被劃分成多個區域每一塊作爲Impostor紋理。當生成多個Impostor的時候只需要一次渲染目標切換。當渲染Impostor Billboard的時候,我們都可以將所有的Billboard頂點複製到一個動態的DirectX頂點緩衝。包含在相同的紋理的Impostor Billboard可以通過一個DP完成繪製。

第一步是分割渲染紋理並每個Impostor產生紋理座標的。紋理座標將Impostor映射到它佔據的區域。清單9給出了AllocImposters功能。函數的參數指定紋理U和V軸的Impostor數目。紋理被切分並且爲每個Impostor分配紋理座標。注意,Impostor對象是一個預分配數組。

清單9中生成的紋理座標將被同時使用生成和渲染Impostor。當生Impostor時,我們需要調用的IDirect3DDevice9功能SetViewport設置渲染紋理的渲染區域。清單10中的函數InitImposterViewport演示瞭如何在紋理座標用於設置視口。調用SetViewport後渲染結果纔會被輸出到渲染紋理的指定區域。

有了渲染到紋理指定區域的能力,我們現在能將多個Imposotr渲染到一個單一的紋理。要做到這一點,我們把清單7中給出的GenerateImposter功能進行調整來處理多個Impostor。清單11中給出的函數GenerateImposters是修改後的版本。需要注意的重要一點是,在執行一個生成多個Impostor循環之後,只有一個DP。注意,繪製3D物體到紋理志強InitImposterViewport被調用來設置正確區域。

隨着多個Impostor打包成一個單一的紋理,我們現在來看看渲染Impostor Billboard更有效的方法。首先,動態的DirectX頂點緩存被創建。動態頂點緩衝區是Impostor渲染正確的選擇。因爲我們打算按照深度對Impostor進行排序,他們有可能在每一幀的渲染順序是不一樣的。因此,我們需要每幀複製頂點到一個動態頂點緩衝區中的。值得注意的是,如果你不使用深度排序,你可以嘗試使用一個靜態頂點緩衝的Impostor定期再生,這只是更新,但是所需要的緩存代碼是超出了本文的範圍。創建動態頂點緩存示例代碼呈現在Listing 12 。

清單13給出了RenderImposterBillboards函數。這個函數渲染紋理中打包的所有Impostor。爲了alpha混合能正確工資,Impostor會先排序,使它們從後到前繪製。將排序後的Billboard頂點,應用動態頂點緩衝。最後,渲染狀態被初始化調用DrawPrimitive一個渲染Billboard。

8. Testing forImposter Regeneration

最後,我們需要確定什麼時候Impostor需要再生。每一幀重新生成所有Impostor是不切實際的。這將比渲染的3D對象更加低效。緩存的Impostor應該被儘可能多的幀重複使用,儘量不重新生成,直到用戶覺得Impostor error變得明顯。清單14提供了兩個測試,確定何時Impostor需要再生。第一個測試檢查從上一次再生到現在的時間。如果這個時間大於所述閾值,則該Impostor需要再生。第二個測試檢查當前的相機矢量和當前Impostor最後生成時相機矢量之間的角度。如果這個角度大於所述閾值角度,然後Impostor需要再生。在任一情況下, requiresRegenerate被設置爲真。


Taking ImpostersFurther

有很多方法可以提高這裏展示的Impostor的系統imposter system presented here can beimproved.

Viewing AngleTests

該代碼示例不考慮改變3D對象的方向。如果考慮這一點,視角測試應在模型空間中計算,而不是世界空間。然後測試將考慮到不僅在相機視場角和模型的位置,而且該模型的方位。一個更好的測試是從相機位置向量之間的角度比較近,遠點在對象的邊界框。這個測試是比較昂貴,但佔不僅攝像機視角,而且大小,位置,方向和距離的物體。這個測試是記錄在實時渲染。

CalculatingTexture Resolution at Runtime

對於示例代碼中,我選擇了硬編碼Impostor的紋理分辨率到應用程序中。一個更通用的Impostor系統可能要在運行時根據3D對象和屏幕分辨率的大小和距離計算的紋理分辨率。更小,更遠處的物體將使用一個較小的紋理分辨率,更大和更密切的對象都將使用更大的紋理分辨率。實時渲染提出了用於計算紋理分辨率有用的公式。

支持任意Impostor紋理分辨率將有助於使Impostor有更好的視覺質量,但爲任意的紋理分辨率的編碼是複雜的,代碼難以優化。我們不想讓事情更難,所以我提出一個簡單的方法。選擇多個離散的紋理分辨率,例如32×32 , 64×64和128×128 。每個離散解決方案創建一個渲染紋理。在運行時,計算所需的任意分辨率,然後映射到這一點的預先定義的離散解決方案之一。

Load BalancingImposter Regeneration

當很多甚至所有Impostor,需要在一幀裏重新生成時會發生什麼?答案很簡單,最簡單的答案是使用負載均衡,來保證一幀頂多只有X個Impostor被重建。 X的值可以根據經驗或通過遊戲的其餘部分的量來確定。正是由於這個原因,在示例代碼中,它測試的再生和執行再生代碼的代碼解耦。通過分離這些,它是可以測試每一幀生成所有Impostor的,以確定他們是否需要再生,那麼,那些需要再生的,只有過程X個被處理。

這種方式負載平衡引入了一個問題。這是可能有被標記爲需要再生,但仍在等待重新生成。在某些時候,正在等待Impostor再生可能會導致Impostor error變相當明顯的,並可能干擾用戶沉浸在場景中。這些影響通過上次再生時相機間的距離和時間排序會有所減少。這使得最接近和最古老的Impostor得到首先再生。這確實讓問題不太明顯,但仍然有機會,一個用戶可能會看到明顯的Impostor error。對於Impostor error已經很明顯的,唯一的選擇就是完全恢復到3D對象。這可以通過視角測試來實現。當視角變得大於所述閾值的冒名頂替α-變淡回3D對象。

發佈了27 篇原創文章 · 獲贊 5 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章