42

I'm trying to get a clear idea of when I should be using indexed arrays of OpenGL vertices, drawn with gl[Multi]DrawElements and the like, versus when I should simply use contiguous arrays of vertices, drawn with gl[Multi]DrawArrays.

(Update: The consensus in the replies I got is that one should always be using indexed vertices.)

I have gone back and forth on this issue several times, so I'm going to outline my current understanding, in the hopes someone can either tell me I'm now finally more or less correct, or else point out where my remaining misunderstandings are. Specifically, I have three conclusions, in bold. Please correct them if they are wrong.

One simple case is if my geometry consists of meshes to form curved surfaces. In this case, the vertices in the middle of the mesh will have identical attributes (position, normal, color, texture coord, etc) for every triangle which uses the vertex.

This leads me to conclude that:

1. For geometry with few seams, indexed arrays are a big win.

Follow rule 1 always, except:

For geometry that is very 'blocky', in which every edge represents a seam, the benefit of indexed arrays is less obvious. To take a simple cube as an example, although each vertex is used in three different faces, we can't share vertices between them, because for a single vertex, the surface normals (and possible other things, like color and texture co-ord) will differ on each face. Hence we need to explicitly introduce redundant vertex positions into our array, so that the same position can be used several times with different normals, etc. This means that indexed arrays are of less use.

e.g. When rendering a single face of a cube:

 0     1
  o---o
  |\  |
  | \ |
  |  \|
  o---o
 3     2

(this can be considered in isolation, because the seams between this face and all adjacent faces mean than none of these vertices can be shared between faces)

if rendering using GL_TRIANGLE_FAN (or _STRIP), then each face of the cube can be rendered thus:

verts  = [v0, v1, v2, v3]
colors = [c0, c0, c0, c0]
normal = [n0, n0, n0, n0]

Adding indices does not allow us to simplify this.

From this I conclude that:

2. When rendering geometry which is all seams or mostly seams, when using GL_TRIANGLE_STRIP or _FAN, then I should never use indexed arrays, and should instead always use gl[Multi]DrawArrays.

(Update: Replies indicate that this conclusion is wrong. Even though indices don't allow us to reduce the size of the arrays here, they should still be used because of other performance benefits, as discussed in the comments)

The only exception to rule 2 is:

When using GL_TRIANGLES (instead of strips or fans), then half of the vertices can still be re-used twice, with identical normals and colors, etc, because each cube face is rendered as two separate triangles. Again, for the same single cube face:

 0     1
  o---o
  |\  |
  | \ |
  |  \|
  o---o
 3     2

Without indices, using GL_TRIANGLES, the arrays would be something like:

verts =   [v0, v1, v2,  v2, v3, v0]
normals = [n0, n0, n0,  n0, n0, n0]
colors =  [c0, c0, c0,  c0, c0, c0]

Since a vertex and a normal are often 3 floats each, and a color is often 3 bytes, that gives, for each cube face, about:

verts   = 6 * 3 floats = 18 floats
normals = 6 * 3 floats = 18 floats
colors  = 6 * 3 bytes  = 18 bytes

= 36 floats and 18 bytes per cube face.

(I understand the number of bytes might change if different types are used, the exact figures are just for illustration.)

With indices, we can simplify this a little, giving:

verts   = [v0, v1, v2, v3]     (4 * 3 = 12 floats)
normals = [n0, n0, n0, n0]     (4 * 3 = 12 floats)
colors  = [c0, c0, c0, c0]     (4 * 3 = 12 bytes)
indices = [0, 1, 2,  2, 3, 0]  (6 shorts)

= 24 floats + 12 bytes, and maybe 6 shorts, per cube face.

See how in the latter case, vertices 0 and 2 are used twice, but only represented once in each of the verts, normals and colors arrays. This sounds like a small win for using indices, even in the extreme case of every single geometry edge being a seam.

This leads me to conclude that:

3. When using GL_TRIANGLES, one should always use indexed arrays, even for geometry which is all seams.

Please correct my conclusions in bold if they are wrong.

4

1 回答 1

35

由此我得出结论,当渲染所有接缝或大部分接缝的几何图形时,当使用 GL_TRIANGLE_STRIP 或 _FAN 时,我不应该使用索引数组,而应该始终使用 gl[Multi]DrawArrays。

不,原因很简单。

您的结论是基于您分析了由两个三角形组成的单个四边形的事实。这两个使用三角形扇形/条形绘制的三角形无法使用索引数组进行简化。

但试着考虑一个大的地形几何。每个地形块都被绘制为一个四边形,使用三角形扇形/条形图元。例如:

图中的每个三角形带具有与相邻三角形带共同的所有顶点,并且使用索引允许压缩几何定义,而不是为每个三角形带重复顶点。


基本上,只要您可以与另一个图元共享单个图元的大部分顶点,使用索引绘制图元(三角形、扇形和条形)就很有用。

共享信息可以节省信息传输带宽,但这并不是唯一的优势。实际上索引数组允许:

  • 避免多次指定属于同一“概念”顶点的信息的同步
  • 允许对单个顶点执行相同的着色器操作,而不是执行多次,每个顶点重复一次。
  • 此外,结合使用三角形带/扇形和索引允许应用程序压缩索引缓冲区,因为带/扇形规范需要更少的索引(三角形每个面总是需要 3 个索引)。

正如您所指定的,当一个顶点不能与另一个重合顶点共享与其关联的所有信息(颜色、纹理坐标等)时,就不能使用索引数组。


只是为了完整起见,几何规范所需信息的大小并不是决定最佳渲染操作的唯一因素。

事实上,原始渲染的另一个基本因素是数据的缓存本地化。错误指定的几何数据(非交错缓冲区对象、长三角形条带......)会导致大量缓存未命中,从而降低显卡性能。

为了优化渲染操作,顶点规范应以重用先前指定顶点的方式重新排序,以最有可能。以这种方式,图形卡缓存行可以重用先前指定的顶点,而无需从内存中获取它们。

于 2010-06-02T08:02:13.657 回答