|  
 | 
				
					
						
							GL Survival Kit for CS559
						
					
					
						Michael L. Gleicher
					
					
						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! 
					
						The example code for the code snippets, along with project files to compile them 
							is VS2003, are available under this tree. 
							There are other useful OpenGL/FlTk examples as well. The 
								example code page from two years ago has these same examples, with a 
							little more explanation. The only thing missing are the project and solution 
							files for VS2003. 
					 
					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.
					 
					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 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). 
					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 "Properties" 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, 
						there are not seperate versions of the library.
						
 - 
						Go to Linker -> Input -> Additional Dependencies
						
 - 
							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 (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: 
					
						- 
						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: 
							(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: 
					
						- 
						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.
 
					 
					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. 
					  |