法線貼圖那些事兒

bzyzhang發表於2020-05-25

概述

在學習法線貼圖的過程中,有幾個比較難以理解的概念,這裡記錄一下。特別說一下,本文的法線貼圖是切線空間下的法線貼圖。

空間變換

空間變換示意圖

如上圖所示,簡單表達了在使用法線貼圖的過程中,涉及到的幾個空間變換:

  1. 切線空間:從法線貼圖中取樣得到的法線,在切線空間中;

  2. 物件空間:物體的本地座標空間,頂點的相關資訊,在物件空間;

  3. 世界空間:光源位置、觀察者位置等,在世界空間中。

在空間變換的過程中,主要涉及到了兩個變換矩陣:

  1. \(TBN\)矩陣:從切線空間變換到物件空間;

  2. \(Model\)矩陣:從物件空間變換到世界空間。

對於上述概念,大部分都是比較熟悉的,只有法線貼圖、切線空間和\(TBN\)矩陣比較陌生。下面,將分別介紹一下。

法線貼圖

在3D計算機圖形學中,法線貼圖是一種用於偽造凹凸光照的技術,是凹凸貼圖的一種實現。它用於新增細節,而不使用更多的多邊形。這種技術的一個常見用途是,通過從高精度多邊形或高度圖生成法線貼圖,來極大地增強低精度多邊形的外觀和細節。下圖來自Paolo Cignoni,圖中對比了兩種方式:

法線可以使低精度模型實現高精度模型的效果

法線貼圖通常儲存為常規RGB影像,其中RGB分量分別對應於表面法線的X,Y和Z座標。

法線的每個分量的值的範圍是\([-1,1]\),而RGB分量的值的範圍是\([0,1]\)。所以,在將法線儲存為RGB影像時,需要對每個分量做一個對映:

\[vec3 \quad rgb\_normal = normal * 0.5 + 0.5 \]

這裡要注意,將法線儲存到法線貼圖的過程中,需要進行上述操作。當我們從法線貼圖中讀取到法線資料後,需要進行上述變換的逆變換,即從\([0,1]\)對映到\([-1,1]\)

切線空間

那麼,法線向量應該相對於哪個座標系呢?我們可以選擇模型頂點的的座標系,即物件空間;也可以選擇模型紋理所在的座標系,即切線空間,也稱為紋理空間。

物件空間中,法線資訊是相對於物件空間的朝向的,各個方向的法線向量都有,所有貼圖看起來色彩比較豐富;而在切線空間中,法線是相對於頂點的,大致指向頂點資訊中的法線方向,即法線向量接近於\((0,0,1)\),對映到RGB是\((0.5,0.5,1)\),這是一種偏藍的顏色。下圖分別是物件空間和切線空間下的法線紋理。

物件空間和切線空間下的法線紋理

那麼,怎麼進行選擇呢?考慮一下二者的優缺點:

  1. 重用性:對於物件空間的法線貼圖,它是相對於特定物件的,假如應用到其他的物件上,可能效果就不正確了;而切線空間中的法線貼圖,記錄的是相對法線資訊,所以可以把它應用到其他物件上,也能得到正確的結果。

  2. 可壓縮:考慮到法線向量是單位向量,而且Z分量總是正的,可以只儲存XY方向,而推匯出Z方向。

綜上所述,我們一般選擇切線空間下的法線貼圖。

\(TBN\)矩陣

在光照的計算過程中,需要用到光線方向、視線方向和法線方向等,為了得到正確的結果,這些變數必須在同一座標系下計算。參考一下本文開頭的“座標變換示意圖”。

在紋理座標系中,x和y分量與2D圖片的水平方向和垂直方向對齊,而z分量指向圖片外部的上方。如下圖所示:

紋理座標系

為了正確使用貼圖中的紋理資訊,我們必須找到一種方法——從切線座標空間變換到物件空間。這可以通過指定切線座標系的座標軸在物件空間中的方向來達到。

對一個單獨的三角形面片來說,我們可以認為紋理貼圖覆蓋在三角形的表面上,如下圖所示:

根據上圖,可以得出:三角面片和紋理貼圖是共面的。那麼,根據平行四邊形法則,可以得出:

