Tag: LWJGL

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

Draw a square

Let’s draw a shape on our newly created Display. Most of the basic OpenGL commands in LWJGL are contained in the GL11 class. They are all static methods due to the procedural nature of the specification. To create points/lines/shapes we specify a number of vertices wrapped in a context.

Vertices

…or points. Whatever you want to call them. These are simply a location in 3-dimensional space specified by 3 co-ordinates (the familiar x, y and z). Actually OpenGL handles vertices using 4 co-ordinates per point, but more about that later…

A vertex can be recorded using floats or doubles as the coordinates:

glVertex3f(float x, float y, float z)
glVertex3d(double x, double y, double z)

Contexts

There are many different contexts to choose from (GL_POINTS, GL_LINES, GL_LINE_LOOP to name a few). The context describes how to associate the vertices that are specified. For example, GL_LINES connects the points with lines, GL_TRIANGLE connects them in triangles etc. The context is specified like so:

glBegin(GL_LINE_LOOP);
		glVertex3f(-0.5f, -0.5f, 0f);
		glVertex3f(-0.5f, 0.5f, 0f);
		glVertex3f(0.5f, 0.5f, 0f);
		glVertex3f(0.5f, -0.5f, 0f);
glEnd();

The specific rules about each context is contained in any good OpenGL reference. A vertex call is meaningless without a context — the vertex cannot be stored and used later so it must always appear along with the information about how it is to be used.

We now have the prerequisite tools to create a square on screen. The code for this example is hosted here.

It's not a square!
It's not a square!

Simple! You probably have a lot of questions now:

1) Why has OpenGL rendered a rectangle instead of a square?
2) How did I know which coordinates to use so that the rectangle appears on the screen?

These questions will be answered in my next post.

HelloWorld using LWJGL

Let’s get started! To create a window with a canvas to draw on (called a display) we use the following code:

import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;

public class HelloWorld {
	public static void main (String args[]){
		try {
			Display.setTitle("Hello World");
			Display.create();
		} catch (LWJGLException e) {
			e.printStackTrace();
		}
	}
}

Easy! This creates a window the size of your current desktop with an area where the shapes we will later draw with OpenGL will appear. Unfortunately this window cannot be closed using the traditional ‘x’ button, you must shut down the java process itself. The next thing to do will be to add the close mechanism.

Add the following to the bottom of the main method:

while(!Display.isCloseRequested()){
			Thread.sleep(100);		
		}
		Display.destroy();

Again, what this code does is obvious. We can see that the developers of LWJGL have tried to ensure that getting to the stage where we can start drawing shapes is as painless as possible.

OpenGL, JOGL and LWJGL

OpenGL is an API specification for 2D and 3D graphics. It is implemented by hardware vendors on graphics cards or can be implemented in software. Hardware vendors pick and choose which operations are to be hardware accelerated.

All implementations can be accessed through a library of procedural C functions. More information about OpenGL can be found for free in the following book “The Red Book”. My favourite book on the subject is more mathematical than the previous one and is for sale here.

I like to develop in Java and hence use the JOGL library which translates the C functions into Java functions via the JNI. The functions are not wrapped, appearing as they would in C code. It would be nice to have a library written in an object oriented fashion (as is available for C++ here) but this is not available currently as far as I know. JOGL is available here.

LWJGL stands for the Light-Weight Java Gaming Library and is another library which gives you a lot of useful functions for writing games including windowing tools and precise timers. It is available here.