Monday 25 February 2013

Using Quaternion to Perform 3D rotations

The title of this tutorial is short and sweet.  The tutorial itself will not be.  First, an introduction.  Quaternions are the things that scare all manner of mice and men.  They are the things that go bump in the night.  They are the reason your math teacher gave you an F.  They are all that you have come to fear, and more.  Quaternions are your worst nightmare.

Okay, not really.  They aren't actually that hard.  I just wanted to scare you.  Quaternions have become a popular tool in 3d game development - and for a good reason.  Once you understand them, quaternions are really pretty amazing.  Also, unlike the other tutorials, I'm going to more or less be assuming that you know nothing about quaternion math in this tutorial.  Here are the basics of a quaternion:
A quaternion represents two things.  It has an x, y, and z component, which represents the axis about which a rotation will occur.  It also has a w component, which represents the amount of rotation which will occur about this axis.  In short, a vector, and a float.  With these four numbers, it is possible to build a matrix which will represent all the rotations perfectly, with no chance of gimbal lock.  (I actually managed to encounter gimbal lock with quaternions when I was first coding them, but it was because I did something incorrectly.  I'll cover that later).  So far, quaternions should seem a lot like the axis angle representation.  However, there are some large differences, which start....now.

A quaternion is technically four numbers, three of which have an imaginary component.  As many of you probably know from math class, i is defined as sqrt(-1).  Well, with quaternions, i = j = k = sqrt(-1).  The quaternion itself is defined as q = w + xi + yj + zk.  w, x, y, and z are all real numbers.  The imaginary components are important if you ever have a math class with quaternions, but they aren't particularly important in the programming.  Here's why: we'll be storing a quaternion in a class with four member variables: float w, x, y, z;.  We'll be ignoring i, j, and k, because we never liked them anyway.  Okay, so we're actually just ignoring them because we don't need them.  We'll define our quaternions (w, x, y, z).

You may have guessed by now that w is the amount of rotation about the axis defined by <x, y, z>.  (Warning: the math is going to start getting pretty heavy.  I'll explain the quaternion specific stuff, though).  Much like unit vectors are necessary for much of what is done in a 3d engine, with lighting, back-face culling, and the like, unit quaternions are needed to perform the operations we'll be doing below.  Luckily, normalizing a quaternion isn't much harder than normalizing a vector.  The magnitude of a quaternion is given by the formula magnitude = sqrt(w2 + x2 + y2 + z2).  For the unit quaternions, the magnitude is one.  Which means that, already, an optimization is in order.  Floating-point numbers aren't perfect.  Eventually, you'll lose accuracy with them, and you may want to check to see if you need to re-normalize your unit quaternion, to prevent math errors.  This can be done by checking the magnitude, which will need to be one... but if you're checking to see if it's one, there is no need for the sqrt() at all (sqrt(1) = 1).  So, if (w2 + x2 + y2 + z2) is more than a certain tolerance away from 1, you'll know that your quaternion needs to be normalized, and you will have saved taking a square root in the cases when it is within tolerance.  Every few clock cycles count.  But if you ever need to actually normalize a quaternion, here's how it would be done:
magnitude = sqrt(w2 + x2 + y2 + z2)
w = w / magnitude
x = x /  magnitude
y = y / magnitude
z = z / magnitude

(Yes, I'm familiar with how to do that in C++, including the /= operator.  I'm trying to keep this easy to read mathematically.)  Performing the above operations will ensure that the quaternion is a unit quaternion.  However, they are (somewhat) expensive in CPU time and should not be called unless needed - which they usually aren't.  There are many different possible unit quaternions - they actually describe a hyper-sphere, a four dimensional sphere.  Don't try to visualize it; your head will explode.  But because the end points for unit quaternions all lay on a hyper-sphere, multiplying one unit quaternion by another unit quaternion will result in a third unit quaternion.  I guess now it's time for me to describe quaternion multiplication.

One of the most important operations with a quaternion is multiplication.  If you are using C++ and coding your own quaternion class, I would highly suggest overloading the * operator to perform multiplications between quaternions.  Here is how the multiplication itself is performed: (sorry about the HTML subscripts, I know they suck)
Let Q1 and Q2 be two quaternions, which are defined, respectively, as (w1, x1, y1, z1) and (w2, x2, y2, z2).
(Q1 * Q2).w = (w1w2 - x1x2 - y1y2 - z1z2)
(Q1 * Q2).x = (w1x2 + x1w2 + y1z2 - z1y2)
(Q1 * Q2).y = (w1y2 - x1z2 + y1w2 + z1x2)
(Q1 * Q2).z = (w1z2 + x1y2 - y1x2 + z1w2

It is very, very, very important for you to note that Q1 * Q2 is NOT equal to Q2 * Q1.  Quaternion multiplication is not commutative.  If you don't remember this, it will give you trouble later, and it won't be easy to spot the cause.  So do yourself a favor and remember it.  I didn't the first time I read it.  I'm speaking from experience here.

What does the quaternion multiplication mean?  To tell you the truth, this is where quaternions start to become beautiful.  Until now, they've been a bunch of math which wasn't difficult, but might have been annoying.  Now, quaternions will become useful.  Remember that a quaternion stores an axis and the amount of rotation about the axis.  So, with that, after I give you the matrix for rotations with quaternions, you would be able to rotate an object over some arbitrarily defined axis by some arbitrary amount, without fear of gimbal lock.  However, changing the rotation would be a trickier manner.  To change the rotation represented by a quaternion, a few steps are necessary.  First, you must generate a temporary quaternion, which will simply represent how you're changing the rotation.  If you're changing the current rotation by rotating backwards over the X-axis a little bit, this temporary quaternion will represent that.  By multiplying the two quaternions (the temporary and permanent quaternions) together, we will generate a new permanent quaternion, which has been changed by the rotation described in the temporary quaternion.  At this point, it's time for a good healthy dose of pseudo-code, before you get so confused we have to bring in the EMTs to resuscitate you.
/*to keep with the conventions I've followed in some posts on cprogramming.com, I
will call the temporary quaternion described above local_rotation.  I'll be calling the
permanent quaternion described above total.*/

// generate local_rotation (details later)
total = local_rotation * total  //multiplication order matters on this line
Before I try to explain that any more, I need to teach you how to generate local_rotation.  You'll need to have the axis and angle prepared, and this will convert them to a quaternion.  Here's the formula for generating the local_rotation quaternion.
//axis is a unit vector
local_rotation.w  = cosf( fAngle/2)
local_rotation.x = axis.x * sinf( fAngle/2 )
local_rotation.y = axis.y * sinf( fAngle/2 )
local_rotation.z = axis.z * sinf( fAngle/2 )

Then, just multiply local_rotation by total as shown above.  Since you'll be multiplying two unit quaternions together, the result will be a unit quaternion.  You won't need to normalize it.  At this point, I feel the need to once again point out that quaternion multiplication is not commutative, and the order matters.
Hang in there; we're almost done.  All that is left for this tutorial is generating the matrix from the quaternion to rotate our points. 

w2+x2-y2-z2
2xy-2wz
2xz+2wy
0
2xy+2wz
w2-x2+y2-z2
2yz+2wx
0
2xz-2wy
2yz-2wx
w2-x2-y2+z2
0
0
0
0
1

And, since we're only dealing with unit quaternions, that matrix can be optimized a bit down to this:
1-2y2-2z2
2xy-2wz
2xz+2wy
0
2xy+2wz
1-2x2-2z2
2yz+2wx
0
2xz-2wy
2yz-2wx
1-2x2-2y2
0
0
0
0
1

Amazing.  Well, maybe not amazing, but pretty cool.  You'll regenerate these matrices each frame.  Most importantly, you won't get gimbal lock if you follow my directions.  One thing that I forgot to mention earlier - you'll need to initialize your total quaternion to the value (1,0,0,0).  This represents the "initial" state of your object - no rotation.

Interpolating between two orientations using quaternions is also the smoothest way to interpolate angles.  It's done via a method known as SLERP, or Spherical Linear intERPolation.  I haven't encountered a need for this yet, so I haven't researched it, but perhaps someday I'll research it and write a tutorial about it to add to this series.  If anyone needs the information, feel free to contact me, and clue me in that someone actually read this and wants more.  That's all for now, have fun coding Quake.  Or Doom.  Make sure to give me a copy of it when you're done.

Using 3D Rotation Matrices in Practice

So, now that you more or less know how to rotate a point in any arbitrary manner in three dimensions, generating matrices along the way, it's time to learn what you should do with each of these matrices.  This tutorial will cover just that: the world matrix, and the camera matrix.
First, a quick review of what you should know from reading other tutorials.  A point will go through multiple transformations before it finally makes it to the screen.  The point will start out in model space.  Model space is just that - the space in which the model was created.  Then, once the model has been rotated and translated to the appropriate spot in the world, the point will be in world space.  When all of the points are in world space, your Quake 4 level will be all laid out and full of monsters, but there is one more step that the points have to take in three dimensional space before they will be ready to be converted to two dimensional space and displayed on the screen.  The points need to all be transformed into camera space, also known as view space.  In a 3D game, instead of the camera moving around the world, the world moves around the camera, and the camera always stays at the origin.  So, when you rotate the camera, you are really rotating the world backwards.  When you move the camera, you're translating the world around you.  A matrix will be built to represent this, and after this camera matrix transforms the world space points, they will be in camera space.  It is then a fairly simple matter to convert them to screen space and display them on the screen.  Many APIs will do this stuff for you, but that's what is going on in the background and you'll need to understand it.
The first thing that needs to happen to a point in model space is that it needs to be rotated while it is still in model space.  Then, it will need to be translated (moved) into world space.  It may also need to be scaled.  This can all be done with one matrix.  After you generate the matrix, it can be used to transform every point in a model into world space.  You'll need to make a new one of these matrices for each model, though.  This will be the "world matrix" and will be set many times each frame.

The next thing that happens to a point is rotating and moving according to the camera's position and orientation.  This can be a stumbling point for a lot of people.  The first thing to remember is that the view matrix need be set only once per frame.  All points will be transformed by it.  Depending on what you need your camera to do, there are several different ways of generating it.  Check your API documentation - chances are fairly strong that there will be a built-in way of generating a camera matrix by just passing a function a few vectors.  Otherwise, there are numerous tutorials online for different methods of building this matrix.

To convert a 3d point to a 2d point may seem to be a non-trivial matter.  You are, in essence, attempting to project a three dimensional point onto the correct spot of a two dimensional plane.  For points farther in the distance, you'll want to create a perspective illusion, and make all points converge to one point in the center of the screen.  After all the math I've hit you with, you're probably preparing yourself for something even more complex.  Wrong.  The hard math doesn't come until the next tutorial.  Just do this:
Screen_X = (viewSpace_X / viewSpace_Z)+(SCREEN_WIDTH/2)
Screen_Y = (viewSpace_Y / viewSpace_Z)+(SCREEN_HEIGHT/2)
Yeah, it's actually that easy.

For a FPS, it's probably safe to do transformations with Euler angles when going from model space into world space.  Chances are good that the monsters in your game will only be rotating about their Y-axis, in which case you're 100% safe with Euler angles.  However, the camera will not be restricted like this.  The camera will rotate on two, or possibly three axes, and thus will be subject to gimbal lock if you use Euler angles.  So, for the camera, either use an axis-angle representation, or quaternions - the mind-blowing, really cool subject of the next tutorial.

Part Three: 3D Rotation About an Arbitrary Axis

The previous method of doing the rotations is called using Euler angles.  It's probably the simplest way of doing rotations, but it has some problems.  The biggest problem is called gimbal lock.  You may or may not have already encountered this if you wrote code according to the last tutorial.  If you encountered it and noticed it, without knowing what it was, you may have spent hours trying to figure out where you went wrong in your code, carefully comparing every line of your code to the tutorial, trying to find the difference.  If that happened, I'm sorry.  There is nothing wrong with your code; there is something wrong with the math.  If you'll recall, I told you two very important things, which you probably didn't connect in the last tutorial.  1) Matrix multiplication is not commutative.  A*B != B*A.  2) We generated matRotationTotal by doing matRotationX * matRotationY * matRotationZ.  If there was nothing wrong with the math, you should have been able to do matRotationY*matRotationZ*matRotationX, or any other order, and gotten exactly the same results.  But you wouldn't.  This problem is the root cause of gimbal lock.  Trying to visualize this might blow your mind, so if you don't understand the next paragraph, don't worry too much.  Just remember that Gimbal Lock happens when one axis gets rotated before another axis, and the axes are no longer mutually perpendicular.  It can be a large problem, or it can go unnoticed, depending on the application.
We multiplied our matrices in the order matRotationX * matRotationY * matRotationZ.  It seemed to work, and for the most part, it did.  But if you think about it carefully, you'll realize that, as you move an object in 3d space, all three axes change at once.  They remain mutually perpendicular to one another.  In the program, however, we're rotating the object over the X-axis first.  That rotates the Y and Z-axes.  Then we rotate over the Y axis, but since we've already rotated over the X-axis, the rotation on the Y-axis only changes the location of the Z-axis.  The rotation of the Z-axis does not change the location of either of the other two axes.  Huge problem if you need to rotate on all three axes, because one axis can literally end up on top of another axis!  (Just in the math.  It can't do that in real life, meaning our representation is not accurate)

