Vertex Arrays (State Management and Drawing Geometric Objects) (OpenGL Programming) Part 1

You may have noticed that OpenGL requires many function calls to render geometric primitives. Drawing a 20-sided polygon requires at least 22 function calls: one call to glBegin(), one call for each of the vertices, and a final call to glEnd(). In the two previous code examples, additional information (polygon boundary edge flags or surface normals) added function calls for each vertex. This can quickly double or triple the number of function calls required for one geometric object. For some systems, function calls have a great deal of overhead and can hinder performance.

An additional problem is the redundant processing of vertices that are shared between adjacent polygons. For example, the cube in Figure 2-14 has six faces and eight shared vertices. Unfortunately, if the standard method of describing this object is used, each vertex has to be specified three times: once for every face that uses it. Therefore, 24 vertices are processed, even though eight would be enough.

Six Sides, Eight Shared Vertices

Figure 2-14 Six Sides, Eight Shared Vertices


OpenGL has vertex array routines that allow you to specify a lot of vertex-related data with just a few arrays and to access that data with equally few function calls. Using vertex array routines, all 20 vertices in a 20-sided polygon can be put into one array and called with one function. If each vertex also has a surface normal, all 20 surface normals can be put into another array and also called with one function.

Arranging data in vertex arrays may increase the performance of your application. Using vertex arrays reduces the number of function calls, which improves performance. Also, using vertex arrays may allow nonredundant processing of shared vertices. (Vertex sharing is not supported on all implementations of OpenGL.)

Note: Vertex arrays became standard in Version 1.1 of OpenGL. Version 1.4 added support for storing fog coordinates and secondary colors in vertex arrays.

There are three steps to using vertex arrays to render geometry:

1. Activate (enable) up to eight arrays, each storing a different type of data: vertex coordinates, surface normals, RGBA colors, secondary colors, color indices, fog coordinates, texture coordinates, or polygon edge flags.

2.  Put data into the array or arrays. The arrays are accessed by the addresses of (that is, pointers to) their memory locations. In the client-server model, this data is stored in the client’s address space.

3.   Draw geometry with the data. OpenGL obtains the data from all activated arrays by dereferencing the pointers. In the client-server model, the data is transferred to the server’s address space. There are three ways to do this:

•    Accessing individual array elements (randomly hopping around)

•    Creating a list of individual array elements (methodically hopping around)

•    Processing sequential array elements

The dereferencing method you choose may depend on the type of problem you encounter. Version 1.4 added support for multiple array access from a single function call.

Interleaved vertex array data is another common method of organization. Instead of several different arrays, each maintaining a different type of data (color, surface normal, coordinate, and so on), you may have the different types of data mixed into a single array. (See “Interleaved Arrays” on page 78.)

Steo 1: Enabling Arrays

The first step is to call glEnableClientState() with an enumerated parameter, which activates the chosen array. In theory, you may need to call this up to eight times to activate the eight available arrays. In practice, you’ll probably activate up to six arrays. For example, it is unlikely that you would activate both GL_COLOR_ARRAY and GL_INDEX_ARRAY, as your program’s display mode supports either RGBA mode or color-index mode, but probably not both simultaneously.

void glEnableClientState(GLenum array)

Specifies the array to enable. The symbolic constants GL_VERTEX_ARRAY, GL_COLOR_ARRAY, GL_SECONDARY_COLOR_ARRAY,

GL_INDEX_ARRAY, GL_NORMAL_ARRAY,)

GL_FOG_COORDINATE_ARRAY, GL_TEXTURE_COORD_ARRAY, and GL_EDGE_FLAG_ARRAY are acceptable parameters.

If you use lighting, you may want to define a surface normal for every vertex. (See "Normal Vectors" on page 63.) To use vertex arrays for that case, you activate both the surface normal and vertex coordinate arrays:

tmp5324-88_thumb

Suppose that you want to turn off lighting at some point and just draw the geometry using a single color. You want to call glDisable() to turn off lighting .Now that lighting has been deactivated, you also want to stop changing the values of the surface normal state, which is wasted effort. To do this, you call

tmp5324-89_thumb

void glDisableClientState(GLenum array)·,

Specifies the array to disable. It accepts the same symbolic constants as glEnableClientStateQ.

You might be asking yourself why the architects of OpenGL created these new (and long) command names, like gl*ClientState(), for example.

Why can’t you just call glEnable() and glDisable()? One reason is that glEnable() and glDisable() can be stored in a display list, but the specification of vertex arrays cannot, because the data remains on the client’s side.

If multitexturing is enabled, enabling and disabling client arrays affects only the active texturing unit. See "Multitexturing" on page 438 for more details.

Step 2: Specifying Data for the Arrays

There is a straightforward way by which a single command specifies a single array in the client space. There are eight different routines for specifying arrays—one routine for each kind of array. There is also a command that can specify several client-space arrays at once, all originating from a single interleaved array.

vOid glVertexPointer(GLint size, GLenum type, GLsizei stride,cOnst GLvOid *pointer);
Specifies where spatial coordinate data can be accessed. pointer is the memory address Of the first cOOrdinate Of the first vertex in the array. type specifies the data type (GL_SHORT,     GL_FLOAT, or GL_DOUBLE)
Of each coordinate in the array. size is the number of coordinates per vertex, which must be 2, 3, Or 4. stride is the byte offset between cOnsecutive vertices. If stride is 0, the vertices are understOOd tO be tightly
packed in the array.

To access the other seven arrays, there are seven similar routines:

