Defining the Projection Matrix

The next thing we need to define is the projection matrix. As we are only concerned with 2D graphics in this chapter, we want to use a parallel projection. How do we do that?

Matrix Modes and Active Matrices

We already discussed that OpenGL ES keeps track of three matrices: the projection matrix, the model-view matrix, and the texture matrix (which we'll continue to ignore). OpenGL ES offers us a couple of specific methods to modify these matrices. Before we can use these methods, however, we have to tell OpenGL ES which matrix we want to manipulate. This is done with the following method:

GL10.glMatrixMode(int mode)

The mode parameter can be GL10.GL_PROJECTION, GL10.GL_MODELVIEW, or GL10.GL_TEXTURE. It should be clear which of these constants will make which matrix active. Any subsequent calls to the matrix manipulation methods will target the matrix we set with this method until we change the active matrix again via another call to this method. This matrix mode is one of OpenGL ES's states (which will get lost when we lose the context if our application is paused and resumed). To manipulate the projection matrix with any subsequent calls, we can call the method like this:

gl.glMatrixMode(GL10.GL_PROJECTION);

Orthographic Projection with glOrthof

OpenGL ES offers us the following method for setting the active matrix to an orthographic (parallel) projection matrix:

GL10.glOrthof(int left, int right, int bottom, int top, int near, int far)

Hey, that looks a lot like it has something to do with our view frustum's clipping planes. And indeed it does. So what values do we specify here?

OpenGL ES has a standard coordinate system, as depicted in Figure 7-4. The positive x-axis points to the right, the positive y-axis points upward, and the positive z-axis points toward us. With glOrthof() we define the view frustum of our parallel projection in this coordinate system. If you look back at Figure 7-3, you can see that the view frustum of a parallel projection is a box. We can interpret the parameters for glOrthof() as specifying two of these corners of our view frustum box. Figure 7-5 illustrates this.

Figure 7-5. An orthographic view frustum

The front side of our view frustum will be directly mapped to our viewport. In the case of a full-screen viewport from, say, (0,0) to (480,320) (e.g., landscape mode on a Hero), the bottom-left corner of the front side would map to the bottom-left corner of our screen, and the top-right corner of the front side would map to the top-left corner of our screen. OpenGL will perform the stretching automatically for us.

Since we want to do 2D graphics, we will specify the corner points (left, bottom, near) and (right, top, far) (see figure 7-5) in a way that allows us to work in a sort of pixel coordinate system, as we did with the Canvas and Mr. Nom. Here's how we could set up such a coordinate system:

Android Canvas Coordinate System

Figure 7-5. An orthographic view frustum

Figure 7-6 shows the view frustum.

Figure 7-6 shows the view frustum.

Opengl Projection Modelview

Figure 7-6. Our parallel projection view frustum for 2D rendering with OpenGL ES

Our view frustum is pretty thin, but that's OK because we'll only be working in 2D. The visible part of our coordinate system goes from (0,0,1) to (480,320,-1). Any points we specify within this box will be visible on the screen as well. The points will be projected onto the front side of this box, which is our beloved near clipping plane. The projection will then get stretched out onto the viewport, whatever dimensions it has. Say we have a Nexus One with a resolution of 800x480 pixels in landscape mode. When we specify our view frustum as just mentioned, we can work in a 480x320 coordinate system, and OpenGL will stretch it to the 800x480 framebuffer (if we specified that the viewport covers the complete framebuffer). Best of all, there's nothing keeping us from using crazier view frustums. We could also use one with the corners (-1,-1,100) and (2,2,100). Everything we specify that falls inside this box will be visible and get stretched automatically. Pretty nifty.

Note that we also set the near and far clipping planes. Since we are going to neglect the z-coordinate completely in this chapter, you might be tempted to use zero for both near and far. However, that's a bad idea for various reasons. To play it safe, we grant the view frustum a little buffer in the z-axis. All our geometries' points will be defined in the x-y plane with z set to zero, though—2D all the way.

NOTE: You might have noticed that the y-axis is pointing upward now, and the origin is in the lower-left corner of our screen. While the Canvas, the UI framework, and many other 2D-rendering APIs use the y-down, origin-top-left convention, it is actually more convenient to use this "new" coordinate system for game programming. For example, if Super Mario is jumping, wouldn't you expect his y-coordinate to increase instead of decrease while he's on his way up? Want to work in the other coordinate system? Fine, just swap the bottom and top parameters of glOrthof(). Also, while the illustration of the view frustum is mostly correct from a geometric point of view, the near and far clipping planes are actually interpreted a little differently by glOrthof(). Since that is a little involved, we'll just pretend the preceding illustrations are correct, though.

A Helpful Snippet

Here's a small snippet that will be used in all of our examples in this chapter. It clears the screen with black, sets the viewport to span the whole framebuffer, and sets up the projection matrix (and thereby the view frustum) so we can work in a comfortable coordinate system with the origin in the lower-left corner of the screen and the y-axis pointing upward:

gl.glClearColor(0,0,0,1); gl.glClear(GL10.GL_COLOR_BUFFER_BIT);

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

gl.glMatrixMode(GL10.GL_PROJECTION);

gl.glLoadIdentity();

Wait, what does glLoadIdentity() do in there? Well, most of the methods OpenGL ES offers us to manipulate the active matrix don't actually set the matrix. Instead they construct a temporary matrix from whatever parameters they take and multiply it with the current matrix. The glOrthof() method is no exception. For example, if we called glOrthof() each frame, we'd multiply the projection matrix to death with itself. So instead of doing that, we make sure we have a clean identity matrix in place before we multiply the projection matrix. Remember, multiplying a matrix by the identity matrix will output the matrix itself again. And that's what glLoadIdentity() is for. Think of it as first loading the value 1 and then multiplying it with whatever we have (in our case, the projection matrix produced by glOrthof()).

Note that our coordinate system now goes from (0,0,1) to (320,480,-1)—that's for portrait mode rendering.

+1 0

Post a comment