OpenGL(三)矩陣的基本使用

前言

在就計(jì)算機(jī)視覺圖形學(xué)中,矩陣是十分常見的計(jì)算單位。那么在OpenGL的學(xué)習(xí)中,矩陣的運(yùn)算肯定是必不可少,因此本文將稍微總結(jié)一下OpenGL中使用矩陣來(lái)完成一些稍微復(fù)雜一點(diǎn)效果。

通過(guò)前面幾篇文章的學(xué)習(xí),大致已經(jīng)明白了OpenGL的基本開發(fā)流程。了解OpenGL如何繪制,但是更多復(fù)雜的效果不可能通過(guò)如此之多的紋理,頂點(diǎn)去完成,我們需要一個(gè)更好的工具去處理圖片效果,這個(gè)工具就是數(shù)學(xué)上的矩陣和向量。

如果遇到問題可以在這里http://www.lxweimin.com/p/4b7c0d59c87c找到本人,歡迎討論

正文

我將不會(huì)再一次花大量的篇幅重新介紹向量和矩陣,這里僅僅只是把常用的向量和矩陣操作過(guò)一遍。

向量

向量最基本的定義是一個(gè)方向,往哪里走,走到哪里。更加正式的來(lái)說(shuō),向量包含一個(gè)方向一個(gè)大小,如下:


image.png

這里有三個(gè)向量,能看到w,n,v都從各自的起點(diǎn)指向各自的終點(diǎn),并且能夠很輕易的算出其長(zhǎng)度。

一個(gè)向量可以表示:\vec{a} = ( \begin{matrix} x\\ y \\z \end{matrix} )

向量的計(jì)算

向量加減

向量加減,實(shí)際上就是對(duì)向量中每個(gè)分量進(jìn)行加減
( \begin{matrix} 1\\ 2 \\3 \end{matrix} ) + x = ( \begin{matrix} 1+x\\ 2+x \\3+x \end{matrix} )

向量之間的加:
(\begin{matrix} 1\\ 2 \\3 \end{matrix} ) + ( \begin{matrix} 4\\ 5 \\ 6 \end{matrix} ) = ( \begin{matrix} 1+4\\ 2+5 \\3+6 \end{matrix} )

向量之間的加減在幾何上意義如下:


image.png

減法可以看成加一個(gè)負(fù)數(shù):
( \begin {matrix} 1\\ 2 \\3 \end{matrix} ) + ( \begin{matrix} -4\\ -5 \\- 6 \end{matrix} ) =( \begin{matrix} 1\\ 2 \\3 \end{matrix} ) - ( \begin{matrix} 4\\ 5 \\ 6 \end{matrix} ) = ( \begin{matrix} 1-4\\ 2-5 \\3-6 \end {matrix} )

向量的長(zhǎng)度

我們使用勾股定理(Pythagoras Theorem)來(lái)獲取向量的長(zhǎng)度(Length)/大小(Magnitude)。如果你把向量的x與y分量畫出來(lái),該向量會(huì)和x與y分量為邊形成一個(gè)三角形:


image.png

我們可以依據(jù)勾股定理把v向量的長(zhǎng)度計(jì)算出來(lái):
||v|| = \sqrt{x^{2} + y^2}

向量的乘法

向量的乘法有兩部分,點(diǎn)乘(內(nèi)積),叉乘(外積)。

點(diǎn)乘:

\vec{v} \cdot \vec{k} = ||v|| \cdot ||k|| \cdot cos\theta

如果用分量來(lái)表示其運(yùn)算,就是每個(gè)分量之間相乘最后相加:
(\begin{matrix} 1\\ 2 \\3 \end{matrix} ) \cdot ( \begin{matrix} 3\\ 4 \\5 \end{matrix}) = (1*3)+(2*4)+(3*5)

幾何意義:

image.png

假設(shè)有兩個(gè)向量a,b.如果這兩個(gè)向量做點(diǎn)乘:

因?yàn)辄c(diǎn)乘符合乘法交換律:可以擴(kuò)起后面,能從涂上看到后面括號(hào)那部分實(shí)際上就是b在a上的投影,也就是上圖的。

所以a_{0}和a是一個(gè)方向上的,因此將會(huì)符合乘法交換律等基礎(chǔ)性質(zhì)。這些不多展開論述。

叉乘:

\vec{v} \times \vec{k} = ||v|| \times ||k|| \times sin\theta

如果用分量來(lái)計(jì)算的就是如下:
假設(shè)有向量A和B,從左到右的排開每個(gè)向量的分量,每一行代表一個(gè)向量:
|\begin{matrix} x & y & z\\ A_{x} & A_{y} & A_{z} \\ B_{x} & B_{y} & B_{z} \end{matrix} |

