TutorialGLSurvival

GL Survival Kit for CS559

Michael L. Gleicher 
Written Fall, 2001, Updated Fall 2005

1.  Introductions

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.

 

1.1  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 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.

 

1.2  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:

 

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).

 

2.  Getting Started

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

 
  1. Open the "Properties" dialog for the project. (either from the menu or by right-clicking on the project in the workspace browser)
  2. Select "all configurations" - this is important since you want the libraries for both debugging and non-debugging versions of your program. Unlike FlTk, there are not seperate versions of the library.
  3. Go to Linker -> Input -> Additional Dependencies
  4. 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.
 

3.  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 (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!

 

4.  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!

 

5.  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:

 
  1. Setup for picking: this includes loading a special coordinate system that is centered around the cursor, and setting up the special pick mode.
  2. Draw the objects (except that they really aren't drawn - they're just tested to see if their pixels are inside the pick region).
  3. See what was hit.

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:

 
  1. 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).
  2. When I draw, each object is given a name for picking.
  3. Also, you can see that I keep some information around about a "selected" object. This is often a handy thing to do.
  4. 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.
  5. 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...
  6. 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.
  7. 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.

6.  Animation

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.

 

7.  Project Source Code