WebGL--開始製作一些真正的三維物體
第四課的原文:
http://learningwebgl.com/blog/?p=370
第四課的案例:http://learningwebgl.com/lessons/lesson04/index.html
這一課的代碼之間的差異和前一個是完全集中在 animate 中, initBuffers 和 drawScene 功能。 如果您向下滾動到 animate ,你會看到一些,非常微小的變化:變數,能記住當前情境中的兩個物體轉動的狀態已被重新命名,它們曾經是 rTri 和 rSquare ,現在我們有:
rPyramid += (90 * elapsed) / 1000.0;
rCube -= (75 * elapsed) / 1000.0;
這就是改變的東西了,好我們再往上看看drawscene。上面的函式宣告,我們有兩個變數有了新的定義:
var rPyramid = 0; var rCube = 0;
接下來我們看看function的頭,在那雷根據我們預先的代碼,我們作出相應的修改後,開始繪製我們的金字塔(立體的四面體)。一旦完成了這些之後,我們把y軸進行旋轉,就好像此前的步驟一樣:
mat4.rotate(mvMatrix,degToRad(rPyramid),[0,1,0]);
然後我們就開始繪製這個圖形了。這個就是和上一課的區別了。簡單吧!但是因為我們這裡需要做得更加多顏色,而且三角形的頂點也變多了,所以我們就需要用initbuffers去處理更多的頂點以及顏色,因此除了上面的變化之外,我們還需要在緩衝區裡面添加以下的代碼以適應變化:
gl.bindBuffer(gl.ARRAY_BUFFER,pyramidVertexPositionBuffer);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, pyramidVertexPositionBuffer.itemSize,gl.FLOAT,false,0,0);
gl.bindBuffer(gl.ARRAY_BUFFER, pyramidVertexColorBuffer);
gl.vertexAttribPointer(shaderProgram.vertexColorAttribute, pyramidVertexColorBuffer.itemSize,gl.FLOAT,false,0,0);
setMatrixUniforms();
gl.drawArrays(gl.TRIANGLES, 0, pyramidVertexPositionBuffer .numItems);
就是這樣子,接下來我們來看看cube(立方體)的代碼吧,第一步就是要旋轉,不過這裡不僅僅只是x軸的旋轉,因為正如你在動畫裡面所看到的一樣,我們要向觀眾呈現的角度是(向上,向右,向正面):
mat4.rotate(mvMatrix,degToRad(rCube),[1,1,1]);
PS:這裡插入一個小解釋:對於rotate的旋轉是這樣子實現的:
(x , y , z)
X:
Y:
Z:
那麼下一步就是我們描繪出一個立方體的方式了,通過以下三種的方法,您可以繪製出一個立方體,至於選用哪種方法就需要您自己去考慮以及選擇了。
1,使用一個Tirangle Strip。如果這個立方體是一種顏色,那麼製作的過程是非常簡單的。我們可以用此前用過的頂點位置(vertex positions)去畫一個面(就是立方體的一個面),然後增加另外兩個點,去增加另外兩個面。如此類推地去建立一個新的面;
2,我們通過一種“欺騙”的形式,就是說傻傻地用六個獨立的正方形面去畫出我們的立方體。這樣的話就要為每一個面訂好位置,上好顏色,有獨立的定點套。其實這樣做的方法是最精準的,而且出來的效果是最好的。但是他也有一個很大的缺點,這樣的話不僅增加了您的代碼輸入的工作量,而且還會使得電腦的計算成本增加,每次地去畫一個面出來,都要告訴webGL模板是什麼,顏色是什麼。
3,最後一個辦法就是制定兩個四面體為一個六面體(就是把兩個金字塔合起來變成一個正方體,這個可以理解吧)。此前的步驟中我們已經提及到如果去建立一個四面體了,這裡就只要你建立兩個四面體,然後把他們合在一齊形成一個六面體。這種像是利用三角形去構造成為一個正方形的方法,之所以要利用他,似乎我們每次都制定要去做整體的三角形出來(就是說兩個三角形是連一起的)。而這樣做的話,就能夠使得我們的點有重合,更好地去指定每一條邊的顏色了。因為這個編寫的方式是最簡潔的,所以我們需要用這個方法去構造我們想要的圖形!下面我們將會用到drawElements
我們要構造我們的六面體的話,首先我們就要去做出整個cube的頂點以及顏色,我們就是好像前面做三角形的一樣,建立
initbuffers,以及用適當的屬性,作出來:
gl.bindBuffer(gl.ARRAY_BUFFER,cubeVertexPositionBuffer); gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute,cubeVertexPositionBuffer.itemSize, gl.FLOAT,false,0,0);
gl.bindBuffer(gl.ARRAY_BUFFER,cubeVertexColorBuffer); gl.vertexAttribPointer(shaderProgram.vertexColorAttribute,cubeVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0);
接下來的步驟就是繪製三角形。有一個問題在這裡,你看看一個正方形(就是我們的立方體的一個面,這個面有四個頂點的位置),而且每一個頂點都有一個相關的顏色定義。繪製這個正方形的時候,我們需要用兩個三角形去繪製,因為我們使用的是簡單的三角形,他需要單獨去指定自己的頂點(指定三角形的頂點)並且共用頂點,而不是一個三角形連環(tirangle strip)。那麼也就是說我們要去指定六個頂點,因為其中有兩個頂點是重合的。不過,到最後的時候,我們僅僅用到他們中的四個,放進數組緩衝區裡面(因為沒有必要說做多兩個頂點來影響市容吧。)
就好像“利用前面三個vertices(頂點)緩衝區繪製出來的三角形,然後繪製另外一個”,這就是我們需要做的。這就是使得我們繪製出來的四方體的其餘部分也是相似的。
這個時候我們賦予這個 元素資料緩衝區 一個新的命名,這個命名就是 drawElement。就好像我們一直使用到現在的 array buffer (數組緩衝區)一樣,這個元素的數組緩衝區的值將會被填入到 inibuffers 中,而且它將會把頂點排列到我們使用的列表上,如果你想要知道詳細的情況,你可以查看代碼。
為了使用它,使得我們的立方體的元素數組緩衝區的其中一個(WebGL會保留其中的array buffer (數組緩衝區)和element array buffers(元素的數組緩衝區),所以我們需要保留那個我們現在正在使用的 gl.bindBuffer ),然後我們做一個代碼,去實現我們的模型,還有驅動我們的顯卡,我們把這個代碼塊稱為 DrawElements ,我們用它來繪製三角形:
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer); setMatrixUniforms(); gl.drawElements(gl.TRIANGLES, cubeVertexIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
這就是對於drawScene的情況,其餘的代碼都是為了initBuffers的。我們定義的名稱與新的緩衝區都是反應新的緩衝區的,以顯示出要處理新的對象。正如前面所說的,我們添加了一個新的緩衝區來定義 立方體的頂點:
var pyramidVertexPositionBuffer; var pyramidVertexColorBuffer; var cubeVertexPositionBuffer; var cubeVertexColorBuffer; var cubeVertexIndexBuffer;
我們將一些項目裡面的數值都改變帶適當的值,然後放進四面體(金字塔)的頂點位置的緩衝區上面:
pyramidVertexPositionBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, pyramidVertexPositionBuffer); var vertices = [ // Front face 0.0, 1.0, 0.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, // Right face 0.0, 1.0, 0.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, // Back face 0.0, 1.0, 0.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, // Left face 0.0, 1.0, 0.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0 ]; gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); pyramidVertexPositionBuffer.itemSize = 3; pyramidVertexPositionBuffer.numItems = 12;
同樣的原理,這裡是四面體的顏色緩衝區:
pyramidVertexColorBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, pyramidVertexColorBuffer); var colors = [ // Front face 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, // Right face 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, // Back face 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, // Left face 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0 ]; gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW); pyramidVertexColorBuffer.itemSize = 4; pyramidVertexColorBuffer.numItems = 12;
那麼這個就是指代立方體的頂點位置的緩衝區:
cubeVertexPositionBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer); vertices = [ // Front face -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, // Back face -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, // Top face -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, // Bottom face -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, 1.0, // Right face 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, // Left face -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, ]; gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); cubeVertexPositionBuffer.itemSize = 3; cubeVertexPositionBuffer.numItems = 24;
但是呢,立方體的顏色緩衝區,在設計的時候顯得稍微複雜了一點。在這裡,我們希望通過建立迴圈,來建立一個顏色的列表,那麼我們就不用說每個顏色,都要重複地指定四次:
cubeVertexColorBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexColorBuffer); colors = [ [1.0, 0.0, 0.0, 1.0], // Front face [1.0, 1.0, 0.0, 1.0], // Back face [0.0, 1.0, 0.0, 1.0], // Top face [1.0, 0.5, 0.5, 1.0], // Bottom face [1.0, 0.0, 1.0, 1.0], // Right face [0.0, 0.0, 1.0, 1.0], // Left face ]; var unpackedColors = []; for (var i in colors) { var color = colors[i]; for (var j=0; j < 4; j++) { unpackedColors = unpackedColors.concat(color); } } gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(unpackedColors), gl.STATIC_DRAW); cubeVertexColorBuffer.itemSize = 4; cubeVertexColorBuffer.numItems = 24;
最後我們定義好數組緩衝區
cubeVertexIndexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer); var cubeVertexIndices = [ 0, 1, 2, 0, 2, 3, // Front face 4, 5, 6, 4, 6, 7, // Back face 8, 9, 10, 8, 10, 11, // Top face 12, 13, 14, 12, 14, 15, // Bottom face 16, 17, 18, 16, 18, 19, // Right face 20, 21, 22, 20, 22, 23 // Left face ] gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(cubeVertexIndices), gl.STATIC_DRAW); cubeVertexIndexBuffer.itemSize = 1; cubeVertexIndexBuffer.numItems = 36;
在這裡,您要注意的是每一個緩衝區的數目,因為這個用來索引到頂點位置以及頂點顏色的。因此,在這裡結合提示,利用繪製三角形的drawScene,成功地使用了三角形的頂點 0,1,2,然後再使用0,2,3點。因為這兩個三角形是相同的顏色,而且他們是相鄰的,所以得到的正方形的頂點就是0,1,2,3。那麼重複相關的步驟,您就可以繪製出一個正方體了。