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。那么重复相关的步骤,您就可以绘制出一个正方体了。