×
关于

现代 OpenGL 教程

第一课 创建一个窗口第二课 绘制一个点第三课 第一个三角形第四课 着色器基础第五课 一致变量第六课 平移变换第七课 旋转变换第八课 缩放变换第九课 插值第十课 索引绘制第11课 矩阵连乘第12课 透视投影变换第13课 相机空间第14课 相机控制(一)第15课 相机控制(二)第16课 基本的纹理贴图第17课 环境光第18课 漫反射光第19课 镜面反射光第20课 点光源第21课 聚光灯第22课 使用Assimp库导入模型第23课 Shadow Mapping 1第24课 Shadow Mapping 2第25课 天空盒第26课 法线纹理第27课 广告牌和几何着色器第28课 创建粒子系统第29课 3D拾取第30课 细分着色器基础第31课 基于PN三角形细分着色第32课 顶点数组对象第33课 实例渲染第34课 GLFX OpenGL特效库第35课 延迟渲染(一)第36课 延迟渲染(二)第37课 延迟渲染(三)第38课 Assimp导入骨骼动画第39课 轮廓检测第40课 模板阴影体第41课 运动模糊第42课 基于 PCF 的阴影优化第43课 点光源多通道阴影映射第44课 GLFW第45课 屏幕空间的环境光遮挡第46课 SSAO与深度重构

第十课 索引绘制


背景

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 以外的其他值,你需要将它转换为这种类型。

操作结果


分类导航

关注微信下载离线手册

bootwiki移动版 bootwiki
(群号:472910771)