譯者:azure
原文地址:
http://www.azure.com.cn/article.asp?id=191譯者序:這是一篇講解3D引擎中,某些細節優化的文章,文中使用的方法同樣適用于其他場合。不過因為本人對這方面的技術和詞匯不是很熟悉,所以這篇翻譯可能存在很多謬誤,希望大家多加批評指正。
原文:Speed-up & Optimization Techniques
緒論
在這個頁面,我收集了一些不同的加速3D引擎的小竅門。我會先介紹一些顯而易見的,因為許多人會忽視它們,接著是一些更精彩的。如果你有其他的竅門和算法,盡可以告訴我。
Float --> int 轉化
值得嘗試的做法,因為這種轉化一般會更慢。我知道的這兩種方法是用#pragma和內聯函數。首先,#pragma:
#pragma aux RoundToInt= "fistp DWORD [eax]" parm nomemory [eax] [8087] modify exact [8087];
這種方法表現的非常好,WDISASM 表明編譯器拋棄了對__CHP的調用而內聯了這個轉化。另一種方法不使用#pragma,可以被移植到其他編譯器。這由"InnerSect"提出:
#define FIST_MAGIC ((((65536.0 * 65536.0 * 16)+(65536.0 * 0.5))* 65536.0))
int32 QuickFist(float inval)
{
double dtemp = FIST_MAGIC + inval;
return ((*(int32 *)&dtemp) - 0x80000000);
}
他做了一些測試,說來有趣,如果我沒記錯的話,他發現上面的方法更快。
提前計算
提前計算所有數據!你沒法提前計算太多東西。不要以為什么都能查表,那樣你會失去靈活性。關注不同的方法。首先要提前計算的是你的法向量。表面法向量和頂點法向量。對調試來說非常重要-這些向量的計算耗時驚人。一旦你提前計算了這些數值,你只要像對待對象的其他部分一樣旋轉他們就可以了。可是不要去平移或者縮放他們。它們必須保持為單位向量。如果你偶然縮放了他們,可以乘以一個相反的比例把他們還原;例如:
| Sx 0 0 0 |
Transform Matrix = | 0 Sy 0 0 |
| 0 0 Sz 0 |
| 0 0 0 0 |
然后你需要這樣:
NormalX *= 1 / Sx
NormalY *= 1 / Sy
NormalZ *= 1 / Sz
當你進行裁減和計算光照的時候,你也不需要平移他們。Also you don't need to transform your normals when you perform culling or lighting. 簡單的反向平移你考慮到的其他向量,避免矩陣/向量的乘法。你反向平移可以簡單的通過重置上方3×3的矩陣來實現:
For every row
For every column
output[row][column] = input[column][row]
End
End
轉換燈光,視角向量,以及任何依賴這個矩陣的向量,都因為提前計算而節省了大量工作。你只需要像往常一樣計算,用新的向量和舊的法向量。
倒數
如果你需要做很多次除數相同的除法,取除數的倒數,變成乘法。倒數就是1/n,n是你的除數。如果你確實很聰明,你會發現在運行 fdiv 的時候還有其他事可做。一個不錯的竅門是預熱高速緩存,高速緩存命中失敗會導致從內存中載入數據等占用很多時間。所以,當你坐等fdiv完成的時候,為什么不讀一些內存地址呢?那樣它們就會被載入高速緩存了。 在我的屏幕渲染的代碼里面,這樣看起來運行良好。你可以把這個技術用在透視紋理映射上。也有助于你做透視轉換。如果想還原那個倒數,再做一次1/n就可以了。非常精彩。如果你喜歡冒險,你可以把數據存儲為倒數,例如Z。我還沒試過-這會很難調試。
頂點標識
任意給定一個需要處理的頂點列表,你會發現有很多-也就是說至少一半-是不可見的,在裁減區域之外。你需要快速排除它們。一個簡單的方法就是給這些頂點加一個'可見'標志。
進入處理循環之前,在準備期間,把這個標志設為false。然后,進入循環之后,如果發現頂點是必要的,就設為true。例如,你發現一個三角形可見,就把它所有的頂點可見標志都設為true。在那之后,你可以簡單的跳過或者排除那些沒有設這個標志的頂點。這對復雜模型非常有用,因為他們包含大量頂點。同樣可以這樣處理有很多不可見部分的很大的場景,跳過不必要的渲染。如果用于光照,那你必須得小心。如果你進行可見性裁減的時候,沒有計算光照,你會發現在邊緣的地方出現錯誤。你在尋找已定義數據和未定義數據的交界,這很費神。。。
一種方法識用一個計數器來標識。方法很簡單。初始化的時候,你把計數器設置為某個非法值,例如-1 (0xFFFFFFFF).。同時,你把幀計數器設置為0。然后在你處理對象的過程中,如果你發現一個面/頂點需要處理,你就把它的計數器設置為和幀計數器相等。反之,不作任何設置和清除。接著,當你實際處理的是時候,你把它和幀計數器對比,那些計數器值和當前幀計數器相等的頂點/面片會被使用,其他的則被跳過。這在大的數據集很方便,因為那種情況下,每幀都正確清除那個標志代價高昂。
指針
指針非常方便。利用它們你能實現精巧的數據結構,就像鏈表,二叉樹等等。你也可以利用它們快速尋址。比如說你有一個數組,每個元素都是27字節長。你可以把它們填充到32字節,利用移位來計算地址。但是,數組的每個元素都浪費了5個字節。這很浪費。因此,用指針來尋址。就是說你的頂點結構有27字節。在三角形結構中,不要用int vertindex[3],而是用vertex *vertptr[3]。然后簡單的載入指針,尋址,就可以了。(譯者注:說實話,這段話我沒有完全理解,有不對的地方懇請讀者指正)
三角形vs 多邊形
三角形易于處理,渲染速度快。但是如果你有一系列6邊形構成的表面,你很容易把它們替換為多邊形,不過對三角形渲染器來說,你得做6倍的工作。但是三角形渲染更快。個人推薦三角形,在基于三角形的環境操作起來很簡單。多邊形有它的優勢,可能值得一試。如果有人擁有凸多邊形偏移紋理的算法,我會非常感興趣。
過度渲染/渲染不完全
過度渲染是速度殺手。尤其是復合渲染代碼。你繪制,再次繪制,二次重繪,三次重繪,等等。損失時間和速度。一個簡單的排除過度渲染的方法是從前到后排列你的三角形,給它們提供Z-Buffer/S-Buffer。這仍然導致問題。完全依賴 Z-Buffer的三角形渲染會最終變成浪費時間。掃描所有的點,而不渲染任何東西!同樣的復雜的分段插入S-Buffers代碼,也要付出一定代價。 對大的三角形 S-Buffer看起來運作良好。但是,對大量小三角形,它就沒那么吃香了;例如,我的S-buffer算法渲染一個不到3k三角形的頭部模型,只計算lambert陰影,耗費了接近30秒鐘。很明顯,三角形數量使得分段插入的代碼負載過大。我想,這里的解決方法是開發更高效的閉合和VSD算法。將來處理這個問題有很多可能性。
另一個問題是“渲染不完全”(我的術語)。這是說,你花費了太多時間在處理離屏的多邊形上。一些有幫助的因素是,場景中的可見多邊形不會明顯變化。到現在為止,你大概可以計算一個FPS中的可見多邊形集了,用玩家的方向尋找一些需要注意的多邊形,如果玩家靠的太近,就把它們變為臨界點。有效范圍例如包圍球/包圍盒在這里也能派上用場。Hierarchial模型也可能有幫助,用來決定哪部分模型是不必要的。
總結
這里有幾條規則,可以讓你的引擎跑得更快:
盡可能提前計算
不做不必要的計算
不要重復計算同一內容
如果可能,要利用以前計算的結果
尋找那些可以事半功倍的場合
在用匯編改寫你的函數前,自問“我已經找到最好的解決方法了嗎?”
試驗!
冒險。
不要 重寫任何代碼僅僅因為“看起來慢”
探索你的目標構架,了解它的特性