LearnGL - 06 - Matrix - 矩陣01


LearnGL - 學習筆記目錄

本人才疏學淺,如有什麼錯誤,望不吝指出。

上一篇:LearnGL - 05.3 - 封裝 Main.cpp 中重複 GLFW代碼,讓新建的項目更方便的開展。

這一篇:這篇我們在講一丟丟關於矩陣的內容。


本來我不想寫關於矩陣的內容。因爲要排版很多內容(LaTex挺多),-_-!!!,但是想了一下,還是加上吧,因爲這個是我自己以前看過的資料總結的,先暫時寫這麼多,後續有空再補上關於矩陣的其他內容。

這裏推薦一下關於矩陣的完整系統教學,我也是參考了其他網站上關於 OpenGL 學習資料中的:變換,這篇文章裏介紹的:

可汗學院

最近需要系統學習一下這方面資料,發現他這在線免費的教程資料太太太好了,忍不住要分享一下。

相比我之前在:網易公開課,裏看的熟肉的質量好多了,因爲我上面推薦的可汗學院的網站是官方網,雖然的內容是英文版,但是還好的是陌生單詞不會很多。

可汗的故事以前看過,反正後來是比爾蓋茨投資他繼續爲科學教育助力,因爲比爾蓋茨也是他的粉絲,而我,是比爾蓋茨的粉絲。

在這裏插入圖片描述

看看:“A world class education for anyone, anywhere 100% free。”,這就是初心,感動人的工匠,這是造福人類啊。


OK,那麼開始正文:

變換 這篇文章裏排版什麼的我是非常喜歡,看起來很舒服。

但裏面剛好有一丟丟我會的內容沒提到,我也在此基礎上添加一丟丟筆記吧。

注意,什麼是 向量標量向量相乘向量加減法向量點積向量乘積矩陣乘法 我都不再說明,因爲 變換 說得足夠的清晰了。

點在座標系下的定位

我們先了解 點在座標系下的定位

先看看座標軸長什麼樣:
在這裏插入圖片描述
這些內容大家肯定了解,畢竟都是初中知識。

O是座標系原點,X就是X軸,其他兩個就是Y軸和Z軸。

它們看起來就像是 三個向量

沒錯,就是三個向量,OK,那麼我們將它們每個軸的向量都標記上向量分量:
在這裏插入圖片描述
我們先給軸都定義一個標識符(字母上面加一個指向右的箭頭)吧:X\overrightarrow X 表 X軸,以此類推 YZ\overrightarrow Y、\overrightarrow Z
然後原點是:OO

X=(1,0,0)\overrightarrow X=(1,0,0)
Y=(0,1,0)\overrightarrow Y=(0,1,0)
Z=(0,0,1)\overrightarrow Z=(0,0,1)

從座標軸長度可以看出來,都是 1的長度(單位化向量長度都是1)。
所以三個向量都是 單位向量(基向量)。
而且三個向量都是相互 垂直的(正交的)。
所以我們也可以叫:正交基向量,也叫 自然基標準化(歸一化)基向量,叫法挺多,選一個方便我們理解的就好,暫時選用 正交基向量

如果我有任意一個點像在此座標系下顯示位置,我們如果處理呢?

假設我有一個點P,它就在原點O(0,0,0)的位置上,它和原點重合了,我們想讓它右邊(X軸正方向)移動0.5的位移量,好讓我們看清這個點:

那麼下面P點的位置變成了:O+0.5XO+0.5 \overrightarrow X

就是在:原點OO基礎上往X\overrightarrow X偏移0.5的量

O=(0,0,0),X=(1,0,0)O=(0,0,0), \overrightarrow X=(1,0,0)
P=O+0.5XP點=O+0.5\overrightarrow X
=(0,0,0)+0.5(1,0,0)=(0,0,0)+0.5\cdot(1,0,0)
=(0.5,0,0)=(0.5,0,0)

在這裏插入圖片描述
然後再向上(Y軸正方向)移動0.2個位移量:O+0.5X+0.2YO+0.5\overrightarrow X+0.2\overrightarrow Y

就是在:原點OO基礎上往X\overrightarrow X偏移0.5的量,再往Y\overrightarrow Y偏移0.2的量

O=(0,0,0),X=(1,0,0),Y=(0,1,0)O=(0,0,0), \overrightarrow X=(1,0,0),\overrightarrow Y=(0,1,0)
P=O+0.5X+0.2YP點=O+0.5\overrightarrow X+0.2\overrightarrow Y
=(0,0,0)+0.5(1,0,0)+0.2(0,1,0)=(0,0,0)+0.5\cdot(1,0,0)+0.2\cdot(0,1,0)
=(0.5,0,0)+(0,0.2,0)=(0.5,0,0)+(0,0.2,0)
=(0.5,0.2,0)=(0.5,0.2,0)
在這裏插入圖片描述
然後再向前(Z軸正方向)移動0.5個位移量:O+0.5X+0.2Y+0.5ZO+0.5\overrightarrow X+0.2\overrightarrow Y+0.5\overrightarrow Z

就是在:原點OO基礎上往X\overrightarrow X偏移0.5的量,再往Y\overrightarrow Y偏移0.2的量,再往Z\overrightarrow Z偏移0.5的量