A \times B = ( \begin{matrix} A_{x}\\ A_{y} \\ A{z} \end{matrix} ) \times ( \begin{matrix} B_{x}\\ B_{y} \\ B_{z} \end{matrix} ) = ( \begin{matrix} A_{y} \times B_{z} - A_{z} \times B_{y} \\ A_{x} \times B_{z} - A{z} \times B_{x} \\ A_{x} \times B_{y} - A_{y} \times B_{x} \end{matrix} )

實(shí)際上,我高中第一次學(xué)的時(shí)候,感覺不太好記叉乘。其實(shí)把上面那個(gè)行列式寫出來(lái),就很好記住了。計(jì)算那一列的數(shù)據(jù),就獲取另外兩列分量,做交叉相乘以及相減。

幾何上的意義:


image.png

從上圖能夠明白實(shí)際上向量a叉乘b,就是找一個(gè)向量同時(shí)垂直于向量a和向量b的向量。實(shí)際上這個(gè)向量就是垂直于a和b構(gòu)成的平面。

這里就不再贅述推導(dǎo)過(guò)程。

矩陣

簡(jiǎn)單來(lái)說(shuō)矩陣就是一個(gè)矩形的數(shù)字、符號(hào)或表達(dá)式數(shù)組。矩陣中每一項(xiàng)叫做矩陣的元素.(最初的誕生是為了解決多元方程式)
下面是一個(gè)2×3矩陣的例子:
\left[ \begin{matrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{matrix} \right]
矩陣可以通過(guò)(i, j)進(jìn)行索引,i是行,j是列,這就是上面的矩陣叫做2×3矩陣的原因(3列2行,也叫做矩陣的維度(Dimension))。這與你在索引2D圖像時(shí)的(x, y)相反,獲取4的索引是(2, 1)(第二行,第一列)(譯注:如果是圖像索引應(yīng)該是(1, 2),先算列,再算行)。

矩陣的加減法

矩陣和標(biāo)量相加
\left[\begin{matrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{matrix} \right] +3 = \left[\begin{matrix} 1+3 & 2+3 & 3+3 \\ 4+3 & 5+3 & 6+3 \end{matrix} \right]

矩陣之間相加,必須是矩陣行列數(shù)相等才能互相相加:
\left [\begin{matrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{matrix} \right] + \left[\begin{matrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end {matrix} \right] = \left[\begin{matrix} 1+1 & 2+2 & 3+3 \\ 4+4 & 5+5 & 6+6 \end{matrix} \right]

減法也是類似。

矩陣的乘法

矩陣乘法分為2部分,數(shù)乘和相乘。

數(shù)乘

矩陣和標(biāo)量相乘,矩陣與標(biāo)量之間的乘法也是矩陣的每一個(gè)元素分別乘以該標(biāo)量
\left [\begin{matrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{matrix} \right] \cdot 3 = \left[\begin{matrix} 1 \cdot 3 & 2 \cdot 3 & 3 \cdot 3 \\ 4 \cdot 3 & 5 \cdot 3 & 6 \cdot3 \end{matrix} \right]

現(xiàn)在我們也就能明白為什么這些單獨(dú)的數(shù)字要叫做標(biāo)量(Scalar)了。簡(jiǎn)單來(lái)說(shuō),標(biāo)量就是用它的值縮放(Scale)矩陣的所有元素

矩陣的乘法

矩陣之間的乘法不見得有多復(fù)雜,但的確很難讓人適應(yīng)。矩陣乘法基本上意味著遵照規(guī)定好的法則進(jìn)行相乘。當(dāng)然,相乘還有一些限制:

1.只有當(dāng)左側(cè)矩陣的列數(shù)與右側(cè)矩陣的行數(shù)相等,兩個(gè)矩陣才能相乘。
2.矩陣相乘不遵守交換律(Commutative),也就是說(shuō)A?B≠B?A。

直接來(lái)看矩陣相乘的例子:
\left[\begin{matrix} 1 & 2 \\ 3 & 4 \end{matrix} \right] \cdot \left[\begin{matrix} 5 & 6 \\ 7 & 8 \end{matrix} \right] = \left[\begin{matrix} 1*5+2*7 & 1*6+2*8 \\ 3*5+4*7 & 3*6+4*8 \end{matrix} \right]

image.png

實(shí)際上計(jì)算過(guò)程就是矩陣的第1個(gè)元素就是第一行乘以第一列每個(gè)元素積的和。擴(kuò)展一下就是如下公式:

矩陣還有除法也就是矩陣的逆,本文沒有涉及,就不多介紹。

單位矩陣

實(shí)際上就是一個(gè)斜對(duì)角全是1,其他都是0的矩陣,數(shù)學(xué)上叫做I

在OpenGL中,由于某些原因我們通常使用4×4的變換矩陣,而其中最重要的原因就是大部分的向量都是4分量的
\left[ \begin{matrix} 1 & 0 & 0 & 0\\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end {matrix} \right]
這個(gè)矩陣的特性很有趣,任何矩陣乘以單位矩陣都等于原來(lái)的矩陣

縮放

對(duì)一個(gè)向量進(jìn)行縮放(Scaling)就是對(duì)向量的長(zhǎng)度進(jìn)行縮放,而保持它的方向不變。由于我們進(jìn)行的是2維或3維操作,我們可以分別定義一個(gè)有2或3個(gè)縮放變量的向量,每個(gè)變量縮放一個(gè)軸(x、y或z)。

假如我們嘗試縮放\vec{v} = (3,2),沿著x軸方向縮小0.5倍數(shù),沿著y軸放大2倍。

image.png

由于OpenGL通常在3d空間內(nèi)操作,那么我們只要把z軸縮放設(shè)置為1就沒有任何影響。

這種x軸和y軸縮放比例不一致的叫做不均勻縮放,而一致稱為均勻縮放。實(shí)際上這個(gè)過(guò)程能夠通過(guò)矩陣去完成。

還記得上面的單位矩陣吧。只要把縮放系數(shù)放到矩陣中對(duì)應(yīng)1的位置就能控制對(duì)應(yīng)軸的縮放系數(shù)。
\left[ \begin{matrix} Sx & 0 & 0 & 0\\ 0 & Sx & 0 & 0 \\ 0 & 0 & Sz & 0 \\ 0 & 0 & 0 & 1 \end{matrix} \right] \cdot \left[\begin{matrix} x \\ y \\ z \\ 1 \end{matrix} \right] = \left[\begin{matrix} x \cdot Sx \\ y \cdot Sy \\ z \cdot Sz \\ 1 \end{matrix} \right]

最后一個(gè)是w是構(gòu)造3d模型,透視時(shí)候用的。暫時(shí)沒涉及,就不細(xì)說(shuō)。

位移

位移(Translation)是在原始向量的基礎(chǔ)上加上另一個(gè)向量從而獲得一個(gè)在不同位置的新向量的過(guò)程,從而在位移向量基礎(chǔ)上移動(dòng)了原始向量。我們已經(jīng)討論了向量加法,所以這應(yīng)該不會(huì)太陌生。

和縮放矩陣一樣,在4×4矩陣上有幾個(gè)特別的位置用來(lái)執(zhí)行特定的操作,對(duì)于位移來(lái)說(shuō)它們是第四列最上面的3個(gè)值。如果我們把位移向量表示為(Tx,Ty,Tz),我們就能把位移矩陣定義為:
\left [ \begin{matrix} 1 & 0 & 0 & Tx\\ 0 & 1 & 0 & Ty \\ 0 & 0 & 1 & Tz \\ 0 & 0 & 0 & 1 \end {matrix} \right] \cdot \left[\begin{matrix} x \\ y \\ z \\ 1 \end{matrix} \right] = \left[\begin{matrix} x +Tx \\ y + Ty \\ z + Tz \\ 1 \end{matrix} \right]

旋轉(zhuǎn)

上面幾個(gè)的變換內(nèi)容相對(duì)容易理解,在2D或3D空間中也容易表示出來(lái),但旋轉(zhuǎn)(Rotation)稍復(fù)雜些。

旋轉(zhuǎn)對(duì)于剛?cè)腴T的人來(lái)說(shuō)是比較新鮮的東西。這里稍微寫一下旋轉(zhuǎn)的證明,我也花了點(diǎn)時(shí)間,證明了一遍,這邊也算是一次總結(jié)。

引入復(fù)數(shù)

為了證明旋轉(zhuǎn),我們會(huì)引入復(fù)數(shù)作為輔助。復(fù)數(shù)是什么?復(fù)數(shù)包含兩個(gè)部分,一個(gè)實(shí)數(shù)部分,一個(gè)虛數(shù)部分,寫法如下:
z = a + bi
a是一個(gè)實(shí)數(shù),bi是一個(gè)虛數(shù)。i是什么?定義i^2 = -1

為什么使用復(fù)數(shù)來(lái)輔助,以前我剛學(xué)習(xí)的時(shí)候不懂。實(shí)際上在我們常用的物理學(xué),數(shù)學(xué),需要保留二維的信息的時(shí)候,往往需要復(fù)數(shù)來(lái)計(jì)算,因?yàn)閺?fù)數(shù)本身性質(zhì)決定的,復(fù)數(shù)本身相加,相乘只允許實(shí)數(shù)和虛數(shù)分開計(jì)算,舉個(gè)例子:
z1 = a + bi,z2 = c + di
z1+z2 = (a+b) + (b+d)i

這樣就能保留兩個(gè)不同的信息了。實(shí)際上也像極了向量/矩陣相加。

復(fù)數(shù)和矩陣的關(guān)系

從上面的公式,直覺上告訴我們復(fù)數(shù)的計(jì)算一定和矩陣元算相關(guān),讓我們探索一下復(fù)數(shù)和矩陣之間的關(guān)系。就以復(fù)數(shù)乘法為例子:
z1 = a + bi,z2 = c + di
z1 * z2 =ac + adi + cbi + bdi^2 = (ac - bd)+(ad + cb)i

如果我們把這個(gè)結(jié)果看成矩陣運(yùn)算將會(huì)是如下一個(gè)矩陣運(yùn)算,把矩陣第一行運(yùn)算看成實(shí)部,第二行運(yùn)算看成虛部:
\left[ \begin{matrix} a & -b \\ b & a\\ \end{matrix} \right] \cdot \left[\begin{matrix} c \\ d \end{matrix} \right]

就不難看出,實(shí)際上復(fù)數(shù)的元算就是對(duì) 下面這個(gè)矩陣做變換運(yùn)算\left[ \begin{matrix} a & -b \\ b & a\\ \end{matrix} \right]

旋轉(zhuǎn)的證明

先給出一個(gè)復(fù)數(shù)在復(fù)平面中表現(xiàn):


image.png

能看到在這個(gè)復(fù)平面中復(fù)數(shù)z的表示就是z = a + bi,這個(gè)向量的長(zhǎng)度根據(jù)勾股定理很容易就求出來(lái)。

我們嘗試著對(duì)復(fù)數(shù)的矩陣進(jìn)行一次變形,目的就是為創(chuàng)造出一個(gè)角度和復(fù)數(shù)之間的關(guān)系,把每一項(xiàng)都除以/ \sqrt{a^2 + b^2},提取出可以勾股定理創(chuàng)造出來(lái)的角度:
\sqrt{a^2 + b^2} \cdot \left[ \begin{matrix} a / \sqrt{a^2 + b^2}& -b / \sqrt{a^2 + b^2} \\ b / \sqrt{a^2 + b^2} & a / \sqrt{a^2 + b^2}\\ \end{matrix} \right]

根據(jù)勾股定理,可以把元算中每一項(xiàng)轉(zhuǎn)化如下:
\sqrt{a^2 + b^2} \cdot \left[ \begin{matrix} cos(\theta) & -sin(\theta) \\ sin(\theta) & cos(\theta)\\ \end{matrix} \right]

很有趣,這樣就構(gòu)建出了角度的關(guān)系了。有了這些還不足。

矩陣的左側(cè)還是有冗余的東西,我們想辦法干掉它。此時(shí)很巧的是,矩陣右側(cè)剛好就是這個(gè)復(fù)平面向量的模(長(zhǎng)度)。

因此可以化簡(jiǎn)如下:
||z|| \cdot \left[ \begin{matrix} cos(\theta) & -sin(\theta) \\ sin(\theta) & cos(\theta)\\ \end{matrix} \right]

又因?yàn)閱挝痪仃嘔乘以任何矩陣還是原來(lái)的矩陣:
||z|| \cdot\left[ \begin{matrix} 1 & 0 \\ 0 & 1\\ \end {matrix} \right] \cdot\left[ \begin{matrix} cos(\theta) & -sin(\theta) \\ sin(\theta) & cos(\theta)\\ \end {matrix} \right] = \left[ \begin{matrix} ||z|| & 0 \\ 0 & ||z||\\ \end{matrix} \right] \cdot\left[ \begin{matrix} cos(\theta) & -sin(\theta) \\ sin(\theta) & cos(\theta)\\ \end{matrix} \right]

實(shí)際上這個(gè)結(jié)果就是3d的旋轉(zhuǎn)縮放矩陣。不信?我們?cè)囋噧蓚€(gè)在復(fù)平面上的向量(0,1),(1,0)。
\left[ \begin{matrix} ||z|| & 0 \\ 0 & ||z||\\ \end{matrix} \right] \cdot\left[ \begin{matrix} cos(\theta) & -sin(\theta) \\ sin(\theta) & cos(\theta)\\ \end{matrix} \right] \cdot \left[ \begin{matrix} 0 \\ 1 \\ \end{matrix} \right] = \left[ \begin{matrix} -||z||sin(\theta) \\ ||z||cos(\theta) \\ \end{matrix} \right]

