GL Survival Kit for CS559
Michael L. Gleicher
Last modified:
19:10 Nov 15, 2001
This document will try to tell you some of the basic things you need
to know to get started with OpenGL under Windows. This is not meant to
replace the GL book!
Note: the samples from
this page are available.
Preface 1: GL vs. OpenGL
OpenGL is a graphics toolkit that provides routines for drawing. It runs
on a wide variety of machines. It is the successor to GL (as in plain
GL) which was a graphics toolkit that ran on Silicon Graphics IRIS workstations.
I sometime use the 2 names interchangeably.
OpenGL is a generalization of GL. For better or worse, in order to make
it more general, it also had to become more complicated.
OpenGL provides routines for drawing graphics within a window. It provides
little help for making the window, providing a user interface, ... For
better or worse, it is designed in a way that leaves the programmer flexibility
in how they organize their programs. How you decide what and when to draw
is your problem.
Preface 2: GLUT vs. FlTk
For doing graphics, it would be nice if you could concentrate on just
doing the relevant drawing, and not worry about user interfaces or the
details of a specific machine. Unfortunately, this is never the case.
If nothing else, you need to put a window up on the screen to draw into.
Basically, this means that your program has to talk to the operating system
and windowing system somehow. For a graphics class, this gives us three
choices:
- We can just not write graphics programs and do it all with pencil
and paper.
- We can force you to learn the specifics of the operating system and
windowing system (for this class, it would be Microsoft Windows).
- We can rely on a toolkit to provide an easier layer of abstractions.
Since approach 1 wouldn't be any fun, and approach 2 is quite challenging
under windows (and would cause us to spend all of our time learning windows
rather than graphics), we (like most graphics classes) choose #3.
For a graphics class, the goal of the UI toolkit is to help you get a
window up on the screen as easily as possible so you can focus on learning
about what to do inside of the window. A secondary goal is to provide
you with enough easy to use user interface tools such that you can build
interactive programs.
GLUT (usually pronounced with an "uh" sound like gluttony) is a simple
toolkit designed to help people build simple OpenGL applications. It provides
an operating system independent way to put up a window, respond to user
interface events like mouse clicks, ... It provides very limited support
for user interfaces (or maybe better said, it only supports very limited
user interfaces). GLUT is a remarkably easy way to get a window up on
the screen so that you can draw into it. In the GL Book, they use GLUT because
their goal is to make small programs that focus on drawing (what OpenGL
does). In many graphics courses, they use GLUT because it probably is
the easiest way to get a picture on the screen.
For CS559, we have been recommending and supporting FlTk,
a user interface toolkit. FlTk supports a lot more features than GLUT
- it is a complete user interface toolkit that allows a programmer to
build complete programs. It has support for windowing, widgets (like buttons
and sliders, even a miniature html browser for making help systems), has
a good model for how to handle events, ...
If your goal is just to get a picture up on the screen with GL (as the
graphics assignments will focus on), GLUT is fine, and FlTk is probably
overkill. However, we still encourage you to use FlTk because:
- You may want to provide more of a user interface.
- It isn't too much harder to use FlTk
- You can use GLUT inside of FlTk.
For the class assignments, you are not required to use FlTk. You can
use GLUT if you prefer. If you have GLUT questions, I can't help you too
much because I haven't used it much. (Richard, the TA has more experience
with it).
Getting Started
The first thing that you'll need to do is set up your project to include
the GL library. Under visual studio, this is done by using the project
settings dialog box.
Note: we're assuming that you already know how to create a new project,
and to use FlTk with it.
To add the gl libraries to your project
- Open the "Settings" dialog for the project. (either from the menu
or by right-clicking on the project in the workspace browser)
- Select "all configurations" - this is important since you want the
libraries for both debugging and non-debugging versions of your program.
Unlike FlTk and libtiff, there are not seperate versions of the library.
- Pick the "Link" tab.
- In the "Object/Library modules" field, add the 3 libraries: opengl32.lib
glu32.lib glaux.lib
The OpenGL headers are in the standard include paths, so you don't need
to change anything to use them. Remember, however, they must be referenced
in their subdirectory. For example, to use the main gl header file you
must add:
#include <GL/gl.h>
to your program. Just including <gl.h> probably won't
work. Also, before reading in gl.h, you need to read in the header file
"windows.h", so to be more precise, your programs should have:
#include <windows.h>
#include <GL/gl.h>
If you use precompiled headers, you may want to put the windows and gl
headers into your precompiled header file as they seem to take a long
time to process.
If you don't know what a precompiled header is, now isn't the
time to learn. The idea of a precompiled header is that you process your
header files once, so you don't need to reprocess them each time you compile
each source file. They are a little bit tricky to set up under Visual
Studio, and probably won't save you that much time.
Once you have your project set up, you're ready to start writing FlTk/OpenGL
programs.
General Strategy: when do I get to draw?
The basic idea for using GL and FlTk is that you will define a subclass
of the FlTk Fl_Gl_Window class that knows how to draw what you want. All
of your drawing happens inside of the draw method for this class.
Here is a very simple example example:
// simple OpenGL / FlTk program
// written 10/16/99, Michael L Gleicher
#include <FL/Fl_Gl_Window.h>
#include <Fl/Fl.h>
#include <windows.h>
#include <GL/gl.h>
// make a Gl window that draws something:
class MyGlWindow : public Fl_Gl_Window {
public:
MyGlWindow(int x, int y, int w, int h) :
Fl_Gl_Window(x,y,w,h,"My GL Window")
{
}
private:
void draw() { // the draw method must be private
glClearColor(0,0,0,0); // clear the window to black
glClear(GL_COLOR_BUFFER_BIT); // clear the window
glColor3f(1,1,0); // draw in yellow
glRectf(-.5,-.5,.5,.5); // draw a filled rectangle
};
};
// the main routine makes the window, and then runs an event loop
// until the window is closed
main()
{
MyGlWindow* gl = new MyGlWindow(100,100,500,500);
gl->show(); // this actually opens the window
Fl::run();
delete gl;
return 1;
}
Thius program draws a large yellow rectangle in the middle of a black
window.
Notice that I did not have to do anything to prepare the window
for drawing, manage it being moved, ... I also didn't have to tell GL
when I was done drawing (FlTk takes care of that).
Also, since in FlTk a window is just like any other widget, they can
be placed inside of one another. (well, I don't think you can place any
other widgets inside of a GL window). So we can do...
// simple OpenGL / FlTk program
// program simple2.cpp
// written 10/16/99, Michael L Gleicher
#include <FL/Fl_Gl_Window.h>
#include <Fl/Fl.h>
#include <windows.h>
#include <GL/gl.h>
#include <Fl/Fl_Double_Window.h>
#include <Fl/Fl_Button.h>
// make a Gl window that draws something:
class MyGlWindow : public Fl_Gl_Window {
public:
float r,g,b;
MyGlWindow(int x, int y, int w, int h) :
Fl_Gl_Window(x,y,w,h,"My GL Window")
{
r = g = 1;
b = 0;
};
private:
void draw() { // the draw method must be private
glClearColor(0,0,0,0); // clear the window to black
glClear(GL_COLOR_BUFFER_BIT); // clear the window
glColor3f(r,g,b); // draw in yellow
glRectf(-.5,-.5,.5,.5); // draw a filled rectangle
};
};
// a simple callback to do something
void changeColor(Fl_Widget* /*button*/, MyGlWindow* myWind)
{
myWind->r = 1 - myWind->r;
myWind->g = 1 - myWind->g;
myWind->b = 1 - myWind->b;
myWind->damage(1);
}
// the main routine makes the window, and then runs an even loop
// until the window is closed
main()
{
Fl_Double_Window* wind =
new Fl_Double_Window(100,100,400,300,"GL Sample");
wind->begin(); // put widgets inside of the window
MyGlWindow* gl = new MyGlWindow(10,10,280,280);
Fl_Button* bt = new Fl_Button(300,10,70,25,"Color");
bt->callback((Fl_Callback*) changeColor, gl);
wind->end();
wind->show(); // this actually opens the window
Fl::run();
delete wind;
return 1;
}
What you should notice about this program is that it places the Gl window
inside of a "Double" window that will take care of doing the double buffering
for us. It allowed us to place widgets around the Gl window (something
we couldn't do with GLUT).
A few other things to notice:
- When we create the GlWindow inside of another window, we specify its
position inside of the parent window.
- We don't need to delete the subwindow - in FlTk, the parent takes
care of deleting all of its children.
- All of the drawing happens in our subclasses draw function.
- All of the things you've learned to do in an FlTk widget (like define
a "handle" function) can be done with the Fl_Gl_Window.
This last point is very important. If you put drawing commands in other
places, you don't know if they will be drawn in the right window! OpenGL
always draws into the "current" window, but you don't necessarily know
where this is! You may have several windows!
Where do I draw?
Now that you know when to draw, you can think about where. In the example,
I arbitrarily chose the coordiantes for the corner of the rectangle.
(well, it wasn't arbitrary).
As you will learn, the coordinate system is one of the elements of state
that GL keeps (just like it keeps a notion of the current color). In this
situation, I just happened to know that the current coordinate system
was going to be having the X and Y axes have values ranging from -1 to
1. You can't rely on this, so when you create a draw function, the first
thing you should do is to define a coordinate system. Otherwise, you have
to rely on luck that the last thing to draw gave you a nice coordinate
system.
A more correct version of the draw method above would be:
void draw() { // the draw method must be private
glMatrixMode(GL_PROJECTION); // set up coord system
glLoadIdentity(); // reset it
glOrtho(-1,1,-1,1,-1,1); // set it to be -1 to 1 in each axis
glMatrixMode(GL_MODELVIEW); // back to normal mode
glLoadIdentity(); // clear what anyone else did
glClearColor(0,0,0,0); // clear the window to black
glClear(GL_COLOR_BUFFER_BIT); // clear the window
glColor3f(r,g,b); // draw in yellow
glRectf(-.5,-.5,.5,.5); // draw a filled rectangle
};
Notice that I first go into "Projection" mode which means I am defining
the coordinate system (GL allows me to keep the transformations that define
the coordinate system and the transformations that define the objects
seperate). glLoadIdentity replaces the top of the transform stack with
the identity matrix, so we have a known value there. glOrtho defines one
kind of coordinate system (generally its what we want for doing things
in 2d).
If you knew that the coordinate system was set correctly, then you wouldn't
have to reset it. FlTK has various ways to help with this, however, I
always tend to be conservative and load a fresh coordinate system everytime
I try to draw.
You should also be warned that this clears out whatever transformation
may have been on the stack. So, if you try to do something that puts something
on the stack first, you may be in for a suprise.
Now you can go off and draw some stuff!
Picking
As part of a user interface, you often want to be able to pick things.
OpenGL helps you do this by having a special drawing mode called "pick"
mode. In this mode, objects aren't actually drawn, but rather, their names
are put onto a list if they are drawn underneath the position of the cursor.
The steps to do picking are:
- Setup for picking: this includes loading a special coordinate system
that is centered around the cursor, and setting up the special pick
mode.
- Draw the objects (except that they really aren't drawn - they're just
tested to see if their pixels are inside the pick region).
- See what was hit.
Here's a sample program that we can look at:
// simple OpenGL / FlTk program
// program simple3.cpp - do picking
// written 10/16/99, Michael L Gleicher
#include <FL/Fl_Gl_Window.h>
#include <Fl/Fl.h>
#include <windows.h>
#include <GL/gl.h>
#include <GL/glu.h> // for pick matrix
#include <Fl/Fl_Double_Window.h>
#include <Fl/Fl_Button.h>
#include <stdio.h>
// make a Gl window that draws something:
class MyGlWindow : public Fl_Gl_Window {
public:
int selected;
MyGlWindow(int x, int y, int w, int h) :
Fl_Gl_Window(x,y,w,h,"My GL Window")
{
selected = 0;
}
private:
// for clarity, we break the draw routine into 3 pieces
void draw() { // the draw method must be private
drawClear();
drawSetupTransform();
drawObjects();
};
// clear the screen and the projection transformation
void drawClear() {
glMatrixMode(GL_PROJECTION); // set up coord system
glLoadIdentity(); // reset it
glClearColor(0,0,0,0); // clear the window to black
glClear(GL_COLOR_BUFFER_BIT); // clear the window
}
// notice that this doesn't reset the projection!
// it must be that way so that picking can use it
void drawSetupTransform() {
glOrtho(-1,1,-1,1,-1,1); // set it to be -1 to 1 in each axis
};
// draw 4 rectangles
// notice that they each have "names" for picking
// if the rectangle is selected, draw it in a different color
void drawObjects() {
// we don't assume that we're in the right matrix mode
glMatrixMode(GL_MODELVIEW); // back to normal mode
glLoadIdentity(); // clear what anyone else did
// rectangle 1
glLoadName(1);
if (selected == 1) glColor3f(1,0,0);
else glColor3f(1,1,0);
glRectd(-.7,-.7,-.3,-.3); // draw a filled rectangle
// rectangle 2
glLoadName(2);
if (selected == 2) glColor3f(1,0,0);
else glColor3f(1,1,0);
glRectd(.7,-.7,.3,-.3); // draw a filled rectangle
// rectangle 3
glLoadName(3);
if (selected == 3) glColor3f(1,0,0);
else glColor3f(1,1,0);
glRectd(-.7,.7,-.3,.3); // draw a filled rectangle
// rectangle(4)
glLoadName(4);
if (selected == 4) glColor3f(1,0,0);
else glColor3f(1,1,0);
glRectd(.7,.7,.3,.3); // draw a filled rectangle
};
//
// handle events - basically, ignore everything except a mouse
// push event. use that to select a rectangle
// because this is a window, we must respond to the show event
// otherwise we'll never get shown if we're a subwindow
int handle(int e) {
if (e==FL_PUSH) {
int mx = Fl::event_x();
int my = Fl::event_y();
// do picking
make_current(); // so we're drawing in our window
// set up a special coordinate system centered at the mouse
// remember, FlTk and Gl have opposite Y directions
glMatrixMode(GL_PROJECTION); // set up special coordinate sys
glLoadIdentity();
// we need the size of the OpenGL window, in its own terms
int viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
gluPickMatrix((double)mx,(double)(viewport[3]-my),5,5,viewport);
drawSetupTransform(); // so object appear in right place
// set up for picking - make a place to put the results
GLuint buf[100];
glSelectBuffer(100,buf);
glRenderMode(GL_SELECT);
glInitNames();
glPushName(0);
// now draw (but nothing is really drawn);
drawObjects();
// go back to drawing mode, and see how picking did
int hits = glRenderMode(GL_RENDER);
printf("Hit %d objects\n!");
selected = 0;
// all we care about is the first hit - see the book for
// info on how to get the other info in the hit record
if (hits) {
printf(" the first hit had %d names [%d was first]\n",
buf[0],buf[3]);
selected = buf[3];
}
damage(1);
return 1;
} if (e==FL_SHOW) {
show();
return 1;
}
return 0;
};
};
// the main routine makes the window, and then runs an even loop
// until the window is closed
main()
{
Fl_Double_Window* wind =
new Fl_Double_Window(100,100,300,300,"GL Picking Sample");
wind->begin(); // put widgets inside of the window
MyGlWindow* gl = new MyGlWindow(10,10,280,280);
wind->end();
wind->show(); // this actually opens the window
Fl::run();
delete wind;
return 1;
}
Now for some commentary:
- Notice how I split the drawing function up into 3 different parts.
This makes it easier when we only want to use part of the drawing function
(for example, when we do picking).
- When I draw, each object is given a name for picking.
- Also, you can see that I keep some information around about a
"selected" object. This is often a handy thing to do.
- Notice that I intercept the FL_SHOW event and handle it. This
turns out to be important for windows. If you don't do this, they don't
show up sometimes.
- The guts of this program is the code that does the picking. Normally,
this would have been made a seperate method called from the handle method,
but since this program does so little...
- Notice that before I do any drawing in a routine other than "draw"
(e.g. in handle), I need to do a make_current. This is an FlTk
method of the window class that tells GL that we want the drawing operations
to happen inside of our own window, not someone else's.
- Each "hit" during picking puts a bunch of information into the pick
buffer. In this case, I knew there would only be one object picked,
it was easy.
|