Unity系列文章目錄
文章目錄
- Unity系列文章目錄
- 前言
- 一、什麼是變換
前言
關于矩陣,很多困擾初學者的問題都是類似的:
點和矢量都可以在圖像中畫出來,那麼矩陣可以嗎?
我聽說矩陣和線性變換、仿射變換有關,這些變換到底是什麼意思呢?
我總是聽到齊次坐标這個名詞,它是什麼意思呢?
變換和矩陣的關系又是什麼呢?或者說,給定一個變換,我如何得到它對應的矩陣呢?
在學習完本節後,希望讀者們能夠回答出這些問題。
對于第一個問題,在三維渲染中矩陣可以可視化嗎?幸運的是,答案是肯定的,這個可視化
的結果就是變換。是以,如果讀者在後面的内容中看到了一個矩陣,那麼你可以認為自己看到的
就是一個變換(當然,線上性代數中矩陣的用處不僅是用于變換,但本書的讨論範圍僅在于此)。
在遊戲的世界中,這些變換一般包含了旋轉、縮放和平移。遊戲開發人員希望給定一個點或
矢量,再給定一個變換(例如把點平移到另一個位置,把矢量的方向旋轉30°等),就可以通過某
個數學運算來求得新的點和矢量。聰明的先人們發現,可以使用矩陣來完美地解決這個問題。那
麼問題就變成了,我們如何使用矩陣來表示這些變換?
一、什麼是變換
變換(transform),指的是我們把一些資料,如點、方向矢量甚至是顔色等,通過某種方式
進行轉換的過程。在計算機圖形學領域,變換非常重要。盡管通過變換我們能夠進行的操作是有
限的,但這些操作已經足夠奠定變換在圖形學領域舉足輕重的地位了。
我們先來看一個非常常見的變換類型—線性變換(linear transform)。線性變換指的是那
些可以保留矢量加和标量乘的變換。用數學公式來表示這兩個條件就是:
f(x)+f(y)=f(x+y)
kf(x)=f(kx)
上面的式子看起來很抽象。縮放(scale)就是一種線性變換。例如,f(x)=2x,可以表示一個
大小為2 的統一縮放,即經過變換後矢量x 的模将被放大兩倍。可以發現,f(x)=2x 是滿足上面的
兩個條件的。同樣,旋轉(rotation)也是一種線性變換。對于線性變換來說,如果我們要對一個
三維的矢量進行變換,那麼僅僅使用3×3 的矩陣就可以表示所有的線性變換。
線性變換除了包括旋轉和縮放外,還包括錯切(shear)、鏡像(mirroring,也被稱為reflection)、
正交投影(orthographic projection)等,但本書着重講述旋轉和縮放變換。
但是,僅有線性變換是不夠的。我們來考慮平移變換,例如f(x)=x+(1,2,3)。這個變換就不是
一個線性變換,它既不滿足标量乘法,也不滿足矢量加法。如果我們令x=(1,1,1),那麼:
f(x)+f(x)=(4,6,8)
f(x+x)=(3,4,5)
可見,兩個運算得到的結果是不一樣的。是以,我們不能用一個3×3 的矩陣來表示一個平移
變換。這是我們不希望看到的,畢竟平移變換是非常常見的一種變換。
這樣,就有了仿射變換(affine transform)。仿射變換就是合并線性變換和平移變換的變換
類型。仿射變換可以使用一個4×4 的矩陣來表示,為此,我們需要把矢量擴充到四維空間下,這
就是齊次坐标空間(homogeneous space)。
表4.1 給出了圖形學中常見變換矩陣的名稱和它們的特性。
在下面的内容中,我們将學習其中一些基本的變換類型:旋轉,縮放和平移。對于正交投影
和透視投影,我們将在4.6.7 節中給出它們的表示方法。而對于其他變換類型,本書不再具體讨論,
讀者可以在本章的擴充閱讀中找到更多内容。
4.5.2 齊次坐标
我們知道,由于3×3 矩陣不能表示平移操作,我們就把其擴充到了4×4 的矩陣(是的,隻
要多一個次元就可以實作對平移的表示)。為此,我們還需要把原來的三維矢量轉換成四維矢量,
也就是我們所說的齊次坐标(homogeneous coordinate)(事實上齊次坐标的次元可以超過四維,
但本書中所說的齊次坐标将泛指四維齊次坐标)。我們可以發現,齊次坐标并沒有神秘的地方,它
隻是為了友善計算而使用的一種表示方式而已。
如上所說,齊次坐标是一個四維矢量。那麼,我們如何把三維矢量轉換成齊次坐标呢?對于
一個點,從三維坐标轉換成齊次坐标是把其w 分量設為1,而對于方向矢量來說,需要把其w 分
量設為0。這樣的設定會導緻,當用一個4×4 矩陣對一個點進行變換時,平移、旋轉、縮放都會
施加于該點。但是如果是用于變換一個方向矢量,平移的效果就會被忽略。我們可以從下面的内
容中了解這些差異的原因。
4.5.3 分解基礎變換矩陣
我們已經知道,可以使用一個4×4 的矩陣來表示平移、旋轉和縮放。我們把表示純平移、純
旋轉和純縮放的變換矩陣叫做基礎變換矩陣。這些矩陣具有一些共同點,我們可以把一個基礎變
換矩陣分解成4 個組成部分:
其中,左上角的矩陣M3×3 用于表示旋轉和縮放,t3×1 用于表示平移,01×3 是零矩陣,即01×3=[0
0 0],右下角的元素就是标量1。
接下來,我們來具體學習如何用這樣一個4×4 的矩陣來表示平移、旋轉和縮放。
4.5.4 平移矩陣
我們可以使用矩陣乘法來表示對一個點進行平移變換:
從結果來看我們可以很容易看出為什麼這個矩陣有平移的效果:點的x、y、z 分量分别增加
了一個位置偏移。在3D 中的可視化效果是,把點(x,y,z)在空間中平移了(tx,ty,tz)個機關。
有趣的是,如果我們對一個方向矢量進行平移變換,結果如下:
可以發現,平移變換不會對方向矢量産生任何影響。這點很容易了解,我們在學習矢量的時
候就說過了,矢量沒有位置屬性,也就是說它可以位于空間中的任意一點,是以對位置的改變(即
平移)不應該對方向矢量産生影響。
現在,讀者應該明白當給定一個平移操作時如何建構一個平移矩陣:基礎變換矩陣中的t3×1
矢量對應了平移矢量,左上角的矩陣M3×3 為機關矩陣I3。
平移矩陣的逆矩陣就是反向平移得到的矩陣,即
可以看出,平移矩陣并不是一個正交矩陣。
4.5.5 縮放矩陣
我們可以對一個模型沿空間的x 軸、y 軸和z 軸進行縮放。同樣,我們可以使用矩陣乘法來
表示一個縮放變換:
對方向矢量可以使用同樣的矩陣進行縮放:
如果縮放系數kx=ky=kz,我們把這樣的縮放稱為統一縮放(uniform scale),否則稱為非統一
縮放(nonuniform scale)。從外觀上看,統一縮放是擴大整個模型,而非統一縮放會拉伸或擠壓
模型。更重要的是,統一縮放不會改變角度和比例資訊,而非統一縮放會改變與模型相關的角度
和比例。例如在對法線進行變換時,如果存在非統一縮放,直接使用用于變換頂點的變換矩陣的
話,就會得到錯誤的結果。正确的變換方法可參見4.7 節。
縮放矩陣的逆矩陣是使用原縮放系數的倒數來對點或方向矢量進行縮放,即
縮放矩陣一般不是正交矩陣。
上面的矩陣隻适用于沿坐标軸方向進行縮放。如果我們希望在任意方向上進行縮放,就需要
使用一個複合變換。其中一種方法的主要思想就是,先将縮放軸變換成标準坐标軸,然後進行沿
坐标軸的縮放,再使用逆變換得到原來的縮放軸朝向。
4.5.6 旋轉矩陣
旋轉是三種常見的變換矩陣中最複雜的一種。我們知道,旋轉操作需要指定一個旋轉軸,這
個旋轉軸不一定是空間中的坐标軸,但本節所講的旋轉就是指繞着空間中的x 軸、y 軸或z 軸進
行旋轉。
如果我們需要把點繞着x 軸旋轉度,可以使用下面的矩陣:
最後,是繞z 軸的旋轉:
旋轉矩陣的逆矩陣是旋轉相反角度得到的變換矩陣。旋轉矩陣是正交矩陣,而且多個旋轉矩
陣之間的串聯同樣是正交的。
4.5.7 複合變換
我們可以把平移、旋轉和縮放組合起來,來形成一個複雜的變換過程。例如,可以對一個模
型先進行大小為(2, 2, 2)的縮放,再繞y 軸旋轉30°,最後向z 軸平移4 個機關。複合變換可以通
過矩陣的串聯來實作。上面的變換過程可以使用下面的公式來計算:
pnew =MtranslationMrotationMscalepold
由于上面我們使用的是列矩陣,是以閱讀順序是從右到左,即先進行縮放變換,再進行旋轉
變換,最後進行平移變換。需要注意的是,變換的結果是依賴于變換順序的,由于矩陣乘法不滿
足交換律,是以矩陣的乘法順序很重要。也就是說,不同的變換順序得到的結果可能是不一樣的。
想象一下,如果讓讀者向前一步然後左轉,記住此時的位置。然後回到原位,這次先左轉再向前
走一步,得到的位置和上一次是不一樣的。究其本質,是因為矩陣的乘法不滿足交換律,是以不
同的乘法順序得到的結果是不一樣的。
在絕大多數情況下,我們約定變換的順序就是先縮放,再旋轉,最後平移。
讀者:為什麼要約定這樣的順序,而不是其他順序呢?
我們:因為這樣的變換順序是我們需要的。想象我們對奶牛妞妞進行一個複合變換。如
果我們按先平移、再縮放的順序進行變換,假設初始情況下妞妞位于原點,我們先按(0, 0, 5)
平移它,現在它距離原點5 個機關。然後再将它放大2 倍,這樣所有的坐标都變成了原來的2
倍,而這意味着妞妞現在的位置是(0, 0, 10),這不是我們希望的。正确的做法是,先縮放再平
移。也就是說,我們先在原點對妞妞進行2 倍的縮放,再進行平移,這樣妞妞的大小正确了,
位置也正确了。
為了從數學公式上了解變換順序的本質,我們可以對比不同變換順序産生的變換矩陣的表達
式。如果我們隻考慮對y 軸的旋轉的話,按先縮放、再旋轉、最後平移這樣的順序組合3 種變換
得到的變換矩陣是:
而如果我們使用了其他變換順序,例如先平移,再縮放,最後旋轉,那麼得到的變換矩陣是:
從兩個結果可以看出,得到的變換矩陣是不一樣的。
除了需要注意不同類型的變換順序外,我們有時還需要小心旋轉的變換順序。在4.5.6 節中,
我們給出了分别繞x 軸、y 軸和z 軸旋轉的變換矩陣。一個問題是,如果我們需要同時繞着3 個
軸進行旋轉,是先繞x 軸、再繞y 軸最後繞z 軸旋轉還是按其他的旋轉順序呢?
當我們直接給出(
x,
y,
z)這樣的旋轉角度時,需要定義一個旋轉順序。在Unity 中,這個旋轉
順序是zxy,這在旋轉相關的API 文檔中都有說明。這意味着,當給定(
x,
y,
z)這樣的旋轉角度時,
得到的組合旋轉變換矩陣是: