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

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

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

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

Правка может быть отменена. Пожалуйста, просмотрите сравнение версий, чтобы убедиться, что это именно те изменения, которые вас интересуют, и нажмите «Записать страницу», чтобы изменения вступили в силу.
Текущая версия Ваш текст
Строка 173: Строка 173:
   }
   }
-
   Scene3D scene1; // создаём виджет класса Scene3D
+
   s = new Scene3D; // создаём виджет класса Scene3D
-
   scene1.setWindowTitle("example"); // название окна   
+
   s->setWindowTitle("example"); // название окна   
-
   scene1.resize(500, 500); // размеры (nWidth, nHeight) окна   
+
   s->resize(500, 500); // размеры (nWidth, nHeight) окна   
-
   scene1.show(); // изобразить виджет
+
   s->show(); // изобразить виджет
-
   // scene1.showFullScreen(); // изобразить виджет на полный экран
+
   // s->showFullScreen(); // изобразить виджет на полный экран
-
   // scene1.showMaximized(); // изобразить виджет развёрнутым на весь экран  
+
   // s->showMaximized(); // изобразить виджет развёрнутым на весь экран  
    
    
   return a.exec();
   return a.exec();
Строка 190: Строка 190:
==Анимация==
==Анимация==
-
Смена изображения за некоторый промежуток времени называется анимацией. Скорость смены изображения измеряется кадровой частотой, т.е. числом кадров в секунду (fps — frames per second). Видеоанимация — это изменение кадра в буфера кадров (то, куда выводится изображение). Работа с анимацией в OpenGL сводится к работе с таймером, благодаря которому изображение будет обновляться (изменяться и заново выводиться в буфер кадров) через некоторый интервал времени. Работу с таймером обеспечивает API библиотеки, которую вы используете.
+
Смена изображения за некоторый промежуток времени называется анимацией. В OpenGL анимация сопровождается изменением буфера кадров (то, куда выводится изображение). Работа с анимацией сводится к работе с таймером, благодаря которому изображение будет обновляться (изменяться и заново выводиться в буфер кадров) через некоторый интервал времени. Работу с таймером обеспечивает API библиотеки, которую вы используете.
===Класс QTimer===
===Класс QTimer===
Строка 249: Строка 249:
   //...
   //...
-
   QTimer *timer = new QTimer(this); // создаём таймер
+
   QTimer *timer = new QTimer; // создаём таймер
   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. Затем связываем объекты классов QTimer и нашего класса Scene3D (используем указатель this) с помощью функции connect() класса QObject. Cигнал-функция должна быть вставлена в макрос-функцию SIGNAL(), слот-функция — в макрос SLOT(). Сигнал-функция timeout() будет вызывать сигналы таймера через определённый интервал времени. Наш объект this будет получать эти сигналы и реагировать на них вызовом слота-функции change(). Наконец, мы запускаем таймер с помощью start() с интервалом 30 миллисекунд. Обычно погрешность измерения времени операционными системами составляет 1 миллисекунду. Если вы ничего не задали в start() или задали 0, то таймер будет установлен на минимально возможный интервал. Если интервал таймера был уже установлен заранее для данного объекта класса QTimer, то функция start() запускает таймер с этим интервалом. Функция stop() остановит таймер, если это будет нужно, до следующего вызова start(). Дополнительную информацию смотрите в Qt Assistant.
