OpenGL на Qt4. Это просто!
Это ознакомительная статья, посвящённая программированию 3D-графики OpenGL (Open Graphics Library - открытая графическая библиотека) с помощью кроссплатформенной библиотеки Qt4 (Q и Trolltech - красивое название). Я надеюсь, она окажется полезной для тех, кто впервые решил познакомиться с OpenGL и выбирает практичную и удобную библиотеку GUI (Graphical User Interface - графический интерфейс пользователя). Возможности библиотеки Qt далеко выходят за рамки разработки GUI и она считается одной из самых успешных библиотек для разработки кроссплатформенных приложений для различных целей. Конечно же, статья направлена на популяризацию Qt как API (Application Programming Interface - интерфейс прикладного программирования) для создания трёхмерной графики. Поэтому эта статья также окажется полезной для профессиональных программистов OpenGL, незнакомых с возможностями Qt4, и программистов Qt, не работавших с OpenGL. От читателя потребуется знание языка C++. Изложение будет построено таким образом: сначала мы рассмотрим основы вместе с примерами - кусками кода. И в конце будет написана обобщающая всё сказанное программа.
Библиотека Qt4 имеет специальный модуль для работы с OpenGL - QtOpenGL. В модуле QtOpenGL содержатся следующие классы: QGLColormap, QGLContext,QGLFormat, QGLFramebufferObject, QGLPixelBuffer, QGLWidget, QWSGLWindowSurface.
Классы подключаются стандартным образом:
#include <QGLWidget> // подключаем класс QGLWidget
#include <QtOpenGL> // подключаем весь модуль QtOpenGL, он уже содержит класс QGLWidget
QGLWidget
QGLWidget::QGLWidget(QWidget* pwgt = 0) // конструктор класса, создает объект класса
virtual void QGLWidget::initializeGL() // устанавливает начальные значения
virtual void QGLWidget::resizeGL(int width, int height) // отвечает за изменение размеров окна виджета
virtual void QGLWidget::paintGL() // рисует изображение
virtual void QGLWidget::updateGL() // вызывает обновление изображения
#include <QGLWidget> // подключаем класс QGLWidget
class Scene3D : public QGLWidget // новый класс Scene3D наследует встроенный класс QGLWidget
{
private:
//...
protected:
/*virtual*/ void initializeGL(); // метод для проведения инициализаций, связанных с OpenGL
/*virtual*/ void resizeGL(int nWidth, int nHeight); // метод вызывается при изменении размеров окна
/*virtual*/ void paintGL(); // метод, чтобы заново перерисовать содержимое виджета
public:
Scene3D(QWidget* pwgt = 0); // конструктор класса Scene3D
};
Scene3D::Scene3D(QWidget* pwgt/*= 0*/) : QGLWidget(pwgt) // конструктор класса Scene3D
{
// передает дальше указатель pwgt
}
#include <QtOpenGL> // заранее подключили весь модуль QtOpenGL
//...
/*virtual*/ void Scene3D::initializeGL() // инициализация
{
qglClearColor(Qt::white); // цвет для очистки буфера изображения - здесь просто фон окна
glEnable(GL_DEPTH_TEST); // устанавливает режим проверки глубины объектов
// glShadeModel(GL_FLAT); // отключает режим сглаживания цветов
glEnable(GL_CULL_FACE); // устанавливается режим, когда строятся только внешние поверхности
}
#include <QtGui> // подключаем модуль QtGui, он содержит класс QColor
//...
QColor burmaline(195, 155, 175, 255); // бурмалиновый цвет, последний аргумент прозрачность
/*virtual*/ void Scene3D::resizeGL(int nWidth, int nHeight) // окно виджета
{
glMatrixMode(GL_PROJECTION); // устанавливает текущей проекционную матрицу
glLoadIdentity(); // присваивает проекционной матрице единичную матрицу
// мировое окно
glOrtho(-1.0, 1.0, -1.0, 1.0, -10.0, 1.0); // параметры видимости ортогональной проекции
// плоскости отсечения (левая, правая, верхняя, нижняя, передняя, задняя)
// glFrustum(-1.0, 1.0, -1.0, 1.0, 1.0, 10.0); // параметры видимости перспективной проекции
// плоскости отсечения: (левая, правая, верхняя, нижняя, ближняя, дальняя)
// поле просмотра
glViewport(0, 0, (GLint)nWidth, (GLint)nHeight); // устанавливает видовое окно с размерами равными окну виджета
}
/*virtual*/ void Scene3D::paintGL() // рисование
{
// glClear(GL_COLOR_BUFFER_BIT); // окно виджета очищается текущим цветом очистки
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // очистка буфера изображения и глубины
glMatrixMode(GL_MODELVIEW); // устанавливает положение и ориентацию матрице моделирования
glLoadIdentity(); // загружает единичную матрицу моделирования
// последовательные преобразования
glRotatef(xRotate, 1.0f, 0.0f, 0.0f); // поворот вокруг оси X
glRotatef(yRotate, 0.0f, 1.0f, 0.0f); // поворот вокруг оси Y
glRotatef(zRotate, 0.0f, 0.0f, 1.0f); // поворот вокруг оси Z
glTranslatef(xTransl, yTransl, zTransl); // трансляция
glScalef(xScale, yScale, zScale); // масштабирование по осям
example_drawAxis(); // рисование осей координат
}
class Scene3D : public QGLWidget // новый класс Scene3D наследует встроенный класс QGLWidget
{
private:
void example_drawAxis();
//...
//...
};
void Scene3D::example_drawAxis()
{
glLineWidth(3); // устанавливаю ширину линии в пикселях
// до вызова здесь команды ширина была равна 1 пикселю по умолчанию
// ось x красного цвета
glColor4f(1.00f, 0.00f, 0.00f, 1.0f); // устанавливается цвет последующих примитивов
glBegin(GL_LINES); // построение линии
glVertex3f( 1, 0, 0); // первая точка
glVertex3f(-1, 0, 0); // вторая точка
glEnd();
// ось y зеленого цвета
QColor halfGreen(0, 128, 0, 255);
qglColor(halfGreen);
glBegin(GL_LINES);
glVertex3f( 0, 1, 0);
glVertex3f( 0, -1, 0);
glEnd();
// ось z синего цвета
glBegin(GL_LINES);
glColor4f(0.00f, 0.00f, 1.00f, 1.0f);
glVertex3f( 0, 0, 1);
glVertex3f( 0, 0, -1);
glEnd();
}
void Scene3D::example_calculate()
{
// вычисления
// преобразования
// ...
updateGL(); // вызывается функция paintGL() -> перерисование сцены -> обновление изображения
}
#ifndef SCENE3D_H
#define SCENE3D_H
#include <QGLWidget> // подключаем класс QGLWidget
class Scene3D : public QGLWidget // класс Scene3D наследует встроенный класс QGLWidget
{
private:
GLfloat xRotate; // переменная хранит угол поворота вокруг оси X
GLfloat yRotate; // переменная хранит угол поворота вокруг оси Y
GLfloat zRotate; // переменная хранит угол поворота вокруг оси Z
GLfloat zTransl; // переменная хранит величину трансляции оси Z
GLfloat nScale; // переменная отвечает за масштабирование обьекта
QPoint ptrMousePosition; // переменная хранит координату указателя мыши в момент нажатия
void scale_plus(); // приблизить сцену
void scale_minus(); // удалиться от сцены
void rotate_up(); // повернуть сцену вверх
void rotate_down(); // повернуть сцену вниз
void rotate_left(); // повернуть сцену влево
void rotate_right(); // повернуть сцену вправо
void translate_down(); // транслировать сцену вниз
void translate_up(); // транслировать сцену вверх
void defaultScene(); // наблюдение сцены по умолчанию
void drawAxis(); // построить оси координат
void getVertexArray(); // определить массив вершин
void getColorArray(); // определить массив цветов вершин
void getIndexArray(); // определить массив индексов вершин
void drawFigure(); // построить фигуру
protected:
/*virtual*/ void initializeGL(); // метод для проведения инициализаций, связанных с OpenGL
/*virtual*/ void resizeGL(int nWidth, int nHeight); // метод вызывается при изменении размеров окна виджета
/*virtual*/ void paintGL(); // метод, чтобы заново перерисовать содержимое виджета
/*virtual*/ void mousePressEvent(QMouseEvent* pe); // методы обработки события мыши при нажатии клавиши мыши
/*virtual*/ void mouseMoveEvent(QMouseEvent* pe); // методы обработки события мыши при перемещении мыши
/*virtual*/ void mouseReleaseEvent(QMouseEvent* pe); // методы обработки событий мыши при отжатии клавиши мыши
/*virtual*/ void wheelEvent(QWheelEvent* pe); // метод обработки событий колесика мыши
/*virtual*/ void keyPressEvent(QKeyEvent* pe); // методы обработки события при нажатии определенной клавиши
public:
Scene3D(QWidget* pwgt = 0); // конструктор класса (= 0 -- главное окно)
};
#endif
#include <QtGui> // подключаем модуль QtGui
//#include <QtCore> // подключаем модуль QtCore
//#include <QtOpenGL> // подключаем модуль QtOpenGL
#include <math.h> // подключаем математическую библиотеку
#include "scene3D.h" // подключаем заголовочный файл scene3D.h
const static float pi=3.141593, k=pi/180; // глобальная переменная
GLfloat VertexArray[11][3]; // декларируем массив вершин
GLfloat ColorArray[11][3]; // декларируем массив цветов вершин
GLubyte IndexArray[20][3]; // декларируем массив индексов вершин
Scene3D::Scene3D(QWidget* pwgt/*= 0*/) : QGLWidget(pwgt) // конструктор класса Scene3D
{
// передает дальше указатель на объект pwgt
// начальные значения
xRotate=-90; yRotate=0; zRotate=0; zTransl=0; nScale=1;
}
/*virtual*/ void Scene3D::initializeGL() // инициализация
{
qglClearColor(Qt::white); // цвет для очистки буфера изображения - здесь просто фон окна
glEnable(GL_DEPTH_TEST); // устанавливает режим проверки глубины объектов
glShadeModel(GL_FLAT); // отключает режим сглаживания цветов
glEnable(GL_CULL_FACE); // устанавливается режим, когда строятся только внешние поверхности
getVertexArray(); // определить массив вершин
getColorArray(); // определить массив цветов вершин
getIndexArray(); // определить массив индексов вершин
glEnableClientState(GL_VERTEX_ARRAY); // активизация массива вершин
glEnableClientState(GL_COLOR_ARRAY); // активизация массива цветов вершин
}
/*virtual*/void Scene3D::resizeGL(int nWidth, int nHeight) // окно виджета
{
glMatrixMode(GL_PROJECTION); // устанавливает текущей проекционную матрицу
glLoadIdentity(); // присваивает проекционной матрице единичную матрицу
GLfloat ratio=(GLfloat)nHeight/(GLfloat)nWidth; // отношение высоты окна виджета к его ширине
// мировое окно
if (nWidth>=nHeight)
glOrtho(-1.0/ratio, 1.0/ratio, -1.0, 1.0, -10.0, 1.0); // параметры видимости ортогональной проекции
else
glOrtho(-1.0, 1.0, -1.0*ratio, 1.0*ratio, -10.0, 1.0); // параметры видимости ортогональной проекции
// плоскости отсечения (левая, правая, верхняя, нижняя, передняя, задняя)
// glFrustum(-1.0, 1.0, -1.0, 1.0, 1.0, 10.0); // параметры видимости перспективной проекции
// плоскости отсечения (левая, правая, верхняя, нижняя, ближняя, дальняя)
// поле просмотра
glViewport(0, 0, (GLint)nWidth, (GLint)nHeight);
}
/*virtual*/ void Scene3D::paintGL() // рисование
{
// glClear(GL_COLOR_BUFFER_BIT); // окно виджета очищается текущим цветом очистки
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // очистка буфера изображения и глубины
glMatrixMode(GL_MODELVIEW); // устанавливает положение и ориентацию матрице моделирования
glLoadIdentity(); // загружает единичную матрицу моделирования
// последовательные преобразования
glRotatef(xRotate, 1.0f, 0.0f, 0.0f); // поворот вокруг оси X
glRotatef(yRotate, 0.0f, 1.0f, 0.0f); // поворот вокруг оси Y
glRotatef(zRotate, 0.0f, 0.0f, 1.0f); // поворот вокруг оси Z
glTranslatef(0.0f, 0.0f, zTransl); // трансляция
glScalef(nScale, nScale, nScale); // масштабирование
drawAxis(); // рисование осей координат
drawFigure(); // нарисовать фигуру
}
/*virtual*/void Scene3D::mousePressEvent(QMouseEvent* pe) // нажатие клавиши мыши
{
ptrMousePosition = pe->pos(); // при нажатии пользователем кнопки мыши переменной ptrMousePosition будет
// присвоена координата указателя мыши
// ptrMousePosition = (*pe).pos(); // можно и так написать
}
/*virtual*/void Scene3D::mouseReleaseEvent(QMouseEvent* pe) // отжатие клавиши мыши
{
// некоторые функции, которые должны выполняться при отжатии клавиши мыши
}
/*virtual*/void Scene3D::mouseMoveEvent(QMouseEvent* pe) // изменение положения стрелки мыши
{
xRotate += 180/nScale*(GLfloat)(pe->y()-ptrMousePosition.y())/height(); // вычисление углов поворота
zRotate += 180/nScale*(GLfloat)(pe->x()-ptrMousePosition.x())/width();
ptrMousePosition = pe->pos();
updateGL(); // обновление изображения
}
/*virtual*/void Scene3D::wheelEvent(QWheelEvent* pe) // вращение колёсика мыши
{
if ((pe->delta())>0) nScale = nScale*1.1; else if ((pe->delta())<0) nScale = nScale/1.1;
updateGL(); // обновление изображения
}
/*virtual*/void Scene3D::keyPressEvent(QKeyEvent* pe) // нажатие определенной клавиши
{
switch (pe -> key())
{
case Qt::Key_Plus:
scale_plus(); // приблизить сцену
break;
case Qt::Key_Equal:
scale_plus(); // приблизить сцену
break;
case Qt::Key_Minus:
scale_minus(); // удалиться от сцены
break;
case Qt::Key_Up:
rotate_up(); // повернуть сцену вверх
break;
case Qt::Key_Down:
rotate_down(); // повернуть сцену вниз
break;
case Qt::Key_Left:
rotate_left(); // повернуть сцену влево
break;
case Qt::Key_Right:
rotate_right(); // повернуть сцену вправо
break;
case Qt::Key_Z:
translate_down(); // транслировать сцену вниз
break;
case Qt::Key_X:
translate_up(); // транслировать сцену вверх
break;
case Qt::Key_Space: // клавиша пробела
defaultScene(); // возвращение значений по умолчанию
break;
}
updateGL(); // обновление изображения
}
void Scene3D::scale_plus() // приблизить сцену
{
nScale = nScale*1.1;
}
void Scene3D::scale_minus() // удалиться от сцены
{
nScale = nScale/1.1;
}
void Scene3D::rotate_up() // повернуть сцену вверх
{
xRotate += 1.0;
}
void Scene3D::rotate_down() // повернуть сцену вниз
{
xRotate -= 1.0;
}
void Scene3D::rotate_left() // повернуть сцену влево
{
zRotate += 1.0;
}
void Scene3D::rotate_right() // повернуть сцену вправо
{
zRotate -= 1.0;
}
void Scene3D::translate_down() // транслировать сцену вниз
{
zTransl -= 0.05;
}
void Scene3D::translate_up() // транслировать сцену вверх
{
zTransl += 0.05;
}
void Scene3D::defaultScene() // наблюдение сцены по умолчанию
{
xRotate=-90; yRotate=0; zRotate=0; zTransl=0; nScale = 1;
}
void Scene3D::drawAxis() // построить оси координат
{
glLineWidth(3); // устанавливаю ширину линии в пикселях
// до вызова команды ширина равна 1 пикселю по умолчанию
// ось X красного цвета
glColor4f(1.00f, 0.00f, 0.00f, 1.0f); // устанавливается цвет последующих примитивов
glBegin(GL_LINES); // построение линии
glVertex3f( 1, 0, 0); // первая точка
glVertex3f(-1, 0, 0); // вторая точка
glEnd();
// ось Y зеленого цвета цвета
QColor halfGreen(0, 128, 0, 255);
qglColor(halfGreen);
glBegin(GL_LINES);
glVertex3f( 0, 1, 0);
glVertex3f( 0, -1, 0);
glEnd();
// ось Z синего цвета
glBegin(GL_LINES);
glColor4f(0.00f, 0.00f, 1.00f, 1.0f);
glVertex3f( 0, 0, 1);
glVertex3f( 0, 0, -1);
glEnd();
}
void Scene3D::getVertexArray() // определить массив вершин
{
GLfloat R=0.75; // радиус сферы
// начальные значения для икосаэдра
GLfloat a=4*R/sqrt(10+2*sqrt(5)); // сторона икосаэдра
GLfloat alpha=acos((1-a*a/2/R/R)); // первый угол поворота по тэта // cos(alpha)=(1-a*a/2/R/R)
// вычисляем точки икосаэдра
//0 точка
VertexArray[0][0]=0; // x
VertexArray[0][1]=0; // y
VertexArray[0][2]=R; // z
//1 точка
VertexArray[1][0]=R*sin(alpha)*sin(0);
VertexArray[1][1]=R*sin(alpha)*cos(0);
VertexArray[1][2]=R*cos(alpha);
//2 точка
VertexArray[2][0]=R*sin(alpha)*sin(72*k);
VertexArray[2][1]=R*sin(alpha)*cos(72*k);
VertexArray[2][2]=R*cos(alpha);
//3 точка
VertexArray[3][0]=R*sin(alpha)*sin(2*72*k);
VertexArray[3][1]=R*sin(alpha)*cos(2*72*k);
VertexArray[3][2]=R*cos(alpha);
//4 точка
VertexArray[4][0]=R*sin(alpha)*sin(3*72*k);
VertexArray[4][1]=R*sin(alpha)*cos(3*72*k);
VertexArray[4][2]=R*cos(alpha);
//5 точка
VertexArray[5][0]=R*sin(alpha)*sin(4*72*k);
VertexArray[5][1]=R*sin(alpha)*cos(4*72*k);
VertexArray[5][2]=R*cos(alpha);
//6 точка
VertexArray[6][0]=R*sin(pi-alpha)*sin(-36*k);
VertexArray[6][1]=R*sin(pi-alpha)*cos(-36*k);
VertexArray[6][2]=R*cos(pi-alpha);
//7 точка
VertexArray[7][0]=R*sin(pi-alpha)*sin(36*k);
VertexArray[7][1]=R*sin(pi-alpha)*cos(36*k);
VertexArray[7][2]=R*cos(pi-alpha);
//8 точка
VertexArray[8][0]=R*sin(pi-alpha)*sin((36+72)*k);
VertexArray[8][1]=R*sin(pi-alpha)*cos((36+72)*k);
VertexArray[8][2]=R*cos(pi-alpha);
//9 точка
VertexArray[9][0]=R*sin(pi-alpha)*sin((36+2*72)*k);
VertexArray[9][1]=R*sin(pi-alpha)*cos((36+2*72)*k);
VertexArray[9][2]=R*cos(pi-alpha);
//10 точка
VertexArray[10][0]=R*sin(pi-alpha)*sin((36+3*72)*k);
VertexArray[10][1]=R*sin(pi-alpha)*cos((36+3*72)*k);
VertexArray[10][2]=R*cos(pi-alpha);
//11 точка
VertexArray[11][0]=0;
VertexArray[11][1]=0;
VertexArray[11][2]=-R;
}
void Scene3D::getColorArray() // определить массив цветов вершин
{
for (int i=0; i<12; i++)
{
ColorArray[i][0]=0.1f*(qrand()%11); // qrand()%11 - псевдослучайное число от 0 до 10
ColorArray[i][1]=0.1f*(qrand()%11);
ColorArray[i][2]=0.1f*(qrand()%11);
}
}
void Scene3D::getIndexArray() // определить массив индексов
{
// 0 треугольник
IndexArray[0][0]=0;
IndexArray[0][1]=2;
IndexArray[0][2]=1;
// 1 треугольник
IndexArray[1][0]=0;
IndexArray[1][1]=3;
IndexArray[1][2]=2;
// 2 треугольник
IndexArray[2][0]=0;
IndexArray[2][1]=4;
IndexArray[2][2]=3;
// 3 треугольник
IndexArray[3][0]=0;
IndexArray[3][1]=5;
IndexArray[3][2]=4;
// 4 треугольник
IndexArray[4][0]=0;
IndexArray[4][1]=1;
IndexArray[4][2]=5;
// 5 треугольник
IndexArray[5][0]=6;
IndexArray[5][1]=1;
IndexArray[5][2]=7;
// 6 треугольник
IndexArray[6][0]=7;
IndexArray[6][1]=1;
IndexArray[6][2]=2;
// 7 треугольник
IndexArray[7][0]=7;
IndexArray[7][1]=2;
IndexArray[7][2]=8;
// 8 треугольник
IndexArray[8][0]=8;
IndexArray[8][1]=2;
IndexArray[8][2]=3;
// 9 треугольник
IndexArray[9][0]=8;
IndexArray[9][1]=3;
IndexArray[9][2]=9;
// 10 треугольник
IndexArray[10][0]=9;
IndexArray[10][1]=3;
IndexArray[10][2]=4;
// 11 треугольник
IndexArray[11][0]=9;
IndexArray[11][1]=4;
IndexArray[11][2]=10;
// 12 треугольник
IndexArray[12][0]=10;
IndexArray[12][1]=4;
IndexArray[12][2]=5;
// 13 треугольник
IndexArray[13][0]=10;
IndexArray[13][1]=5;
IndexArray[13][2]=6;
// 14 треугольник
IndexArray[14][0]=6;
IndexArray[14][1]=5;
IndexArray[14][2]=1;
// 15 треугольник
IndexArray[15][0]=7;
IndexArray[15][1]=11;
IndexArray[15][2]=6;
// 16 треугольник
IndexArray[16][0]=8;
IndexArray[16][1]=11;
IndexArray[16][2]=7;
// 17 треугольник
IndexArray[17][0]=9;
IndexArray[17][1]=11;
IndexArray[17][2]=8;
// 18 треугольник
IndexArray[18][0]=10;
IndexArray[18][1]=11;
IndexArray[18][2]=9;
// 19 треугольник
IndexArray[19][0]=6;
IndexArray[19][1]=11;
IndexArray[19][2]=10;
}
void Scene3D::drawFigure() // построить фигуру
{
glVertexPointer(3, GL_FLOAT, 0, VertexArray); // указываем, откуда нужно извлечь данные о массиве вершин
glColorPointer(3, GL_FLOAT, 0, ColorArray); //указываем, откуда нужно извлечь данные о массиве цветов вершин
glDrawElements(GL_TRIANGLES, 60, GL_UNSIGNED_BYTE, IndexArray); // строим поверхности, используя массивы вершин и индексов
}
#include <QApplication>
#include "scene3D.h"
int main(int argc, char** argv)
{
QApplication app(argc, argv);
Scene3D scene1; // создаём виджет класса Scene3D
scene1.resize(500, 500);
scene1.show();
// scene1.showFullScreen();
// scene1.showMaximized();
return app.exec();
}
Для таких статей есть http://wiki.crossplatform.ru/index.php/%D0%97%D0%B0%D0%B3%D0%BB%D0%B0%D0%B2%D0%BD%D0%B0%D1%8F_%D1%81%D1%82%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D0%B0
Спойлеры бы! А то колесико аж горит.
И весь проэкт архивом, что бы по-тестить, тоже хорошо бы.
Показали бы как с помощью Qt и OpenGL написать шейдер Bump Mapping'a или импортировать сцену из Blender'a.
Статья исправленная/редактированная на Вики http://www.wiki.crossplatform.ru/index.php/OpenGL_%D0%BD%D0%B0_Qt4
Спасибо!!!
Форум Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)