Android OpenGL ES 2.0(九)---法線矩陣

本文從下面鏈接翻譯過來:

第一篇 Normal Transformation
第二篇 The Normal Matrix

關於法線矩陣的計算方式個人覺得上面兩篇文章講得比較好,下面依次對兩篇文章進行翻譯,先來翻譯第一篇文章。

第一篇 法線變換

在計算光照時,關於入射角的餘弦值計算有一定的要求。它涉及到兩個向量,光照的方向向量以及頂點的法線向量,並且這兩個向量都是單位向量。

我們來看這樣一個例子,2D圓。 我們可以將非均勻比例縮放(不同軸上的縮放比例不一樣)應用於此圓上的頂點座標,將其轉換爲橢圓:

圖1.圓形縮放

這一切看起來都很好,但考慮到法線轉換的情況:

中間橢圓的頂點法線計算方式如果你使用跟圓頂點變換相同的矩陣來計算的話,橢圓的法線可能會是單位長度的向量,但是它不會再反射出橢圓的形狀。右邊橢圓反映出實際形狀的法線。

事實證明,你真正想做的是用與位置相同的旋轉矩陣來轉換法線,但是縮放需要使用縮放矩陣的逆矩陣來轉換法線。也就是說,沿X軸的0.5的比例將使該軸上的位置縮小一半。對於頂點的法線,您希望將法線的X值加倍,然後對結果法向量單位化處理。

如果你的應用程序只有一個簡單的縮放矩陣,這很容易。但是,如果你的應用程序由多個連續旋轉,縮放和其他操作組成的複雜的矩陣並不那麼容易計算。

我們可以使用矩陣的逆轉置來計算法線的變換矩陣。這意味着我們首先計算逆矩陣,然後計算該逆矩陣的轉置。矩陣的轉置就是沿對角線翻轉的相同矩陣。原始矩陣的列是其轉置後的行。

圖2.矩陣的轉置

那麼這個逆轉置矩陣如何幫助我們呢?

記住:我們想要的是複合矩陣中縮放矩陣的逆矩陣而不影響旋轉矩陣部分。給定一個僅由旋轉和縮放變換組成的3x3矩陣M,我們可以表達這個矩陣,如下所示:

這個矩陣表達了一個複合矩陣的變換,先進行一個旋轉變換R1,然後在進行縮放變換S,最後在進行旋轉變換R2。無論使用多少個縮放和旋轉矩陣來構建M,我們都可以做到這一點。

回想一下,我們想要的是縮放矩陣的逆矩陣。如果我們在原始縮放矩陣的縮放爲0.4,則我們希望在逆矩陣縮放爲2.5。縮放矩陣的逆矩陣其實是該矩陣每個軸上的縮放比例的倒數。 因此,我們可以表達我們真正想要的法線變換矩陣:

關於旋轉矩陣的一個有趣的事實:任何旋轉矩陣的逆是它的轉置。 此外,如果對一個矩陣進行連續兩次逆變換的結果會得到原始矩陣。因此,您可以將任何旋轉矩陣表示爲其自身的反轉置,而不會影響矩陣。 由於逆是它的轉置,並且在矩陣上進行兩次轉置不會改變其值,因此旋轉矩陣的逆轉置是無操作。

此外,由於縮放矩陣的值是在對角線上的,因此對縮放矩陣進行轉置操作不起作用,還是縮放矩陣本身。有了這兩個事實,我們可以重新表達我們想要計算的矩陣:

使用矩陣代數的知識,我們可以將轉置因子排除在外,但這樣做需要顛倒矩陣乘法的順序:

類似地,我們可以分解逆運算,但這需要再次顛倒順序:

因此,逆轉置矩陣解決了我們的問題。GLSL有內置的功能,可以爲我們做這些操作。 雖然你能避免在GLSL中進行反轉置,但強烈建議你這樣做; 這不是一個微不足道的計算。

還有一點需要注意。只有在使用非均勻的縮放比例時才需要進行反轉置。 實際上,使用這種縮放比例因子實際上很少見。均勻的縮放比例更常用。 因此,即使您沒有使用反轉置,您仍然需要在使用模型視圖矩陣到相機矩陣進行變換後對法線進行標準化。

 

第二篇 法線矩陣

許多計算都是在眼睛空間完成的。這與通常在該空間中執行光照的計算有關,相反的由於需要依賴於眼睛位置,否則鏡面光很難實現。

因此,我們需要一種方法將法線轉換爲眼睛空間。 要將頂點轉換爲眼睛空間,我們可以編寫:

