|

OpenGL Tutorial Part 02 - by Ulf Ochsenfahrt
Hi, this is the second of the OpenGL with FreePascal tutorials. For a short introduction and setup instructions, see tutorial 01!
Idea
This time we will start differently than in the first tutorial. The goal is to understand the general OpenGL application outline. So, we will produce a general OpenGL application that will be the basis of the following OpenGL tutorials.
So, how does GLUT work? Remember, GLUT does all the windowing stuff for us. All we have to do is draw, if something has to be drawn and process events, if there are events. GLUT works on the basis of registering callback functions. They are called when the appropriate events have occurred. In this tutorial you will see how callback functions work and how to register them with GLUT.
General Outline
We'll have a standard application frame of course:
Program Tutor02;
Uses GL, GLUT;
Begin
End.
Our application is divided into several parts that are common to every GLUT OpenGL application. Sometimes parts may be omitted or occur in a completely different order. But these are the standard first-level subdivisions of it:
- declaration of constants and variables
- program initialization functions
- miscellaneus functions
- OpenGL list creation functions
- drawing functions
- callback functions
- OpenGL and GLUT initialization
- callback registration
- glutMainLoop
Our general outline will look like this now:
Program Tutor02;
Uses GL, GLUT;
//declaration of constants and variables
//program initialization
//misc
//list creation
//drawing
//callback
Begin
//OpenGL and GLUT initialization
//callback registration
//glutMainLoop
glutMainLoop;
End.
WARNING
Of course you may, but you should NOT place code after the call to the glutMainLoop-function. Although it only looks like a procedure call, it is not. This procedure will NEVER return and thus code behind this procedure will never be executed.
Callback Functions
What are callback functions and how do they work?
Callback functions are functions that can be called by GLUT. GLUT has an internal event queue (event list) that keeps all the possible events. If there is an event in the event queue, it is removed and the appropriate callback function is called if it exists. If the event queue is empty, an special callback function - the idle function - is called if it exists.
Callback functions are the main parts of an GLUT OpenGL app, as they are the only functions that are known to GLUT. If you want to utilize a certain callback function, you have to write it and thereafter register it with GLUT.
How are callback functions declared?
Since GLUT (and windows) do not use the same subfunction call model, we have to tell FreePascal that we want to write functions that can be called by GLUT. In general, callback functions are declared like this:
//general callback function declaration
Procedure callbackfunction(parameterlist); stdcall;
Begin
End;
IMPORTANT: Notice the "stdcall;" at the end of the function declaration.
How are callback functions registered?
You have to register callback functions like this:
//general callback function registration
glutCallbackFunc(@callbackfunction);
IMPORTANT: Notice the "@" before the callback function name.
Which callback functions exist and how are they registered?
There are several callback functions with (of course) different behavior and tasks:
| event |
GLUT register function |
to do |
| draw/paint |
glutDisplayFunc |
draw the entire scene |
| resize |
glutReshapeFunc |
setup the viewport and view matrix |
| idle |
glutIdleFunc |
do as you like, e.g. issue a glutPostRedisplay |
| key pressed |
glutKeyboardFunc |
process the character |
| window becomes (in)visible |
glutVisibilityFunc |
you shouldn't draw into an invisible window |
See the GLUT specification for a complete list (available at opengl.org).
Description of the two most important callback functions
display callback
This is certainly the most important function for your program as it should draw the entire scene every time it is called. For sake of speed, it should make extensive use of lists. What lists are and how they work will be explained in a later tutorial. It takes no parameters.
Procedure draw; stdcall;
Begin
//do some drawing
...
glFlush; //tell OpenGL to flush internal buffers
glutSwapBuffers; //tell GLUT to actually show the buffer (only if double buffered)
End;
//draw callback function registration
glutDisplayFunc(@draw);
It is important to remember, that OpenGL can (and does) delay the actual painting until the next glFlush is issued. If you work with double-buffered graphics, you also have to issue a glutSwapBuffers to actually show what you have drawn into the buffer before.
reshape callback
This function is important (lets say it's second place) as well, as it gives you the actual screen size which may differ from what you have requested (see glutInitWindowSize). You should use it to setup the projection matrix (see math tutorial).
Procedure reshape(width, height : LongInt); stdcall;
Begin
//setup new projection matrix which depends on window size
...
End;
//reshape callback function registration
glutReshapeFunc(@reshape);
For more detail on how to set up this, see the math tutorial. For now, it may suffice to know, that the standard code from the first tutorial should fare you well.
General Outline Version 2.0
Our general outline will look like this now:
Program Tutor02;
Uses GL, GLUT;
//declaration of constants and variables
//program initialization
//misc
//list creation
//drawing
//callback
Procedure reshape(width, height : LongInt); stdcall;
Begin
//setup new projection matrix which depends on window size
...
End;
Procedure draw; stdcall;
Begin
//do some drawing
...
glFlush; //tell OpenGL to flush internal buffers
glutSwapBuffers; //tell GLUT to actually show the buffer (only if double buffered)
End;
Begin
//OpenGL and GLUT initialization
//callback registration
glutDisplayFunc(@draw);
glutReshapeFunc(@reshape);
//glutMainLoop
glutMainLoop;
End.
You still cannot actually compile this application - there are still some important parts missing.
OpenGL and GLUT initialization
GLUT initialization
Let's start with the easier of the two parts. We have already covered a little bit of GLUT
initialization and there is not much left. We had callback functions and the glutMainLoop. We still need
the start initialization. There's really not much about this, the standard parameters will fare you well or
you can easily see what you may change.
At the program start, we have to tell GLUT that we want to have a window and set some parameters.
- let GLUT initialize itself
- set initial window position (optional)
- set initial window size (optional)
- set initial display mode
- create a window
- register callback functions
- glutMainLoop
GLUT initializes itself
//let GLUT initialize itself
glutInit(@argc, argv);
There's not much to say about this. Just a
WARNING
GLUT uses some of the comand-line parameters, so you have to be careful if you want to make use of them.
set initial window position and size
//set initial window position
glutInitWindowPosition(x, y);
//set initial window size
glutInitWindowSize(xsize, ysize);
If these are not set, the standard values of the underlying windowing system (e.g. Microsoft Windows) are used.
window creation
//create a window
glutCreateWindow('Tutorial 02');
You might be able to create more than one GLUT-(OpenGL)-Window. Try if you like, but be careful: The callback functions are global and you might have to change the actual window and things...
That's all initialization needed at this stage of development. We'll come back to this some time later.
OpenGL drawing
clearing the screen
As we start simple (!), we'll have to clear the screen before starting to draw. Sometimes there is no need to clear the screen, e.g. if there is an object or polygon at every point of the screen.
As there may be many buffers, we will have to tell OpenGL, which buffer to clear. For example, we might use a COLOR buffer, a DEPTH buffer and a STENCIL buffer in some application.
//clear the color buffer
glClear(GL_COLOR_BUFFER_BIT);
//clear the color and the depth buffer
// glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
(In some future tutorial, this will be extended.)
the 3. dimension
Although OpenGL offers 2-dimensional drawing as well, we'll start with the 3. dimension (as it's more interesting...).
general outline
In general, OpenGL drawing works like this:
You tell OpenGL what you want to draw, e.g. a line or a triangle. There is actually only little difference between those two modes of drawing. Yeah, let's call it like that. You select a mode of drawing.
Then you give OpenGL the parameters needed, which will mainly consist of vector data (at least at first).
Last but..., you tell OpenGL that you have finished giving it parameters and that this shall be drawn to the screen.
Yes, it's actually as easy as this. Of course, there are lots of parameters you can change, that influence how the objects are being drawn, but in general it's always like this. So let's have a look at
some code
//draw something ;-)
glBegin(GL_QUADS); //draw something with 4 vertexes
glVertex3f(-0.5, -0.5, -2.0); //first vertex
glVertex3f(-0.5, 0.5, -2.0); //second vertex
glVertex3f( 0.5, 0.5, -2.0); //...
glVertex3f( 0.5, -0.5, -2.0);
glEnd; //thats four vertexes, draw(!!)
Don't forget to end every begin.
Our mode of drawing is GL_QUADS. Every corner of our rectangle has 3 coordinates (x, y, z). The negative part of the z-axis is the direction of view. Normally our window has x and y coordinates in the range of -1.0 to 1.0. That's easy, huh?
setting up the projection matrix
There's not much left, as you can see in the "General Outline Version 2.0" and we'll finish this tutorial soon. Unfortunatly this is the part that involves enough math to make it difficult to explain.
That's why I don't explain it too deeply. Think of this: We have 3-dimensional objects - thus we have 3-dimensional vectors to the vertexes of our objects - BUT, we only have a 2-dimensional screen. So we have to think of some mathematical formula to calculate 2-dimensional vectors out of the 3-dimensional ones.
Imagine a piece of wood with a square hole in it. The square is exactly 2 by 2 something. Your head is at some point before this hole looking at it directly. Now, imagine you stand in front of a wall, so you cannot look infinetly wide. Then the space you can see through that hole is a frustum, that is, a pyramid without its top.
Now the hole in the wood is our screen. All objects behind the screen can be seen on it, while the space we see has the form of a frustum. The correct mathematical formula can be expressed with a 4-dimensional matrix (not the movie !!). However, OpenGL offers a possibility to easily setup a matrix with those values.
glMatrixMode(GL_PROJECTION); //switch to projection-matrix
glLoadIdentity; //setup standard matrix
glFrustum(-1.0*aspect, 1.0*aspect, 1.0, -1.0, 1.0, 20.0); //setup viewing matrix
glMatrixMode(GL_MODELVIEW); //switch back to the modelview-matrix
glLoadIdentity; //setup standard matrix
OpenGL has more than one matrix. By the way, it has three different matrixes, the PROJECTION, the MODELVIEW and the TEXTURE matrix. However, the projection matrix keeps the formula we have only just spoken of. We'll switch back to the modelview matrix, as we will need it for other calculations.
General Outline Version 3.0
Our general outline will look like this now:
Program Tutor02;
Uses GL, GLUT;
//declaration of constants and variables
//program initialization
//misc
//list creation
//drawing
//callback
Procedure reshape(width, height : LongInt); stdcall;
Var aspect : glFloat;
Begin
aspect := width / height;
//setup new projection matrix which depends on window size
glMatrixMode(GL_PROJECTION); //switch to projection-matrix
glLoadIdentity; //setup standard matrix
glFrustum(-1.0*aspect, 1.0*aspect, 1.0, -1.0, 1.0, 20.0); //setup viewing matrix
glMatrixMode(GL_MODELVIEW); //switch back to the modelview-matrix
glLoadIdentity; //setup standard matrix
End;
Procedure draw; stdcall;
Begin
//clear the color buffer
glClear(GL_COLOR_BUFFER_BIT);
//do some drawing
glBegin(GL_QUADS); //draw something with 4 vertexes
glVertex3f(-0.5, -0.5, -2.0); //first vertex
glVertex3f(-0.5, 0.5, -2.0); //second vertex
glVertex3f( 0.5, 0.5, -2.0); //...
glVertex3f( 0.5, -0.5, -2.0);
glEnd; //thats four vertexes, draw(!!)
glFlush; //tell OpenGL to flush internal buffers
// glSwapBuffers; //we actually don't use the double-buffer
End;
Begin
//OpenGL and GLUT initialization
glutInit(@argc, argv);
glutCreateWindow('Tutorial 02');
//callback registration
glutDisplayFunc(@draw);
glutReshapeFunc(@reshape);
//glutMainLoop
glutMainLoop;
End.
When the program is started, it initializes GLUT and registers two callback functions. GLUT creates a window and calls the reshape callback, which setups the projection matrix. GLUT then issues a DRAW event, because the window shall be shown on the screen. The draw callback then draws a white rectangle on the screen. And that's it.
I have deeply dropped into OpenGL and GLUT and I hope you got an idea of what it is all about. The next few tutorials will explaine some details that have been left out now and then we'll start making special effects (imagine diabolic grin). Nevertheless we will keep to this general outline so you should have an idea of what I was taking about.
Back to previous page
|