前言
在就計(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è)大小,如下:
這里有三個(gè)向量,能看到w,n,v都從各自的起點(diǎn)指向各自的終點(diǎn),并且能夠很輕易的算出其長(zhǎng)度。
一個(gè)向量可以表示:
向量的計(jì)算
向量加減
向量加減,實(shí)際上就是對(duì)向量中每個(gè)分量進(jìn)行加減
向量之間的加:
向量之間的加減在幾何上意義如下:
減法可以看成加一個(gè)負(fù)數(shù):
向量的長(zhǎng)度
我們使用勾股定理(Pythagoras Theorem)來(lái)獲取向量的長(zhǎng)度(Length)/大小(Magnitude)。如果你把向量的x與y分量畫出來(lái),該向量會(huì)和x與y分量為邊形成一個(gè)三角形:
我們可以依據(jù)勾股定理把v向量的長(zhǎng)度計(jì)算出來(lái):
向量的乘法
向量的乘法有兩部分,點(diǎn)乘(內(nèi)積),叉乘(外積)。
點(diǎn)乘:
如果用分量來(lái)表示其運(yùn)算,就是每個(gè)分量之間相乘最后相加:
幾何意義:
假設(shè)有兩個(gè)向量a,b.如果這兩個(gè)向量做點(diǎn)乘:
因?yàn)辄c(diǎn)乘符合乘法交換律:可以擴(kuò)起后面,能從涂上看到后面括號(hào)那部分實(shí)際上就是b在a上的投影,也就是上圖的。
所以和a是一個(gè)方向上的,因此將會(huì)符合乘法交換律等基礎(chǔ)性質(zhì)。這些不多展開論述。
叉乘:
如果用分量來(lái)計(jì)算的就是如下:
假設(shè)有向量A和B,從左到右的排開每個(gè)向量的分量,每一行代表一個(gè)向量:
實(shí)際上,我高中第一次學(xué)的時(shí)候,感覺不太好記叉乘。其實(shí)把上面那個(gè)行列式寫出來(lái),就很好記住了。計(jì)算那一列的數(shù)據(jù),就獲取另外兩列分量,做交叉相乘以及相減。
幾何上的意義:
從上圖能夠明白實(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矩陣的例子:
矩陣可以通過(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)量相加
矩陣之間相加,必須是矩陣行列數(shù)相等才能互相相加:
減法也是類似。
矩陣的乘法
矩陣乘法分為2部分,數(shù)乘和相乘。
數(shù)乘
矩陣和標(biāo)量相乘,矩陣與標(biāo)量之間的乘法也是矩陣的每一個(gè)元素分別乘以該標(biāo)量
現(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)看矩陣相乘的例子:
實(shí)際上計(jì)算過(guò)程就是矩陣的第1個(gè)元素就是第一行乘以第一列每個(gè)元素積的和。擴(kuò)展一下就是如下公式:
矩陣還有除法也就是矩陣的逆,本文沒有涉及,就不多介紹。
單位矩陣
實(shí)際上就是一個(gè)斜對(duì)角全是1,其他都是0的矩陣,數(shù)學(xué)上叫做
在OpenGL中,由于某些原因我們通常使用4×4的變換矩陣,而其中最重要的原因就是大部分的向量都是4分量的
這個(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)。
假如我們嘗試縮放,沿著x軸方向縮小0.5倍數(shù),沿著y軸放大2倍。
由于OpenGL通常在3d空間內(nèi)操作,那么我們只要把z軸縮放設(shè)置為1就沒有任何影響。
這種x軸和y軸縮放比例不一致的叫做不均勻縮放,而一致稱為均勻縮放。實(shí)際上這個(gè)過(guò)程能夠通過(guò)矩陣去完成。
還記得上面的單位矩陣吧。只要把縮放系數(shù)放到矩陣中對(duì)應(yīng)1的位置就能控制對(duì)應(yīng)軸的縮放系數(shù)。
最后一個(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),我們就能把位移矩陣定義為:
旋轉(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ù)部分,寫法如下:
a是一個(gè)實(shí)數(shù),bi是一個(gè)虛數(shù)。i是什么?定義
為什么使用復(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è)例子:
,
這樣就能保留兩個(gè)不同的信息了。實(shí)際上也像極了向量/矩陣相加。
復(fù)數(shù)和矩陣的關(guān)系
從上面的公式,直覺上告訴我們復(fù)數(shù)的計(jì)算一定和矩陣元算相關(guān),讓我們探索一下復(fù)數(shù)和矩陣之間的關(guān)系。就以復(fù)數(shù)乘法為例子:
,
如果我們把這個(gè)結(jié)果看成矩陣運(yùn)算將會(huì)是如下一個(gè)矩陣運(yùn)算,把矩陣第一行運(yùn)算看成實(shí)部,第二行運(yùn)算看成虛部:
就不難看出,實(shí)際上復(fù)數(shù)的元算就是對(duì) 下面這個(gè)矩陣做變換運(yùn)算
旋轉(zhuǎn)的證明
先給出一個(gè)復(fù)數(shù)在復(fù)平面中表現(xiàn):
能看到在這個(gè)復(fù)平面中復(fù)數(shù)z的表示就是,這個(gè)向量的長(zhǎng)度根據(jù)勾股定理很容易就求出來(lái)。
我們嘗試著對(duì)復(fù)數(shù)的矩陣進(jìn)行一次變形,目的就是為創(chuàng)造出一個(gè)角度和復(fù)數(shù)之間的關(guān)系,把每一項(xiàng)都除以,提取出可以勾股定理創(chuàng)造出來(lái)的角度:
根據(jù)勾股定理,可以把元算中每一項(xiàng)轉(zhuǎn)化如下:
很有趣,這樣就構(gòu)建出了角度的關(guān)系了。有了這些還不足。
矩陣的左側(cè)還是有冗余的東西,我們想辦法干掉它。此時(shí)很巧的是,矩陣右側(cè)剛好就是這個(gè)復(fù)平面向量的模(長(zhǎng)度)。
因此可以化簡(jiǎn)如下:
又因?yàn)閱挝痪仃嘔乘以任何矩陣還是原來(lái)的矩陣:
實(shí)際上這個(gè)結(jié)果就是3d的旋轉(zhuǎn)縮放矩陣。不信?我們?cè)囋噧蓚€(gè)在復(fù)平面上的向量(0,1),(1,0)。
當(dāng)復(fù)平面上的z長(zhǎng)度為1時(shí)候,如下圖:
因此下面這個(gè)矩陣是旋轉(zhuǎn)時(shí)候的縮放矩陣:
下面這個(gè)是旋轉(zhuǎn)矩陣:
用復(fù)數(shù)表示如下:
那么我們可以由2d往3d推,可以很輕易得到如下三種情況:
當(dāng)沿著x軸旋轉(zhuǎn),下面矩陣稱為:
當(dāng)沿著y軸旋轉(zhuǎn),下面矩陣稱為:
當(dāng)沿著z軸旋轉(zhuǎn),下面矩陣稱為:
有了這三個(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ō)就是,
因此可以得到如下這個(gè)復(fù)合矩陣:
是不是很討厭,很麻煩。更麻煩的在后面,這種基于歐拉角變換的旋轉(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)范圍就被限制住了。
如下:
如何解決呢?這個(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型矩陣,剛好能夠正常解析。
這樣就能看到了沿著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é)果
誒?奇怪了?怎么根據(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)和位移的順序變換了,會(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));
如下:
為什么會(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);
});
總結(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ò)一遍,證明一遍,才敢寫出文章。