O=(0,0,0),X=(1,0,0),Y=(0,1,0),Z=(0,0,1)O=(0,0,0), \overrightarrow X=(1,0,0),\overrightarrow Y=(0,1,0),\overrightarrow Z=(0,0,1)
P=O+0.5X+0.2Y+0.5ZP點=O+0.5\overrightarrow X+0.2\overrightarrow Y+0.5\overrightarrow Z
=(0,0,0)+0.5(1,0,0)+0.2(0,1,0)+0.5(0,0,1)=(0,0,0)+0.5\cdot(1,0,0)+0.2\cdot(0,1,0)+0.5\cdot(0,0,1)
=(0.5,0,0)+(0,0.2,0)+(0,0,0.5)=(0.5,0,0)+(0,0.2,0)+(0,0,0.5)
=(0.5,0.2,0.5)=(0.5,0.2,0.5)
在這裏插入圖片描述

OK,這就是點P(0.5,0.2,0.5)P(0.5,0.2,0.5)在此座標系下的位置。

更重要的是,我們得出了一個式子:O+0.5X+0.2Y+0.5ZO+0.5\overrightarrow X+0.2\overrightarrow Y+0.5\overrightarrow Z

再把:P(0.5,0.2,0.5)P(0.5,0.2,0.5),換成:P(px,py,pz)P(p_x,p_y,p_z)

那麼式子:O+0.5X+0.2Y+0.5ZO+0.5\overrightarrow X+0.2\overrightarrow Y+0.5\overrightarrow Z

變成了:O+pxX+pyY+pzZO+p_x\cdot \overrightarrow X+p_y\cdot \overrightarrow Y+p_z\cdot \overrightarrow Z

其實這式子就是3D圖形學中4x4矩陣的前身,因爲這個式子可以表示一次變換。

注意:而使用矩陣的原因是因爲矩陣數據集合本身可以修改。假設我們有一個矩陣變換(座標系)A,只要我們用另一個矩陣變換 B 去修改它,那麼它就相當於在原來的矩陣的變換(或是說座標系)基礎上,再次改變了座標系的軸(旋轉或縮放),變成了另一個新的座標系:CC=BAC = B\cdot A,這個 C 矩陣就相當於柔和了 A 和 B 矩陣變換的累積變換。


我就舉個機械手臂的例子:
在這裏插入圖片描述

將他們的每個子矩陣變換,或者說是子座標系,羅列一下可以有這麼幾個
在這裏插入圖片描述

有:

  • AA 它是我們的肱二頭肌
  • BB 是手腕
  • CC 是類似人類手掌
  • D1,D2D_1,D_2 是手指DD關節1、2
  • E1,E2E_1,E_2 是手指EE關節1、2
  • F1,F2F_1,F_2 是手指FF關節1、2
  • GG 是我們的紅球

其中 G 會比較特殊

AAGG都的座標系之前的嵌套可以理解爲:

	A
	|
	+--->B
		|
		+--->C
			|
			+--->D_1
			|	|
			|	+--->D2
			+--->E_1
			|	|
			|	+--->E_2
			+--->F_1
				|
				+--->F_2

G 呢?它沒有屬於機械手臂的座標系嗎?因爲我們的機械手臂的D,E,F關節都是可以旋轉而夾緊紅球 G,並且 D,E,F 不再變換後,D,E,F,G都數據 C 的子座標系。

只要 G 求沒有東西固定它,它都屬於世界座標。

當然,我們上面通常是簡單的遊戲中的製作方法,更好的方法是直接使用物理系統來給球體、機械手臂添加物理剛體網格,讓他們使用物理系統來驅動相應。

每一個關節都有一個座標系,或是說:矩陣變換。

如果我們驅動 A 矩陣變換,那麼其他所有的 子座標 或是說 子矩陣變換 都一同跟着變換,如果我們驅動 B,那麼 A 保持不動,B 下的所有 子矩陣變換 又會跟着變化。(有些系統是例外,如:實現IK動力學,是需要 子矩陣變換 來計算驅動 父級矩陣 如何變化的)

這些每個座標系相對世界座標系統(I10I,單位矩陣,對角都是1,其餘都爲0)來說,他們都可以這麼表示:

  • IAI\cdot A
  • IABI\cdot A \cdot B
  • IABCI\cdot A \cdot B \cdot C
  • IABCD1I\cdot A \cdot B \cdot C \cdot D_1
  • IABCD1D2I\cdot A \cdot B \cdot C \cdot D_1 \cdot D_2
  • IABCE1I\cdot A \cdot B \cdot C \cdot E_1
  • IABCE1E2I\cdot A \cdot B \cdot C \cdot E_1 \cdot E_2
  • IABCF1I\cdot A \cdot B \cdot C \cdot F_1
  • IABCF1F2I\cdot A \cdot B \cdot C \cdot F_1 \cdot F_2

繼續上面的 座標系下點的定位的 內容,我們可以將:O+pxX+pyY+pzZO+p_x\cdot \overrightarrow X+p_y\cdot \overrightarrow Y+p_z\cdot \overrightarrow Z寫成矩陣的方式:

首先是每個軸,我們都寫成3x3中某個列的向量來表示:
X=[X],Y=[Y],Z=[Z]\overrightarrow X=\begin{bmatrix}|\\\overrightarrow X\\|\end{bmatrix},\overrightarrow Y=\begin{bmatrix}|\\\overrightarrow Y\\|\end{bmatrix},\overrightarrow Z=\begin{bmatrix}|\\\overrightarrow Z\\|\end{bmatrix}
這樣我們的三個 正交基向量 就構成了一個3x3矩陣:MM
M=[XYZ]M=\begin{bmatrix} | & | & |\\ \overrightarrow X & \overrightarrow Y & \overrightarrow Z\\ | & | & | \end{bmatrix}
然後是我們的點PP可以是表示爲一個3x1的向量(矩陣):
(pxpypz) \left(\begin{matrix} p_x\\ p_y\\ p_z\end{matrix}\right)

