1. 程式人生 > >凹凸貼圖與法線貼圖的區別

凹凸貼圖與法線貼圖的區別

我翻譯這篇教程的目的是為了幫助那些對圖形渲染技術有興趣卻又苦於找不到免費中文學習資料的人。在我的身邊沒有任何一位從事計算機專業的前輩,從剛學會WINDOWS的基本操作到現在,我的計算機技術完全都是一步步自學過來的,算算學程式設計的歷史也近5年時間了。我往往要花一半以上的學習時間用來查學習資料(記得我學GIF影象壓縮解壓演算法時,用了近2個星期時間才編出了顯示GIF圖片的程式,主要原因就是資料不夠,只看一兩篇短篇幅的教程來寫程式,其中多數時間花在除錯程式碼和猜格式上)。所以我對那些不是從事計算機專業,身邊又沒有計算機高手的學習者深有同感——查一堆堆資料是自學中最痛苦的過程。這幾天我也正在學BUMP MAPPING演算法,在看完一篇簡單的E文教程後,我覺得自己也確實應該為中國的教程事業做出一點我微薄的貢獻。我並不是個3D高手,也只是個初學 OpenGL的新手,所以只能先做翻譯了。我也希望我的翻譯小樣能夠拋磚引玉,看到中國越來越多的高手能夠真正寫點自己的東西,拿出來與大家分享。由於本人水平有限,翻譯難免有錯誤和不妥之處,請多加指正。

為了尊重原作者,同時也是為了讀者能夠對照原文,在此貼出原文地址:

http://freespace.virgin.net/hugo.elias/graphics/x_polybm.htm 
 

凹凸對映Bump Mapping

  凹凸對映和紋理對映非常相似。然而,紋理對映是把顏色加到多邊形上,而凹凸對映是把粗糙資訊加到多邊形上。這在多邊形的視覺上會產生很吸引人的效果。我們只需要新增一點資訊到本來需要使用大量多邊形的物體上。需要注意的是這個物體是平的,但是它看起來卻是粗糙不平的。讓我們來看看左邊的那個立方體。如果你很近地觀察它時,你會發現它上面的很多細節。它看起來好像是由成千上萬個多邊形構成的,其實它只是由6個矩形構成。你或許會問:“這和紋理對映有什麼不同?”它們的不同之處在於——凹凸對映是一種負責光方向的紋理對映。

(1)凹凸對映背後的原理



  讓我們來看看一個粗糙的表面。
    

  從遠處看,你判斷這個物體是粗糙的的唯一證據是在它表面上下的亮度有改變。你的大腦能夠獲得這些亮暗不一的圖案資訊,然後判斷出它們是表面中有凹凸的部位。

  那麼你也許會問:我是怎麼知道哪些點要亮,哪些點要暗呢?這不難。絕大多數人生活在這樣一種環境下——這個環境的大多數光源來自上方(譯者注:比如白天主要的光來自太陽,夜晚主要的光來自天花板上的日光燈)。所以向上傾的地方就會更亮,而向下傾的地方就會更暗。所以這種現象使你的眼睛看到一個物體上亮暗區域時,可以判斷出它的凹凸情況。相對亮的塊被判斷是面向上的,相對暗的塊被判斷是面向下的。所以我只需要給物體上的線條簡單得上色。

  如果你想要更多的證據,這裡還有一幅幾乎相同的圖,不同於前的是它旋轉了180度。所以它是前一幅圖倒轉的影象。那些先前看起來是凹進去的區域,現在看起來是凸出來的了。
    



  這個時候你的大腦並沒有被完全欺騙,你腦中存留的視覺印象使你仍然有能力判斷出這是前一幅圖,只是它的光源變了,是從小往上照的你的大腦可能強迫性地判斷出它是第一幅圖。事實上,你只要始終盯著它,並且努力地想像著光是從右下方向照射的,你就會理解它是凹的(譯者注:因為日常生活的習慣,你會很容易把這些圖形判斷成凸出的圖形,但是因為有了上一幅對照圖的印象,你可能才會特別注意到這些圖塊其實還是凹入的,只是判斷方法不符合我們日常生活習慣,因為這時大多數光不是從上方照射,而是從下往上照射)。

