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.
The section that we need to closely look at is the projection matrix. This is a 4*4 matrix mapping vertices from . Any vertices that are mapped to the cube bounded by
are eventually drawn on screen.
So, we are interested in which section of 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.
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 );
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.
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
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);
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
After this command is called, it is good practice to call
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
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.