vertexEyeSpace = gl_ModelViewMatrix * gl_Vertex;

那麼爲什麼我們不能用法線量做同樣的事情呢? 首先法線是3個浮點數的向量,模型視圖矩陣是4×4。 其次,由於法線是一個向量,我們只想轉換它的方向。包含方向的模型視圖矩陣的區域是左上3×3子矩陣。 那麼爲什麼不通過這個子矩陣乘以法線呢?

使用以下代碼可以輕鬆實現這一點:

normalEyeSpace = vec3(gl_ModelViewMatrix * vec4(gl_Normal,0.0));

那麼,GLSL的內置變量gl_NormalMatrix只是簡化代碼編寫嗎? 不,不是真的。 上面的代碼行在某些情況下會起作用,但不是全部。

讓我們看一下潛在的問題:

在上圖中,我們看到一個三角形,帶有法線和切線向量。下圖顯示了當modelview矩陣包含非均勻比例的縮放矩陣時會發生什麼。

注意:如果縮放矩陣在所有軸上的縮放是均勻的,那麼法線的方向將不會改變,只是法線的長度會受到影響,但這可以通過將法線單位化來進行修正。

在上圖中,Modelview矩陣應用於所有頂點以及法線,結果顯然是錯誤的,變換後的法線不再垂直於表面。

我們知道向量可以表示爲兩點之間的差。 考慮切線向量,可以將其計算爲三角形邊緣的兩個頂點之間的差。 如果P1和P2是定義邊的頂點,我們知道:

T = P2 - P1

考慮到向量可以寫成四元組,最後一個設置爲零,我們可以將等式的兩邊乘以Modelview矩陣

T * Modelview = (P2-P1)* Modelview

這導致了:

T * Modelview = P2 * Modelview - P1 * Modelview
T' = P2' - P1'

由於P1'和P2'是變換之後三角形的頂點,因此T'保持與三角形邊緣相切。 因此,Modelview保留切線,但它不保留法線。

考慮到用於向量T的相同方法,我們可以找到兩個點Q1和Q2

N = Q2 - Q1 

主要問題是通過轉換之後點Q2' -  Q1'定義的向量不一定保持正確的法線,如上圖所示。

所以現在我們知道我們不能在所有情況下應用Modelview來轉換法向量。那麼問題是,我們應該應用什麼矩陣?

考慮一個3×3矩陣G,讓我們看看如何計算這個矩陣來正確地轉換法向量。

我們知道,根據向量垂直的定義,在矩陣變換之前T.N = 0,那麼在變換之後N'.T'必須也保持等於零,因爲它們必須保持彼此垂直。 T可以通過乘以模型視圖的左上3×3子矩陣得到(T是向量,因此w分量爲零),讓我們稱之爲子矩陣M.

假設矩陣G是用於變換法向量的正確矩陣。因此,以下等式:

N' . T' = (GN) . (MT) = 0

向量的點積可以轉換爲矩陣的乘積,因此:

(GN).(MT) = (GN)^T * (MT)

注意,必須考慮第一個向量的轉置,因爲這需要乘以向量。 根據矩陣轉置乘法的相關知識,上面等式與可以轉爲下面等式

(GN)^T  (MT) = N^TG^TMT

因爲

N.T = N^T * T 

所以結合前面等式可以推到出

G^TM = I

其中I是單位矩陣,所以矩陣G最終結果如下:

G^TM = I -> G = (M^{-1})^T

因此,轉換法線的正確矩陣是M矩陣的逆的轉置。 OpenGL在gl_NormalMatrix中爲我們計算。

在本節的開頭,說道有時候使用Modelview矩陣在某些情況下會起作用。 每當Modelview的3×3左上子矩陣正交時,我們都有:

M^{-1} = M^T -> G = M 

這是因爲對於正交矩陣,轉置與逆矩陣相同。 什麼是正交矩陣? 正交矩陣是所有列/行是單位長度,並且是相互垂直的。 這意味着當兩個向量乘以這樣的矩陣時,在通過正交矩陣變換之後它們之間的角度與該變換之前的角度相同。 簡單地說,變換保留了向量之間的角度關係,因此變換後的法線保持垂直於切線! 此外,它還保留了向量的長度。

那麼我們何時可以確定M是正交的? 當我們將幾何操作限制爲旋轉和平移時,即在OpenGL應用程序中,我們只使用glRotate和glTranslate而不是glScale。 這些操作保證M是正交的。 注意:gluLookAt還會創建一個正交矩陣。

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