Michael L.
Gleicher
Written Fall, 2001, Updated Fall 2005
This document tells 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!
There are another set of tutorials that have much more specific details about the mechanics of setting up projects in the CS environment. After reading this guide, I recommend starting with the Tutorial.
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 sometimes use the 2 names interchangeably.
OpenGL is a generalization of GL. To make it more general, it 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.
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:
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 to 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 with an 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 many 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. (And the TA has never used it).
See the new tutorial for an updated and more specific set of information on how to set up OpenGLprojects.
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
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.
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 (link to code):
// 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... (link to code)
// 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:
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!
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!
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:
Here's a sample program that we can look at: (link to code)
// 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:
To make animation happen, you need to use an "idle callback" so that FlTk gives your application a chance to do work when the computer is just sitting there. I have encapsulated this functionality into a convenient "time slider" widget called a "RunButton", the files are RunButton.h, RunButton.cpp and there's a sample program that uses it in simple4.cpp.