7.7. Specifying Vertex Attributes
One way you can pass vertex data to OpenGL is by calling glBegin, followed by some sequence of glColor/glNormal/glVertex/etc. A call to glEnd terminates this method of specifying vertex data.
These calls continue to work in the OpenGL programmable environment. As before, a call to glVertex indicates that the data for an individual vertex is complete and should be processed. However, if a valid vertex shader has been installed with glUseProgram, the vertex data is processed by that vertex shader instead of by the usual fixed functionality of OpenGL. A vertex shader can use the following built-in variables to access the standard types of vertex data passed to OpenGL:
attribute vec4 gl_Color; attribute vec4 gl_SecondaryColor; attribute vec3 gl_Normal; attribute vec4 gl_Vertex; attribute vec4 gl_MultiTexCoord0; attribute vec4 gl_MultiTexCoord1; attribute vec4 gl_MultiTexCoord2; . . . attribute vec4 gl_FogCoord;
OpenGL's vertex-at-a-time interface is simple and powerful, but on today's systems it is definitely not the highest-performance way of transferring vertex data to the graphics accelerator. Whenever possible, applications should use the vertex array interface instead. This interface allows you to store vertex data in arrays and set pointers to those arrays. Instead of sending one vertex at a time to OpenGL, you can send a whole set of primitives at a time. With vertex buffer objects, it is even possible that vertex arrays are stored in memory on the graphics board to exact maximum performance.
The vertex array interface also works the same way in the OpenGL programmable environment as it did previously. When a vertex array is sent to OpenGL, the vertex data in the vertex array is processed one vertex at a time, just like the vertex-at-a-time interface. If a vertex shader is active, each vertex is processed by the vertex shader rather than by the fixed functionality of OpenGL.
However, the brave new world of programmability means that applications no longer need to be limited to the standard attributes defined by OpenGL. There are many additional per-vertex attributes that applications might like to pass into a vertex shader. It is easy to imagine that applications will want to specify per-vertex data such as tangents, temperature, pressure, and who knows what else. How do we allow applications to pass nontraditional attributes to OpenGL and operate on them in vertex shaders?
The answer is that OpenGL provides a small number of generic locations for passing in vertex attributes. Each location is numbered and has room to store up to four floating-point components (i.e., it is a vec4). An implementation that supports 16 attribute locations will have them numbered from 0 to 15. An application can pass a vertex attribute into any of the generic numbered slots by using one of the following functions:
This set of commands has a certain set of rules for converting data to the floating-point internal representation specified by OpenGL. Floats and doubles are mapped into OpenGL internal floating-point values as you would expect, and integer values are converted to floats by a decimal point added to the right of the value provided. Thus, a value of 27 for a byte, int, short, unsigned byte, unsigned int, or unsigned short becomes a value of 27.0 for computation within OpenGL.
Another set of entry points supports the passing of normalized values as generic vertex attributes:
N in a command name indicates that, for data types other than float or double, the arguments will be linearly mapped to a normalized range in the same way as data provided to the integer variants of glColor or glNormalthat is, for signed integer variants of the functions, the most positive, representable value maps to 1.0, and the most negative representable value maps to 1.0. For the unsigned integer variants, the largest representable value maps to 1.0, and the smallest representable value maps to 0.
Attribute variables are allowed to be of type mat2, mat3, or mat4. Attributes of these types can be loaded with the glVertexAttrib entry points. Matrices must be loaded into successive generic attribute slots in column major order, with one column of the matrix in each generic attribute slot. Thus, to load a mat4 attribute, you would load the first column in generic attribute slot i, the second in slot i + 1, the third in slot i + 2, and the fourth in slot i + 3.
With one exception, generic vertex attributes are just thatgeneric. They pass additional color values, tangents, binormals, depth values, or anything. The exception is that the generic vertex attribute with index 0 indicates the completion of a vertex just like a call to glVertex.
A glVertex2, glVertex3, or glVertex4 command is completely equivalent to the corresponding glVertexAttrib command with an index argument of 0. There are no current values for generic vertex attribute 0 (an error is generated if you attempt to query its current value). This is the only generic vertex attribute with this property; calls to set other standard vertex attributes can be freely mixed with calls to set any of the other generic vertex attributes. You are also free to mix calls to glVertex and glVertexAttrib with index 0.
The vertex array API has been similarly extended to allow generic vertex attributes to be specified as vertex arrays. The following call establishes the vertex array pointer for a generic vertex attribute:
After the vertex array information has been specified for a generic vertex attribute array, the array needs to be enabled. When enabled, the generic vertex attribute data in the specified array is provided along with other enabled vertex array data when vertex array drawing commands such as glDrawArrays are called. To enable or disable a generic vertex attribute array, use the commands
This solves the question of how generic vertex data is passed into OpenGL, but how do we access that data from within a vertex shader? We don't want to refer to these numbered locations in our shader, because this approach is not very descriptive and is prone to errors. The OpenGL Shading Language API provides two ways for associating generic vertex indices with vertex shader attribute variables.
The first way is to let the linker assign the bindings automatically. In this case, the application would need to query OpenGL after linking to determine the generic vertex indices that were assigned and then would use these indices when passing the attributes to OpenGL.
The second way is for the application to choose the index value of the generic vertex attribute to be used and explicitly bind it to a specific attribute variable in the vertex shader by using the following function before linking occurs:
glBindAttribLocation can be called before any vertex shader objects are attached to the specified program object. It is also permissible to bind an attribute variable name that is never used in a vertex shader to a generic attribute index.
Applications are allowed to bind more than one vertex shader attribute name to the same generic vertex attribute index. This is called ATTRIBUTE ALIASING, and it is allowed only if just one of the aliased attributes is active in the executable program or if no path through the shader consumes more than one attribute of a set of attributes aliased to the same location. Another way of saying this is that more than one attribute name may be bound to a generic attribute index if, in the end, only one name is used to access the generic attribute in the vertex shader. The compiler and linker are allowed to assume that no aliasing is done and are free to employ optimizations that work only in the absence of aliasing. OpenGL implementations are not required to do error checking to detect attribute aliasing. Because there is no way to bind standard attributes, it is not possible to alias generic attributes with conventional ones.
The binding between an attribute variable name and a generic attribute index can be specified at any time with glBindAttribLocation. Attribute bindings do not go into effect until glLinkProgram is called, so any attribute variables that need to be bound explicitly for a particular use of a shader should be bound before the link operation occurs. After a program object has been linked successfully, the index values for attribute variables remain fixed (and their values can be queried) until the next link command occurs. To query the attribute binding for a named vertex shader attribute variable, use glGetAttribLocation. It returns the binding that actually went into effect the last time glLinkProgram was called for the specified program object. Attribute bindings that have been specified since the last link operation are not returned by glGetAttribLocation.
Using these functions, we can create a vertex shader that contains a user-defined attribute variable named Opacity that is used directly in the lighting calculations. We can decide that we want to pass per-vertex opacity values in generic attribute location 1 and set up the proper binding with the following line of code:
glBindAttribLocation(myProgram, 1, "Opacity");
Subsequently, we can call glVertexAttrib to pass an opacity value at every vertex:
The glVertexAttrib calls are all designed for use between glBegin and glEnd. As such, they offer replacements for the standard OpenGL calls such as glColor, glNormal, and so on. But as we have already pointed out, vertex arrays should be used if graphics performance is a concern.
The jargon in this section can get a little confusing, so let's look at a diagram to make sure we have things straight. Figure 7.1 illustrates how commands to set standard vertex attributes modify the values of built-in attribute variables defined by the OpenGL Shading Language. The mappings between commands to set standard attributes (color, normal, vertex, etc.) and the built-in attribute variables (gl_Color, gl_Normal, gl_Vertex, etc.) are done automatically, and they are done in a way that doesn't conflict with the use of any generic attribute location that will be used. Each of these calls except glVertex also sets the current state for that attribute. (The value provided in a call to glVertex is never saved as part of current state.) The value for a built-in attribute variable is automatically updated when a call is made to set the value of the corresponding standard vertex attribute.
Figure 7.1. Mapping of standard vertex attribute commands to built-in attribute variables
Now let's look at the case of generic vertex attributes as illustrated in Figure 7.2. A user-defined attribute variable must be bound to a generic vertex attribute index. This binding can be done with glBindAttribLocation, or it can happen implicitly at link time.
Figure 7.2. Mapping of generic vertex attribute commands to user-defined attribute variables
Let's assume we have a vertex shader that uses three user-defined attribute variables: Opacity, Binormal, and MyData. These are shown on the right side of Figure 7.2. These user-defined attribute variables can each be bound to a generic vertex attribute index as follows:
glBindAttribLocation(myProgram, 1, "Opacity"); glBindAttribLocation(myProgram, 2, "Binormal"); glBindAttribLocation(myProgram, 3, "MyData");
This sets up the mapping so that values written into generic vertex attribute locations 1, 2, and 3 will modify the values of the attribute variables Opacity, Binormal, and MyData in the vertex shader. Generic vertex attribute 0 can be bound to a user-defined attribute variable, or its value can be obtained through the built-in attribute variable gl_Vertex. The diagram shows that generic vertex attribute index N is not currently bound to any user-defined attribute variable.
As mentioned, each of the generic attribute locations has enough room for four floating-point components. Applications are permitted to store 1, 2, 3, or 4 components in each location. A vertex shader may access a single location by using a user-defined attribute variable that is a float, a vec2, a vec3, or a vec4. It may access two consecutive locations by using a user-defined attribute variable that is a mat2, three using a mat3, and four using a mat4.
The bindings between generic attribute index values and user-defined attribute variables (i.e., the arrows on the right side of Figure 7.2) are part of the state maintained within a program object, whereas the contents of the attribute array itself is considered current attribute state (except for the generic vertex attribute with index 0). The application can provide a different program object and specify different names and mappings for attribute variables in the vertex shader, and if no calls have been made to update the attribute values in the interim, the attribute variables in the new vertex shader get the values left behind by the previous one.
Attribute variables that can be accessed when a vertex shader is executed are called ACTIVE ATTRIBUTES. To obtain information about an active attribute, use the following command:
The glGetActiveAttrib command can be useful in an environment in which shader development occurs separately from application development. If some attribute-naming conventions are agreed to between the shader writers and the application developers, the latter could query the program object at runtime to determine the attributes that are actually needed and could pass those down. This approach can provide more flexibility in the shader development process.