Latest revision as of 11:52, 20 September 2013

This tutorial [1] was originally written by Mikael Vejdemo Johansson, and was copied here with permission. Parts of the tutorial have been modified and extended to keep it up to date.

As we left off the last installment, we were just about capable to open up a window, and draw some basic things in it by giving coordinate lists to the command

renderPrimitive

. The programs we built suffered under a couple of very infringing and ugly restraints when we wrote them - for one, they weren't really very modularized. The code would have been much clearer had we farmed out important subtasks on other modules. For another, we never even considered the fact that some manipulations would not necessarily be good to do on the entire picture.

Now, compiling this entire section with the command ghc --make HelloWorld.hs compiles and links each module needed, and produces an executable to be used. There we go! Much more modularized, much smaller and simpler bits and pieces. And - an added boon - we won't normally need to recompile as much for each change we do. As an alternative, you could just load HelloWorld.hs into GHCi and run it via

As you may have noticed, rendering graphics in OpenGL relies extensively on actions. Some action-based rendering functions include rotate, translate, and color. When using renderPrimitive, a sequence of vertex actions is executed - one for each vertex. While working on a project, we may want to focus on lists of vertices, rather extensive quantities of actions in which our vertices are hidden. Let's take a look at how we might rewrite Cube.hs to focus on vertices.

One of the core reasons I started to write this tutorial series was that I wanted to figure out why Panitz' tutorial didn't work for me. The core explanation is simple - the names of some of the functions used has changed since he wrote them. Thus, the matrixExcursion in his tutorial is nowadays named preservingMatrix. This may well change further - though I hope it won't - in which case this tutorial will be painfully out of date as well.

The idea of preservingMatrix, however, is to take a small piece of drawing actions, and perform them independent of the transformations going on outside that small piece. For demonstration, let's draw a bunch of cubes, shall we?

We'll change the rather boring display subroutine in Display.hs into one using preservingMatrix to modify each cube drawn individually, giving a new Display.hs:

where we note that we need to renormalize our colours to get them within the interval [0,1] from the interval [-1,1] in order to get valid colour values. The output looks like:

The point of this yoga doesn't come apparent until you start adding some global transformations as well. So let's! We add the line

scale 0.70.7(0.7::GLfloat)

just after the

clear [ColorBuffer]

, in order to scale the entire picture. As a result, we get

We can do this with all sorts of transformations - we can rotate the picture, skew it, move the entire picture around. Using preservingMatrix, we make sure that the transformations “outside” apply in the way we'd expect them to.

A lot of the OpenGL programming is centered around the program being prepared to launch some sequence when some event occurs. Let's use this to build a rotating version of our bunch of points up there. In order to do things over time, we're going to be using the global callbacks idleCallback and timerCallback. So, we'll modify the structure of our files a bit - starting from the top.

We'll need a new callback. And we'll also need a state variable of our own, which in turn needs to be fed to all functions that may need to use it. Incorporating these changes, we get a new HelloWorld.hs. If you are using Linux, you may want to skip ahead to the section using double buffers.

Note the addition of an angle, and an idle. We need to feed the value of angle both to idle and to display, in order for them to use it accordingly. Now, we need to define idle somewhere - and since we keep all the bits and pieces we modify a LOT in display, let's put it in there.

Exporting it all the way requires us to change the first line of Bindings.hs to

Now, running this program makes a couple of different things painfully obvious. One is that things flicker. (Note: Mac OS X double-buffers internally so it does not flicker). Another is that our ring is shrinking violently. The shrinking is due to our forgetting to reset all our transformations before we apply the next, and the flicker is because we're redrawing an entire picture step by step. Much smoother animation'll be had if we use a double buffering technique. Now, this isn't at all hard. We need to modify a few places - tell HOpenGL that we want to do doublebuffering and also when we want to swap the ready drawn canvas for the one on the screen. So, we modify, again, HelloWorld.hs:

There we are! That looks pretty, doesn't it? Now, we could start adding control to the user, couldn't we? Let's add some keyboard interfaces. We'll start by letting the rotation direction change when we press spacebar, and let the arrows displace the whole figure and + and - increase/decrease the rotation speed.
Again, we're adding states, so we need to modify HelloWorld.hs:

Finally, in Display.hs we use the new information to accordingly redraw the scene, specifically the now changing amount to change the current angle with. Note that in order to avoid the placement of the circle to be pulled in with all the other modifications we're doing, we do the translation outside a

The code we have written so far may not handle depth properly, but the program as-written won't reveal whether or not this is the case! Let's extend the example to add outlines the cubes, and add depth to the animation!

The code for the wire frame belongs in Cube.hs. We can draw a wire frame using vertex3f from above:

function, you may not get the results you expect. You might see lines which should be occluded, or you might not see new lines at all. Let's take a look at how we can fix some of these problems.
The first thing we need to do is ensure that we initialize our window with a depth buffer. The depth buffer indicates the current depth of a pixel on our screen, allowing OpenGL to determine whether or not to draw over the current color. We also need to specify how our depth buffer will do this. We want things with less depth to be rendered above those with more depth, so we use the comparison function

The animation starts off with all the cubes facing you, so increase the speed and let it run for a bit to let the corners show up. You should now have cubes revolving around an off-center axis with outlines, showing off their corners!

Note that the code covered here allows us to add some depth to our image, but may not be sufficient to cover transparency and blending.