• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            WisKeyのLullaby

            huangwei.pro 『我失去了一只臂膀』「就睜開了一只眼睛」

              C++博客 :: 首頁 :: 聯(lián)系 :: 聚合  :: 管理
              12 Posts :: 0 Stories :: 23 Comments :: 0 Trackbacks

            公告

            “我該走哪條路?”
            “這取決于你要去哪里。”
            “我只想能到某個地方。”
            “只要你走的夠遠(yuǎn),你始終能到達(dá)那個地方。”

            Home: huangwei.pro
            E-Mail: sir.huangwei [at] gmail.com
            09.6 畢業(yè)于杭州電子科技大學(xué)
            進(jìn)入網(wǎng)易杭州研究院工作至今

            常用鏈接

            留言簿(1)

            我參與的團(tuán)隊

            搜索

            •  

            積分與排名

            • 積分 - 51537
            • 排名 - 445

            最新評論

            閱讀排行榜

            評論排行榜

            http://huangwei.pro/2015-08/modern-opengl3/

             

            本文中,我會將不會動的2D三角形替換為旋轉(zhuǎn)的3D立方體。你會看到這樣的效果:

             

            現(xiàn)在我們終于能在屏幕上搞點有趣的東西了,我放了更多的動圖在這里:http://imgur.com/a/x8q7R

            為了生成旋轉(zhuǎn)立方體,我們需要學(xué)些關(guān)于矩陣的數(shù)學(xué),用于創(chuàng)建透視投影,旋轉(zhuǎn),平移和“相機(jī)”概念。我們還有必要學(xué)習(xí)些深度緩沖,和典型的隨時間改變的3D應(yīng)用,比如動畫。

            獲取代碼

            所有例子代碼的zip打包可以從這里獲取:https://github.com/tomdalling/opengl-series/archive/master.zip

            這一系列文章中所使用的代碼都存放在:https://github.com/tomdalling/opengl-series。你可以在頁面中下載zip,加入你會git的話,也可以復(fù)制該倉庫。

            本文代碼你可以在source/02_textures目錄里找到。使用OS X系統(tǒng)的,可以打開根目錄里的opengl-series.xcodeproj,選擇本文工程。使用Windows系統(tǒng)的,可以在Visual Studio 2013里打開opengl-series.sln,選擇相應(yīng)工程。

            工程里已包含所有依賴,所以你不需要再安裝或者配置額外的東西。如果有任何編譯或運(yùn)行上的問題,請聯(lián)系我。

            矩陣原理

            本文講的最多的就是關(guān)于3D中的矩陣,所以讓我們在寫代碼前先了解下矩陣原理。我不會過多關(guān)注數(shù)學(xué),網(wǎng)上有很多好的這類資源。我們只需要使用GLM來實現(xiàn)相關(guān)運(yùn)算。我會注重于那些應(yīng)用在我們3D程序里的矩陣。

            矩陣是用來進(jìn)行3D變換。可能的變換包括(點擊可以看動畫):

            一個矩陣是一個數(shù)字表格,像這樣:

             

            矩陣英文matrix的復(fù)數(shù)形式是matrices。

            不同的數(shù)值的能產(chǎn)生不同類型的變換。上面的那個矩陣會繞著Z軸旋轉(zhuǎn)90°。我們會使用GLM來創(chuàng)建矩陣,所以我們不用理解如何計算出這些數(shù)值。

            矩陣可以有任意行和列,但3D變換使用4×4矩陣,就像上面看到的那樣。無論我在那說到“矩陣”,指的就是4×4矩陣。

            當(dāng)用代碼實現(xiàn)矩陣時,一般會用一個浮點數(shù)組來表示。我們使用glm::mat4類來表示4×4矩陣。

            兩個最重要的矩陣操作是:

            • matrix × matrix = combined matrix
            • matrix × coordinate = transformed coordinate

            矩陣 × 矩陣

            當(dāng)你要對兩個矩陣進(jìn)行相乘時,它們的乘積是一個包含兩者變換的新矩陣。

            比如,你將一個旋轉(zhuǎn)矩陣乘以一個平移矩陣,得到的結(jié)果就是“組合”矩陣,即先旋轉(zhuǎn)然后平移。下面的例子展示這類矩陣相乘。

             

            不像普通的乘法,矩陣乘法中順序很重要。 比如,AB是矩陣,A*B不一定等于B*A。下面我們會使用相同的矩陣,但改變下乘法順序:

             

            注意不同的順序,結(jié)果也不同。下面動畫說明順序有多重要。相同的矩陣,不同的順序。兩個變換分別是沿Y軸上移,和旋轉(zhuǎn)45°。

             

             

            當(dāng)你編碼的時候,假如看到變換出錯,請回頭檢查下你的矩陣運(yùn)算是否是正確的順序。

            矩陣 × 坐標(biāo)

            當(dāng)你用矩陣乘以一個坐標(biāo)時,它們的乘積就是一個變換后的新坐標(biāo)。

            比如,你有上面提到的旋轉(zhuǎn)矩陣,乘上坐標(biāo)(1,1,0),它的結(jié)果就是(-1,1,0)。變換后的坐標(biāo)就是原始坐標(biāo)繞著Z周旋轉(zhuǎn)90°。下面是該乘法的圖例:

             

            為何我們會使用4D坐標(biāo)

            你可能注意到了上面的坐標(biāo)是4D的,而非3D。它的格式是這樣的:

             

            為何我們會使用4D坐標(biāo)?因為我們需要用4x4的矩陣完成所有我們需要的3D變換。不管怎樣,矩陣乘法需要左邊的列數(shù)等于右邊的行數(shù)。這就意味著4x4矩陣無法與3D坐標(biāo)相乘,因為矩陣有4列,但坐標(biāo)只有3行。我們需要使用4D坐標(biāo),因為4x4的矩陣需要用它們來完成矩陣運(yùn)算。

            一些變換,比如旋轉(zhuǎn),縮放,只需要3x3矩陣。對于這些變換,我們不需要4D坐標(biāo),因為3D坐標(biāo)就能運(yùn)算。但無論如何,變換需要至少是4x3的矩陣,而透視投影矩陣需要4x4矩陣,而我們兩者都會用到,所以我們強(qiáng)制使用4D。

            這些被稱為齊次坐標(biāo)。在后續(xù)的教程里,我們會講到有向光照,那里我們會學(xué)到有關(guān)“W”維度的表示。在這里,我們只需要將3D轉(zhuǎn)換為4D。3D轉(zhuǎn)換為4D只要將第四維坐標(biāo)“W”設(shè)為1即可。比如,坐標(biāo)(22,33,44)轉(zhuǎn)換為:

             

            當(dāng)需要將4D坐標(biāo)變?yōu)?D時,假如“W”維度是1,你可以直接忽略它,使用X,Y,Z的值即可。如果你發(fā)現(xiàn)“W”的值不為1,好吧,你就需要做些額外處理,或者這里出了個bug。

            構(gòu)造一個立方體

            代碼上第一個變動就是用立方體替換之前的三角形。

            我們用三角形來構(gòu)造立方體,用兩個三角形表示6個面的每個面。在舊版本的OpengGL中,我們可以使用1個正方形(GL_QUADS)來替代2個三角表示每個面,但GL_QUADS已經(jīng)被現(xiàn)代版本的OpenGL給移除了。X,Y,Z坐標(biāo)值域為-1到1,這意味著立方體是兩個單位寬,立方體中心點在原點(原點坐標(biāo)(0,0,0))。我們將使用256×256的貼圖給立方體每個面貼上。后序文章中都會使用這個數(shù)據(jù),我們不需要改變太多。這里有立方體數(shù)據(jù):

            GLfloat vertexData[] = { //  X     Y     Z       U     V // bottom -1.0f,-1.0f,-1.0f, 0.0f, 0.0f, 1.0f,-1.0f,-1.0f, 1.0f, 0.0f, -1.0f,-1.0f, 1.0f, 0.0f, 1.0f, 1.0f,-1.0f,-1.0f, 1.0f, 0.0f, 1.0f,-1.0f, 1.0f, 1.0f, 1.0f, -1.0f,-1.0f, 1.0f, 0.0f, 1.0f, // top -1.0f, 1.0f,-1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f,-1.0f, 1.0f, 0.0f, 1.0f, 1.0f,-1.0f, 1.0f, 0.0f, -1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, // front -1.0f,-1.0f, 1.0f, 1.0f, 0.0f, 1.0f,-1.0f, 1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,-1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, // back -1.0f,-1.0f,-1.0f, 0.0f, 0.0f, -1.0f, 1.0f,-1.0f, 0.0f, 1.0f, 1.0f,-1.0f,-1.0f, 1.0f, 0.0f, 1.0f,-1.0f,-1.0f, 1.0f, 0.0f, -1.0f, 1.0f,-1.0f, 0.0f, 1.0f, 1.0f, 1.0f,-1.0f, 1.0f, 1.0f, // left -1.0f,-1.0f, 1.0f, 0.0f, 1.0f, -1.0f, 1.0f,-1.0f, 1.0f, 0.0f, -1.0f,-1.0f,-1.0f, 0.0f, 0.0f, -1.0f,-1.0f, 1.0f, 0.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f,-1.0f, 1.0f, 0.0f, // right 1.0f,-1.0f, 1.0f, 1.0f, 1.0f, 1.0f,-1.0f,-1.0f, 1.0f, 0.0f, 1.0f, 1.0f,-1.0f, 0.0f, 0.0f, 1.0f,-1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,-1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f }; 

            我們需要更改下Render函數(shù)中glDrawArrays調(diào)用,之前是用來繪制三角形的。立方體6個面,每個面有2個三角形,每個三角形有3個頂點,所以需要繪制的頂點數(shù)是:6 × 2 × 3 = 36。新的glDrawArrays調(diào)用像這樣:

            glDrawArrays(GL_TRIANGLES, 0, 6*2*3); 

            最后,我們使用新的貼圖“wooden-crate.jpg”,我們更改LoadTexture中的文件名,如下:

            tdogl::Bitmap bmp = tdogl::Bitmap::bitmapFromFile(ResourcePath("wooden-crate.jpg")); 

            就是這樣!我們已經(jīng)提供了所有繪制帶貼圖立方體的需要用到的數(shù)據(jù)。假如你運(yùn)行程序,你可以看到這樣的:

             

            此時此刻,我們有兩個問題。第一,這個立方體看上去非常2D,因為我們只看到了一個面。我們需要“移動相機(jī)”,以不同角度觀察這個立方體。第二,上面有些問題,因為立方體寬和高應(yīng)該相等,但從截圖看上去寬度明顯比高度大。為了修復(fù)這兩個問題,我們需要學(xué)習(xí)更多的矩陣知識,和如何應(yīng)用到3D程序中。

            裁剪體 - 默認(rèn)相機(jī)

            為了理解3D中的“相機(jī)”,我們首先得理解裁剪體。

            裁剪體是一個立方體。無論什么東西在裁剪體中的都會顯示在屏幕上,任何在裁剪體之外的都不會顯示。裁剪體跟我們上面的立方體是相同大小,它的X,Y,Z坐標(biāo)值域也是從-1到+1。-X表示左邊,+X表示右邊,-Y是底部,+Y是頂部,+Z是遠(yuǎn)離相機(jī),-Z是朝著相機(jī)。

            因為我們的立方體和裁剪體一樣大,所以我們只能看到立方體的正面。

            這也解釋了為何我們的立方體看起來比較寬。窗口顯示了裁剪體里的所有東西。窗口的左右邊緣是X軸的-1和+1,窗口的底部和頂部邊緣是Y軸的-1和+1。裁剪體被拉伸了,用來跟窗口的可視大小相適應(yīng),所以我們的立方體看上去不是正方形的。

            固定住相機(jī),讓世界移動起來

            我們需要移動相機(jī),使得可以從不同角度進(jìn)行觀察,或放大縮小。但不管怎樣,裁剪體不會更改。它永遠(yuǎn)是一樣的大小和位置。所以我們換種方式來替代移動相機(jī),我們可以移動3D場景讓它正確得出現(xiàn)在裁剪體中。比如,我們想要讓相機(jī)往右旋轉(zhuǎn),我們可以把整個世界往左旋轉(zhuǎn)。假如我們想要讓相機(jī)離玩家近些,我們可以把玩家挪到相機(jī)前。這就是“相機(jī)”在3D中的工作方式,變換整個世界使得它出現(xiàn)在裁剪體中并且看上去是正確的。

            無論你走到哪里,都會覺得是世界沒動,是你在移動。但你也能想象出當(dāng)你不動,而世界在你腳下滾動,就像你在跑步機(jī)上一樣。這就是“移動相機(jī)”和“移動世界”的區(qū)別,這兩種方式,對于觀察者而言,看上去都是一樣的。

            我們?nèi)绾螌?D場景進(jìn)行變換來適應(yīng)裁剪體呢?這里我們需要用到矩陣。

            實現(xiàn)相機(jī)矩陣

            讓我們先來實現(xiàn)相機(jī)矩陣。3D中“相機(jī)”的解釋可認(rèn)為是對3D場景的一系列變換。因為相機(jī)就是一個變換,所以我們可以用矩陣來表示。

            首先,我們需要包含GLM頭文件,用來創(chuàng)建不同類型的矩陣。

            #include <glm/gtc/matrix_transform.hpp> 

            接著,我們需要更新頂點著色器。我們創(chuàng)建一個相機(jī)矩陣變量叫做camera,并且每個頂點都會乘上這個相機(jī)矩陣。這樣我們就將整個3D場景進(jìn)行了變換。每個頂點都會被相機(jī)矩陣所變換。新的頂點著色器看上去應(yīng)該是這樣的:

            #version 150  uniform mat4 camera; //this is the new variable  in vec3 vert; in vec2 vertTexCoord;  out vec2 fragTexCoord;  void main() { // Pass the tex coord straight through to the fragment shader fragTexCoord = vertTexCoord; // Transform the input vertex with the camera matrix gl_Position = camera * vec4(vert, 1); } 

            現(xiàn)在我們需要在C++代碼中設(shè)置camera著色器變量。在LoadShaders函數(shù)的地步,我們添加這樣的代碼:

            gProgram->use();  glm::mat4 camera = glm::lookAt(glm::vec3(3,3,3), glm::vec3(0,0,0), glm::vec3(0,1,0)); gProgram->setUniform("camera", camera);  gProgram->stopUsing(); 

            這個相機(jī)矩陣在本文中不會再被改變,當(dāng)所有著色器被創(chuàng)建后,我們只需這樣設(shè)置一次。

            你無法在設(shè)置著色器變量,除非著色器在使用中,這就是為何我們用到了gProgram->use()gProgram->stopUsing()

            我們使用glm::lookAt函數(shù)為我們創(chuàng)建相機(jī)矩陣。假如你使用的是舊版本的OpenGL,那你應(yīng)該使用gluLookAt函數(shù)來達(dá)到相同目的,但gluLookAt已經(jīng)在最近的OpenGL版本中被移除了。第一個參數(shù)glm::vec3(3,3,3)是相機(jī)的位置。第二個參數(shù)glm::vec3(0,0,0)是相機(jī)觀察的點。立方體中心是(0,0,0),相機(jī)就朝著這個點觀察。最后一個參數(shù)glm::vec3(0,1,0)是“向上”的方向。我們需要垂直擺放相機(jī),所以我們設(shè)置“向上”是沿著Y軸的正方向。假如相機(jī)是顛倒或者傾斜的,這里就是其它值了。

            在我們生成了相機(jī)矩陣后,我們用gProgram->setUniform("camera", camera);來設(shè)置camera著色器變量,setUniform方法屬于tdogl::Program類,它會調(diào)用glUniformMatrix4fv來設(shè)置變量。

            就是這樣!我們現(xiàn)在有了一個可運(yùn)行的相機(jī)。

            不幸的是,假如你現(xiàn)在運(yùn)行程序,你會看到整個都是黑屏。因為我們的立方體頂點經(jīng)過相機(jī)矩陣變換后,飛出了裁剪體。這就是上面我提到的,在裁剪體之外的它是不會被顯示。為了能再次看到它,我們需要設(shè)置投影矩陣

            實現(xiàn)投影矩陣

            記住裁剪體只有2個單元寬、高和深。假設(shè)1個單元等于我們3D場景中的1米。這就意味著我們在相機(jī)中能看到正前方2米,這樣不是很方便。

            我們需要擴(kuò)大裁剪體使得能看到3D場景中的更多東西,可憐我們又不能改變裁剪體的大小,但,我們能縮小整個場景。縮小是一個變換,所以我們用矩陣來表示,基本上說,投影矩陣就是用來干這個的。

            讓我們在頂點著色器中加入投影矩陣變量。更新后的代碼看上去是這樣的:

            #version 150  uniform mat4 projection; //this is the new variable uniform mat4 camera;  in vec3 vert; in vec2 vertTexCoord;  out vec2 fragTexCoord;  void main() { // Pass the tex coord straight through to the fragment shader fragTexCoord = vertTexCoord; // Apply camera and projection transformations to the vertex gl_Position = projection * camera * vec4(vert, 1); } 

            注意矩陣相乘的順序:projection * camera * vert。相機(jī)變換是放在首位的,投影矩陣是第二位。矩陣乘法中,變換從右往左,從頂點角度說是從最近的變換到更早前的變換。

            現(xiàn)在讓我們在C++代碼中設(shè)置projection著色器變量,方式和我們設(shè)置camera變量相同。在LoadShaders函數(shù)中,添加如下代碼:

            glm::mat4 projection = glm::perspective(glm::radians(50.0f), SCREEN_SIZE.x/SCREEN_SIZE.y, 0.1f, 10.0f); gProgram->setUniform("projection", projection); 

            假如你使用的是舊版本OpenGL,你可以使用gluPerspective來設(shè)置投影矩陣,同樣gluPerspective函數(shù)在最近版本的OpenGL中也被移除了。幸運(yùn)的是你可以使用glm::perspective來替代。

            glm::perspective第一個參數(shù)是“可視區(qū)域”參數(shù)。這個參數(shù)是個弧度,用來說明相機(jī)視野有多寬。弧度換算我們可以用glm::radians函數(shù)來將50度轉(zhuǎn)換為弧度。大的可視區(qū)域意味著我們的相機(jī)可以看到更多場景,看上去就像是縮小了。小的可視區(qū)域意味著相機(jī)只能看到場景的一小部分,看上去像是放大了。第二個參數(shù)是“縱橫比”,該參數(shù)表示可視區(qū)域的縱橫比率。一般該參數(shù)設(shè)置為窗口的width/height,倒數(shù)第二個參數(shù)是“近平面”,近平面是裁剪體的前面,0.1表示近平面離相機(jī)是0.1單位遠(yuǎn)。任何離相機(jī)小于0.1單位的物體均不可見。近平面的值必須大于0。最后一個參數(shù)是“遠(yuǎn)平面”,遠(yuǎn)平面是裁剪體的后面。10.0表示相機(jī)所顯示的物體均離相機(jī)10個單位之內(nèi)。任何大于10單位的物體均不可見。我們的立方體是3單位遠(yuǎn),所以它能被看見。

            glm::perspective對將可視錐體對應(yīng)到裁剪體中非常有用。一個錐體像是一個金字塔被砍掉了頂端。金字塔的底部就是遠(yuǎn)平面,頂部就是近平面。可視區(qū)域就是該錐體胖瘦。任何在錐體里的物體都會被顯示,而不再內(nèi)的就隱藏。

             

            有了相機(jī)矩陣和投影矩陣的組合,我們就可以看到立方體了。運(yùn)行程序你會看到:

             

            這看上去。。。幾乎是對的。

            這個立方體看上去已經(jīng)是正方形了,不再是矩形。這是因為glm::perspective中的“縱橫比”參數(shù),能夠基于窗口的寬和高進(jìn)行正確的調(diào)整比例。

            不幸的是,截圖看上去立方體的背面渲染并覆蓋到前面來了。我們當(dāng)然不希望發(fā)生這樣的事,我們需要開啟深度緩沖來解決。

            深度緩沖

            OpenGL默認(rèn)會將最新的繪制覆蓋到之前的繪制上。假如一個物體的背面在前面之后繪制,就會發(fā)生背面擋住前面。深度緩沖就是為了防止背景層覆蓋到前景層的東西。

            假如深度緩沖被開啟,每個被繪制的像素到相機(jī)的距離都是可知的。這個距離會以一個數(shù)值保存在深度緩沖里。當(dāng)你繪制一個像素在另外一個已存在的像素上時,OpenGL會查找深度緩沖來決定哪個像素應(yīng)該離相機(jī)更近。假如新的像素離相機(jī)更近,那該像素點就會被重寫。假如之前的像素離相機(jī)更近,那新像素就會被拋棄。所以,一個之前已存在的像素只會當(dāng)新像素離相機(jī)更近時才會被重寫。這就叫做“深度測試”。

            實現(xiàn)深度緩沖

            AppMain函數(shù)中,調(diào)用了glewInit之后,我們添加如下代碼:

            glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); 

            這告訴OpenGL開啟深度測試。調(diào)用glDepthFunc是表明假如像素離相機(jī)的距離小于之前的像素距離時應(yīng)該被重寫。

            最后一步我們需要在渲染每幀之后清理深度緩沖。假如我們不清理,舊的像素距離會保存在緩沖中,這樣會影響到繪制新的一幀。在Render函數(shù)里,我們改變glClear來實現(xiàn)它:

            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 

             

            旋轉(zhuǎn)立方體

            假如你完成了上述例子,祝賀你走了這么遠(yuǎn)!最后我們來實現(xiàn)會旋轉(zhuǎn)的立方體動畫。

            如何實現(xiàn)旋轉(zhuǎn)?你會猜到:另外一個矩陣。這與之前的矩陣不同的是,這個矩陣是每幀都在改變,之前的矩陣都是常量。

            我需要新建一個“模型”矩陣。在常見的3D引擎中,每個物體都有一個模型矩陣。相機(jī)和投影矩陣對整個場景來說是一樣的,但模型矩陣是每個物體都不同。模型矩陣用來擺放每個物體在正確的位置上(平移),設(shè)置正確的面向(旋轉(zhuǎn)),或者改變物體大小(縮放)。我們只有一個物體在當(dāng)前3D場景上,所以,我們只需要一個模型矩陣。

            讓我們添加一個model矩陣變量到頂點著色器,就像我們添加相機(jī)和投影一樣。最終版本的頂點著色器應(yīng)該是這樣的:

            #version 150  uniform mat4 projection; uniform mat4 camera; uniform mat4 model; //this is the new variable  in vec3 vert; in vec2 vertTexCoord;  out vec2 fragTexCoord;  void main() { // Pass the tex coord straight through to the fragment shader fragTexCoord = vertTexCoord; // Apply all matrix transformations to vert gl_Position = projection * camera * model * vec4(vert, 1); } 

            還是要注意矩陣相乘的順序。模型矩陣是vert變量最近的一次變換,意味著模型矩陣應(yīng)該第一個被使用,其次是相機(jī),最后是投影。

            現(xiàn)在我們需要設(shè)置新的model著色器變量。不像相機(jī)和投影變量,模型變量需要每幀都被設(shè)置,所以我們把它放在Render函數(shù)里。在gProgram->use()之后添加這樣的代碼:

            gProgram->setUniform("model", glm::rotate(glm::mat4(), glm::radians(45.0f), glm::vec3(0,1,0))); 

            我們使用glm::rotate函數(shù)創(chuàng)建一個旋轉(zhuǎn)矩陣。第一個參數(shù)是一個已存在的需要進(jìn)行旋轉(zhuǎn)的矩陣。在這我們不需要對已存在的矩陣進(jìn)行旋轉(zhuǎn),所以我們傳個新的glm::mat4對象就可以了。下一個參數(shù)是旋轉(zhuǎn)的角度,或者說是要旋轉(zhuǎn)多少度。現(xiàn)在讓我給它設(shè)置個45°。最后一個參數(shù)是旋轉(zhuǎn)的軸。想象下旋轉(zhuǎn)像是將物體插在叉子上,然后轉(zhuǎn)動叉子。叉子就是軸,角度就是你的轉(zhuǎn)動。在我們的例子中,我們使用垂直的叉子,所以立方體像在一個平臺上旋轉(zhuǎn)。

            運(yùn)行程序,你們看到立方體被旋轉(zhuǎn):

             

            它還沒有轉(zhuǎn)動,因為矩陣沒有被更改-它永遠(yuǎn)是旋轉(zhuǎn)了45°。最后一步就是讓它每幀都旋轉(zhuǎn)一下。

            動畫

            首先,添加一個新的全局變量叫gDegreesRotated

            GLfloat gDegreesRotated = 0.0f; 

            每幀,我們會輕微的增加gDegreesRotated,并且我們用它來計算新的旋轉(zhuǎn)矩陣。這樣就能達(dá)到動畫效果。我們需要做的就是更新,繪制,更新,繪制,更新,繪制,這樣一個模式。

            讓我們創(chuàng)建一個Update函數(shù),用來每次增加gDegreesRotated

            void Update() { //rotate by 1 degree gDegreesRotated += 1.0f; //don't go over 360 degrees while(gDegreesRotated > 360.0f) gDegreesRotated -= 360.0f; } 

            我們需要每幀都調(diào)用一次Update函數(shù)。讓我們把它加入到AppMain的循環(huán)中,在調(diào)用Render之前。

            while(glfwGetWindowParam(GLFW_OPENED)){ // process pending events glfwPollEvents(); // update the rotation animation Update(); // draw one frame Render(); } 

            現(xiàn)在我們需要基于gDegreesRotated變量來重新計算模型矩陣。在Render函數(shù)中我們修改相關(guān)代碼來設(shè)置模型矩陣:

            gProgram->setUniform("model", glm::rotate(glm::mat4(), glm::radians(gDegreesRotated), glm::vec3(0,1,0))); 

            與之前唯一不同的是我們使用了gDegreesRotated來替換45°常量。

            你現(xiàn)在運(yùn)行程序能看到一個漂亮,平滑轉(zhuǎn)動的立方體動畫。唯一的問題就是轉(zhuǎn)動的速度很你的FPS幀率有關(guān)。假如FPS高,你的立方體旋轉(zhuǎn)的就快。假如FPS降低,那立方體旋轉(zhuǎn)的就慢些。這不夠理想。一個程序應(yīng)該能正確更新,而不在乎于運(yùn)行的幀率。

            基于時間的動畫

            為了使程序跑起來更正確,不依賴于FPS,動畫應(yīng)該每秒更新,而非每幀更新。最簡單得方式就是對時間進(jìn)行計數(shù),并相對上次更新時間來正確更新。讓我們改下Update函數(shù),增加個變量secondsElapsed

            void Update(float secondsElapsed) { const GLfloat degreesPerSecond = 180.0f; gDegreesRotated += secondsElapsed * degreesPerSecond; while(gDegreesRotated > 360.0f) gDegreesRotated -= 360.0f; } 

            這段代碼使得立方體每秒旋轉(zhuǎn)180°,而無關(guān)多少幀率。

            AppMain循環(huán)中,我們需要計算離上次更新過去了多少秒。新的循環(huán)應(yīng)該是這樣:

            double lastTime = glfwGetTime(); while(glfwGetWindowParam(GLFW_OPENED)){ // process pending events glfwPollEvents(); // update the scene based on the time elapsed since last update double thisTime = glfwGetTime(); Update((float)(thisTime - lastTime)); lastTime = thisTime; // draw one frame Render(); } 

            glfwGetTime返回從程序啟動開始到現(xiàn)在所逝去的時間。

            我們使用lastTime變量來記錄上次更新時間。每次迭代,我們獲取最新的時間存入變量thisTime。從上次更新到現(xiàn)在的差值就是thisTime - lastTime。當(dāng)更新結(jié)束,我們設(shè)置lastTime = thisTime以便下次循環(huán)迭代的時候很正常工作。

            這是基于時間更新的最簡單方法。這里還有更好的更新方法,但我們還不需要搞得這么復(fù)雜。

            下篇預(yù)告

            下一篇,我們會使用tdogl::Camera類來實現(xiàn)用鍵盤操作第一人稱射擊類型的相機(jī)移動,可以用鼠標(biāo)觀察不同方向,或者用鼠標(biāo)滾輪來放大縮小。

            更多資源

            posted on 2015-08-14 17:03 威士忌 閱讀(1785) 評論(0)  編輯 收藏 引用

            只有注冊用戶登錄后才能發(fā)表評論。
            網(wǎng)站導(dǎo)航: 博客園   IT新聞   BlogJava   博問   Chat2DB   管理


            久久夜色精品国产欧美乱| 久久久久亚洲Av无码专| 久久国产香蕉一区精品| 国产精品久久影院| 欧美伊香蕉久久综合类网站| 色偷偷888欧美精品久久久| 久久久久18| 伊人久久大香线蕉综合5g| 久久婷婷是五月综合色狠狠| 日韩AV无码久久一区二区| 久久国产精品77777| 久久久99精品成人片中文字幕 | 国产精品久久网| 99久久国产热无码精品免费久久久久| 国产成人精品久久二区二区| 国产成人综合久久精品尤物| 国产成人精品综合久久久| 潮喷大喷水系列无码久久精品| segui久久国产精品| 精品久久久久久久久免费影院| 久久国产亚洲高清观看| 欧美亚洲另类久久综合婷婷| 精品综合久久久久久888蜜芽| 99久久精品免费国产大片| 久久免费看黄a级毛片| 国产精品VIDEOSSEX久久发布| 一本一本久久A久久综合精品| 国产精品丝袜久久久久久不卡| 99久久国产亚洲综合精品| 国产亚洲成人久久| 久久久亚洲欧洲日产国码aⅴ| 亚洲精品99久久久久中文字幕| 日韩精品久久久久久| AV无码久久久久不卡网站下载| 久久乐国产综合亚洲精品| 久久99精品国产99久久6| 久久久久亚洲av无码专区| 日韩人妻无码精品久久久不卡 | 久久亚洲国产成人影院网站| 中文字幕成人精品久久不卡| 久久国产热精品波多野结衣AV|