顶点规范
Now that you have data in buffers, and you know how to write a basic vertex shader, it’s time to hook the data up to the shader. You’ve already read aboutvertex array objects, which contain information about where data is located and how it is laid out, and functions likeglVertexAttribPointer().It’s time to take a deeper dive into vertex specifications, other variants ofglVertexAttribPointer(), and how to specify data for vertex attributes that aren’t floating point or aren’t enabled.
VertexAttribPointer in Depth
TheglVertexAttribPointer()command was briefly introduced in Chapter 1. The prototype is as follows:
The state set byglVertexAttribPointer()is stored in the currently bound vertex array object (VAO).sizeis the number of elements in the attribute’s vector (1, 2, 3, or 4), or the special token GL_BGRA, which should be specified whenpackedvertex data is used. Thetypeparameter is a token that specifies the type of the data that is contained in the buffer object. Table 3.6 describes the token names that may be specified fortypeand the OpenGL data type that they correspond to:
Table 3.6. Values ofTypefor glVertexAttribPointer()
Token Value |
OpenGL Type |
GL_BYTE |
GLbyte (signed 8-bit bytes) |
GL_UNSIGNED_BYTE |
GLubyte (unsigned 8-bit bytes) |
GL_SHORT |
GLshort (signed 16-bit words) |
GL_UNSIGNED_SHORT |
GLushort (unsigned 16-bit words) |
GL_INT |
GLint (signed 32-bit integers) |
GL_UNSIGNED_INT |
GLuint (unsigned 32-bit integers) |
GL_FIXED |
GLfixed (16.16 signed fixed point) |
GL_FLOAT |
GLfloat (32-bit IEEE single-precision floating point) |
GL_HALF_FLOAT |
GLhalf (16-bit S1E5M10 half-precision floating point) |
GL_DOUBLE |
GLdouble (64-bit IEEE double-precision floating point) |
GL_INT_2_10_10_10_REV |
GLuint (packed data) |
GL_UNSIGNED_INT_2_10_10_10_REV |
GLuint (packed data) |
Note that while integer types such as GL_SHORT or GL_UNSIGNED_INT can be passed to thetypeargument, this tells OpenGL only what data type is stored in memory in the buffer object. OpenGL will convert this data to floating point in order to load it into floating-point vertex attributes. The way this conversion is performed is controlled by thenormalizeparameter. Whennormalizeis GL_FALSE, integer data is simply typecast into floating-point format before being passed to the vertex shader. This means that if you place the integer value 4 into a buffer and use the GL_INT token for thetypewhennormalizeis GL_FALSE, the value 4.0 will be placed into the shader. Whennormalizeis GL_TRUE, the data is normalized before being passed to the vertex shader. To do this, OpenGL divides each element by a fixed constant that depends on the incoming data type. When the data type is signed, the following formula is used:
Whereas, if the data type is unsigned, the following formula is used:
In both cases,fis the resulting floating-point value,cis the incoming integer component, andbis the number of bits in the data type (i.e., 8 for GL_UNSIGNED_BYTE, 16 for GL_SHORT, and so on). Note that unsigned data types are also scaled and biased before being divided by the type-dependent constant. To return to our example of putting 4 into an integer vertex attribute, we get:
which works out to about 0.000000009313—a pretty small number!
Integer Vertex Attributes
If you are familiar with the way floating-point numbers work, you’ll also realize that precision is lost as numbers become very large, and so the full range of integer values cannot be passed into a vertex shader using floating-point attributes. For this reason, we haveinteger vertex attributes.These are represented in vertex shaders by theint
,ivec2
,ivec3
, orivec4
types or their unsigned counterparts—uint
,uvec2
,uvec3
, anduvec4
.
A second vertex-attribute function is needed in order to pass raw integers into these vertex attributes—one that doesn’t automatically convert everything to floating point. This isglVertexAttribIPointer()—the I stands for integer.
Notice that the parameters toglVertexAttribIPointer()are identical to the parameters toglVertexAttribPointer(), except for the omission of thenormalizeparameter.normalizeis missing because it’s not relevant to integer vertex attributes. Only the integer data type tokens, GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, and GL_UNSIGNED_INT may be used for thetypeparameter.
Double-Precision Vertex Attributes
The third variant ofglVertexAttribPointer()isglVertexAttribLPointer()—here the L stands for “long”. This version of the function is specifically for loading attribute data into64-bit double-precisionfloating-point vertex attributes.
Again, notice the lack of thenormalizeparameter. InglVertexAttribPointer(),normalizewas used only for integer data types that aren’t legal here, and so the parameter is not needed. If GL_DOUBLE is used withglVertexAttribPointer(), the data is automatically down-converted to 32-bit single-precision floating-point representation before being passed to the vertex shader—even if the target vertex attribute was declared using one of the double-precision typesdouble
,dvec2
,dvec3
, ordvec4
, or one of the double-precision matrix types such asdmat4
.However, withglVertexAttribLPointer(), the full precision of the input data is kept and passed to the vertex shader.
Packed Data Formats for Vertex Attributes
Going back to theglVertexAttribPointer()command, you will notice that the allowed values for thesizeparameter are 1, 2, 3, 4, and the special token GL_BGRA. Also, thetypeparameter may take one of the special values GL_INT_2_10_10_10_REV or GL_UNSIGNED_INT_2_10_10_10_REV, both of which correspond to the GLuint data type. These special tokens are used to representpackeddata that can be consumed by OpenGL. The GL_INT_2_10_10_10_REV and GL_UNSIGNED_INT_2_10_10_10_REV tokens represent four-component data represented as ten bits for each of the first three components and two for the last, packed in reverse order into a single 32-bit quantity (a GLuint). GL_BGRA could just have easily been called GL_ZYXW.5Looking at the data layout within the 32-bit word, you would see the bits divided up as shown inFigure 3.3.
Figure 3.3.Packing of elements in a BGRA-packed vertex attribute
InFigure 3.3, the elements of the vertex are packed into a single 32-bit integer in the orderw,x,y,z—which when reversed isz,y,x,w, orb,g,r,awhen using color conventions. InFigure 3.4, the coordinates are packed in the orderw,z,y,x, which reversed and written in color conventions isr,g,b,a.
Figure 3.4.Packing of elements in a RGBA-packed vertex attribute
Vertex data may be specified only in the first of these two formats by using the GL_INT_2_10_10_10_REV or GL_UNSIGNED_INT_2_10_10_10_REV tokens. When one of these tokens is used as thetypeparameter toglVertexAttribPointer(), each vertex consumes one 32-bit word in the vertex array. The word is unpacked into its components and then optionally normalized (depending on the value of thenormalizeparameter before being loaded into the appropriate vertex attribute. This data arrangement is particularly well suited to normals or other types of attributes that can benefit from the additional precision afforded by the 10-bit components but perhaps don’t require the full precision offered by half-float data (which would take 16-bits per component). This allows the conservation of memory space and bandwidth, which helps improve performance.
Static Vertex-Attribute Specification
Remember from Chapter 1 where you were introduced toglEnableVertexAttribArray()andglDisableVertexAttribArray().These functions are used to tell OpenGL which vertex attributes are backed by vertex buffers. Before OpenGL will read any data from your vertex buffers, you must enable the corresponding vertex attribute arrays withglEnableVertexAttribArray().You may wonder what happens if you don’t enable the attribute array for one of your vertex attributes. In that case, thestatic vertex attributeis used. The static vertex attribute for each vertex is the default value that will be used for the attribute when there is no enabled attribute array for it. For example, imagine you had a vertex shader that would read the vertex color from one of the vertex attributes. Now suppose that all of the vertices in a particular mesh or part of that mesh had the same color. It would be a waste of memory and potentially of performance to fill a buffer full of that constant value for all the vertices in the mesh. Instead, you can just disable the vertex attribute array and use the static vertex attribute to specify color for all of the vertices.
The static vertex attribute for each attribute may be specified using one ofglVertexAttrib*()functions. When the vertex attribute is declared as a floating-point quantity in the vertex shader (i.e., it is of typefloat
,vec2
,vec3
,vec4
, or one of the floating-point matrix types such asmat4
), the followingglVertexAttrib*()commands can be used to set its value.
所有这些函数的隐式转换增刊lied parameters to floating-point before passing them to the vertex shader (unless they’re already floating-point). This conversion is a simple typecast. That is, the values are converted exactly as specified as if they had been specified in a buffer and associated with a vertex attribute by callingglVertexAttribPointer()with thenormalizeparameter set to GL_FALSE. For the integer variants of the functions, versions exist that normalize the parameters to the range [0, 1] or [–1, 1] depending on whether the parameters are signed or unsigned. These are:
Even with these commands, the parameters are still converted to floating-point before being passed to the vertex shader. Thus, they are suitable only for setting the static values of attributes declared with one of the single-precision floating-point data types. If you have vertex attributes that are declared as integers or double-precision floating-point variables, you should use one of the following functions:
Furthermore, if you have vertex attributes that are declared as one of the double-precision floating-point types, you should use one of theLvariants ofglVertexAttrib*(), which are:
Both theglVertexAttribI*()andglVertexAttribL*()variants ofglVertexAttrib*()pass their parameters through to the underlying vertex attribute just as theIversions ofglVertexAttribIPointer()do.
If you use one of theglVertexAttrib*()functions with less components than there are in the underlying vertex attribute (e.g., you useglVertexAttrib*()2f to set the value of a vertex attribute declared as avec4
), default values are filled in for the missing components. Forw, 1.0 is used as the default value, and foryandz, 0.0 is used.6If you use a function that takes more components than are present in the vertex attribute in the shader, the additional components are simply discarded.