Luckily for you, many math geniuses have dealt with this problem.  There was a famous man named Euler, whom you'll hear mentioned in Calculus.  He determined that any series of rotations in three dimensional space can be represented as a single rotation over an arbitrary axis.  For this representation, called angle/axis representation, you'll need to store the arbitrary axis about which you are rotating, and the amount by which you are rotating.

Now for the cameo by Charles, who was kind enough to write the following section for me: Arbitrary axis rotation by Charles Thibault

I am going to describe the calculations I perform in order to perform rotations about an arbitrary axis.  These calculations are NOT the matrix form of the rotations.  Up to this point you know you can combine matrices into a single transformation.  The single transformation matrix involves about 29 multiplication operations and 9 addition operations, whereas completely rotating a vector using my transformations (meaning calling my RotateVector function TWICE, once over the Y axis then once over the Strafe vector) entails about ten percent more multiplications and about twice as many addition operations (32 multiplications for two RotateVector calls, and 18 addition operations for two RotateVector calls).

How do you actually perform a rotation about an arbitrary axis?  Well firstly you must understand rotations in two dimensions, because the concept stays the same on an arbitrary plane.  I'm going to make this as short and sweet as possible.  Instead of rotating the X and Y components of a vector, the X is really the component of the vector you are trying to rotate Perpendicular to the vector that is the normal to the plane.  Likewise the Y is really the cross product between the vector you are trying to rotate about and the actual vector being rotated. 

Steps to rotate a vector:
-Calculate the Perpendicular component, multiply it by the cosine of the angle you are trying to rotate through
-Calculate the cross product between the vector you are trying to rotate about and the vector you are rotating, multiply it by the sine of the angle
-Add the results together
-Add the component of the vector you are trying to rotate that is parallel to the vector you are rotating about

Note it is not totally necessary to calculate the parallel component if the vector you are rotating and the vector you are rotating about are already orthogonal.  I do it in all cases anyway to avoid any mishaps and make sure it is mathematically correct, but it seems to work both ways.  Plus, by leaving it in the code you can rotate vector A about vector P even if A and P are not orthogonal.  (orthogonal means mutually perpendicular to one another)

The rest of this will, once again, be written by me (Confuted).

