我們經常會對物體做一些變換,比如放大縮小,移動位置。其中例如原地旋轉,尺寸縮放,甚至歪斜這類不改變物體原點的變換,被叫做線性變換。線性變換都可以用矩陣乘上原式數據來計算。
比如在平面上,我們要把一組數據 ( x , y ) (x, y) ( x , y )   放大 3/2 倍,我們可以這樣表示:
[ 3 / 2 0 0 3 / 2 ] [ x y ] = [ 3 / 2 x 3 / 2 y ] \begin{bmatrix}
    3/2 & 0\\
    0 & 3/2
\end{bmatrix}
\begin{bmatrix}
    x\\
    y
\end{bmatrix}=
\begin{bmatrix}
    3/2x\\
    3/2y
\end{bmatrix} [ 3/2 0  0 3/2  ] [ x y  ] = [ 3/2 x 3/2 y  ]  
而旋轉的變換可以用三角函數相關的矩陣來表示,比如我們想要旋轉 θ \theta θ   度:
[ cos  θ − sin  θ sin  θ cos  θ ] [ x y ] = [ x cos  θ − y sin  θ x sin  θ + y cos  θ ] \begin{bmatrix}
    \cos\theta & -\sin\theta\\
    \sin\theta & \cos\theta
\end{bmatrix}
\begin{bmatrix}
    x\\
    y
\end{bmatrix}=
\begin{bmatrix}
    x\cos\theta-y\sin\theta\\
    x\sin\theta+y\cos\theta
\end{bmatrix} [ cos θ sin θ  − sin θ cos θ  ] [ x y  ] = [ x cos θ − y sin θ x sin θ + y cos θ  ]  
如果我們想要將這些線性變換組合在一起,我們可以把這些矩陣相乘。比如我們先放大 3/2 倍,然後再旋轉 θ \theta θ   度:
[ cos  θ − sin  θ sin  θ cos  θ ] [ 3 / 2 0 0 3 / 2 ] [ x y ] = [ 3 / 2 x cos  θ − 3 / 2 y sin  θ 3 / 2 x sin  θ + 3 / 2 y cos  θ ] \begin{bmatrix}
    \cos\theta & -\sin\theta\\
    \sin\theta & \cos\theta
\end{bmatrix}
\begin{bmatrix}
    3/2 & 0\\
    0 & 3/2
\end{bmatrix}
\begin{bmatrix}
    x\\
    y
\end{bmatrix}=
\begin{bmatrix}
    3/2x\cos\theta-3/2y\sin\theta\\
    3/2x\sin\theta+3/2y\cos\theta
\end{bmatrix} [ cos θ sin θ  − sin θ cos θ  ] [ 3/2 0  0 3/2  ] [ x y  ] = [ 3/2 x cos θ − 3/2 y sin θ 3/2 x sin θ + 3/2 y cos θ  ]  
像平移這樣的變換,就不是用矩陣乘來表示,而是用加法:
[ x y ] + [ t x t y ] = [ x + t x y + t y ] \begin{bmatrix}
    x\\
    y
\end{bmatrix}+
\begin{bmatrix}
    t_x\\
    t_y
\end{bmatrix}=
\begin{bmatrix}
    x+t_x\\
    y+t_y
\end{bmatrix} [ x y  ] + [ t x  t y   ] = [ x + t x  y + t y   ]  
這樣我們就把 ( x , y ) (x,y) ( x , y )   平移了 ( t x , t y ) (t_x,t_y) ( t x  , t y  )  。這類不是用乘法的變換則是非線性變換。
如果此時,我對物品的一系列變換的組合感興趣,其中包含線性或非線性。比如我想要對一個物體放大、平移、旋轉,再放大,再平移:
[ x ′ y ′ ] = [ 3 / 2 0 0 3 / 2 ] [ cos  θ − sin  θ sin  θ cos  θ ] ( [ 3 / 2 0 0 3 / 2 ] [ x y ] + [ t x t y ] ) + [ t x ′ t y ′ ] \begin{bmatrix}
    x'\\
    y'
\end{bmatrix}=
\begin{bmatrix}
    3/2 & 0\\
    0 & 3/2
\end{bmatrix}
\begin{bmatrix}
    \cos\theta & -\sin\theta\\
    \sin\theta & \cos\theta
\end{bmatrix}
\bigg(
\begin{bmatrix}
    3/2 & 0\\
    0 & 3/2
\end{bmatrix}
\begin{bmatrix}
    x\\
    y
\end{bmatrix}+
\begin{bmatrix}
    t_x\\
    t_y
\end{bmatrix}
\bigg)+
\begin{bmatrix}
    t_x'\\
    t_y'
