2018年10月20日 星期六

Unity上常用的基本矩陣計算 (Matrix)

常見的Matrix種類

Identity Matrix單位矩陣,簡稱I):左上到右下的對角線是1,其他都是0,MI = IM = M

Transposed Matrix轉置矩陣,用上標T表示):把Matrix的Row變成Column,Column變成Row地反轉,(AB)^T = (B^T)(A^T)

Inverse Matrix反矩陣,用上標-1表示):M * M^-1 = M^-1 * M = I

Orthogonal Matrix正交矩陣):M * M^T = M^T * M = I ,即 M^T = M^-1

判斷一個matrix是否orthogonal,一般教科書都是直接用其定義來判斷,即計算Matrix乘以自己的transposed matrix是否等於I。但是在寫遊戲程式的時候,計算matrix相乘其實會用很多時間,因此我們想用一個更快的方法來做判斷:

把orthogonal matrix拆成每個每個column來檢查的話,會發現它們都是unit vector(因為這些vector和自己的dot product是1),而和matrix中除自己外的其他column是垂直的(因為dot product是0)。因此我們在用vector們建構一個matrix的時候,已經可以透過vector本身的特質來判斷建構出來的matrix是否orthogonal。

(參考:How to identify an orthogonal(orthonormal matrix)?

這一特點在做座標轉換的時候非常有用,因為我們一般都是使用正交座標(即xyz axis互相垂直),而由這三個axis的vector形成的matrix就是orthogonal的。如果我們知道一個matrix是orthogonal的話,那麼當我們想要計算它的inverse時,就可以直接用它的transpose,可以減少很多繁複的計算。

另外,多於一個orthogonal matrix互相相乘的結果也是orthogonal。

(參考:Why is the matrix product of 2 orthogonal matrices also an orthogonal matrix?

Vector和Matrix相乘的先後次序

兩個Matrix如果相乘的先後次序不一樣,結果也會不同(即AB =/= BA)。如果我們需要對一個Vector進行transformation,那麼究竟是要用row Vector乘以matrix,還是用matrix乘以column vector呢?在Unity中一般是matrix乘column vector,不過如果涉及多個matrix相乘時我們可以做一點optimization讓相乘更快:

例如我們要把一個vector v做三次transformation:
CBAv

如果從左至右開始計算(即((CB)A)v),那麼每次乘完後的matrix都會很大(不止一個column),但如果從右邊開始計算(即(C(B(Av))),那麼每次乘完的matrix都只有一個column。

你也可以把ABC做transpose,這樣就能把v放在左邊了:
v(A^T)(B^T)(C^T)

到底是橫列直行還是橫行直列?

經常會看見「列矩陣」和「橫矩陣」這兩個詞,但「列矩陣」到底是指大小1 x n(打橫n個數字)還是 n x 1(打直n個數字)的matrix?

我發現不同的教材和討論之間有分歧,似乎在不同地區或者社群中對於列是指橫還是直都有不同的定義。我甚至見過在同一份教材中列矩陣有時是指橫、有時是指直的情況。在英文中就不會有這個問題,Row從來都是橫的,Column永遠是直的,Row Matrix就是橫著三個數字(1x3)。

大概只能根據上下文中的例子來推斷到底該文章是使用橫列直行抑或橫行直列了。

Matrix Transformation (矩陣轉換)的種類

在遊戲中我們經常要對vector進行轉換(transform),例如移動或者縮放,又或者是轉到另外一個世界,最容易做轉換的方式就是用一個matrix來乘以這個vector(也就是單靠這個matrix已經能表達出我們想做的轉換),這種方式能做到大部分我們想做的效果。

Linear Transformation(線性轉換)

線性轉換的定義是符合下面兩個特性:
Vector加:  f(x)+f(y)=f(x+y)
Scalar乘:  kf(x) = f(kx)

線性轉換只需要和vector同一dimension的matrix就足以表達(2D遊戲是2x2,3D遊戲是3x3)。

以下列出常見的線性轉換。下列的matrix都是以origin為中心進行轉換,如果你是需要以遊戲物件的中心進行轉換的話,就需要先把vector本身轉成物件的local position再進行轉換,然後再轉回world position。

Scaling (縮放)

matrix的左上到右下的對角線上的數字為縮放比例k
[ kx 0 0
  0 ky 0
  0 0 kz]

Rotation(旋轉)

以x axis為中心逆時針旋轉:
[1    0     0
 0  cosθ  -sinθ
 0  sinθ  cosθ]

以y axis為中心逆時針旋轉:
[cosθ   0  sinθ
   0      1     0
 -sinθ  0  cosθ]

以z axis為中心逆時針旋轉:(一般來說2D遊戲中的旋轉都是用這個)
[cosθ    -sinθ    0
 sinθ      cosθ    0
   0           0       1]

要點:

  • Rotation matrix都是orthogonal的。
  • 這些matrix的inverse就是向順時針方向旋轉同樣的角度。
  • 如果需要進行多於一個axis的旋轉,要留意Unity定義的預設旋轉順序是MzMxMy

Shear(錯切,例如把一個長方形扭成平行四邊形)


Mirroring/Reflection(鏡像)


Orthographic projection(正交投影)


Translation Transformation(平移轉換)

簡單來說translate就是把一個物件從一個位置移動到另外一個位置。

以公式表達的話一般是這樣:
f(x) = x + c
因為有constant的存在,所以並不符合線性轉換的定義。

想用一個matrix來表示translation的話,需要比原本的vector多一個dimension(2D遊戲需要3x3,3D遊戲需要4x4)。

參考:Why do 2D transformations need 3x3 matrices?

Affine Transformation(仿射轉換)

Affine其實就是包括了線性和平移的轉換,即是一個matrix就能同時表示兩種轉換(當然也可以只表示一種)。
既然包含了平移轉換,那就需要比vector多一個dimension的matrix,然後在相乘之前,把vector也擴充到相同的dimension(我們稱為homogeneous coordinate,齊次座標)。我們把多出來的那個dimension的數叫做w,即整個vector是(x,y,z,w)。

首先要看看該vector是哪一種vector:
位置(點):把w設為1(代表以該點為中心進行轉換)
方向:把w設為0(代表以origin為中心進行轉換)

而matrix的結構如下:
[ M 3x3   t 3x1
   0 1x3    1      ]
M代表線性轉換,t則代表平移(試試乘一下就能發現t單純是加在位置vector上,而對方向vector就不會有任何影響)
如果不要做線性轉換,把M設成Identity Matrix即可
如果不要做平移轉換,把t設成0即可(又或者只做3x3的matrix multiplication)

在此結構下,可以用一個matrix便能表達數次複合的轉換。一般來說按「縮放→旋轉→平移」的次序進行轉換最不容易出錯。如果不按這個順序的話可能會讓後面的轉換影響到前面的轉換(例如先平移再縮放的話,會導致原本平移的距離也一起被縮放,容易讓人混淆並質疑是不是真的要這樣做)。(參考:What is the correct order to multiply scale, rotation and translation matrices for a proper world matrix?

例如想對一個vector v先縮放再旋轉再平移,那麼要計算轉換後的vector v'的公式就是:
v' = (Mtranslation)(Mrotation)(Mscaling)v

因為Unity是使用column vector,要放在最右邊,所以公式也要從右開始寫。
而把 (Mtranslation)(Mrotation)(Mscaling)這三個matrix相乘便能得出一個結合了三次轉換的matrix。




沒有留言:

張貼留言