Specifying Per Vertex Color

In the last example we set a global default color for all vertices we draw via glColor4f(). Sometimes we want to have more granular control (e.g., we want to set a color per vertex). OpenGL ES offers us this functionality, and it's really easy to use. All we have to do is add RGBA float components to each vertex and tell OpenGL ES where it can find the color for each vertex, similar to how we told it where it can find the position for each vertex. Let's start by adding the colors to each vertex:

ByteBuffer byteBuffer = ByteBuffer.allocateDirect(3 * VERTEX_SIZE); byteBuffer.order(ByteOrder.nativeOrder()); FloatBuffer vertices = byteBuffer.asFloatBuffer(); vertices.put( new float[] { 0.0f, 0.0f, 1, 0, 0, 1,

319.0f, 0.0f, 0, 1, 0, 1, 160.0f, 479.0f, 0, 0, 1, 1});

vertices.flip();

We first have to allocate a ByteBuffer for our three vertices. How big should that ByteBuffer be? We have two coordinates and four (RGBA) color components per vertex, so that's six floats in total. Each float value takes up 4 bytes, so a single vertex uses 24 bytes. We store this information in VERTEX_SIZE. When we call

ByteBuffer.allocateDirect(), we just multiply VERTEX_SIZE by the number of vertices we want to store in the ByteBuffer. The rest is pretty self-explanatory. We get a FloatBuffer view to our ByteBuffer and put() the vertices into the ByteBuffer. Each row of the float array holds the x- and y-coordinates, and the R, G, B, and A components of a vertex, in that order.

If we want to render this, we have to tell OpenGL ES that our vertices not only have a position, but also a color attribute. We start off, as before, by calling glEnableClientState():

gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); gl.glEnableClientState(GL10.GL_C0L0R_ARRAY);

Now that OpenGL ES knows that it can expect position and color information for each vertex, we have to tell it where it can find that information:

vertices.position(0);

gl.glVertexPointer(2, GL10.GL_FL0AT, VERTEX_SIZE, vertices); vertices.position(2);

gl.glColorPointer(4, GL10.GL_FL0AT, VERTEX_SIZE, vertices);

We start of by setting the position of our FloatBuffer, which holds our vertices to 0. The position thus points to the x-coordinate of our first vertex in the buffer. Next we call glVertexPointer(). The only difference from the previous example is that we now also specify the vertex size (remember, it's given in bytes). OpenGL ES will then start reading in vertex positions from the position in the buffer we told it to start from. For the second vertex position it will add VERTEX_SIZE bytes to the first position's address, and so on.

Next we set the position of the buffer to the R component of the first vertex and call glColorPointer(), which tells OpenGL ES where it can find the colors of our vertices. The first argument is the number of components per color. This is always four, as OpenGL ES demands an R, G, B, and A component per vertex from us. The second parameter specifies the type of each component. As with the vertex coordinates, we use GL10.GL_FL0AT again to indicate that each color component is a float in the range between 0 and 1. The third parameter is the stride between vertex colors. It's of course the same as the stride between vertex positions. The final parameter is our vertices buffer again.

Since we called vertices.position(2) before theglColorPointer() call, OpenGL ES knows that the first vertex color can be found starting from the third float in the buffer. If we wouldn't have set the position of the buffer to 2, OpenGL ES would have started reading in the colors from position 0. That would have been bad, as that's where the x-coordinate of our first vertex is. Figure 7-8 shows where OpenGL ES will read our vertex attributes from, and how it jumps from one vertex to the next for each attribute.

glVertexPointer()

glColorPointerQ

\f

X

y 1 r 1 g

b

a

X

y

r

g

b

a

X

y

A a stride = VERTEX SIZE

Figure 7-8. Our vertices FloatBuffer, start addresses for OpenGL ES to read position/color from, and stride to be used to jump to the next position/color.

To draw our triangle, we again call glDrawElements(), which tells OpenGL ES to draw a triangle using the first three vertices of our FloatBuffer:

gl.glDrawElements(GL10.GL_TRIANGLES, 0, 3);

Since we enabled the GL10.GL_VERTEX_ARRAY and GL10.GL_COLOR_ARRAY, OpenGL ES knows that it should use the attributes specified by glVertexPointer() and glColorPointer(). It will ignore the default color, as we provide our own per-vertex colors.

NOTE: The way we just specified our vertices' positions and colors is called interleaving. This means that we pack the attributes of a vertex in one continuous memory block. There's another way we could have achieved this: noninterleaved vertex arrays. We'd have used two FloatBuffers, one for the positions and one for the colors. However, interleaving performs a lot better due to memory locality, so we won't discuss noninterleaved vertex arrays here.

Putting it all together into a new GLGame and Screen implementation should be a breeze by now. Listing 7-6 shows an excerpt from the file ColoredTriangleTest.java. I left out the boilerplate code.

Listing 7-6. Excerpt from ColoredTriangleTest.java; Interleaving Position and Color Attributes class ColoredTriangleScreen extends Screen { final int VERTEX_SIZE = (2 + 4) * 4; GLGraphics glGraphics; FloatBuffer vertices;

public ColoredTriangleScreen(Game game) { super(game);

glGraphics = ((GLGame) game).getGLGraphics();

ByteBuffer byteBuffer = ByteBuffer.allocateDirect(3 * VERTEX_SIZE); byteBuffer.order(ByteOrder.nativeOrder()); vertices = byteBuffer.asFloatBuffer(); vertices.put( new float[] { 0.0f, 0.0f, 1, 0, 0, 1,

319.0f, 0.0f, 0, 1, 0, 1, 160.0f, 479.0f, 0, 0, 1, 1});

vertices.flip();

@Override public void present(float deltaTime) { GL10 gl = glGraphics.getGL();

gl.glViewport(0, 0, glGraphics.getWidth(), glGraphics.getHeight());

gl.glClear(GL10. GL_COLOR_BUFFER_BIT);

gl.glMatrixMode(GL10.GL_PROJECTION);

gl.glLoadIdentity();

gl.glEnableClientState(GL10. GL_VERTEX_ARRAY); gl.glEnableClientState(GL10.GL_COLOR_ARRAY);

vertices.position(0);

gl.glVertexPointer(2, GL10.GL_FLOAT, VERTEX_SIZE, vertices); vertices.position(2);

gl.glColorPointer(4, GL10.GL_FLOAT, VERTEX_SIZE, vertices); gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 3);

gl.glVertexPointer(2, GL10.GL_FLOAT, VERTEX_SIZE, vertices); vertices.position(2);

gl.glColorPointer(4, GL10.GL_FLOAT, VERTEX_SIZE, vertices); gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 3);

Cool, that still looks pretty straightforward. All we changed compared to the previous example is adding the four color components to each vertex in our FloatBuffer and enabling the GL10.GL_COLOR_ARRAY. The best thing about it is that any additional vertex attributes we add in the subsequent examples will work the same way. We just tell OpenGL ES to not use the default value for that specific attribute but instead look up the attributes in our FloatBuffer, starting at a specific position and moving from vertex to vertex by VERTEX_SIZE bytes.

Now, we could also turn off the GL10.GL_COLOR_ARRAY so that OpenGL ES uses the default vertex color again, which we can specify via glColor4f() as we did previously. For this we can call gl.glDisableClientState(GL10.GL_COLOR_ARRAY);

OpenGL ES will just turn off the feature to read the colors from our FloatBuffer. If we already set a color pointer via glColorPointer(), OpenGL ES will remember the pointer, though. We just told it to not use it.

To round this example out, let's have a look at the output of the preceding program. Figure 7-9 shows a screenshot.

0 0

Post a comment