Up until this point, we have always defined lists of triangles, where each triangle has its own set of vertices. We have actually only ever drawn a single triangle, but adding more would not have been a big deal.
There are cases, however, where two or more triangles can share some vertices. Let's think about how we'd render a rectangle with our current knowledge. We'd simply define two triangles that would have two vertices with the same positions, colors, and texture coordinates. We can do better. Figure 7-14 shows the old way and the new way of rendering a rectangle.
Instead of duplicating vertex v1 and v2 with vertex v4 and v6, we only define these vertices once. We still render two triangles in this case, but we tell OpenGL ES explicitly which vertices to use for each triangle (e.g., use v1, v2, and v3 for the first triangle and v3, v4, and v1 for the second one). Which vertices to use for each triangle is defined via indices into our vertices array. The first vertex in our array has index 0, the second vertex has index 1, and so on. For the preceding rectangle, we'd have a list of indices like this:
Incidentally, OpenGL ES wants us to specify the indices as shorts (which is not entirely correct; we could also use bytes). However, as with the vertex data, we can't just pass a short array to OpenGL ES. It wants a direct ShortBuffer. We already know how to handle that:
ByteBuffer byteBuffer = ByteBuffer.allocate(indices.length * 2);
byteBuffer.order(Byte0rder.native0rder()); ShortBuffer shortBuffer = byteBuffer.asShortBuffer(); shortBuffer.put(indices); shortBuffer.flip();
A short needs 2 bytes of memory, so we allocate indices.length x 2 bytes for our ShortBuffer. We set the order to native again and get a ShortBuffer view so we can handle the underlying ByteBuffer more easily. All that's left is putting our indices into the ShortBuffer and flipping it so the limit and position are set correctly.
If we wanted to draw Bob as a rectangle with two indexed triangles, we could define our vertices like this:
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(4 * VERTEX_SIZE); byteBuffer.order(Byte0rder.native0rder()); vertices = byteBuffer.asFloatBuffer(); vertices.put(new float[] { 100.0f, 100.0f, 0.0f, 1.0f,
228.0f, 100.0f, 1.0f, 1.0f, 228.0f, 229.0f, 1.0f, 0.0f, 100.0f, 228.0f, 0.0f, 0.0f });
vertices.flip();
The order of the vertices is exactly the same as in the right part of Figure 7-13. We tell OpenGL ES that we have positions and texture coordinates for our vertices and where it can find these vertex attributes via the usual calls to glEnableClientState() and glVertexPointer()/glTexCoordPointer(). The only thing that is different is the method we call to actually draw the two triangles:
gl.glDrawElements(GL10.GL_TRIANGLES, 6, GL10.GL_UNSIGNED_SHORT, indices);
It is very similar to glDrawArrays(), actually. The first parameter specifies the type of primitive we want to render—in this case a list of triangles. The next parameter specifies how many vertices we want to use, which equals six in our case. The third parameter specifies what type the indices have—we specify unsigned short. Note that Java has no unsigned types, though. However, given the one-complement encoding of signed numbers, it's OK to use a ShortBuffer that actually holds signed shorts. The last parameter is our ShortBuffer holding the six indices.
So, what will OpenGL ES do? It knows that we want to render triangles. It knows that we want to render two triangles, as we specified six vertices to be rendered. But instead of fetching six vertices sequentially from the vertices array, it goes sequentially through the index buffer and uses the vertices indexed by it.
Was this article helpful?