• <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

            公告

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

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

            常用鏈接

            留言簿(1)

            我參與的團(tuán)隊(duì)

            搜索

            •  

            積分與排名

            • 積分 - 51538
            • 排名 - 445

            最新評(píng)論

            閱讀排行榜

            評(píng)論排行榜

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

             

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

             

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

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

            獲取代碼

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

            這一系列文章中所使用的代碼都存放在:https://github.com/tomdalling/opengl-series。你可以在頁面中下載zip,加入你會(huì)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)行上的問題,請(qǐng)聯(lián)系我。

            矩陣原理

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

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

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

             

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

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

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

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

            兩個(gè)最重要的矩陣操作是:

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

            矩陣 × 矩陣

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

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

             

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

             

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

             

             

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

            矩陣 × 坐標(biāo)

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

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

             

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

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

             

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

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

            這些被稱為齊次坐標(biāo)。在后續(xù)的教程里,我們會(huì)講到有向光照,那里我們會(huì)學(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時(shí),假如“W”維度是1,你可以直接忽略它,使用X,Y,Z的值即可。如果你發(fā)現(xiàn)“W”的值不為1,好吧,你就需要做些額外處理,或者這里出了個(gè)bug。

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

            代碼上第一個(gè)變動(dòng)就是用立方體替換之前的三角形。

            我們用三角形來構(gòu)造立方體,用兩個(gè)三角形表示6個(gè)面的每個(gè)面。在舊版本的OpengGL中,我們可以使用1個(gè)正方形(GL_QUADS)來替代2個(gè)三角表示每個(gè)面,但GL_QUADS已經(jīng)被現(xiàn)代版本的OpenGL給移除了。X,Y,Z坐標(biāo)值域?yàn)?1到1,這意味著立方體是兩個(gè)單位寬,立方體中心點(diǎn)在原點(diǎn)(原點(diǎn)坐標(biāo)(0,0,0))。我們將使用256×256的貼圖給立方體每個(gè)面貼上。后序文章中都會(huì)使用這個(gè)數(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個(gè)面,每個(gè)面有2個(gè)三角形,每個(gè)三角形有3個(gè)頂點(diǎn),所以需要繪制的頂點(diǎn)數(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)行程序,你可以看到這樣的:

             

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

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

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

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

            因?yàn)槲覀兊牧⒎襟w和裁剪體一樣大,所以我們只能看到立方體的正面。

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

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

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

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

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

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

            讓我們先來實(shí)現(xiàn)相機(jī)矩陣。3D中“相機(jī)”的解釋可認(rèn)為是對(duì)3D場(chǎng)景的一系列變換。因?yàn)橄鄼C(jī)就是一個(gè)變換,所以我們可以用矩陣來表示。

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

            #include <glm/gtc/matrix_transform.hpp> 

            接著,我們需要更新頂點(diǎn)著色器。我們創(chuàng)建一個(gè)相機(jī)矩陣變量叫做camera,并且每個(gè)頂點(diǎn)都會(huì)乘上這個(gè)相機(jī)矩陣。這樣我們就將整個(gè)3D場(chǎng)景進(jìn)行了變換。每個(gè)頂點(diǎn)都會(huì)被相機(jī)矩陣所變換。新的頂點(diǎn)著色器看上去應(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(); 

            這個(gè)相機(jī)矩陣在本文中不會(huì)再被改變,當(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版本中被移除了。第一個(gè)參數(shù)glm::vec3(3,3,3)是相機(jī)的位置。第二個(gè)參數(shù)glm::vec3(0,0,0)是相機(jī)觀察的點(diǎn)。立方體中心是(0,0,0),相機(jī)就朝著這個(gè)點(diǎn)觀察。最后一個(gè)參數(shù)glm::vec3(0,1,0)是“向上”的方向。我們需要垂直擺放相機(jī),所以我們?cè)O(shè)置“向上”是沿著Y軸的正方向。假如相機(jī)是顛倒或者傾斜的,這里就是其它值了。

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

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

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

            實(shí)現(xiàn)投影矩陣

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

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

            讓我們?cè)陧旤c(diǎn)著色器中加入投影矩陣變量。更新后的代碼看上去是這樣的:

            #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ī)變換是放在首位的,投影矩陣是第二位。矩陣乘法中,變換從右往左,從頂點(diǎn)角度說是從最近的變換到更早前的變換。

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

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

             

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

             

            這看上去。。。幾乎是對(duì)的。

            這個(gè)立方體看上去已經(jīng)是正方形了,不再是矩形。這是因?yàn)?code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: 14px; padding: 2px 4px; color: #c7254e; white-space: nowrap; border-radius: 4px; background-color: #f9f2f4;">glm::perspective中的“縱橫比”參數(shù),能夠基于窗口的寬和高進(jìn)行正確的調(diào)整比例。

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

            深度緩沖

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

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

            實(shí)現(xiàn)深度緩沖

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

            glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); 

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

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

            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 

             

            旋轉(zhuǎn)立方體

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

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

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

            讓我們添加一個(gè)model矩陣變量到頂點(diǎn)著色器,就像我們添加相機(jī)和投影一樣。最終版本的頂點(diǎn)著色器應(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)該第一個(gè)被使用,其次是相機(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)建一個(gè)旋轉(zhuǎn)矩陣。第一個(gè)參數(shù)是一個(gè)已存在的需要進(jìn)行旋轉(zhuǎn)的矩陣。在這我們不需要對(duì)已存在的矩陣進(jìn)行旋轉(zhuǎn),所以我們傳個(gè)新的glm::mat4對(duì)象就可以了。下一個(gè)參數(shù)是旋轉(zhuǎn)的角度,或者說是要旋轉(zhuǎn)多少度。現(xiàn)在讓我給它設(shè)置個(gè)45°。最后一個(gè)參數(shù)是旋轉(zhuǎn)的軸。想象下旋轉(zhuǎn)像是將物體插在叉子上,然后轉(zhuǎn)動(dòng)叉子。叉子就是軸,角度就是你的轉(zhuǎn)動(dòng)。在我們的例子中,我們使用垂直的叉子,所以立方體像在一個(gè)平臺(tái)上旋轉(zhuǎn)。

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

             

            它還沒有轉(zhuǎn)動(dòng),因?yàn)榫仃嚊]有被更改-它永遠(yuǎn)是旋轉(zhuǎn)了45°。最后一步就是讓它每幀都旋轉(zhuǎn)一下。

            動(dòng)畫

            首先,添加一個(gè)新的全局變量叫gDegreesRotated

            GLfloat gDegreesRotated = 0.0f; 

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

            讓我們創(chuàng)建一個(gè)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變量來重新計(jì)算模型矩陣。在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)行程序能看到一個(gè)漂亮,平滑轉(zhuǎn)動(dòng)的立方體動(dòng)畫。唯一的問題就是轉(zhuǎn)動(dòng)的速度很你的FPS幀率有關(guān)。假如FPS高,你的立方體旋轉(zhuǎn)的就快。假如FPS降低,那立方體旋轉(zhuǎn)的就慢些。這不夠理想。一個(gè)程序應(yīng)該能正確更新,而不在乎于運(yùn)行的幀率。

            基于時(shí)間的動(dòng)畫

            為了使程序跑起來更正確,不依賴于FPS,動(dòng)畫應(yīng)該每秒更新,而非每幀更新。最簡(jiǎn)單得方式就是對(duì)時(shí)間進(jìn)行計(jì)數(shù),并相對(duì)上次更新時(shí)間來正確更新。讓我們改下Update函數(shù),增加個(gè)變量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)中,我們需要計(jì)算離上次更新過去了多少秒。新的循環(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返回從程序啟動(dòng)開始到現(xiàn)在所逝去的時(shí)間。

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

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

            下篇預(yù)告

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

            更多資源

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

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


            国内精品伊人久久久久影院对白| 久久久久久精品免费看SSS| 四虎国产精品成人免费久久| 91精品观看91久久久久久| 日本精品久久久久中文字幕8| 99久久国产热无码精品免费| 久久精品国内一区二区三区| 国产精品美女久久久久AV福利| 久久精品人妻一区二区三区| 亚洲欧美日韩久久精品| 久久婷婷五月综合色高清| 久久久久国产精品麻豆AR影院| 久久久亚洲裙底偷窥综合 | 久久精品女人天堂AV麻| 伊人久久大香线蕉综合5g| 色综合久久精品中文字幕首页| 久久精品卫校国产小美女| 久久精品国产亚洲7777| 色偷偷88欧美精品久久久| 免费观看成人久久网免费观看| 久久久久久久女国产乱让韩| 久久国产成人| 久久99精品久久久久久秒播| 91久久精品国产成人久久| 国产精品久久久久久搜索| 久久青青草原精品国产| 久久天天躁狠狠躁夜夜躁2O2O | 国产精品岛国久久久久| 国内精品久久久久久久久电影网| 亚洲AⅤ优女AV综合久久久| 国产高清国内精品福利99久久| 久久精品九九亚洲精品天堂| 好属妞这里只有精品久久| 超级碰久久免费公开视频| 亚洲国产精品嫩草影院久久| 久久精品亚洲精品国产欧美| 一级做a爰片久久毛片看看| 久久精品欧美日韩精品| 亚洲国产天堂久久久久久| 99re这里只有精品热久久| 亚洲欧美日韩久久精品|