Graphics with OpenGL

OpenGL is a standard API for rendering 2D and 3D graphics. Qt applications can draw OpenGL graphics by using Qt's QGL module. This section assumes that you are familiar with OpenGL. If OpenGL is new to you, a good place to start learning it is http://www.opengl.org/.

Drawing graphics with OpenGL from a Qt application is straightforward: We must subclass QGLWidget, reimplement a few virtual functions, and link the application against the QGL and OpenGL libraries. Because QGLWidget inherits from QWidget, most of what we already know still applies. The main difference is that we use standard OpenGL functions to perform the drawing instead of QPainter.

To show how this works, we will review the code of the Cube application shown in Figure 8.20. The application presents a 3D cube with faces of different colors. The user can rotate the cube by pressing a mouse button and dragging. The user can set the color of a face by double-clicking it and choosing a color from the QColorDialog that pops up.

Figure 8.20. The Cube application

class Cube : public QGLWidget { public: Cube(QWidget *parent = 0, const char *name = 0); protected: void initializeGL(); void resizeGL(int width, int height); void paintGL(); void mousePressEvent(QMouseEvent *event); void mouseMoveEvent(QMouseEvent *event); void mouseDoubleClickEvent(QMouseEvent *event); private: void draw(); int faceAtPosition(const QPoint &pos); GLfloat rotationX; GLfloat rotationY; GLfloat rotationZ; QColor faceColors[6]; QPoint lastPos; };

Cube inherits from QGLWidget. The initializeGL(), resizeGL(), and paintGL() functions are reimplemented from QGLWidget. The mouse event handlers are reimplemented from QWidget as usual. QGLWidget is defined in .

Cube::Cube(QWidget *parent, const char *name) : QGLWidget(parent, name) { setFormat(QGLFormat(DoubleBuffer | DepthBuffer)); rotationX = 0; rotationY = 0; rotationZ = 0; faceColors[0] = red; faceColors[1] = green; faceColors[2] = blue; faceColors[3] = cyan; faceColors[4] = yellow; faceColors[5] = magenta; }

In the constructor, we call QGLWidget::setFormat() to specify the OpenGL display context, and we initialize the class's private variables.

void Cube::initializeGL() { qglClearColor(black); glShadeModel(GL_FLAT); glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); }

The initializeGL() function is called once before paintGL() is called. This is the place where we can set up the OpenGL rendering context, define display lists, and perform other initializations.

All the code is standard OpenGL, except for the call to QGLWidget's qglClearColor() function. If we wanted to stick to standard OpenGL, we would call glClearColor() in RGBA mode and glClearIndex() in color index mode instead.

void Cube::resizeGL(int width, int height) { glViewport(0, 0, width, height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); GLfloat x = (GLfloat)width / height; glFrustum(-x, x, -1.0, 1.0, 4.0, 15.0); glMatrixMode(GL_MODELVIEW); }

The resizeGL() function is called once before paintGL() is called the first time, but after initializeGL() is called. This is the place where we can set up the OpenGL viewport, projection, and any other settings that depend on the widget's size.

void Cube::paintGL() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); draw(); }

The paintGL() function is called whenever the widget needs to be repainted. This is similar to QWidget::paintEvent(), but instead of QPainter functions we use OpenGL functions. The actual drawing is performed by the private function draw().

void Cube::draw() { static const GLfloat coords[6] [4] [3] = { { { +1.0, -1.0, +1.0 }, { +1.0, -1.0, -1.0 }, { +1.0, +1.0, -1.0 }, { +1.0, +1.0, +1.0 } }, { { -1.0, -1.0, -1.0 }, { -1.0, -1.0, +1.0 }, { -1.0, +1.0, +1.0 }, { -1.0, +1.0, -1.0 } }, { { +1.0, -1.0, -1.0 }, { -1.0, -1.0, -1.0 }, { -1.0, +1.0, -1.0 }, { +1.0, +1.0, -1.0 } }, { { -1.0, -1.0, +1.0 }, { +1.0, -1.0, +1.0 }, { +1.0, +1.0, +1.0 }, { -1.0, +1.0, +1.0 } }, { { -1.0, -1.0, -1.0 }, { +1.0, -1.0, -1.0 }, { +1.0, -1.0, +1.0 }, { -1.0, -1.0, +1.0 } }, { { -1.0, +1.0, +1.0 }, { +1.0, +1.0, +1.0 }, { +1.0, +1.0, -1.0 }, { -1.0, +1.0, -1.0 } } }; glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslatef(0.0, 0.0, -10.0); glRotatef(rotationX, 1.0, 0.0, 0.0); glRotatef(rotationY, 0.0, 1.0, 0.0); glRotatef(rotationZ, 0.0, 0.0, 1.0); for (int i = 0; i < 6; ++i) { glLoadName(i); glBegin(GL_QUADS); qglColor(faceColors[i]); for (int j = 0; j < 4; ++j) { glVertex3f(coords[i] [j] [0], coords[i] [j] [1], coords[i] [j] [2]); } glEnd(); } }

