webgl绘制一个三角形

转载来自: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
];
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章