法線貼圖原理

   法線貼圖的出現,是爲了低面數的模型模擬出高面數的模型的" 光照信息 ".光照信息最重要的當然是光入射方向與入射點的法線夾角.法線貼圖本質上就是記錄了這個夾角的相關信息.光照的計算與某個面上的法線方向息息相關.

我們知道計算機裏的

模型,是通過多個多邊形面組合來近似模擬一個物體的.它不是圓滑的.面數越多,則越接近真實物體.光照到某個面當中的一點時,法線是通過這個面的幾個頂點通過插值得到的.插值其實也是爲了模擬這個點"正確"的法線方向,不然整個面所有點的法線一致的話,光照上去,我們看到的模型誇張點就像一面面鏡子拼接起來了.但法線插值不可避免的仍然會失真.模型的面數越高,失真的程度自然越小.要是能無限細分到人眼看不出的地步,根本不用插值了.

面數高,需要計算的量和內存需求就高.前輩找到了法線貼圖(前身是凹凸貼圖)這個辦法,使低模能夠近似享受高模的光照細節信息.代價是有的,就是需要一個記錄這些信息的文件.這是程序中常用的存儲空間換計算時間的做法.3D程序中偏愛使用這個手法.誰叫存儲硬件的單位價格比計算硬件的單位價格降低速度快很多呢.

顯卡包括包括與之相輔的圖形api,讀的數據最初來源是圖片.所以記錄這個信息的文件就被我們保存爲圖片格式.法線貼圖後邊2個字就這麼來的.很好你已經明白一半了.

因爲面數少,低模上某個區域的一個面,可能就是高模上相同區域的幾個面.看下圖的高模與低模的對比(爲了簡便我們抽象爲2維的線段)

上邊凹凹凸凸的曲線表示高模.下邊比較平滑的表示低模.因爲高模細節多,所以在某段區域它的方向變化自然比平平板板的低模多.上圖看不懂我表示無能爲力.

看到這圖,一些人應該有所感覺了.沒感覺也不要緊,接下再來.

不管高模還是低模,反正最後還是要被上色的.假設模型已經被渲染完成有顏色了,現在我們想象用剪刀把模型展開(類似給動物扒皮的過程),得到2張差不多一樣大小的皮,畢竟面積不會差太多.高模的皮當然膚白體嫩精度高,低模的皮就有些糙了.現在再想象這麼個過程:逐漸把高模的皮移到低模的皮上方一定高度直到水平重疊.

現在這個樣子你有感覺沒有?沒感覺也不要緊,接下再來.

雖然模型精度不一樣,無論如何,這2張皮每一點都是有顏色了的(插值的功勞).兩張皮上相同一點的顏色,高模這張皮上的更真實,因爲在計算最終顏色信息所依賴的法線,高模上的 點比低模上的點更精確.我們如何給低模這張皮美容,使它能夠接近高模的效果呢?換句話說,找到辦法,使土肥圓演變爲黑木耳,質變爲白富美是不可能的,那得下輩子.

辦法很暴力.現在再想象你用一根針,從上往下,刺穿高模的皮,再刺到低模的皮.保證針垂直,這樣就刺到同一點了.再想象如果這針有魔力的話,它刺穿高模皮的過程中,盜取了一些信息,傳送到低模皮上邊.低模皮依靠這些信息計算,成功蛻變爲黑木耳.這些信息是什麼呢?當然是法線信息了.現在 高模這張皮被密密麻麻插滿了針眼,換句話說,保存高模泄漏來的信息,必定是點對點的.即這張皮上的每個點,都得被保存.所以法線貼圖跟原始的貼圖是一樣大小的,貼圖內每個點都保存了對應高模某個點的法線信息.實際的計算,只會關心由貼圖裏得來的法線信息,低模上的那些法線,被拋棄了.

現在這個樣子你有感覺沒有?沒感覺也不要緊,接下再來.

