對於法線貼圖(Normal Map) 的深入研究

前幾篇文章寫過有關法線貼圖的內容,這次文章將討論其原理及相關優化。回過頭來看一下原來的文章真有種想刪掉的感覺。。。

爲什麼叫法線貼圖,我們知道法線(Normal)是垂直於一個面的直線,通過計算光線與這條法線的角度就可以知道與面的角度,進而可以計算出面應得到的顏色值。如果我們知道物體每個面的法線就能實現對這個物體進行光照渲染。但是一堵牆也許只有四個頂點,也就是隻有一個面,它最終的渲染效果將會非常單一,假設這堵牆上有更多的磚的凹凸痕跡,我們增樣實現僅用四個頂點渲染出立體感很強,細節層次感很強的這堵牆呢,答案就是 法線貼圖(Normal Map)。
 在法線貼圖技術中,我們就是通過把牆面的每個像素的法線存儲在一張紋理中,渲染的時候根據每個像素的法線確定他們的陰暗程度,而這張法線貼圖是可以用photoshop軟件從一張牆的紋理生成對應的法線貼圖的。到此,熟悉法線貼圖的朋友會對以上內容很熟悉的。
  試想在渲染過程中,如果把每個法向量都轉換到世界空間中跟光向量計算角度的話,那麼這麼多的像素法向量肯定影響性能,但是如果把光向量轉換到法向量所在的空間中,豈不快哉?因此我們這裏提到一個正切空間(Tangent Space)。正切空間就是法向量所在的空間,在這個座標系中,法向量作爲高度軸,類似Z軸。再找到其他的兩個軸問題就會變得簡單,但恰好這裏是比較麻煩的。
  其實我們已經知道 高度軸了,再找到一個軸,第三個軸就可以通過已知的兩個軸做叉乘得到。我們要找的那個軸就叫做 正切向量(Tangent Vector)。正切向量是需要平行於法向量的平面的。
  明白了這些問題就來實際的操作:
  在正切空間中,三個基向量分別叫做T、B、N,T代表Tangent,B代表Binormal,N代表Normal。在Texture Space(紋理空間中),兩個二維基向量分別叫做U、V,T就可以映射到紋理空間中的U,B就可以映射到紋理空間中的V。下面我們來推導一下三個向量的計算公式:
  假設一個世界空間中一點pi,其紋理座標爲ui,vi,則:
   pi = ui.T + vi.B
  一個面有三個點p1,p2,p3,則:
   p1 = u1.T + v1.B
   p2 = u2.T + v2.B
   p3 = u3.T + v3.B

  等價形式:
   p2 - p1 = (u2 - u1).T + (v2 - v1).B
   p3 - p1 = (u3 - u1).T + (v3 - v1).B

  最終的變換形式:
          (v3 - v1).(p2 - p1) - (v2 - v1).(p3 - p1)
   T = ---------------------------------------
            (u2 - u1).(v3 - v1) - (v2 - v1).(u3 - u1)

            (u3 - u1).(p2 - p1) - (u2 - u1).(p3 - p1)
   B = ---------------------------------------
            (v2 - v1).(u3 - u1) - (u2 - u1).(v3 - v1)

  而N軸可以由兩軸叉乘得到:
                        N = cross(T, B)
  寫成TBN矩陣的形式:
                        |Tx Bx Nx|
                        |Ty By Ny|
                        |Tz Bz Nz|

  正切空間到世界空間轉換:
   VWorld = TBN·VTangent = VTangentT·TBNT
  世界空間到正切空間轉換:
   VTangent = TBN-1·VWorld = VWorldT·TBN-T

  而剛纔說過N我們是可以存儲到紋理中,是已知的。所以只需要找到每個面的三個點來計算T,要知道這種計算式相當耗時間的,所以對於T或者B的計算,我們依然可以選擇預先處理的辦法,即存儲在模型中,而大部分建模軟件中的導出插件都具備這樣的功能,即導出信息中包含Tangent分量。如果非要自己計算也是可以的,但是我們會遇到一個問題:
  每個面即每個三角形有三個點,通過這三個點計算每個點的T向量,但是如果一個點被兩個以上的面所共用,我們應該選擇哪個面作爲計算的依據?答案是,先分別計算每個面中該點的T向量,然後得出平均值,更嚴格的來講應該加權求均值,這裏的權應該怎樣計算?獲許是根據面的角度吧,我沒有具體實現過,所以不確定。
  有人說,對於xyz嚴格對齊的模型,時刻以人工指定其T值的,但是這樣也有漏洞,比如一面對其x軸的牆超北,顯示正常,同時朝南的牆也就會不正常。
  通過這個方法,我得到了一個啓發,我們爲什麼要費這麼大勁算TBN空間?不就是爲了把光線轉換到TBN空間中,然後和法向量做角度計算 確定顏色值嗎?我們完全可以直接在法向量所在的空間中定義光線的方向啊!
  計算TBN的部分shader代碼:
  float3 normal = tex2D(NormapTex,inTxr) * 2 - 1; 
  float3 TBNLightPos = mul(lightPos,TBN_matrix);
  float   DiffuseAttn =  clamp(0, 1,dot(normal, TBNLightPos ));

  不計算TBN直接指定光向量部分shader代碼:
 float3 normal = tex2D(NormapTex,inTxr) * 2 - 1; 
 float DiffuseAttn =  clamp(0, 1,dot(normal, float3(1,-1,1)));

  省去了TBN的計算,性能下降幾乎爲0。
  但是這樣做有缺點,就是不能實時更新光源位置。這裏我指定的float3(1,-1,1),是比較普遍適用的,它表示光源從斜45度方向向下照射。效果還是可以接受的。

以下是未使用法線貼圖效果:

以下是使用不計算TBN空間的法線貼圖效果:


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