There's still the problem of performing the actual rotation about your arbitrary axis.  Luckily, this can also be done with a matrix.  Again, I'm not going to derive it, I'm going to spoon feed it to you.  You can thank me later.
Left Handed *
Right Handed *
tX2 + c
tXY - sZ
tXZ + sY
0
tXY+sZ
tY2 + c
tYZ - sX
0
tXZ - sY
tYZ + sX
tZ2 + c
0
0
0
0
1
tX2 + c
tXY + sZ
tXZ - sY
0
tXY-sZ
tY2 + c
tYZ + sX
0
tXZ + sY
tYZ - sX
tZ2 + c
0
0
0
0
1
Where c = cos (theta), s = sin (theta), t = 1-cos (theta), and <X,Y,Z> is the unit vector representing the arbitrary axis
Now, you can replace matRotationTotal with this matrix, and completely eliminate matRotationX, matRotationY, and matRotationZ.  Of course, there is extra math involved elsewhere.  But by using the axis/angle representation for your rotations, it is possible to avoid gimbal lock.  However, it's also possible to still suffer from it, if you do something incorrectly.  In the next tutorial, I'll talk about some of the uses for the things I've been saying, and after that, brace yourself for the exciting and strange world of quaternions.  If you don't have a headache from thinking too much yet, you probably will after the quaternions.

Rotations in Three Dimensions: 3D Rotation Matrices

Okay, so I assume going into this tutorial that you know how to perform matrix multiplication.  I don't care to explain it, and it's available all over the Internet.  However, once you know how to perform that operation, you should be good to go for this tutorial.
The way presented for doing rotations in the last tutorial wasn't really a good one.  It works just fine in two dimensions, but as soon as you want to rotate around the X or Y-axes, it becomes more difficult.  Sure, it's easy to make equations that will represent a rotation on any one of those axes, but just go ahead and try to make equations that will represent changes on three axes at once.  If you manage to pull that off, make sure to let us know.  Meanwhile, I'll present a way to do the rotations with matrices.
Matrices might seem scary, especially to someone who has never used them before.  However, they really aren't too difficult to use.  They're also very powerful.  The first thing to note is that it is possible to use a vector to specify a point in 3d space.  Basically, every point is a displacement from the origin by a certain amount, which is described by the vector.  Vectors are useful for lots of other things as well, and perhaps someday I'll write about some of those.  Meanwhile, we'll just use them for storing points.

A vector can be multiplied by a matrix, and after the multiplication, you'll get a new vector.  This may seem useless, but when you multiply the vector by the right matrix, you'll get a point that has been transformed by the matrix.  This can mean rotated on any axis (including arbitrary ones!  that will come later), translated, or both.  You see, the thing with matrices is this:  If you have one matrix representing rotation on the X axis, and another matrix representing rotation on the Y axis, you can multiply them together to get a new matrix which represents the rotation on both axes.  However, in case you didn't catch this in any tutorials you read about matrices, please note that if A and B are matrices, A*B != B*A.  This will cause us some problems later, but for now, just keep it in the back of your mind.

I'm not going to derive these matrices that I'm about to give you here.  One reason is that it's been done other places.  Another reason is that it would involve me explaining a lot of things that have also been explained better elsewhere.  The most important reason is because I can't.  However, that doesn't matter.  You don't need to be able to derive these matrices; you just need to know how to use them.  You'll also need to know which coordinate system you're using: left-handed or right-handed.  OpenGL uses right-handed coordinates; DirectX uses left-handed coordinates.  This is also explained in detail elsewhere, so I won't go into it.  If you're not using OpenGL or DirectX, either figure out what your API uses, or if you're writing your own or something, pick one and stick with it.  There will be no turning back.
Left Handed
Right Handed
X Rotation *
1
0
0
0
0
cos (phi)
-sin (phi)
0
0
sin (phi)
cos (phi)
0
0
0
0
1
Y Rotation *
cos (theta)
0
sin (theta)
0
0
1
0
0
-sin (theta)
0
cos (theta)
0
0
0
0
1
Z Rotation *
cos (psi)
-sin (psi)
0
0
sin (psi)
cos (psi)
0
0
0
0
1
0
0
0
0
1
X Rotation *
1
0
0
0
0
cos (phi)
sin (phi)
0
0
-sin (phi)
cos (phi)
0
0
0
0
1
Y Rotation *
cos (theta)
0
-sin (theta)
0
0
1
0
0
sin (theta)
0
cos (theta)
0
0
0
0
1
Z Rotation *
cos (psi)
sin (psi)
0
0
-sin (psi)
cos (psi)
0
0
0
0
1
0
0
0
0
1
* Where (phi) represents the rotation about the X axis, (theta) represents the rotation about the Y axis, and (psi) represents the rotation about the Z axis
Those really aren't as complicated as they look.  And for those of you wondering why I didn't store all of those in 3*3 matrices, just hold on ;)  That's coming later.  For the purposes of this tutorial, I'm going to try to avoid picking a coordinate system, so that it will be equally useful for both OpenGL and DirectX programmers.  We'll call the rotation matrix for the X axis matRotationX, the rotation matrix for the Y axis matRotationY, and the rotation matrix for the Z axis matRotationZ.

By multiplying the vector representing a point by one of these matrices (with the values properly filled in), you can rotate the point around any axis.  However, you'll probably want to allow rotation about all three axes.  You could multiply the vector by one matrix, then multiply it by the next matrix, then multiply it by the next matrix... but that would produce some very slow code, because you would be performing far too many operations for each point.  Matrices can be combined, which will save you some very valuable time in your code.  We'll call the matrix which represents all your rotations matRotationTotal, and here's the way to generate it:
matRotationTotal = matRotationX * matRotationY * matRotationZ

After that, you can simply transform each point with matRotationTotal, and the point will be rotated about all three axes.  When you need to change the amount of rotation, rebuild matRotationX, matRotationY, and matRotationZ, and then multiply them together to get the new matRotationTotal.  Pretty easy, and it gets points rotating around in three dimensions.  (Note: If you don't know how to multiply matrices, or if you don't know how to multiply a vector by a matrix, consult a basic tutorial and matrix math.  To give you a hint, a vector can be represented by a 1*4 matrix...)

That wraps up this tutorial.  Go implement this if you think you're ready.  Alternatively, read the next tutorial first.

Monday 18 February 2013

Part One: The Basics of Rotations in Three Dimensions

The purpose of this tutorial series is to explain the math involved behind rotating points in three dimensions. It will start out by describing how to use a rotation matrix to rotate a point over the Z-axis, simply because this is the easiest rotation to visualize and implement. It's just like a clock hand going around. Then, the tutorials will move on to give you the matrices for rotation over the x and y axes, tell you how to use them, and then give you a matrix which will allow rotations around an arbitrary axis. Translation matrices will also be covered. There will be talk of cameras and simply rotating objects, and then will move on to quaternions, which are the solution to the problem of gimbal lock, which is encountered when using Euler angles. I may mention some other things, or I may not. The current plan is to type these until I get sick of it.
This first tutorial will be short - mainly because there are so many sources available online and in print which teach about vectors, three-dimensional axes, matrices, vertices, trig and the like. I'll be glad to let you consult those for the basic information that I do not provide here. Please understand the concept of vectors and understand that a triangle has three vertices and any more complex polygon can be made from several triangles before proceeding with these tutorials. An understanding of trigonometry will also be essential for much of the discussion.

Rotation About The Z Axis:
Rotation about the Z-axis really isn't difficult. If you are strong with trig, you can probably work out the equations necessary to rotate a point around the Z-axis. After all, it's taught in math class when you learn parametric equations. Suppose you're rotating the point (1,0) 90 degrees over the Z-axis. When you're rotating over the Z-axis, the Z component won't change, so you can obviously ignore it in your calculations for this axis. It will be a good way to ease yourself in. Anyway, you're rotating the point (1,0) about the Z-axis 90 degrees. A bit of visualization will tell you that you're going to end up with the point (0,1), but you can't just do that in a program, and you can't even do it in your head for stranger angles, like 37 degrees. Anyway, by looking at a triangle in a unit circle, we can determine the following formulas for the rotation.
Xrotated = hypotenuse * cos(theta)
Yrotated = hypotenuse * sin(theta)
Where Xrotated is the X coordinate of the point after the rotation, Yrotated is the Y coordinate of the point after the rotation, theta is the number of degrees/radians we are rotating the point about the Z axis, and hypotenuse is the distance between the point and the origin. The hypotenuse can be easily calculated using the Pythagorean theorem, also known as the Distance Formula. Extending this theorem to three dimensions isn't difficult, but I'll get to that later. Here's how to calculate the hypotenuse in two dimensions:
hypotenuse= sqrt( x2 + y2 )