In draw(), we draw the cube, taking into account the x, y, and z rotations and the colors stored in the faceColors array. Everything is standard OpenGL, except for the qglColor() call. We could have used one of the OpenGL functions glColor3d() or glIndex(), depending on the mode.

void Cube::mousePressEvent(QMouseEvent *event) { lastPos = event->pos(); } void Cube::mouseMoveEvent(QMouseEvent *event) { GLfloat dx = (GLfloat) (event->x() - lastPos.x()) / width(); GLfloat dy = (GLfloat) (event->y() - lastPos.y()) / height(); if (event->state() & LeftButton) { rotationX += 180 * dy; rotationY += 180 * dx; updateGL(); } else if (event->state() & RightButton) { rotationX += 180 * dy; rotationZ += 180 * dx; updateGL(); } lastPos = event->pos(); }

The mousePressEvent() and mouseMoveEvent() functions are reimplement from QWidget to allow the user to rotate the view by clicking and dragging. The left mouse button allows the user the rotate around to x and y axes, the right mouse button around the x and z axes.

After modifying the rotationX, rotationY, and/or rotationZ variables, we call updateGL() to redraw the scene.

void Cube::mouseDoubleClickEvent(QMouseEvent *event) { int face = faceAtPosition(event->pos()); if (face != -1) { QColor color = QColorDialog::getColor(faceColors[face], this); if (color.isValid()) { faceColors[face] = color; updateGL(); } } }

The mouseDoubleClickEvent() is reimplemented from QWidget to allow the user to set the color of a cube face by double-clicking it. We call the private function faceAtPosition() to determine which cube face, if any, is located under the cursor. If a face was double-clicked, we call QColorDialog::getColor() to obtain a new color for that face. Then we update the faceColors array with the new color, and we call updateGL() to redraw the scene.

int Cube::faceAtPosition(const QPoint &pos) { const int MaxSize = 512; GLuint buffer[MaxSize]; GLint viewport[4]; glGetIntegerv(GL_VIEWPORT, viewport); glSelectBuffer(MaxSize, buffer); glRenderMode(GL_SELECT); glInitNames(); glPushName(0); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); gluPickMatrix((GLdouble)pos.x(), (GLdouble) (viewport[3] - pos.y()), 5.0, 5.0, viewport); GLfloat x = (GLfloat)width() / height(); glFrustum(-x, x, -1.0, 1.0, 4.0, 15.0); draw(); glMatrixMode(GL_PROJECTION); glPopMatrix(); if (!glRenderMode(GL_RENDER)) return -1; return buffer[3]; }

The faceAtPosition() function returns the number of the face at a certain position on the widget, or 1 if there is no face at that position. The code for determining this in OpenGL is a bit complicated. Essentially, what we do is render the scene in GL_SELECT mode to take advantage of OpenGL's picking capabilities and then retrieve the face number (its "name") from the OpenGL hit record.

Here's main.cpp:

#include #include "cube.h" int main(int argc, char *argv[]) { QApplication app(argc, argv); if (!QGLFormat::hasOpenGL()) qFatal("This system has no OpenGL support"); Cube cube; cube.setCaption(QObject::tr("Cube")); cube.resize(300, 300); app.setMainWidget(&cube); cube.show(); return app.exec(); }

If the user's system doesn't support OpenGL, we print an error message to the console and abort using Qt's qFatal() global function.

To link the application against the QGL and OpenGL libraries, the .pro file needs this entry:

CONFIG += opengl

That completes the Cube application. For more information about the QGL module, see the reference documentation for QGLWidget, QGLFormat, QGLContext, and QGLColormap.

Категории