Редактирование: Работа с OpenGL на Qt 4 (часть 1)
Материал из Wiki.crossplatform.ru
Внимание: Вы не представились системе. Ваш IP-адрес будет записан в историю изменений этой страницы.
ПРЕДУПРЕЖДЕНИЕ: Длина этой страницы составляет 67 килобайт. Страницы, размер которых приближается к 32 КБ или превышает это значение, могут неверно отображаться в некоторых браузерах. Пожалуйста, рассмотрите вариант разбиения страницы на меньшие части.
Правка может быть отменена. Пожалуйста, просмотрите сравнение версий, чтобы убедиться, что это именно те изменения, которые вас интересуют, и нажмите «Записать страницу», чтобы изменения вступили в силу.
Текущая версия | Ваш текст | ||
Строка 12: | Строка 12: | ||
==Модуль QtOpenGL== | ==Модуль QtOpenGL== | ||
- | Библиотека Qt4 имеет специальный модуль для работы с OpenGL — '''QtOpenGL'''. Модуль QtOpenGL реализован как | + | Библиотека Qt4 имеет специальный модуль для работы с OpenGL — '''QtOpenGL'''. Модуль QtOpenGL реализован как платформонезависимый Qt/C++ упаковщик (стандартизация внешних обращений) над платформозависимыми GLX (OpenGL в Linux), WGL (OpenGL в Windows) или AGL (OpenGL в MacOS) API. Функциональность модуля QtOpenGL очень схожа с библиотекой GLUT, но при этом вам предоставляются все возможности Qt, которые несвязаны с OpenGL. В модуле QtOpenGL версии Qt 4.7.1 определены следующие классы: QGLBuffer, QGLColormap, QGLContext,QGLFormat, QGLFramebufferObject, QGLFramebufferObjectFormat, QGLPixelBuffer, QGLShader, QGLShaderProgram, QGLWidget, QWSGLWindowSurface. |
Классы подключаются стандартным образом: | Классы подключаются стандартным образом: | ||
Строка 33: | Строка 33: | ||
''Конструктор класса:'' | ''Конструктор класса:'' | ||
<source lang="cpp-qt"> | <source lang="cpp-qt"> | ||
- | QGLWidget::QGLWidget(QWidget* | + | QGLWidget::QGLWidget(QWidget* pwgt = 0) // конструктор класса, создает объект класса |
</source> | </source> | ||
---- | ---- | ||
Строка 58: | Строка 58: | ||
updateGL()-->glDraw()-->{?glInit(), ?resizeGL(), paintGL(), ...} | updateGL()-->glDraw()-->{?glInit(), ?resizeGL(), paintGL(), ...} | ||
- | Знак вопроса (?) на схеме условно означает, что в этих местах проверяется, нужно ли сейчас вызвать эти функции или нет. Более полную и подробную информацию о классе QGLWidget можно найти в справке Qt Assistant, который всегда поставляется с Qt, а ещё | + | Знак вопроса (?) на схеме условно означает, что в этих местах проверяется, нужно ли сейчас вызвать эти функции или нет. Более полную и подробную информацию о классе QGLWidget можно найти в справке Qt Assistant, который всегда поставляется с Qt, а ещё более подробно смотрите в исходных текстах Qt. |
- | Аргументом конструктора класса является указатель на объект класса QWidget. Класс QWidget обеспечивает работу с виджетами (интерфейсными элементами окна), а класс QGLWidget унаследован от QWidget и осуществляет связь виджетов и OpenGL. | + | Аргументом конструктора класса является указатель на объект класса QWidget. Класс QWidget обеспечивает работу с виджетами (интерфейсными элементами окна), а класс QGLWidget унаследован от QWidget и осуществляет связь виджетов и OpenGL. Значение *pwgt=0 указывает, что виджет будет просто окном. Пример: |
---- | ---- | ||
<source lang="cpp-qt"> | <source lang="cpp-qt"> | ||
Строка 76: | Строка 76: | ||
public: | public: | ||
- | Scene3D(QWidget* | + | Scene3D(QWidget* pwgt = 0) ; // конструктор класса Scene3D |
}; | }; | ||
//... | //... | ||
- | Scene3D::Scene3D(QWidget* | + | Scene3D::Scene3D(QWidget* pwgt/*= 0*/) : QGLWidget(pwgt) // конструктор класса Scene3D |
{ | { | ||
- | // передает дальше указатель | + | // передает дальше указатель pwgt |
} | } | ||
</source> | </source> | ||
---- | ---- | ||
- | + | Функции initializeGL(), resizeGL(), paintGL() являются виртуальными функциями (подробнее о динамическом полиморфизме можно прочитать, например, в книге: Аверкин В.П., Бобровский А.И., Веснич В.В., Радушинский В.Ф., Хомоненко А.Д. «Программирование на C++»). Следовательно, в классе-наследнике Scene3D обозначать функции как virtual не нужно. Эти три функции изначально имеют полностью пустое тело, которое нужно заполнить командами OpenGL. Здесь нужно отметить, что функции glDraw() и glInit() принадлежат классу QGLWidget, а не к OpenGL, как можно было бы подумать из префикса gl. Функции glDraw() и glInit() для пользовательской работы не нужны, они просто вызывают initializeGL(), resizeGL(), paintGL(), о которых и пойдёт речь далее. | |
- | + | ||
- | + | ||
- | + | ||
- | + | ||
===Инициализация контекста рендеринга=== | ===Инициализация контекста рендеринга=== | ||
Строка 98: | Строка 94: | ||
---- | ---- | ||
<source lang="cpp-qt"> | <source lang="cpp-qt"> | ||
+ | #include <QtOpenGL> // заранее подключили весь модуль QtOpenGL | ||
+ | |||
+ | //... | ||
+ | |||
/*virtual*/ void Scene3D::initializeGL() // инициализация | /*virtual*/ void Scene3D::initializeGL() // инициализация | ||
{ | { | ||
Строка 119: | Строка 119: | ||
В соответствии с правилами обозначения в OpenGL значение 1.0f обрабатывается как тип GLfloat (собственный тип OpenGL). Зачем нужны собственные типы? Дело в том, что различные компиляторы и платформы по-разному распределяют память под стандартные типы и программист должен всегда это держать в уме. Собственные типы OpenGL обрабатываются везде одинаково и освобождают нас от этого обременительного занятия. | В соответствии с правилами обозначения в OpenGL значение 1.0f обрабатывается как тип GLfloat (собственный тип OpenGL). Зачем нужны собственные типы? Дело в том, что различные компиляторы и платформы по-разному распределяют память под стандартные типы и программист должен всегда это держать в уме. Собственные типы OpenGL обрабатываются везде одинаково и освобождают нас от этого обременительного занятия. | ||
- | Следующая функция glEnable(GL_DEPTH_TEST) является уже функцией OpenGL, что можно понять из приставки gl в отличие от qgl для первой функции. | + | Следующая функция glEnable(GL_DEPTH_TEST) является уже функцией OpenGL, что можно понять из приставки gl в отличие от qgl для первой функции. glEnable(GL_DEPTH_TEST) устанавливает режим проверки глубины объектов, известных также как z-буфер (z-buffer) или буфер глубины. Суть его заключается в том, что каждому пикселю объекта на экране с координатами x и y даётся еще координата z, характеризующая расстояние до наблюдателя. Как раз значения глубины для каждого пикселя объекта хранятся в буфере глубины. Таким образом, одни объекты могут быть ближе к нам, а другие дальше, а значит, ближние объекты могут закрыть собой дальние. Зачем рисовать дальние объекты, если они невидны? Правильно, незачем! Поэтому нужно иметь метод, чтобы не рисовать скрытые (невидимые) поверхности («закрытые» пиксели). Если при рисовании сравнивать глубину для каждого пикселя всех объектов, то можно определить виден ли конкретный пиксель объекта на экране или скрыт. Как раз это и выполняет glEnable(GL_DEPTH_TEST), т.е. производит сравнение. Также нужно будет сообщить системе об использовании буфера глубины. Qt автоматически в конструкторе класса Scene3D по умолчанию задаст такой формат в контекст, т.е. использовать буфер глубины. Поэтому буфер глубины можно явно и не подключать, он подключится сам. Использование буфера глубины и сравнение глубины повышают скорость вывода изображения за счёт невидимых поверхностей. |
Закомментированная функция glShadeModel(GL_FLAT) отключает режим сглаживания цветов, который всегда установлен по умолчанию. Если вершины имеют разный цвет, то цвет между ними будет плавно переходить из одного в другой. Отключать режим сглаживания нет необходимости, поэтому функция закомментирована и не используется. | Закомментированная функция glShadeModel(GL_FLAT) отключает режим сглаживания цветов, который всегда установлен по умолчанию. Если вершины имеют разный цвет, то цвет между ними будет плавно переходить из одного в другой. Отключать режим сглаживания нет необходимости, поэтому функция закомментирована и не используется. | ||
Строка 152: | Строка 152: | ||
Функция glViewport(0, 0, (GLint)nWidth, (GLint)nHeight) определяет поле просмотра (порт просмотра) внутри окна в пикселях экрана и образует прямоугольник с левой нижней точкой (0, 0) и правой верхней точкой (nWidth, nHeight). В этом поле и будет всё рисоваться. Для нас важно, что прямоугольник поля просмотра совпадает с прямоугольником виджета окна. Так как nWidth и nHeight типа int, стоит произвести преобразования к типам GLint. | Функция glViewport(0, 0, (GLint)nWidth, (GLint)nHeight) определяет поле просмотра (порт просмотра) внутри окна в пикселях экрана и образует прямоугольник с левой нижней точкой (0, 0) и правой верхней точкой (nWidth, nHeight). В этом поле и будет всё рисоваться. Для нас важно, что прямоугольник поля просмотра совпадает с прямоугольником виджета окна. Так как nWidth и nHeight типа int, стоит произвести преобразования к типам GLint. | ||
- | Часто новички, задавая одинаковые первоначальные значения ширины и высоты, недоумевают почему их изображение растягивается по ширине при развертывании окна на весь экран. Ничего удивительного в этом нет — ширина становится больше по значению, чем высота. И OpenGL автоматически масштабирует всю проекцию сцены так, что она становится вытянутой по координате x экрана. Чуть позже в заключительном листинге обобщающей программы будет показано, как этого избежать и «сохранить квадрат квадратным». Также необходимо помнить, что отсчёт координаты y на экране производиться по-разному: из самой нижней точки вверх в OpenGL и из самой верхней точки вниз в экранных координатах в | + | Часто новички, задавая одинаковые первоначальные значения ширины и высоты, недоумевают почему их изображение растягивается по ширине при развертывании окна на весь экран. Ничего удивительного в этом нет — ширина становится больше по значению, чем высота. И OpenGL автоматически масштабирует всю проекцию сцены так, что она становится вытянутой по координате x экрана. Чуть позже в заключительном листинге обобщающей программы будет показано, как этого избежать и «сохранить квадрат квадратным». Также необходимо помнить, что отсчёт координаты y на экране производиться по-разному: из самой нижней точки вверх в OpenGL и из самой верхней точки вниз в экранных координатах в OS Windows. |
Построение изображения в OpenGL происходит по следующему принципу: (мировые координаты)-->(мировое окно)-->(поле просмотра). | Построение изображения в OpenGL происходит по следующему принципу: (мировые координаты)-->(мировое окно)-->(поле просмотра). | ||
Строка 186: | Строка 186: | ||
Далее приведены последовательные преобразования: поворот, трансляция, масштабирование. Значения (GLfloat) xRot, yRot, zRot, xTra, yTra, zTra, xSca, ySca, zSca удобно определить как private данные-члены класса и задать им начальные значения в теле конструктора класса. Как вы догадались, это углы поворота вокруг трёх осей, величины трансляции и масштабирования. Особенность углов поворота в том, что они задаются в градусах, а не в радианах. Функция glRotatef() осуществляет поворот наблюдателя на заданный угол вокруг заданной оси. Соответственно, функции glTranslatef() и glScalef() производят трансляцию сцены и масштабирование. Важно напомнить ещё раз, что любое преобразование изменяет текущие матрицы. И следующее преобразование осуществляется относительно предыдущего. Поэтому операции (поворот->трансляция) и (трансляция->поворот) приводят к разным результатам; операции (поворот1->поворот2) и (поворот2->поворот1) также могут привести к разным конечным преобразованиям. Все эти преобразования в итоге изменяют матрицу моделирования от единичной. Именно поэтому необходимо при перерисовании сцены загружать единичную матрицу, после чего будут осуществляться указанные преобразования над ней. К тому же вы можете загружать матрицы, сделанные «своими руками». | Далее приведены последовательные преобразования: поворот, трансляция, масштабирование. Значения (GLfloat) xRot, yRot, zRot, xTra, yTra, zTra, xSca, ySca, zSca удобно определить как private данные-члены класса и задать им начальные значения в теле конструктора класса. Как вы догадались, это углы поворота вокруг трёх осей, величины трансляции и масштабирования. Особенность углов поворота в том, что они задаются в градусах, а не в радианах. Функция glRotatef() осуществляет поворот наблюдателя на заданный угол вокруг заданной оси. Соответственно, функции glTranslatef() и glScalef() производят трансляцию сцены и масштабирование. Важно напомнить ещё раз, что любое преобразование изменяет текущие матрицы. И следующее преобразование осуществляется относительно предыдущего. Поэтому операции (поворот->трансляция) и (трансляция->поворот) приводят к разным результатам; операции (поворот1->поворот2) и (поворот2->поворот1) также могут привести к разным конечным преобразованиям. Все эти преобразования в итоге изменяют матрицу моделирования от единичной. Именно поэтому необходимо при перерисовании сцены загружать единичную матрицу, после чего будут осуществляться указанные преобразования над ней. К тому же вы можете загружать матрицы, сделанные «своими руками». | ||
- | Теперь можно полностью представить, как происходит долгий путь построения изображения в OpenGL: (мировые координаты)--> ... -->(координаты в окне). Конвейер преобразований: (мировые координаты)-->[матрица моделирования]-->(преобразованные координаты)-->[матрица проекции]-->(мировое окно - координаты с отсечением)-->[перспективное преобразование]-->(преобразованные координаты)-->[преобразование поля просмотра]-->(координаты в окне) | + | Теперь можно полностью представить, как происходит долгий путь построения изображения в OpenGL: (мировые координаты)--> ... -->(координаты в окне). Конвейер преобразований: (мировые координаты)-->[матрица моделирования]-->(преобразованные координаты)-->[матрица проекции]-->(мировое окно - координаты с отсечением)-->[перспективное преобразование]-->(преобразованные координаты)-->[преобразование поля просмотра]-->(координаты в окне). |
Нашу последнюю функцию example_drawAxis() можно определить как функцию-член класса. example_drawAxis() для примера создаёт оси координат из примитивов-линий с помощью команд glBegin(GL_LINES) и glEnd(). Аргументом glBegin() является тип примитива, а в теле задаются вершины с помощью glVertex3f(). | Нашу последнюю функцию example_drawAxis() можно определить как функцию-член класса. example_drawAxis() для примера создаёт оси координат из примитивов-линий с помощью команд glBegin(GL_LINES) и glEnd(). Аргументом glBegin() является тип примитива, а в теле задаются вершины с помощью glVertex3f(). | ||
Строка 303: | Строка 303: | ||
public: | public: | ||
- | Scene3D(QWidget* | + | Scene3D(QWidget* pwgt = 0) ; // конструктор класса (= 0 -- главное окно) |
}; | }; | ||
#endif | #endif | ||
Строка 329: | Строка 329: | ||
GLubyte IndexArray[20][3]; // декларируем массив индексов вершин | GLubyte IndexArray[20][3]; // декларируем массив индексов вершин | ||
- | Scene3D::Scene3D(QWidget* | + | Scene3D::Scene3D(QWidget* pwgt/*= 0*/) : QGLWidget(pwgt) // конструктор класса Scene3D |
{ | { | ||
// setFormat(QGLFormat(QGL::DepthBuffer)); // использовать буфер глубины | // setFormat(QGLFormat(QGL::DepthBuffer)); // использовать буфер глубины | ||
Строка 788: | Строка 788: | ||
</source> | </source> | ||
---- | ---- | ||
- | В главной функции main() мы создаём объект графического интерфейса класса QApplication, который осуществляет контроль и управление приложением. Потом создаём виджет | + | В главной функции main() мы создаём объект графического интерфейса класса QApplication, который осуществляет контроль и управление приложением. Потом создаём виджет нашего класса Scene3D, задаём его отображаемое название и размеры (nWidth, nHeight) и указываем, во-первых, что его нужно изобразить и, во-вторых, при необходимости как его изобразить. Вызов функции exec(), принадлежащей классу QApplication, производит запуск приложения и сопутствующий контроль. |
В файле-проекте .pro необходимо добавить следующую строку: | В файле-проекте .pro необходимо добавить следующую строку: | ||
Строка 796: | Строка 796: | ||
для сборки приложения совместно с модулем QtOpenGL и системной библиотекой OpenGL. | для сборки приложения совместно с модулем QtOpenGL и системной библиотекой OpenGL. | ||
- | Замечу, что если бы вы использовали API Windows для создания GUI, то код получился бы на порядки больше и сложнее для понимания. Вы бы потратили колоссальное количество времени, чтобы его освоить и в нём разобраться, при этом ваш код оказался бы полностью бесполезным в Linux и MacOS. Чего нельзя сказать об использовании кроссплатформенной библиотеки Qt. Например, вот как будет выглядеть | + | Замечу, что если бы вы использовали API Windows для создания GUI, то код получился бы на порядки больше и сложнее для понимания. Вы бы потратили колоссальное количество времени, чтобы его освоить и в нём разобраться, при этом ваш код оказался бы полностью бесполезным в Linux и MacOS. Чего нельзя сказать об использовании кроссплатформенной библиотеки Qt. Например, вот как будет выглядеть программа, откомпилированная на openSUSE 11.2 (Linux), без изменения кода! |
<center>[[Файл:Lection1linux.jpeg]]</center> | <center>[[Файл:Lection1linux.jpeg]]</center> | ||
Строка 819: | Строка 819: | ||
<small>Дублирование авторской статьи: http://www.gamedev.ru/code/articles/OpenGL_Qt4</small> | <small>Дублирование авторской статьи: http://www.gamedev.ru/code/articles/OpenGL_Qt4</small> | ||
- | |||
- | |||
- | |||
- | |||
'''(c) registr''' | '''(c) registr''' | ||
- | Если вы обнаружили ошибки и неточности, то сообщите, пожалуйста, о них | + | Если вы обнаружили ошибки и неточности, то сообщите, пожалуйста, о них на форуме. |