(2)什麼是凹凸圖(Bump Map)

  凹凸圖和紋理圖很相似。但是不同的是,凹凸圖包含的不是顏色資訊,而是凹凸資訊。最通常的方法是通過儲存高度值實現。我們要用到一個灰色的紋理圖,灰色的亮度體現出每個點分別凸出多少(見右圖)。這就是一個非常方便的儲存凹凸圖的方法,而且這種圖很容易製作。這副圖具體又是怎樣被渲染器使用的呢?你接著往下看就會明白了。

  當然,你並不一定要把自己侷限於這些簡單的圖形,你可以擴充套件,用它來做木材,做石頭,做脫了漆的牆面,做任何你想做的物體。
    

(3)那麼它是怎麼工作的

  凹凸對映是補色渲染技術(Phong Shading Technique)的一項擴充套件,只是在補色渲染裡,多邊形表面上的法線將被改變,這個向量用來計算該點的亮度。當你加入了凹凸對映,法線向量會略微地改變,怎麼改變則基於凹凸圖。改變法線向量就會改變多邊形的點的顏色值。就這麼簡單。

  現在,有幾種方法來達到這個目的(譯者注:這個目的指改變法線向量)。我並沒有實際編寫補色渲染和凹凸對映的程式,但是我在這裡將介紹一種我喜歡的方法來實現!

  現在我們需要將凹凸圖中的高度資訊轉換成補色渲染用到的法線的調節資訊。這個做起來不難,但是解釋起來比較費勁。

    
  好的,我們現在將凹凸點陣圖的資訊轉換成一些小向量——一個向量對應於一個點。請看左邊一副放大的凹凸圖。相對亮的點比相對暗的點更為凸出。看清楚了嗎?現在計算每個點的向量,這些向量表徵了每個點的傾斜情況,請看下圖的描繪。圖中紅色小圓點表示向量是向下的:
    

  有很多計算向量的方法,不同的方法精確度不同,但是選擇什麼方法要取決於你所要求的精確度是個什麼層次。最通常的方法是分別計算每個點上X和Y的傾斜度:

  x_gradient = pixel(x-1, y) - pixel(x+1, y)
  y_gradient = pixel(x, y-1) - pixel(x, y+1)

在得出了這兩個傾斜度後,你就可以計算多邊形點的法線了。

    

  這裡有一個多邊形,圖上繪出了它的一條法線向量——n。除此,還有兩條向量,它們將用來調節該點法線向量。這兩條向量必須與當前被渲染的多邊形的凹凸圖對齊,換句話說,它們要與凹凸圖使用同一種座標軸。下邊的圖分別是凹凸圖和多邊形,兩副圖都顯示了U、V兩條向量(譯者注:也就是平面2D座標的兩條軸):

    

  現在你可以看到被調節後的新法線向量了。這個調節公式很簡單:

  New_Normal = Normal + (U * x_gradient) + (V * y_gradient)

有了新法線向量後,你就可以通過補色渲染技術計算出多邊形每個點的亮度了



2.2
 法線貼圖法(Normal Map)

但事實上游戲程式設計員卻通常並不喜歡使用BMEM技術,因為他執行速度慢,因此他們通常使用DP3技術:直接把高度圖(Height map)轉換成一張法線圖(Normal Map),其圖的RGB分別是原高度圖該點的法線指向:Nx、Ny、Nz,這張圖可由Direct3D的專門函式幫助我們計算。最後在渲染的時候直接將該 高度圖的每個畫素與光源的向量點乘,即可得到表示每一點的明暗係數的圖:根據高度圖,越突出的地方,法線與光源夾角越小,該點的數值越大。接著將這張圖乘 到渲染線中即可,這樣就使模型在背光的凹處有陰影而在面向光源處更亮的效果,這樣的3D模型看起來就像真的凹凸不平一樣!這些都可以直接在渲染流水線中由 機器完成。

