Drawing a proper square using the Projection Matrix

To understand why the square in the post before was rendered as a rectangle we need to understand how OpenGL transforms the 3D vertices we specify into 2D window coordinates. This is achieved through a four stage process as shown below.

Vertices -> Pixels
Vertices -> Pixels

The section that we need to closely look at is the projection matrix. This is a 4*4 matrix mapping vertices from \mathbb{R}^3 \rightarrow \mathbb{R}^3 . Any vertices that are mapped to the cube bounded by

-1<=x<=1, -1<=y<=1, -1<=z<=1

are eventually drawn on screen.

So, we are interested in which section of \mathbb{R}^3 is mapped to the above cube, i.e. we need to know the preimage of the cube so that we know where to place our vertices. We usually use two types of projection matrix which OpenGL provides by default.

Orthographic projection

An orthographic projection maps a rectangular box to the cube defined above. One property of the orthographic projection is that shapes closer or further away from the ‘camera’ are the drawn as the same size i.e. there is no perspective correlation. I say ‘camera’ with some reserve as there isn’t really a camera in OpenGL. The only sense in which we have a camera is that if we have two opaque objects which are exactly the same except one is at z=-1 and the other is at z=-2, then only the first one will be visible because OpenGL draws the first on top of the second. In this sense the ‘camera’ is pointing down the negative z-axis but still has no real position. We set an orthographic projection using the following command:

void glOrtho(float left,
   float right,
   float bottom,
   float top,
   float nearZVal,
   float farZVal
);
The two cubes look the same size on screen
The two cubes look the same size on screen

This type of projection matrix is used for CAD and architectural systems where is is important to be able to compare sizes of objects and in 2D games.
Perspective projection
This performs a mapping from a frustrum to the cube as defined above. This type of projection matrix is used to model the real world as we see it. It has the property that 2 identical objects one close to the origin and one far away appear on the screen as different sizes, the closer the object is, the bigger it seems.

Later in the process this cube is mapped to the 2D window coordinates, so the projection matrix defines what area of the 3D world is eventually visible on screen and more specifically, which vertices that we specify are visible. Our visible area looks like this in the 3D world

Only points inside the frustum are visible
Only points inside the frustum are visible

Quite simply, any vertex that is in this area after the modelview transformation is drawn on screen. The focus of the frustum (the point where the edges would meet if extended) is always (0,0,0) by definition. The analogy with real-world viewing angles is immediately apparent. If we imagine that our viewer is at (0,0,0) and looking in the direction of the frustum, then we can think of the nearest boundary of the frustum as the computer screen and the far boundary as the point beyond nothing which useful can be seen.

We define a frustum with the following command:

void glFrustum(float left,
			  float right,
			  float bottom,
			  float top,
			  float zNear,
			  float zFar);

where left, right, bottom etc are as definied here. The derivation of the matrix which performs the mapping is derived here.

Some other Points

Both glOrtho and glFrustum can only be called once OpenGL is put in specific mode. It must be told to interpret the following commands in terms of a projective matrix. This command is

glMatrixMode(GL_PROJECTION);

After this command is called, it is good practice to call

glLoadIdentity();

in case there are any commands preloaded into the projection matrix. You should also note that OpenGL takes commands after glMatrixMode accumulatively i.e. if you give lots of commands, you end up with a matrix which is a product of the previous commands.

Why is our square a rectangle?

We return to the original question now. Why is our square drawing incorrectly? It turns out that this problem is due to the size of our window. In our program, we didn’t specify a projection matrix so the default one is used. This is identity matrix, and is equivalent to calling

glOrtho(1,-1,-1,1,-1,1);

So, our square is within the viewing volume and is drawn. It looks exactly as it did before the transformation. Now the square is mapped to the screen, in particular our window. However, our window isn’t square in shape, it is a rectangle. So, our square is squashed vertically to make it fit. This is bad.

How do we solve this? Well, we could change the shape of the window, but that isn’t getting to the root of the problem. The answer is to specify a projection transformation that maps our square to a rectangle stretched in the x-direction by the right amount so that when it is mapped to the screen it appears correctly. The right amount is clearly the aspect ratio of the screen. We will call

glOrtho(aspectRatio,- aspectRatio, -1.0d, 1.0d, 1.0d, -1.0d);

The source code for the fixed square is here.

Advertisements

One thought on “Drawing a proper square using the Projection Matrix

  1. I dont use LWJGL, however, this post saved me when I was staring against a blank wall (actually, a rectangle that ought to have been a square) after several hours of fruitless debugging of my OpenGL, checking and rechecking my buffers and projection transforms. When I read your post, I felt like kicking myself.

    Anyway, nice post, nice blog. Also, thanks!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s