Before we finish this chapter with a look at integrated development environments and in particular KDevelop, let’s do some fun stuff—three-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.
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; }
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
GLUT—to 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"