- Normal Map中的值 -
有沒有想過,Normal Map(法線貼圖)爲何看上去都是「偏藍色」的?這是由於,在map中存儲的值都是在Tangent Space(切空間)下的。好比,一根正好垂直於表面的法線向量在切空間下是(0,0,1),假如用一個char(注意不是unsigned char)來表達像素的話,該向量就會被轉換爲(0,0,127)。這樣的值無疑是「藍色」。因爲大部分的法線都不會偏移這根「標準法線」太遠(好比[0.1, 0.2, 0.8]...)因此大部分像素都是「偏藍」的。用這種方式存儲Normal,能夠視爲這種法線老是「貼着」模型表面「插」上去的,而不用考慮這根法線到底在世界空間/模型空間的什麼地方,又會通過怎麼樣的轉換。這樣就能夠與各類可能的空間變換操做解耦,並且直觀友好,簡單易懂。
- Tangent Space (切空間) -
那麼,什麼是切空間?我我的理解,切空間就是針對表面上某個考察的點,以該點的uv二維座標系表達該點的切線(tangent)和該點的次法線(binormal)所構成的切平面,再加上垂直於它們的法線,就組成了一個能夠用來被描述的空間。該空間就是切空間,它的座標系(三根軸,三個基「basis」)分別是tangent(對應x), binormal(對應y)和normal(對應z)。
這裏,比較難理解的是切空間和uv座標的對應關係。不妨假想如今有一個構造很是複雜的模型,但它的表面(surface)能夠像剝鹿皮同樣完整剝開,而它的uv也恰能夠以這樣的「比喻」展開。它的法線既到處都垂直於它的表面。如今想象全部的法線都「扎」在這個表面上,而後讓咱們把這個表面以展uv的方式剝開並攤平(那麼你如今面對的實際上是一張uv set)。當你聚焦(focus)到某一個點的法線上時,這根法線正對着你,它的方向就是切空間的z軸。而這張uv set就是「切平面」。鏈接該點與下一點的座標u的方向就是該點的切平面的x軸,而鏈接該點與下一點的座標v的方向,就是它的切平面的y軸。
出於省事的說法,有的人可能會這樣描述,「切空間下的x軸和y軸就是頂點u,v的座標」 。若是你聽到某人這麼說,你能夠揣測他多是真的是行家,由於這個意思算是「點」對了。但不幸的是,嚴格來講,這種說法是錯誤的。切空間下的x軸方向(切線方向),是該點u座標指向下一點u座標的方向;對應地,y軸方向(次法線方向),是該點v座標指向下一點v座標的方向。若是你在一張uv set上審視它,也就等因而在一個二維笛卡爾座標系上審視它,那麼獲得的x軸方向就是「水平」的,而y軸方向就是「豎直」的。這裏想要強調的意思是,不管是切線方向仍是次法線方向,它們做爲一個方向,勢必是由兩點之間的走向關係決定的。單獨的某點u,v座標,僅僅是個值而已,單憑一個點的值,既不可能獲得切線方向,也不可能獲得次法線方向。換成這種說法 「切空間下的x軸和y軸就是頂點所在uv座標系下的u軸和v軸」, 就對了。
- 求算 Tangent 與 Binormal -
- 「切空間」下的切線
再來考慮一個問題。在空間中某點的position和normal是很容易知道的,那麼相應也很容易獲得該點的「切平面」。那麼,可否隨意在這個「切平面」上指定一條tangent和一條binormal,做爲該點的切線和次法線?從數學理論上來說這沒問題。隨着隨意構造的tangent,binormal和normal指定好後,一個空間座標系就由此指定了。固然了,根據以前的假設,這個座標系未必是正交的(由於tangent不必定垂直於binormal),但normal = tangent cross binormal。這樣的空間座標系能夠構造出無數個來,但若是要構造出上一節討論過的「切空間」座標系,卻只能有一種構造方法。而「切空間」在行業中是一種廣泛認可和使用的空間,所以有必要弄清楚如何求算這個屬於「切空間」下的tangent和binormal向量。(至於將切空間和uv座標系聯繫起來究竟有什麼好處,我還不是太理解。暫時做爲事實接受下來)。注意這裏說求算tangent和binormal向量,是指它們從切空間(tangent space)被「轉換」,或者說「映射(mapping)」到物體座標系下(object space)下的值。
- 從另外一個角度看「轉換(映射)」
在開始正式推導前,爲了可以使接下來的一個結論看起來「顯而易見」,我想舉一個直觀的例子,從另一個角度談談我對「轉換」,或者說「映射」的認識。想象有一段路面在某點開始分了叉,一條路是上坡,而另外一條路是下坡。一輛汽車行駛在這段路面上並開始上坡,太陽在它身後,將它的影子"映"在了那段下坡的路上。如今,能夠這麼理解,汽車和汽車影子分別處於不一樣的兩個空間內,一個是上坡的空間,另外一個是下坡的空間。但它們之間有某種聯繫,那就是當汽車在行駛時,它們都會發生相關聯的變化。假如我知道,當汽車在上坡的空間中處於某點p1時,它的陰影處於下坡空間中的某點s1;當汽車前進到上坡空間中的某點p2時,它的陰影對應地前進到下坡空間中的s2。接下來我想知道,當汽車陰影在下坡的空間中前進了一個單位的長度時,對應地汽車在上坡的空間中前進了多少?
這個問題應該是「顯而易見」的,答案是(p2-p1)/(s2-s1),這也是「除法」的基本含義。同時,當p2無限接近p1(因爲關聯性s2也無限接近s1)時,這也變成了「導數」的含義(dp/ds),由於這個問題實質上就是在問「變化率」的問題。在這個例子中,因爲上坡和下坡都是一條直線,因此狀況能夠簡化爲dp = p2-p1, ds = s2-s1, 因而答案也等於dp/ds。
dp/ds這個值的單位,是處於dp所在的空間中的。回到上面這個例子中來,dp/ds這個表達式,是在說當汽車影子在本身的空間中變化ds個單位時,對應汽車在本身的空間中變化了dp個單位。同時也能夠這麼理解,汽車影子在本身空間中運動了ds個單位,「映射」到汽車所在的空間,汽車對應在本身的空間中運動了dp個單位。
這個問題能夠歸納爲,自變量(x)在本身的空間內變化一個單位,「映射"到因變量(y)所在空間中,因變量會變化dy/dx個單位。
有了這個基礎的理解,能夠將其推廣到多維的狀況。自變量和因變量都處於各自的空間中,它們的維數還不必定相等。例如,考察一架飛機在單位時間(t)內位移(s)的變化,就是ds/dt。其中ds是在三維空間中,而dt卻處於一維空間(時間)中。這裏它們各自是幾維是不重要的,關鍵在於自變量與因變量之間的」關聯「。正是因爲這種」關聯「,使得當自變量只變化微小的一丁點時,因變量也會變化那麼微小的一丁點。
- *擴展閱讀 : pbrt中的 pi = p0 + (dp/du) * u + (dp/dv) * v
(注:若是你沒在看《pbrt》,或者你從沒見過上面這個公式,那麼此節能夠略過不看。這節只是源於自問自答的一個想法。曾經爲理解這個公式卡了好久,既然如今稍稍有些心得,那也該本着善始善終的態度所有記錄下來爲好。)
讀過pbrt的同窗知道,第3.6.2節講的是如何求得射線與三角形相交的那一點「微面(facet)"的所有信息。其中就包括tangent和binormal。只不過,它將tangent值表達成了dp/du, binormal值表達成了dp/dv。關於這個觀點,在本文的上一節中已經進行了力所能及的理解。只不過,書中在進行推導時,是從下面這個假設起步的,即在由三個點pi(i=1,2,3)組成的三角形中,設p0是三角形所在平面的其中一點,那麼則有:pi = p0 + (dp/du) * u + (dp/dv) * v。
這裏,我就想補充解釋一下爲何會有這個結論。
提及來也簡單,只是書中沒有明說,這個p0點對應的uv座標值就是(0,0)。而後咱們把這個公式變通一下,就成了:pi - p0 = (dp/du)*(u-0) + (dp/dv)*(v-0)。這個公式,就很像本文上節最後獲得的結論。只不過是把點2換成了點i,點1換成了點0。書中的意思是說,在這個三角形所在的這個平面上,任意一點均可以經過這種公式計算獲得,只要給定了uv(0,0)對應的p0點以及欲求點的uv座標值(u,v)便可。
這裏可能讓人感到困惑的一個地方是,uv座標與頂點之間的對應關係,通常是經過人爲指定的。那憑什麼認爲uv座標基點(0,0)所對應的p0,會剛好在pi(i=1,2,3)這個三角形所在的平面上?實際上這裏說的uv座標(0,0)點,並非咱們一般認爲的人爲指定的那個點,而是從數學角度上」推斷「出的一個點。能夠這樣理解。既然咱們知道三角形的三個頂點的三維空間座標值(xi, yi, zi)(i=1,2,3)與uv座標值(ui, vi)(i=1,2,3),又知道三點可以肯定一個平面這個常識。那麼就能夠推斷出在uv空間中的任意一點,在三角形所在的這個平面中一定存在對應的頂點。 那麼也就能夠肯定在這個平面上找到一點p0,使得它的uv座標值是(0,0)。
- 開始求算!
有了以上知識理解上的準備,接下來的求算任務基本上就是直截了當的了,沒什麼太須要費腦力的地方。不過須要對線性代數的基礎知識有點了解: 其實只要知道如何求逆矩陣就好了。
切線和次法線永遠老是針對面(face)而言的,單獨考察一個點的切線或是次法線沒有意義。而構成一個面最簡單的方式就是三角形,因此咱們就考察如何求算三角形的tangent和binormal。給定一個三角形,已知它的三個物理空間的頂點爲pi(i=1,2,3),對應的uv空間座標爲uvi(i=1,2,3)。因爲這是一個三角形平面,所以三個點的tangent值與binormal值都是該面上的值。設 tangent = dp/du, binormal = dp/dv。根據上幾節的推導,咱們很容易就能夠獲得,
p2 - p1 = dp/du * (u2 - u1) + dp/dv * (v2 - v1)
p3 - p1 = dp/du * (u3 - u1) + dp/dv * (v3 - v1)
修改爲矩陣形式,就成了下面這樣,
再變換一下,就獲得
求解這個式子的細節就不列在這裏了。關鍵瞭解一下如何求一個矩陣的逆矩陣(伴隨矩陣數除其行列式),而後根據矩陣的乘法運算規則就能獲得結果。隨着結果的求得,咱們也就知道了tangent和binormal的值。
- TBN Matrix (TBN 矩陣)
上節求得了
tangent(簡寫爲
T)與
binormal(簡寫爲
B),再叉乘一下就獲得了
normal(簡寫爲
N):即
N =
T X
B。 由此三個向量就能夠構成一個空間,[
T, B, N]。
這個矩陣表示的是,切空間(tangent space)中的三個基向量被轉換到當前座標系下所對應的三個基向量構成的空間。任何一個切空間下的向量,經過這個矩陣即可以變換到當前座標系下(好比模型的物體座標系亦或它所在的世界座標系),究竟被轉到什麼座標系,這要看當時求算T,B時利用的Pi點所在的空間:若是Pi是模型的物體座標系,那對應該矩陣也就會把切空間的向量轉到物體座標系下;若是Pi是模型所在的世界座標系,那對應矩陣就會把向量轉到世界座標系下。好比,
normal in object space =
[T,B,N] X normal in tangent space
這裏咱們關心的問題是,這種轉換的意義是什麼?費這麼大勁作轉換,究竟是爲了什麼?答案是,經過這種轉換,就能夠把Normal Map中的法線轉換到對應的物體/世界座標系下,與這個空間下的光線進行計算,從而可以算出對應此點的正確光照。咱們知道Normal Map的法線爲何存儲在「切空間」下是有道理的(第一節有提到),但它不能直接被使用。通過這樣的轉換,就能夠與各類可能的空間座標聯繫起來了。
不過常常用的,還不是把normal值轉換到物體/世界座標系下,而是反過來,把光線「反」轉換到到切空間下,與normal計算出光照值。爲何?由於一個模型可能有很是多的點,對應normal map中的normal值數量必然也是巨大的,若是把每一根normal都作轉換,這種計算量的成本是至關高的。但反過來,光線就那麼幾條,轉換一次光線就能給全部切空間下的normal使用,相對來講這種計算要「便宜」得多,因此反轉光線值這種作法是更爲常見的作法。
light in tangent space =
[T,B,N]-1 X light in object space
一樣須要求其逆矩陣。不過若是TBN三向量彼此正交的話(通常來講是這樣),那麼它的逆矩陣就簡單地等於它的轉置矩陣。
實際上TBN的做用還不止於此,它的應用很普遍並且也很是重要。好比作displacement mapping時,基於Vector置換的作法就一樣用到了TBN矩陣。用法和原理與Normal Map都是同樣的,只不過此時Map換成了再切空間下的用來置換的向量,而非法線。
-----------------------
reference :
3.book "pbrt"(2nd edition) section-3.6.2