How it works...

Vertex attributes are the input variables to our vertex shader. In the given vertex shader, our two attributes are VertexPosition and VertexColor. The main OpenGL program refers to vertex attributes by associating each (active) input variable with a generic attribute index. These generic indices are simply integers between 0 and GL_MAX_VERTEX_ATTRIBS - 1. We can specify the relationship between these indices and the attributes using the layout qualifier. For example, in our vertex shader, we use the layout qualifier to assign VertexPosition to attribute index 0 and VertexColor to attribute index 1:

layout (location = 0) in vec3 VertexPosition; 
layout (location = 1) in vec3 VertexColor; 

We refer to the vertex attributes in our OpenGL code by referring to the corresponding generic vertex attribute index.

It is not strictly necessary to explicitly specify the mappings between attribute variables and generic attribute indexes, because OpenGL will automatically map active vertex attributes to generic indexes when the program is linked. We could then query for the mappings and determine the indexes that correspond to the shader's input variables. It may be somewhat clearer, however, to explicitly specify the mapping, as we do in this example.

The first step involves setting up a pair of buffer objects to store our position and color data. As with most OpenGL objects, we start by creating the objects and acquiring handles to the two buffers by calling glGenBuffers. We then assign each handle to a separate descriptive variable to make the following code more clear.

For each buffer object, we first bind the buffer to the GL_ARRAY_BUFFER binding point by calling glBindBuffer. The first argument to glBindBuffer is the target binding point. In this case, since the data is essentially a generic array, we use GL_ARRAY_BUFFER. Examples of other kinds of targets (such as GL_UNIFORM_BUFFER or GL_ELEMENT_ARRAY_BUFFER) will be seen in later examples.

Once our buffer object is bound, we can populate the buffer with our vertex/color data by calling glBufferData. The second and third arguments to this function are the size of the array and a pointer to the array containing the data. Let's focus on the first and last arguments. The first argument indicates the target buffer object. The data provided in the third argument is copied into the buffer that is bound to this binding point. The last argument is one that gives OpenGL a hint about how the data will be used so that it can determine how best to manage the buffer internally. For full details about this argument, take a look into the OpenGL documentation. In our case, the data is specified once, will not be modified, and will be used many times for drawing operations, so this usage pattern best corresponds to the  GL_STATIC_DRAW value.

Now that we have set up our buffer objects, we tie them together into a Vertex Array Object (VAO). The VAO contains information about the connections between the data in our buffers and the input vertex attributes. We create a VAO using the glGenVertexArrays function. This gives us a handle to our new object, which we store in the vaoHandle (global) variable. Then, we enable the generic vertex attribute indexes 0 and 1 by calling glEnableVertexAttribArray. Doing so indicates that that the values for the attributes will be accessed and used for rendering.

The next step makes the connection between the buffer objects and the generic vertex attribute indexes:

// Map index 0 to the position buffer 
glBindBuffer(GL_ARRAY_BUFFER, positionBufferHandle); 
glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, 0, NULL ); 

First, we bind the buffer object to the GL_ARRAY_BUFFER binding point, then we call glVertexAttribPointer, which tells OpenGL which generic index the data should be used with, the format of the data stored in the buffer object, and where it is located within the buffer object that is bound to the GL_ARRAY_BUFFER binding point.

The first argument is the generic attribute index. The second is the number of components per vertex attribute (1, 2, 3, or 4). In this case, we are providing three-dimensional data, so we want three components per vertex. The third argument is the data type of each component in the buffer. The fourth is a Boolean that specifies whether the data should be automatically normalized (mapped to a range of [-1, 1] for signed integral values or [0, 1] for unsigned integral values). The fifth argument is the stride, which indicates the byte offset between consecutive attributes. Since our data is tightly packed, we can use a value of zero. The last argument is a pointer, which is not treated as a pointer! Instead, its value is interpreted as a byte offset from the beginning of the buffer to the first attribute in the buffer. In this case, there is no additional data in either buffer before the first element, so we use a value of zero.

The glVertexAttribPointer function stores (in the VAO's state) a pointer to the buffer currently bound to the GL_ARRAY_BUFFER binding point. When another buffer is bound to that binding point, it does not change the value of the pointer.

The VAO stores all of the OpenGL states related to the relationship between buffer objects and the generic vertex attributes, as well as the information about the format of the data in the buffer objects. This allows us to quickly return all of this state when rendering.

The VAO is an extremely important concept, but can be tricky to understand. It's important to remember that the VAO's state is primarily associated with the enabled attributes and their connection to buffer objects. It doesn't necessarily keep track of buffer bindings. For example, it doesn't remember what is bound to the GL_ARRAY_BUFFER binding point. We only bind to this point in order to set up the pointers via glVertexAttribPointer.

Once we have the VAO set up (a one-time operation), we can issue a draw command to render our object. In our render function, we clear the color buffer using glClear, bind to the vertex array object, and call glDrawArrays to draw our triangle. The glDrawArrays function initiates rendering of primitives by stepping through the buffers for each enabled attribute array, and passing the data down the pipeline to our vertex shader. The first argument is the render mode (in this case, we are drawing triangles), the second is the starting index in the enabled arrays, and the third argument is the number of indices to be rendered (three vertices for a single triangle).

To summarize, we followed these steps:

  1. Make sure to specify the generic vertex attribute indexes for each attribute in the vertex shader using the layout qualifier
  2. Create and populate the buffer objects for each attribute
  3. Create and define the vertex array object by calling glVertexAttribPointer while the appropriate buffer is bound
  4. When rendering, bind to the vertex array object and call glDrawArrays, or an other appropriate rendering function (for example, glDrawElements)
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.213.128