Редактирование: Работа с OpenGL на Qt 4 (часть 1)
Материал из Wiki.crossplatform.ru
Внимание: Вы не представились системе. Ваш IP-адрес будет записан в историю изменений этой страницы.
ПРЕДУПРЕЖДЕНИЕ: Длина этой страницы составляет 70 килобайт. Страницы, размер которых приближается к 32 КБ или превышает это значение, могут неверно отображаться в некоторых браузерах. Пожалуйста, рассмотрите вариант разбиения страницы на меньшие части.
Правка может быть отменена. Пожалуйста, просмотрите сравнение версий, чтобы убедиться, что это именно те изменения, которые вас интересуют, и нажмите «Записать страницу», чтобы изменения вступили в силу.
Текущая версия | Ваш текст | ||
Строка 12: | Строка 12: | ||
==Модуль 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. | + | Библиотека 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. |
Классы подключаются стандартным образом: | Классы подключаются стандартным образом: | ||
Строка 60: | Строка 60: | ||
Знак вопроса (?) на схеме условно означает, что в этих местах проверяется, нужно ли сейчас вызвать эти функции или нет. Более полную и подробную информацию о классе QGLWidget можно найти в справке Qt Assistant, который всегда поставляется с Qt, а ещё подробнее смотрите в исходных текстах Qt. | Знак вопроса (?) на схеме условно означает, что в этих местах проверяется, нужно ли сейчас вызвать эти функции или нет. Более полную и подробную информацию о классе QGLWidget можно найти в справке Qt Assistant, который всегда поставляется с Qt, а ещё подробнее смотрите в исходных текстах Qt. | ||
- | Аргументом конструктора класса является указатель на объект класса QWidget. Класс QWidget обеспечивает работу с виджетами (интерфейсными элементами окна), а класс QGLWidget унаследован от QWidget и осуществляет связь виджетов и OpenGL. Запись QWidget *parent=0 означает, что виджет parent | + | Аргументом конструктора класса является указатель на объект класса QWidget. Класс QWidget обеспечивает работу с виджетами (интерфейсными элементами окна), а класс QGLWidget унаследован от QWidget и осуществляет связь виджетов и OpenGL. Запись QWidget *parent=0 означает, что виджет parent будет как бы главным окном (или точнее окном-объектом верхнего уровня). Пример: |
---- | ---- | ||
<source lang="cpp-qt"> | <source lang="cpp-qt"> | ||
Строка 76: | Строка 76: | ||
public: | public: | ||
- | Scene3D(QWidget* parent = 0); // конструктор класса Scene3D | + | Scene3D(QWidget* parent = 0) ; // конструктор класса Scene3D |
}; | }; | ||
Строка 87: | Строка 87: | ||
</source> | </source> | ||
---- | ---- | ||
- | Класс виджета, который будет работать с OpenGL, должен наследоваться от класса QGLWidget. Мы назвали этот класс Scene3D. Объектом класса Scene3D будет виджет, в котором будет рисоваться трёхмерная сцена | + | Класс виджета, который будет работать с OpenGL, должен наследоваться от класса QGLWidget. Мы назвали этот класс Scene3D. Объектом класса Scene3D будет виджет, в котором будет рисоваться трёхмерная сцена. |
- | + | Объект класса Scene3D будет являться объектом-потомком от объекта-родителя класса QGLWidget, который сам будет потомком от объекта-родителя класса QWidget. Как раз указатель QWidget* parent= 0 означает, что объект класса QWidget есть объект верхнего уровня, который находится наверху иерархии объектов: | |
+ | 1) объект класса QWidget (верх иерархии); | ||
+ | 2) объект класса QGLWidget; | ||
+ | 3) объект класса Scene3D (низ иерархии). | ||
+ | Создав объект класса Scene3D, мы автоматически создадим такую иерархию объектов. | ||
+ | |||
+ | При уничтожении объекта-родителя (например, объекта pwgt класса QWidget) все его объекты-потомки (например, объект класса Scene3D) уничтожаются автоматически. Более того, важно также отметить, что Qt самостоятельно отслеживает освобождение памяти от динамических объектов. Объекты и тем более объекты-потомки лучше создавать динамически, а программисту не нужно следить за освобождением памяти от динамических объектов и не нужно удалять их через delete. Вообще нединамически разумно создавать только объекты верхнего уровня. | ||
В конструкторе Scene3D() нужно задать начальные значения, не являющиеся как таковыми командами OpenGL, например, углы поворотов при наблюдении сцены. Определять деструктор в данном случае необязательно; деструктор по умолчанию сам создастся компилятором. Функции initializeGL(), resizeGL(), paintGL() являются виртуальными функциями (подробнее о динамическом полиморфизме можно прочитать, например, в книге: Аверкин В.П., Бобровский А.И., Веснич В.В., Радушинский В.Ф., Хомоненко А.Д. «Программирование на C++»). Следовательно, в классе-наследнике Scene3D обозначать функции как virtual не нужно. Эти три функции изначально имеют полностью пустое тело, которое нужно заполнить командами OpenGL. Здесь нужно отметить, что функции glDraw() и glInit() принадлежат классу QGLWidget, а не к OpenGL (как можно было бы подумать из префикса gl). Функции glDraw() и glInit() не нужны для пользовательской работы; они просто вызывают initializeGL(), resizeGL(), paintGL(), о которых и пойдёт речь далее. Как раз только эти три функци и используются для пользовательской работы с OpenGL: при инициализации настроек рендеринга, при изменении размера окна виджета и при рисовании. | В конструкторе Scene3D() нужно задать начальные значения, не являющиеся как таковыми командами OpenGL, например, углы поворотов при наблюдении сцены. Определять деструктор в данном случае необязательно; деструктор по умолчанию сам создастся компилятором. Функции initializeGL(), resizeGL(), paintGL() являются виртуальными функциями (подробнее о динамическом полиморфизме можно прочитать, например, в книге: Аверкин В.П., Бобровский А.И., Веснич В.В., Радушинский В.Ф., Хомоненко А.Д. «Программирование на C++»). Следовательно, в классе-наследнике Scene3D обозначать функции как virtual не нужно. Эти три функции изначально имеют полностью пустое тело, которое нужно заполнить командами OpenGL. Здесь нужно отметить, что функции glDraw() и glInit() принадлежат классу QGLWidget, а не к OpenGL (как можно было бы подумать из префикса gl). Функции glDraw() и glInit() не нужны для пользовательской работы; они просто вызывают initializeGL(), resizeGL(), paintGL(), о которых и пойдёт речь далее. Как раз только эти три функци и используются для пользовательской работы с OpenGL: при инициализации настроек рендеринга, при изменении размера окна виджета и при рисовании. | ||
Строка 119: | Строка 125: | ||
В соответствии с правилами обозначения в 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: | Строка 158: | ||
Функция 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: | Строка 192: | ||
Далее приведены последовательные преобразования: поворот, трансляция, масштабирование. Значения (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: | Строка 309: | ||
public: | public: | ||
- | Scene3D(QWidget* parent = 0); // конструктор класса | + | Scene3D(QWidget* parent = 0) ; // конструктор класса (= 0 -- главное окно) |
}; | }; | ||
#endif | #endif | ||
Строка 777: | Строка 783: | ||
QApplication app(argc, argv); // создаём приложение, инициализация оконной системы | QApplication app(argc, argv); // создаём приложение, инициализация оконной системы | ||
- | Scene3D scene1; // создаём виджет класса Scene3D | + | Scene3D *scene1; // создаём виджет класса Scene3D |
- | scene1 | + | scene1->setWindowTitle("lecture1"); // название окна |
- | scene1 | + | scene1->resize(500, 500); // размеры (nWidth, nHeight) окна |
- | scene1 | + | scene1->show(); // изобразить виджет |
- | // scene1 | + | // scene1->showFullScreen(); |
- | // scene1 | + | // scene1->showMaximized(); |
return app.exec(); | return app.exec(); | ||
Строка 788: | Строка 794: | ||
</source> | </source> | ||
---- | ---- | ||
- | В главной функции main() мы создаём объект графического интерфейса класса QApplication, который осуществляет контроль и управление приложением. Потом создаём виджет | + | В главной функции main() мы создаём объект графического интерфейса класса QApplication, который осуществляет контроль и управление приложением. Потом создаём виджет нашего класса Scene3D, задаём его отображаемое название и размеры (nWidth, nHeight) и указываем, во-первых, что его нужно изобразить и, во-вторых, при необходимости как его изобразить. Вызов функции exec(), принадлежащей классу QApplication, производит запуск приложения и сопутствующий контроль. |
В файле-проекте .pro необходимо добавить следующую строку: | В файле-проекте .pro необходимо добавить следующую строку: |