\left[ \begin{matrix} ||z|| & 0 \\ 0 & ||z||\\ \end{matrix} \right] \cdot\left[ \begin{matrix} cos(\theta) & -sin(\theta) \\ sin(\theta) & cos(\theta)\\ \end{matrix} \right] \cdot \left[ \begin{matrix} 1 \\ 0 \\ \end{matrix} \right] = \left[ \begin{matrix} ||z||cos(\theta) \\ ||z||sin(\theta) \\ \end{matrix} \right]

當(dāng)復(fù)平面上的z長(zhǎng)度為1時(shí)候,如下圖:


image.png

因此下面這個(gè)矩陣是旋轉(zhuǎn)時(shí)候的縮放矩陣:
\left[ \begin{matrix} ||z|| & 0 \\ 0 & ||z||\\ \end{matrix} \right]

下面這個(gè)是旋轉(zhuǎn)矩陣:
\left[ \begin{matrix} cos(\theta) & -sin(\theta) \\ sin(\theta) & cos(\theta)\\ \end{matrix} \right]

用復(fù)數(shù)表示如下:
||z||(cos(\theta) - sin(\theta) i)

那么我們可以由2d往3d推,可以很輕易得到如下三種情況:
當(dāng)沿著x軸旋轉(zhuǎn),下面矩陣稱為R_{x}
\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] \cdot \left[\begin{matrix} x \\ y \\ z \\ 1 \end{matrix} \right] = \left[\begin{matrix} x \\ cosθ?y?sinθ?z \\ sinθ?y+cosθ?z\\ 1 \end{matrix} \right]

