WebGL教程5:真正的3d物体
来源:http://www.hiwebgl.com
HiWebGL译者声明:因为译者个人方便的原因,我们将原教程中的第三方图形库由glMatrix改为Oak3D实现,这不影响到Demo的最终效果和实现,也不影响到WebGL的讲解和学习。原教程正文中相应的代码和讲解也为做了相应修改!本教程由HiWebGL翻译整理,转载请注明出处!
关于Oak3D:Oak3D是一套简单易用、性能优越的WebGL Javascript Library。您可以在他们的主页找到更多信息。Oak3D主页:http://www.oak3d.com
欢迎大家来到WebGL教程的第四课。我将会向大家展示一些真正的3D的物体。这节课的内容是基于NeHe的OpenGL教程的第五节改写的。
以下这个视频就是本节课最终完成的效果。
下面我们来看看它是怎么工作的……
惯例声明:本系列的教程是针对那些已经具备相应编程知识但没有实际3D图形经验的人的;目标是让学习者创建并运行代码,并且明白代码其中的含义,从而可以快速地创建自己的3D Web页面。如果你还没有阅读第一课,请先阅读第一课的内容吧。因为本课中我只会讲解那些与第一课中不同的新知识。
另外,我编写这套教程是因为我在独立学习WebGL,所以教程中可能(非常可能)会有错误,所以还请风险自担。尽管如此,我还是会不断的修正bug和改正其中的错误的,所以如果你发现了教程中的错误,请告诉我。
有两种方法可以获得上面实例的代码:在实例的独立页面中选择“查看源代码”;你也可以点击这里,下载我们为您打包好的压缩包。
本课和上一课的不同集中在Animate, initBuffers和drawScene这三个函数中. 如果你正在看代码, 请跳到Animate函数的函数体部分, 你会看到第一处细小的差别: 用来记录两个物体当前旋转状态的变量名改变了, 上一课中的rTri, rSquare变成了rPyramid与rCube, 同时, Cube的旋转方向也与上一课相反(这样显得更漂亮一些). 所以, 本课中的旋转代码如下:
338 339 |
rPyramid += (90 * elapsed) / 1000.0; rCube -= (75 * elapsed) / 1000.0; |
284 285 |
var rPyramid = 0; var rCube = 0; |
296 |
mvMatrix.rotY(OAK.SPACE_LOCAL, rPyramid, true); |
298 299 300 301 302 303 304 305 |
gl.bindBuffer(gl.ARRAY_BUFFER, pyramidVertexPositionBuffer); gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, |
看吧,很简单,接着我们再来看一下立方体的代码。第一步,就是旋转它。这一次,我们不仅仅需要让他沿X轴旋转,还需要让他沿着一个从右至上的轴,向你的方向(观看者的方向)旋转。
313 |
mvMatrix.rot(OAK.SPACE_LOCAL, rCube, 1.0, 1.0, 1.0, true); |
使用三角形带(Triangle Strip), 如果整个立方体是单色的, 那Triangle Strip方式可以很好的绘制我们想要的东西——首先,我们使用最开始的三个顶点来绘制一个三角面,之后,增加一个顶点(译者注:原文中为增加两个点,但似乎strip方式绘制三角形每次都只需增加一个点,也许原文作者想要表达的是webgl目前并不支持的quad strip),并和刚才绘制的三角形中最后两个顶点组合为一个新的三角面进行绘制,以此类推,直到完成整个绘制过程, 这非常简单高效,但可惜在这里我们不能使用这种技术,因为我们想要立方体在不同的面可以有不同的颜色,立方体中,每一个顶点都被三个不同的面所共享,因此我们需要每个顶点被独立使用三次。这样做十分的麻烦,没必要花时间解释这个……
我们可以采用一些投机取巧的方法来绘制这个我们需要的立方体,让我们先来绘制六个独立正方形,每一个正方形都是立方体的一个面,我们分别设置这些正方形的顶点和颜色。这个课程的第一个版本(2009年10月30日之前的版本)就是这样做的。很好用,但是却并不是实用的好方法,因为每次独立的绘制调用都会带来额外的性能开销,我们应当将调用drawArrays的次数最小化来提高绘制的性能.
最后一种选择就是将立方体指定为六个正方形,每个正方形有两个三角形构成,但是我们将让WebGL一次性完成这些图形的绘制。这个方法有点类似我们用三角形带制作立方体。但由于我们每次都是定义一个完整的三角形,而不是通过添加点来定义三角形,我们很容易来定义每一个面的颜色。这个方法的另一个好处就是它的代码是最简洁的,也方便我来介绍一个新函数 – drawElements, 我们就用这个方法来实现它。
首先,我们将包含有这个立方体的顶点的数组对象和我们将会在initbuffers中用到的颜色通过适当的属性关联起来,就像我们处理锥体的过程一样。
316 317 318 319 320 |
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer); gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, |
下一个步骤就是绘制那些三角形,这里有一些小问题,我们需要考虑一下哪个面在前,我们一共有4个顶点,每一个点都有一个与之相关的颜色。每一个面需要有两个三角形构成。由于我们使用的是简单三角形,我们需要为每个三角形分别指定顶点,而不是像三角形带一样共享顶点,这样的话,我们就需要为它制定6个顶点。但是我们在数组对象里面只提供了四个。
我们将要做的是,先绘制一个由前三个储存在数组对象中的顶点组成的三角形,接着用第一个、第三个和第四个顶点绘制另一个三角形,这样,我们就有了立方体的正面。绘制余下三角形的方法也和这个类似,这就是我们所需要做的。
在这里,我们将使用的是元素数组对象(Element Array Buffer)和一个叫做 drawElements的函数。和我们一直使用的数组对象(Array Buffer) 一样,元素数组对象将在 initBuffers里面被赋予适当的值,并且,它会通过基于零点索引的方式,为顶点位置和顶点颜色保留一份顶点数据列表。
为了使用它,我们需要指定我们的立方体的元素数组对象成为我们当前使用的数组对象(WebGL 保留不同的当前数组对象和当前元素数组对象,因此我们必须调用gl.bindBuffer来指定哪一个数组对象是我们绑定的),之后我们就像往常一样,将模型视图矩阵和投影矩阵传送到显卡端,最后调用drawElements 来绘制我们需要的三角形。
322 323 324 |
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer); setMatrixUniforms(); gl.drawElements(gl.TRIANGLES, cubeVertexIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0); |
142 143 144 145 146 |
var pyramidVertexPositionBuffer; var pyramidVertexColorBuffer; var cubeVertexPositionBuffer; var cubeVertexColorBuffer; var cubeVertexIndexBuffer; |
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
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; |
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 |
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; |
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 |
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; |
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 |
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; |
268 269 270 271 272 273 274 275 276 277 278 279 280 |
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; |
这样,你就知道了如何使用3D物体制作WebGL场景。并且你们还知道了该如何通过element array buffers 和 drawElements 来循环使用你在array buffers 指定的顶点。如果有什么问题,请留下评论。
在下节课中,我们将会引入纹理。