版本記錄
| 版本號(hào) | 時(shí)間 |
|---|---|
| V1.0 | 2017.07.26 |
前言
OpenGL 圖形庫(kù)項(xiàng)目中一直也沒(méi)用過(guò),最近也想學(xué)著使用這個(gè)圖形庫(kù),感覺(jué)還是很有意思,也就自然想著好好的總結(jié)一下,希望對(duì)大家能有所幫助。
1. OpenGL 圖形庫(kù)使用(一) —— 概念基礎(chǔ)
2. OpenGL 圖形庫(kù)使用(二) —— 渲染模式、對(duì)象、擴(kuò)展和狀態(tài)機(jī)
3. OpenGL 圖形庫(kù)使用(三) —— 著色器、數(shù)據(jù)類型與輸入輸出
4. OpenGL 圖形庫(kù)使用(四) —— Uniform及更多屬性
5. OpenGL 圖形庫(kù)使用(五) —— 紋理
變換
??前面我們知道了如何創(chuàng)建一個(gè)物體、著色以及加入紋理,但是他們都是靜態(tài)的物體,如果想要物體發(fā)生運(yùn)動(dòng),需要另外一個(gè)方案,那就是使用多個(gè)矩陣Matrix對(duì)象可以更好的變換Transform一個(gè)物體。下面我們繼續(xù)深入的了解下向量。
向量
??向量最基本的定義就是一個(gè)方向?;蛘吒降恼f(shuō),向量有一個(gè)方向(Direction)和大小(Magnitude,也叫做強(qiáng)度或長(zhǎng)度)。
1. 向量與標(biāo)量運(yùn)算
標(biāo)量(Scalar)只是一個(gè)數(shù)字(或者說(shuō)是僅有一個(gè)分量的向量)。當(dāng)把一個(gè)向量加/減/乘/除一個(gè)標(biāo)量,我們可以簡(jiǎn)單的把向量的每個(gè)分量分別進(jìn)行該運(yùn)算
2. 向量取反
對(duì)一個(gè)向量取反(Negate)會(huì)將其方向逆轉(zhuǎn)。一個(gè)指向東北的向量取反后就指向西南方向了。我們?cè)谝粋€(gè)向量的每個(gè)分量前加負(fù)號(hào)就可以實(shí)現(xiàn)取反了(或者說(shuō)用-1數(shù)乘該向量)。
3. 向量加減
向量的加法可以被定義為是分量的(Component-wise)相加,即將一個(gè)向量中的每一個(gè)分量加上另一個(gè)向量的對(duì)應(yīng)分量。
4. 長(zhǎng)度
我們使用勾股定理(Pythagoras Theorem)來(lái)獲取向量的長(zhǎng)度(Length)/大小(Magnitude)。還有一個(gè)特殊類型的向量叫做單位向量(Unit Vector)`,單位向量有一個(gè)特別的性質(zhì)——它的長(zhǎng)度是1。
5. 向量相乘
兩個(gè)向量相乘是一種很奇怪的情況。普通的乘法在向量上是沒(méi)有定義的,因?yàn)樗谝曈X(jué)上是沒(méi)有意義的。但是在相乘的時(shí)候我們有兩種特定情況可以選擇:一個(gè)是點(diǎn)乘(Dot Product),記作vˉ? kˉ,另一個(gè)是叉乘(Cross Product),記作vˉ× kˉ。
1. 點(diǎn)乘
兩個(gè)向量的點(diǎn)乘等于它們的數(shù)乘結(jié)果乘以兩個(gè)向量之間夾角的余弦值。
2. 叉乘
叉乘只在3D空間中有定義,它需要兩個(gè)不平行向量作為輸入,生成一個(gè)正交于兩個(gè)輸入向量的第三個(gè)向量。如果輸入的兩個(gè)向量也是正交的,那么叉乘之后將會(huì)產(chǎn)生3個(gè)互相正交的向量。接下來(lái)的教程中這會(huì)非常有用。下面的圖片展示了3D空間中叉乘的樣子。下面看一個(gè)圖。

下面你會(huì)看到兩個(gè)正交向量A和B叉積。

矩陣
簡(jiǎn)單來(lái)說(shuō)矩陣就是一個(gè)矩形的數(shù)字、符號(hào)或表達(dá)式數(shù)組。矩陣中每一項(xiàng)叫做矩陣的元素(Element)。
1. 矩陣加減
矩陣與矩陣之間的加減就是兩個(gè)矩陣對(duì)應(yīng)元素的加減運(yùn)算,所以總體的規(guī)則和與標(biāo)量運(yùn)算是差不多的,只不過(guò)在相同索引下的元素才能進(jìn)行運(yùn)算。這也就是說(shuō)加法和減法只對(duì)同維度的矩陣才是有定義的。
2. 矩陣的數(shù)乘
和矩陣與標(biāo)量的加減一樣,矩陣與標(biāo)量之間的乘法也是矩陣的每一個(gè)元素分別乘以該標(biāo)量。
3. 矩陣相乘
矩陣之間的乘法不見(jiàn)得有多復(fù)雜,但的確很難讓人適應(yīng)。矩陣乘法基本上意味著遵照規(guī)定好的法則進(jìn)行相乘。當(dāng)然,相乘還有一些限制:
- 只有當(dāng)左側(cè)矩陣的列數(shù)與右側(cè)矩陣的行數(shù)相等,兩個(gè)矩陣才能相乘。
- 矩陣相乘不遵守交換律
(Commutative),也就是說(shuō)A?B ≠ B?A。
矩陣與向量相乘
讓我們更深入了解一下向量,它其實(shí)就是一個(gè)N×1矩陣,N表示向量分量的個(gè)數(shù)(也叫N維(N-dimensional)向量)。但是為什么我們會(huì)關(guān)心矩陣能否乘以一個(gè)向量?好吧,正巧,很多有趣的2D/3D變換都可以放在一個(gè)矩陣中,用這個(gè)矩陣乘以我們的向量將變換(Transform)這個(gè)向量。
1. 單位矩陣
在OpenGL中,由于某些原因我們通常使用4×4的變換矩陣,而其中最重要的原因就是大部分的向量都是4分量的。我們能想到的最簡(jiǎn)單的變換矩陣就是單位矩陣(Identity Matrix)。單位矩陣是一個(gè)除了對(duì)角線以外都是0的N×N矩陣。
2. 縮放
對(duì)一個(gè)向量進(jìn)行縮放(Scaling)就是對(duì)向量的長(zhǎng)度進(jìn)行縮放,而保持它的方向不變。由于我們進(jìn)行的是2維或3維操作,我們可以分別定義一個(gè)有2或3個(gè)縮放變量的向量,每個(gè)變量縮放一個(gè)軸(x、y或z)。
OpenGL通常是在3D空間進(jìn)行操作的,對(duì)于2D的情況我們可以把z軸縮放1倍,這樣z軸的值就不變了。我們剛剛的縮放操作是不均勻(Non-uniform)縮放,因?yàn)槊總€(gè)軸的縮放因子(Scaling Factor)都不一樣。如果每個(gè)軸的縮放因子都一樣那么就叫均勻縮放(Uniform Scale)。下面就是縮放的例子。

3. 位移
位移(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è)值。

齊次坐標(biāo)(Homogeneous Coordinates)
向量的w分量也叫齊次坐標(biāo)。想要從齊次向量得到3D向量,我們可以把x、y和z坐標(biāo)分別除以w坐標(biāo)。我們通常不會(huì)注意這個(gè)問(wèn)題,因?yàn)閣分量通常是1.0。使用齊次坐標(biāo)有幾點(diǎn)好處:它允許我們?cè)?D向量上進(jìn)行位移(如果沒(méi)有w分量我們是不能位移向量的),而且下一章我們會(huì)用w值創(chuàng)建3D視覺(jué)效果。
如果一個(gè)向量的齊次坐標(biāo)是0,這個(gè)坐標(biāo)就是方向向量(Direction Vector),因?yàn)閣坐標(biāo)是0,這個(gè)向量就不能位移
4. 旋轉(zhuǎn)
如果你想知道旋轉(zhuǎn)矩陣是如何構(gòu)造出來(lái)的,我推薦你去看可汗學(xué)院線性代數(shù)的視頻。
首先我們來(lái)定義一個(gè)向量的旋轉(zhuǎn)到底是什么。2D或3D空間中的旋轉(zhuǎn)用角(Angle)來(lái)表示。在3D空間中旋轉(zhuǎn)需要定義一個(gè)角和一個(gè)旋轉(zhuǎn)軸(Rotation Axis)。物體會(huì)沿著給定的旋轉(zhuǎn)軸旋轉(zhuǎn)特定角度。
旋轉(zhuǎn)矩陣在3D空間中每個(gè)單位軸都有不同定義,旋轉(zhuǎn)角度用θ表示:

利用旋轉(zhuǎn)矩陣我們可以把我們的位置向量沿一個(gè)單位軸進(jìn)行旋轉(zhuǎn)。也可以把多個(gè)矩陣結(jié)合起來(lái),比如先沿著x軸旋轉(zhuǎn)再沿著y軸旋轉(zhuǎn)。但是這會(huì)很快導(dǎo)致一個(gè)問(wèn)題——萬(wàn)向節(jié)死鎖(Gimbal Lock,可以看看這個(gè)視頻(優(yōu)酷)來(lái)了解)。
下面是繞任意軸旋轉(zhuǎn)的公式,見(jiàn)下面這個(gè)公式,(Rx,Ry,Rz)代表任意旋轉(zhuǎn)軸:

5. 矩陣組合
使用矩陣進(jìn)行變換的真正力量在于,根據(jù)矩陣之間的乘法,我們可以把多個(gè)變換組合到一個(gè)矩陣中。讓我們看看我們是否能生成一個(gè)變換矩陣,讓它組合多個(gè)變換。建議您在組合矩陣時(shí),先進(jìn)行縮放操作,然后是旋轉(zhuǎn),最后才是位移。
實(shí)踐
OpenGL沒(méi)有自帶任何的矩陣和向量知識(shí),所以我們必須定義自己的數(shù)學(xué)類和函數(shù)。在教程中我們更希望抽象所有的數(shù)學(xué)細(xì)節(jié),使用已經(jīng)做好了的數(shù)學(xué)庫(kù)。幸運(yùn)的是,有個(gè)易于使用,專門為OpenGL量身定做的數(shù)學(xué)庫(kù),那就是GLM。
1. GLM
GLM是OpenGL Mathematics的縮寫,它是一個(gè)只有頭文件的庫(kù),也就是說(shuō)我們只需包含對(duì)應(yīng)的頭文件就行了,不用鏈接和編譯。GLM可以在它們的網(wǎng)站上下載。把頭文件的根目錄復(fù)制到你的includes文件夾,然后你就可以使用這個(gè)庫(kù)了。
我們需要的GLM的大多數(shù)功能都可以從下面這3個(gè)頭文件中找到:
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
我們來(lái)看看是否可以利用我們剛學(xué)的變換知識(shí)把一個(gè)向量(1, 0, 0)位移(1, 1, 0)個(gè)單位(注意,我們把它定義為一個(gè)glm::vec4類型的值,齊次坐標(biāo)設(shè)定為1.0)。
glm::vec4 vec(1.0f, 0.0f, 0.0f, 1.0f);
glm::mat4 trans;
trans = glm::translate(trans, glm::vec3(1.0f, 1.0f, 0.0f));
vec = trans * vec;
std::cout << vec.x << vec.y << vec.z << std::endl;
我們先用GLM內(nèi)建的向量類定義一個(gè)叫做vec的向量。接下來(lái)定義一個(gè)mat4類型的trans,默認(rèn)是一個(gè)4×4單位矩陣。下一步是創(chuàng)建一個(gè)變換矩陣,我們是把單位矩陣和一個(gè)位移向量傳遞給glm::translate函數(shù)來(lái)完成這個(gè)工作的(然后用給定的矩陣乘以位移矩陣就能獲得最后需要的矩陣)。
我們來(lái)做些更有意思的事情,讓我們來(lái)旋轉(zhuǎn)和縮放之前教程中的那個(gè)箱子。首先我們把箱子逆時(shí)針旋轉(zhuǎn)90度。然后縮放0.5倍,使它變成原來(lái)的一半大。我們先來(lái)創(chuàng)建變換矩陣:
glm::mat4 trans;
trans = glm::rotate(trans, glm::radians(90.0f), glm::vec3(0.0, 0.0, 1.0));
trans = glm::scale(trans, glm::vec3(0.5, 0.5, 0.5));
首先,我們把箱子在每個(gè)軸都縮放到0.5倍,然后沿z軸旋轉(zhuǎn)90度。
GLM希望它的角度是弧度制的(Radian),所以我們使用glm::radians將角度轉(zhuǎn)化為弧度。注意有紋理的那面矩形是在XY平面上的,所以我們需要把它繞著z軸旋轉(zhuǎn)。因?yàn)槲覀儼堰@個(gè)矩陣傳遞給了GLM的每個(gè)函數(shù),GLM會(huì)自動(dòng)將矩陣相乘,返回的結(jié)果是一個(gè)包括了多個(gè)變換的變換矩陣。下一個(gè)大問(wèn)題是:如何把矩陣傳遞給著色器?我們?cè)谇懊婧?jiǎn)單提到過(guò)
GLSL里也有一個(gè)mat4類型。所以我們將修改頂點(diǎn)著色器讓其接收一個(gè)mat4的uniform變量,然后再用矩陣uniform乘以位置向量:
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;
out vec2 TexCoord;
uniform mat4 transform;
void main()
{
gl_Position = transform * vec4(aPos, 1.0f);
TexCoord = vec2(aTexCoord.x, 1.0 - aTexCoord.y);
}
- 在把位置向量傳給
gl_Position之前,我們先添加一個(gè)uniform,并且將其與變換矩陣相乘。我們的箱子現(xiàn)在應(yīng)該是原來(lái)的二分之一大小并(向左)旋轉(zhuǎn)了90度。當(dāng)然,我們?nèi)孕枰炎儞Q矩陣傳遞給著色器。
unsigned int transformLoc = glGetUniformLocation(ourShader.ID, "transform");
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));
我們首先查詢uniform變量的地址,然后用有Matrix4fv后綴的glUniform函數(shù)把矩陣數(shù)據(jù)發(fā)送給著色器。第一個(gè)參數(shù)你現(xiàn)在應(yīng)該很熟悉了,它是uniform的位置值。第二個(gè)參數(shù)告訴OpenGL我們將要發(fā)送多少個(gè)矩陣,這里是1。第三個(gè)參數(shù)詢問(wèn)我們我們是否希望對(duì)我們的矩陣進(jìn)行置換(Transpose),也就是說(shuō)交換我們矩陣的行和列。OpenGL開(kāi)發(fā)者通常使用一種內(nèi)部矩陣布局,叫做列主序(Column-major Ordering)布局。GLM的默認(rèn)布局就是列主序,所以并不需要置換矩陣,我們填GL_FALSE。最后一個(gè)參數(shù)是真正的矩陣數(shù)據(jù),但是GLM并不是把它們的矩陣儲(chǔ)存為OpenGL所希望接受的那種,因此我們要先用GLM的自帶的函數(shù)value_ptr來(lái)變換這些數(shù)據(jù)。
我們創(chuàng)建了一個(gè)變換矩陣,在頂點(diǎn)著色器中聲明了一個(gè)uniform,并把矩陣發(fā)送給了著色器,著色器會(huì)變換我們的頂點(diǎn)坐標(biāo)。
下面我們看一下效果圖。

我們現(xiàn)在做些更有意思的,看看我們是否可以讓箱子隨著時(shí)間旋轉(zhuǎn),我們還會(huì)重新把箱子放在窗口的右下角。要讓箱子隨著時(shí)間推移旋轉(zhuǎn),我們必須在游戲循環(huán)中更新變換矩陣,因?yàn)樗诿恳淮武秩镜卸家?。我們使?code>GLFW的時(shí)間函數(shù)來(lái)獲取不同時(shí)間的角度。
glm::mat4 trans;
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));
要記住的是前面的例子中我們可以在任何地方聲明變換矩陣,但是現(xiàn)在我們必須在每一次迭代中創(chuàng)建它,從而保證我們能夠不斷更新旋轉(zhuǎn)角度。這也就意味著我們不得不在每次游戲循環(huán)的迭代中重新創(chuàng)建變換矩陣。通常在渲染場(chǎng)景的時(shí)候,我們也會(huì)有多個(gè)需要在每次渲染迭代中都用新值重新創(chuàng)建的變換矩陣。
盡管在代碼中我們先位移再旋轉(zhuǎn),實(shí)際的變換卻是先應(yīng)用旋轉(zhuǎn)再是位移的。明白所有這些變換的組合,并且知道它們是如何應(yīng)用到物體上是一件非常困難的事情。只有不斷地嘗試和實(shí)驗(yàn)這些變換你才能快速地掌握它們。

后記
未完,待續(xù)~~
