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