So, that's pretty easy, and you could use it to make a program which displayed an analog clock, for example. You can also use that to draw a circle. All you have to do for the circle is rotate the point in one-degree increments (other numbers of degrees will work) and display a point at every location, and draw lines between points. I'll leave that to the reader, because it's boring. You want 3d math! You want matrices! You want to make Quake! Well, that's coming. Hang in there. More will be revealed in Part Two.
Oh, and in case you want my contact information, here it is:
I'm Matthew Hansen, known on Cprogramming.com as Confuted. I'm currently residing in Traverse City, Michigan.

Next: 3D Rotation Matrices
Make Free Flash Website

Orthographic and Perspective Projections in OpenGL

Note: All code beginning with the next lesson has been created and compiled using Microsoft Visual Studio .NET Enterprise Architect, not Visual Studio 6.0

Welcome to the seventh lesson in my OpenGL series! In this lesson we will learn about Projections and even put them to some simple use to see them in action. Some of you may have realized that we've used projection transformations in code before, and you're right, we have. The difference is now we will discuss how they work, and then demonstrate these concepts.
OpenGL consists of two general classes of projection transformations: orthographic (parallel) and perspective. So let's get into detail on both of these!

Orthographic Projections

Orthographic, or parallel, projections consist of those that involve no perspective correction. There is no adjustment for distance from the camera made in these projections, meaning objects on the screen will appear the same size no matter how close or far away they are.

Traditionally this type of projection was included in OpenGL for uses in CAD, or Computer Aided Design. Some uses of orthographic projections are making 2D games, or for creating isometric games. To setup this type of projection we use the OpenGL provided glOrtho() function.
glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far);
left and right specify the x-coordinate clipping planes, bottom and top specify the y-coordinate clipping planes, and near and far specify the distance to the z-coordinate clipping planes. Together these coordinates provide a box shaped viewing volume.

Since orthographic projections are commonly used in 2D scenes the Utility Library provides an additional routine to set them up for scenes that won't be using the z-coordinate.
gluOrtho2D
(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top);

Perspective Projections

Although orthographic projections can be interesting, perspective projections create more realistic looking scenes, so that's what you will most likely be using most often. In perspective projections, as an object gets farther from the viewer it will appear smaller on the screen- an effect often referred to as foreshortening. The viewing volume for a perspective projection is a frustum, which looks like a pyramid with the top cut off, with the narrow end toward the user.

There is a few different ways you can setup the view frustum, and thus the perspective projection. The first we will look at is as follows:
void glFrustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far);
Using glFrustum enables you to specify an asymmetrical frustum, which can be very useful in some instances, but isn't what you typically want to do. For a different solution we again turn to the Utility Library:
void gluPerspective(GLdouble fov, GLdouble aspect, GLdouble near, GLdouble far);
fov specifies, in degrees, the angle in the y direction that is visible to the user; aspect is the aspect ratio of the scene, which is width divided by the height. This will determine the field of view in the x direction.

Ok, so let's look at some code. This code is taken right from OpenGL Game Programming. Take the time to study and manipulate the code, then you will be ready for lesson 8!
// OpenGLProjectionExample.cpp : Defines the entry point for the application.
//

#define WIN32_LEAN_AND_MEAN

#pragma comment(lib, "opengl32.lib")
#pragma comment(lib, "glu32.lib")
#pragma comment(lib, "glaux.lib")
#pragma comment(linker, "/subsystem:windows")

#include "stdafx.h"
#include <windows.h>                                    // standard Windows app include
#include <winuser.h>          // Windows constants
#include <gl/gl.h>                                              // standard OpenGL include
#include <gl/glu.h>                                             // OpenGL utilties
#include <glut.h>                                       // OpenGL utilties

#define WND_CLASS_NAME  "OpenGL Window Class"


/*************************** Constants and Macros ***************************/
const int   SCREEN_WIDTH    = 500;
const int   SCREEN_HEIGHT   = 500;
const int   SCREEN_BPP      = 32;
const bool  USE_FULLSCREEN  = false;
const char  *APP_TITLE      = "Projections";


/********************************* Globals **********************************/
HDC       g_hdc;                                                                  // global device context
HGLRC     g_hrc;                  // global rendering context
BOOL      g_isFullscreen = TRUE;  // toggles fullscreen and windowed display
BOOL      g_isActive = TRUE;      // false if window is minimized
HWND      g_hwnd = NULL;          // handle of our window
HINSTANCE g_hInstance;            // application instance


/******************************** Prototypes ********************************/
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);

BOOL    SetupWindow(const char *title, int width, int height, int bits, bool isFullscreen);
BOOL    KillWindow();

GLvoid  ResizeScene(GLsizei width, GLsizei height);
BOOL    InitializeScene();
BOOL    DisplayScene();
BOOL    Cleanup();

void    UpdateProjection(GLboolean toggle = GL_FALSE);


/*****************************************************************************
 WinMain()

 Windows entry point
*****************************************************************************/
int WINAPI WinMain(HINSTANCE g_hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
  MSG   msg;       // message
  BOOL  isDone;    // flag indicating when the app is done

  // if the window is set up correctly, we can proceed with the message loop
  if (SetupWindow(APP_TITLE, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, USE_FULLSCREEN))
    isDone = FALSE;
  // otherwise, we need to never enter the loop and proceed to exit
  else
    isDone = TRUE;

  // main message loop
  while (!isDone)
  {
    if(PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE))
    {
      if (msg.message == WM_QUIT)   // do we receive a WM_QUIT message?
      {
        isDone = TRUE;              // if so, time to quit the application
      }
      else
      {
        TranslateMessage(&msg);     // translate and dispatch to event queue
        DispatchMessage(&msg);
      }
    }

    // don't update the scene if the app is minimized
    if (g_isActive)
    {
      // update the scene every time through the loop
      DisplayScene();
      // switch the front and back buffers to display the updated scene
      SwapBuffers(g_hdc);
    }
  }

  Cleanup();
  KillWindow();

  return msg.wParam;
} // end WinMain()


/*****************************************************************************
 WndProc()

 Windows message handler
*****************************************************************************/
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  switch(message)
  {
  case WM_ACTIVATE:  // watch for the window being minimized and restored
    {
      if (!HIWORD(wParam))
      {
        // program was restored or maximized
        g_isActive = TRUE;
      }
      else
      {
        // program was minimized
        g_isActive=FALSE;
      }

      return 0;
    }

  case WM_SYSCOMMAND:  // look for screensavers and powersave mode
    {
      switch (wParam)
      {
      case SC_SCREENSAVE:     // screensaver trying to start
      case SC_MONITORPOWER:   // monitor going to powersave mode
        // returning 0 prevents either from happening
        return 0;
      default:
        break;
      }
    } break;

  case WM_CLOSE:    // window is being closed
    {
      // send WM_QUIT to message queue
      PostQuitMessage(0);

      return 0;
    }

  case WM_SIZE:
    {
      // update perspective with new width and height
      ResizeScene(LOWORD(lParam), HIWORD(lParam));
      return 0;
    }

  case WM_CHAR:
    {
      switch (toupper(wParam))
      {
      case VK_SPACE:
        {
          UpdateProjection(GL_TRUE);
          return 0;
        }
      case VK_ESCAPE:
        {
          // send WM_QUIT to message queue
          PostQuitMessage(0);
          return 0;
        }
      default:
        break;
      };
    } break;

  default:
    break;
  }

  return (DefWindowProc(hwnd, message, wParam, lParam));
} // end WndProc()


