圖形學 旋轉與投影矩陣—2
game101 第二次作業; webgl 實現
使用 THREEJS 作為基礎框架,構建各類矩陣,自定義矩陣運算,最終完成
- 正確構建模型矩陣
- 正確構建透視投影矩陣
- 看到變換後的三角形
- 按 A 和 D 三角形能夠進行旋轉
- 按 Q 和 E 三角形能夠繞任意過原點的向量進行旋轉
最終效果
前文簡介
在旋轉與投影矩陣第一篇中,以二維座標系舉例,詳細解釋了三種基礎變換的矩陣形式,其中包括縮放變換,旋轉變換和平移變換。平移變換比較特殊,由於 2*2 的矩陣無法表示平移變換,增加一個維度表示平移變換能獲得很多好處,因此後續均採用 3*3 的矩陣表示基礎變換。
補充:繞原點旋轉的矩陣已經在前文推斷出來,如果想繞指定點進行旋轉,此時應該如何處理?這裡轉個彎,先將指定點和物體同時移動相同距離直至指定點到原點,再應用繞原點旋轉的矩陣,最後再還原移動,恢復開始移動的距離。具體流程如圖一所示。
圖一:繞任意點旋轉
三維變換
在三維中的變換和在二維中的變換是類似的,可以採用相同的推斷,3*3 的矩陣可以表示縮放和旋轉,無法表示平移變換,因此需要引入第四個維度,使用 4*4 的矩陣統一描述縮放、旋轉和平移的變換。
縮放變換數值全在對角線上
\[\left[
\begin{matrix}
x' \\ y' \\ z'
\end{matrix}
\right]
=
\left[
\begin{matrix}
S_x & 0 & 0 \\ 0 & S_y & 0 \\ 0 & 0 & S_z
\end{matrix}
\right]
\times
\left[
\begin{matrix}
x \\ y \\ z
\end{matrix}
\right]
\\
表示在\: x,y,z\: 上分別縮放\: S_x,S_y,S_z \:倍
\]
剛剛也談到要用四維座標表示,四維座標擴充套件很容易,擴充套件位除了(3, 3)位全是 0,(3, 3)位是 1
\[縮放矩陣=
\left[
\begin{matrix}
S_x & 0 & 0 & 0 \\ 0 & S_y & 0 & 0 \\ 0 & 0 & S_z & 0 \\ 0 & 0 & 0 & 1
\end{matrix}
\right]
\]
平移變換比較簡單,數值處於第四列
\[平移矩陣=
\left[
\begin{matrix}
1 & 0 & 0 & T_x \\ 0 & 1 & 0 & T_y \\ 0 & 0 & 1 & T_z \\ 0 & 0 & 0 & 1
\end{matrix}
\right] \\
表示在\: x,y,z\: 上分別移動 \: T_x, T_y,T_z \: 個單位長度
\]
旋轉矩陣稍微特殊一點,推導方式與二維一致
一個立方體,繞原點旋轉 θ 角度,選中點進行代入計算,最終算出矩陣每個元素的值,三維旋轉可以分成繞 X 軸旋轉,繞 Y 軸旋轉和繞 Z 軸旋轉,可以想象一下繞這三種旋轉最終得到的圖形。經過代入計算可得
\[R_x(\theta)=
\left[
\begin{matrix}
1 & 0 & 0 & 0 \\ 0 & \cos(\theta) & -\sin(\theta) & 0 \\
0 & \sin(\theta) & \cos(\theta) & 0 \\ 0 & 0 & 0 & 1
\end{matrix}
\right]
\\
\\
R_z(\theta)=
\left[
\begin{matrix}
\cos(\theta) & -\sin(\theta) & 0 & 0 \\
\sin(\theta) & \cos(\theta) & 0 & 0 \\
0 & 0 & 1 & 0 \\
0 & 0 & 0 & 1
\end{matrix}
\right]
\\
\\
R_y(\theta)=
\left[
\begin{matrix}
\cos(\theta) & 0 & \sin(\theta) & 0 \\
0 & 1 & 0 & 0 \\
-\sin(\theta) & 0 & \cos(\theta) & 0 \\
0 & 0 & 0 & 1
\end{matrix}
\right]
\]
這個旋轉矩陣和二維旋轉是非常類似的矩陣
\[二維旋轉矩陣=
\left[
\begin{matrix}
\cos(\theta) & -\sin(\theta) \\ \sin(\theta) & \cos(\theta)
\end{matrix}
\right]
\]
Ry(θ) 稍有不同,Rx 和 Rz 相當於是在單位矩陣上固定相應軸的值,在進行二維旋轉。Ry 的特殊性在於
\[\overrightarrow{x} \times \overrightarrow{y}
= \overrightarrow{z} \\
\overrightarrow{y} \times \overrightarrow{z}
= \overrightarrow{x} \\
特殊處:
\overrightarrow{x} \times \overrightarrow{z}
= -\overrightarrow{y} \\
\]
依圖所示加上右手定則,向量叉乘可得出,因此繞 y 軸的旋轉矩陣會特殊一點
描述了繞 x,y,z 軸的旋轉矩陣,實際上還有一種,繞原點任意方向進行旋轉,這個方程在程式中也會被用到,設向量為 n,旋轉角度為 θ
\[R(n, \theta) =
\cos(\theta) \times I +
(1-\cos(\theta)) \times n \times n^T +
\sin(\theta) \times
\left[
\begin{matrix}
0 & -n_z & n_y \\ n_z & 0 & -n_x \\ -n_y & n_x & 0
\end{matrix}
\right]
\\
n_x, n_y, n_z \: 分別是向量\: \overrightarrow{n} \: 的x,y,z值
\]
該式該需要轉換為 4*4 適配整套計算,方法很簡單,[3, 3] 位置為 1, 其餘位置補充為0。這裡提及的是繞過原點的 n 向量旋轉,那想求過任意點的 n 向量旋轉該怎麼做呢?方法之前也提到過,先將任意點移動到原點,旋轉後在糾正回去即可。
因為旋轉矩陣運算不適合插值計算,因此需要四元數處理旋轉,在計算時更加方便和高效。
檢視矩陣
之前的各種三維變換,把他們組合在一起,就形成了模型矩陣,也就是說,模型矩陣是物體的三維變換的矩陣乘積。模型的位置基本確定,接下來得確定相機的位置。相機預設在原點處,up 向上方向是 y 軸正方向,lookAt 方向位 z 軸負方向,如果相機不在,那麼就得矯正,美名曰規範化。
規範化分為兩步,第一步,將相機移動到原點,第二步,將相機的 up 和 lookAt 方向規範
規範化的相機視角
假設現在相機移動,旋轉後,如果復原呢?
復原過程
分步驟計算
第一步:轉換位置到原點。由於當前相機位置是 e,對應的座標為(xe, ye, ze),容易寫出平移矩陣 Tview,如下所示
\[T_{view} =
\left[
\begin{matrix}
1 & 0 & 0 & -x_e \\
0 & 1 & 0 & -y_e \\
0 & 0 & 1 & -z_e \\
0 & 0 & 0 & 1
\end{matrix}
\right]
\]
第二步:轉換 up 和 looAt 方向。向著復原過程求解不太容易,轉換為求從標準狀態轉換為當前狀態更好理解。也就是說,現在可以求規範化的相機如何旋轉到目前的方向,然後對這個方向求逆矩陣。還是代入計算
\[R_{view}^{-1} \times
\left[
\begin{matrix}
0 \\ 0 \\ 1 \\ 0
\end{matrix}
\right]
=
\left[
\begin{matrix}
x_{-g} \\ y_{-g} \\ z_{-g} \\ 0
\end{matrix}
\right]
\\
lookAt 方向計算
\\
\\
R_{view}^{-1} \times
\left[
\begin{matrix}
0 \\ 1 \\ 0 \\ 0
\end{matrix}
\right]
=
\left[
\begin{matrix}
x_{t} \\ y_{t} \\ z_{t} \\ 0
\end{matrix}
\right]
\\
up方向計算
\\
\\
得到 lookAt 方向和 up 方向後,x 軸方向為 lookAt \times up
\\
\\
R_{view}^{-1} =
\left[
\begin{matrix}
x_{g\times t} & x_t & x_{-g} & 0 \\
y_{g\times t} & y_t & y_{-g} & 0 \\
z_{g\times t} & z_t & z_{-g} & 0 \\
0 & 0 & 0 & 1
\end{matrix}
\right]
= R^T \\
得到 \\
R =
\left[
\begin{matrix}
x_{g\times t} & y_{g\times t} & z_{g\times t} & 0 \\
x_t & y_t & z_t & 0 \\
x_{-g} & y_{-g} & z_{-g} & 0 \\
0 & 0 & 0 & 1
\end{matrix}
\right]
\]
第三步,求得檢視矩陣 Mview = Rview * Tview
規範立方體
給出一個立方體,用 top, bottom, left, right, near, far,六個面切割形成一個四稜柱,正交矩陣的作用是將這個四稜柱規範化,將中心置為原點處,長寬高分別為2,形成 [-1, -1, -1] 到 [1, 1, 1] 的規範立方體。
這個過程也是分為兩步,第一步,將中心置於原點處,第二步,將圖形壓縮成規範立方體,由此可得正交矩陣,先設 top, bottom, left, right, near, far 值分別為 t, b, l, r, n, f
\[M_{ortho}= 旋轉矩陣 * 平移矩陣
\\
M_{ortho}=
\left[
\begin{matrix}
\frac2{r-l} & 0 & 0 & 0 \\
0 & \frac2{t-b} & 0 & 0 \\
0 & 0 & \frac2{n-f} & 0 \\
0 & 0 & 0 & 1
\end{matrix}
\right]
\times
\left[
\begin{matrix}
1 & 0 & 0 & -\frac{r+l}{2} \\
0 & 1 & 0 & -\frac{t+b}{2} \\
0 & 0 & 1 & -\frac{n+f}{2} \\
0 & 0 & 0 & 1
\end{matrix}
\right]
\]
注意:先操作的矩陣在右邊,後操作的矩陣在左邊,這是因為元素座標是 4*1 的,需要左乘矩陣。這也代表元素最先相乘的是所有變換矩陣的右側,案例見下列算式
\[gl\_Position = M_{persp} \times M_{view} \times M_{model} \times position
\]
由此,正交矩陣便已經解析出來了
投影矩陣
投影相機看到的視角和人眼看到的視角可以保持一致,因此,在三維世界中採用的一般是投影相機,加入運算的便是投影矩陣,如果直接求解投影矩陣是比較麻煩的,可以將過程轉換為,先把投影矩陣轉換為正交矩陣再進行計算
\[M_{persep} =M_{ortho} \times M_{persp \rightarrow ortho}
\]
因為正交矩陣上文已經求解出來了,現在只需要求解投影到正交的轉換矩陣。現在有一個點的座標是(x, y, z),經過轉換後投影再相機的近平面上,經過轉換後的點的座標為 (x', y', z'),轉換過程如下圖所示
從投影到正交的轉換過程中,我們可以得到一些等式從而解出這個轉換方程式,具體如下
\[y' = \frac n z \times y \\
x' = \frac n z \times x \\
\left[
\begin{matrix}
x' \\ y' \\ z' \\ 1
\end{matrix}
\right]
\Rightarrow
\left[
\begin{matrix}
\frac n z x \\ \frac n z y \\ ? \\ 1
\end{matrix}
\right]
=
\left[
\begin{matrix}
\ n x \\ n y \\ ? \\ z
\end{matrix}
\right]
\]
這時,我們得到了轉換後點的座標 [x', y', z', 1] = [nx, ny, ?, z],為啥可以同時乘 z ,前文中也提到,最後一個引數最後總會歸一化成 1,在之前無論怎麼變換,最後,x,y,z 都會同時 /w ,因此在計算時,可以 x, y, z, w 同時乘上一個數,意義不變。
已知轉換前後點的座標,再列方程式
\[M_{persp \rightarrow ortho} \times
\left[
\begin{matrix}
x \\ y \\ z \\ 1
\end{matrix}
\right]
=
\left[
\begin{matrix}
x' \\ y' \\ z' \\ 1
\end{matrix}
\right]
=
\left[
\begin{matrix}
\ n x \\ n y \\ ? \\ z
\end{matrix}
\right]
\\
\Downarrow
\\
M_{persp \rightarrow ortho} =
\left[
\begin{matrix}
\ n & 0 & 0 & 0 \\ 0 & n & 0 & 0 \\ ? & ? & ? & ?\\ 0 & 0 & 1 & 0
\end{matrix}
\right]
\]
注意轉換過程中有兩個性質
- 近平面所有座標不變,遠平面進行壓縮
- 近平面與遠平面的 z 值不變
- 遠平面中心座標不變
因此可以代入兩個點的變換
\[1: \:
\left[
\begin{matrix}
x \\ y \\ n \\ 1
\end{matrix}
\right]
\Rightarrow
\left[
\begin{matrix}
x' \\ y' \\ n \\ 1
\end{matrix}
\right] =
\left[
\begin{matrix}
nx' \\ ny' \\ n^2 \\ n
\end{matrix}
\right]
\\
2: \:
\left[
\begin{matrix}
x \\ y \\ f \\ 1
\end{matrix}
\right]
\Rightarrow
\left[
\begin{matrix}
x' \\ y' \\ f \\ 1
\end{matrix}
\right] =
\left[
\begin{matrix}
fx' \\ fy' \\ f^2 \\ f
\end{matrix}
\right]
\]
可得
\[\left[
\begin{matrix}
? & ? & ? & ?
\end{matrix}
\right] \times
\left[
\begin{matrix}
x \\ y\\ n \\ 1
\end{matrix}
\right]
=
n^2
\\
\left[
\begin{matrix}
? & ? & ? & ?
\end{matrix}
\right] \times
\left[
\begin{matrix}
x \\ y\\ f \\ 1
\end{matrix}
\right]
=
f^2
\\
\]
因為得出的結果與x, y沒有關係,因此可以推斷出前兩個問號為0,設第三和第四個問號為A,B
\[\left[
\begin{matrix}
0 & 0 & A & B
\end{matrix}
\right] \times
\left[
\begin{matrix}
x \\ y\\ n \\ 1
\end{matrix}
\right]
=
n^2
\\
\left[
\begin{matrix}
0 & 0 & A & B
\end{matrix}
\right] \times
\left[
\begin{matrix}
x \\ y\\ f \\ 1
\end{matrix}
\right]
=
f^2
\\
\Downarrow
\\
\left\{
\begin{array}{}
A \times n + B = n^2 \\
A \times f + B = f^2 \\
\end{array}
\right.
\rightarrow
\left\{
\begin{array}{}
A = n + f \\
B = -n \times f \\
\end{array}
\right.
\]
由此可得到投影轉換矩陣的具體內容
\[M_{persp \rightarrow ortho} =
\left[
\begin{matrix}
\ n & 0 & 0 & 0 \\ 0 & n & 0 & 0 \\ 0 & 0 & n+f & -nf\\ 0 & 0 & 1 & 0
\end{matrix}
\right]
\]
最終的投影矩陣的表示式為
\[M_{persep} =M_{ortho} \times M_{persp \rightarrow ortho}
\]
透視矩陣引數
正交矩陣需要的引數是 n, f, l, r, t, b,分別表示 near, far, left, right, top, bottom 透視矩陣也需要這麼多,但是很多引數都是可以推導的,這裡新增兩個概念,寬高比,aspect, 視角 fov。
寬高比比較好理解,字如其意,aspect = 寬/高;視角分為兩種,一種叫 fovY,另外一種叫 fovX, 在 THREEJS 中,fov 代表的就是 fovY,表示如下圖所示
可以寫出下面兩個式子
\[aspect = \frac r t = \frac {2r}{2t} = \frac 寬 高
\\
tan{\frac {fovY} 2} = \frac t {|n|}
\]
由於相機是朝z軸負方向看,因此 n 和 f 是負數,這裡需要加個絕對值。n 和 f 都是必須要知道的,已知 n 和 fovY,就能求出 t, t 和 f 互為相反數,因此可以得到 f, 知道 aspect 和 t,就能知道 r, r 和 l 互為相反數。
因此,知道了 fovY, aspect,n 和 f 就能求出所有需要的值
結論
在三維世界中,有三個重要的矩陣,模型矩陣,檢視矩陣和投影矩陣,只有知道這三個矩陣才能將元素的元素座標轉換為規範立方體內的座標
\[gl\_Position = M_{persp} \times M_{view} \times M_{model} \times position
\]
目前,每個矩陣均已經求解完畢,只要提供 position,就能知道最終座標。
旋轉與投影矩陣分為了三篇,第一篇描述了二維座標下的矩陣變換,第二篇即本篇描述了三維座標系下的基礎概念,和相應的轉換公式,基於這些轉換公式便可以開始進行實驗,第三篇以程式碼為主,主要講實現文章開頭的五個需求,構建一個三維世界出來