-
Про механизм сигналов и слотов следует указать, что можно создавать свои собственные сигналы и их эмитировать; можно соединять сигналы с сигналами; отправку сигналов можно блокировать; сигналы и слоты могут содержать параметры, но, разумеется, одинакового типа; сигналы и слоты можно не только соединять, но и разъединять. При уничтожении объекта все сигнально-слотовые связи с ним разъединяются автоматически. Недостаток механизма сигналов и слотов состоит в том, он работает медленнее, чем механизм функций обратного вызова. Дело в том, что при использовании сигналов и слотов появляеется задержка, связанная с отлавливанием сигнала. В связи с эти разберём другой дополнительный, низкоуровневый, но более быстрый по выполнению метод.
+
Про механизм сигналов и слотов следует указать, что можно создавать свои собственные сигналы и их эмитировать; можно соединять сигналы с сигналами; отправку сигналов можно блокировать; сигналы и слоты могут содержать параметры, но, разумеется, одинакового типа; сигналы и слоты можно не только соединять, но и разъединять. При уничтожении объекта все сигнально-слотовые связи с ним разъединяются автоматически. Недостаток механизма сигналов и слотов состоит в том, он работает медленнее, чем механизм функций обратного вызова, и это может быть заметным на «очень медленных» компьютерах. В связи с эти разберем другой дополнительный, низкоуровневый, но более быстрый по выполнению метод. На более-менее современных компьютерах вряд ли вы заметите различие в скорости выполнения.
===Другой метод: событие таймера===
===Другой метод: событие таймера===
-
Этот альтернативный метод не задействует класс QTimer, а значит и не задействует механизм сигналов и слотов, и использует функции startTimer() и timeEvent() класса QObject. Функция startTimer() запускает таймер с указанным в ней интервалом (также в миллисекундах) и возвращает id (идентификатор, т.е. имя) таймера, а функция timeEvent() выполняет событие таймера. В теле виртуальной переопределённой функции timeEvent() нужно указать, что должно произойти, когда она вызовится таймером. Пример:
+
Этот альтернативный метод не задействует класс QTimer и использует функции startTimer() и timeEvent() класса QObject. Функция startTimer() запускает таймер с указанным в ней интервалом (также в миллисекундах) и возвращает id (идентификатор, т.е. имя) таймера, а функция timeEvent() выполняет событие таймера. В теле виртуальной переопределённой функции timeEvent() нужно указать, что должно произойти, когда она вызовится таймером. Пример:
----  
----  
<source lang="cpp-qt">
<source lang="cpp-qt">
Строка 344: Строка 344:
</source>
</source>
----
----
-
Функция killTimer() класса QObject уничтожает таймер с указанным в ней id. Событие таймера является более точным методом работы с таймером, чем использование сигналов и слотов. Например, на моём компьютере при интервале 10 миллисекунд механизм сигналов и слотов может иногда немного запаздывать по сравнению с событием таймера. Визуально, конечно, это запаздывание незаметно.
+
Функция killTimer() класса QObject уничтожает таймер с указанным в ней id.
-
 
+
-
===Синхронизация кадров с дисплеем===
+
-
 
+
-
''Будет дописано со временем''
+
==Текстуры==
==Текстуры==
Строка 854: Строка 850:
   xRot2=-90.0f; yRot2=0.0f; zRot2=0.0f; zTra2=0.0f;
   xRot2=-90.0f; yRot2=0.0f; zRot2=0.0f; zTra2=0.0f;
-
   timer = new QTimer(this); // создаём таймер
+
   timer = new QTimer; // создаём таймер
   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, это более логично и к тому же требует меньше программного кода.
Строка 1253: Строка 1249:
MainWindow::MainWindow() // конструктор
MainWindow::MainWindow() // конструктор
{  
{  
-
   scene1 = new Scene3D; // создаю динамический объект класса Scene3D  
+
   scene1 = new Scene3D; // создаю динамический объект класса Scene3D
   setCentralWidget(scene1); // обозначаю scene1 центральным виджетом в главном окне
   setCentralWidget(scene1); // обозначаю scene1 центральным виджетом в главном окне
      
      
Строка 1308: Строка 1304:
</source>
</source>
----
----
-
В главной функции main() создаётся главное окно, устанавливаются его размеры по умолчанию и оно показывается максимально развёрнутым. Важно отметить, что динамические объекты удалаются автоматически за счёт дерева объектов, на вершине которого будет находится объект mainwindow1.
+
В главной функции main() создаётся главное окно, устанавливаются его размеры по умолчанию и оно показывается максимально развёрнутым.
-
 
+
-
===Дерево объектов===
+
-
''Будет дописано со временем''
+
Обратите внимание, что в программе было создано много динамических объектов (через new) классов Qt, а также динамический объект класса Scene3D, наследника от QGLWidget. Все эти классы унаследованы от класса-первородителя QObject. Объекты этих классов являются наследниками первообъекта класса QObject, который создаётся автоматически. При уничтожении объекта-родителя в Qt уничтожаются все его объекты-потомки. Когда приложение завершает работу, то автоматически удаляется объект класса QObject, что приводит к автоматическому удалению всех его потомков, и удалять их через delete в деструкторах уже не нужно.
===Включение изображения в исполняемый файл===
===Включение изображения в исполняемый файл===

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