當(dāng)沿著y軸旋轉(zhuǎn),下面矩陣稱為R_{y}
\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] \cdot \left[\begin{matrix} x \\ y \\ z \\ 1 \end{matrix} \right] = \left[\begin{matrix} cosθ?x+sinθ?z \\ y \\ ?sinθ?x+cosθ?z \\ 1 \end{matrix} \right]

當(dāng)沿著z軸旋轉(zhuǎn),下面矩陣稱為R_{z}
\left[ \begin{matrix} cos(\theta) & -sin(\theta) & 0 & 0\\ sin(\theta)& cos(\theta)&0& 0 \\ 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 1 \end {matrix} \right] \cdot \left[\begin{matrix} x \\ y \\ z \\ 1\\ \end{matrix} \right] = \left[\begin{matrix} cosθ?x-sinθ?y \\ sinθ?x+cosθ?y \\ z \\ 1 \end{matrix} \right]

有了這三個(gè)基礎(chǔ)矩陣之后,我們可以做任意變化,比如先旋轉(zhuǎn)z軸,再旋轉(zhuǎn)x軸,最后旋轉(zhuǎn)y軸。也就是把這三個(gè)矩陣從右到左乘起來(lái),但是又因?yàn)榭梢赞D(zhuǎn)為復(fù)數(shù),而復(fù)數(shù)符合乘法交換律,因此先轉(zhuǎn)動(dòng)哪一個(gè)都沒問題。

