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

下面你會看到兩個正交向量A和B叉積。

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

3. 位移
位移(Translation)是在原始向量的基礎(chǔ)上加上另一個向量從而獲得一個在不同位置的新向量的過程,從而在位移向量基礎(chǔ)上移動了原始向量。我們已經(jīng)討論了向量加法,所以這應(yīng)該不會太陌生。和縮放矩陣一樣,在4×4矩陣上有幾個特別的位置用來執(zhí)行特定的操作,對于位移來說它們是第四列最上面的3個值。

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

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

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

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

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