8.4. Графика OpenGL.

OpenGL -- это стандарт API, для отображения двух- и трехмерной графики. Приложения Qt могут использовать OpenGL, посредством модуля QGL. Мы полагаем, что вы уже имеете некоторое знакомство с OpenGL. Если это не так, то рекомендуем начать изучение с посещения сайта http://www.opengl.org/.

Рисование трехмерных объектов, с помощью OpenGL, не так сложно, как может показаться на первый взгляд. Все что вам нужно сделать -- создать дочерний класс от QGLWidget, перекрыть некоторые виртуальные методы предка и связать приложение с модулем QGL и библиотекой OpenGL. Поскольку QGLWidget ведет свою родословную от QWidget, то здесь вполне применимы знания, которые вы уже получили. Основное отличие здесь состоит в том, что теперь, вместо QPainter, вам придется использовать стандартные функции рисования из OpenGL.

Рисунок 8.20. Приложение Cube.


Для демонстрации возможностей библиотеки OpenGL, напишем приложение Cube, изображенное на рисунке 8.20. Приложение рисует трехмерный куб, грани которого окрашены в различные цвета. Пользователь может вращать куб и перемещать его. Двойным щелчком мыши по грани куба, он сможет изменить ее цвет, с помощью диалога выбора цвета QColorDialog. 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 порожден от QGLWidget. Функции initializeGL(), resizeGL() и paintGL() перекрывают методы родительского класса QGLWidget. Обработчики событий от мыши перекрывают обработчики, унаследованные от QWidget. Определение класса QGLWidget находится в заголовке <qgl.h>. 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; } В конструкторе вызывается QGLWidget::setFormat(), чтобы задать контекст устройства отображения OpenGL, и инициализируются приватные переменные-члены класса. void Cube::initializeGL() { qglClearColor(black); glShadeModel(GL_FLAT); glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); } Функция initializeGL() вызывается один раз, перед вызовом paintGL(). Здесь выполняется настройка контекста отображения.

Все функции являются стандартными вызовами из библиотеки OpenGL, за исключением qglClearColor() -- метода класса QGLWidget. Если задаться целью, до конца следовать стандарту OpenGL, то мы могли бы вызвать функцию glClearColor(), в режиме RGBA, или glClearIndex(), в режиме индексированных цветов.

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); } Функция resizeGL() вызывается один раз, перед paintGL(), но после того, как будет вызвана функция initializeGL(). Здесь настраивается область просмотра (viewport), проекция и прочие настройки, которые зависят от размера виджета. void Cube::paintGL() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); draw(); } Функция paintGL() вызывается всякий раз, когда возникает необходимость перерисовать содержимое виджета. Она напоминает обработчик события QWidget::paintEvent(), только вместо QPainter здесь используются обращения к функциям OpenGL. Собственно рисование выполняется внутри приватной функции 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(); } } Внутри функции draw() выполняется рисование куба, с учетом вращения по осям x, y и z и цветов граней, находящихся в массиве faceColors. Все вызовы являются стандартными для OpenGL, за исключением qglColor(). Мы могли бы использовать вместо нее стандартные функции OpenGL glColor3d() или glIndex(), в зависимости от выбранного режима цветопередачи. 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(); } Функции mousePressEvent() и mouseMoveEvent() позволяют пользователю вращать куб и перемещать его по поверхности экрана. Левой кнопкой мыши выполняется вращение по осям x и y, правой -- по осям x и z.

После изменения переменных rotationX и/или rotationY и rotationZ, вызывается функция updateGL(), которая перерисовывает изображение.

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(); } } } Обработчик mouseDoubleClickEvent() позволяет пользователю изменить цвет грани по двойному щелчку мыши. Для определения номера грани вызывается функция faceAtPosition(). Если под указателем мыши действительно находится какая либо грань куба, вызывается QColorDialog::getColor(), чтобы получить от пользователя новый цвет грани. Затем он заносится в массив faceColors и вызывается updateGL(), чтобы перерисовать изображение. 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]; } Функция faceAtPosition() возвращает либо номер грани, находящейся в заданных координатах, либо -1, если точка с заданными координатами не входит ни в одну из граней. Код, выполняющий проверку, достаточно сложен. По сути -- он переводит сцену в режим GL_SELECT, чтобы мы могли воспользоваться дополнительными возможностями OpenGL, и отыскивает номер грани ("name").

Далее приводится содержимое файла main.cpp:

#include <qapplication.h> #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(); } Если система не поддерживает OpenGL, то, с помощью вызова qFatal(), приложение выводит сообщение об ошибке и завершает работу.

Чтобы связать приложение Cube с модулем QGL и библиотекой OpenGL, в файл .pro нужно добавить строчку:

CONFIG += opengl За дополнительной информацией о модуле QGL, обращайтесь к сопроводительной документации по классам QGLWidget, QGLFormat, QGLContext и QGLColormap.