/*****************************************************************************
 SetupWindow()

 Create the window and everything else we need, including the device and
 rendering context. If a fullscreen window has been requested but can't be
 created, the user will be prompted to attempt windowed mode. Finally,
 InitializeScene is called for application-specific setup.

 Returns TRUE if everything goes well, or FALSE if an unrecoverable error
 occurs. Note that if this is called twice within a program, KillWindow needs
 to be called before subsequent calls to SetupWindow.
*****************************************************************************/
BOOL SetupWindow(const char *title, int width, int height, int bits, bool isFullscreen)
{
  // set the global flag
  g_isFullscreen = isFullscreen;

  // get our instance handle
  g_hInstance = GetModuleHandle(NULL);

  WNDCLASSEX  wc;    // window class

  // fill out the window class structure
  wc.cbSize         = sizeof(WNDCLASSEX);
  wc.style          = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
  wc.lpfnWndProc    = WndProc;
  wc.cbClsExtra     = 0;
  wc.cbWndExtra     = 0;
  wc.hInstance      = g_hInstance;
  wc.hIcon          = LoadIcon(NULL, IDI_APPLICATION);  // default icon
  wc.hIconSm        = LoadIcon(NULL, IDI_WINLOGO);      // windows logo small icon
  wc.hCursor        = LoadCursor(NULL, IDC_ARROW);      // default arrow
  wc.hbrBackground  = NULL;     // no background needed
  wc.lpszMenuName   = NULL;     // no menu
  wc.lpszClassName  = WND_CLASS_NAME;

  // register the windows class
  if (!RegisterClassEx(&wc))
  {
    MessageBox(NULL,"Unable to register the window class", "Error", MB_OK | MB_ICONEXCLAMATION);

    // exit and return FALSE
    return FALSE;
  }

  // if we're in fullscreen mode, set the display up for it
  if (g_isFullscreen)
  {
    // set up the device mode structure
    DEVMODE screenSettings;
    memset(&screenSettings,0,sizeof(screenSettings));

    screenSettings.dmSize       = sizeof(screenSettings);
    screenSettings.dmPelsWidth  = width;    // screen width
    screenSettings.dmPelsHeight = height;   // screen height
    screenSettings.dmBitsPerPel = bits;     // bits per pixel
    screenSettings.dmFields     = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;

    // attempt to switch to the resolution and bit depth we've selected
    if (ChangeDisplaySettings(&screenSettings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
    {
      // if we can't get fullscreen, let them choose to quit or try windowed mode
      if (MessageBox(NULL, "Cannot run in the fullscreen mode at the selected resolution\n"
                           "on your video card. Try windowed mode instead?",
                           "OpenGL Game Programming",
                           MB_YESNO | MB_ICONEXCLAMATION) == IDYES)
      {
        g_isFullscreen = FALSE;
      }
      else
      {
        return FALSE;
      }
    }
  }

  DWORD dwExStyle;
  DWORD dwStyle;

  // set the window style appropriately, depending on whether we're in fullscreen mode
  if (g_isFullscreen)
  {
    dwExStyle = WS_EX_APPWINDOW;
    dwStyle = WS_POPUP;           // simple window with no borders or title bar
    ShowCursor(FALSE);            // hide the cursor for now
  }
  else
  {
    dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
    dwStyle = WS_OVERLAPPEDWINDOW;
  }

  // set up the window we're rendering to so that the top left corner is at (0,0)
  // and the bottom right corner is (height,width)
  RECT  windowRect;
  windowRect.left = 0;
  windowRect.right = (LONG) width;
  windowRect.top = 0;
  windowRect.bottom = (LONG) height;

  // change the size of the rect to account for borders, etc. set by the style
  AdjustWindowRectEx(&windowRect, dwStyle, FALSE, dwExStyle);

  // class registered, so now create our window
  g_hwnd = CreateWindowEx(dwExStyle,          // extended style
                          WND_CLASS_NAME,     // class name
                          title,              // app name
                          dwStyle |           // window style
                          WS_CLIPCHILDREN |   // required for
                          WS_CLIPSIBLINGS,    // using OpenGL
                          0, 0,               // x,y coordinate
                          windowRect.right - windowRect.left, // width
                          windowRect.bottom - windowRect.top, // height
                          NULL,               // handle to parent
                          NULL,               // handle to menu
                          g_hInstance,        // application instance
                          NULL);              // no extra params

  // see if our window handle is valid
  if (!g_hwnd)
  {
    MessageBox(NULL, "Unable to create window", "Error", MB_OK | MB_ICONEXCLAMATION);
    return FALSE;
  }

  // get a device context
  if (!(g_hdc = GetDC(g_hwnd)))
  {
    MessageBox(NULL,"Unable to create device context", "Error", MB_OK | MB_ICONEXCLAMATION);
    return FALSE;
  }

  // set the pixel format we want
  PIXELFORMATDESCRIPTOR pfd = {
    sizeof(PIXELFORMATDESCRIPTOR),  // size of structure
    1,                              // default version
    PFD_DRAW_TO_WINDOW |            // window drawing support
    PFD_SUPPORT_OPENGL |            // OpenGL support
    PFD_DOUBLEBUFFER,               // double buffering support
    PFD_TYPE_RGBA,                  // RGBA color mode
    bits,                           // 32 bit color mode
    0, 0, 0, 0, 0, 0,               // ignore color bits, non-palettized mode
    0,                              // no alpha buffer
    0,                              // ignore shift bit
    0,                              // no accumulation buffer
    0, 0, 0, 0,                     // ignore accumulation bits
    16,                             // 16 bit z-buffer size
    8,                              // no stencil buffer
    0,                              // no auxiliary buffer
    PFD_MAIN_PLANE,                 // main drawing plane
    0,                              // reserved
    0, 0, 0 };                      // layer masks ignored

  GLuint  pixelFormat;

  // choose best matching pixel format
  if (!(pixelFormat = ChoosePixelFormat(g_hdc, &pfd)))
  {
    MessageBox(NULL, "Can't find an appropriate pixel format", "Error", MB_OK | MB_ICONEXCLAMATION);
    return FALSE;
  }

  // set pixel format to device context
  if(!SetPixelFormat(g_hdc, pixelFormat,&pfd))
  {
    MessageBox(NULL, "Unable to set pixel format", "Error", MB_OK | MB_ICONEXCLAMATION);
    return FALSE;
  }

  // create the OpenGL rendering context
  if (!(g_hrc = wglCreateContext(g_hdc)))
  {
    MessageBox(NULL, "Unable to create OpenGL rendering context", "Error",MB_OK | MB_ICONEXCLAMATION);
    return FALSE;
  }

  // now make the rendering context the active one
  if(!wglMakeCurrent(g_hdc, g_hrc))
  {
    MessageBox(NULL,"Unable to activate OpenGL rendering context", "ERROR", MB_OK | MB_ICONEXCLAMATION);
    return FALSE;
  }

  // show the window in the forground, and set the keyboard focus to it
  ShowWindow(g_hwnd, SW_SHOW);
  SetForegroundWindow(g_hwnd);
  SetFocus(g_hwnd);

  // set up the perspective for the current screen size
  ResizeScene(width, height);

  // do one-time initialization
  if (!InitializeScene())
  {
    MessageBox(NULL, "Initialization failed", "Error", MB_OK | MB_ICONEXCLAMATION);
    return FALSE;
  }

  return TRUE;
} // end SetupWindow()


/*****************************************************************************
 KillWindow()

 Deletes the DC, RC, and Window, and restores the original display.
*****************************************************************************/
BOOL KillWindow()
{
  // restore the original display if we're in fullscreen mode
  if (g_isFullscreen)
  {
    ChangeDisplaySettings(NULL, 0);
    ShowCursor(TRUE);
  }

  // if we have an RC, release it
  if (g_hrc)
  {
    // release the RC
    if (!wglMakeCurrent(NULL,NULL))
    {
MessageBox(NULL, "Unable to release rendering context", "Error", MB_OK | MB_ICONINFORMATION);
    }

 // delete the RC
 if (!wglDeleteContext(g_hrc))
   {
 
 MessageBox(NULL, "Unable to delete rendering context", "Error", MB_OK | MB_ICONINFORMATION);
    }

    g_hrc = NULL;
  }

  // release the DC if we have one
  if (g_hdc && !ReleaseDC(g_hwnd, g_hdc))
  {
    MessageBox(NULL, "Unable to release device context", "Error", MB_OK | MB_ICONINFORMATION);
    g_hdc = NULL;
  }

  // destroy the window if we have a valid handle
  if (g_hwnd && !DestroyWindow(g_hwnd))
  {
    MessageBox(NULL, "Unable to destroy window", "Error", MB_OK | MB_ICONINFORMATION);
    g_hwnd = NULL;
  }

  // unregister our class so we can create a new one if we need to
  if (!UnregisterClass(WND_CLASS_NAME, g_hInstance))
  {
    MessageBox(NULL, 
"Unable to unregister window class", "Error", MB_OK | MB_ICONINFORMATION);
    g_hInstance = NULL;
  }

  return TRUE;
} // end KillWindow()


/*****************************************************************************
 ResizeScene()

 Called once when the application starts and again every time the window is
 resized by the user.
*****************************************************************************/
GLvoid ResizeScene(GLsizei width, GLsizei height)
{
  // avoid divide by zero
  if (height==0)
  {
    height=1;
  }

  // reset the viewport to the new dimensions
  glViewport(0, 0, width, height);

  // set up the projection, without toggling the projection mode
  UpdateProjection();
} // end ResizeScene()


/*****************************************************************************
 InitializeScene()

 Performs one-time application-specific setup. Returns FALSE on any failure.
*****************************************************************************/
BOOL InitializeScene()
{
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glShadeModel(GL_SMOOTH);
  glEnable(GL_DEPTH_TEST);

  return TRUE;
} // end InitializeScene()


/*****************************************************************************
 DisplayScene()

 The work of the application is done here. This is called every frame, and
 handles the actual rendering of the scene.
*****************************************************************************/
BOOL DisplayScene()
{
  GLfloat yellow[4] = { 1.0f, 1.0f, 0.2f, 1.0f };
  GLfloat blue[4] = { 0.2f, 0.2f, 1.0f, 1.0f };
  GLfloat green[4] = { 0.2f, 1.0f, 0.2f, 1.0f };

  glLoadIdentity();
  gluLookAt(-0.5, 1.0, 7.0,
            0.0, 0.0, 0.0,
            0.0, 1.0, 0.0);

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, yellow);
  glPushMatrix();
  glTranslatef(0.3, 0.0, 1.0);
  glutSolidCube(0.5);
  glPopMatrix();

  glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, blue);
  glPushMatrix();
  glutSolidCube(0.5);
  glPopMatrix();

  glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, green);
  glPushMatrix();
  glTranslatef(-0.3, 0.0, -1.0);
  glutSolidCube(0.5);
  glPopMatrix();

  return TRUE;
} // end DisplayScene()


