在使用 OpenGL 的應用程序中,當我們指定了模型的頂點后,頂點依次會變換到不同的 OpenGL 空間中,最后才會被顯示到屏幕上。在變換的過程中,通過使用矩陣,我們更高效地來完成這些變換工作。
本篇博客主要介紹的是矩陣以及矩陣在空間幾何中的應用。關於 OpenGL 空間,我把它們安排在了另一篇博客OpenGL 的空間變換(下):空間變換中來介紹。
本篇博客主要分為兩部分:矩陣基礎和矩陣在空間幾何中的應用。對熟悉矩陣的讀者來說,可以跳過矩陣基礎直接閱讀第二部分。
矩陣基礎
數學上,一個 mxn 的矩陣是一個由 m 行 n 列元素排列成的矩形陣列。矩陣里的元素可以是數字、符號或者數學式。例如下面是一個由 6 個數字構成的 2 行 3 列矩陣:
對於行(列)數為 1 的矩陣,我們稱為行(列)向量。注意,這里的向量與空間幾何中的向量並不是同一個概念。為了更好地區分兩者,接下來只要描述的是矩陣的向量,本文都會以行(列)向量來表示。否則,描述的就是空間幾何中的向量。
矩陣的基本運算
矩陣最基本的運算包括加(減)法、數乘和轉置運算。
加(減)法:mxn 矩陣 A 和 B 的和(差):A±B 為一個 mxn 矩陣,其中每個元素是 A 和 B 相應元素的和(差)。
根據加(減)法我們還可以推導出,矩陣的取負操作實則是對每個元素進行取負操作。
數乘:標量 c 與矩陣 A 的數乘:c·A 的每個元素是 A 的相應元素與 c 的乘積。
轉置:mxn 矩陣 A 的轉置是一個 nxm 的矩陣,記為 A‘,其中的第 i 行第 j 列元素是原矩陣 A 的第 j 行第 i 列元素。
矩陣乘法
與矩陣的數乘不同,矩陣的乘法是指兩個矩陣相乘,並且當且僅當在第一個矩陣 A 的列數與另一個矩陣 B 的行數相等時才有定義。假設 A 為 mxn 矩陣,而 B 是 nxp 矩陣,那么 A 乘以 B 的乘積 A·B 則是個 m x p 的矩陣。它的任意元素為:
其中, 其中1 ≤ i ≤ m, 1 ≤ j ≤ p。
通過下面這張示意圖,我們可以更好地理解 A·B 的過程:
矩陣的乘法滿足結合律和對矩陣加法的分配律(左分配律和右分配律):
結合律:(A·B)·C = A·(B·C)
左分配律:(A+B)·C = A·C+B·C
右分配律:C·(A+B) = C·A+C·B
矩陣的乘法與數乘運算之間也滿足類似結合律的規律:c·(A·B) = (c·A)·B = A·(c·B);與轉置之間則滿足倒置的分配律:(A·B)’ = B’·A’。
值得注意的是,矩陣乘法不滿足交換律。一般來說,矩陣 A 及 B 的乘積 A·B 存在,但 B·A 不一定存在。即使存在,大多數時候 A·B 也不等於 B·A。
特殊方陣
我們把行數與列數相等的矩陣稱為方塊矩陣,簡稱方陣或 n 維矩陣。對於一個方陣來說,其主對角線是一條由左上角至右下角的對角線。而另一條對角線則稱作反對角線或次對角線。下面來介紹兩種特殊的方陣(也叫特殊矩陣):
單位矩陣
如果一個 n 維矩陣除了主對角線的一組元素為 1.0 之外,其他元素均為 0.0。那么稱該矩陣為單位矩陣。例如,下面是一個 4 維單位矩陣:
將一個矩陣乘以(無論是左乘還是右乘)一個單位矩陣,就相當於該矩陣乘以 1(這里指的是數乘),不會發生任何變化。
逆矩陣
設有兩個 n 維矩陣 A、B,如果 A 乘以(無論左乘還是右乘)B 得到的是一個單位矩陣,那么 A 與 B 就互稱為逆矩陣。一個矩陣的逆矩陣如果存在的話,那么就是唯一的。用公式來表示:A·B = B·A = 單位矩陣。
當然,除了上述的兩種特殊矩陣之外,還有許多其他的特殊矩陣。但對於本文接下來的內容,我們只需要知道單位矩陣和逆矩陣就足夠了。
矩陣在空間幾何中的應用
矩陣是高等代數中的常見工具,也常見於統計分析等應用數學學科中。在物理學中,矩陣於電路學、力學、光學和量子物理都有應用。不僅如此,矩陣在空間幾何中也有很重要的應用,它可以用來描述空間幾何中的對象(坐標系、點位置、向量)和變換,這是接下來將詳細介紹的內容。
在學習矩陣在空間幾何中的應用之前,我們先來了解一個空間幾何的重要概念——齊次坐標。
齊次坐標
一般來說,我們都是用笛卡爾坐標來描述幾何空間的。在三維空間中,笛卡爾坐標系是由三條兩兩垂直的坐標軸構成的,三條坐標軸相交於原點:
我們可以使用 (x, y, z) 的方式來表示三維空間中一個點的位置。其中 x、y、z 分別對應點在 x-軸、y-軸、z-軸上的坐標。除此之外,這種方式也可以用來表示三維空間中的一個向量。如果用來表示向量的話,那么 x、y、z 則分別表示向量在 x-軸、y-軸、z-軸方向上的分量。
笛卡爾坐標通過上述方式用 N 個分量來描述 N 維空間。這種方式本身存在一個缺陷,那就是它無法用來表示一個無窮遠的點,比如 (∞, ∞, ∞)。針對這個問題,數學家們提出了齊次坐標。
齊次坐標在笛卡爾坐標的基礎上增加了一個新的分量 w,用 N+1 個分量來描述 N 維空間。對於笛卡爾坐標系中的任意一個點 (x, y, z),都可表示為一族齊次坐標 (w·x, w·y, w·z, w) ,其中 w 不等於 0。
所以,對於笛卡爾坐標系中的點的位置 (1, 2, 1), 我們可以用齊次坐標表示為 (1, 2, 1, 1) 或 (2, 4, 2, 2) 等等。如果想要將一個齊次坐標轉換為笛卡爾坐標,只需將齊次坐標的每一分量都除以 w,使得齊次坐標的 w 分量將變為 1。我們把這個過程稱為齊次坐標標准化。
如果點 (1, 2, 1) 沿着它與坐標系原點所在的直線移動到無限遠處,那么它的位置用齊次坐標來表示則應該是 (1, 2, 1, 0)。(本質上是一個向量)
對於齊次坐標來說,點位置和向量采用的表示方法是不同的:
點位置:(x, y, z, w),其中 w 不為 0
向量:(x, y, z, 0)
相比笛卡爾坐標(點位置和向量都采用同樣的表示方法),使用齊次坐標來表示可以更好地區分點位置和向量。
齊次坐標的好處其實還不止這些,這個我們后面會再講到。
用矩陣來表示三維空間中的對象
在三維空間中,一個點的位置或一個向量可以用一個四分量的齊次坐標來表示。對應地,我們可以用一個 4x1 的矩陣(列向量)來表示該齊次坐標。而對於坐標系,它是由一個原點和三條坐標軸的方向來定義。即它可以由一個點位置和三個向量來確定。我們可以用下面的 4 維矩陣來表示一個三維空間的坐標系:
其中,前三個列向量分別表示坐標系 x-軸、y-軸、z-軸;第四個列向量表示坐標系的原點。
用矩陣來表示三維空間中的變換
在對一個幾何對象進行平移、旋轉或縮放操作時,對象的幾何狀態(位置、方向)會發生相應的變化。變化前后的狀態我們可以用矩陣來表示。不僅如此,對於這些操作(平移、旋轉或縮放)本身,我們也可以用矩陣來表示,這種矩陣稱為變換矩陣。變換矩陣描述的是操作本身,與被操作的幾何對象無關。
將表示幾何對象的矩陣乘以變換矩陣,所得的結果就是進行相應操作后的幾何對象的矩陣表示。
平移變換
平移矩陣僅僅是將幾何對象沿着其父坐標系的 3 個坐標軸中的一個或多個進行平移:
其中 t.x、t.y 和 t.z 分別表示在 x-軸、y-軸和 z-軸上的平移。
點位置的平移變換:
向量的平移變換:
子坐標系的平移變換:
從變換后的結果矩陣可以看出,點位置會隨着平移而發生相應的變化;而向量則始終保持不變。同理,子坐標系的原點也會隨着平移發生相應的變化,而其坐標軸則始終保持不變。
旋轉變換
對於三維空間中的旋轉,我們可以有三種方式來描述,分別是:旋轉矩陣、歐拉角,還有四元數。
旋轉矩陣
旋轉矩陣將一個幾何對象圍繞其父坐標系的 3 個坐標軸中的一個或多個進行旋轉。不同坐標軸上的旋轉矩陣是不一樣的:
其中,s 表示 sin,c 表示 cos。
按照順序將以上三個基本旋轉矩陣乘在一起,可以得出一個復合旋轉矩陣。該矩陣可以一次性完成 x、y、z 三條軸上的所有旋轉變換:
該矩陣表示先圍繞 x-軸旋轉 φ ,緊接着圍繞 y-軸旋轉 θ,最后再圍繞 z-軸旋轉 ψ,這里的坐標軸指的是父坐標系的坐標軸。
歐拉角
歐拉角是一個包含三個角度的的集合,我們可以將其表示為 (α, β, γ)。每個角度分別表示圍繞對應坐標軸的旋轉角度。這里的坐標軸指的是被旋轉模型自身的坐標系,而不是其父坐標系。對於一個給定的歐拉角 (α, β, γ),我們會將模型先圍繞 α 所對應的坐標軸旋轉 α,然后基於旋轉后模型自身的坐標系,再圍繞 β 所對應的坐標軸旋轉 β,最后基於旋轉后模型自身的坐標系,再圍繞 γ 所對應的坐標軸旋轉 γ。
值得注意的是,歐拉角所包含的三個角度 α、β、γ 與坐標軸的對應關系不一定是 XYZ。只要連續的兩次旋轉不是基於(圍繞)同一條坐標軸即可,如 ZYX 甚至是 ZXZ 都可以,但是像 ZZX 則不行。歐拉角總共有 12 種對應方式。
按照對應方式的順序將基本旋轉矩陣乘在一起,就可以得出歐拉角的矩陣表示。假設一個歐拉角的對應方式為 ZXZ,則其矩陣表示為 Rz(α)·Ry(β)·Rz(γ)。
細心的同學可能已經發現,在計算復合旋轉矩陣時,最先進行的旋轉其矩陣在最右側,說明該矩陣最先與點的齊次坐標相乘,旋轉矩陣按照旋轉的次序從右向左排列。而在歐拉角中,最先進行的旋轉其矩陣在最左側。這是因為對於旋轉矩陣來說,我們始終是以模型的父坐標系為參照來的,父坐標系不會因為模型的旋轉而發生變化。而對於歐拉角來說,每一次的旋轉都是基於模型自身的坐標系,而不是其父坐標系。當模型被旋轉時,其自身的坐標系也會跟着一起旋轉。
歐拉角有一個弊端——萬向節死鎖。因為萬向節死鎖的存在,所以在使用歐拉角時無法實現球面平滑插值。有興趣了解的讀者可以觀看此視頻 歐拉旋轉—萬向節鎖—在線播放—優酷網,視頻高清在線觀看。
比起旋轉矩陣,歐拉角具有節省存儲空間(因為它只需用三個值)和直觀的優點,很多游戲引擎都是使用歐拉角來表示旋轉的。雖然歐拉角存在萬向節死鎖的弊端,但是我們可以通過添加一些制約來規避它。除了這兩種表示方式,我們還可以另外一種更好的方式來表示三維空間中的旋轉,那就是四元數。
四元數
我們還可以通過指定一條任意方向的旋轉軸 A(用標准化的向量表示)以及圍繞旋轉軸 A 旋轉的角度 θ,來描述三維空間中的任意旋轉。這種表示方法稱為軸-角。我們可以通過標准旋轉矩陣的復合運用來表示軸-角:
Rx(-p)·Ry(-q)·Rz(θ)·Ry(q)·Rx(p)
原理是通過兩次旋轉使得旋轉軸 A 與 z-軸重合,然后圍繞 z-軸旋轉 θ。最后再通過兩次旋轉將旋轉軸 A 擺回到原來的角度。和計算復合旋轉矩陣時一樣,最先進行的旋轉,其矩陣在最右側:
首先,圍繞 x-軸旋轉角度 p,使得旋轉軸 A 在 x-軸 與 z-軸所在的平面上
然后,圍繞 y-軸旋轉角度 q,使得旋轉軸 A 與 z-軸重合
接着,圍繞 z-軸旋轉角度 θ(實際上也是圍繞旋轉軸 A 旋轉 θ)
之后,圍繞 y-軸轉回角度 q(即 -q)
最后,圍繞 x-軸轉回角度 p(即 -p)
其中,x-軸、y-軸和 z-軸指的是父坐標系的坐標軸,p 和 q 的值需要通過旋轉軸 A 計算出來。
不過,上述的表示方式還是比較復雜。我們可以使用一種更加簡潔的方式來表示軸-角——四元數。它可以表示為 (a, b, c, d),我們定義其與旋轉軸 A、旋轉角度 θ 的關系如下:
a = sin(θ/2) * A.x
b = sin(θ/2) * A.y
c = sin(θ/2) * A.z
d = cos(θ/2)
其中,A.x、A.y、A.z 分別對應旋轉軸 A 的三個分量。
對於四元數所表示的旋轉,其對應的旋轉矩陣如下(這里省去推導過程直接給出結論):
用四元數來表示旋轉需要的存儲空間很小(只需用四個值)。而且相比起歐拉角來說,四元數不存在萬向節死鎖的問題。所以,我們可以使用被稱為球面線性插值的方法對四元數進行插值運算,從而解決了平滑旋轉的插值問題。
假設幾何對象(點、向量或子坐標系)圍繞其父坐標系的 y-軸旋轉了 θ,則對應的矩陣變換如下:
點位置的旋轉變換
向量的旋轉變換
坐標系的旋轉變換
從變換后的結果矩陣可以看出,無論點位置、還是向量都會隨着旋轉而發生相應的變化;同樣,子坐標系的原點和坐標軸也會隨着旋轉而發生相應的變化。
縮放變換
縮放矩陣將幾何對象沿着其父坐標系的 3 個坐標軸方向,按照指定比例進行放大或縮小:
這里 s.x、s.y 和 s.z 分別代表在 x、y 和 z 軸方向上的縮放比。
點位置的縮放變換
向量的縮放變換
坐標系的縮放變換
從變換后的結果矩陣可以看出,無論點位置、還是向量都會隨着縮放而發生相應的變化;同理,子坐標系的原點和坐標軸也會隨着縮放而發生相應的變化。
綜合變換
實際上,我們很少會只進行這三種變換中的一種。反而,總會想要同時進行這些變換。我們可以將這三種類型的變換矩陣乘在一起來得到一個復合的幾何變換矩陣。將一個幾何對象乘以該復合矩陣,可以對該對象同時進行所有相應的變換。
另外,對於點位置(或坐標系的原點)而言,如果其 w 分量不為 1,則應先將點位置(或坐標系的原點)進行齊次坐標標准化。然后,再使用其標准化的結果來進行對應的幾何變換。
本篇博客介紹了矩陣的基本知識,以及如何用矩陣來表示幾何對象和三種最基本的幾何變換。除此之外,矩陣還可以用來表示一些其他的變換。這部分的內容會在OpenGL 的空間變換(下):空間變換中進行詳細的介紹。