換句話說(shuō)就是,R_{復(fù)合旋轉(zhuǎn)} = R_{x} \cdot R_{y} \cdot R_{z}

因此可以得到如下這個(gè)復(fù)合矩陣:


image.png

是不是很討厭,很麻煩。更麻煩的在后面,這種基于歐拉角變換的旋轉(zhuǎn)很容易就出現(xiàn)了萬(wàn)向節(jié)死鎖。如果是做游戲動(dòng)畫的人一定對(duì)這個(gè)不陌生。

使用這種方式連續(xù)變換的時(shí)候,當(dāng)出現(xiàn)x,y,z其中兩個(gè)坐標(biāo)系在同一水平面時(shí)候,另外一個(gè)軸的旋轉(zhuǎn)范圍就被限制住了。
如下:


萬(wàn)向節(jié)死鎖.gif

如何解決呢?這個(gè)時(shí)候就需要四元數(shù)了。本文將不涉及四元數(shù),因此不做更多的詳解,后面將會(huì)和大家聊聊。不過(guò)記住了,上面引入復(fù)數(shù)進(jìn)行推到旋轉(zhuǎn)公式的方式將會(huì)運(yùn)用到四元數(shù)的推導(dǎo)中。本文先做一個(gè)鋪墊。

有了這些理論基礎(chǔ)之后,我們可以嘗試編寫代碼。

實(shí)戰(zhàn)演練

為了實(shí)踐上面問題,我們這邊繼續(xù)沿用上一篇文章的笑臉箱子的代碼,來(lái)實(shí)現(xiàn)三種效果,位移,旋轉(zhuǎn),縮放。

首先,我們要稍微改造一下原來(lái)的頂點(diǎn)著色器。開放一個(gè)uniform來(lái)操作頂點(diǎn)著色器中位置。

#version 330 core
layout(location = 0)in vec3 aPos;
layout(location = 1)in vec3 aColor;
layout(location = 2)in vec2 aTexCoord;

out vec3 ourColor;
out vec2 TexCoord;

uniform mat4 transform;

void main(){
    gl_Position = transform*vec4(aPos,1.0);
    ourColor = aColor;
    TexCoord = aTexCoord;
}

通過(guò)transform乘法來(lái)對(duì)位置進(jìn)行一次矩陣變換。因?yàn)樵贕LSL中已經(jīng)確定好了是mat4.因此在外面也要?jiǎng)?chuàng)造一個(gè)4維的矩陣。