\end{bmatrix} [ x ′ y ′  ] = [ 3/2 0  0 3/2  ] [ cos θ sin θ  − sin θ cos θ  ] ( [ 3/2 0  0 3/2  ] [ x y  ] + [ t x  t y   ] ) + [ t x ′  t y ′   ]  
僅僅是這樣,我們的表達式已經開始變得混亂。但如果這樣的操作要嵌套幾十層,那麼我們的表達式會複雜的難以想象。我們希望盡可能的只用乘法來表示所有的變換,這樣我們的式子就能簡潔很多。
齊次坐標  
對於矩陣加法來說,我們可以寫成:
[ x y ] + [ t x t y ] = [ 1 [ t x t y ] ] [ [ x y ] 1 ] \begin{bmatrix}
    x\\
    y
\end{bmatrix}+
\begin{bmatrix}
    t_x\\
    t_y
\end{bmatrix}=
\begin{bmatrix}
    1&
    \begin{bmatrix}
    t_x\\
    t_y
    \end{bmatrix}
\end{bmatrix}
\begin{bmatrix}
\begin{bmatrix}
    x\\
    y
\end{bmatrix}\\
1
\end{bmatrix} [ x y  ] + [ t x  t y   ] = [ 1  [ t x  t y   ]  ]  [ x y  ] 1    
如果我們把它寫開變成 3 維:
[ 1 0 t x 0 1 t y 0 0 1 ] [ x y 1 ] = [ x + t x y + t y 1 ] \begin{bmatrix}
    1&0&t_x\\
    0&1&t_y\\
    0&0&1
\end{bmatrix}
\begin{bmatrix}
    x\\
    y\\
    1
\end{bmatrix}=
\begin{bmatrix}
    x+t_x\\
    y+t_y\\
    1
\end{bmatrix}  1 0 0  0 1 0  t x  t y  1    x y 1   =  x + t x  y + t y  1    
原本我們在 2 維時的非線性平移變換,增加一個維度變成 3 維後,就可以用矩陣乘法來表示了。這樣的增加維度的方法,就是齊次坐標。我們可以把 2 維的點 ( x , y ) (x,y) ( x , y )   變成 3 維的點 ( x , y , 1 ) (x,y,1) ( x , y , 1 )  ,這樣我們就可以用矩陣乘法來表示平移了。
這種通過增加一個維度的方法就是齊次坐標。我們規定,三維中一個點 ( x , y , z ) (x,y,z) ( x , y , z )   的齊次坐標是 ( x , y , z , 1 ) (x,y,z,1) ( x , y , z , 1 )  。而如果我們要把一個齊次坐標 ( x , y , z , w ) (x,y,z,w) ( x , y , z , w )   變回普通坐標,我們可以這樣做:
[ x y z w ]    ⟹    [ x / w y / w z / w ] \begin{bmatrix}
    x\\
    y\\
    z\\
    w
\end{bmatrix}\implies
\begin{bmatrix}
    x/w\\
    y/w\\
    z/w
\end{bmatrix}  x y z w   ⟹  x / w y / w z / w    
也就是說,在齊次坐標中,∀ a ≠ 0 \forall a \neq 0 ∀ a  = 0 
[ a x a y a z a w ] = [ x / w y / w z / w ] \begin{bmatrix}
    ax\\
    ay\\
    az\\
    aw
\end{bmatrix}=
\begin{bmatrix}
    x/w\\
    y/w\\
    z/w
\end{bmatrix}  a x a y a z a w   =  x / w y / w z / w    
而當 w = 0 w=0 w = 0   時,我們就認為這個點是無窮遠的。
當我們想要做任何變換時,只需要把原本的坐標轉成齊次坐標,變換後再把齊次坐標轉回普通坐標。這樣我們就可以用矩陣乘法來表示所有的變換了。
我們可以把上次透視投影的變換用齊次坐標來表示。可以發現,x 和 y 的變換是一樣的,都是除上 1 − z / c 1-z/c 1 − z / c  。根據齊次坐標的轉換規則,我們會希望第四個維度表示成 1 − z / c 1-z/c 1 − z / c  。所以我們可以把透視投影的變換寫成:
[ 1 0 0 0 0 1 0 0 0 0 1 0 0 0 − 1 / c 1 ] [ x y z 1 ] = [ x y z 1 − z / c ]    ⟹    [ x / ( 1 − z / c ) y / ( 1 − z / c ) z / ( 1 − z / c ) ] \begin{bmatrix}
    1&0&0&0\\
    0&1&0&0\\
    0&0&1&0\\
    0&0&-1/c&1
\end{bmatrix}
\begin{bmatrix}
    x\\
    y\\
    z\\
    1
\end{bmatrix}=
\begin{bmatrix}
    x\\
    y\\
    z\\
    1-z/c
\end{bmatrix}\implies
\begin{bmatrix}
    x/(1-z/c)\\
    y/(1-z/c)\\
    z/(1-z/c)
