【Modern OpenGL】转换 Transformations
說明:跟著learnopengl的內(nèi)容學(xué)習(xí),不是純翻譯,只是自己整理記錄。
強(qiáng)烈推薦原文,無論是內(nèi)容還是排版。 原文鏈接
本文地址: http://blog.csdn.net/aganlengzi/article/details/50421159
轉(zhuǎn)換 Transformations
我們已經(jīng)學(xué)會了怎樣創(chuàng)建對象,并且學(xué)會利用著色或者紋理使他們呈現(xiàn)出表面細(xì)節(jié),但是它們還并不是十分有趣,因?yàn)樗鼈冎皇庆o止的對象。我們雖然可以通過在每幀中改變它們的頂點(diǎn)坐標(biāo)值揮著通過重新配置他們的緩存區(qū)使它們動起來,但是這樣做是十分繁雜并且消耗更多能量。實(shí)際上,有更好的辦法可以轉(zhuǎn)換一個對象:使用一個矩陣對象。
矩陣是一種十分強(qiáng)大的數(shù)學(xué)概念,一開始看上去是令人生畏的。但是當(dāng)你習(xí)慣它們的時候,你會覺得它們非常有用。
但是,為了完全理解轉(zhuǎn)換,在討論矩陣之前,我們首先不得不深入探究一下向量。本次教程的目的是讓你能有相關(guān)數(shù)學(xué)背景知識,以方便我們后面的討論。如果你覺得這次的內(nèi)容太難了,那就能理解多少就理解多少吧。當(dāng)后面再次用到這里面的內(nèi)容的時候,可以再回過頭來看。
向量 Vectors
向量,指具有大小(magnitude)和方向的幾何對象。向量可以有不同的維度,多少維都可以。如果我們使用二維向量,那它的物理意義就是二維平面上的一個方向。如果我們使用三維向量,那它的可以表示三維世界中的任意方向。
下面你將看到三個二維向量,它們在二維坐標(biāo)系中用(x,y)的形式表示。因?yàn)楸硎径S向量更加直觀,所以我們就不用三維向量作為例子了。實(shí)際上,也可以把這幾個向量想象成三維向量,只是它們的z軸坐標(biāo)被設(shè)置成了0而已。因?yàn)橄蛄勘举|(zhì)上是代表方向的,所以其起始點(diǎn)并不會改變它的值。在下圖中,我們可以看到v和w是相等的,雖然它們的起點(diǎn)不同。
后面討論的關(guān)于向量的內(nèi)容在我們的初中或者高中肯定都已經(jīng)學(xué)習(xí)過,主要有:
向量的表示(在坐標(biāo)系中的表示和坐標(biāo)表示);
向量和標(biāo)量的加減乘除(向量的每個分量和標(biāo)量做計(jì)算);
向量和向量的加減和意義(方向的改變);
向量的取反(反向);
向量的長度計(jì)算方法(勾股定理);
向量的單位向量(長度值為單位1時候的坐標(biāo)表示);
向量之間乘法
點(diǎn)乘(向量表示):
點(diǎn)乘(坐標(biāo)表示):兩個向量a = [a1, a2,…, an]和b = [b1, b2,…, bn],
其點(diǎn)積定義為:a·b=a1b1+a2b2+……+anbn。
點(diǎn)乘得到的是一個具體數(shù)值。
?
叉乘:叉乘的結(jié)果還是一個向量,垂直原來兩個所在的平面,方向也有原來兩個向量決定。
?
現(xiàn)在先記這么多關(guān)于向量的知識吧,后面如果再用到的話就再回過頭來看一看。
矩陣 Matrices
前面我們看了向量相關(guān)的知識,下面來看一下矩陣相關(guān)的知識:
矩陣(Matrix)是一個按照長方陣列排列的實(shí)數(shù)(還包括符號,表達(dá)式等等)集合。其中的每一個數(shù)(符號或表達(dá)式)都叫做矩陣的元素。一個m x n 的矩陣如下圖所示:
其中的每個元素都可以通過(i,j)的形式索引到,其中i表示行,j表示列,其實(shí)就是一個二維數(shù)組,只不過注意矩陣中下標(biāo)是從1開始的而不是從0開始的。
這大概就是關(guān)于矩陣定義的所有內(nèi)容了。我們來看看作用在矩陣上的操作:
?
矩陣和數(shù)值的加減:每個元素都和數(shù)值相加減。
矩陣和矩陣的加減:只有相同行列數(shù)的矩陣才能夠進(jìn)行加減操作,對應(yīng)元素相加減。
矩陣和數(shù)值的乘除:每個元素都和數(shù)值相乘除。
矩陣和矩陣相乘:兩個矩陣一前一后,只有前面矩陣的列數(shù)和后面矩陣的行數(shù)相同時才能夠進(jìn)行相乘的操作。具體相乘操作是前面矩陣每行的每個元素和后面矩陣每列的每個元素相乘后相加得到結(jié)果矩陣中的(行,列)位置的數(shù)值。舉個例子基本就清楚了:
?
矩陣和向量相乘
上面講了向量,講了矩陣,最終是要用它們。用它們做什么呢?相乘!至少形式上是可以滿足矩陣和向量相乘的。一個m x n的矩陣和一個n維向量是正好可以相乘的,而且相乘的結(jié)果還是一個n維向量。換句話說,我們將一個m x n的矩陣作用在了一個n維向量上得到了作用的結(jié)果也就是二者相乘的結(jié)果。這次教程講的是轉(zhuǎn)換,這就是其所在了!矩陣就是用來對向量進(jìn)行轉(zhuǎn)換的工具。而對向量進(jìn)行轉(zhuǎn)換就只需要左乘相應(yīng)的轉(zhuǎn)換矩陣就好了。
先看一下最簡單的轉(zhuǎn)換矩陣單位矩陣。
單位矩陣
單位矩陣是個方陣(行列數(shù)相等),從左上角到右下角的對角線(稱為主對角線)上的元素均為1。它的作用就像是數(shù)值乘法運(yùn)算中的1。一個矩陣左乘一個單位矩陣,得到的還是本身。如下圖所示:
?
縮放
縮放矩陣可以利用單位矩陣來理解。單位矩陣的主對角線上都是1,向量的每一個分量和其相乘后得到的值還是向量的值本身。如果將這些值改成不是1的數(shù)值,那么得到的效果就是不同的分量和這些非1值相乘的結(jié)果。因?yàn)橄蛄勘硎镜氖且粋€點(diǎn)的坐標(biāo)(目前以點(diǎn)的坐標(biāo)舉例)。如果圖形上的所有點(diǎn)的坐標(biāo)都做了相同的縮放操作(表示每個點(diǎn)的向量都左乘這個縮放矩陣),那么得到的整體圖形就進(jìn)行了縮放操作,這應(yīng)該不難理解??s放矩陣一般形式:
需要注意的有兩點(diǎn):首先,縮放矩陣有兩種,一種是按比例縮放,一種是不按比例縮放。按比例縮放的縮放矩陣應(yīng)該保證主對角線上除w分量(前面教程中講過OpenGL中的向量分為x,y,z,w最多四個分量,實(shí)際上這是三維向量表示的標(biāo)準(zhǔn)統(tǒng)一化表示)相等;而不安比例的縮放則無需保證。其次,就是這個w分量,相當(dāng)于我們在用四維向量來表示三維坐標(biāo),用四維方陣來轉(zhuǎn)換4維向量。實(shí)際上,就縮放來說,沒有必要用到四維,但是為什么要這樣用呢?后面會講到。
?
平移
和縮放矩陣類似,平移轉(zhuǎn)換矩陣也能夠從單位矩陣中推導(dǎo)出來。只不過縮放是對坐標(biāo)值成比例(乘除)的改變。而平移是對向量分量的整體加減操作,舉例來說就是:
可以看到,x,y,z上的平移量T_x,T_y,T_z在每次計(jì)算的時候都是和w分量相乘之后加到原來的向量分量數(shù)值上的。這個時候就體現(xiàn)出了向量和矩陣中的w分量的作用了。
?
為什么縮放的時候用不到w分量還要加上?實(shí)際上是為了統(tǒng)一表示,這就是齊次坐標(biāo)w的作用所在。
關(guān)于齊次坐標(biāo),目前實(shí)際上記住:它的使用使得轉(zhuǎn)換矩陣在形式上能夠保證一致(行列數(shù)),這樣在計(jì)算的時候不用擔(dān)心不滿足左邊矩陣的列數(shù)不等于右邊向量的行數(shù)的尷尬局面。另外,w分量的作用并不僅限于此,它的值也不僅限于1,在下一個教程中會講到,利用w值來改變?nèi)S對象。
旋轉(zhuǎn)
相較于以上介紹的縮放和平移轉(zhuǎn)換矩陣,旋轉(zhuǎn)轉(zhuǎn)換矩陣在理解上可能會有些難度,雖然它在形式上和上面的兩個轉(zhuǎn)換矩陣比較相似(肯定比較相似,都是一個矩陣,只不過矩陣中的元素根據(jù)我們要實(shí)現(xiàn)的功能設(shè)置不同的數(shù)值)。
在學(xué)習(xí)旋轉(zhuǎn)矩陣之前,我們應(yīng)該首先看一下什么是向量的旋轉(zhuǎn)。我們只說三維空間中的旋轉(zhuǎn)。在三維空間中,所有的點(diǎn)都在三維坐標(biāo)系中,都可以通過三維坐標(biāo)來指定。其中某個點(diǎn)可以看成是從原點(diǎn)到這個點(diǎn)的一個實(shí)際的向量(帶箭頭的線段)。在三維空間中的旋轉(zhuǎn)是和特定的坐標(biāo)軸相關(guān)的,即旋轉(zhuǎn)是繞某一個坐標(biāo)軸進(jìn)行一定角度的旋轉(zhuǎn)。所以對于三維空間中的點(diǎn)的旋轉(zhuǎn)轉(zhuǎn)換矩陣,有三個,分別是:
繞x軸旋轉(zhuǎn)變化矩陣:
繞y軸旋轉(zhuǎn)變化矩陣:
繞z軸旋轉(zhuǎn)變化矩陣:
很顯然,我們在實(shí)際使用的時候不會只對繪制的對象進(jìn)行按照x,y或z軸的單獨(dú)的旋轉(zhuǎn),我們也可以按照先x后y最后z的方式組合達(dá)到我們的效果,但是這種方法是不推薦的,它會引入問題。推薦的方法是繞一個方向單位向量進(jìn)行旋轉(zhuǎn),其形式如下,假設(shè)是繞(R_x,R_y,R_z)進(jìn)行旋轉(zhuǎn):
?
組合矩陣
組合矩陣在本教程中的含義就是矩陣相乘。
實(shí)際上,我們在對生成的三維對象進(jìn)行操作的時候,往往是對它們做一系列的轉(zhuǎn)換,其中當(dāng)然包括最基本的縮放、平移、旋轉(zhuǎn)操作。但是,如果我們每次都要進(jìn)行一系列操作(比如說縮放、平移和旋轉(zhuǎn)三種操作),一種方法是:要變換的向量首先左乘縮放轉(zhuǎn)換矩陣,得到的向量再左乘平移變換矩陣,得到的向量再左乘旋轉(zhuǎn)變換矩陣,最終得到了我們想要的結(jié)果;另一種方法是:首先按照順序?qū)⒖s放矩陣左乘平移矩陣,得到的結(jié)果左乘旋轉(zhuǎn)矩陣,然后要變換的向量左乘其結(jié)果。這兩種方法的到的效果是一致的。但是從運(yùn)算量上來看,第二種方法顯然優(yōu)于第一種,因?yàn)樵诘谝环N中,對象的每個點(diǎn)都要做相同的3次左乘矩陣的操作,而第二種只需要每個點(diǎn)完成1次左乘矩陣的操作就好了。所以,這才是使用變換矩陣和齊次坐標(biāo)的意義所在。
實(shí)踐一下 In practice
我們已經(jīng)解釋了轉(zhuǎn)換背后的原理,是時候看一下我們應(yīng)該怎樣使用這些理論了。OpenGL本身是沒有任何關(guān)于矩陣或者向量相關(guān)的內(nèi)置信息的。所以我們需要自己來定義數(shù)學(xué)類和函數(shù)。在這個教程中我們使用之前就已經(jīng)有的數(shù)學(xué)庫來方便我們轉(zhuǎn)換操作。幸運(yùn)的是,GLM就是一個易用并且專為OpenGL定制過的數(shù)學(xué)庫。
GLM
GLM是OpenGL Mathematics的縮寫。它是一個只有頭文件的庫,也就一位置我們只需要包含它的合適的頭文件就能夠進(jìn)行愉快的使用了。不需要像之前我們使用的GLEW、GLFW、SOIL等還需要編譯和配置(配置還是需要的)。你可以從這兒下載到所需的文件。在配置的時候我們只需要讓我們的工程能夠找到需要的GLM的文件就可以了。
我的做法是將下載到的glm-0.9.7.1.zip解壓到某個目錄下比如說GLM_ROOT,然后:
在我的工程中—->屬性—->VC++目錄—->包含目錄中添加GLM_ROOT就可以了。
實(shí)際上大多數(shù)情況下,我們只需要包含下面的三個頭文件就已經(jīng)夠用了:
#include <glm/glm.hpp> #include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/type_ptr.hpp>首先讓我們先生成一個沒有什么具體含義的轉(zhuǎn)換矩陣,僅僅是為了測試一下矩陣和向量相乘的結(jié)果:
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;如上面的代碼所示,我們想要做的是將(1,0,0)通過(1,1,0)生成的矩陣(如下圖所示)轉(zhuǎn)換成三個維度上的坐標(biāo)分別是(2,1,0)的結(jié)果。
在代碼中,我們通過GLM中的glm::vec4數(shù)據(jù)類型聲明了一個四維向量vec;通過glm::mat4數(shù)據(jù)類型生成了一個4 x 4的矩陣trans,默認(rèn)為單位矩陣;通過glm::translate函數(shù)借助于向量(1.0f, 1.0f, 0.0f)將這個矩陣變換成上圖所示的轉(zhuǎn)換矩陣,然后將向量vec左乘變換矩陣trans,并輸出結(jié)果向量的x,y和z軸分量坐標(biāo)。我運(yùn)行的結(jié)果如下圖所示:
?
接下來,讓我們嘗試一下更有趣的東西。
讓我們對上次教程中那個由笑臉和盒子混合貼圖而成的矩形進(jìn)行操作:首先對其進(jìn)行逆時針90度旋轉(zhuǎn),然后我們將其等比例縮放0.5。好的,開始干吧,首先我們定義轉(zhuǎn)換矩陣:
注意上面的轉(zhuǎn)換順序,因?yàn)檗D(zhuǎn)換矩陣對向量的操作都是左乘進(jìn)行的,上面的glm::rotate和glm::scale函數(shù)也是默認(rèn)左乘的規(guī)則生成轉(zhuǎn)換矩陣,那么最終得到的trans相當(dāng)于是“旋轉(zhuǎn) * 縮放”矩陣的結(jié)果,當(dāng)這個組合后的變換矩陣作用到圖形的每個坐標(biāo)點(diǎn)的時候,實(shí)際上是先和縮放矩陣相乘,然后和旋轉(zhuǎn)矩陣相乘的。另外,通過以上方法能夠簡單方便地生成組合后的最終轉(zhuǎn)換矩陣,大大減少計(jì)算量(還記得上面講到的第二種方法吧)。
有一些版本的GLM是不支持以度數(shù)來表示角度的,而是支持弧度表示,在這種情況下,可能需要手動進(jìn)行一下轉(zhuǎn)換。
剩下的問題就是將這個轉(zhuǎn)換矩陣作用到我們圖形上的每一個點(diǎn)了。當(dāng)然應(yīng)該是在vertex shader中進(jìn)行坐標(biāo)的轉(zhuǎn)換(fragment中是進(jìn)行顏色值生成的),當(dāng)然你肯定也像我一樣想到了用uniform,但是你可能像我一樣沒有記起來GLSL中也有一個mat4數(shù)據(jù)類型,表示一個4 x 4的矩陣。有了這個數(shù)據(jù)結(jié)構(gòu),我們才可以定義變量,才可以將其定義成uniform類型的變量,才可以將我們的矩陣傳遞進(jìn)shader中,像下面這樣:
#version 330 core layout (location = 0) in vec3 position; layout (location = 1) in vec3 color; layout (location = 2) in vec2 texCoord;out vec3 ourColor; out vec2 TexCoord;uniform mat4 transform;void main() {gl_Position = transform * vec4(position, 1.0f);ourColor = color;TexCoord = vec2(texCoord.x, 1.0 - texCoord.y); }實(shí)際上,GLSL還有mat2和mat3數(shù)據(jù)類型,并且也支持大尺寸的矩陣指定部分給小尺寸的向量賦值的操作,和前面講的向量中的類似的靈活操作相類似。
好的,上面的代碼中,我們首先定義了用于傳遞轉(zhuǎn)換矩陣的uniform變量,然后在主函數(shù)中將原來的點(diǎn)的位置向量左乘上了我們的transform矩陣。在OpenGL程序中,我們需要對這個transform矩陣進(jìn)行賦值,這個應(yīng)該是比較熟悉的:
GLuint transformLoc = glGetUniformLocation(ourShader.Program, "transform"); glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));首先在我們的程序中查找uniform類型變量transform的位置,然后調(diào)用glUniformMatrix4fv函數(shù)來將我們定義的trans矩陣傳遞給這個位置。其中需要注意的是:
用于給uniform類型變量傳遞值的glUniform函數(shù)的后綴是Matrix4fv,它的各個參數(shù)的含義如下:
第一個參數(shù)比較簡單,是上面找到的這個uniform類型變量的地址
第二個參數(shù)指定我們需要傳遞的矩陣的個數(shù)
第三個參數(shù)指定了我們是否需要對這個矩陣進(jìn)行轉(zhuǎn)置(使用GLM一般不用)
最后一個參數(shù)是實(shí)際的數(shù)據(jù)的地址,因?yàn)镚LM存儲的方式和OpenGL接收的方式有所不同,所以需要使用GLM內(nèi)置的轉(zhuǎn)換函數(shù)value_ptr進(jìn)行數(shù)據(jù)格式的轉(zhuǎn)換以保證數(shù)據(jù)的正確輸入。
以上,我們利用GLM生成了一個轉(zhuǎn)換矩陣,并且利用uniform mat4類型的變量將這個矩陣值傳遞到了vertex shader中,并在shader中將對象上的每個坐標(biāo)向量都進(jìn)行了左乘操作,結(jié)果應(yīng)該就是這個樣子了:
效果達(dá)到!
下面我們想讓它動起來!讓它進(jìn)行旋轉(zhuǎn)~
基本的步驟是相同的:
首先定義或者說利用GLM生成一個轉(zhuǎn)換矩陣,其次將這個矩陣傳遞到vertex shader中并進(jìn)行左乘操作。
上面的這個矩陣是隨著時間動態(tài)改變的,那個時間函數(shù)就是我們前面用到的動態(tài)改變?nèi)切晤伾姆椒?。定義的旋轉(zhuǎn)軸是z軸。
glm::rotate函數(shù)的第一個參數(shù)是矩陣,第二個參數(shù)是角度,我們設(shè)置了隨時間變化的角度;第三個參數(shù)是參照的方向向量,我們設(shè)置的是z軸。得到的效果應(yīng)該是:
為了得到上述結(jié)果,需要注意的是嗎,這個矩陣的生成需要在game loop中進(jìn)行定義,否則變換矩陣并不會進(jìn)行更新。也就得不到想要的旋轉(zhuǎn)的效果。
所有的代碼(main.cpp和shader)在這兒可以得到。
總結(jié)
以上是生活随笔為你收集整理的【Modern OpenGL】转换 Transformations的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: bat
- 下一篇: 如何在UE4中创建线程