操作一

先縮小一半,再繞著z軸90度旋轉(zhuǎn)。
根據(jù)上面的公式,無(wú)論是位移,旋轉(zhuǎn)還是縮放,我們只需要對(duì)著原矩陣依次做矩陣乘法即可。

先準(zhǔn)備一個(gè)單位矩陣:

GLfloat mat4[4][4] = {
            {1.0f,0.0f,0.0f,0.0f},
            {0.0f,1.0f,0.0f,0.0f},
            {0.0f,0.0f,1.0f,0.0f},
            {0.0f,0.0f,0.0f,1.0f}
        };

準(zhǔn)備一個(gè)縮放的矩陣:

GLfloat vec3[] = {
            //x    //y    //z
            0.5f,0.5f,1.0f
            
        };

根據(jù)公式,4維矩陣縮放操作:

void scaleMat4(GLfloat dst[4][4],GLfloat src[4][4],GLfloat* vec){
    
    dst[0][0] = src[0][0] * vec[0];
    dst[1][1] = src[1][1] * vec[1];
    dst[2][2] = src[2][2] * vec[3];
    dst[3][3] = src[3][3];

}

根據(jù)公式的乘積結(jié)果,我直接寫出沿著z軸旋轉(zhuǎn)的方法

void rotationZ(GLfloat dst[4][4],GLfloat src[4][4],double degree){
    double angle = PI * degree / 180.0;
    
    dst[0][0] = src[0][0]*cos(angle) - src[1][0]*sin(angle);
    dst[0][1] = src[0][1]*cos(angle) - src[1][1]*sin(angle);
    dst[0][2] = src[0][2]*cos(angle) - src[1][2]*sin(angle);
    dst[0][3] = src[0][3]*cos(angle) - src[1][3]*sin(angle);
    
    
    dst[1][0] = src[0][0]*sin(angle)  + src[1][0]*cos(angle);
    dst[1][1] = src[0][1]*sin(angle)  + src[1][1]*cos(angle);
    dst[1][2] = src[0][2]*sin(angle)  + src[1][2]*cos(angle);
    dst[1][3] = src[0][3]*sin(angle)  + src[1][3]*cos(angle);
    
    dst[2][0] = src[2][0];
    dst[2][1] = src[2][1];
    dst[2][2] = src[2][2];
    dst[2][3] = src[2][3];
    
    dst[3][0] = src[3][0];
    dst[3][1] = src[3][1];
    dst[3][2] = src[3][2];
    dst[3][3] = src[3][3];
}

此時(shí)我們?cè)谶M(jìn)入渲染loop之前依次調(diào)用:

        scaleMat4(result,mat4, vec3);
        rotationZ(dst,result,90.0);

        GLuint transformLoc = glGetUniformLocation(shader->ID,"transform");
        glUniformMatrix4fv(transformLoc,1,GL_FALSE,&dst[0][0]);

讀取uniform,并且把變換之后的矩陣首地址賦值給transform。由于GLSL中也是4維float型矩陣,剛好能夠正常解析。

image.png

這樣就能看到了沿著x,y軸縮小了一般,同時(shí)沿著z軸順時(shí)針旋轉(zhuǎn)了90度。

其實(shí)我這么寫旋轉(zhuǎn)還是有問題,因?yàn)槲抑苯佑?jì)算變換后的矩陣,直接賦值。并沒有很好的泛用性。每一次都要自己寫這么麻煩的矩陣計(jì)算,對(duì)于開發(fā)來(lái)說(shuō)不是很友好。

glm

還好有一個(gè)glm庫(kù),專門輔助計(jì)算矩陣,向量。而且全是頭文件,不需要編譯直接引入即可。稍微閱讀了源碼,實(shí)際上是挺簡(jiǎn)單的一個(gè)庫(kù),抽象了mat以及vec類,并且復(fù)寫里面的操作符。

用法很簡(jiǎn)單,同樣的,我們要引入如下頭文件:

#include"glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "glm/gtc/type_ptr.hpp"

初始化一個(gè)4維單位矩陣:

glm::mat4 trans = glm::mat4(1.0f);

接著做著一樣的縮放之后旋轉(zhuǎn)代碼:

        trans = glm::rotate(trans, glm::radians(90.0f),glm::vec3(0.0,0.0,1.0));
        trans = glm::scale(trans, glm::vec3(0.5f,0.5f,1.0f));
        GLuint transformLoc = glGetUniformLocation(shader->ID,"transform");
        glUniformMatrix4fv(transformLoc,1,GL_FALSE,&trans[0][0]);

scale縮放的api需要傳入一個(gè)向量,分別指的是x,y,z軸分別縮小放大多少,rotate旋轉(zhuǎn)api,需要傳遞一個(gè)旋轉(zhuǎn)的角度以及圍繞哪幾個(gè)軸旋轉(zhuǎn)。

