Введение
По замыслу разработчиков, спецификация OpenGL была создана независимой от какой-либо конкретной системы управления окнами. В результате мы получили переносимый, рационализированный и эффективный интерфейс к библиотеке двух- и трехмерного рендеринга. Конкретная система управления окнами должна сама позаботится об открытии и отображении окон. Библиотека OpenGL взаимодействует с оконной системой через дополнительные библиотеки, например, библиотека GLX описывает взаимодействие между OpenGL и системой X-Window.
Библиотека набора утилит OpenGL (GLUT, от GL Utility Toolkit) - это интерфейс для программистов, создающих на языках ANSI C и FORTRAN приложения OpenGL, не зависящие от системы управления окнами. Библиотека была написана Mark J. Kilgard и закрыла собой огромную брешь, оставленную спецификацией OpenGL. Благодаря разработчикам GLUT мы имеем возможность использовать единый интерфейс для работы с окнами независимо от платформы. Приложения OpenGL, использующие GLUT могут быть с легкостью перенесены с платформы на платформу, без многочисленных переделок исходного кода. GLUT значительно облегчает написание кода приложений и дополняет библиотеку OpenGL.
Библиотека GLUT относительно невелика и легка в изучении. Библиотека хорошо разработана и дополнена замечательной документацией. Таким образом, посвящать этому вопросу отдельную серию статей на LinuxFocus представляется излишним. Мы советуем каждому серьезному разработчику прочитать документацию, предоставленную автором. Цель этой регулярной колонки, посвященной GLUT, в том, чтобы шаг за шагом на примерах ознакомить читателя с библиотекой и ее использованием, в дополнение к серии статей по OpenGL в этом журнале. Мы так же надеемся, что внесем посильный вклад и дадим повод большему числу программистов присоединиться к союзу OpenGL-Linux. В любом случае, собственная копия авторской документации по GLUT послужит хорошим справочником.
Программный интерфейс GLUT представляет собой конечный автомат (state machine), как и сам OpenGL. Это значит, что GLUT содержит ряд переменных состояния, которые изменяются во время исполнения приложения. Начальные состояния машины GLUT были выбраны с таким расчетом, чтобы удовлетворять большинству приложений. Программа может изменять значения этих переменных состояния, для удовлетворения специфических требований. Каждый раз при вызове функции GLUT меняют свое поведение в зависимости от значений переменных состояния. Функции GLUT просты и требуют минимума параметров. Они не возвращают указателей, а единственными указателями в параметрах функций являются указатели на текстовые строчки и дескрипторы шрифтов.
Функции GLUT могут быть классифицированы на несколько групп согласно своей функциональности:
-
Инициализация
-
Начало обработки событий
-
Управление окнами
-
Управление перекрытием
-
Управление меню
-
Регистрация вызываемых (callback) функций
-
Управление индексированной палитрой цветов
-
Чтение состояния
-
Отображение шрифтов
-
Отображение геометрических фигур
В этой статье мы ознакомимся с некоторыми функциями инициализации, обработки событий и управления окнами, которые необходимы для запуска простейшей программы OpenGL.
Инициализация
Каждая программа OpenGL, использующая GLUT, должна начинаться с инициализации GLUT-машины. Все функции инициализации имеют префикс glutInit-. Главная инициализирующая функция называется glutInit:
Использование:
glutInit(int **argcp, char
**argv);
argcp - это указатель на еще не измененную переменную argc главной функции программы (main). После возврата из функции, значение, на которое указывает argcp, может измениться, так как glutInit вычленяет все опции командной строки, относящиеся к библиотеке GLUT, например, в системе X-Window все опции, относящиеся к управлению окнами, ассоциируются с GLUT.
argv
- это еще не измененная переменная argv главной функции.
glutInit позаботится об инициализации переменных состояния GLUT и откроет сессию с системой управления окнами. Есть всего лишь несколько функций, которые могут быть вызваны перед glutInit; это только те функции, которые имеют префикс glutInit-.
Данные функции могут быть использованы для установки начального состояния окна.
Например:
Использование:
glutInitWindowPosition(int x,
int **y);
glutInitWindowSize(int width,
int **height);
x,y =
позиция окна на экране в пикселях (точнее - позиция левого верхнего угла окна)
width,height - ширина и высота окна в пикселях.
Есть еще одна функция, которая присутствует практически во всех приложениях OpenGL, glutInitDisplayMode():
Использование:
glutInitDisplayMode(unsigned int mode);
mode - это режим состояния экрана, который может быть получен с помощью двоичной ИЛИ-комбинации режимов GLUT (каждый режим представляет собой двоичную маску). Возможные значения режимов:
GLUT_RGBA |
Режим RGBA. Используется по умолчанию, если не указаны явно режимы GLUT_RGBA или GLUT_INDEX. |
GLUT_RGB |
То же, что и GLUT_RGBA. |
GLUT_INDEX |
Режим индексированных цветов. Отменяет GLUT_RGBA. |
GLUT_SINGLE |
Окно с одиночным буфером. Используется по умолчанию. |
GLUT_DOUBLE |
Окно с двойным буфером. Отменяет GLUT_SINGLE. |
GLUT_ACCUM |
Окно с аккумулирующим буфером. |
GLUT_ALPHA |
Окно с альфа-компонентой к цветовым буферам. |
GLUT_DEPTH |
Окно с буфером глубины. |
GLUT_STENCIL |
Окно с буфером трафаретов. |
GLUT_MULTISAMPLE |
Окно с поддержкой multisampling. |
GLUT_STEREO |
Стерео-окно. |
GLUT_LUMINANCE |
Стерео окно с яркостной ("luminance") моделью цветов. |
Не беспокойтесь, если некоторые из указанных режимов кажутся Вам незнакомыми, рано или поздно мы опишем их все. Давайте обратимся к нескольким примерам.
Первый - простая инициализация для приложения с одним кадром:
#include <GL/glut.h>
void main(int argcp, char **argv){
/* Установка размеров и положения окна */
glutInitWindowSize(640, 480);
glutInitWindowPosition(0, 0);
/* Выбор режима окна:
Одиночный буфер и RGBA палитра */
glutInitDisplayMode(GLUT_RGBA | GLUT_SINGLE);
/* Инициализация первоначального состояния GLUT */
glutInit(&argcp, argv);
.....немного кода;
};
Второй - пример анимационной программы:
#include <GL/glut.h>
void main(int argcp, char **argv){
/* Установка размеров и положения окна */
glutInitWindowSize(640, 480);
glutInitWindowPosition(0, 0);
/* Выбор режима окна:
Двойной буфер и RGBA палитра */
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
/* Инициализация первоначального состояния GLUT */
glutInit(&argcp, argv);
.....еще немного кода;
};
Мы будем возвращаться к этим двум примерам по мере того, как будем узнавать GLUT все глубже. Главное различие примеров в том, что во втором случае окно инициализируется в режиме двойного буфера, что идеально для анимации, так как устраняет эффект мерцания при смене кадров в анимационной последовательности.
Обработка событий
Как мы уже упоминали выше, GLUT представляет собой конечный автомат (буквально - машину состояний). Теперь мы покажем, что GLUT разработан и как событийно-управляемый механизм. Это означает, что есть некоторый таймер или последовательный цикл, который запускается после соответствующей инициализации и обрабатывает, один за другим, все события, объявленные GLUT во время инициализации. К событиям относятся: щелчок мыши, закрытие окна, изменение свойств окна, передвижение курсора, нажатие клавиши, и даже довольно любопытное "пустое" (idle) событие, когда ничего не произошло! Каждое из возможных событий должно быть зарегистрировано в одной из переменных состояния GLUT для того, чтобы по таймеру или в последовательном цикле происходила периодическая проверка вызова пользователем того или иного события.
Например, мы можем зарегистрировать "щелчок мышки" как событие, за которым будет следить GLUT. События регистрируются через регистрацию вызываемых (callback) функций. Все они имеют синтакс glut[Событие]Func, в случае щелчка мыши мы имеем glutMouseFunc.
Регистрация вызываемых функций назначает механизму GLUT определенные пользователем функции, которые должны быть вызваны при наступлении того или иного события. Таким образом, если я напишу мою собственную процедуру MyMouse которая определяет, что делать по щелчку левой кнопки мыши, (или правой, итд.), то я могу зарегистрировать мою вызываемую функцию после glutInit() в главной функции main() с помощью утверждения "glutMouseFunc(MyMouse);" .
Оставим на потом, какие вызываемые функции и события разрешены в GLUT. Для нас сейчас важно, что после регистрации всех требуемых событий в нашем приложении, мы должны вызвать процедуру обработки событий в GLUT, а именно - glutMainLoop(). Эта функции никогда не возвращается, наша программа по существу входит в бесконечный цикл. В каждом приложении OpenGL функция main() должна заканчиваться вызовом glutMainLoop(). Например, в заготовке нашего примера с анимацией:
#include <GL/glut.h>
void main(int argcp, char **argv){
/* Инициализация GLUT */
glutInit(&argcp, argv);
glutInitWindowSize(640, 480);
glutInitWindowPosition(0, 0);
/* Открытие окна */
glutCreateWindow("My OpenGL Application");
/* Выбор режима:
Двойной буфер и RGBA палитра */
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
/* Регистрация вызываемых функций */
.....
/* Запуск механизма обработки событий */
glutMainLoop();
};
Заметьте, что я добавил в код вызов функции, которую нигде ранее не упоминал. Это - одна из функций управления окнами GLUT, glutCreateWindow(char **name).
Вот что мне особенно нравится в философии OpenGL и GLUT - достаточно взглянуть на название функции, чтобы понять, что она делает! (Create Window - создать окно). Функция позаботится о передаче указаний открыть окно для приложения OpenGL нижестоящей системе управления окнами. Окно будет иметь заголовок, который передается в качестве строкового параметра функции. В системе X-Window этот заголовок появится в левом верхнем углу заголовка окна. Секция управления окнами GLUT включает в себя много других функций, к которым нам время от времени придется обращаться. Сейчас же нам достаточно этой одной функции. Я также изменил порядок следования инициализационных функций, чтобы показать, что они могут быть помещены и после вызова glutInit().
Вернемся к событиям... Я хочу представить две вызываемые функции, которые фундаментальны в любой анимационной программе. Первая - glutDisplayFunc, которая устанавливает функцию рисования для текущего окна, вторая - glutIdleFunc, которая устанавливает функцию обработки пустого события. Обе регистрационные функции в качестве параметра используют функции типа void *(void). К примеру, мы напишем дополнительно две вызываемые функции к нашей заготовке анимационной программы, void MyDisplay(void), которая позаботится о вызове инструкций OpenGL для отрисовки собственно сцены в окне, и void MyIdle(void), которая будет вызываться каждый раз, когда нет событий от пользователя, другими словами - всегда, когда механизм обработки событий GLUT проходит в очередной раз бесконечный цикл (glutMainLoop()) и не находит ни одного нового события, он обращается к MyIdle. Почему я должен регистрировать вызываемую функцию для пустого события в анимационной программе? Потому что если мы желаем изменить какое-либо из изображений (кадров), показываемых во время анимации, независимо от действий пользователя, должна существовать функция (вызываемая функция пустого события) которая будет вызываться так часто в течении работы приложения OpenGL, чтобы изменять кадры прежде, чем они будут отрисованы функцией MyDisplay().
Пример анимации
Наконец, перед Вами заготовка анимационной программы:
#include <GL/glut.h>
void MyIdle(void){
/* Код, который меняет переменные, определяющие следующий кадр */
....
};
void MyDisplay(void){
/* Код OpenGL, который отображает кадр */
....
/* После отрисовки мы переставляем (заменяем) буфера */
glutSwapBuffers();
};
void main(int argcp, char **argv){
/* Инициализация GLUT */
glutInit(&argcp, argv);
glutInitWindowSize(640, 480);
glutInitWindowPosition(0, 0);
/* Открытие окна */
glutCreateWindow("My OpenGL Application");
/* Выбор режима:
Двойной буфер и RGBA палитра */
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
/* Регистрация вызываемых функций */
glutDisplayFunc(MyDisplay)
glutIdleFunc(MyIdle)
/* Запуск механизма обработки событий */
glutMainLoop();
};
Заметьте, что в конце функции MyDisplay я добавил вызов новой функции GLUT, glutSwapBuffers(). (Swap Buffers - обмен буферов). Это очень полезно при анимации. Мы используем окно с двойной буферизацией, один буфер отображаемый и второй - спрятанный. В таком режиме инструкции OpenGL по рисованию изображений всегда применяются к спрятанному буферу. Вызов функции glutSwapBuffers обменивает буфера, отображая окно целиком после того, как оно уже сформировано. Эта технология обычна в компьютерной анимации, так как не позволяет человеческому глазу видеть, как линия за линией формируется кадр.
Данного материала достаточно для того, чтобы начать писать собственное приложение OpenGL. Мы опустили только инструкции OpenGL в функции MyDisplay, которые собственно занимаются рисованием... но это уже другая история ;-).
В следующей статье по программированию GLUT мы углубимся в функциональные возможности секции управления окнами в GLUT, например - как можно открыть несколько сцен внутри одного окна. Мы изучим также использование меню, включая особенности переносимости.
Перевод: Vladas Lapinskas
|