转载来自:http://www.jiazhengblog.com/blog/2016/02/19/2910/
很多WebGL教程都会从绘制一个三角形开始,如同其它语言教程中的Hello World。我也不能免俗,来一起看一下如何在WebGL中绘制三角形吧。本文将向你介绍如下内容:
- 如何定义三角形顶点
- 如何绘制三角形
- 如何绘制多个三角形
绘制一个三角形
完整的代码可以看这里。
如果你对比之前绘制点的代码,发现绘制三角形仅在两个地方进行了修改:
- 顶点定义部分
- 绘制部分
一个三角形包含三个顶点,因此vertex变量中数据内容增多了:
1
2
3
4
5
|
var vertex = [ -.5, -.2, 0, .5, -.2, 0, 0, .6, 0 ]; |
数组中包含了三个点的座标信息,每个点包含x、y、z三个维度,因此数组的长度为9。为了可读性,顶点数据一般每行写三个元素,这样每一行就代表一个点,也比较容易看出点的数量。
三角形顶点的顺序可以任意来写,但是我的习惯是从左下角的顶点开始写起,并按照逆时针的顺序。无论采用什么方案,只要在一个项目中保持一致即可。从下图我们可以看到顶点的对应关系。
下面一个发生变化的地方是绘制代码:
1
|
gl.drawArrays(gl.TRIANGLES, 0, 3); |
这里绘制模式变为gl.TRIANGLES,表示绘制为三角形,即要绘制一个面,只有面才会有填充颜色。第三个参数由1变为3,表示要绘制三个顶点。
我们会在浏览器里看到下面的效果:
绘制多个三角形
在WebGL中不论多么复杂的几何体最终都是由三角形构成,因此在绘制之前都需要将物体进行三角形化(Triangulation),也就是把复杂的多边形分解为一个个三角形,再交由WebGL绘制。在下面的例子中,我们来绘制一个矩形,其中包含两个三角形,如下图所示:
首先还是准备顶点数组,现在是两个三角形,于是数组的长度变为18(2个三角形 x 3个顶点 x 3个座标):
1
2
3
4
5
6
7
8
|
var vertex = [ -.5, -.3, 0, .5, -.3, 0, .5, .3, 0, -.5, -.3, 0, .5, .3, 0, -.5, .3, 0 ]; |
数组中座标对应关系如下图所示:
最后我们修改drawArray代码:
1
|
gl.drawArray(gl.TRIANGLES, 0, 6); |
可以看到一个矩形绘制出来了。
如果你是个细节控、或者爱问问题,那么你可能会发现定义座标的时候存在数据冗余,因为两个三角形中有两个顶点是共用的。有没有办法来避免有冗余数据呢?
索引缓冲区对象 Index Buffer Object(IBO)
除了VBO,WebGL还有另外一类缓冲区对象,称作索引缓冲区对象(Index Buffer Object,IBO,后文均采用IBO一词)。通过索引的方式可以达到去除冗余顶点数据的目的。我们来看看它是怎么做到的。
首先修改vertex数组,里面只包含必须的4个顶点:
1
2
3
4
5
6
|
var vertex = [ -.5, -.3, 0, .5, -.3, 0, .5, .3, 0, -.5, .3, 0 ]; |
在给VBO填充数据之后我们增加创建IBO的代码:
1
2
3
4
5
6
7
|
var index = [ 0, 1, 2, 0, 2, 3 ]; var indexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(index), gl.STATIC_DRAW); |
index中的数据指明每个三角形的顶点在VBO中的位置,第一行 0, 1, 2 表示第一个三角形由VBO中第0个、第1个和第2个顶点(注意是顶点,每个顶点包含三个座标,所以你要三个元素三个元素的看)构成,第二行的 0, 2, 3 表示第二个三角形由VBO中的第0个、第2个和第3个顶点构成。
接着创建缓冲区对象,这和之前创建VBO代码一样。在绑定缓冲区时,第一个参数为gl.ELEMENT_ARRAY_BUFFER,此时WebGL才知道这是一个索引缓冲区,后面的bufferData的首个参数也是这个值,传输的数据同样需要typed array,这里我们使用Uint16Array,也就是每个索引值采用无符号16位整型数值,当然对于我们这个简单的例子,用Uint8Array也是可以的。
最后我们需要使用WebGL另外一个绘制函数来绘制:
1
|
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0); |
如果使用了IBO,就需要用drawElements方法来替代原来的drawArrays,drawElements原型如下:
1
|
void drawElements(GLenum mode, GLsizei count, GLenum type, GLintptr offset); |
这里多了一个type,表示索引的数据类型。这里我用使用unsigned short类型,它和之前Uint16Array是一致的。如果前面用了Uint8Array,那么这里需要改成gl.UNSIGNED_BYTE。
在采用了索引方式后,vertex内容确实减少了,在复杂的模型里这会节省出不少的内存空间。如果你是一个追求极致的人,你会发现索引里仍然存在冗余,索引0和2出现了两次。有没有办法把索引中的冗余也去掉呢?请你往下看。
不同的绘制模式
不论是drawArrays还是drawElements,第一个参数都是指定绘制模式(mode),目前我们绘制三角形使用的模式都是gl.TRIANGLES,此外还有两种与绘制三角形相关的模式:gl.TRIANGLE_STRIP和gl.TRIANGLE_FAN。
关于这三种模式的区别,我们用一张图来说明:
- gl.TRIANGLES:用来绘制相互独立的三角形。从缓冲区中每次获取3个顶点的数据作为一个三角形。
- gl.TRIANGLE_STRIP:用来绘制有共享边的三角形。从第二个三角形开始,每次读取一个顶点,并利用前面的末尾两个顶点构成一个三角形,以此类推。
- gl.TRIANGLES_FAN:同样用来绘制有共享边的三角形。从第二个三角形开始,每次读取一个顶点,并利用首个顶点和之前最后一个顶点来构成一个三角形,以此类推。
在绘制矩形的例子中,我们分别看如何使用strip和fan这两种模式。
strip模式下,我们修改index为:
1
2
3
|
var index = [ 1, 2, 0, 3 ]; |
fan模式下,修改index为:
1
2
3
|
var index = [ 0, 1, 2, 3 ]; |