然後是點PPMM座標系(變換矩陣)中定位後(變換後)的座標定義爲:P1P1,那麼P1P1等於:
(p1xp1yp1z) \left(\begin{matrix} p1_x\\ p1_y\\ p1_z\end{matrix}\right)
P1=MP(p1xp1yp1z)=[XYZ](pxpypz)pxX+pyY+pzZ P1=M\cdot P \rightarrow \left(\begin{matrix} p1_x\\ p1_y\\ p1_z\end{matrix}\right) =\begin{bmatrix} | & | & |\\ \overrightarrow X & \overrightarrow Y & \overrightarrow Z\\ | & | & | \end{bmatrix} \cdot \left(\begin{matrix} p_x\\ p_y\\ p_z\end{matrix}\right) \rightarrow p_x\cdot \overrightarrow X+p_y\cdot \overrightarrow Y+p_z\cdot \overrightarrow Z

等等,我們是否少了個 OO 原點了?

先理解這個OO,它是原點,我們都可以理解爲相對上一個座標系的原點偏移位置。

因爲它爲(0,0,0),我暫時沒寫,那麼,如果OO不爲0呢?那麼我們還是把它加上吧。

3x3擴展4x4添加偏移量

按照3x3矩陣乘法(之前說過我這裏不再說明矩陣乘法了,因爲那篇文章說的足夠清晰),我們的偏移量在3x3表示不了了。

所以我們給3x3擴展維度到4x4
M=[OxXYZOyOz0001]M=\begin{bmatrix} | & | & | & O_x\\ \overrightarrow X & \overrightarrow Y & \overrightarrow Z & O_y\\ | & | & | & O_z\\ 0 & 0 & 0 & 1 \end{bmatrix}

然後將我們3x1的PP也需要擴展到4x1:
(pxpypz1) \left(\begin{matrix} p_x\\ p_y\\ p_z\\ 1\end{matrix}\right)
同樣P1P1也擴展到4x1:
(p1xp1yp1z1) \left(\begin{matrix} p1_x\\ p1_y\\ p1_z\\ 1\end{matrix}\right)

齊次座標

PP1P、P1我們多在行維度多添加了一個維度(x,y,z,w),並且最後一個分量w設置爲1。

這個新的座標可稱爲:齊次座標。
齊次座標有下面的特點:
(1,2,3,1)(2,4,6,2)(3,6,9,3) (1,2,3,1)\\ (2,4,6,2)\\ (3,6,9,3)

上面三個點都表示爲3D中的同一個點。

引用 songho 博主 齊次座標2D笛卡爾座標 關係 的一張圖
在這裏插入圖片描述

齊次座標有兩個作用:

  • 便於位移信息加入4x4矩陣實現帶位移的線性變換:爲了將位移量添加到4x4矩陣的第4列,以配合齊次座標中的最後一個分量1相乘即可累加該軸向的偏移量。
  • 在3D渲染中常用的方式,一般會將當前對象的世界座標/視圖座標的Z值,經過投影矩陣的M[4][3]M[4][3]的1或-1的矩陣變換後,來傳入到齊次座標的w分量中,以便 OpenGL 底層處理齊次座標透視除法時用。這樣Z值越大,投影平面座標X,Y分量將收縮得越小,以此來實現透視效果。

齊次座標的透視除法:(wx,wy,wz,w)=(wx/w,wy/w,wz/w,w/w)=(x,y,z,1)(wx,wy,wz,w)=(wx/w,wy/w,wz/w,w/w)=(x,y,z,1)

那麼ww越大,或說是 投影變換前視圖空間/座標系 下的座標的zz越大(它就是我們齊次座標中的ww,也是 與相機之間的z分量的距離值),那麼該齊次座標的 透視除法 後變爲投影近平面上的座標就越小,透視除法後 的這個座標是 NDC座標,NDC座標的原點是正中間的,所以那些 視圖空間/座標系 下的zz越大的座標到了 NDC座標後就越小,就靠近屏幕中間。想象一下我們站在長長的火車鐵軌上(務必保證沒有火車),望着遠處的鐵軌盡頭,兩排鐵軌本是平行的,但怎麼越是遠處看起來就相交了呢?這就是透視現象(這是由於我們的人類的肉眼結構決定的,但是不同物種的透視是不同的)。想象一下,如果齊次座標的第四個分量如果爲 0 的話,會怎麼樣呢?透視除法後,所有值都無窮大,意思就是無窮遠的一個點,這個點幾乎是看不到的,因爲太遠,所以我們通常會看到,3D座標中,如果第四個分量爲1,都會當作是一個座標值處理(點),如果第四個分量爲0,都會當作是一個方向處理(向量)。
在這裏插入圖片描述
(Win Paint.exe上鼠標簡單作畫,以前的老畫板找不到了,-_-!!!)

