Редактирование: Работа с OpenGL на Qt 4 (часть 2)
Материал из Wiki.crossplatform.ru
Внимание: Вы не представились системе. Ваш IP-адрес будет записан в историю изменений этой страницы.
ПРЕДУПРЕЖДЕНИЕ: Длина этой страницы составляет 127 килобайт. Страницы, размер которых приближается к 32 КБ или превышает это значение, могут неверно отображаться в некоторых браузерах. Пожалуйста, рассмотрите вариант разбиения страницы на меньшие части.
Правка может быть отменена. Пожалуйста, просмотрите сравнение версий, чтобы убедиться, что это именно те изменения, которые вас интересуют, и нажмите «Записать страницу», чтобы изменения вступили в силу.
Текущая версия | Ваш текст | ||
Строка 190: | Строка 190: | ||
==Анимация== | ==Анимация== | ||
- | Смена изображения за некоторый промежуток времени называется анимацией. | + | Смена изображения за некоторый промежуток времени называется анимацией. В OpenGL анимация сопровождается изменением буфера кадров (то, куда выводится изображение). Работа с анимацией сводится к работе с таймером, благодаря которому изображение будет обновляться (изменяться и заново выводиться в буфер кадров) через некоторый интервал времени. Работу с таймером обеспечивает API библиотеки, которую вы используете. |
===Класс QTimer=== | ===Класс QTimer=== | ||
Строка 251: | Строка 251: | ||
QTimer *timer = new QTimer(this); // создаём таймер | QTimer *timer = new QTimer(this); // создаём таймер | ||
connect(timer, SIGNAL(timeout()), this, SLOT(change())); // связываем сигналы, генерируемые таймером, со слотом | connect(timer, SIGNAL(timeout()), this, SLOT(change())); // связываем сигналы, генерируемые таймером, со слотом | ||
- | timer->start( | + | timer->start(30); // запускаем таймер с интервалом 30 миллисекунд |
} | } | ||
Строка 270: | Строка 270: | ||
{ | { | ||
// изменяем углы поворотов | // изменяем углы поворотов | ||
- | xRot += | + | xRot +=1.0f; |
- | yRot += | + | yRot +=1.0f; |
- | zRot -= | + | zRot -=1.0f; |
if ((xRot>360)||(xRot<-360)) xRot=0.0f; | if ((xRot>360)||(xRot<-360)) xRot=0.0f; | ||
Строка 287: | Строка 287: | ||
===QTimer + сигналы и слоты=== | ===QTimer + сигналы и слоты=== | ||
- | Обратите внимание на макрос Q_OBJECT, который стоит вначале определения класса. Именно по этому макросу MOC (Meta Object Compiler — метаобъектный компилятор) определит, что в этом классе используется механизм сигналов и слотов и запишет соотвествующую информацию в специальный файл при препроцессорной обработке. Механизм сигналов и слотов связывает между собой объекты классов так, что объект одного класса может создавать сигнал, а объект другого класса получать этот сигнал и реагировать на него. В качестве слота класса Scene3D выступает слот change(), который будет изменять значения углов наблюдения (модельно-видовой матрицы) и обновлять изображение. В конструкторе класса Scene3D мы создаём динамический объект класса QTimer, потомка объекта класса Scene3D, о чём говорит параметр this. Затем связываем объекты классов QTimer и нашего класса Scene3D (используем указатель this) с помощью функции connect() класса QObject. Cигнал-функция должна быть вставлена в макрос-функцию SIGNAL(), слот-функция — в макрос SLOT(). Сигнал-функция timeout() будет вызывать сигналы таймера через определённый интервал времени. Наш объект this будет получать эти сигналы и реагировать на них вызовом слота-функции change(). Наконец, мы запускаем таймер с помощью start() с интервалом | + | Обратите внимание на макрос Q_OBJECT, который стоит вначале определения класса. Именно по этому макросу MOC (Meta Object Compiler — метаобъектный компилятор) определит, что в этом классе используется механизм сигналов и слотов и запишет соотвествующую информацию в специальный файл при препроцессорной обработке. Механизм сигналов и слотов связывает между собой объекты классов так, что объект одного класса может создавать сигнал, а объект другого класса получать этот сигнал и реагировать на него. В качестве слота класса Scene3D выступает слот change(), который будет изменять значения углов наблюдения (модельно-видовой матрицы) и обновлять изображение. В конструкторе класса Scene3D мы создаём динамический объект класса QTimer, потомка объекта класса Scene3D, о чём говорит параметр this. Затем связываем объекты классов QTimer и нашего класса Scene3D (используем указатель this) с помощью функции connect() класса QObject. Cигнал-функция должна быть вставлена в макрос-функцию SIGNAL(), слот-функция — в макрос SLOT(). Сигнал-функция timeout() будет вызывать сигналы таймера через определённый интервал времени. Наш объект this будет получать эти сигналы и реагировать на них вызовом слота-функции change(). Наконец, мы запускаем таймер с помощью start() с интервалом 30 миллисекунд. Обычно погрешность измерения времени операционными системами составляет 1 миллисекунду. Если вы ничего не задали в start() или задали 0, то таймер будет установлен на минимально возможный интервал. Если интервал таймера был уже установлен заранее для данного объекта класса QTimer, то функция start() запускает таймер с этим интервалом. Функция stop() остановит таймер, если это будет нужно, до следующего вызова start(). Дополнительную информацию смотрите в Qt Assistant. |
Про механизм сигналов и слотов следует указать, что можно создавать свои собственные сигналы и их эмитировать; можно соединять сигналы с сигналами; отправку сигналов можно блокировать; сигналы и слоты могут содержать параметры, но, разумеется, одинакового типа; сигналы и слоты можно не только соединять, но и разъединять. При уничтожении объекта все сигнально-слотовые связи с ним разъединяются автоматически. Недостаток механизма сигналов и слотов состоит в том, он работает медленнее, чем механизм функций обратного вызова. Дело в том, что при использовании сигналов и слотов появляеется задержка, связанная с отлавливанием сигнала. В связи с эти разберём другой дополнительный, низкоуровневый, но более быстрый по выполнению метод. | Про механизм сигналов и слотов следует указать, что можно создавать свои собственные сигналы и их эмитировать; можно соединять сигналы с сигналами; отправку сигналов можно блокировать; сигналы и слоты могут содержать параметры, но, разумеется, одинакового типа; сигналы и слоты можно не только соединять, но и разъединять. При уничтожении объекта все сигнально-слотовые связи с ним разъединяются автоматически. Недостаток механизма сигналов и слотов состоит в том, он работает медленнее, чем механизм функций обратного вызова. Дело в том, что при использовании сигналов и слотов появляеется задержка, связанная с отлавливанием сигнала. В связи с эти разберём другой дополнительный, низкоуровневый, но более быстрый по выполнению метод. | ||
Строка 344: | Строка 344: | ||
</source> | </source> | ||
---- | ---- | ||
- | Функция killTimer() класса QObject уничтожает таймер с указанным в ней id. Событие таймера является более точным методом работы с таймером, чем использование сигналов и слотов. Например, на моём компьютере при интервале 10 миллисекунд механизм сигналов и слотов | + | Функция killTimer() класса QObject уничтожает таймер с указанным в ней id. Событие таймера является более точным методом работы с таймером, чем использование сигналов и слотов. Например, на моём компьютере при интервале 10 миллисекунд механизм сигналов и слотов начинает немного запаздывать по сравнению с событием таймера. |
- | + | ||
- | + | ||
- | + | ||
- | + | ||
==Текстуры== | ==Текстуры== | ||
Строка 856: | Строка 852: | ||
timer = new QTimer(this); // создаём таймер | timer = new QTimer(this); // создаём таймер | ||
connect(timer, SIGNAL(timeout()), this, SLOT(change())); // связываем сигналы и слоты | connect(timer, SIGNAL(timeout()), this, SLOT(change())); // связываем сигналы и слоты | ||
- | timer->start( | + | timer->start(30); // запускаем таймер |
} | } | ||
Строка 1098: | Строка 1094: | ||
glPopMatrix(); // взять из стека матрицу моделирования | glPopMatrix(); // взять из стека матрицу моделирования | ||
- | + | glPopName(); // вытащить имя из стека имён | |
hits=glRenderMode(GL_RENDER); // число совпадений и переход в режим рисования | hits=glRenderMode(GL_RENDER); // число совпадений и переход в режим рисования | ||
Строка 1120: | Строка 1116: | ||
if (motionParameters[0]) // изменение для первой фигуры | if (motionParameters[0]) // изменение для первой фигуры | ||
{ | { | ||
- | xRot1 -= | + | xRot1 -=1.0f; |
- | yRot1 -= | + | yRot1 -=1.0f; |
- | zRot1 += | + | zRot1 +=1.0f; |
if ((xRot1>360)||(xRot1<-360)) xRot1=0.0f; | if ((xRot1>360)||(xRot1<-360)) xRot1=0.0f; | ||
Строка 1129: | Строка 1125: | ||
if (abs(zTra1)>0.5f) signs[0] *=-1; | if (abs(zTra1)>0.5f) signs[0] *=-1; | ||
- | zTra1 -=signs[0]*0. | + | zTra1 -=signs[0]*0.01f; |
} | } | ||
if (motionParameters[1]) // изменение для второй фигуры | if (motionParameters[1]) // изменение для второй фигуры | ||
{ | { | ||
- | xRot2 += | + | xRot2 +=1.0f; |
- | yRot2 += | + | yRot2 +=1.0f; |
- | zRot2 -= | + | zRot2 -=1.0f; |
if ((xRot2>360)||(xRot2<-360)) xRot2=0.0f; | if ((xRot2>360)||(xRot2<-360)) xRot2=0.0f; | ||
Строка 1143: | Строка 1139: | ||
if (abs(zTra2)>0.5f) signs[1] *=-1; | if (abs(zTra2)>0.5f) signs[1] *=-1; | ||
- | zTra2 +=signs[1]*0. | + | zTra2 +=signs[1]*0.01f; |
} | } | ||
Строка 1194: | Строка 1190: | ||
В функции selectFigures() демонстрируется работа с буфером выбора, с помощью которого можно выбирать графические объекты на сцене. Все примитивы можно объединить в группы (графические объекты), дав им имена в виде целого числа без знака (тип GLuint). В нашем случае такими двумя графическими объектами являются два октаэдра. По умолчанию всегда установлен режим рендера (когда изображение выводится в буфер кадров), поэтому нужно перейти в режим выбора — такой режим визуализации, когда изображение не выводится в буфер кадров. Когда щёлкается клавиша мыши, определяется координата на экране под указателем мыши. Далее около координаты мыши формируется наблюдаемый объём, в котором ищутся графические объекты (т.е. графические объекты, которые пересекают указанный объём). При переходе обратно в режим рендера буфер выбора заполняется записями соответствий. Из буфера выбора затем извлекается вся необходимая информация, в частности, имена графических объектов в наблюдаемом объёме. | В функции selectFigures() демонстрируется работа с буфером выбора, с помощью которого можно выбирать графические объекты на сцене. Все примитивы можно объединить в группы (графические объекты), дав им имена в виде целого числа без знака (тип GLuint). В нашем случае такими двумя графическими объектами являются два октаэдра. По умолчанию всегда установлен режим рендера (когда изображение выводится в буфер кадров), поэтому нужно перейти в режим выбора — такой режим визуализации, когда изображение не выводится в буфер кадров. Когда щёлкается клавиша мыши, определяется координата на экране под указателем мыши. Далее около координаты мыши формируется наблюдаемый объём, в котором ищутся графические объекты (т.е. графические объекты, которые пересекают указанный объём). При переходе обратно в режим рендера буфер выбора заполняется записями соответствий. Из буфера выбора затем извлекается вся необходимая информация, в частности, имена графических объектов в наблюдаемом объёме. | ||
- | Первым делом мы декларируем массив selectBuffer, который и называется буфером выбора. Он должен содержать как минимум четыре элемента. Число hits есть число совпадений (т.е. число записей соответствий); у нас оно станет равным 1 (когда есть одно совпадение) или 0 (когда нет совпадения). В функции glSelectBuffer() указывается, какой массив мы выделяем для буфера выбора. Затем нужно перейти к текущей матрице проекции с помощью glMatrixMode(GL_PROJECTION) и сохранить её в стеке матриц (чтобы после задания нового объёма вернуть из стека матриц нашу старую матрицу обратно). Функция glRenderMode() с параметром GL_SELECT осуществляет переход в режим выбора. Команда glLoadIdentity() загружает единичную матрицу в матрицу проекции. Теперь нам нужно создать новый объём, в котором будут искаться графические объекты; это делается с помощью функции gluPickMatrix(). В функцию gluPickMatrix() нужно передать координаты в окне OpenGL (т.е. координаты мыши в окне), указать размеры нового объёма в виде ширины и высоты (у нас они равны одному пикселю) и также передать матрицу поля просмотра viewport. Мы должны учесть, что | + | Первым делом мы декларируем массив selectBuffer, который и называется буфером выбора. Он должен содержать как минимум четыре элемента. Число hits есть число совпадений (т.е. число записей соответствий); у нас оно станет равным 1 (когда есть одно совпадение) или 0 (когда нет совпадения). В функции glSelectBuffer() указывается, какой массив мы выделяем для буфера выбора. Затем нужно перейти к текущей матрице проекции с помощью glMatrixMode(GL_PROJECTION) и сохранить её в стеке матриц (чтобы после задания нового объёма вернуть из стека матриц нашу старую матрицу обратно). Функция glRenderMode() с параметром GL_SELECT осуществляет переход в режим выбора. Команда glLoadIdentity() загружает единичную матрицу в матрицу проекции. Теперь нам нужно создать новый объём, в котором будут искаться графические объекты; это делается с помощью функции gluPickMatrix(). В функцию gluPickMatrix() нужно передать координаты в окне OpenGL (т.е. координаты мыши в окне), указать размеры нового объёма в виде ширины и высоты (у нас они равны одному пикселю) и также передать матрицу поля просмотра viewport. Мы должны учесть, что координата y окна «перевёрнута» относительно координаты y OpenGL. Поэтому в y-параметр функции gluPickMatrix() нужно вставить: viewport[3]-mp.y(), здесь элемент viewport[3] равен высоте поля просмотра; мы также могли бы написать вместо viewport[3] функцию height() класса QWidget. Затем ещё нужно задать проекцию и мировое окно. |
В параметрах функции gluPickMatrix() скрыт ещё один нюанс: для нас важно, где определяется функция mousePressEvent() с методом pos(), а она может определяться и в классе Scene3D (центральный виджет), и в классе главного окна. Дело в том, что под меню сверху экрана выделяется область, равная по высоте примерно 20 пикселям. Например, в системе Windows XP высота под меню равна 20 пикселям, а в системе OpenSuse 11.2 она равна 21 пикселю. Тогда поле просмотра недополучает эти 20 пикселей по высоте, т.е. поле просмотра становится меньше по высоте примерно на 20 пикселей. Если методы, связанные с размером по высоте, pos(), y(), height() используется в главном окне, то они учитывают дополнительный размер меню. Но если они используются в центральном виджете (это наш класс Scene3D), то размеры меню они уже никак учитывать не будут. В обоих случаях отсчёт ведётся как бы «от себя». Поэтому здесь принципиально важно, чтобы функция mousePressEvent() и методы pos(), y(), height() находились все в одном классе, тогда смещение от меню будет либо учитываться автоматически, либо не учитываться вообще. Для их размещения мы выбрали класс Scene3D, это более логично и к тому же требует меньше программного кода. | В параметрах функции gluPickMatrix() скрыт ещё один нюанс: для нас важно, где определяется функция mousePressEvent() с методом pos(), а она может определяться и в классе Scene3D (центральный виджет), и в классе главного окна. Дело в том, что под меню сверху экрана выделяется область, равная по высоте примерно 20 пикселям. Например, в системе Windows XP высота под меню равна 20 пикселям, а в системе OpenSuse 11.2 она равна 21 пикселю. Тогда поле просмотра недополучает эти 20 пикселей по высоте, т.е. поле просмотра становится меньше по высоте примерно на 20 пикселей. Если методы, связанные с размером по высоте, pos(), y(), height() используется в главном окне, то они учитывают дополнительный размер меню. Но если они используются в центральном виджете (это наш класс Scene3D), то размеры меню они уже никак учитывать не будут. В обоих случаях отсчёт ведётся как бы «от себя». Поэтому здесь принципиально важно, чтобы функция mousePressEvent() и методы pos(), y(), height() находились все в одном классе, тогда смещение от меню будет либо учитываться автоматически, либо не учитываться вообще. Для их размещения мы выбрали класс Scene3D, это более логично и к тому же требует меньше программного кода. | ||
Строка 1309: | Строка 1305: | ||
---- | ---- | ||
В главной функции main() создаётся главное окно, устанавливаются его размеры по умолчанию и оно показывается максимально развёрнутым. Важно отметить, что динамические объекты удалаются автоматически за счёт дерева объектов, на вершине которого будет находится объект mainwindow1. | В главной функции main() создаётся главное окно, устанавливаются его размеры по умолчанию и оно показывается максимально развёрнутым. Важно отметить, что динамические объекты удалаются автоматически за счёт дерева объектов, на вершине которого будет находится объект mainwindow1. | ||
- | |||
- | |||
- | |||
- | |||
===Включение изображения в исполняемый файл=== | ===Включение изображения в исполняемый файл=== |