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

2021-06-21 03:27:55 字數 2738 閱讀 3567

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

為什麼叫法線貼圖,我們知道法線(normal)是垂直於乙個面的直線,通過計算光線與這條法線的角度就可以知道與面的角度,進而可以計算出面應得到的顏色值。如果我們知道物體每個面的法線就能實現對這個物體進行光照渲染。但是一堵牆也許只有四個頂點,也就是只有乙個面,它最終的渲染效果將會非常單一,假設這堵牆上有更多的磚的凹凸痕跡,我們怎樣實現僅用四個頂點渲染出立體感很強,細節層次感很強的這堵牆呢,答案就是 法線貼圖(normal map)。

在法線貼圖技術中,我們就是通過把牆面的每個畫素的法線儲存在一張紋理中,渲染的時候根據每個畫素的法線確定他們的陰暗程度,而這張法線貼圖是可以用photoshop軟體從一張牆的紋理生成對應的法線貼圖的。到此,熟悉法線貼圖的朋友會對以上內容很熟悉的。

試想在渲染過程中,如果把每個法向量都轉換到世界空間中跟光向量計算角度的話,那麼這麼多的畫素法向量肯定影響效能,但是如果把光向量轉換到法向量所在的空間中,豈不快哉(#add 逆向思維)?因此我們這裡提到乙個正切空間(tangent space)。正切空間就是法向量所在的空間,在這個座標系中,法向量作為高度軸,類似z軸。再找到其他的兩個軸問題就會變得簡單,但恰好這裡是比較麻煩的。

其實我們已經知道 高度軸了,再找到乙個軸,第三個軸就可以通過已知的兩個軸做叉乘得到。我們要找的那個軸就叫做 正切向量(tangent vector)。正切向量是需要平行於法向量的平面的。

明白了這些問題就來實際的操作:

在正切空間中,三個基向量分別叫做t、b、n,t代表tangent,b代表binormal,n代表normal。在texture space(紋理空間中),兩個二維基向量分別叫做u、v,  b就可以對映到紋理空間中的u,t就可以對映到紋理空間中的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空間的法線貼圖效果:

記憶體對映對於大檔案的使用

平時很少使用大檔案的記憶體對映,碰巧遇到了這樣的要求,所以把過程記錄下來,當給各位乙個引子吧,因為應用不算複雜,可能有考慮不到的地方,歡迎交流。對於一些小檔案,用普通的檔案流就可以很好的解決,可是對於超大檔案,比如2g或者更多,檔案流就不行了,所以要使用api的記憶體對映的相關方法,即使是記憶體對映...

對於馮嘉禮老師定性對映理論的複習

馮老師,給我上過課,那應該是他退休前最後一次教學吧。很慚愧,我不是乙個好學生,上課不大喜歡認真聽講。只是,到了學期即將結束時,要求交一篇 我才開始閱讀,了解,部分理解馮老師的理論。馮老師,是搞數學出身的,所以上課時什麼公理化教學很擅長 老人家經歷比較多,所以上課非常生動風趣。馮先生,後來轉向了人工智...

對於馮嘉禮老師定性對映理論的複習

馮老師,給我上過課,那應該是他退休前最後一次教學吧。很慚愧,我不是乙個好學生,上課不大喜歡認真聽講。只是,到了學期即將結束時,要求交一篇 我才開始閱讀,了解,部分理解馮老師的理論。馮老師,是搞數學出身的,所以上課時什麼公理化教學很擅長 老人家經歷比較多,所以上課非常生動風趣。馮先生,後來轉向了人工智...