前幾篇文章寫過有關法線貼圖的內容,這次文章將討論其原理及相關優化。回過頭來看一下原來的文章真有種想刪掉的感覺。。。
為什麼叫法線貼圖,我們知道法線(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的記憶體對映的相關方法,即使是記憶體對映...
對於馮嘉禮老師定性對映理論的複習
馮老師,給我上過課,那應該是他退休前最後一次教學吧。很慚愧,我不是乙個好學生,上課不大喜歡認真聽講。只是,到了學期即將結束時,要求交一篇 我才開始閱讀,了解,部分理解馮老師的理論。馮老師,是搞數學出身的,所以上課時什麼公理化教學很擅長 老人家經歷比較多,所以上課非常生動風趣。馮先生,後來轉向了人工智...
對於馮嘉禮老師定性對映理論的複習
馮老師,給我上過課,那應該是他退休前最後一次教學吧。很慚愧,我不是乙個好學生,上課不大喜歡認真聽講。只是,到了學期即將結束時,要求交一篇 我才開始閱讀,了解,部分理解馮老師的理論。馮老師,是搞數學出身的,所以上課時什麼公理化教學很擅長 老人家經歷比較多,所以上課非常生動風趣。馮先生,後來轉向了人工智...