爲什麼我之前強調垂直呢?不只是爲針能扎到同一點.現在請把這個過程,想象到上圖中.圖中的箭頭,表示高模上某個點的法線方向.如何記錄這個方向信息?現在請想象逐漸把高模和低模重疊在一起,爲了方便想象,低模小一些被高模包住了.或者你乾脆想象高模的面在低模面的正上方.再想象有一束光線(針的等價物),從上往下照射,把高模上的法線投射到低模上.

前戲大功告成,現在我們來處理稍微細節些的問題了.這是一個投影過程.但是影子是2維的啊?向量是由x,y,z三個分量構成的.高模上某點投影到低模上對應點所在平面,只剩2個分量的投影了.好比我們現在只知道法線在x-y平面的投影方向,那在z軸的方向呢?只要我們確保投影前法線是單位向量,那很簡單z=1-x*x-y*y.這樣我們還可以省下保存z的空間.其實我們既然已經知道這個法線方向(高模object space內的法線方向),而且被單位化了,直接保存也是可以的.投影過程只是個思想實驗,實際是不會有什麼光線由上到下投射的.

到此可以明確了,"正統"的法線貼圖生成,是高模,低模不可缺一的.因爲沒有高模就不知道法線方向,沒有低模,就不知道高模上某點的法線對應於低模上哪個點.

因爲某點的法線信息是被保存到法線貼圖上對應像素點的.實際計算是把法線x,y,z方向大小映射到顏色空間rgb裏.就是把x值存在r裏,把y值存在g裏,把z值存在b裏.因爲rgb是8字節爲單位的.所以高模的法線信息存儲到像素裏是要丟失精度的.而且前面計算高模與低模對應點也不可能完全匹配到,本來就是個模擬過程.自然法線貼圖也不是無敵的.

現在我們可以回答之前的Photoshop根據diffuse貼圖生成法線貼圖的問題了.實際的diffuse貼圖是根本沒有包含模型上的法線信息的.因此它根據diffuse貼圖得出的法線貼圖根本就是錯誤的.但爲什麼能夠應用呢?請想象高模的精度高的嚇人,高到渲染後把高模皮扒下來後,就成了一張照片.再想象之前高模上的貼圖是佈滿了鐵鏽.於是你就得到了一張鐵鏽照片.Photoshop處理這張鐵鏽照片,其實是根據一些算法(sobel等等)把顏色值轉化爲梯度值,近似模擬了法線.因爲我們其實不關心鐵鏽的精確分佈,像那麼一回事就可以了,所以這種情況下如此處理是可以將就的,坑坑窪窪效果最適合如此做法.photoshop這種脫離高模低模的做法容易讓人迷惑,導致新手以爲法線是從diffuse貼圖上來的,或者乾脆被阻斷了思路.

我們上邊計算法線貼圖所用到的法線,又是從哪裏來的.如果這個法線方向,是處於世界座標中的(world space),那稱爲world space normal.如果是處於物體本身局部座標中的,那稱爲object space normal.很容易想象,world space normal一旦從貼圖裏解壓出來後,就可以直接用了,效率很高.但是有個缺點,這個world space normal 是固定了,如果物體沒有保持原來的方向和位置,那原來生成的normal map就作廢了.因此又有人保存了object space normal.它從貼圖裏解壓,還需要乘以model-view矩陣轉換到世界座標,或者轉換到其他座標取決於計算過程及需求.object space normal生成的貼圖,物體可以被旋轉和位移.基本讓人滿意.但仍有一個缺點.就是一張貼圖只能對應特定的一個模型,模型不能有變形(deform).

>> tangent space normal map

