Section 21.9. Introduction to OpenGL Programming

Before we finish this chapter with a look at integrated development environments and in particular KDevelop, let's do some fun stuffthree-dimensional graphics programming using the OpenGL libraries!

Of course, it would be far too ambitious to give proper coverage of OpenGL programming in this book, so we just concentrate on a simple example and show how to get started and how OpenGL integrates with two popular toolkits.

21.9.1. GLUT

The GL Utility Toolkit was written by Mark Kilgard of SGI fame. It is not free software, but it comes with full source code and doesn't cost anything. The strength of GLUT is that it is tailored specifically for being very simple to get started with programming OpenGL. Mesa comes with a copy of GLUT included, and a free software reimplementation of GLUT is available from http://freeglut.sourceforge.net/. Basically, GLUT helps with initial housekeeping, such as setting up a window and so on, so you quickly can get to the fun part, namely, writing OpenGL code.

To use GLUT, you first need to access its definitions:

#include <GL/glut.h>

Next, call a couple of initialization functions in main( ):

glutInit(&argc, argv)

to initialize GLUT and allow it to parse command-line parameters, and then:

glutInitDisplayMode( unsigned int mode )

where mode is a bitwise OR of some constants from glut.h. We will use GLUT_RGBA|GLUT_SINGLE to get a true-color single-buffered window.

The window size is set using:

glutInitWindowSize(500,500)

and finally the window is created using:

glutCreateWindow("Some title")

To be able to redraw the window when the window system requires it, we must register a callback function. We register the function disp( ) using:

glutDisplayFunc(disp)

The function disp( ) is where all the OpenGL calls happen. In it, we start by setting up the transformation for our object. OpenGL uses a number of transformation matrixes, one of which can be made "current" with the glMatrixMode(GLenum mode) function. The initial matrix is GL_MODELVIEW, which is used to transform objects before they are projected from 3D space to the screen. In our example, an identity matrix is loaded and scaled and rotated a bit.

Next the screen is cleared and a four-pixel-wide white pen is configured. Then the actual geometry calls happen. Drawing in OpenGL takes place between glBegin( ) and glEnd( ), with the parameter given to glBegin( ) controlling how the geometry is interpreted.

We want to draw a simple box, so first we draw four line segments to form the long edges of the box, followed by two rectangles (with GL_LINE_LOOP) for the end caps of the box. When we are done we call glFlush( ) to flush the OpenGL pipeline and make sure the lines are drawn on the screen.

To make the example slightly more interesting, we add a timer callback timeout( ) with the function glutTimerFunc( ) to change the model's rotation and redisplay it every 50 milliseconds.

Here is the complete example:

#include <GL/glut.h>

static int glutwin;
static float rot = 0.;

static void disp(void)
{
  float scale=0.5;
  /* transform view */
  glLoadIdentity(  );

  glScalef( scale, scale, scale );
  glRotatef( rot, 1.0, 0.0, 0.0 );
  glRotatef( rot, 0.0, 1.0, 0.0 );
  glRotatef( rot, 0.0, 0.0, 1.0 );

  /* do  a clearscreen */
  glClear(GL_COLOR_BUFFER_BIT);

  /* draw something */
  glLineWidth( 3.0 );
  glColor3f( 1., 1., 1. );

  glBegin( GL_LINES ); /* long edges of box */
  glVertex3f(  1.0,  0.6, -0.4 );   glVertex3f(  1.0,  0.6, 0.4 );
  glVertex3f(  1.0, -0.6, -0.4 );   glVertex3f(  1.0, -0.6, 0.4 );
  glVertex3f( -1.0, -0.6, -0.4 );   glVertex3f( -1.0, -0.6, 0.4 );
  glVertex3f( -1.0,  0.6, -0.4 );   glVertex3f( -1.0,  0.6, 0.4 );
  glEnd(  );

  glBegin( GL_LINE_LOOP ); /* end cap */
  glVertex3f(  1.0,  0.6, -0.4 ); glVertex3f(  1.0, -0.6, -0.4 );
  glVertex3f( -1.0, -0.6, -0.4 ); glVertex3f( -1.0,  0.6, -0.4 );
  glEnd(  );

  glBegin( GL_LINE_LOOP ); /* other end cap */
  glVertex3f(  1.0,  0.6, 0.4 ); glVertex3f(  1.0, -0.6, 0.4 );
  glVertex3f( -1.0, -0.6, 0.4 ); glVertex3f( -1.0,  0.6, 0.4 );
  glEnd(  );

  glFlush(  );
}

