在渲染中,有很多的計算是在視角空間中完成的。這是因為光照通常是在這個空間中計算的,否則,像一些依賴視角的效果,比如高光,將會比較難以實現(xiàn)。
因此,我們需要一種方法將法線轉換到視角空間。我們可以通過下面的寫法把一個頂點轉換到視角空間:
vertexEyeSpace = gl_ModelViewMatrix * gl_Vertex;
那么,我們?yōu)槭裁床荒苤苯影堰@個應用到法線向量呢?首先,法線是一個3維向量,而ModelView矩陣是一個4x4矩陣;其次,因為法線是一個向量,所以我們只需要轉換它的方向。ModelView矩陣左上方的3x3子矩陣包含了方向的信息,那么我們?yōu)槭裁床荒苁褂眠@個子矩陣去轉換法線呢?
這可以通過下面的代碼很簡單的實現(xiàn):
normalEyeSpace = vec3(gl_ModelViewMatrix * vec4(gl_Normal,0.0));
但上面的代碼只在部分情況下可以正常工作。
讓我們來看下一個潛在的問題:

在上面的圖中,我們看到一個帶了一個法線和切線向量的三角形。在下面的圖中,顯示了如果ModelView矩陣包含了非均勻縮放會發(fā)生什么樣的偏差。

注意:如果是均勻縮放,那么法線的方向將會保持,雖然長度會受到影響,但可以簡單的通過標準化修復。
在上面的圖中,ModelView矩陣應用到了所有的向量,包括法線,結果是顯而易見的錯誤的:轉換后的法線不再與表面垂直了。
我們知道,一個向量可以表示為兩個點之間的差異。比如切線向量,它可以通過計算三角形一條邊上的兩個頂點之間的差距來獲得。如果P1和P2是定義一條邊的兩個頂點,那么我們知道:
T = P2 - P1
如果把這個向量表示為包含4個元素且最后一個元素為0的元組,那么我們可以把這個等式兩邊都乘以ModelView矩陣
T * ModelView = (P2 - P1) * ModelView
結果是
T * ModelView = P2 * ModelView - P1 * ModelView
T' = P'2 - P'1
因為P'1和P'2為轉換后的三角形的頂點,所以T'仍然與三角形的邊相切。因此,ModelView保持了切線。但它卻沒有保持法線。
使用和T向量一樣的方法,我們可以找到兩個點Q1和Q2,讓以下等式成立
N = Q2 - Q1
主要的問題是,就如上面的圖所展示的那樣,由轉換后的的點定義的向量,Q2 - Q1,不一定仍然與邊垂直。法線向量不是如切線向量那樣,定義為兩個點之間的差距,它定義為垂直于一個表面的向量。
現(xiàn)在我們知道了,我們不能在所有情況下使用ModelView來轉換法線向量。那么我們應該使用什么樣的矩陣呢?
假設G為一個3X3的矩陣,讓我們看看如何計算它來正確的轉換法線向量。
我們知道,在矩陣轉換之前,因為切線和法線向量是垂直的,所以T.N = 0。我們也知道,轉換之后,它們也一定要垂直,所以N'.T'也一定要等于0。我們把ModelView的左上方的3x3矩陣稱為M矩陣,T可以安全的乘以M(T是一個向量,所以w元素是0).
我們假設G是正確轉換法線向量N的矩陣,那么就有了下面的等式
N'.T' = (GN).(MT) = 0
把點乘轉換為叉乘,那么
(GN).(MT) = (GN)T * (MT)
注意為了叉乘向量,第一個向量的轉置是必須的。我們知道兩個矩陣相乘的轉置,等于轉置后矩陣的相乘,但順序要相反,因此:
(GN)T * (MT) = NTGTMT
我們前面已經(jīng)說過了N和T的點乘是0,所以如果
GTM = I
那么我們有
N'.T' = N.T = 0
這正是我們要的,所以我們可以根據(jù)M來計算G。
GTM = I <==> G = (M-1)T
因此M矩陣的逆矩陣的轉置,是正確轉換法線的矩陣。
前面我們說直接使用ModelView矩陣在部分情況下可以正常工作。當ModelView左上方的3x3矩陣是正交矩陣的時候,我們有:
M-1 = MT ==> G == M
這是因為正交矩陣的轉置與逆矩陣是一樣的。那么什么是正交矩陣呢?正交矩陣就是矩陣的每行/列都是單位長度,并且互相垂直的矩陣。這意味這兩個向量,被正交矩陣轉換前后,它們之間的角度是一樣的,因此轉換后的法線依然垂直于切線。此外,它還保持了向量的長度。
那么我們?nèi)绾伪WCM是正交的呢?只要我們限制我們的幾何變換僅限于旋轉和位移,比如在OpenGL程序中我們僅僅使用glRotate和glTranslate,不使用glScale,那么可以確保M是正交的。注意:gluLookAt也創(chuàng)建了一個正交矩陣。