Структура
консольного приложения
Будем рассматривать
построение консольного приложения при
помощи библиотеки GLUT или GL Utility Toolkit,
получившей в последнее время широкое
распространение. Эта библиотека
обеспечивает единый интерфейс для
работы с окнами вне зависимости от
платформы, поэтому описываемая ниже
структура приложения остается
неизменной для операционных систем Windows,
Linux и многих других.
Функции GLUT могут быть
классифицированы на несколько групп по
своему назначению:
- Инициализация
- Начало обработки событий
- Управление окнами
- Управление меню
- Регистрация вызываемых
(callback) функций
- Управление индексированной
палитрой цветов
- Отображение шрифтов
- Отображение дополнительных
геометрических фигур(тор, конус и др.)
Инициализация проводится с
помощью функции glutInit(int *argcp, char **argv)
Переменная argcp есть указатель
на стандартную переменную argc
описываемую в функции main(), а argv–
указатель на параметры, передаваемые
программе при запуске, который
описывается там же. Эта функция проводит
необходимые начальные действия для
построения окна приложения, и только
несколько функций GLUT могут быть вызваны
до нее. К ним относятся:
glutInitWindowPosition (int x, int y)
glutInitWindowSize (int width, int height)
glutInitDisplayMode (unsigned int mode)
Первые две функции задают
соответственно положение и размер окна,
а последняя функция определяет
различные режимы отображения
информации, которые могут совместно
задаваться с использованием операции
побитового “или”(|):
GLUT_RGBA Режим RGBA. Используется
по умолчанию, если не указаны явно
режимы GLUT_RGBA или GLUT_INDEX.
GLUT_RGB То же, что и GLUT_RGBA.
GLUT_INDEX Режим индексированных цветов (использование
палитры). Отменяет GLUT_RGBA.
GLUT_SINGLE Окно с одиночным буфером.
Используется по умолчанию.
GLUT_DOUBLE Окно с двойным буфером.
Отменяет GLUT_SINGLE.
GLUT_DEPTH Окно с буфером глубины.
Это неполный список
параметров для данной функции, однако
для большинства случаев этого бывает
достаточно.
Двойной буфер обычно
используют для анимации, сначала рисуя
что-нибудь в одном буфере, а затем меняя
их местами, что позволяет избежать
мерцания. Буфер глубины или z-буфер
используется для удаления невидимых
линий и поверхностей.
Функции библиотеки GLUT
реализуют так называемый событийно-управляемый
механизм. Это означает, что есть
некоторый внутренний цикл, который
запускается после соответствующей
инициализации и обрабатывает, одно за
другим, все события, объявленные во
время инициализации. К событиям
относятся: щелчок мыши, закрытие окна,
изменение свойств окна, передвижение
курсора, нажатие клавиши, и "пустое"
(idle) событие, когда ничего не происходит.
Для проведения периодической проверки
совершения того или иного события надо
зарегистрировать функцию, которая будет
его обрабатывать. Для этого
используются функции вида:
void glutDisplayFunc (void (*func) (void))
void glutReshapeFunc (void (*func) (int width,
int height))
void glutMouseFunc (void (*func) (int button, int
state, int x, int y))
void glutIdleFunc (void (*func) (void))
То есть параметром для них
является имя соответствующей функции
заданного типа. С помощью glutDisplayFunc()
задается функция рисования для окна
приложения, которая вызывается при
необходимости создания или
восстановления изображения. Для явного
указания, что окно надо обновить, иногда
удобно использовать функцию void glutPostRedisplay(void)
Через glutReshapeFunc()
устанавливается функция обработки
изменения размеров окна пользователем,
которой передаются новые размеры.
glutMouseFunc() определяет
обработчика команд от мыши, а glutIdleFunc()
задает функцию, которая будет
вызываться каждый раз, когда нет событий
от пользователя.
Контроль всех событий
происходит внутри бесконечного цикла в
функции void glutMainLoop(void) которая обычно
вызывается в конце любой программы,
использующей GLUT.
Структура приложения,
использующего анимацию, будет следующей:
#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 | GLUT_DEPTH);
//-- Регистрация вызываемых функций --//
glutDisplayFunc(MyDisplay);
glutIdleFunc(MyIdle);
//-- Запуск механизма обработки событий
--//
glutMainLoop();
};
Этот шаблон используется в
тексте приложения, использующего OpenGL,
который приводится в конце этого
пособия.
В случае, если приложение
должно строить статичное изображение,
можно заменить GLUT_DOUBLE на GLUT_SINGLE, так как
одного буфера в этом случае будет
достаточно, и убрать вызов функции
glutIdleFunc().
Определение атрибутов вершины
Под вершиной понимается точка
в трехмерном пространстве, координаты
которой можно задавать следующим
образом:
void glVertex[2 3 4][s i f d](type coords)
void glVertex[2 3 4][s i f d]v(type *coords)
Координаты точки задаются
максимум четырьмя значениями: x, y, z, w, при
этом можно указывать два (x,y) или три (x,y,z)
значения, а для остальных переменных в
этих случаях используются значения по
умолчанию: z=0, w=1. Как уже было сказано
выше, число в названии команды
соответствует числу явно задаваемых
значений, а последующий символ – их типу.
Координатные оси расположены
так, что точка (0,0) находится в левом
нижнем углу экрана, ось x направлена
влево, ось y- вверх, а ось z- из экрана. Это
расположение осей мировой системы
координат, в которой задаются
координаты вершин объекта, другие
системы координат будут рассмотрены
ниже.
Однако чтобы задать какую-нибудь
фигуру одних координат вершин
недостаточно, и эти вершины надо
объединить в одно целое, определив
необходимые свойства. Для этого в OpenGL
используется понятие примитивов, к
которым относятся точки, линии,
связанные или замкнутые линии,
треугольники и так далее. Задание
примитива происходит внутри командных
скобок:
void glBegin(GLenum mode)
void glEnd(void)
Параметр mode определяет тип
примитива, который задается внутри и
может принимать следующие значения:
GL_POINTS каждая
вершина задает координаты некоторой
точки.
GL_LINES каждая
отдельная пара вершин определяет
отрезок; если задано нечетное число
вершин, то последняя вершина
игнорируется.
GL_LINE_STRIP каждая
следующая вершина задает отрезок вместе
с предыдущей.
GL_LINE_LOOP отличие от
предыдущего примитива только в том, что
последний отрезок определяется
последней и первой вершиной, образуя
замкнутую ломаную.
GL_TRIANGLES каждая отдельная
тройка вершин определяет треугольник;
если задано не кратное трем число вершин,
то последние вершины игнорируются.
GL_TRIANGLE_STRIP каждая
следующая вершина задает треугольник
вместе с двумя предыдущими.
GL_TRIANGLE_FAN треугольники
задаются первой и каждой следующей
парой вершин (пары не пересекаются).
GL_QUADS каждая
отдельная четверка вершин определяет
четырехугольник; если задано не кратное
четырем число вершин, то последние
вершины игнорируются.
GL_QUAD_STRIP
четырехугольник с номером n
определяется вершинами с номерами 2n-1, 2n,
2n+2, 2n+1.
GL_POLYGON
последовательно задаются вершины
выпуклого многоугольника.