\[\begin{matrix} E_{1} = \Delta U_{1}U + \Delta V_{1}V \\ E_{2} = \Delta U_{2}U + \Delta V_{2}V \\ \end{matrix} \]

其中,\(E_{1}\)\(E_{2}\)是兩個頂點之間的向量差,可以根據頂點的座標計算出來;\(\Delta U_{1}\)\(\Delta V_{1}\)\(\Delta U_{2}\)\(\Delta V_{2}\)分別是紋理座標的水平和垂直方向的差,可以根據紋理座標計算得到。\(U\)\(V\)分別是紋理的水平和垂直座標軸,是要計算的未知量。

寫成座標表示:

\[\begin{matrix} \left( E_{1x},E_{1y},E_{1z} \right) = \Delta U_{1}\left(U_{x},U_{y},U_{z}\right) + \Delta V_{1}\left( V_{x},V_{y},V_{z} \right) \\ \left( E_{2x},E_{2y},E_{2z} \right) = \Delta U_{2}\left(U_{x},U_{y},U_{z}\right) + \Delta V_{2}\left( V_{x},V_{y},V_{z} \right) \\ \end{matrix} \]

上面的方程,也可以寫成矩陣乘法的形式:

\[\begin{bmatrix} E_{1x} & E_{1y} & E_{1z} \\ E_{2x} & E_{2y} & E_{2z} \\ \end{bmatrix}= \begin{bmatrix} \Delta U_{1} & \Delta V_{1} \\ \Delta U_{2} & \Delta V_{2} \\ \end{bmatrix} \begin{bmatrix} U_{x} & U_{y} & U_{z} \\ V_{x} & V_{y} & V_{z} \\ \end{bmatrix} \]

兩邊同時乘以\(\Delta U \Delta V\)的逆矩陣,可得:

\[\begin{bmatrix} U_{x} & U_{y} & U_{z} \\ V_{x} & V_{y} & V_{z} \\ \end{bmatrix}= \begin{bmatrix} \Delta U_{1} & \Delta V_{1} \\ \Delta U_{2} & \Delta V_{2} \\ \end{bmatrix}^{-1} \begin{bmatrix} E_{1x} & E_{1y} & E_{1z} \\ E_{2x} & E_{2y} & E_{2z} \\ \end{bmatrix} \]

求逆矩陣不太方便,可以使用伴隨矩陣法

\[\begin{bmatrix} U_x & U_y & U_z \\ V_x & V_y & V_z \end{bmatrix} = \frac{1}{\Delta U_1 \Delta V_2 - \Delta U_2 \Delta V_1} \begin{bmatrix} \Delta V_2 & -\Delta V_1 \\ -\Delta U_2 & \Delta U_1 \end{bmatrix} \begin{bmatrix} E_{1x} & E_{1y} & E_{1z} \\ E_{2x} & E_{2y} & E_{2z} \end{bmatrix} \]

至此,我們求出了\(U\)\(V\)向量。但是我們需要的構成\(TBN\)空間的座標軸是正交的,這裡求出的\(U\)\(V\)並不一定能滿足正交的條件。這裡,頂點的法線\(N\)是已知的,我們可以根據\(U\)\(V\)\(N\),根據格拉姆-施密特正交化方法,求出與\(N\)正交的\(T\)\(B\)(此處假設切線空間是右手座標系):

\[\begin{aligned} T &= normalize \left( U - dot \left( U,N \right) * N \right) \\ B &= normalize \left( cross \left( N,T \right) \right) \\ TBN &= mat3 \left( T,B,N \right) \end{aligned} \]

這樣,我們獲得了座標軸相互正交的\(TBN\)矩陣。

實際使用中的法線貼圖

看完上面計算切線空間的\(TBN\)矩陣的部分,估計也是頭大。不禁想到,每次使用法線貼圖的過程,真的如此麻煩嗎?

幸運的是,答案是否定的。

一般情況下,模型儲存的頂點資訊中,都包含了頂點的法線和切線的資料,這樣,我們就不用進行上面的複雜計算了,直接使用法線和切線的叉乘,求出副切線,從而構成\(TBN\)矩陣。

參考

相關文章