Part I A Simple Game of Air Hockey 2. Defining Vertices and Shaders
4. Adding Color and Shade
4.4 Rendering with the New Color Attribute 71
Now that we’ve added a color attribute to our data and we’ve updated the vertex and fragment shaders to use this attribute, the next steps are to remove the old code that passed in the color via a uniform and to tell OpenGL to read in colors as a vertex attribute.
Updating Constants
Let’s add the following constants to the top of AirHockeyRenderer:
AirHockey2/src/com/airhockey/android/AirHockeyRenderer.java private static final String A_COLOR = "a_Color";
private static final int COLOR_COMPONENT_COUNT = 3;
private static final int STRIDE =
(POSITION_COMPONENT_COUNT + COLOR_COMPONENT_COUNT) * BYTES_PER_FLOAT;
We’ll also need a new member variable:
AirHockey2/src/com/airhockey/android/AirHockeyRenderer.java private int aColorLocation;
We can now remove the old constants and variables associated with u_Color. Did you notice that we added a special constant, called STRIDE? As we now have both a position and a color attribute in the same data array, OpenGL can no longer assume that the next position follows immediately after the previous position. Once OpenGL has read the position for a vertex, it will have to skip over the color for the current vertex if it wants to read the position for the next vertex. We’ll use the stride to tell OpenGL how many bytes are between each position so that it knows how far it has to skip.
In Figure 17, A single vertex array with multiple attributes, on page 73, we can see a visual example of how our vertex array is currently storing the data.
Rendering with the New Color Attribute • 71
The stride tells OpenGL the interval between each position or each color.
Instead of using a stride, we could use multiple vertex arrays for each attribute, as seen in Figure 18, Multiple vertex arrays, each with a single attribute, on page 73. While packing everything into a single array is usually more efficient, using multiple arrays might make more sense if we need to update all of the colors or all of the positions on a regular basis.
Updating onSurfaceCreated()
The next step will be to update onSurfaceCreated() to reflect the new color attribute. We first need to get the location of our new attribute, so let’s remove the code associated with u_Color and add the following code:
AirHockey2/src/com/airhockey/android/AirHockeyRenderer.java
aColorLocation = glGetAttribLocation(program, A_COLOR);
We should also update the call to glVertexAttribPointer() to add in the stride:
AirHockey2/src/com/airhockey/android/AirHockeyRenderer.java
glVertexAttribPointer(aPositionLocation, POSITION_COMPONENT_COUNT, GL_FLOAT, false, STRIDE, vertexData);
Now we can add in the code to tell OpenGL to associate our vertex data with a_Color in the shader. Add the following code to the end of onSurfaceCreated():
AirHockey2/src/com/airhockey/android/AirHockeyRenderer.java vertexData.position(POSITION_COMPONENT_COUNT);
glVertexAttribPointer(aColorLocation, COLOR_COMPONENT_COUNT, GL_FLOAT, false, STRIDE, vertexData);
glEnableVertexAttribArray(aColorLocation);
This is an important bit of code, so let’s take the time to understand each line carefully:
1. First we set the position of vertexData to POSITION_COMPONENT_COUNT, which is set to 2. Why do we do this? Well, when OpenGL starts reading in the color attributes, we want it to start at the first color attribute, not the first position attribute.
We need to skip over the first position ourselves by taking the position component size into account, so we set the position to POSITION_COMPO- NENT_COUNT so that the buffer’s position is set to the position of the very first color attribute. Had we set the position to 0 instead, OpenGL would be reading in the position as the color.
2. We then call glVertexAttribPointer() to associate our color data with a_Color in our shaders. The stride tells OpenGL how many bytes are between each Chapter 4. Adding Color and Shade • 72
Figure 17—A single vertex array with multiple attributes
Figure 18—Multiple vertex arrays, each with a single attribute
color, so that when it reads in the colors for all of the vertices, it knows how many bytes it needs to skip to read the color for the next vertex. It’s very important that the stride be specified in terms of bytes.
Even though a color in OpenGL has four components (red, green, blue, and alpha), we don’t have to specify all of them. Unlike uniforms, OpenGL Rendering with the New Color Attribute • 73
will replace unspecified components in attributes with defaults: the first three components will be set to 0, and the last component set to 1.
3. Finally, we enable the vertex attribute for the color attribute, just like we did for the position attribute.
Updating onDrawFrame
We have just one more thing to do: update onDrawFrame(). All we have to do here is delete the calls to glUniform4f(), because we no longer need them. Since we’ve already associated our vertex data with a_Color, all we need to do is call glDrawArrays(), and OpenGL will automatically read in the color attributes from our vertex data.
Putting It All Together
Let’s run our program and see what we get; it should look like the following figure:
Figure 19—Brightening the center with linear interpolation
Chapter 4. Adding Color and Shade • 74
Our air hockey table looks nicer than before, and we can definitely see that the table is now brighter in the center than on the edges. However, we can also make out the shape of each triangle. The reason for this is because the direction of the linear interpolation follows the triangle, so while things look smooth within that triangle, we can sometimes still see where one triangle ends and another begins.
To reduce or eliminate this effect, we can use more triangles or we can use a lighting algorithm and calculate the color values on a per-fragment basis.
We’ll learn more about lighting algorithms in Chapter 13, Lighting Up the World, on page 253.