此時(shí)是沿著z軸,倍數(shù)為1的旋轉(zhuǎn)。縮放為x,y縮小一般,軸不變

這樣就有如此結(jié)果


image.png

誒?奇怪了?怎么根據(jù)公式計(jì)算出來(lái)的是相反的呢?一個(gè)順時(shí)針旋轉(zhuǎn)了90度,一個(gè)逆時(shí)針旋轉(zhuǎn)了90度。

讓我們翻翻旋轉(zhuǎn)的源碼,實(shí)際上很簡(jiǎn)單:

template<typename T, qualifier Q>
    GLM_FUNC_QUALIFIER mat<4, 4, T, Q> rotate(mat<4, 4, T, Q> const& m, T angle, vec<3, T, Q> const& v)
    {
        T const a = angle;
        T const c = cos(a);
        T const s = sin(a);

        vec<3, T, Q> axis(normalize(v));
        vec<3, T, Q> temp((T(1) - c) * axis);

        mat<4, 4, T, Q> Rotate;
        Rotate[0][0] = c + temp[0] * axis[0];
        Rotate[0][1] = temp[0] * axis[1] + s * axis[2];
        Rotate[0][2] = temp[0] * axis[2] - s * axis[1];

        Rotate[1][0] = temp[1] * axis[0] - s * axis[2];
        Rotate[1][1] = c + temp[1] * axis[1];
        Rotate[1][2] = temp[1] * axis[2] + s * axis[0];

        Rotate[2][0] = temp[2] * axis[0] + s * axis[1];
        Rotate[2][1] = temp[2] * axis[1] - s * axis[0];
        Rotate[2][2] = c + temp[2] * axis[2];

        mat<4, 4, T, Q> Result;
        Result[0] = m[0] * Rotate[0][0] + m[1] * Rotate[0][1] + m[2] * Rotate[0][2];
        Result[1] = m[0] * Rotate[1][0] + m[1] * Rotate[1][1] + m[2] * Rotate[1][2];
        Result[2] = m[0] * Rotate[2][0] + m[1] * Rotate[2][1] + m[2] * Rotate[2][2];
        Result[3] = m[3];
        return Result;
    }

這里面實(shí)際上就是上面復(fù)合旋轉(zhuǎn)的公式。Rotate實(shí)際上是根據(jù)當(dāng)前傳進(jìn)來(lái)的向量對(duì)復(fù)合旋轉(zhuǎn)矩陣處理之后,再通過(guò)這個(gè)復(fù)合旋轉(zhuǎn)矩陣計(jì)算結(jié)果。

我們注意到一點(diǎn),所有關(guān)于z軸的計(jì)算全部從顛倒為負(fù)。這樣的話,我上面的公式實(shí)際上等效glm下面這份代碼:

trans = glm::rotate(trans, glm::radians(90.0f),glm::vec3(0.0,0.0,-1.0));

沿著z軸的負(fù)半段進(jìn)行旋轉(zhuǎn)。

至于為什么這么做,下一篇文章會(huì)揭曉。主要是因?yàn)樵贠penGL是右手坐標(biāo),向左邊旋轉(zhuǎn)才是在OpenGL的正向旋轉(zhuǎn)方向。

實(shí)戰(zhàn)演練二

我們嘗試著把它轉(zhuǎn)動(dòng)起來(lái),只需要讓uniform讀取的數(shù)據(jù)根據(jù)時(shí)間變化而變化。

        engine->loop(VAO, VBO, texture, 1,shader, [](Shader* shader,GLuint VAO,
                                                     GLuint* texture,GLFWwindow *window){
            

            
            glm::mat4 trans = glm::mat4(1.0f);
            trans = glm::translate(trans, glm::vec3(0.5f,-0.5f,0.0f));
//旋轉(zhuǎn)根據(jù)時(shí)間來(lái)
            trans = glm::rotate(trans, (float)glfwGetTime(), glm::vec3(0.0f,0.0f,1.0f));
            

            GLuint transformLoc = glGetUniformLocation(shader->ID,"transform");
            glUniformMatrix4fv(transformLoc,1,GL_FALSE,glm::value_ptr(trans));
            
            //箱子
            glActiveTexture(GL_TEXTURE0);
            glBindTexture(GL_TEXTURE_2D,texture[0]);
            
            //笑臉
            glActiveTexture(GL_TEXTURE1);
            glBindTexture(GL_TEXTURE_2D,texture[1]);
            glBindVertexArray(VAO);
            glDrawElements(GL_TRIANGLES,6,GL_UNSIGNED_INT,0);
            
        });

如下:


旋轉(zhuǎn)笑臉.gif

