- 最后登录
- 2018-12-19
- 注册时间
- 2012-8-20
- 阅读权限
- 90
- 积分
- 54706
- 纳金币
- 32328
- 精华
- 41
|
欢迎大家来到WebGL教程的第四课。我将会向大家展示一些真正的3D的物体。这节课的内容是基于NeHe的OpenGL教程的第五节改写的。
以下这个视频就是本节课最终完成的效果。
[media=swf?VideoIDS=XMjk5NDk0MDk2&embedid=NTkuNTYuMjI2Ljc4Ajc0ODczNTI0AgI%3D&wd=,640,480]http://static.youku.com/v1.0.0261/v/swf/loader.swf?VideoIDS=XMjk5NDk0MDk2&embedid=NTkuNTYuMjI2Ljc4Ajc0ODczNTI0AgI%3D&wd=[/media]
下面我们来看看它是怎么工作的……
惯例声明:本系列的教程是针对那些已经具备相应编程知识但没有实际3D图形经验的人的;目标是让学习者创建并运行代码,并且明白代码其中的含义,从而可以快速地创建自己的3D Web页面。如果你还没有阅读第一课,请先阅读第一课的内容吧。因为本课中我只会讲解那些与第一课中不同的新知识。
另外,我编写这套教程是因为我在独立学习WebGL,所以教程中可能(非常可能)会有错误,所以还请风险自担。尽管如此,我还是会不断的修正bug和改正其中的错误的,所以如果你发现了教程中的错误,请告诉我。
有两种方法可以获得上面实例的代码:在实例的独立页面中选择“查看源代码”;你也可以点击这里,下载我们为您打包好的压缩包。
本课和上一课的不同集中在animate, initBuffers和drawScene这三个函数中. 如果你正在看代码, 请跳到animate函数的函数体部分, 你会看到第一处细小的差别: 用来记录两个物体当前旋转状态的变量名改变了, 上一课中的rTri, rSquare变成了rPyramid与rCube, 同时, Cube的旋转方向也与上一课相反(这样显得更漂亮一些). 所以, 本课中的旋转代码如下:
338 rPyramid += (90 * elapsed) / 1000.0;
339 rCube -= (75 * elapsed) / 1000.0;
好,介绍完这个函数了,接着我们来看一下drawScene函数,在定义函数之前,我么将定义两个新的变量。
284 var rPyramid = 0;
285 var rCube = 0;
接着是函数头,绘制工作的初始化及确认椎体绘制位置,之后我们让它沿着Y轴转动,就像我们在前一节课上对那个三角形所做的操作一样,角度大小由rPyramid指定。
296 mvMatrix.rotY(OAK.SPACE_LOCAL, rPyramid, ***e);
然后开始绘制。新代码中,我们画这个彩色的锥体的代码与上一节教程里我们画那个彩色的三角形所用的代码的唯一不同就是它拥有更多的顶点和颜色,而这些工作, 都是由initBuffers来完成的.(这也是我们马上要讲的)。这就意味着,除了我们所使用的buffer名字不同外,代码是完全相同的。
gl.bindBuffer(gl.ARRAY_BUFFER, pyramidVertexPositionBuffer);
298 gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute,
299 pyramidVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
300
301 gl.bindBuffer(gl.ARRAY_BUFFER, pyramidVertexColorBuffer);
302 gl.vertexAttribPointer(shaderProgram.vertexColorAttribute,
303pyramidVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0);
304
305 setMatrixUniforms();
gl.drawArrays(gl.TRIANGLES, 0, pyramidVertexPositionBuffer.numItems);
看吧,很简单,接着我们再来看一下立方体的代码。第一步,就是旋转它。这一次,我们不仅仅需要让他沿X轴旋转,还需要让他沿着一个从右至上的轴,向你的方向(观看者的方向)旋转。
313 mvMatrix.rot(OAK.SPACE_LOCAL, rCube, 1.0, 1.0, 1.0, ***e);
接着,我们开始画这个立方体,这可能会有点复杂。有三种方法可以画出一个立方体。
使用三角形带(Triangle Strip), 如果整个立方体是单色的, 那Triangle Strip方式可以很好的绘制我们想要的东西——首先,我们使用最开始的三个顶点来绘制一个三角面,之后,增加一个顶点(译者注:原文中为增加两个点,但似乎strip方式绘制三角形每次都只需增加一个点,也许原文作者想要表达的是webgl目前并不支持的quad strip),并和刚才绘制的三角形中最后两个顶点组合为一个新的三角面进行绘制,以此类推,直到完成整个绘制过程, 这非常简单高效,但可惜在这里我们不能使用这种技术,因为我们想要立方体在不同的面可以有不同的颜色,立方体中,每一个顶点都被三个不同的面所共享,因此我们需要每个顶点被独立使用三次。这样做十分的麻烦,没必要花时间解释这个……
我们可以采用一些投机取巧的方法来绘制这个我们需要的立方体,让我们先来绘制六个独立正方形,每一个正方形都是立方体的一个面,我们分别设置这些正方形的顶点和颜色。这个课程的第一个版本(2009年10月30日之前的版本)就是这样做的。很好用,但是却并不是实用的好方法,因为每次独立的绘制调用都会带来额外的性能开销,我们应当将调用drawArrays的次数最小化来提高绘制的性能.
最后一种选择就是将立方体指定为六个正方形,每个正方形有两个三角形构成,但是我们将让WebGL一次性完成这些图形的绘制。这个方法有点类似我们用三角形带制作立方体。但由于我们每次都是定义一个完整的三角形,而不是通过添加点来定义三角形,我们很容易来定义每一个面的颜色。这个方法的另一个好处就是它的代码是最简洁的,也方便我来介绍一个新函数 – drawElements, 我们就用这个方法来实现它。
首先,我们将包含有这个立方体的顶点的数组对象和我们将会在initbuffers中用到的颜色通过适当的属性关联起来,就像我们处理锥体的过程一样。
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer);
316 gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute,
317cubeVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
318
319 gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexColorBuffer);
320 gl.vertexAttribPointer(shaderProgram.vertexColorAttribute,
cubeVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0);
下一个步骤就是绘制那些三角形,这里有一些小问题,我们需要考虑一下哪个面在前,我们一共有4个顶点,每一个点都有一个与之相关的颜色。每一个面需要有两个三角形构成。由于我们使用的是简单三角形,我们需要为每个三角形分别指定顶点,而不是像三角形带一样共享顶点,这样的话,我们就需要为它制定6个顶点。但是我们在数组对象里面只提供了四个。
我们将要做的是,先绘制一个由前三个储存在数组对象中的顶点组成的三角形,接着用第一个、第三个和第四个顶点绘制另一个三角形,这样,我们就有了立方体的正面。绘制余下三角形的方法也和这个类似,这就是我们所需要做的。
在这里,我们将使用的是元素数组对象(Element Array Buffer)和一个叫做 drawElements的函数。和我们一直使用的数组对象(Array Buffer) 一样,元素数组对象将在 initBuffers里面被赋予适当的值,并且,它会通过基于零点索引的方式,为顶点位置和顶点颜色保留一份顶点数据列表。
为了使用它,我们需要指定我们的立方体的元素数组对象成为我们当前使用的数组对象(WebGL 保留不同的当前数组对象和当前元素数组对象,因此我们必须调用gl.bindBuffer来指定哪一个数组对象是我们绑定的),之后我们就像往常一样,将模型视图矩阵和投影矩阵传送到显卡端,最后调用drawElements 来绘制我们需要的三角形。
322 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer);
323 setMatrixUniforms();
324 gl.drawElements(gl.TRIANGLES, cubeVertexIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
这就是drawScene代码。代码的剩余部分在initBuffers 里面,而且十分浅显易懂。我们将会使用新的名字来定义新的buffers,以反映我们将要处理的新的物体。我们在立方体的顶点索引数组对象里面将加入新的项。
142 var pyramidVertexPositionBuffer;
143 var pyramidVertexColorBuffer;
144 var cubeVertexPositionBuffer;
145 var cubeVertexColorBuffer;
146 var cubeVertexIndexBuffer;
我们在锥体的顶点位置数组内为每个面填入相应的值,另外对numItems参数也作出相应的修改。
149 pyramidVertexPositionBuffer = gl.createBuffer();
150 gl.bindBuffer(gl.ARRAY_BUFFER, pyramidVertexPositionBuffer);
151 var vertices = [
152 // Front face
153 0.0, 1.0, 0.0,
154 -1.0, -1.0, 1.0,
155 1.0, -1.0, 1.0,
156
157 // Right face
158 0.0, 1.0, 0.0,
159 1.0, -1.0, 1.0,
160 1.0, -1.0, -1.0,
161
162 // Back face
163 0.0, 1.0, 0.0,
164 1.0, -1.0, -1.0,
165 -1.0, -1.0, -1.0,
166
167 // Left face
168 0.0, 1.0, 0.0,
169 -1.0, -1.0, -1.0,
170 -1.0, -1.0, 1.0
171 ];
172 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
173 pyramidVertexPositionBuffer.itemSize = 3;
174 pyramidVertexPositionBuffer.numItems = 12;
对于锥体的顶点颜色数组也一样:
176 pyramidVertexColorBuffer = gl.createBuffer();
177 gl.bindBuffer(gl.ARRAY_BUFFER, pyramidVertexColorBuffer);
178 var colors = [
179 // Front face
180 1.0, 0.0, 0.0, 1.0,
181 0.0, 1.0, 0.0, 1.0,
182 0.0, 0.0, 1.0, 1.0,
183
184 // Right face
185 1.0, 0.0, 0.0, 1.0,
186 0.0, 0.0, 1.0, 1.0,
187 0.0, 1.0, 0.0, 1.0,
188
189 // Back face
190 1.0, 0.0, 0.0, 1.0,
191 0.0, 1.0, 0.0, 1.0,
192 0.0, 0.0, 1.0, 1.0,
193
194 // Left face
195 1.0, 0.0, 0.0, 1.0,
196 0.0, 0.0, 1.0, 1.0,
197 0.0, 1.0, 0.0, 1.0
198 ];
199 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
200 pyramidVertexColorBuffer.itemSize = 4;
201 pyramidVertexColorBuffer.numItems = 12;
对于立方体的顶点位置数组:
204 cubeVertexPositionBuffer = gl.createBuffer();
205 gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer);
206 vertices = [
207 // Front face
208 -1.0, -1.0, 1.0,
209 1.0, -1.0, 1.0,
210 1.0, 1.0, 1.0,
211 -1.0, 1.0, 1.0,
212
213 // Back face
214 -1.0, -1.0, -1.0,
215 -1.0, 1.0, -1.0,
216 1.0, 1.0, -1.0,
217 1.0, -1.0, -1.0,
218
219 // Top face
220 -1.0, 1.0, -1.0,
221 -1.0, 1.0, 1.0,
222 1.0, 1.0, 1.0,
223 1.0, 1.0, -1.0,
224
225 // Bottom face
226 -1.0, -1.0, -1.0,
227 1.0, -1.0, -1.0,
228 1.0, -1.0, 1.0,
229 -1.0, -1.0, 1.0,
230
231 // Right face
232 1.0, -1.0, -1.0,
233 1.0, 1.0, -1.0,
234 1.0, 1.0, 1.0,
235 1.0, -1.0, 1.0,
236
237 // Left face
238 -1.0, -1.0, -1.0,
239 -1.0, -1.0, 1.0,
240 -1.0, 1.0, 1.0,
241 -1.0, 1.0, -1.0
242 ];
243 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
244 cubeVertexPositionBuffer.itemSize = 3;
245 cubeVertexPositionBuffer.numItems = 24;
颜色数组会稍微复杂一些,因为我们使用一个循环来生成一个顶点颜色的列表,这样我们就不必定义4次颜色,每个顶点一次:
247 cubeVertexColorBuffer = gl.createBuffer();
248 gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexColorBuffer);
249 colors = [
250 [1.0, 0.0, 0.0, 1.0], // Front face
251 [1.0, 1.0, 0.0, 1.0], // Back face
252 [0.0, 1.0, 0.0, 1.0], // Top face
253 [1.0, 0.5, 0.5, 1.0], // Bottom face
254 [1.0, 0.0, 1.0, 1.0], // Right face
255 [0.0, 0.0, 1.0, 1.0] // Left face
256 ];
257 var unpackedColors = [];
258 for (var i in colors) {
259 var color = colors;
260 for (var j=0; j < 4; j++) {
261 unpackedColors = unpackedColors.concat(color);
262 }
263 }
264 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(unpackedColors), gl.STATIC_DRAW);
265 cubeVertexColorBuffer.itemSize = 4;
266 cubeVertexColorBuffer.numItems = 24;
最后,我们要定义元素数组对象。请注意gl.bindBuffer和gl.bufferData这两个函数的第一个参数与之前的不同,之前都是gl.ARRAY_BUFFER,用于指定这是一个用于存放顶点属性数据的缓存对象;现在则是gl.ELEMENT_ARRAY_BUFFER,用于指定这是一个用于存放索引数据的缓存对象。
268 cubeVertexIndexBuffer = gl.createBuffer();
269 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer);
270 var cubeVertexIndices = [
271 0, 1, 2, 0, 2, 3, // Front face
272 4, 5, 6, 4, 6, 7, // Back face
273 8, 9, 10, 8, 10, 11, // Top face
274 12, 13, 14, 12, 14, 15, // Bottom face
275 16, 17, 18, 16, 18, 19, // Right face
276 20, 21, 22, 20, 22, 23 // Left face
277 ];
278 gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(cubeVertexIndices), gl.STATIC_DRAW);
279 cubeVertexIndexBuffer.itemSize = 1;
280 cubeVertexIndexBuffer.numItems = 36;
记住,每个在buffer里面的数字都是对于一个顶点位置和颜色buffer的索引。所以,在第一行内,结合在drawScene里面绘制三角形的说明,这个代码意味着我们利用顶点0,1,2绘制一个三角形,接着用0,2,3绘制了另一个三角形,这两个三角形的颜色都是一样的。因为两个三角形颜色一样,彼此邻近,这样就绘制出了一个以0,1,2,3为顶点的正方形。在绘制下一个正方形的时候再次循环这个过程。
这样,你就知道了如何使用3D物体制作WebGL场景。并且你们还知道了该如何通过element array buffers 和 drawElements 来循环使用你在array buffers 指定的顶点。如果有什么问题,请留下评论。
在下节课中,我们将会引入纹理。
|
|