vOid glColorPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer);
void glSecondaryColorPointer(GLint size, GLenum type, GLsizei stride,
const GLvoid *pointer);
void gllndexPointer(GLenum type, GLsizei stride, const GLvOid *pointer);
void glNormalPointer(GLenum type, GLsizei stride,const GLvoid *pointer);
void glFogCoordPointer(GLenum type, GLsizei stride,const GLvoid *pointer);
vOid glTexCoordPointer(GLint size, GLenum type, GLsizei stride,
const GLvOid *pointer);
vOid glEdgeFlagPointer(GLsizei stride, cOnst GLvoid *pointer);

The main difference among the routines is whether size and type are unique or must be specified. For example, a surface normal always has three components, so it is redundant to specify its size. An edge flag is always a single Boolean, so neither size nor type needs to be mentioned. Table 2-4 displays legal values for size and data types.

For OpenGL implementations that support multitexturing, specifying a texture coordinate array with glTexCoordPointer() only affects the currently active texture unit. See "Multitexturing" on page 438 for more information.

Example 2-9 uses vertex arrays for both RGBA colors and vertex coordinates. RGB floating-point values and their corresponding (x, y) integer coordinates are loaded into the GL_COLOR_ARRAY and GL_VERTEX_ARRAY.

Command

Sizes

Values for type Argument

glVertexPointer

2, 3,4

GL_SHORT, GLJNT, GlJLoAT, GL_DOUBLE

glColorPointer

3,4

GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GLJJNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT, GL_FLOAT, GL_DOUBLE

glSecondaryColorPointer

3

GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GLJNT, GL_UNSIGNEDJNT, GL_FLOAT, GL_DOUBLE

gllndexPointer

1

GL_UNSIGNED_BY’I'E, GL_SHORT, GLJNT, GL_FLOAT, GL_DOUBLE

glNormalPointer

3

GL_BYTE, GL_SHORT, GLJNT, GL_FLOaT, GL_DOUBLE

glFogCoordPointer

1

GL_FLOAT, GL_DOUBLE

glTexCoordPointer

1, 2, 3, 4

GL_SHORT, GLJNT, GL_FLOAT, GL_DOUBLE

glEdgeFlagPointer

1

no type argument (type of data must be GLboolean)

Table 2-4    Vertex Array Sizes (Values per Vertex) and Data Types

Example 2-9    Enabling and Loading Vertex Arrays: varray.c

Enabling and Loading Vertex Arrays: varray.c

Stride

The stride parameter for the gl*Pointer() routines tells OpenGL how to access the data you provide in your pointer arrays. Its value should be the number of bytes between the starts of two successive pointer elements, or zero, which is a special case. For example, suppose you stored both your vertex’s RGB and (x, y, z) coordinates in a single array, such as the following:

tmp5324-93_thumb

To reference only the color values in the intertwined array, the following call starts from the beginning of the array (which could also be passed as &intertwined[0]) and jumps ahead 6 * sizeof(GLfloat) bytes, which is the size of both the color and vertex coordinate values. This jump is enough to get to the beginning of the data for the next vertex:

tmp5324-94_thumb

For the vertex coordinate pointer, you need to start from further in the array, at the fourth element of intertwined (remember that C programmers start counting at zero):

tmp5324-95_thumb

If your data is stored similar to the intertwined array above, you may find "Interleaved Arrays" on page 78 more convenient for storing your data.

With a stride of zero, each type of vertex array (RGB color, color index, vertex coordinate, and so on) must be tightly packed. The data in the array must be homogeneous; that is, the data must be all RGB color values, all vertex coordinates, or all some other data similar in some fashion.

Step 3: Dereferencing and Rendering

Until the contents of the vertex arrays are dereferenced, the arrays remain on the client side, and their contents are easily changed. In Step 3, contents of the arrays are obtained, sent to the server, and then sent down the graphics processing pipeline for rendering.

You can obtain data from a single array element (indexed location), from an ordered list of array elements (which may be limited to a subset of the entire vertex array data), or from a sequence of array elements.

Dereferencing a Single Array Element

void glArrayElement(GLint ith)

Obtains the data of one (the ith) vertex for all currently enabled arrays. For the vertex coordinate array, the corresponding command would be glVertexfs/zeHfypelvO, where size is one of [2, 3, 4], and type is one of [s,i,f,d] for GLshort, GLint, GLfloat, and GLdouble, respectively. Both size and type were defined by glVertexPointer(). For other enabled arrays, glArrayElement() calls glEdgeFlagv(), glTexCoord [vz'zej [type]v(), glColor[s/ze]\type]v(), g]SecondaryColor3[iype]v(), gllndex[fype]v(), glNormal3[iype]v(), and glFogGoord [type] v(). If the vertex coordinate array is enabled, the glVertex*v() routine is executed last, after the execution (if enabled) of up to seven corresponding array values.

glArrayElement() is usually called between glBegin() and glEnd(). (If called outside, glArrayElement() sets the current state for all enabled arrays, except for vertex, which has no current state.) In Example 2-10, a triangle is drawn using the third, fourth, and sixth vertices from enabled vertex arrays. (Again, remember that C programmers begin counting array locations with zero.)

Example 2-10 Using elArrayElementi) to Define Colors and Vertices

Using elArrayElementi) to Define Colors and Vertices

When executed, the latter five lines of code have the same effect as

tmp5324-97_thumb

 

 

tmp5324-98_thumb

Since glArrayElement() is only a single function call per vertex, it may reduce the number of function calls, which increases overall performance.

Be warned that if the contents of the array are changed between glBegin() and glEnd(), there is no guarantee that you will receive original data or changed data for your requested element. To be safe, don’t change the contents of any array element that might be accessed until the primitive is completed.

Next post:

Previous post: