一般的紋理映射雖然可以添加精致的表面細節,但它無法影響物體表面的光照細節,因此不適合模擬粗糙表面。生成物體凹凸表面的一個簡單的方法是對其表面法向量添加擾動,影響光照效果,這種技術又稱凹凸映射(bump mapping)。同一般的紋理映射一樣,凹凸映射也需要一張紋理圖,但這張圖通常是灰度圖,存放的也僅僅是像素的高度信息。實際產生效果的時候是通過計算凹凸圖中相鄰像素的高度差值來改變表面法向量的值。如下圖:
左邊是凹凸圖的一部分,右圖則顯示出各像素點間的高度差值(用向量表示),這個向量有很多種計算方式,不同的方法精確度不同,但是選擇什么方法要取決於你所要求的精確度是個什么層次。最通常的方法是分別計算每個點上X和Y的傾斜度:
x_gradient = pixel(x-1, y) - pixel(x+1, y)
y_gradient = pixel(x, y-1) - pixel(x, y+1)
為了能用這兩個斜度值對法線進行正確的干擾,我們必須在物體表面定義一個局部坐標系。
將每個頂點看成坐標原點,然后分別使用該頂點的切線T (Tangent),第二法線B (Binormal)和法線N (Normal)構成一個坐標系。在幾何上,由T B N組成的坐標系稱為Frenet Frame。於是這個貼圖坐標系F就可以表示為:
F = [ T B N ]
只要知道F其中任意兩個分量,都可以求出第三個分量,它們之間有這樣的關系:
T = B × N
B = N × T
N = T × B
實際上可以這樣理解,副法線B對應的是紋理空間坐標系的u軸,而切向量T對應了紋理空間的v軸,於是很容易得到干擾后的法向量:
New_Normal = Normal + (B * x_gradient) + (T * y_gradient)。
看到這里,我們可能會想,為什么不直接把法線信息存在紋理中,在需要的時候直接取出來用呢?這種想法正是法線貼圖的想法,實際上法線貼圖也可以算是一種特定的凹凸貼圖方法。DX中有專門的函數D3DXComputeNormalMap可以從一張高度圖生成法線圖,法線圖的RGB分別是原高度圖該點的法線指向:Nx、Ny、Nz。法線圖一般看起來比較怪異(通常偏紫色),如下圖:
雖然從法線圖可以直接得到法線向量(注:要通過一定的轉換,把顏色值從0x00-0xff范圍轉到-1~1范圍(因為0x00-0xff要表示正負兩個方向的向量值,所以[0x80,0x80,0x80]才表示向量為零向量),並規范化),但是我們仍然需要用到切向空間的信息,就是說我們需要副法線向量B和切線向量T的值,這樣才能將法線圖中的信息轉換到局部坐標系下,否則同一個物體變換一下方向,我們就得需要更新一下法線圖,不可重用,而動畫就更沒辦法使用了。對於頂點切線T和副法線B,我們只需要求出一個,就可以用公式T = B × N和B = N × T得到另一個,DirectX中提供了專門的求頂點切線的API,但我們也可以通過參考文章[1]中給出的方法來求切線。精確到表面每一個像素的切線T的計算可以用與普通紋理貼圖一樣的插值算法(如雙線性插值)來得到,最后將從法線圖中得到的法線inputnormal轉化到物體表面的局部坐標空間下:
finalnormal = inputnormal.b * B + inputnormal.g * T + inputnormal.r * N
下圖是我在之前的pathtracing算法中加入了法線貼圖后的效果,感覺還行。
最后需要說的是不管是凹凸貼圖還是法線貼圖都只是一種障眼法,仔細看還是能看出不真實的地方,如模型無法產生自陰影,當光照方向與表面法線方向角度很大時,無法看到由於凸起部分的遮擋而產生的陰影。並且,也無法在物體邊緣看到凹凸現象,因為它本身不會改變模型。可以看出下面的球體雖然使用了凹凸貼圖,但是球的邊緣仍然保持圓形而並沒有顯示出起伏。當下用曲面細分的方法可以產生真實的多邊形細節來模擬物體表面的凹凸不平,隨着技術的不斷進步,可能某一天凹凸貼圖就會被曲面細分方法或者其他技術替代。
參考文章:
[1].Computing Tangent Space Basis Vectors for an Arbitrary Mesh
http://www.terathon.com/code/tangent.html
[2].凹凸貼圖(Bump Map)實現原理以及與法線貼圖(Normal Map)的區別
http://hi.baidu.com/niujingqian/item/c5e897130a4aac031994ecda
[3].openGL CG 系列教程06 – Normal Mapping (法線貼圖)