\end{bmatrix}  1 0 0 0  0 1 0 0  0 0 1 − 1/ c  0 0 0 1    x y z 1   =  x y z 1 − z / c   ⟹  x / ( 1 − z / c ) y / ( 1 − z / c ) z / ( 1 − z / c )    
這裡我們同樣保留 z 軸的深度信息。
let   mut  projection  =   Matrix :: identity ( 4 ) ; projection [ ( 3 ,   2 ) ]   =   - 1.0   /  camera . z ; let  screen_coords  =   [      world_to_screen ( m2v ( & projection  *   & v2m ( ver [ 0 ] ) ) ) ,      world_to_screen ( m2v ( & projection  *   & v2m ( ver [ 1 ] ) ) ) ,      world_to_screen ( m2v ( & projection  *   & v2m ( ver [ 2 ] ) ) ) , ] ;  
其中 m2v 和 v2m 齊次坐標和普通坐標的轉換函數。
fn   v2m ( v :   & Vertex < f32 > )   ->   Matrix   {      let   mut  m  =   Matrix :: new ( 4 ,   1 ) ;     m [ ( 0 ,   0 ) ]   =  v . x ;     m [ ( 1 ,   0 ) ]   =  v . y ;     m [ ( 2 ,   0 ) ]   =  v . z ;     m [ ( 3 ,   0 ) ]   =   1.0 ;     m } fn   m2v ( m :   Matrix )   ->   Vertex < f32 >   {      Vertex   {         x :  m [ ( 0 ,   0 ) ]   /  m [ ( 3 ,   0 ) ] ,         y :  m [ ( 1 ,   0 ) ]   /  m [ ( 3 ,   0 ) ] ,         z :  m [ ( 2 ,   0 ) ]   /  m [ ( 3 ,   0 ) ] ,      } }  
坐標系變換  
單獨一個物體的數據,大多是  以自身的坐標系為基礎的。但我們把一個物體放到另一個空間中時,就需要根據坐標的改變來對物體的數據進行變換。
在歐式空間中,向量可以由原點和基底(base)來表示。假如 P 點在坐標系 (O,i,j,k) 中的坐標是 (x,y,z),那麼 OP 向量可以表示為:
O P ⃗ = i ⃗ x + j ⃗ y + k ⃗ z [ i ⃗ j ⃗ k ⃗ ] [ x y z ] \vec{OP}=\vec{i}x+\vec{j}y+\vec{k}z
\begin{bmatrix}
    \vec{i} & \vec{j} & \vec{k}
\end{bmatrix}
\begin{bmatrix}
    x\\
    y\\
    z