/*****************************************************************************
 Cleanup()

 Called at the end of successful program execution.
*****************************************************************************/
BOOL Cleanup()
{
  return TRUE;
} // end Cleanup()


/****************************************************************************
 UpdateProjection()

 Sets the current projection mode. If toggle is set to GL_TRUE, then the
 projection will be toggled between perspective and orthograpic. Otherwise,
 the previous selection will be used again.
*****************************************************************************/
void UpdateProjection(GLboolean toggle)
{
  static GLboolean s_usePerspective = GL_TRUE;

  // toggle the control variable if appropriate
  if (toggle)
    s_usePerspective = !s_usePerspective;

  // select the projection matrix and clear it out
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();

  // choose the appropriate projection based on the currently toggled mode
  if (s_usePerspective)
  {
    // set the perspective with the appropriate aspect ratio
    glFrustum(-1.0, 1.0, -1.0, 1.0, 5, 100);
  }
  else
  {
    // set up an orthographic projection with the same near clip plane
    glOrtho(-1.0, 1.0, -1.0, 1.0, 5, 100);
  }

  // select modelview matrix and clear it out
  glMatrixMode(GL_MODELVIEW);
} // end UpdateProjection
So now you have learned about Projections, and use them in code. In lesson 9 we will move on to Matrices.

Sample OpenGL Program in C or C++

In this lesson I shall introduce several functions and show you actual OpenGL rendering in a program. Prior to showing you the code, however, I want to go over a few things with you. This will give you a better understanding of what is going on when you do see the code, so you don't stare at the screen wondering what you're looking at. So, on with the show.
Transformations in OpenGL rely on the matrix for all mathematical computations. No, not the movie. Concentrate grasshopper. OpenGL has what is known as a matrix stack, which comes in handy for constructing models composed of many simple objects.

The modelview matrix defines the coordinate system that is being used to place and orient objects. It is a 4x4 matrix that is multiplied by vertices and transformations to create a new matrix that reflects the result of any transformations that have been applied to the vertices. When we want to modify the modelview matrix we use the command glMatrixMode(). We define this as
void glMatrixMode(GLenum mode);
Before you call any transformation commands you MUST specify whether you want to modify the modelview matrix or the projection matrix. The argument for modifying the modelview matrix is GL_MODELVIEW. So the complete line would appear as:
void glMatrixMode(GL_MODELVIEW);
Now we will look at translation. Translation allows you to move an object from one location to another within a 3D environment. The functions for this in OpenGL are glTranslatef() and glTranslated(). Here are their descriptions:
void glTranslatef(GLfloat x, GLfloat y, GLfloat z);
void glTranslated(GLdouble x, GLdouble y, GLdouble z);
Note that you must pass float types to glTranslatef() and double types to glTranslated(). X, Y, and Z represent the amount of translation on that axis.

Rotation in OpenGL is accomplished through the glRotate*() function, which is defined as
void  glRotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z);
void glRotated(GLdouble angle, GLdouble x, GLdouble y, GLdouble z);
Now let's take a look at these, and a few others, functions mentioned in a program. The following code is taken from OpenGL Game Programming and is commented by myself. If you have any problems building and using this code, feel free to contact me.
/*      Steven Billington
        January 13, 2003
        May 26, 2003 - UPDATE
        RobotOGL.cpp
        rod@cprogramming.com

        The following program creates a window and then
        uses multiple OpenGL functions to display a
        animated robot constructed of different size
        cubes. To compile this code you must make the
        proper library links in project--->settings.

        I apologize for any amount of scattered code or
        mis-formatting that printing this may occur. Please
        feel free to email me at the address above for the .cpp
        file.
*/

/*      These are what we refer to as Pre-processor
        Directives. In order for certain functions in
        C++ to operate you must include certain header
        files. Each header file below contains different
        functions needed throughout this program.
*/

#pragma comment(linker, "/subsystem:windows")

#include <windows.h>
#include <gl/gl.h>
#include <gl/glu.h>
#include <gl/glaux.h>

/*      Here we find a few global variables. While
        i don't really like to use global variables,
        i found them very handy for this particular
        program. These variables will control angles,
        fullscreen, and the global device context.
*/

HDC g_HDC;
float angle = 0.0f;
float legAngle[2] = {0.0f, 0.0f};
float armAngle[2] = {0.0f, 0.0f};
bool fullScreen = false;

/*      Function:       DrawCube
        Purpose:        As the name would suggest, this is
                                the function for drawing the cubes.
*/

void DrawCube(float xPos, float yPos, float zPos)
{
        glPushMatrix();
        glBegin(GL_POLYGON);

                /*      This is the top face*/
                glVertex3f(0.0f, 0.0f, 0.0f);
                glVertex3f(0.0f, 0.0f, -1.0f);
                glVertex3f(-1.0f, 0.0f, -1.0f);
                glVertex3f(-1.0f, 0.0f, 0.0f);

                /*      This is the front face*/
                glVertex3f(0.0f, 0.0f, 0.0f);
                glVertex3f(-1.0f, 0.0f, 0.0f);
                glVertex3f(-1.0f, -1.0f, 0.0f);
                glVertex3f(0.0f, -1.0f, 0.0f);

                /*      This is the right face*/
                glVertex3f(0.0f, 0.0f, 0.0f);
                glVertex3f(0.0f, -1.0f, 0.0f);
                glVertex3f(0.0f, -1.0f, -1.0f);
                glVertex3f(0.0f, 0.0f, -1.0f);

                /*      This is the left face*/
                glVertex3f(-1.0f, 0.0f, 0.0f);
                glVertex3f(-1.0f, 0.0f, -1.0f);
                glVertex3f(-1.0f, -1.0f, -1.0f);
                glVertex3f(-1.0f, -1.0f, 0.0f);

                /*      This is the bottom face*/
                glVertex3f(0.0f, 0.0f, 0.0f);
                glVertex3f(0.0f, -1.0f, -1.0f);
                glVertex3f(-1.0f, -1.0f, -1.0f);
                glVertex3f(-1.0f, -1.0f, 0.0f);

                /*      This is the back face*/
                glVertex3f(0.0f, 0.0f, 0.0f);
                glVertex3f(-1.0f, 0.0f, -1.0f);
                glVertex3f(-1.0f, -1.0f, -1.0f);
                glVertex3f(0.0f, -1.0f, -1.0f);

        glEnd();
        glPopMatrix();
}

