Предлагаю попробовать разобрать основы работы с Qt OpenGL, понять последовательность вызова функций и получить набор «начальных инструментов»
Вводные слова
Не столь давно я заинтересовался одной вещью – работа с графическими библиотеками.
В качестве «подопытного» был выбран язык Qt и графическая библиотека OpenGL.
По данной теме на хабре я нашел только одну статью. Этот пост, на мой взгляд, имеет право на существование, но хотелось бы описать свой опыт, благо, даже повод есть – пришла пора писать курсовую.
Ознакомиться с тем, что получилось (под Windows), можно здесь: архив с программой.
К чему эта статья: «Чтобы рассказать о самых-самых азах Qt OpenGL и предложить «инструменты» для «пытающихся»»
Приступаем к знакомству
Где можно почерпнуть информацию по данной теме? Я воспользовался прекрасной, встроенной в Qt помощью, одним из Demo проектов Qt – OpenGL -> OverPainting, документацией по OpenGL на http://www.opengl.org и (неким русским вариантом) http://opengl.gamedev.ru/
Итак. Qt OpenGL.
Создаем новый проект и добавляем класс, который будет наследоваться от QGLWidget. Для корректной работы проверяем наличие: (данные файлы пригодятся чуть позже)
#include <QGLWidget>
#include <QtOpenGL>
#include <QBrush>
#include <QImage>
#include <QTimer>
#include <QtGui>
Теперь переходим к разбору последовательных вызовов в OpenGL:
При первоначальном создании виджета происходит следующий порядок последовательных вызовов:
- Конструктор виджета
- glDraw()
При изменении размеров окна виджета вызывается также функция glDraw(). Разберем данную функцию. glDraw() (а также и glInit()) принадлежат классу QGLWidget. Их не перегружают. Данные функции вызывают следующие методы: initializeGL(), resizeGL(), paintGL(). Эти три функции переопределяются программистом.
Посмотрим на данные функции:
Инициализация
initializeGL() – позволяет инициализировать (если по каким-то причинам не сделали в конструкторе) переменные, включить определенные возможности графической библиотеки(glEnable) и т.п.
Ресайз
resizeGL – реакция виджета на изменение размеров окна. Как правило, данная функция принимает два параметра – ширину и высоту. В ней необходимо задать область просмотра с помощью glViewport(x,y,width,height), установить матричный режим glMatrixMode(mode), где mode – матричный режим, который может быть одним из четырех:
- GL_MODELVIEW — объектно-видовая матрица.
- GL_PROJECTION — матрица проекций.
- GL_TEXTURE — текстурная матрица.
- GL_COLOR — цветовая матрица.
Прорисовка
Третья функция, подлежащая разбору – paintGL(). Оговорюсь сразу, функцию paintGL() можно заменить на аналогичную paintEvent(QPaintEvent *event) – ее преимущество, на мой взгляд, в том, что в нее передается параметр QPaintEvent*, с помощью которого мы можем получить QRect(прямоугольник), в котором происходит прорисовка (к примеру).
И в случае с paintGL, и с paintEvent работа выполняется аналогично. А именно – затираем предыдущий кадр:
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
qglClearColor(QColor(100,150,80));
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
В данном примере — поместили матрицу в стек, очистили кадр, «забрали» матрицу из стека.
После «затирки» необходимо создать следующий кадр (собственно, за что paintGL и отвечает)
Рисовать будем следующим образом – создадим переменную типа QPainter, у которой QPaintDevice* =this, т.е.
QPainter* painter = new QPainter(this);
С помощью данной переменной и будем рисовать кадр.
Прорисовка будет иметь следующий вид:
- Устанавливаем кисть (QBrush)
- Рисуем, используя (к примеру) геометрические фигуры.
Разберем прорисовку блока и окружности с помощью данного способа
Окружность:
painter->save(); //сохранили состояние QPainter
painter->translate(position.x() - radius, position.y() - radius);//переводим систему координат, смещая ее на заданные параметры x и y
/* Теперь блок, в котором создаем градиентную заливку (для кисти). Как рекомендация – лучше вынести данную работу в отдельную функцию и создать переменную для кисти (типа QBrush),
чтобы не терять лишнее время. */
QRadialGradient gradient(QPointF(radius, radius), radius,
QPointF(radius*0.5, radius*0.5));
gradient.setColorAt(0, QColor(255, 255, 255, 255));
gradient.setColorAt(0.25, innerColor);
gradient.setColorAt(1, outerColor);
//Конец блока описания заливки
painter->setBrush(QBrush(gradient));//установили кисть
painter->drawEllipse(0, 0, int(2*radius), int(2*radius));//Нарисовали эллипс (в данном случае окружность)
painter->restore();// Восстановили состояние QPainter
Для прямоугольника работа будет почти такой же, изменению подвергнется только drawEllipse. Он изменится на drawRect
Описывать входные параметры для них я не буду, т.к. они более-менее понятны, да и встроенная помощь Qt c этим отлично справится.
Остановимся немного на QBrush. Было бы невесело, если бы он мог работать с одним цветом или градиентной заливкой. Поэтому, предупреждая вопрос о прорисовке картинок, скажу – QBrush позволяет «грузить» картинку. В качестве примера:
QBrush(QImage("Theme.bmp"));
В заключении о QPainter скажу – необходимо следить за работой с ним, а именно – то, что прорисовано выше по коду, будет соответственно отображаться «под» описанным ниже и наоборот. И после работы с данной переменной имеет смысл логически завершить ее работу, вызвав метод end();
Вот три основные функции, которые программист должен переопределить в своем проекте. Замечу, что данные функции не вызываются периодически, и поэтому, если на экране происходит какое-то действие, то необходимо ввести таймер, который по срабатыванию будет вызывать update(). Эта функция вызовет функцию прорисовки.
Заключение
В данном небольшом посте я попытался немного рассказать о простейших вещах, которые обязательно пригодятся для работы с Qt OpenGL. Надеюсь, что данная статься поможет сформировать цепочку вызовов функций и упростит новичкам создание первых приложений с использованием OpenGL
Если у кого-то появился интерес к реализованному небольшому приложению, то исходники (.h и .cpp) можно просмотреть по следующему адресу — гитхаб
Автор: xnim