\end{bmatrix} OP = i x + j  y + k z [ i  j   k  ]  x y z    
如果我們有另一套坐標系 (O',i',j',k'),P 點在這套坐標系中的坐標是 (x',y',z'),如果我們還知道 O' 相對於 O 的位置,即在 O 坐標系中 OO' 向量:
那麼我們可以把 OP 向量重新表示為:
O P ⃗ = O O ′ ⃗ + O ′ P ⃗ = [ i ⃗ j ⃗ k ⃗ ] [ O x ′ O x ′ O z ′ ] + [ i ′ ⃗ j ′ ⃗ k ′ ⃗ ] [ x ′ y ′ z ′ ] \vec{OP} = \vec{OO'} + \vec{O'P} =
\begin{bmatrix}
    \vec{i} & \vec{j} & \vec{k}
\end{bmatrix}
\begin{bmatrix}
    O_x'\\
    O_x'\\
    O_z'
\end{bmatrix} + 
\begin{bmatrix}
    \vec{i'} & \vec{j'} & \vec{k'}
\end{bmatrix}
\begin{bmatrix}
    x'\\
    y'\\
    z'
\end{bmatrix} OP = O O ′ + O ′ P = [ i  j   k  ]  O x ′  O x ′  O z ′    + [ i ′  j ′   k ′  ]  x ′ y ′ z ′    
因為 ( i , j , k ) (i,j,k) ( i , j , k )   和 ( i ′ , j ′ , k ′ ) (i',j',k') ( i ′ , j ′ , k ′ )   都是基底,是從原點出發的向量。所以存在一個矩陣 M M M  ,使得:
[ i ′ ⃗ j ′ ⃗ k ′ ⃗ ] = [ i ⃗ j ⃗ k ⃗ ] M \begin{bmatrix}
    \vec{i'} & \vec{j'} & \vec{k'}
\end{bmatrix}=
\begin{bmatrix}
    \vec{i} & \vec{j} & \vec{k}
\end{bmatrix}
M [ i ′  j ′   k ′  ] = [ i  j   k  ] M  
因此
O P ⃗ = [ i ⃗ j ⃗ k ⃗ ] ( [ O x ′ O x ′ O z ′ ] + M [ x ′ y ′ z ′ ] ) \vec{OP}=
\begin{bmatrix}
    \vec{i} & \vec{j} & \vec{k}
\end{bmatrix}
\bigg(
\begin{bmatrix}
    O_x'\\
    O_x'\\
    O_z'
\end{bmatrix}+ M
\begin{bmatrix}
    x'\\
    y'\\
    z'
\end{bmatrix}
\bigg) OP = [ i  j   k  ] (  O x ′  O x ′  O z ′    + M  x ′ y ′ z ′   )  
也就是說
[ x y z ] = [ O x ′ O x ′ O z ′ ] + M [ x ′ y ′ z ′ ]    ⟹    [ x ′ y ′ z ′ ] = M − 1 ( [ x y z ] − [ O x ′ O x ′ O z ′ ] ) \begin{bmatrix}
    x\\
    y\\
    z
\end{bmatrix}=
\begin{bmatrix}
    O_x'\\
    O_x'\\
    O_z'
\end{bmatrix}+ M
\begin{bmatrix}
    x'\\
    y'\\
    z'
\end{bmatrix}
\implies
\begin{bmatrix}
    x'\\
    y'\\
    z'
\end{bmatrix}=
M^{-1}\bigg(
\begin{bmatrix}
    x\\
    y\\
    z
\end{bmatrix}-
\begin{bmatrix}
    O_x'\\
    O_x'\\
    O_z'
\end{bmatrix}
\bigg)  x y z   =  O x ′  O x ′  O z ′    + M  x ′ y ′ z ′   ⟹  x ′ y ′ z ′   = M − 1 (  x y z   −  O x ′  O x ′  O z ′    )  
移動相機  
如果我們想要觀察一個物體或者世界的不同角度,我們可以移動相機。但我們也可以反過來移動物體,讓相機固定在原地。比如我想要通過相機觀察物體的右側,那我可以把物體向相反的方向,也就是左側移動。
我們假設相機的位置在 e e e   點,並且面向 c c c   點,而 u ⃗ \vec{u} u   向量則是指向相機正上方。那麼 (c, x',y',z') 坐標就時我們屏幕的坐標系。而物體的數據是在 (O, x,y,z) 坐標系中的。因此我們需要 計算把 (O, x,y,z) 坐標系變換到 (c, x',y',z') 坐標系。而我們要給的參數則是在 (O, x,y,z) 坐標系中的相機的位置、渲染的坐標系原點、以及指向相機上方向量。
fn   lookat ( eye :   & Vertex < f32 > ,  center :   & Vertex < f32 > ,  up :   & Vertex < f32 > )   ->   Matrix   {      let  z  =   ( eye  -  center ) . normalize ( ) ;      let  x  =   ( * up  ^  z ) . normalize ( ) ;      let  y  =   ( z  ^  x ) . normalize ( ) ;      let   mut  m  =   Matrix :: identity ( 4 ) ;      let   mut  tr  =   Matrix :: identity ( 4 ) ;      for  i  in   0 .. 3   {         m [ ( i ,   0 ) ]   =  x [ i ] ;         m [ ( i ,   1 ) ]   =  y [ i ] ;         m [ ( i ,   2 ) ]   =  z [ i ] ;         tr [ ( 0 ,   3 ) ]   =   - center . x ;      }      let  m_inv  =  m . transpose ( ) ;     m_inv  *  tr }  
因為我們假定 up 向量並不與視線垂直,它只是一個大致的方位。因此我們需要計算出一組互相垂直的向量 x,y,z ,來構成新的坐標系。這裡的 ^ 是向量的外積運算符。我們在計算時還要求 x,y,z 是歸一化的,這是為了方便我們計算 M − 1 M^{-1} M − 1  ,因為歸一化的正交矩陣的逆矩陣就是它的轉置矩陣。
我們讓 tr 矩陣的第四個元素是相機坐標的相反數,因為我們的新坐標系是以相機為原點的。
t r [ x y z 1 ] = [ 1 0 0 − e x 0 1 0 − e y 0 0 1 − e z 0 0 0 1 ] [ x y z 1 ] = [ x − e x y − e y z − e z 1 ] tr
\begin{bmatrix}
    x\\
    y\\
    z\\
    1
\end{bmatrix}=
\begin{bmatrix}
    1&0&0&-e_x\\
    0&1&0&-e_y\\
    0&0&1&-e_z\\
    0&0&0&1
\end{bmatrix}
\begin{bmatrix}
    x\\
    y\\
    z\\
    1
\end{bmatrix}=
\begin{bmatrix}
    x-e_x\\
    y-e_y\\
    z-e_z\\
    1
\end{bmatrix} t r  x y z 1   =  1 0 0 0  0 1 0 0  0 0 1 0  − e x  − e y  − e z  1    x y z 1   =  x − e x  y − e y  z − e z  1