Для задания текущего цвета
вершины используются команды
void glColor[3 4][b s i f](GLtype components)
void glColor[3 4][b s i f]v(GLtype components)
Первые три параметра задают R,
G, B компоненты цвета, а последний
параметр определяет alpha-компоненту,
которая задает уровень прозрачности
объекта. Если в названии команды указан
тип ‘f’ (float), то значения всех
параметров должны принадлежать отрезку
[0,1], при этом по умолчанию значение alpha-компоненты
устанавливается равным 1.0, что
соответствует полной непрозрачности.
Если указан тип ‘ub’ (unsigned byte), то
значения должны лежать в отрезке [0,255].
Разным вершинам можно
назначать различные цвета и тогда будет
проводиться линейная интерполяция
цветов по поверхности примитива.
Для управления режимом
интерполяции цветов используется
команда void glShadeModel(GLenummode) вызов
которой с параметром GL_SMOOTH включает
интерполяцию (установка по умолчанию), а
с GL_FLAT отключает.
Например, чтобы нарисовать
треугольник с разными цветами в
вершинах, достаточно написать:
GLfloat BlueCol[3]={0,0,1};
glBegin(GL_TRIANGLE);
glColor3f(1.0, 0.0, 0.0); //красный
glVertex3f(0.0, 0.0, 0.0);
glColor3ub(0,255,0); //зеленый
glVertex3f(1.0, 0.0, 0.0);
glColor3fv(BlueCol); //синий
glVertex3f(1.0, 1.0, 0.0);
glEnd();
Для задания цвета фона
используется команда void glClearColor(GLclampf
red, GLclampf green, GLclampf blue, GLclampf alpha). Значения
должны находиться в отрезке [0,1] и по
умолчанию равны нулю. После этого вызов
команды void glClear(GLbitfield mask) с
параметром GL_COLOR_BUFFER_BIT устанавливает
цвет фона во все буфера, доступные для
записи цвета (иногда удобно
использовать несколько буферов цвета).
Кроме цвета аналогичным
образом можно определить нормаль в
вершине, используя команды
void glNormal3[b s i f d](type coords)
void glNormal3[b s i f d]v(type coords)
Задаваемый вектор может не
иметь единичной длины, но он будет
нормироваться автоматически в режиме
нормализации, который включается
вызовом команды glEnable(GL_NORMALIZE).Команды
void glEnable(GLenum mode)
void glDisable(GLenum mode)
производят включение и
отключение того или иного режима работы
конвейера OpenGL. Эти команды применяются
достаточно часто, и их влияние будет
рассматриваться в конкретных случаях.
Вообще, внутри командных
скобок glBegin() и glEnd() можно производить
вызов лишь нескольких команд, в которые
входят glVertex..(), glColor..()glNormal..(), glRect..(),
glMaterial..() и glTexCoord..().
Последние две команды будут
рассматриваться ниже, а с помощью
команды void glRect[s i f d]( GLtype x1, GLtype y1, GLtype
x2, GLtype y2 ), void glRect[s i f d]v( GLtype *v1, GLtype *v2 )
можно нарисовать прямоугольник в
плоскости z=0 с координатами
противоположных углов (x1,y1) и (x2,y2), либо
набор прямоугольников с координатами
углов в массивах v1 и v2.
Кроме задания самих
примитивов можно определить метод их
отображения на экране, где под
примитивами в данном случае понимаются
многоугольники.
Однако сначала надо
определить понятие лицевых и обратных
граней.
Под гранью понимается одна из
сторон многоугольника, и по умолчанию
лицевой считается та сторона, вершины
которой обходятся против часовой
стрелки. Направление обхода вершин
лицевых сторон можно изменить вызовом
команды void glFrontFace(GLenum mode) со
значением параметра mode равным GL_CW, а
отменить- с GL_CCW.
Чтобы изменить метод
отображения многоугольника
используется команда void glPolygonMode(GLenum
face, Glenum mode)
Параметр mode определяет, как
будут отображаться многоугольники, а
параметр face устанавливает тип
многоугольников, к которым будет
применяться эта команда и может
принимать следующие значения:
GL_FRONT для лицевых
граней
GL_BACK для
обратных граней
GL_FRONT_AND_BACK для
всех граней
Параметр mode может быть равен:
GL_POINT при таком
режиме будут отображаться только
вершины многоугольников.
GL_LINE при таком
режиме многоугольник будет
представляться набором отрезков.
GL_FILL при таком
режиме многоугольники будут
закрашиваться текущим цветом с учетом
освещения и этот режим установлен по
умолчанию.
Кроме того, можно указывать,
какой тип граней отображать на экране.
Для этого сначала надо установить
соответствующий режим вызовом команды
glEnable(GL_CULL_FACE), а затем выбрать тип
отображаемых граней с помощью команды
void glСullFace(GLenum mode)
Вызов с параметром GL_FRONT
приводит к удалению из изображения всех
лицевых граней, а с параметром GL_BACK-
обратных (установка по умолчанию).
Кроме рассмотренных
стандартных примитивов в библиотеках GLU
и GLUT описаны более сложные фигуры, такие
как сфера, цилиндр, диск (в GLU) и сфера,
куб, конус, тор, тетраэдр, додекаэдр,
икосаэдр, октаэдр и чайник(в GLUT).
Автоматическое наложение текстуры
предусмотрено только для фигур из
библиотеки GLU (создание текстур в OpenGL
будет рассматриваться ниже).
Например, чтобы нарисовать
сферу или цилиндр, надо сначала создать
объект специального типа GLUquadricObj с
помощью команды
GLUquadricObj* gluNewQuadric(void)
а затем вызвать
соответствующую команду:
void gluSphere(GLUquadricObj * qobj, GLdouble
radius, GLint slices, GLint stacks)
void gluCylinder(GLUquadricObj * qobj, GLdouble
baseRadius, GLdouble topRadius, GLdouble height, GLint slices, GLint
stacks)
где параметр slices задает число
разбиений вокруг оси z, а stacks – вдоль оси
z.
Более подробную информацию об
этих и других командах построения
примитивов можно найти приложении.
Важно отметить, что для
корректного построения перечисленных
примитивов необходимо удалять
невидимые линии и поверхности, для чего
надо включить соответствующий режим
вызовом команды glEnable(GL_DEPTH_TEST).
Массивы вершин
Если вершин много, то чтобы не
вызывать для каждой команду glVertex..(),
удобно объединять вершины в массивы,
используя команду
void glVertexPointer( GLint size, GLenum type,
GLsizei stride, void *ptr )
которая определяет способ
хранения и координаты вершин. При этом
size определяет число координат вершины
(может быть равен 2, 3, 4), type определяет тип
данных (может быть равен GL_SHORT, GL_INT, GL_FLOAT,
GL_DOUBLE). Иногда удобно хранить в одном
массиве другие атрибуты вершины, и тогда
параметр stride задает смещение от
координат одной вершины до координат
следующей; если stride равен нулю, это
значит, что координаты расположены
последовательно. В параметре ptr
указывается адрес, где находятся данные.
Аналогично можно определить
массив нормалей, цветов и некоторых
других атрибутов вершины, используя
команды
void NormalPointer(GLenum type, GLsizei stride,
void*pointer)
void ColorPointer(GLintsize, GLenum type, GLsizei
stride, void *pointer)
Для того, чтобы эти массивы
можно было использовать в дальнейшем,
надо вызвать команду
void glEnableClientState(GLenum array)
с параметрами GL_VERTEX_ARRAY,
GL_NORMAL_ARRAY, GL_COLOR_ARRAY соответственно. После
окончания работы с массивом желательно
вызвать команду
void glDisableClientState(GLenum array)
с соответствующим значением
параметра array.
Для отображения содержимого
массивов используется команда
void glArrayElement(GLint index)
которая передает OpenGL атрибуты
вершины, используя элементы массива с
номером index. Это аналогично
последовательному применению команд
вида glColor..(…), glNormal..(…), glVertex..(…) c
соответствующими параметрами. Однако
вместо нее обычно вызывается команда
void glDrawArrays(GLenum mode, GLint first,
GLsizei count)
рисующая count примитивов,
определяемых параметром mode, используя
элементы из массивов с индексами от first
до first+count-1. Это эквивалентно вызову
команды glArrayElement() с соответствующими
индексами.
В случае если одна вершина
входит в несколько примитивов, то вместо
дублирования ее координат в массиве
удобно использовать ее индекс.
Для этого надо вызвать команду
void glDrawArrays(GLenum mode, GLsizei count,
GLenum type, void *indices)
где indices– это массив номеров
вершин, которые надо использовать для
построения примитивов, type определяет
тип элементов этого массива: GL_UNSIGNED_BYTE,
GL_UNSIGNED_SHORT, GL_UNSIGNED_INT, а count задает их
количество.
Преобразования координат и
проекции
В OpenGL используются как
основные три системы координат:
левосторонняя, правосторонняя и
оконная. Первые две системы являются
трехмерными и отличаются друг от друга
направлением оси z: в правосторонней она
направлена на наблюдателя, а в
левосторонней – в глубь экрана.
Расположение осей x и y аналогично
описанному выше. Левосторонняя система
используется для задания значений
параметрам команды gluPerspective(), glOrtho(),
которые будут рассмотрены ниже, а
правосторонняя или мировая система
координат во всех остальных случаях.
Отображение трехмерной информации
происходит в двумерную оконную систему
координат.
Для задания различных
преобразований объектов сцены в OpenGL
используются операции над матрицами,
при этом различают три типа матриц:
видовая, проекций и текстуры. Все они
имеют размер 4x4. Видовая матрица
определяет преобразования объекта в
мировых координатах, такие как
параллельный перенос, изменение
масштаба и поворот. Матрица проекций
задает как будут проецироваться
трехмерные объекты на плоскость экрана
(в оконные координаты), а матрица
текстуры определяет наложение текстуры
на объект.
Для того, чтобы выбрать, какую
матрицу надо изменить, используется
команда
void glMatrixMode(GLenum mode)
вызов которой со значением
параметра mode равным GL_MODELVIEW, GL_PROJECTION,
GL_TEXTURE включает режим работы с видовой,
проекций и матрицей текстуры
соответственно. Для вызова команд,
задающих матрицы того или иного типа
необходимо сначала установить
соответствующий режим.
Для определения элементов
матрицы текущего типа вызывается
команда
void glLoadMatrix[f d](GLtype *m)
где m указывает на массив из 16
элементов типа float или double в
соответствии с названием команды, при
этом сначала в нем должен быть записан
первый столбец матрицы, затем второй,
третий и четвертый.
Команда
void glLoadIdentity(void)
заменяет текущую матрицу на
единичную. Часто нужно сохранить
содержимое текущей матрицы для
дальнейшего использования, для чего
используют команды
void glPushMatrix(void)
void glPopMatrix(void)
Они записывают и
восстанавливают текущую матрицу из
стека, причем для каждого типа матриц
стек свой. Для видовых матриц его
глубина равна как минимум 32, а для двух
оставшихся типов как минимум 2.
Для умножения текущей матрицы
слева на другую матрицу используется
команда
void glMultMatrix[f d](GLtype *m)
где m должен задавать матрицу
размером 4x4 в виде массива с описанным
расположением данных. Однако обычно для
изменения матрицы того или иного типа
удобно использовать специальные
команды, которые по значениям своих
параметров создают нужную матрицу и
перемножают ее с текущей. Чтобы сделать
текущей созданную матрицу, надо перед
вызовом этой команды вызвать glLoadIdentity().
В целом, для отображения
трехмерных объектов сцены в окно
приложения используется следующая
последовательность действий