爲解決適應變形的normal map,我們仍能從這兩種方法中得到啓示.world space normal直接保存的是世界座標系中的高模法線方向.因此低模取出該點法線就可以直接使用,前提是低模的世界座標系與高模一致,一點旋轉都不能有,不然法線方向就改變了.object space normal保存的是模型空間座標系中的高模方向,低模取出該點取出來法線,還需要乘以所在的model-view矩陣,轉化爲低模的世界座標系中的方向,也就是說低模端還需要做一個運算.因此即使低模任意旋轉也不怕,有model-view矩陣可以把法線貼圖中的值轉換兩者效率由高到低,靈活度由低到高.問題來了,我們是否能找到高模上的另外一個座標系統,使低模變形也時也能較正確的變換法線到世界座標系中?

我們考察一下object space.當一個低模旋轉時,因爲是剛體不變形,相當於每個點都乘以一個旋轉矩陣R,之後各點關係保持不變.實際上,我們保持物體不旋轉,將object space的座標系(x,y,z三個軸)旋轉,得到的結果是一樣的.這個關係相信大家都能理解.換句話說,法線針對於object space是固定不動的,物體保持在object space固定,只管跟隨座標系的移動,旋轉就行了.現在我們想象低模的某個點需要變形時,那原則上也可以通過讓object space座標系乘以某個變形矩陣T來達到.但是不同的點有不同的變形,不可能存在一個矩陣T即適合這個點又適合這個點.因此object space座標系是不能用的.會有哪個單一的座標系能存在一個所有點都共用的變形矩陣嗎?顯然無法想象.

變形時,頂點關係改變了,即面的形狀,方向改變了.如果面上存在一個固定的座標系,那當物體變形,移動,旋轉時,這個座標系必定跟着面一起運動,那麼在這個座標系裏的某個點或向量(比如我們把高模法線轉換到這個座標系裏),不需要變動.當整個面發生變化時,我們只需要計算面上的座標系到世界座標系的轉換矩陣,那麼定義在這個面上的點或座標(固定的),乘以這個矩陣即可得到在世界中的座標.這個座標如何構造目前對我們不重要,請務必理解這個概念.我們不過是尋求一個局部座標系,局部座標系中的點座標,乘以局部座標系到世界座標系的轉換矩陣(這個矩陣是低模渲染時動態計算的的),得到局部座標系中的點在世界座標系中的座標.這樣法線貼圖中存儲的固定的值(法線方向),才能進行有意義的計算.

看到這裏很明顯的,這種做法需要數千個不同的定義在面上的座標系.低模上有多少個面,就得有多少個這樣的座標系.這種方法的計算量自然是比object space normal map要大一些的.在低模的每個面上,要構造出這個座標系.這個座標系術語裏稱爲tangent space.

object space normal map的中,低模的object space座標系與高模中的object space座標系是重合的.所以不需要構建,所以低模上某點才能直接用高模的法線替換自己的法線.座標系重合這個概念很重要.新方法中,低模上的這個tangent space,也必須與高模上的座標系tangent space.因爲低模上的一個面,可能對應了高模上的幾個面(精度高),按照新方法每個面都有一個局部座標系,那對於低模上的每個面,高模因爲存在好幾個面,就會出現好幾個面,這肯定是不行的.所以高模所用的tangent space,就是低模上的.生成法線貼圖,必定會確認高模上哪些面都對應低模上的哪個面,然後高模上的這幾個面的法線,都會轉換爲低模這個面上所構建的tangent的座標.這樣,當低模變形時,即三角面變化時,它的tangent space也會跟着變化,保存在貼圖裏的法線乘以低模這個面的tangent space到外部座標系的轉換矩陣即可得到外部座標.順便再提一點,高模保存的這個法線,是高模上object space裏的法線,看到這裏你該明白這是自然而然的.你搜索文章時可能會看到什麼把光轉換到tangent space裏,確保處於同一個座標系下的話.有一點3d數學知識的人都知道,還要你提的不痛不癢?我以爲確保tangent sapce重合及做法,纔是讓人頓悟tangent space的訣竅點.

