Following the OpenGL Pipeline
In this chapter, we will walk all the way along the OpenGL pipeline from start to finish, providing insight into each of the stages, which include fixed-function blocks and programmable shader blocks. You have already read a whirlwind introduction to the vertex and fragment shader stages. However, the application that you constructed simply drew a single triangle at a fixed position. If we want to render anything interesting with OpenGL, we’re going to have to learn a lot more about the pipeline and all of the things you can do with it. This chapter introduces every part of the pipeline, hooks them up to one another, and provides an example shader for each stage.
Passing Data to the Vertex Shader
The vertex shader is the first programmable stage in the OpenGL pipeline and has the distinction of being the only mandatory stage in the graphics pipeline. However, before the vertex shader runs, a fixed-function stage known as vertex fetching, or sometimes vertex pulling, is run. This automatically provides inputs to the vertex shader.
Vertex Attributes
In GLSL, the mechanism for getting data in and out of shaders is to declare global variables with the in and out storage qualifiers. You were briefly introduced to the out qualifier in Chapter 2, “Our First OpenGL Program,” when Listing 2.4 used it to output a color from the fragment shader. At the start of the OpenGL pipeline, we use the in keyword to bring inputs into the vertex shader. Between stages, in and out can be used to form conduits from shader to shader and pass data between them. We’ll get to that shortly. For now, consider the input to the vertex shader and what happens if you declare a variable with an in storage qualifier. This marks the variable as an input to the vertex shader, which means that it is essentially an input to the OpenGL graphics pipeline. It is automatically filled in by the fixed-function vertex fetch stage. The variable becomes known as a vertex attribute.
Vertex attributes are how vertex data is introduced into the OpenGL pipeline. To declare a vertex attribute, you declare a variable in the vertex shader using the in storage qualifier. An example of this is shown in Listing 3.1, where we declare the variable offset as an input attribute.
Listing 3.1: Declaration of a vertex attribute
#version 450 core // 'offset' is an input vertex attribute layout (location = 0) in vec4 offset; void main(void) { const vec4 vertices[3] = vec4[3](vec4(0.25, -0.25, 0.5, 1.0), vec4(-0.25, -0.25, 0.5, 1.0), vec4(0.25, 0.25, 0.5, 1.0)); // Add 'offset' to our hard-coded vertex position gl_Position = vertices[gl_VertexID] + offset; }
In Listing 3.1, we have added the variable offset as an input to the vertex shader. As it is an input to the first shader in the pipeline, it will be filled automatically by the vertex fetch stage. We can tell this stage what to fill the variable with by using one of the many variants of the vertex attribute functions, glVertexAttrib*(). The prototype for glVertexAttrib4fv(), which we use in this example, is
void glVertexAttrib4fv(GLuint index, const GLfloat * v);
Here, the parameter index is used to reference the attribute and v is a pointer to the new data to put into the attribute. You may have noticed the layout (location = 0) code in the declaration of the offset attribute. This is a layout qualifier, which we have used to set the location of the vertex attribute to zero. This location is the value we’ll pass in index to refer to the attribute.
Each time we call one of the glVertexAttrib*() functions (of which there are many), it will update the value of the vertex attribute that is passed to the vertex shader. We can use this approach to animate our one triangle. Listing 3.2 shows an updated version of our rendering function that updates the value of offset in each frame.
Listing 3.2: Updating a vertex attribute
// Our rendering function virtual void render(double currentTime) { const GLfloat color[] = { (float)sin(currentTime) * 0.5f + 0.5f, (float)cos(currentTime) * 0.5f + 0.5f, 0.0f, 1.0f }; glClearBufferfv(GL_COLOR, 0, color); // Use the program object we created earlier for rendering glUseProgram(rendering_program); GLfloat attrib[] = { (float)sin(currentTime) * 0.5f, (float)cos(currentTime) * 0.6f, 0.0f, 0.0f }; // Update the value of input attribute 0 glVertexAttrib4fv(0, attrib); // Draw one triangle glDrawArrays(GL_TRIANGLES, 0, 3); }
When we run the program with the rendering function of Listing 3.2, the triangle will move in a smooth oval shape around the window.