static void timeout( int value )
{
  rot++; if( rot >= 360. ) rot = 0.;
  glutPostRedisplay(  );
  glutTimerFunc( 50, timeout, 0 );
}

int main( int argc, char** argv )
{
  /* initialize glut */
  glutInit(&argc, argv);
  /* set display mode */
  glutInitDisplayMode(GLUT_RGBA | GLUT_SINGLE);
  /* output window size */
  glutInitWindowSize(500,500);
  glutwin = glutCreateWindow("Running Linux 3D Demo");
  glutDisplayFunc(disp);
  /* define the color we use to clearscreen */
  glClearColor(0.,0.,0.,0.);
  /* timer for animation */
  glutTimerFunc( 0, timeout, 0 );
  /* enter the main loop */
  glutMainLoop(  );
  return 0;
}

21.9.2. Qt

As an example of how to do OpenGL programming with a more general-purpose GUI toolkit, we will redo the GLUT example from the previous section in C++ with the Qt toolkit. Qt is available from http://www.trolltech.com/ under the GPL license and is used by large free software projects such as KDE.

We start out by creating a subclass of QGLWidget, which is the central class in Qt's OpenGL support. QGLWidget works like any other QWidget, with the main difference being that you do the drawing with OpenGL instead of a QPainter. The callback function used for drawing with GLUT is now replaced with a reimplementation of the virtual method paintGL( ), but otherwise it works the same way. GLUT took care of adjusting the viewport when the window was resized, but with Qt, we need to handle this manually. This is done by overriding the virtual method resizeGL(int w, int h). In our example we simply call glViewport( ) with the new size.

Animation is handled by a QTimer that we connect to a method timout( ) to have it called every 50 milliseconds. The updateGL( ) method serves the same purpose as glutPostRedisplay( ) in GLUTto make the application redraw the window.

The actual OpenGL drawing commands have been omitted because they are exactly the same as in the previous example. Here is the full example:

#include <qapplication.h>
#include <qtimer.h>
#include <qgl.h>

class RLDemoGLWidget : public QGLWidget {
  Q_OBJECT
public:
  RLDemoGLWidget(QWidget* parent,const char* name = 0);
public slots:
  void timeout(  );
protected:
  virtual void resizeGL(int w, int h);
  virtual void paintGL(  );
private:
  float rot;
};

RLDemoGLWidget::RLDemoGLWidget(QWidget* parent, const char* name)
  : QGLWidget(parent,name), rot(0.)
{
  QTimer* t = new QTimer( this );
  t->start( 50 );
  connect( t, SIGNAL( timeout(  ) ),
                   this, SLOT( timeout(  ) ) );
}

void RLDemoGLWidget::resizeGL(int w, int h)
{
  /* adjust viewport to new size */
  glViewport(0, 0, (GLint)w, (GLint)h);
}

void RLDemoGLWidget::paintGL(  )
{
  /* exact same code as disp(  ) in GLUT example */
  ...
}

void RLDemoGLWidget::timeout(  )
{
  rot++; if( rot >= 360. ) rot = 0.;
  updateGL(  );
}

int main( int argc, char** argv )
{
  /* initialize Qt */
  QApplication app(argc, argv);
  /* create gl widget */
  RLDemoGLWidget w(0);
  app.setMainWidget(&w);
  w.resize(500,500);
  w.show(  );
  return app.exec(  );
}
#include "main.moc"




Part I: Enjoying and Being Productive on Linux
Part II: System Administration