返回首页 现代 OpenGL 教程(连载)

modern opengl tutorial

第三十七课 延迟渲染(三)(未完待续)

第三十八课 使用Assimp导入骨骼动画(未完待续)

第三十九课 轮廓检测(未完待续)

第四十课 阴影锥(未完待续)

第四十一课 运动模糊(未完待续)

第四十二课 基于PCF的阴影(未完待续)

第四十三课 基于点光源的阴影(未完待续)

第四十四课 GLFW(未完待续)

第四十五课 屏幕空间的环境遮挡(未完待续)

第四十六课 SSAO与深度重构(未完待续)

end

第十课 索引绘制

背景

OpenGL 提供了多个绘制函数。我们目前使用的 glDrawArrays()函数属于“顺序绘制”的类别。这意味着顶点缓冲区以指定的偏移量进行扫描,它将每 X( 1 个顶点时表示点,两个顶点表示线,等等)个顶点作为一个图元。这种方法使用起来很方便,但是它的缺点是如果有多个图元共享通一个顶点,那么这个被共享的顶点就会在顶点缓冲区中存在多次。即,在顺序绘制中不存在共享的概念。要实现顶点的共享,我们需要使用与“索引绘制”相关的函数,实际上在 OpenGL 中除了顶点缓冲区之外还有索引缓冲区,索引缓冲区中存放的是指向顶点缓冲区内顶点的索引。扫描索引缓冲区和扫描顶点缓冲区相似:都是以每 X 个索引作为一个图元,为了实现共享,你仅需要重复使用共享顶点的索引即可。顶点共享对于内存效率来说是非常重要的,因为场景中的大多数对象都是由相互紧挨着的三角形封闭网格来表现,所以大多数顶点都是被多个三角形共享的。

这是一个顺序绘制的例子:

如果我们用上面的数据绘制三角形,GPU将依据后面的数据序列进行绘制:V0/1/2,V3/4/5,V6/7/8 等。

这是一个索引绘制的例子:

在此情况下,GPU 将依据后面的数据序列进行绘制:V4/0/1,V5/2/1,V6/1/7等。

使用 OpenGL 中的索引绘制要求生成并填充一个索引缓冲区,而且它必须在调用绘制命令之前使用不同于绑定顶点缓存区的API将其绑定。

代码

GLuint IBO;

首先我们为索引缓冲区增加了一个对象句柄。

Vertices[0] = Vector3f(-1.0f, -1.0f,0.0f);
Vertices[1] = Vector3f(0.0f, -1.0f, 1.0f);
Vertices[2] = Vector3f(1.0f, -1.0f, 0.0f);
Vertices[3] = Vector3f(0.0f, 1.0f, 0.0f);

为了便于展示如何实现顶点共享,我们需要一个稍微复杂的网格模型。为此很多教程使用著名的旋转立方体。这个模型需要 8 个顶点和 12 个三角形。鄙人稍懒就用旋转的锥体来代替。它仅需要 4 个顶点和 4 个三角形,并且更易于手动生成。

当我们从顶部(沿着 Y 轴负方向)看这些顶点时,我们会看到下面的情况:

  unsigned intIndices[] = { 0, 3, 1,
                          1, 3, 2,
                          2, 3, 0,
                         0, 1, 2 };

在这个索引缓冲区中我们使用索引数组来填充。索引值与顶点缓冲区中顶点的位置相对应。由索引数组和上面的示意图你能看出,最后一个三角形是角锥底座而其它三三角形组成了角锥的三个面。角锥不是对称的,但对我们来说很容易生成。

glGenBuffers(1, &IBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices),
Indices, GL_STATIC_DRAW);

我们创建一个索引缓存并使用索引数组填充索引缓冲区。你可以看到,创建顶点缓冲区和索引缓冲区唯一的区别是,顶点缓存使用 GL_ARRAY_BUFFER 作为缓冲区类型,而索引缓存使用 GL_ELEMENT_ARRAY_BUFFER 作为缓冲区类型。

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);

在绘制之前,除了绑定顶点缓存之外,我们还必须绑定索引缓存。我们再一次使用 GL_ELEMENT_ARRAY_BUFFER 作为缓冲类型。

glDrawElements(GL_TRIANGLES, 12, GL_UNSIGNED_INT, 0);

在绘制的时候我们调用 glDrawElements 函数而不是 glDrawArrays 函数。第一个参数是要绘制的图元的类型(与 glDrawArrays 相同)。第二个参数是图元生成所要用到的索引缓冲区中索引的数量。第三个参数是每个索引的类型,GPU 必须被告知每个索引的大小,否则它就不知道如何解析缓冲区。这里我们也有其他参数可供选择,如 GL_UNSIGNED_BYTE,GL_UNSIGNED_SHORT 和 GL_UNSIGNED_INT。如果索引的范围较小,那么你也许会想用占用内存空间较小的数据类型以节约空间;如果索引范围较大,你也许会想用占用内存空间较大的数据类型以满足数据的索引。最后一个参数告诉 GPU 要进行绘制的第一个索引的位置相对于索引缓冲区起始位置的偏移量的字节数。当相同的索引缓冲区包含多个对象的索引时,这是很有用的。通过指定偏移量和偏移数,你能告诉 GPU 要渲染哪个对象。本例中我们要从索引缓冲区的起始地址进行绘制,所以我们将其指定为 0。请注意,最后一个参数的类型是 GLvoid* 的,因此如果您指定 0 以外的其他值,你需要将它转换为这种类型。

操作结果