• <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++博客 :: 首頁 :: 聯系 :: 聚合  :: 管理
              12 Posts :: 0 Stories :: 23 Comments :: 0 Trackbacks

            公告

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

            Home: huangwei.pro
            E-Mail: sir.huangwei [at] gmail.com
            09.6 畢業于杭州電子科技大學
            進入網易杭州研究院工作至今

            常用鏈接

            留言簿(1)

            我參與的團隊

            搜索

            •  

            積分與排名

            • 積分 - 51417
            • 排名 - 443

            最新評論

            閱讀排行榜

            評論排行榜

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

             

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

             

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

            為了生成旋轉立方體,我們需要學些關于矩陣的數學,用于創建透視投影,旋轉,平移和“相機”概念。我們還有必要學習些深度緩沖,和典型的隨時間改變的3D應用,比如動畫。

            獲取代碼

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

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

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

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

            矩陣原理

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

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

            一個矩陣是一個數字表格,像這樣:

             

            矩陣英文matrix的復數形式是matrices。

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

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

            當用代碼實現矩陣時,一般會用一個浮點數組來表示。我們使用glm::mat4類來表示4×4矩陣。

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

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

            矩陣 × 矩陣

            當你要對兩個矩陣進行相乘時,它們的乘積是一個包含兩者變換的新矩陣。

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

             

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

             

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

             

             

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

            矩陣 × 坐標

            當你用矩陣乘以一個坐標時,它們的乘積就是一個變換后的新坐標。

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

             

            為何我們會使用4D坐標

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

             

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

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

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

             

            當需要將4D坐標變為3D時,假如“W”維度是1,你可以直接忽略它,使用X,Y,Z的值即可。如果你發現“W”的值不為1,好吧,你就需要做些額外處理,或者這里出了個bug。

            構造一個立方體

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

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

            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函數中glDrawArrays調用,之前是用來繪制三角形的。立方體6個面,每個面有2個三角形,每個三角形有3個頂點,所以需要繪制的頂點數是:6 × 2 × 3 = 36。新的glDrawArrays調用像這樣:

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

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

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

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

             

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

            裁剪體 - 默認相機

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

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

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

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

            固定住相機,讓世界移動起來

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

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

            我們如何對3D場景進行變換來適應裁剪體呢?這里我們需要用到矩陣。

            實現相機矩陣

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

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

            #include <glm/gtc/matrix_transform.hpp> 

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

            #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); } 

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

            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(); 

            這個相機矩陣在本文中不會再被改變,當所有著色器被創建后,我們只需這樣設置一次。

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

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

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

            就是這樣!我們現在有了一個可運行的相機。

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

            實現投影矩陣

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

            我們需要擴大裁剪體使得能看到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。相機變換是放在首位的,投影矩陣是第二位。矩陣乘法中,變換從右往左,從頂點角度說是從最近的變換到更早前的變換。

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

            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來設置投影矩陣,同樣gluPerspective函數在最近版本的OpenGL中也被移除了。幸運的是你可以使用glm::perspective來替代。

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

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

             

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

             

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

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

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

            深度緩沖

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

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

            實現深度緩沖

            AppMain函數中,調用了glewInit之后,我們添加如下代碼:

            glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); 

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

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

            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 

             

            旋轉立方體

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

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

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

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

            #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變量最近的一次變換,意味著模型矩陣應該第一個被使用,其次是相機,最后是投影。

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

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

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

            運行程序,你們看到立方體被旋轉:

             

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

            動畫

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

            GLfloat gDegreesRotated = 0.0f; 

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

            讓我們創建一個Update函數,用來每次增加gDegreesRotated

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

            我們需要每幀都調用一次Update函數。讓我們把它加入到AppMain的循環中,在調用Render之前。

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

            現在我們需要基于gDegreesRotated變量來重新計算模型矩陣。在Render函數中我們修改相關代碼來設置模型矩陣:

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

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

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

            基于時間的動畫

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

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

            這段代碼使得立方體每秒旋轉180°,而無關多少幀率。

            AppMain循環中,我們需要計算離上次更新過去了多少秒。新的循環應該是這樣:

            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返回從程序啟動開始到現在所逝去的時間。

            我們使用lastTime變量來記錄上次更新時間。每次迭代,我們獲取最新的時間存入變量thisTime。從上次更新到現在的差值就是thisTime - lastTime。當更新結束,我們設置lastTime = thisTime以便下次循環迭代的時候很正常工作。

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

            下篇預告

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

            更多資源

            posted on 2015-08-14 17:03 威士忌 閱讀(1773) 評論(0)  編輯 收藏 引用
            亚洲国产成人久久精品99 | 久久青青草原精品国产| 久久久久久久精品成人热色戒| 三级韩国一区久久二区综合| 亚洲七七久久精品中文国产| 久久99热只有频精品8| 国产无套内射久久久国产| 久久精品视频一| 亚洲国产精品久久| 波多野结衣AV无码久久一区| 中文字幕一区二区三区久久网站| 亚洲午夜精品久久久久久app| 久久精品中文騷妇女内射| 久久中文字幕视频、最近更新 | 久久国产精品无| 青青草国产精品久久久久| 亚洲日本va中文字幕久久| 国产99久久久久久免费看| 久久久无码精品亚洲日韩按摩 | 国产色综合久久无码有码| 精品久久久久久久久久久久久久久 | 亚洲乱亚洲乱淫久久| 午夜精品久久久久久久久| 一本大道久久东京热无码AV| 狠狠色丁香婷婷综合久久来 | 久久国产精品免费一区| 久久婷婷成人综合色综合| 久久久精品人妻一区二区三区蜜桃| A狠狠久久蜜臀婷色中文网| 色婷婷综合久久久久中文一区二区 | 国产亚州精品女人久久久久久 | 一本色道久久综合| 亚洲国产精品成人久久蜜臀| 精品久久久久久无码中文字幕| 狠狠色丁香婷综合久久| 精品国产乱码久久久久久1区2区 | 国产精品激情综合久久| 国产成人精品久久综合| 国产真实乱对白精彩久久| 国内精品久久久久影院网站| 99久久精品国产毛片|