/*      Function:       DrawArm
        Purpose:        This function draws the arm
                                for the robot.
*/

void DrawArm(float xPos, float yPos, float zPos)
{
        glPushMatrix();

                /*      Sets color to red*/
                glColor3f(1.0f, 0.0f, 0.0f);
                glTranslatef(xPos, yPos, zPos);

                /*      Creates 1 x 4 x 1 cube*/
                glScalef(1.0f, 4.0f, 1.0f);
                DrawCube(0.0f, 0.0f, 0.0f);

        glPopMatrix();
}

/*      Function:       DrawHead
        Purpose:        This function will create the
                                head for the robot.
*/

void DrawHead(float xPos, float yPos, float zPos)
{
        glPushMatrix();

                /*      Sets color to white*/
                glColor3f(1.0f, 1.0f, 1.0f);
                glTranslatef(xPos, yPos, zPos);

                /*      Creates 2 x 2 x 2 cube*/
                glScalef(2.0f, 2.0f, 2.0f);
                DrawCube(0.0f, 0.0f, 0.0f);

        glPopMatrix();
}

/*      Function:       DrawTorso
        Purpose:        Function will do as suggested
                                and draw a torso for our robot.
*/

void DrawTorso(float xPos, float yPos, float zPos)
{
        glPushMatrix();

                /*      Sets color to blue*/
                glColor3f(0.0f, 0.0f, 1.0f);
                glTranslatef(xPos, yPos, zPos);

                /*      Creates 3 x 5 x 1 cube*/
                glScalef(3.0f, 5.0f, 1.0f);
                DrawCube(0.0f, 0.0f, 0.0f);

        glPopMatrix();
}

/*      Function:       DrawLeg
        Purpose:        Not to sound repetitve, but as suggested
                                this function will draw our robots legs.
*/

void DrawLeg(float xPos, float yPos, float zPos)
{
        glPushMatrix();

                /*      Sets color to yellow*/
                glColor3f(1.0f, 1.0f, 0.0f);
                glTranslatef(xPos, yPos, zPos);

                /*      Creates 1 x 5 x 1 cube*/
                glScalef(1.0f, 5.0f, 1.0f);
                DrawCube(0.0f, 0.0f, 0.0f);

        glPopMatrix();
}

/*      Function:       DrawRobot
        Purpose:        Function to draw our entire robot
*/

void DrawRobot(float xPos, float yPos, float zPos)
{
        /*      Variables for state of robots legs. True
                means the leg is forward, and False means
                the leg is back. The same applies to the
                robots arm states.
        */
        static bool leg1 = true;
        static bool leg2 = false;
        static bool arm1 = true;
        static bool arm2 = false;

        glPushMatrix();

                /*      This will draw our robot at the
                        desired coordinates.
                */
                glTranslatef(xPos, yPos, zPos);

                /*      These three lines will draw the
                        various components of our robot.
                */
                DrawHead(1.0f, 2.0f, 0.0f);
                DrawTorso(1.5f, 0.0f, 0.0f);
                glPushMatrix();


                /*      If the arm is moving forward we will increase
                        the angle; otherwise, we will decrease the
                        angle.
                */
                if (arm1)
                {
                        armAngle[0] = armAngle[0] + 1.0f;
                }
                else
                {
                        armAngle[0] = armAngle[0] - 1.0f;
                }

                /*      Once the arm has reached its max angle
                        in one direction, we want it to reverse
                        and change direction.
                */
                if (armAngle[0] >= 15.0f)
                {
                        arm1 = false;
                }
                if (armAngle[0] <= 15.0f)
                {
                        arm1 = true;
                }


                /*      Here we are going to move the arm away
                        from the torso and rotate. This will
                        create a walking effect.
                */
                glTranslatef(0.0f, -0.5f, 0.0f);
                glRotatef(armAngle[0], 1.0f, 0.0f, 0.0f);
                DrawArm(2.5f, 0.0f, -0.5f);

        glPopMatrix();

        glPushMatrix();


                /*      If the arm is moving forward we will increase
                        the angle, otherwise we will decrease the
                        angle
                */
                if (arm2)
                {
                        armAngle[1] = armAngle[1] + 1.0f;
                }
                else
                {
                        armAngle[1] = armAngle[1] - 1.0f;
                }

                /*      Here we are going to move the arm away
                        from the torso and rotate. This will
                        create a walking effect.
                */
                glTranslatef(0.0f, -0.5f, 0.0f);
                glRotatef(armAngle[1], 1.0f, 0.0f, 0.0f);
                DrawArm(-1.5f, 0.0f, -0.5f);

        glPopMatrix();

        /*      Now its time to rotate the legs relative to the
                robots position in the world, this is the first
                leg, ie the right one.
        */
        glPushMatrix();

                /*      If the leg is moving forward we will increase
                        the angle; otherwise, we will decrease the
                        angle.
                */
                if (leg1)
                {
                        legAngle[0] = legAngle[0] + 1.0f;
                }
                else
                {
                        legAngle[0] = legAngle[0] - 1.0f;
                }

                /*      Once the leg has reached its max angle
                        in one direction, we want it to reverse
                        and change direction.
                */
                if (legAngle[0] >= 15.0f)
                {
                        leg1 = false;
                }
                if (legAngle[0] <= -15.0f)
                {
                        leg1 = true;
                }


                /*      Here we are going to move the leg away
                        from the torso and rotate. This will
                        create a walking effect.
                */
                glTranslatef(0.0f, -0.5f, 0.0f);
                glRotatef(legAngle[0], 1.0f, 0.0f, 0.0f);


                /*      Time to draw the leg.
                */
                DrawLeg(-0.5f, -5.0f, -0.5f);

        glPopMatrix();

        /*      Same as above, for the left leg.
        */
        glPushMatrix();

                /*      If the leg is moving forward we will increase
                        the angle, otherwise we will decrease the
                        angle
                */
                if (leg2)
                {
                        legAngle[1] = legAngle[1] + 1.0f;
                }
                else
                {
                        legAngle[1] = legAngle[1] - 1.0f;
                }

                /*      Once the leg has reached its max angle
                        in one direction, we want it to reverse
                        and change direction.
                */
                if (legAngle[1] >= 15.0f)
                {
                        leg2 = false;
                }
                if (legAngle[1] <= -15.0f)
                {
                        leg2 = true;
                }

                /*      Here we are going to move the leg away
                        from the torso and rotate. This will
                        create a walking effect.
                */
                glTranslatef(0.0f, -0.5f, 0.0f);
                glRotatef(legAngle[1], 1.0f, 0.0f, 0.0f);
                DrawLeg(1.5f, -5.0f, -0.5f);

        glPopMatrix();

        glPopMatrix();

}

/*      Function:       Render
        Purpose:        This function will be responsible
                                for the rendering, got to love my
                                descriptive function names : )
*/
void Render()
{
        /*      Enable depth testing
        */
        glEnable(GL_DEPTH_TEST);

        /*      Heres our rendering. Clears the screen
                to black, clear the color and depth
                buffers, and reset our modelview matrix.
        */
        glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glLoadIdentity();

        /*      Increase rotation angle counter
        */
        angle = angle + 1.0f;

        /*      Reset after we have completed a circle
        */
        if (angle >= 360.0f)
        {
                angle = 0.0f;
        }

        glPushMatrix();
                glLoadIdentity();

                /*      Move to 0,0,-30 , rotate the robot on
                        its y axis, draw the robot, and dispose
                        of the current matrix.
                */
                glTranslatef(0.0f, 0.0f, -30.0f);
                glRotatef(angle, 0.0f, 1.0f, 0.0f);
                DrawRobot(0.0f, 0.0f, 0.0f);
        glPopMatrix();

        glFlush();

        /*      Bring back buffer to foreground
        */
        SwapBuffers(g_HDC);
}