根據齊次座標特性,所以我們的式子重新調整爲:矩陣由3x3調整爲4x4,並將座標原點偏移量放置到第四列的前三個分量;與矩陣相乘的3x1向量調整爲4x1,第四個分量填充1:
P1=MP(p1xp1yp1z1)=[OxXYZOyOz0001](pxpypz1)pxX+pyY+pzZ+O P1=M\cdot P \rightarrow \left(\begin{matrix} p1_x\\ p1_y\\ p1_z\\ \color{#ff0000}1\end{matrix}\right) =\begin{bmatrix} | & | & | & O_x\\ \overrightarrow X & \overrightarrow Y & \overrightarrow Z & O_y\\ | & | & | & O_z\\ \color{#ff0000}0 & \color{#ff0000}0 & \color{#ff0000}0 & \color{#ff0000}1 \end{bmatrix} \cdot \left(\begin{matrix} p_x\\ p_y\\ p_z\\ \color{#ff0000}1\end{matrix}\right) \rightarrow p_x\cdot \overrightarrow X+p_y\cdot \overrightarrow Y+p_z\cdot \overrightarrow Z + O

我們將尾部的式子調整爲 標量向量乘法(這裏不再說明,可以看那個網站上的說明)
尾部的式子:pxX+pyY+pzZ+O將各向量XYZ=px(XxXyXz0)+py(YxYyYz0)+pz(ZxZyZz0)+1(OxOyOz1)將點P的分量(標量)乘進XYZO(這裏的O把它當做向量來運算,這樣就可以把O的分量的分別偏移原來點) 向量:=(pxXxpxXypxXzpx0)+(pyYxpyYypyYzpy0)+(pzZxpzZypzZzpz0)+(1Ox1Oy1Oz11) \text{尾部的式子:} p_x\cdot \overrightarrow X+p_y\cdot \overrightarrow Y+p_z\cdot \overrightarrow Z + O \\ \text{將各向量$\overrightarrow X、\overrightarrow Y、\overrightarrow Z 展開分量$:}=p_x \cdot \left(\begin{matrix} \color{#ff0000}\overrightarrow X_x\\ \color{#ff0000}\overrightarrow X_y\\ \color{#ff0000}\overrightarrow X_z\\ 0 \end{matrix}\right) + p_y \cdot \left(\begin{matrix} \color{#ff0000}\overrightarrow Y_x\\ \color{#ff0000}\overrightarrow Y_y\\ \color{#ff0000}\overrightarrow Y_z\\ 0 \end{matrix}\right) + p_z \cdot \left(\begin{matrix} \color{#ff0000}\overrightarrow Z_x\\ \color{#ff0000}\overrightarrow Z_y\\ \color{#ff0000}\overrightarrow Z_z\\ 0 \end{matrix}\right) + 1 \cdot \left(\begin{matrix} \color{#ff0000}O_x\\ \color{#ff0000}O_y\\ \color{#ff0000}O_z\\ 1 \end{matrix}\right) \\ \text{將點$P$的分量(標量)乘進$\overrightarrow X、\overrightarrow Y、\overrightarrow Z、\overrightarrow O$(這裏的O把它當做向量來運算,這樣就可以把O的分量的分別偏移原來點) 向量:} \\=\left(\begin{matrix} p_x\cdot\overrightarrow X_x\\ p_x\cdot\overrightarrow X_y\\ p_x\cdot\overrightarrow X_z\\ p_x\cdot0 \end{matrix}\right) + \left(\begin{matrix} p_y\cdot\overrightarrow Y_x\\ p_y\cdot\overrightarrow Y_y\\ p_y\cdot\overrightarrow Y_z\\ p_y\cdot 0 \end{matrix}\right) + \left(\begin{matrix} p_z\cdot\overrightarrow Z_x\\ p_z\cdot\overrightarrow Z_y\\ p_z\cdot\overrightarrow Z_z\\ p_z\cdot0 \end{matrix}\right) + \left(\begin{matrix} 1\cdot O_x\\ 1\cdot O_y\\ 1\cdot O_z\\ 1\cdot 1 \end{matrix}\right)

然後根據 向量加法 (這裏不再說明,之前說的那篇文章有說明,也足夠清晰)
=(pxXx+pyYx+pzZx+1OxpxXy+pyYy+pzZy+1OypxXz+pyYz+pzZz+1Ozpx0+py0+pz0+1) =\left(\begin{matrix} p_x\cdot\overrightarrow X_x+p_y\cdot\overrightarrow Y_x+p_z\cdot\overrightarrow Z_x+1\cdot O_x\\ p_x\cdot\overrightarrow X_y+p_y\cdot\overrightarrow Y_y+p_z\cdot\overrightarrow Z_y+1\cdot O_y\\ p_x\cdot\overrightarrow X_z+p_y\cdot\overrightarrow Y_z+p_z\cdot\overrightarrow Z_z+1\cdot O_z\\ p_x\cdot0+p_y\cdot 0+p_z\cdot0+1 \end{matrix}\right)
調整一下排版便於理解:
=(pxXx+pyYx+pzZx+1OxpxXy+pyYy+pzZy+1OypxXz+pyYz+pzZz+1Ozpx0+py0+pz0+1) =\left(\begin{matrix} p_x\cdot\overrightarrow X_x & + & p_y\cdot\overrightarrow Y_x & + & p_z\cdot\overrightarrow Z_x & + & 1\cdot O_x\\ p_x\cdot\overrightarrow X_y & + & p_y\cdot\overrightarrow Y_y & + & p_z\cdot\overrightarrow Z_y & + & 1\cdot O_y\\ p_x\cdot\overrightarrow X_z & + & p_y\cdot\overrightarrow Y_z & + & p_z\cdot\overrightarrow Z_z & + & 1\cdot O_z\\ p_x\cdot0 & + & p_y\cdot 0 & + & p_z\cdot0 & + & 1 \end{matrix}\right)

結果和 矩陣和矩陣的乘法 一毛一樣(4x4 \cdot 4x1),或是 矩陣和向量的乘法 也可以,把後面的4x1當做列向量。
結果我還是不小心又重複了 矩陣的乘法內容,其實直觀的本質東西,就是那些抽象的同本質的東西的另一種更好理解的表述方式。

注意OOPwP_w也就是PP向量(點)的第四個分量1111乘以任何實數等於它本身,也就是保留偏移量的意思,所以你現在應該弄懂了我之前說的齊次座標的兩個作用當中的第一個作用了吧?

縮放矩陣

前面演示了 點(向量)如何在一下矩陣座標系下顯示/定位的,那麼使用的是一個像是2D的座標系(因爲有一個Z軸垂直於XY平面),它是一個左手座標系(我要是早知道GGB的3D視圖只能有右手座標系的話,我就早應該用右手座標系來說明了,但是能確定 OpenGL 的 NDC 空間下是左手座標系的,因爲我使用過一個矩陣旋轉時,看旋轉方式就知道了)

那麼 爲了演示縮放,我使用 GGB 的 3D視圖來演示,這是一個右手座標系。
P2P2點就是我們上面說的沿着3個軸,分量平移後得到的最終座標。
在這裏插入圖片描述
如果在這個座標軸下,我們縮放座標軸,那麼P2P2點會怎麼樣的變換呢?
在這裏插入圖片描述

從觀察結果得到一個結論:

  • P2P2點沒去手動調整它的座標,但是調整了它所在的座標系的 正交基向量 的座標軸,它卻跟着變化了。
  • 其實也可以不調整座標系的軸,改而直接調整P2P2點的座標,一樣可以達到同樣的效果。

先來看看之前的式子:
(p1xp1yp1z1)=[OxXYZOyOz0001](pxpypz1) \left(\begin{matrix} p1_x\\ p1_y\\ p1_z\\ 1\end{matrix}\right) =\begin{bmatrix} | & | & | & O_x\\ \overrightarrow X & \overrightarrow Y & \overrightarrow Z & O_y\\ | & | & | & O_z\\ 0 & 0 & 0 & 1 \end{bmatrix} \cdot \left(\begin{matrix} p_x\\ p_y\\ p_z\\ 1\end{matrix}\right)

按照上面示意圖中,那麼對應矩陣會作何調整呢?很明顯直接對座標軸縮放就可以了,假設要放大整體的X,Y,Z軸的2倍,那麼可以這麼表示:
(2p1x2p1y2p1z1)=[Ox2X2Y2ZOyOz0001](pxpypz1) \left(\begin{matrix} 2p1_x\\ 2p1_y\\ 2p1_z\\ 1\end{matrix}\right) =\begin{bmatrix} | & | & | & O_x\\ 2\overrightarrow X & 2\overrightarrow Y & 2\overrightarrow Z & O_y\\ | & | & | & O_z\\ 0 & 0 & 0 & 1 \end{bmatrix} \cdot \left(\begin{matrix} p_x\\ p_y\\ p_z\\ 1\end{matrix}\right)

那麼最終的式子的代入、展開後的結果爲:
=(px(2Xx)+py(2Yx)+pz(2Zx)+1Oxpx(2Xy)+py(2Yy)+pz(2Zy)+1Oypx(2Xz)+py(2Yz)+pz(2Zz)+1Ozpx0+py0+pz0+1) =\left(\begin{matrix} p_x\cdot(2\overrightarrow X_x) + p_y\cdot(2\overrightarrow Y_x) + p_z\cdot(2\overrightarrow Z_x) + 1\cdot O_x\\ p_x\cdot(2\overrightarrow X_y) + p_y\cdot(2\overrightarrow Y_y) + p_z\cdot(2\overrightarrow Z_y) + 1\cdot O_y\\ p_x\cdot(2\overrightarrow X_z) + p_y\cdot(2\overrightarrow Y_z) + p_z\cdot(2\overrightarrow Z_z) + 1\cdot O_z\\ p_x\cdot0 + p_y\cdot 0 + p_z\cdot0 + 1 \end{matrix}\right)

然後我們把:
Xx=1,Xy=0,Xz=0\overrightarrow X_x=1, \overrightarrow X_y=0,\overrightarrow X_z=0
Yx=0,Yy=1,Yz=0\overrightarrow Y_x=0, \overrightarrow Y_y=1,\overrightarrow Y_z=0
Zx=0,Zy=0,Zz=1\overrightarrow Z_x=0, \overrightarrow Z_y=0,\overrightarrow Z_z=1
Ox=0,Oy=0,Oz=0O_x=0, O_y=0,O_z=0

都代入:
=(px(21)+py(20)+pz(20)+10px(20)+py(21)+pz(20)+10px(20)+py(20)+pz(21)+10px0+py0+pz0+1)=(2p1x2p1y2p1z1) =\left(\begin{matrix} p_x\cdot(2\cdot 1) + p_y\cdot(2\cdot 0) + p_z\cdot(2\cdot 0) + 1\cdot 0\\ p_x\cdot(2\cdot 0) + p_y\cdot(2\cdot 1) + p_z\cdot(2\cdot 0) + 1\cdot 0\\ p_x\cdot(2\cdot 0) + p_y\cdot(2\cdot 0) + p_z\cdot(2\cdot 1) + 1\cdot 0\\ p_x\cdot0 + p_y\cdot 0 + p_z\cdot0 + 1 \end{matrix}\right)= \left(\begin{matrix} 2p1_x\\ 2p1_y\\ 2p1_z\\ 1\end{matrix}\right)

(2p1x2p1y2p1z1)\left(\begin{matrix} 2p1_x\\ 2p1_y\\ 2p1_z\\ 1\end{matrix}\right)就是我們想要結果。

寫成 LaTex 公式就是:
[sclaex0000scaley0000scalez00001](xyz1)=(scalexxscaleyyscalezz1), \begin{bmatrix} \color{#ff0000} sclae_x & \color{#ff0000} 0 & \color{#ff0000} 0 & \color{#ff0000}0\\ \color{#008800} 0 & \color{#008800} scale_y & \color{#008800} 0 & \color{#008800}0\\ \color{#0000ff} 0 & \color{#0000ff} 0 & \color{#0000ff} scale_z & \color{#0000ff}0\\ \color{#bb00bb} 0 & \color{#bb00bb} 0 & \color{#bb00bb} 0 & \color{#bb00bb}1 \end{bmatrix}\cdot \left(\begin{matrix} x\\ y\\ z\\ 1 \end{matrix}\right)= \left(\begin{matrix} {\color{#ff0000} scale_x} \cdot x\\ {\color{#008800} scale_y} \cdot y\\ {\color{#0000ff} scale_z} \cdot z\\ 1 \end{matrix}\right),

注意 ,我們的 縮放 矩陣現在新的座標系的正交基向量分別爲:
X=(sclaex000),Y=(0sclaey00),Z=(00sclaez0) \overrightarrow X= \left(\begin{matrix} \color{#ff0000} sclae_x\\ \color{#008800} 0\\ \color{#0000ff} 0\\ \color{#bb00bb} 0 \end{matrix}\right), \overrightarrow Y= \left(\begin{matrix} \color{#ff0000} 0\\ \color{#008800} sclae_y\\ \color{#0000ff} 0\\ \color{#bb00bb} 0 \end{matrix}\right), \overrightarrow Z= \left(\begin{matrix} \color{#ff0000} 0\\ \color{#008800} 0\\ \color{#0000ff} sclae_z\\ \color{#bb00bb} 0 \end{matrix}\right)

旋轉矩陣

旋轉矩陣 變換 這篇裏面沒有詳細講解,他也是建議想深入瞭解的朋友去看 可汗學院 的教程。

他列出了矩陣公式:
在這裏插入圖片描述

上面的公式,可能基礎稍微缺一些的同學,可能就會看懵

以:沿Z軸旋轉的矩陣爲例:
[cosθsinθ00sinθcosθ0000100001](xyz1)=(cosθxsinθysinθx+cosθyz1) \begin{bmatrix} \color{#ff0000} \cos\theta & \color{#ff0000} -\sin\theta & \color{#ff0000} 0 & \color{#ff0000}0\\ \color{#008800} \sin\theta & \color{#008800}\cos\theta & \color{#008800} 0 & \color{#008800}0\\ \color{#0000ff} 0 & \color{#0000ff}0 & \color{#0000ff} 1 & \color{#0000ff}0\\ \color{#bb00bb} 0 & \color{#bb00bb}0 & \color{#bb00bb} 0 & \color{#bb00bb}1 \end{bmatrix}\cdot \left(\begin{matrix} x\\ y\\ z\\ 1 \end{matrix}\right)= \left(\begin{matrix} {\color{#ff0000} \cos\theta} \cdot x {\color{#ff0000} -\sin\theta} \cdot y\\ {\color{#008800} \sin\theta} \cdot x + {\color{#008800} \cos\theta} \cdot y\\ z\\ 1 \end{matrix}\right)

在這裏插入圖片描述

先從單個 X\color{#ff0000}X 點的旋轉情況來看,現在在 XY 平面中,有兩個點:XsourceXX_{source},\color{#ff0000}X

其實還有一個 Z 軸,但是與 XY 平面垂直了,在畫面中值呈現於一個奇點(與原點OO重合了),就不畫出來了。

該 Z 軸的增量方向(正方向)指向着屏幕內的方向,所以整個座標是一個:左手座標。

XsourceX_{source}X\color{#ff0000}X 點的原始位置,爲了直觀,我將 XsourceXX_{source},\color{#ff0000}X 兩個點的位置,用了向量鏈接着,向量的起點在原點,終點分別指向兩個點。

上圖中,左上角分別是:degree(角度)=20,radius(XsourceX_{source}點與原點的半徑距離)=1。

這時如果我們調整 degree的話,就可以看到旋轉的過程:
在這裏插入圖片描述

因爲 radius 等於 1,我們可以不先,這樣可以讓圖像更清晰一些:
在這裏插入圖片描述

可以看到:原來在 X 軸上的 XsourceX_{source} 點作爲:(1,0),在旋轉後就變成了:X(cosd,sind)\color{#ff0000}X(\cos d, \sin d)(這個三角函數的知識點在初中就學過,希望你還沒有忘記)。

我們可以把上面的XsourceX_{source}點當做是X軸的單位軸。

在回頭看看之前的沿着Z軸的旋轉矩陣:
[cosθsinθ00sinθcosθ0000100001](xyzw)=(cosθxsinθysinθx+cosθyzw) \begin{bmatrix} \color{#ff0000} \cos\theta & \color{#ff0000} -\sin\theta & \color{#ff0000} 0 & \color{#ff0000}0\\ \color{#008800} \sin\theta & \color{#008800}\cos\theta & \color{#008800} 0 & \color{#008800}0\\ \color{#0000ff} 0 & \color{#0000ff}0 & \color{#0000ff} 1 & \color{#0000ff}0\\ \color{#bb00bb} 0 & \color{#bb00bb}0 & \color{#bb00bb} 0 & \color{#bb00bb}1 \end{bmatrix}\cdot \left(\begin{matrix} x\\ y\\ z\\ w \end{matrix}\right)= \left(\begin{matrix} {\color{#ff0000} \cos\theta} \cdot x {\color{#ff0000} -\sin\theta} \cdot y\\ {\color{#008800} \sin\theta} \cdot x + {\color{#008800} \cos\theta} \cdot y\\ z\\ w \end{matrix}\right)

它其實對應着:
[Xxsinθ00Xycosθ0000100001](xyzw)=(XxxsinθyXyx+cosθyzw) \begin{bmatrix} \color{#ff0000} X軸_{x分量} & \color{#ff0000} -\sin\theta & \color{#ff0000} 0 & \color{#ff0000}0\\ \color{#008800} X軸_{y分量} & \color{#008800}\cos\theta & \color{#008800} 0 & \color{#008800}0\\ \color{#0000ff} 0 & \color{#0000ff}0 & \color{#0000ff} 1 & \color{#0000ff}0\\ \color{#bb00bb} 0 & \color{#bb00bb}0 & \color{#bb00bb} 0 & \color{#bb00bb}1 \end{bmatrix}\cdot \left(\begin{matrix} x\\ y\\ z\\ w \end{matrix}\right)= \left(\begin{matrix} {\color{#ff0000} X軸_{x分量}} \cdot x {\color{#ff0000} -\sin\theta} \cdot y\\ {\color{#008800} X軸_{y分量} } \cdot x + {\color{#008800} \cos\theta} \cdot y\\ z\\ w \end{matrix}\right)

同樣的,我們將Y軸的單位軸添加上,也添加上對應旋轉後的Y軸方向
在這裏插入圖片描述
它其實就是對應着矩陣:
[XxYx00XyYy0000100001](xyzw)=(Xxx+YxyXyx+Yyyzw) \begin{bmatrix} \color{#ff0000} X軸_{x分量} & \color{#ff0000} Y軸_{x分量} & \color{#ff0000} 0 & \color{#ff0000}0\\ \color{#008800} X軸_{y分量} & \color{#008800} Y軸_{y分量} & \color{#008800} 0 & \color{#008800}0\\ \color{#0000ff} 0 & \color{#0000ff}0 & \color{#0000ff} 1 & \color{#0000ff}0\\ \color{#bb00bb} 0 & \color{#bb00bb}0 & \color{#bb00bb} 0 & \color{#bb00bb}1 \end{bmatrix}\cdot \left(\begin{matrix} x\\ y\\ z\\ w \end{matrix}\right)= \left(\begin{matrix} {\color{#ff0000} X軸_{x分量}} \cdot x + {\color{#ff0000} Y軸_{x分量}} \cdot y\\ {\color{#008800} X軸_{y分量}} \cdot x + {\color{#008800} Y軸_{y分量}} \cdot y\\ z\\ w \end{matrix}\right)

這時可以確定:
X軸

  • Xx=cosd{\color{#ff0000} X軸_{x分量}}=\cos d
  • Xy=sind{\color{#008800} X軸_{y分量}}=\sin d

Y軸

  • Yx=sind{\color{#ff0000} Y軸_{x分量}}=-\sin d
  • Yy=cosd{\color{#008800} Y軸_{y分量}}=\cos d

我們的任意點:
(xyzw)\left(\begin{matrix} x\\ y\\ z\\ w \end{matrix}\right)

現在新的X、Y軸向爲:
X=(Xx,Xy)=(cosd,sind)X軸=({\color{#ff0000} X軸_{x分量}},{\color{#008800} X軸_{y分量}})=({\color{#ff0000}\cos d}, {\color{#008800} \sin d})
Y=(Yx,Yy)=(sind,cosd)Y軸=({\color{#ff0000} Y軸_{x分量}},{\color{#008800} Y軸_{y分量}})=({\color{#ff0000} -\sin d},{\color{#008800} \cos d})

注意 ,我們的 旋轉 矩陣現在新的座標系的 正交基向量 分別爲:
X=(XxXy00),Y=(YxYy00),Z=(0010) \overrightarrow X= \left(\begin{matrix} \color{#ff0000} X軸_{x分量}\\ \color{#008800} X軸_{y分量}\\ \color{#0000ff} 0\\ \color{#bb00bb} 0 \end{matrix}\right), \overrightarrow Y= \left(\begin{matrix} \color{#ff0000} Y軸_{x分量}\\ \color{#008800} Y軸_{y分量}\\ \color{#0000ff} 0\\ \color{#bb00bb} 0 \end{matrix}\right), \overrightarrow Z= \left(\begin{matrix} \color{#ff0000} 0\\ \color{#008800} 0\\ \color{#0000ff} 1\\ \color{#bb00bb} 0 \end{matrix}\right)

然是沿着X,Y軸旋轉的原理是一樣的,就不再說明了。

在 Shader 中實現一些旋轉

着色器中使用矩陣來旋轉紋理、頂點、等效果

旋轉頂點

Shader 的代碼:

// jave.lin - testing_matrix_rotate_vertex.vert - 測試 shader 矩陣旋轉頂點着色器
#version 450 compatibility
uniform float time;
attribute vec3 vPos;
attribute vec2 vUV;
varying vec2 fUV;
void main() {

#define ROT_TYPE 3					// 有3種方式應用旋轉

#if ROT_TYPE == 1
	float s = 0, c = 0;
	// sincos(time, s, c); 			// glsl 沒有類似 cg 的 sincos
	s = sin(time);
	c = cos(time);
	vec3 p = vec3(					// 非矩陣方式,直接矩陣的軸數據拿出來計算
		c * vPos.x - s * vPos.y,
		s * vPos.x + c * vPos.y,
		vPos.z
	);

	gl_Position = vec4(p, 1.0);
#elif ROT_TYPE == 2
	float s = 0, c = 0;
	// sincos(time, s, c); 			// glsl 沒有類似 cg 的 sincos
	s = sin(time);
	c = cos(time);
	vec3 p = vPos;
	mat2 rM = mat2(c, s, -s, c); 	// 主列設置,即:先設置的是每一列的向量軸
	/*
	// 或是寫成下面的方式更好理解全文介紹矩陣的旋轉的 正交基向量,作爲座標軸
	// 我們只要調整座標軸,即可調整屬於這個座標以下的向量做變換
	vec2 x_a = vec2( c, s);			// X 軸
	vec2 y_a = vec2(-s, c);			// Y 軸
	mat2 rM = mat2(					// X,Y軸組成的座標系,或叫矩陣變換
	//	X軸			Y軸
		x_a, 		y_a
	);
	*/
	p.xy = rM * p.xy;
	gl_Position = vec4(p, 1.0);
#else
	float s = 0, c = 0;
	// sincos(time, s, c); 			// glsl 沒有類似 cg 的 sincos
	s = sin(time);
	c = cos(time);
	vec3 p = vPos;
	// 或是寫成下面的方式更好理解全文介紹矩陣的旋轉的 正交基向量,作爲座標軸
	// 我們只要調整座標軸,即可調整屬於這個座標以下的向量做變換
	vec2 x_a = vec2( c, s);			// X 軸
	vec2 y_a = vec2(-s, c);			// Y 軸
	mat2 rM = mat2(					// X,Y軸組成的座標系,或叫矩陣變換
	//	X軸			Y軸
		x_a, 		y_a
	);
	p.xy = rM * p.xy;
	gl_Position = vec4(p, 1.0);
#endif
	fUV = vUV;
}

// jave.lin - testing_matrix_rotate_vertex.frag - 測試 shader 矩陣旋轉片段着色器
#version 450 compatibility
varying vec2 fUV;					// uv 座標
varying vec3 fPos;

uniform sampler2D main_tex;			// 主紋理
uniform sampler2D mask_tex;			// 遮罩紋理
uniform sampler2D flash_light_tex;	// 閃光/流光紋理

uniform float time;					// 時間(秒)用於動畫

void main() {
	vec3 mainCol 	= texture(main_tex, fUV).rgb;
	float mask 		= texture(mask_tex, fUV).r;
	vec4 flashCol 	= texture(flash_light_tex, fUV + vec2(-time, 0));
	flashCol *= flashCol.a * mask;
	mainCol 		= mainCol + flashCol.rgb;
	gl_FragColor 	= vec4(mainCol, 1.0);
}

fragment shader 片段着色器的代碼不用看,因爲和上一節的一樣。

主要我們改動的是 vertex shader 頂點着色器。

在代碼中,註釋寫的非常的清楚 關於矩陣旋轉的 三個寫法本質 上是 同一 種處理。

運行效果

在這裏插入圖片描述

旋轉紋理 UV

這回不旋轉頂點,改而旋轉 UV 紋理座標,我選擇在片段着色器處理。

Shader 代碼:

// jave.lin - testing_matrix_rotate_uv.vert - 測試矩陣旋轉 UV 紋理座標,頂點着色器不需要處理
#version 450 compatibility
attribute vec3 vPos;
attribute vec2 vUV;
varying vec2 fUV;
varying vec3 fPos;
void main() {
	gl_Position = vec4(vPos, 1.0);
	fUV = vUV;
	fPos = vPos;
}

// jave.lin - testing_matrix_rotate_uv.vert - 測試矩陣旋轉 UV 紋理座標的片段着色器
#version 450 compatibility
varying vec2 fUV;					// uv 座標
varying vec3 fPos;

uniform sampler2D main_tex;			// 主紋理
uniform sampler2D mask_tex;			// 遮罩紋理
uniform sampler2D flash_light_tex;	// 閃光/流光紋理

uniform float time;					// 時間(秒)用於動畫

void main() {

	float t = (sin(time) * 0.5 + 0.5) * 3.14159265359 * 2;
	// t *= length(fPos);				// 半徑越大旋轉越多
	// t *= 1 / length(fPos);		// 半徑約小旋轉越多
	vec2 uv = fUV;
	uv -= 0.5;
	uv = vec2(
		cos(t) * uv.x - sin(t) * uv.y,
		sin(t) * uv.x + cos(t) * uv.y
	);
	uv += 0.5;

	vec3 mainCol 	= texture(main_tex, uv).rgb;
	float mask 		= texture(mask_tex, uv).r;
	vec4 flashCol 	= texture(flash_light_tex, uv + vec2(-time, 0));
	flashCol *= flashCol.a * mask;
	mainCol 		= mainCol + flashCol.rgb;
	gl_FragColor 	= vec4(mainCol, 1.0);
	// gl_FragColor = vec4(uv,0,1);
}

運行效果

在這裏插入圖片描述
調整一下控制參數,實現按半徑大小來控制旋轉量:
在這裏插入圖片描述
運行效果:
在這裏插入圖片描述

調整參數:
在這裏插入圖片描述
使用 半徑越小旋轉越大的方式,再次運行效果:
在這裏插入圖片描述
最後我們使用UV座標來當做顏色來輸出看到數據,一般調式 shader 常用方法之一:
在這裏插入圖片描述
在運行看看:
在這裏插入圖片描述

總結

3D 中的矩陣的線性變換引用是非常廣泛的,建議理解其中的意義,對於後續製作其他的效果是很有幫助的。

之所以用矩陣,因爲方便。

因爲照樣可以用一堆零散的變量來存貯線性變換方程組裏的每個常量,每個變換就是一個方程組的係數,我只要讓向量都應用這些係數來作爲一次變換,一樣是可以的。

但用使用矩陣後,書寫上會變得很方便。

不要認爲矩陣有多神奇,它其實是古代數學家、物理學家、等,先輩在計算線性變換時,想到的一個方便計算的數學工具而已。

其實我現在才發現自己還是挺喜歡數學的,因爲現在很多要製作的內容,或是工作需要,都是需要數學的,現在只能在空閒的時候偶爾去看看一些零散的資料,好在現在發現 可汗學院 的官方網,好後悔以前數學沒學好啊,-_-!!!我這隻能算初中水平的,也忘記的七七八八了,也很佩服那些數學系的。

References

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章