具體可以使用以下簡單的語句來實現:

  1. //將光源位置轉 換成ARGB
  2. DWORD Vector2ARGB(D3DXVECTOR3 *v,float height)
  3. {
  4. DWORD r=(DWORD)(127.0f*v->x+128.0f);
  5. DWORD g=(DWORD)(127.0f*v->y+128.0f);
  6. DWORD b=(DWORD)(127.0f*v->z+128.0f);
  7. DWORD a=(DWORD)(255.0f*height);
  8. return((a<<24L)+(r<<16L)+(g<<8L)+b);
  9. }
  10. //生成法線圖
  11. D3DXComputeNormalMap(pNormalMap,pHeightMap,NULL,0,D3D_CHANNEL_RED,1.0f);//pHeightMap 為原高度圖的指標,pNormalMap為一張空紋理,用於存放法線圖
  12. //在渲染程式段中可以這樣寫:
  13. //light是單位化的光源向量
  14. DWORD F=Vector2ARGB(&light,0.0f);
  15. //pD是D3D的裝置指標,這句將光源法線引數輸入
  16. pD->SetRenderState(D3DRS_TEXTUREFACTOR,F);
  17. //設定原紋理,如上面的球,如有需要可以貼上紋理樣式
  18. pD->SetTexture(1,TEXTURE);
  19. //使用上面生成好的法線圖
  20. pD->SetTexture(0,normalmap);
  21. //設定“來源1”為法線圖
  22. pD->SetTextureStageState(0,D3DTSS_COLORARG1,D3DTA_TEXTURE);
  23. //將“來源1”(法線圖)與“來源2”(光源法線)進行點乘
  24. pD->SetTextureStageState(0,D3DTSS_COLOROP,D3DTOP_DOTPRODUCT3);
  25. //設定“來源2”為光線的光源法線引數
  26. pD->SetTextureStageState(0,D3DTSS_COLORARG2,D3DTA_TFACTOR);
  27. //這步和下面幾步將圖片的原紋理加上
  28. pD->SetTextureStageState(1,D3DTSS_COLORARG1,D3DTA_TEXTURE);
  29. pD->SetTextureStageState(1,D3DTSS_COLOROP,D3DTOP_MODULATE);
  30. pD->SetTextureStageState(1,D3DTSS_COLORARG2,D3DTA_CURRENT);
複製程式碼



2.3  假凹凸貼圖

三維計算機圖形程式設計師有時使用計算量較小的假凹凸貼圖模擬凹凸貼圖效果。其中一個方法是使用紋素索引變化取代曲面法線變化,這種方法經常用於二維凹凸貼圖。在 GeForce 2 型別的圖形加速硬體中就使用了這項技術。(或者說利用小尺寸的灰度圖來為一張更大尺寸的表面起伏不大的平面提供高度圖的索引)

全屏的二維假凹凸貼 圖,可以很容易地用簡單快速的渲染迴圈實現,在二十世紀九十年代的示範影像是一個非常普通的 視覺效果。

3 與位移對映之間的差別

位移對映與凹凸貼圖之 間區別在例 圖中已經很明顯地顯現出來了:在凹凸貼圖中,只有法線進行了擾動,而幾何體本身沒有擾動,這樣的結果就是人為改變只出現在物體的輪廓上,而球體本身仍然是原來的圓 形。即凹凸貼圖只 是視覺上的改變,就像一個畫得很透視的圖片;而位移對映卻真的將3D物體變得“凹凸不平”。


from : http://www.cppblog.com/lovedday/archive/2008/05/21/50652.html

Bump Map儲存的是高度差,而非Normal Map直接儲存法線值。這一點值得讓人思考。即,Bump儲存一張8bit灰度圖,而Normal儲存一張24bit RGB彩色圖!

記得第一次見到這個技術好像還是98年前後的時候,好像是MGA系列的顯示卡用這個東西作為自己賣點(那會鄙人還上中學,年代久遠實在記不清了)。至於為什 麼不直接儲存法線,我覺得是為了資料儲存的最小化考慮。因為高度差可以很容易的用定點表示,並用定點紀錄當前高度圖所在的刻度範圍,理論上這種方式表示的 高度圖不會有太大的精度問題。反向,法線則不行。而且高度差影象可以很容易的做到壓縮,對簡單的壓縮就是一個高度值,後面跟著幅影象在當前高度的掩碼圖。

因為當時DRAM還是不便宜的,所以高壓縮比例的紋理儲存是首要被滿足的目標。當然,我也是按照常理推測,也沒有經歷過那個年代。如果有網友知道更高深的 原因,還望指教 :〉