Редактирование: Работа с OpenGL на Qt 4 (часть 2)

Материал из Wiki.crossplatform.ru

Перейти к: навигация, поиск
Внимание: Вы не представились системе. Ваш IP-адрес будет записан в историю изменений этой страницы.

ПРЕДУПРЕЖДЕНИЕ: Длина этой страницы составляет 127 килобайт. Страницы, размер которых приближается к 32 КБ или превышает это значение, могут неверно отображаться в некоторых браузерах. Пожалуйста, рассмотрите вариант разбиения страницы на меньшие части.

Правка может быть отменена. Пожалуйста, просмотрите сравнение версий, чтобы убедиться, что это именно те изменения, которые вас интересуют, и нажмите «Записать страницу», чтобы изменения вступили в силу.
Текущая версия Ваш текст
Строка 190: Строка 190:
==Анимация==
==Анимация==
-
Смена изображения за некоторый промежуток времени называется анимацией. Скорость смены изображения измеряется кадровой частотой, т.е. числом кадров в секунду (fps — frames per second). Видеоанимация — это изменение кадра в буфера кадров (то, куда выводится изображение). Работа с анимацией в OpenGL сводится к работе с таймером, благодаря которому изображение будет обновляться (изменяться и заново выводиться в буфер кадров) через некоторый интервал времени. Работу с таймером обеспечивает API библиотеки, которую вы используете.
+
Смена изображения за некоторый промежуток времени называется анимацией. В 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(20); // запускаем таймер с интервалом 20 миллисекунд
+
   timer->start(30); // запускаем таймер с интервалом 30 миллисекунд
}
}
Строка 270: Строка 270:
{
{
   // изменяем углы поворотов
   // изменяем углы поворотов
-
   xRot +=0.1f;
+
   xRot +=1.0f;
-
   yRot +=0.1f;
+
   yRot +=1.0f;
-
   zRot -=0.1f;
+
   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() с интервалом 20 миллисекунд. Обычно погрешность измерения времени операционными системами составляет 1 миллисекунду. Если вы ничего не задали в start() или задали 0, то таймер будет установлен на минимально возможный интервал. Если интервал таймера был уже установлен заранее для данного объекта класса QTimer, то функция start() запускает таймер с этим интервалом. Функция stop() остановит таймер, если это будет нужно, до следующего вызова start(). Дополнительную информацию смотрите в Qt Assistant.
+
Обратите внимание на макрос 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(10); // запускаем таймер
+
   timer->start(30); // запускаем таймер
}
}
Строка 1098: Строка 1094:
   glPopMatrix(); // взять из стека матрицу моделирования
   glPopMatrix(); // взять из стека матрицу моделирования
-
   // glPopName(); // вытащить имя из стека имён
+
   glPopName(); // вытащить имя из стека имён
   hits=glRenderMode(GL_RENDER); // число совпадений и переход в режим рисования
   hits=glRenderMode(GL_RENDER); // число совпадений и переход в режим рисования
Строка 1120: Строка 1116:
   if (motionParameters[0]) // изменение для первой фигуры
   if (motionParameters[0]) // изменение для первой фигуры
   {
   {
-
       xRot1 -=0.05f;
+
       xRot1 -=1.0f;
-
       yRot1 -=0.05f;
+
       yRot1 -=1.0f;
-
       zRot1 +=0.05f;
+
       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.005f;
+
       zTra1 -=signs[0]*0.01f;
   }
   }
   if (motionParameters[1]) // изменение для второй фигуры
   if (motionParameters[1]) // изменение для второй фигуры
   {
   {
-
       xRot2 +=0.05f;
+
       xRot2 +=1.0f;
-
       yRot2 +=0.05f;
+
       yRot2 +=1.0f;
-
       zRot2 -=0.05f;
+
       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.005f;
+
       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. Мы должны учесть, что y-координата окна «перевёрнута» относительно y-координаты OpenGL. Поэтому в y-параметр функции gluPickMatrix() нужно вставить: viewport[3]-mp.y(), здесь элемент viewport[3] равен высоте поля просмотра; мы также могли бы написать вместо viewport[3] функцию height() класса QWidget. Затем ещё нужно задать проекцию и мировое окно.
+
Первым делом мы декларируем массив 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.
-
 
-
===Дерево объектов===
 
-
 
-
''Будет дописано со временем''
 
===Включение изображения в исполняемый файл===
===Включение изображения в исполняемый файл===

Пожалуйста, обратите внимание, что все ваши добавления могут быть отредактированы или удалены другими участниками. Если вы не хотите, чтобы кто-либо изменял ваши тексты, не помещайте их сюда.
Вы также подтверждаете, что являетесь автором вносимых дополнений, или скопировали их из источника, допускающего свободное распространение и изменение своего содержимого (см. Wiki.crossplatform.ru:Авторское право). НЕ РАЗМЕЩАЙТЕ БЕЗ РАЗРЕШЕНИЯ ОХРАНЯЕМЫЕ АВТОРСКИМ ПРАВОМ МАТЕРИАЛЫ!