如果我們把旋轉(zhuǎn)和位移的順序變換了,會(huì)如何?

     glm::mat4 trans = glm::mat4(1.0f);
            trans = glm::rotate(trans, (float)glfwGetTime(), glm::vec3(0.0f,0.0f,1.0f));
            trans = glm::translate(trans, glm::vec3(0.5f,-0.5f,0.0f));
//旋轉(zhuǎn)根據(jù)時(shí)間來(lái)

            

            GLuint transformLoc = glGetUniformLocation(shader->ID,"transform");
            glUniformMatrix4fv(transformLoc,1,GL_FALSE,glm::value_ptr(trans));

如下:


旋轉(zhuǎn)笑臉2.gif

為什么會(huì)這樣,原本我們把整個(gè)笑臉繪制在原點(diǎn)區(qū)域,先位移到左下角再旋轉(zhuǎn)現(xiàn)象和我們料想的一樣。

當(dāng)我們先旋轉(zhuǎn)再移動(dòng),實(shí)際上矩陣的叉乘本質(zhì)是一個(gè)基變換的過(guò)程。基變換是什么東西?本文就不多討論。我們可以想象旋轉(zhuǎn)矩陣并不是旋轉(zhuǎn)圖片本身,而是旋轉(zhuǎn)圖片后面的坐標(biāo)系,構(gòu)成一個(gè)這個(gè)圖片上所有新的坐標(biāo)點(diǎn),在這里就是給整個(gè)坐標(biāo)旋轉(zhuǎn)了90度。

經(jīng)過(guò)基變換后的坐標(biāo)系再次移動(dòng)相同方向當(dāng)然出現(xiàn)完全不一樣的。這也是為什么矩陣的乘法,有左右順序可言。

實(shí)戰(zhàn)演練三

當(dāng)我們需要花兩個(gè)不同的笑臉,做不同的行為。比如說(shuō)另一個(gè)笑臉跑到左上角,做縮放。
實(shí)際上還是一樣對(duì)著原來(lái)的圖片做一次矩陣變換,在調(diào)用一次glDrawElements繪制方法。

engine->loop(VAO, VBO, texture, 1,shader, [](Shader* shader,GLuint VAO,
                                                     GLuint* texture,GLFWwindow *window){
            
            //            changeMixValue(window);
            //            shader->setFloat("mixValue", mixValue);
            
            glm::mat4 trans = glm::mat4(1.0f);
            trans = glm::translate(trans, glm::vec3(0.5f,-0.5f,0.0f));
            trans = glm::rotate(trans, (float)glfwGetTime(), glm::vec3(0.0f,0.0f,1.0f));
            
            
            
            GLuint transformLoc = glGetUniformLocation(shader->ID,"transform");
            glUniformMatrix4fv(transformLoc,1,GL_FALSE,glm::value_ptr(trans));
            
            //箱子
            glActiveTexture(GL_TEXTURE0);
            glBindTexture(GL_TEXTURE_2D,texture[0]);
            
            //笑臉
            glActiveTexture(GL_TEXTURE1);
            glBindTexture(GL_TEXTURE_2D,texture[1]);
            glBindVertexArray(VAO);
            glDrawElements(GL_TRIANGLES,6,GL_UNSIGNED_INT,0);
            
            trans = glm::mat4(1.0f);
            trans = glm::translate(trans, glm::vec3(-0.5f,0.5f,0.0f));
            float scale = sin(glfwGetTime());
            trans = glm::scale(trans, glm::vec3(scale,scale,scale));
            
            glUniformMatrix4fv(transformLoc,1,GL_FALSE,glm::value_ptr(trans));
            glDrawElements(GL_TRIANGLES,6,GL_UNSIGNED_INT,0);
            
        });
旋轉(zhuǎn)笑臉3.gif

總結(jié)

本文只是介紹了一部分基礎(chǔ)的矩陣變換知識(shí)。實(shí)際上,要深刻的理解計(jì)算機(jī)圖形學(xué),線性數(shù)學(xué)是一個(gè)很重要的工具。你可以看到我之前寫的那一篇人工智能梯度下降推導(dǎo),矩陣在計(jì)算機(jī)領(lǐng)域中是一個(gè)很基礎(chǔ)且通用工具。不求掌握精通,但是至少能夠各種熟悉操作,才能讓我們的學(xué)習(xí)更加輕松。

寫這篇文章和OpenCV的文章其實(shí)比起寫Android底層源碼分析還要痛苦。哈哈,很多數(shù)學(xué)工具都丟到爪哇國(guó)了。只是下意識(shí)知道怎么用,怎么回事,但是真的要提煉成文字,我真的必須翻閱很多數(shù)學(xué)資料,重新過(guò)一遍,證明一遍,才敢寫出文章。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,694評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,690評(píng)論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,019評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評(píng)論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,188評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,718評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,438評(píng)論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,667評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,845評(píng)論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,384評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,635評(píng)論 2 380