1. 程式人生 > >法線貼圖那些事兒

法線貼圖那些事兒

# 概述 在學習[法線貼圖](https://learnopengl.com/Advanced-Lighting/Normal-Mapping)的過程中,有幾個比較難以理解的概念,這裡記錄一下。**特別說一下,本文的法線貼圖是切線空間下的法線貼圖。** # 空間變換 ![空間變換示意圖](https://cdn.jsdelivr.net/gh/bzyzhang/ImgHosting//img/2020-5-17/20200523103017.png) 如上圖所示,簡單表達了在使用法線貼圖的過程中,涉及到的幾個空間變換: 1. 切線空間:從法線貼圖中取樣得到的法線,在切線空間中; 2. 物件空間:物體的本地座標空間,頂點的相關資訊,在物件空間; 3. 世界空間:光源位置、觀察者位置等,在世界空間中。 在空間變換的過程中,主要涉及到了兩個變換矩陣: 1. $TBN$矩陣:從切線空間變換到物件空間; 2. $Model$矩陣:從物件空間變換到世界空間。 對於上述概念,大部分都是比較熟悉的,只有法線貼圖、切線空間和$TBN$矩陣比較陌生。下面,將分別介紹一下。 # 法線貼圖 在3D計算機圖形學中,法線貼圖是一種用於偽造凹凸光照的技術,是凹凸貼圖的一種實現。它用於新增細節,而不使用更多的多邊形。這種技術的一個常見用途是,通過從高精度多邊形或高度圖生成法線貼圖,來極大地增強低精度多邊形的外觀和細節。下圖來自Paolo Cignoni,圖中對比了兩種方式: ![法線可以使低精度模型實現高精度模型的效果](https://cdn.jsdelivr.net/gh/bzyzhang/ImgHosting//img/2020-5-17/20200523101852.png) 法線貼圖通常儲存為常規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)$,這是一種偏藍的顏色。下圖分別是物件空間和切線空間下的法線紋理。 ![物件空間和切線空間下的法線紋理](https://cdn.jsdelivr.net/gh/bzyzhang/ImgHosting//img/2020-5-17/20200523105701.jpg) 那麼,怎麼進行選擇呢?考慮一下二者的優缺點: 1. 重用性:對於物件空間的法線貼圖,它是相對於特定物件的,假如應用到其他的物件上,可能效果就不正確了;而切線空間中的法線貼圖,記錄的是相對法線資訊,所以可以把它應用到其他物件上,也能得到正確的結果。 2. 可壓縮:考慮到法線向量是單位向量,而且Z分量總是正的,可以只儲存XY方向,而推匯出Z方向。 綜上所述,我們一般選擇切線空間下的法線貼圖。 # $TBN$矩陣 在光照的計算過程中,需要用到光線方向、視線方向和法線方向等,為了得到正確的結果,這些變數必須在同一座標系下計算。參考一下本文開頭的“座標變換示意圖”。 在紋理座標系中,x和y分量與2D圖片的水平方向和垂直方向對齊,而z分量指向圖片外部的上方。如下圖所示: ![紋理座標系](https://cdn.jsdelivr.net/gh/bzyzhang/ImgHosting//img/2020-5-17/20200523140057.png) 為了正確使用貼圖中的紋理資訊,我們必須找到一種方法——**從切線座標空間變換到物件空間**。這可以通過指定切線座標系的座標軸在物件空間中的方向來達到。 對一個單獨的三角形面片來說,我們可以認為紋理貼圖覆蓋在三角形的表面上,如下圖所示: ![](https://cdn.jsdelivr.net/gh/bzyzhang/ImgHosting//img/2020-5-17/20200523141704.png) 根據上圖,可以得出:三角面片和紋理貼圖是**共面**的。那麼,根據平行四邊形法則,可以得出: $$ \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$矩陣。 # 參考 - [1] [Normal Mapping](https://learnopengl.com/Advanced-Lighting/Normal-Mapping) - [2] [Tangent Space Normal Mapping](https://docs.cryengine.com/display/SDKDOC4/Tangent+Space+Normal+Mapping) - [3] [切線空間(Tangent Space)完全解析](https://zhuanlan.zhihu.com/p/139593847) - [4] [Normal Mapping](https://en.wikipedia.org/wiki/Normal_mapping) - [5] [切線空間(Tangent Space) 的計算與應用](http://windsmoon.com/2017/11/28/切線空間-Tangent-Space-的計算與應用/) - [6] [Foundations of Game Engine Development Volume 2: Rendering](http://foundationsofgameengined