當我自己想到上邊這段話時,tangent space的法線貼圖原理就豁然開朗了.接下來我們構建這個tangent space座標系.

面在動時,tangent space也得跟着動.面上的垂直法線是跟着動的,因此這個法線N可以作爲tangent space的一個座標軸. 非常非常需要注意的是,這裏所說的面上垂直法線,不是指插值所得來的法線,那個法線正是是我們需要保存的內容.N單純就是指垂直於這個面的方向.

我們考察上圖,對於一個三角面,它的邊V2V1,V3V1,V3V2我們總是能夠確定的.邊也定會在變形時跟着動.因此我們可以選擇一條邊作爲tangent space的第二個座標軸T.第三個座標軸就簡單了,直接根據叉積來B=T * N.這個座標軸就訂好了.其實,座標軸的選定幾乎可以是任意的,只要你能夠確保每次都能構建出來.比如你可以先選擇V1V3,V1V2作爲座標軸,N=V1V3 * V1V2.這裏N恰好和前面一樣方向.但如此一來這個座標系中V1V2,V1V3不是垂直的,不正交的座標基在矩陣運算中是不方便的,還得正交化.因此我們選擇第一種最直觀最清晰最方便的方法.

既然三個座標軸都確定了,那構建object space到tangent space的矩陣O-TBN就簡單了,我們把T,B,N單位化,分別作爲tangent space的x,y,z軸.根據三個座標基我們構造矩陣如下:

高模上object space內的某點法線,乘以這個矩陣,即得到tangent space內的法線方向,再把這個值映射到rgb空間,存爲貼圖即可.這個矩陣爲什麼是這樣,這是題外話了.我簡略說一下:object space的三個座標軸(1,0,0),(0,1,0),(0,0,1)乘以這個矩陣,必須剛好爲tangent space中的座標軸,很自然矩陣就是上邊的樣子.而object space其他點的座標都是x,y,z三個單位座標的線性組合.所以這個矩陣對於其他點必定是正確的.

實際上在vertex shader中,我們只能知道當前頂點的信息,三角形的另外兩個頂點我們是不知道的.但現代的shader能夠爲頂點提供一個tangent信息,表示在頂點處的切線.你可以想象一個足球上經過某點的切線.因此我們會把頂點的tangent方向作爲上邊的T向量.這也是tangent space叫這個名稱的由來. 你會看到很多文章中提到紋理的u,v方向.因爲面上某點的u,v是沿各條邊線性插值的,所以u,v方向與邊的方向相同.其實我們現在已經有現成的tangent可以用了.

  好了,現在高模面上各點的法線值,都轉換爲低模上的tangent space座標了.現在我們考慮具體的低模上的渲染計算了.假設在低模上的某個面我們計算出了這個矩陣,並取出了面上某點的對應在法線貼圖裏法線值.現在需要計算光照.我們可以把光向量轉換到tangent space裏做計算.也可以把得到的法向量轉換到world space與光向量進行計算.結果是一樣的.實際考量,你會發現後一種方法不好.因爲對於面上的每個點,都要計算一次normal到world space的準換.而前一種方法,對一個面上的所有點,只要計算一次光向量到tangent space的計算.然後再考慮到vertex shader與fragment shader的流程,你會發現剛好我們可以在vertex shader計算光線到tangent space的轉換,在fragment sader取出法線值與前面得到的tangent space裏的光線方向做計算即可.這裏提醒一下,一般verteix shader中我們得到的光線方向是基於world space的,而法線貼圖保存的是高模的object space內的方向然後再轉換到tangent space,所以在vertex shader中,我們必須先把光線先轉換到object space,再轉換到tangent space.這樣才能保證最終計算時,光線與法線是基於同一個座標系的.

以上是法線貼圖的原理.因爲該原理的應用範圍很廣,也能夠串起很多知識點,是非常值得搞清楚的

原圖、法線圖以及程序計算後的效果如下:



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章