//function to set the pixel format for the device context
/*      Function:       SetupPixelFormat
        Purpose:        This function will be responsible
                                for setting the pixel format for the
                                device context.
*/
void SetupPixelFormat(HDC hDC)
{
        /*      Pixel format index
        */
        int nPixelFormat;

        static PIXELFORMATDESCRIPTOR pfd = {
                sizeof(PIXELFORMATDESCRIPTOR),          //size of structure
                1,                                      //default version
                PFD_DRAW_TO_WINDOW |                    //window drawing support
                PFD_SUPPORT_OPENGL |                    //opengl support
                PFD_DOUBLEBUFFER,                       //double buffering support
                PFD_TYPE_RGBA,                          //RGBA color mode
                32,                                     //32 bit color mode
                0, 0, 0, 0, 0, 0,                       //ignore color bits
                0,                                      //no alpha buffer
                0,                                      //ignore shift bit
                0,                                      //no accumulation buffer
                0, 0, 0, 0,                             //ignore accumulation bits
                16,                                     //16 bit z-buffer size
                0,                                      //no stencil buffer
                0,                                      //no aux buffer
                PFD_MAIN_PLANE,                         //main drawing plane
                0,                                      //reserved
                0, 0, 0 };                              //layer masks ignored

                /*      Choose best matching format*/
                nPixelFormat = ChoosePixelFormat(hDC, &pfd);

                /*      Set the pixel format to the device context*/
                SetPixelFormat(hDC, nPixelFormat, &pfd);
}

/*      Windows Event Procedure Handler
*/
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
        /*      Rendering and Device Context
                variables are declared here.
        */
        static HGLRC hRC;
        static HDC hDC;

        /*      Width and Height for the
                window our robot is to be
                displayed in.
        */
        int width, height;

        switch(message)
        {
                case WM_CREATE: //window being created

                        hDC = GetDC(hwnd);  //get current windows device context
                        g_HDC = hDC;
                        SetupPixelFormat(hDC); //call our pixel format setup function

                        /*      Create rendering context and make it current
                        */
                        hRC = wglCreateContext(hDC);
                        wglMakeCurrent(hDC, hRC);

                        return 0;
                        break;

                case WM_CLOSE:  //window is closing

                        /*      Deselect rendering context and delete it*/
                        wglMakeCurrent(hDC, NULL);
                        wglDeleteContext(hRC);

                        /*      Send quit message to queue*/
                        PostQuitMessage(0);

                        return 0;
                        break;

                case WM_SIZE:

                        /*      Retrieve width and height*/
                        height = HIWORD(lParam);
                        width = LOWORD(lParam);

                        /*      Don't want a divide by 0*/
                        if (height == 0)
                        {
                                height = 1;
                        }

                        /*      Reset the viewport to new dimensions*/
                        glViewport(0, 0, width, height);

                        /*      Set current Matrix to projection*/
                        glMatrixMode(GL_PROJECTION);
                        glLoadIdentity(); //reset projection matrix

                        /*      Time to calculate aspect ratio of
                                our window.
                        */
                        gluPerspective(54.0f, (GLfloat)width/(GLfloat)height, 1.0f, 1000.0f);

                        glMatrixMode(GL_MODELVIEW); //set modelview matrix
                        glLoadIdentity(); //reset modelview matrix

                        return 0;
                        break;

                default:

                        break;
        }

        return (DefWindowProc(hwnd, message, wParam, lParam));
}

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
        WNDCLASSEX windowClass; //window class
        HWND    hwnd;                   //window handle
        MSG             msg;                    //message
        bool    done;                   //flag for completion of app
        DWORD   dwExStyle;              //window extended style
        DWORD   dwStyle;                //window style
        RECT    windowRect;

        /*      Screen/display attributes*/
        int width = 800;
        int height = 600;
        int bits = 32;

        windowRect.left =(long)0;               //set left value to 0
        windowRect.right =(long)width;  //set right value to requested width
        windowRect.top =(long)0;                //set top value to 0
        windowRect.bottom =(long)height;//set bottom value to requested height

        /*      Fill out the window class structure*/
        windowClass.cbSize                      = sizeof(WNDCLASSEX);
        windowClass.style                       = CS_HREDRAW | CS_VREDRAW;
        windowClass.lpfnWndProc         = WndProc;
        windowClass.cbClsExtra          = 0;
        windowClass.cbWndExtra          = 0;
        windowClass.hInstance           = hInstance;
        windowClass.hIcon                       = LoadIcon(NULL, IDI_APPLICATION);
        windowClass.hCursor                     = LoadCursor(NULL, IDC_ARROW);
        windowClass.hbrBackground       = NULL;
        windowClass.lpszMenuName        = NULL;
        windowClass.lpszClassName       = "MyClass";
        windowClass.hIconSm                     = LoadIcon(NULL, IDI_WINLOGO);

        /*      Register window class*/
        if (!RegisterClassEx(&windowClass))
        {
                return 0;
        }

        /*      Check if fullscreen is on*/
        if (fullScreen)
        {
                DEVMODE dmScreenSettings;
                memset(&dmScreenSettings, 0, sizeof(dmScreenSettings));
                dmScreenSettings.dmSize = sizeof(dmScreenSettings);
                dmScreenSettings.dmPelsWidth = width;   //screen width
                dmScreenSettings.dmPelsHeight = height; //screen height
                dmScreenSettings.dmBitsPerPel = bits;   //bits per pixel
                dmScreenSettings.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;

                if (ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN !=
                                                                  DISP_CHANGE_SUCCESSFUL))
                {
                        /*      Setting display mode failed, switch to windowed*/
                        MessageBox(NULL, "Display mode failed", NULL, MB_OK);
                        fullScreen = false;
                }
        }

        /*      Check if fullscreen is still on*/
        if (fullScreen)
        {
                dwExStyle = WS_EX_APPWINDOW;    //window extended style
                dwStyle = WS_POPUP;                             //windows style
                ShowCursor(FALSE);                              //hide mouse pointer
        }

        else
        {
                dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; //window extended style
                dwStyle = WS_OVERLAPPEDWINDOW;                                  //windows style
        }

        AdjustWindowRectEx(&windowRect, dwStyle, FALSE, dwExStyle);

        /*      Class registerd, so now create our window*/
        hwnd = CreateWindowEx(NULL, "MyClass",  //class name
                                                  "OpenGL Robot",       //app name
                                                  dwStyle |
                                                  WS_CLIPCHILDREN |
                                                  WS_CLIPSIBLINGS,
                                                  0, 0,                         //x and y coords
                                                  windowRect.right - windowRect.left,
                                                  windowRect.bottom - windowRect.top,//width, height
                                                  NULL,                 //handle to parent
                                                  NULL,                 //handle to menu
                                                  hInstance,    //application instance
                                                  NULL);                //no xtra params

        /*      Check if window creation failed (hwnd = null ?)*/
        if (!hwnd)
        {
                return 0;
        }

        ShowWindow(hwnd, SW_SHOW);      //display window
        UpdateWindow(hwnd);                     //update window

        done = false;   //initialize loop condition variable

        /*      Main message loop*/
        while (!done)
        {
                PeekMessage(&msg, hwnd, NULL, NULL, PM_REMOVE);

                        if (msg.message == WM_QUIT)     //did we receive a quit message?
                        {
                                done = true;
                        }

                        else
                        {
                                Render();
                                TranslateMessage(&msg);
                                DispatchMessage(&msg);
                        }
        }

        if (fullScreen)
        {
                ChangeDisplaySettings(NULL, 0);
                ShowCursor(TRUE);
        }

        return msg.wParam;

}
That's a lot of code! Spend some time studying the example, practice a bit, and then we will proceed to coming lesson, where more of this code will be explained. Also we will cover Projections.
Next: Projections in OpenGL