Введение в OpenGL
Содержание:
- Введение
- Вершины и примитивы
- Преобразования координат и проекции
- Материалы и освещение
- Текстуры
- Приложение
Введение
OpenGL является на данный момент одним из самых популярных программных интерфейсов (API) для разработки приложений в области двумерной и трехмерной графики. Стандарт OpenGL был разработан и утвержден в 1992 году ведущими фирмами в области разработки программного обеспечения, а его основой стала библиотека IRIS GL, разработанная Silicon Graphics.
На данный момент реализация OpenGL включает в себя несколько библиотек (описание базовых функций OpenGL, GLU,GLUT,GLAUX и другие), назначение которых будет описано ниже.
Характерными особенностями OpenGL, которые обеспечили распространение и развитие этого графического стандарта, являются:
Стабильность
Дополнения и изменения в стандарте реализуются таким образом, чтобы сохранить совместимость с разработанным ранее программным обеспечением.
Надежность и переносимость
Приложения, использующие OpenGL, гарантируют одинаковый визуальный результат вне зависимости от типа используемой операционной системы и организации отображения информации. Кроме того, эти приложения могут выполняться как на персональных компьютерах, так и на рабочих станциях и суперкомпьютерах.
Легкость применения
Стандарт OpenGL имеет продуманную структуру и интуитивно понятный интерфейс, что позволяет с меньшими затратами создавать эффективные приложения, содержащие меньше строк кода, чем с использованием других графических библиотек. Необходимые функции для обеспечения совместимости с различным оборудованием реализованы на уровне библиотеки и значительно упрощают разработку приложений.
Основные возможности OpenGL
- Набор базовых примитивов: точки, линии, многоугольники и т.п.
- Видовые и координатные преобразования
- Удаление невидимых линий и поверхностей (z-буфер)
- Использование сплайнов для построения линий и поверхностей
- Наложение текстуры и применение освещения
- Добавление специальных эффектов: тумана, изменение прозрачности,сопряжение цветов (blending), устранение ступенчатости (anti-aliasing).
Как уже было сказано, существует реализация OpenGL для разных платформ, для чего было удобно разделить базовые функции графической системы и функции для отображения графической информации и взаимодействия с пользователем. Были созданы библиотеки для отображения информации с помощью оконной подсистемы для операционных систем Windows и Unix (WGL и GLX соответственно), а также библиотеки GLAUX и GLUT, которые используются для создания так называемых консольных приложений.
Библиотека GLAUX уступает по популярности написанной несколько позже библиотеке GLUT, хотя они предоставляют примерно одинаковые возможности. В дальнейшем в данном пособии в качестве основной будет рассматриваться библиотека GLUT, предоставляющая широкий набор средств взаимодействия с пользователем.
В состав библиотеки GLU вошла реализация более сложных функций, таких как набор популярных геометрических примитивов (куб, шар, цилиндр, диск), функции построения сплайнов, реализация дополнительных операций над матрицами и т.п. Все они реализованы через базовые функции OpenGL.
Архитектура и особенности синтаксиса
С точки зрения архитектуры, графическая система OpenGL является конвейером, состоящим из нескольких этапов обработки данных:
- Аппроксимация кривых и поверхностей
- Обработка вершин и сборка примитивов
- Растеризация и обработка фрагментов
- Операции над пикселами
- Подготовка текстуры
- Передача данных в буфер кадра
Вообще, OpenGL можно сравнить с конечным автоматом, состояние которого определяется множеством значений специальных переменных (их имена обычно начинаются с символов GL_) и значениями текущей нормали, цвета и координат текстуры. Все эта информация будет использована при поступлении в систему координат вершины для построения фигуры, в которую она входит. Смена состояний происходит с помощью команд, которые оформляются как вызовы функций.
Для обеспечения интуитивно понятных названий в OpenGL полное имя команды имеет вид:
type glCommand_name[1 2 3 4][b s i f d ub us ui][v](type1 arg1,…,typeN argN)
Таким образом, имя состоит из нескольких частей:
gl это имя библиотеки, в которой описана эта функция: для базовых функций OpenGL, функций из библиотек GLU, GLUT, GLAUX это gl, glu, glut, aux соответственно.
Command_name имя команды [1 2 3 4]число аргументов команды [b s i f d ub us ui ]тип аргумента:
символ b означает тип GLbyte (аналог char в С\С++), символ f тип GLfloat (аналог float), символ i– тип GLint(аналог int) и так далее.
Полный список типов и их описание можно посмотреть в файле gl.h
[v] наличие этого символа показывает, что в качестве параметров функции используется указатель на массив значений
Символы в квадратных скобках в некоторых названиях не используются. Например, команда glVertex2i() описана как базовая в библиотеке OpenGL, и использует в качестве параметров два целых числа, а команда glColor3fv() использует в качестве параметра указатель на массив из трех вещественных чисел.
Структура консольного приложения
Будем рассматривать построение консольного приложения при помощи библиотеки 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.
Структура приложения, использующего анимацию, будет следующей:
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 отключает.
Например, чтобы нарисовать треугольник с разными цветами в вершинах, достаточно написать:
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 задает их количество.
Списки изображений
Если нужно несколько раз обращаться к одной и той же группе команд,эти команды можно объединить в так называемый список изображений (display list) и вызывать его при необходимости. Для того, чтобы создать новый список изображений надо поместить все команды, которые должны в него войти между командными скобками:
- void glNewList(GLuint list, GLenum mode)
- void glEndList()
Для различения списков используются целые положительные числа, задаваемые при создании списка значением параметра list, а параметр mode определяет режим обработки команд, входящих в список:
- GL_COMPILE команды записываются в список без выполнения
- GL_COMPILE_AND_EXECUTE команды сначала выполняются, а затем записываются в список
После того, как список создан, его можно вызвать командой
void glCallList(GLuint list)
указав в параметре list идентификатор нужного списка. Чтобы вызвать сразу несколько списков, можно воспользоваться командой
void glCallLists(GLsizei n, GLenum type, const GLvoid *lists)
вызывающей n списков с идентификаторами из массива lists, тип элементов которого указывается в параметре type. Это могут быть типы GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_INT, GL_UNSIGNED_INT >и некоторые другие. Для удаления списков используется команда
void glDeleteLists(GLint list, GLsizei range)
которая удаляет списки с идентификаторами ID из диапазона list <=ID<= list+range-1.
Преобразования координат и проекции
В 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().
В целом, для отображения трехмерных объектов сцены в окно приложения используется следующая последовательность действий:
Координаты объекта | => | Видовые координаты | => | Усеченные координаты | => | Нормализованные координаты | => | Оконные координаты |
Рассмотрим каждое из этих преобразований отдельно.
Видовое преобразование
К видовым преобразованиям будем относить перенос, поворот и изменение масштаба вдоль координатных осей. Для проведения этих операций достаточно умножить на соответствующую матрицу каждую вершину объекта и получить измененные координаты этой вершины:
(x’, y’, z’, 1)T =M * (x, y, z, 1)T
где M матрица видового преобразования. Перспективное преобразование и проектирование производится аналогично. Сама матрица может быть создана с помощью следующих команд:
- void glTranslate[f d](GLtype x, GLtype y, GLtype z)
- void glRotate[f d](GLtype angle, GLtype x, GLtype y, GLtype z)
- void glScale[f d](GLtype x, GLtype y, GLtype z)
glTranlsate..() производит перенос объекта, прибавляя к координатам его вершин значения своих параметров.
glRotate..() производит поворот объекта против часовой стрелки на угол angle (измеряется в градусах) вокруг вектора ( x,y,z ).
glScale..() производит масштабирование объекта (сжатие или растяжение), домножая соответствующие координаты его вершин на значения своих параметров.
Все эти преобразования будут применяться к примитивам, описания которых будут находиться ниже в программе. В случае если надо, например, повернуть один объект сцены, а другой оставить неподвижным, удобно сначала сохранить текущую видовую матрицу в стеке командой glPushMatrix(), затем вызвать glRotate..() с нужными параметрами, описать примитивы, из которых состоит этот объект, а затем восстановить текущую матрицу командой glPopMatrix().
Кроме изменения положения самого объекта иногда бывает нужно изменить положение точки наблюдения, что однако также приводит к изменению видовой матрицы. Это можно сделать с помощью команды
GLdouble eyey,
GLdouble eyez,
GLdouble centerx,
GLdouble centery,
GLdouble centerz,
GLdouble upx,
GLdouble upy,
GLdouble upz)
где точка ( eyex,eyey,eyez ) определяет точку наблюдения, ( centerx, centery, centerz )задает центр сцены, который будет проектироваться в центр области вывода, а вектор ( upx,upy,upz ) задает положительное направление оси у, определяя поворот камеры. Если, например, камеру не надо поворачивать, то задается значение (0,1,0), а со значением (0,-1,0) сцена будет перевернута.
Фактически, эта команда совершает перенос и поворот объектов сцены, но в таком виде задавать параметры бывает удобнее.
Проекции
В OpenGL существуют ортографическая (параллельная) и перспективная проекция. Первый тип проекции может быть задан командами
- void glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far)
- void gluOrtho2D(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top)
Первая команда создает матрицу проекции в усеченный объем видимости (параллелограмм видимости) в левосторонней системе координат. Параметры команды задают точки (left, bottom, -near) и (right, top, -near), которые отвечают левому нижнему и правому верхнему углам окна вывода. Параметры near и far задают расстояние до ближней и дальней плоскостей отсечения по дальности от точки (0,0,0) и могут быть отрицательными.
Во второй команде, в отличие от первой, значения near и far устанавливаются равными –1 и 1 соответственно.
Перспективная проекция определяется командой
void gluPerspective(GLdouble angley, GLdouble aspect, GLdouble znear, GLdouble zfar)
которая задает усеченный конус видимости в левосторонней системе координат. Параметр angley определяет угол видимости в градусах по оси у и должен находиться в диапазоне от 0 до 180. Угол видимости вдоль оси x задается параметром aspect, который обычно задается как отношение сторон области вывода. Параметры zfar и znear задают расстояние от наблюдателя до плоскостей отсечения по глубине и должны быть положительными. Чем больше отношение zfar/znear, тем хуже в буфере глубины будут различаться расположенные рядом поверхности, так как по умолчанию в него будет записываться ‘сжатая’ глубина в диапазоне от 0 до 1 (см. следующий пункт).
Область вывода
После применения матрицы проекций на вход следующего преобразования подаются так называемые усеченные (clip) координаты, для которых значения всех компонент (xc, yc, zc, wc)T находятся в отрезке [-1,1]. После этого находятся нормализованные координаты вершин по формуле:
(xn, yn, zn)T=(xc/wc, yc/wc, zc/wc)T
Область вывода представляет из себя прямоугольник в оконной системе координат, размеры которого задаются командой:
void glViewPort(GLint x, GLint y, GLint width, GLint height)
Значения всех параметров задаются в пикселах и определяют ширину и высоту области вывода с координатами левого нижнего угла ( x,y ) в оконной системе координат. Размеры оконной системы координат определяются текущими размерами окна приложения, точка (0,0)находится в левом нижнем углу окна.
Используя параметры команды glViewPort(), вычисляются оконные координаты центра области вывода (ox, oy) по формулам ox=x+width/2, oy=y+height/2.
Пусть px=width, py=height, тогда можно найти оконные координаты каждой вершины:
(xw, yw, zw)T = ( (px/2) xn+ ox , (py/2) yn+ oy , [(f-n)/2] zn+(n+f)/2 )T
При этом целые положительные величины n и f задают минимальную и максимальную глубину точки в окне и по умолчанию равны 0 и 1 соответственно. Глубина каждой точки записывается в специальный буфер глубины (z-буфер), который используется для удаления невидимых линий и поверхностей. Установить значения n и f можно вызовом функции
void glDepthRange(GLclampd n, GLclampd f)
Команда glViewPort() обычно используется в функции, зарегистрированной с помощью команды glutReshapeFunc(), которая вызывается, если пользователь изменяет размеры окна приложения, изменяя соответсвующим образом область вывода.
Материалы и освещение
Для создания реалистических изображений необходимо определить как свойства самого объекта, так и свойства среды, в которой он находится. Первая группа свойств включает в себя параметры материла, из которого сделан объект, способы нанесения текстуры на его поверхность, степень прозрачности объекта. Ко второй группе можно отнести количество и свойства источников света, уровень прозрачности среды. Все эти свойства можно задавать, используя соответствующие команды OpenGL.
Свойства материала
Для задания параметров текущего материала используются команды
- void glMaterial[i f](GLenum face, GLenum pname, GLtype param)
- void glMaterial[i f]v(GLenum face, GLenum pname, GLtype *params)
С их помощью можно определить рассеянный, диффузный и зеркальный цвета материала, а также цвет степень зеркального отражения и интенсивность излучения света, если объект должен светиться. Какой именно параметр будет определяться значением param, зависит от значения pname:
GL_AMBIENT параметр params должен содержать четыре целых или вещественных значения цветов RGBA, которые определяют рассеянный цвет материала (цвет материала в тени).
Значение по умолчанию: (0.2, 0.2, 0.2, 1.0).
GL_DIFFUSE параметр params должен содержать четыре целых или вещественных значения цветов RGBA, которые определяют цвет диффузного отражения материала.
Значение по умолчанию:(0.8, 0.8, 0.8, 1.0).
GL_SPECULAR параметр params должен содержать четыре целых или вещественных значения цветов RGBA, которые определяют цвет зеркального отражения материала.
Значение по умолчанию: (0.0, 0.0, 0.0, 1.0).
GL_SHININESS параметр params должен содержать одно целое или вещественное значение в диапазоне от 0 до 128, которое определяет степень зеркального отражения материала.
Значение по умолчанию: 0.
GL_EMISSION параметр params должен содержать четыре целых или вещественных значения цветов RGBA, которые определяют интенсивность излучаемого света материала.
Значение по умолчанию: (0.0, 0.0, 0.0, 1.0).
GL_AMBIENT_AND_DIFFUSE эквивалентно двум вызовам команды glMaterial..() со значением pname GL_AMBIENT и GL_DIFFUSE и одинаковыми значениями params.
Из этого следует, что вызов команды glMaterial[i f]() возможен только для установки степени зеркального отражения материала. В большинстве моделей учитывается диффузный и зеркальный отраженный свет; первый определяет естественный цвет объекта, а второй – размер и форму бликов на его поверхности.
Параметр face определяет тип граней, для которых задается этот материал и может принимать значения GL_FRONT, GL_BACK или GL_FRONT_AND_BACK.
Если в сцене материалы объектов различаются лишь одним параметром, рекомендуется сначала установить нужный режим, вызвав glEnable() c параметром GL_COLOR_MATERIAL, а затем использовать команду
void glColorMaterial(GLenum face, GLenum pname)
где параметр face имеет аналогичный смысл, а параметр pname может принимать все перечисленные значения. После этого, значения выбранного с помощью pname свойства материала для конкретного объекта (или вершины) устанавливается вызовом команды glColor..(), что позволяет избежать вызовов более ресурсоемкой команды glMaterial..() и повышает эффективность программы.
Источники света
Добавить в сцену источник света можно с помощью команд
- void glLight[i f](GLenum light, GLenum pname, GLfloat param)
- void glLight[i f](GLenum light, GLenum pname, GLfloat *params)
Параметр light однозначно определяет источник,и выбирается из набора специальных символических имен вида GL_LIGHTi, где i должно лежать в диапазоне от 0 до GL_MAX_LIGHT, которое не превосходит восьми.
Оставшиеся два параметра имеют аналогичный смысл, что и в команде glMaterial..(). Рассмотрим их назначение (вначале описываются параметры для первой команды, затем для второй):
GL_SPOT_EXPONENT параметр param должен содержать целое или вещественное число от 0 до 128, задающее распределение интенсивности света. Этот параметр описывает уровень сфокусированности источника света.
Значение по умолчанию: 0 (рассеянный свет).
GL_SPOT_CUTOFF параметр param должен содержать целое или вещественное число между 0 и 90 или равное 180, которое определяет максимальный угол разброса света. Значение этого параметра есть половина угла в вершине конусовидного светового потока, создаваемого источником.
Значение по умолчанию: 180 (рассеянный свет).
GL_AMBIENT параметр params должен содержать четыре целых или вещественных значения цветов RGBA, которые определяют цвет фонового освещения.
Значение по умолчанию: (0.0, 0.0, 0.0, 1.0).
GL_DIFFUSE параметр params должен содержать четыре целых или вещественных значения цветов RGBA, которые определяют цвет диффузного освещения.
Значение по умолчанию: (1.0, 1.0, 1.0, 1.0)для LIGHT0 и (0.0, 0.0, 0.0, 1.0) для остальных.
GL_SPECULAR параметр params должен содержать четыре целых или вещественных значения цветов RGBA, которые определяют цвет зеркального отражения.
Значение по умолчанию: (1.0, 1.0, 1.0, 1.0)для LIGHT0 и (0.0, 0.0, 0.0, 1.0) для остальных.
GL_POSITION параметр params должен содержать четыре целых или вещественных, которые определяют положение источника света. Если значение компоненты w равно 0.0, то источник считается бесконечно удаленным и при расчете освещенности учитывается только направление на точку (x,y,z), в противном случае считается, что источник расположен в точке (x,y,z,w).
Значение по умолчанию: (0.0, 0.0, 1.0, 0.0).
GL_SPOT_DIRECTION параметр params должен содержать четыре целых или вещественных числа, которые определяют направление света.
Значение по умолчанию: (0.0, 0.0, -1.0, 1.0).
При изменении положения источника света следует учитывать следующие факты: если положение задается командой glLight..() перед определением ориентации взгляда (командой glLookAt() ), то будет считаться, что источник находится в точке наблюдения. Если положение устанавливается между заданием ориентации и преобразованиями видовой матрицы, то оно фиксируется и не зависит от видовых преобразований. В последнем случае, когда положение задано после ориентации и видовой матрицы, его положение можно менять, устанавливая как новую ориентацию наблюдателя, так и меняя видовую матрицу.
Для использования освещения сначала надо установить соответствующий режим вызовом команды glEnable (GL_LIGHTNING), а затем включить нужный источник командой glEnable(GL_LIGHTn).
Модель освещения
В OpenGL используется модель освещения Фонга, в соответствии с которой цвет точки определяется несколькими факторами: свойствами материала и текстуры, величиной нормали в этой точке, а также положением источника света и наблюдателя. Для корректного расчета освещенности в точке надо использовать единичные нормали, однако команды типа glScale..(), могут изменять длину нормалей. Чтобы это учитывать, используется уже упоминавшийся режим нормализации нормалей, который включается вызовом команды glEnable(GL_NORMALIZE).
Для задания глобальных параметров освещения используются команды
- void glLightModel[i f](GLenum pname, GLenum param)
- void glLightModel[i f]v(GLenum pname, const GLtype *params)
Аргумент pname определяет, какой параметр модели освещения будет настраиваться и может принимать следующие значения:
GL_LIGHT_MODEL_LOCAL_VIEWER параметр param должен быть булевским и задает положение наблюдателя. Если он равен FALSE, то направление обзора считается параллельным оси –z, вне зависимости от положения в видовыx координатах. Если же он равен TRUE, то наблюдатель находится в начале видовой системы координат. Это может улучшить качество освещения, но усложняет его расчет.
Значение по умолчанию: FALSE.
GL_LIGHT_MODEL_TWO_SIDE параметр param должен быть булевским и управляет режимом расчета освещенности как для лицевых, так и для обратных граней. Если он равен FALSE, то освещенность рассчитывается только для лицевых граней. Если же он равен TRUE, расчет проводится и для обратных граней. Значение по умолчанию: FALSE .
GL_LIGHT_MODEL_AMBIENT параметр params должен содержать четыре целых или вещественных числа, которые определяют цвет фонового освещения даже в случае отсутствия определенных источников света.
Значение по умолчанию:(0.2, 0.2, 0.2,1.0).
Текстуры
Наложение текстуры на поверхность объектов сцены повышает ее реалистичность, однако при этом надо учитывать, что этот процесс требует значительных вычислительных затрат. Под текстурой будем понимать некоторое изображение, которое надо определенным образом нанести на объект. Для этого следует выполнить следующие этапы:
- выбрать изображение и преобразовать его к нужному формату
- загрузить изображение в память
- определить, как текстура будет наноситься на объект и как она будет с ним взаимодействовать.
Рассмотрим каждый из этих этапов.
Подготовка текстуры
Принятый в OpenGL формат хранения изображений отличается от стандартного формата Windows DIB только тем, что компоненты (R,G,B) для каждой точки хранятся в прямом порядке, а не в обратном и выравнивание задается программистом. Считывание графических данных из файла и их преобразование можно проводить и вручную, однако удобней воспользоваться функцией, входящей в состав библиотеки GLAUX (для ее использования надо дополнительно подключить glaux.lib), которая сама проводит необходимые операции. Это функция
AUX_RGBImageRec* auxDIBImageLoad(string file)
где file– название файла с расширением *.bmp или *.dib. В качестве результата функция возвращает указатель на область памяти, где хранятся преобразованные данные.
При создании образа текстуры в памяти следует учитывать следующие требования.
Во-первых, размеры текстуры как по горизонтали, так и по вертикали должны представлять собой степени двойки. Это требование накладывается для компактного размещения текстуры в памяти и способствует ее эффективному использованию. Использовать только текстуры с такими размерами конечно неудобно, поэтому перед загрузкой их надо преобразовать. Изменение размеров текстуры проводится с помощью команды
GLint widthin,
GL heightin,
GLenum typein,
const void *datain,
GLint widthout,
GLint heightout,
GLenum typeout,
void *dataout)
В качестве значения параметра format обычно используется значение GL_RGB или GL_RGBA, определяющее формат хранения информации. Параметры widthin, heightin, widhtout, heightout определяют размеры входного и выходного изображений, а с помощью typein и typeout задается тип элементов массивов, расположенных по адресам datain и dataout. Как и обычно, то может быть тип
GL_UNSIGNED_BYTE, GL_SHORT, GL_INT и так далее. Результат своей работы функция заносит в область памяти, на которую указывает параметр dataout.
Во-вторых, надо предусмотреть случай, когда объект по размерам значительно меньше наносимой на него текстуры. Чем меньше объект, тем меньше должна быть наносимая на него текстура и поэтому вводится понятие уровней детализации текстуры. Каждый уровень детализации задает некоторое изображение, которое является как правило уменьшенной в два раза копией оригинала. Такой подход позволяет улучшить качество нанесения текстуры на объект. Например, для изображения размером 2mx2n можно построить max(m,n)+1 уменьшенных изображений, соответствующих различным уровням детализации.
Эти два этапа создания образа текстуры в памяти можно провести с помощью команды
GLint components,
GLint width,
GLint height,
GLenum format,
GLenum type,
const void *data)
где параметр target должен быть равен GL_TEXTURE_2D, components определяет количество цветовых компонент текстуры, которые будут использоваться при ее наложении и может принимать значения от 1 до 4 (1-только красный,2-красный и alpha, 3-красный, синий, зеленый, 4-все компоненты).
Параметры width, height, data определяют размеры и расположение текстуры соответственно, а format и type имеют аналогичный смысл, что и в команде gluScaleImage().
В OpenGL допускается использование одномерных текстур, то есть размера 1xN, однако это всегда надо указывать, используя в качестве значения target константу GL_TEXTURE_1D. Существует одномерный аналог рассматриваемой команды- gluBuild1DMipmaps(), который отличается от двумерного отсутствием параметра height.
При использовании в сцене нескольких текстур, в OpenGL применяется подход, напоминающий создание списков изображений. Вначале, с помощью команды
void glGenTextures(GLsizei n, GLuint*textures)
надо создать n идентификаторов для используемых текстур, которые будут записаны в массив textures. Перед началом определения свойств очередной текстуры следует вызвать команду
void glBindTexture(GLenum target, GLuint texture)
где target может принимать значения GL_TEXTURE_1D или GL_TEXTURE_2D, а параметр texture должен быть равен идентификатору той текстуры, к которой будут относиться последующие команды. Для того, чтобы в процессе рисования сделать текущей текстуру с некоторым идентификатором, достаточно опять вызвать команду glBindTexture() c соответствующим значением target и texture. Таким образом, команда glBindTexture() включает режим создания текстуры с идентификатором texture, если такая текстура еще не создана, либо режим ее использования, то есть делает эту текстуру текущей.
Методы наложения текстуры
При наложении текстуры, как уже упоминалось, надо учитывать случай, когда размеры текстуры отличаются от размеров объекта, на который она накладывается. При этом возможно как растяжение, так и сжатие изображения, и то, как будут проводиться эти преобразования может серьезно повлиять на качество построенного изображения. Для определения положения точки на текстуре используется параметрическая система координат (s,t), причем значения s и t находятся в отрезке [0,1]. Для изменения различных параметров текстуры применяются команды:
- void glTexParameter[i f](GLenum target, GLenum pname, GLenum param)
- void glTexParameter[i f]v(GLenum target, GLenum pname, GLenum *params)
При этом target имеет аналогичный смысл, что и раньше, pname определяет, какое свойство будем менять,а с помощью param или params устанавливается новое значение. Возможные значения pname:
GL_TEXTURE_MIN_FILTER параметр param определяет функцию, которая будет использоваться для сжатия текстуры. При значении GL_NEAREST будет использоваться один (ближайший), а при значении GL_LINEAR четыре ближайших элемента текстуры.
Значение по умолчанию: GL_LINEAR.
GL_TEXTURE_MAG_FILTER параметр param определяет функцию, которая будет использоваться для увеличения (растяжения) текстуры. При значении GL_NEAREST будет использоваться один (ближайший), а при значении GL_LINEAR четыре ближайших элемента текстуры.
Значение по умолчанию: GL_LINEAR.
GL_TEXTURE_WRAP_S параметр param устанавливает значение координаты s, если оно не входит в отрезок [0,1]. При значении GL_REPEAT целая часть s отбрасывается, и в результате изображение размножается по поверхности. При значении GL_CLAMP используются краевые значения: 0 или 1, что удобно использовать, если на объект накладывается один образ.
Значение по умолчанию: GL_REPEAT.
GL_TEXTURE_WRAP_T аналогично предыдущему значению, только для координаты t.
Использование режима GL_NEAREST значительно повышает скорость наложения текстуры, однако при этом снижается качество, так как в отличие от GL_LINEAR интерполяция не производится.
Для того, чтобы определить, как текстура будет взаимодействовать с материалом, из которого сделан объект, используются команды
- void glTexEnv[i f](GLenum target, GLenum pname, GLtype param)
- void glTexEnv[i f]v(GLenum target, GLenum pname, GLtype *params)
Параметр target должен быть равен GL_TEXTURE_ENV, а в качестве pname рассмотрим только одно значение GL_TEXTURE_ENV_MODE, которое наиболее часто применяется. Параметр если param может быть равен:
- GL_MODULATE конечный цвет находится как произведение цвета точки на поверхности и цвета соответствующей ей точки на текстуре.
- GL_REPLACE в качестве конечного цвета используется цвет точки на текстуре.
- GL_BLEND конечный цвет находится как сумма цвета точки на поверхности и цвета соответствующей ей точки на текстуре с учетом их яркости.
Координаты текстуры
Перед нанесением текстуры на объект осталось установить соответствие между точками на поверхности объекта и на самой текстуре. Задавать это соответствие можно двумя методами: отдельно для каждой вершины или сразу для всех вершин, задав параметры специальной функции отображения.
Первый метод реализуется с помощью команд
- void glTexCoord[1 2 3 4][s i f d](type coord)
- void glTexCoord[1 2 3 4][s i f d]v(type *coord)
Чаще всего используется команды вида glTexCoord2..(type s, type t), задающие текущие координаты текстуры. Вообще, понятие текущих координат текстуры аналогично понятиям текущего цвета и текущей нормали, и является атрибутом вершины. Однако даже для куба нахождение соответствующих координат текстуры является довольно трудоемким занятием, поэтому в библиотеке GLU помимо команд, проводящих построение таких примитивов, как сфера, цилиндр и диск, предусмотрено также наложение на них текстур. Для этого достаточно вызвать команду
void gluQuadricTexture(GLUquadricObj*quadObject, GLboolean textureCoords)
с параметром textureCoords равным GL_TRUE, и тогда текущая текстура будет автоматически накладываться на примитив.
Второй метод реализуется с помощью команд
- void glTexGen[i f d](GLenum coord, GLenum pname, GLtype param)
- void glTexGen[i f d]v(GLenum coord, GLenum pname, const GLtype *params)
Параметр coord определяет для какой координаты задается формула и может принимать значение GL_S, GL_T; pname определяет тип формулы и может быть равен GL_TEXTURE_GEN_MODE, GL_OBJECT_PLANE, GL_EYE_PLANE. С помощью params задаются необходимые параметры, а param может быть равен GL_OBJECT_LINEAR, GL_EYE_LINEAR, GL_SPHERE_MAP. Рассмотрение всех возможных комбинаций значений аргументов этой команды заняло бы слишком много места, поэтому в качестве примера рассмотрим, как можно задать зеркальную текстуру. При таком наложении текстуры изображение будет как бы отражаться от поверхности объекта, вызывая интересный оптический эффект. Для этого сначала надо создать два целочисленных массива коэффициентов s_coeffs и t_coeffs со значениями (1,0,0,1) и (0,1,0,1) соответственно, а затем вызвать команды:
glEnable(GL_TEXTURE_GEN_S); glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR); glTexGendv(GL_S, GL_EYE_PLANE, s_coeffs); и такие же команды для координаты t с соответствующими изменениями.
Приложение
Приложение содержит информацию в основном прикладного характера, которая может понадобиться при изучении описанного выше материала.
Стандартные геометрические примитивы
Рассмотрим стандартные команды построения примитивов, которые реализованы в библиотеках GLU и GLUT.
Как уже было сказано, чтобы построить примитив из библиотеки GLU, надо сначала создать указатель на quadric- объект с помощью команды gluNewQuadric(), а затем вызвать одну из команд gluSphere(), gluCylinder(), gluDisk(), gluPartialDisk(). Рассмотрим эти команды отдельно:
GLdouble radius,
GLint slices,
GLint stacks)
Строит сферу с центром в начале координат и радиусом radius. При этом число разбиений сферы вокруг оси z задается параметром slices, а вдоль оси z параметром stacks.
void gluCylinder(GLUquadricObj*qobj, GLdouble baseRadius, GLdouble topRadius, GLdouble height, GLint slices, GLint stacks)
Строит цилиндр без оснований (то есть кольцо), продольная ось параллельна оси z, заднее основание имеет радиус baseRadius , и расположено в плоскости z=0, переднее основание имеет радиус topRadius и расположено в плоскости z=height. Если задать один из радиусов равным нулю, то будет построен конус.
Параметры slices и stacks имеют тот же смысл, что и в предыдущей команде.
GLdouble innerRadius,
GLdouble outerRadius,
GLint slices,
GLint loops)
Строит плоский диск (то есть круг) с центром в начале координат и радиусом outerRadius. При этом если значение innerRadius ненулевое, то в центре диска будет находиться отверстие радиусом innerRadius. Параметр slices задает число разбиений диска вокруг оси z, а параметр loops –число концентрических колец, перпендикулярных оси z.
GLdouble innerRadius,
GLdouble outerRadius,
GLint slices,
GLint loops,
GLdouble startAngle,
GLdouble sweepAngle);
Отличие этой команды от предыдущей заключается в том, что она строит сектор круга, начальный и конечный углы которого отсчитываются против часовой стрелки от положительного направления оси y и задаются параметрами startAngle и sweepAngle. Углы измеряются в градусах.
Команды, проводящие построение примитивов из библиотеки GLUT, реализованы через стандартные примитивы OpenGL и GLU. Для построения нужного примитива достаточно произвести вызов соответствующей команды.
- void glutSolidSphere(GLdouble radius, GLint slices, GLint stacks)
- void glutWireSphere(GLdouble radius, GLint slices, GLint stacks)
Команда glutSolidSphere() строит сферу, а glutWireSphere() -каркас сферы радиусом radius. Остальные параметры имеют тот же смысл, что и в предыдущих командах.
- void glutSolidCube(GLdouble size)
- void glutWireCube(GLdouble size)
Эти команды строят куб или каркас куба с центром в начале координат и длиной ребра size.
- void glutSolidCone(GLdouble base, GLdouble height, GLint slices, GLint stacks)
- void glutWireCone(GLdouble base, GLdouble height, GLint slices, GLint stacks)
Эти команды строят конус или его каркас высотой height и радиусом основания base, расположенный вдоль оси z. Основание находится в плоскости z=0. Остальные параметры имеют тот же смысл, что и в предыдущих командах.
- void glutSolidTorus(GLdouble innerRadius, GLdouble outerRadius, GLint nsides, GLint rings)
- void glutWireTorus(GLdouble innerRadius, GLdouble outerRadius, GLint nsides, GLint rings)
Эти команды строят тор или его каркас в плоскости z=0. Внутренний и внешний радиусы задаются параметрами innerRadius, outerRadius. Параметр nsides задает число сторон в кольцах, составляющих ортогональное сечение тора, а rings- число радиальных разбиений тора.
- void glutSolidTetrahedron(void)
- void glutWireTetrahedron (void)
Эти команды строят тетраэдр (правильную треугольную пирамиду) или его каркас, при этом радиус описанной сферы вокруг него равен 1.
- void glutSolidOctahedron(void)
- void glutWireOctahedron(void)
Эти команды строят октаэдр или его каркас, радиус описанной вокруг него сферы равен 1.
- void glutSolidDodecahedron(void)
- void glutWireDodecahedron(void)
Эти команды строят додекаэдр или его каркас, радиус описанной вокруг него сферы равен квадратному корню из трех.
- void glutSolidIcosahedron(void)
- void glutWireIcosahedron(void)
Эти команды строят икосаэдр или его каркас, радиус описанной вокруг него сферы равен 1.
Создание приложения в среде MS Visual C++ 5.0
Перед началом работы необходимо скопировать файлы glut.h, glut32.lib glut32.dll в каталоги ..\MSVC\Include\Gl, ..\MSVC\Lib, ..\Windows\System соответственно. Также в этих каталогах надо проверить наличие файлов gl.h, glu.h, opengl32.lib, glu32.lib, opengl32.dll, glu32.dll, которые обычно входят в состав Visual C++ и Windows. При использовании команд из библиотеки GLAUX к перечисленным файлам надо добавить glaux.h, glaux.lib.
Для создания приложения надо выполнить следующие действия:
- Создание проекта: для этого надо выбрать File->New->Projects->Win32 Console Application, набрать имя проекта, OK.
- В появившемся окне выбрать ‘ An empty project ’, Finish , OK .
- Текст программы можно либо разместить в созданном текстовом файле(выбрав File->New->Files->Text File ), либо добавив файл с расширением *.c или *.cpp в проект (выбрав Project->Add To Project->Files ).
- Подключить к проекту библиотеки OpenGL. Для этого надо выбрать Project->Settings->Link и в поле Object/library modules набрать названия нужных библиотек: opengl32.lib, glu32.lib, glut32.lib и, если надо, glaux.lib.
- Для компиляции выбрать Build->Build program.exe , для выполнения – Build->Execute program.exe .
- Чтобы при запуске не появлялось текстовое окно, надо выбрать Project->Settings->Link и в поле Project Options вместо ‘subsystem:console’ набрать ‘subsystem:windows’,и набрать там же строку ‘/entry:mainCRTStartup’
- Когда программа готова, рекомендуется перекомпилировать ее в режиме ‘ Release ’ для оптимизации по быстродействию и объему. Для этого сначала надо выбрать Build->Set Active Configuration… и отметить -Win32 Release, а затем заново подключить необходимые библиотеки.
Создание приложения в среде Borland C++ 5.02
Как и для Visual C++, сначала надо обеспечить наличие файлов glut.h, glut32.lib, glut32.dll в каталогах ..\BorlandC\Include\Gl, ..\BorlandC\Lib, ..\Windows\System соответственно. Также в этих каталогах надо проверить наличие файлов gl.h, glu.h, opengl32.lib, glu32.lib, opengl32.dll, glu32.dll, которые обычно входят в состав BorlandC++ и Windows. При этом надо учитывать, что версии Microsoft файлов opengl32.lib, glu32.lib, glut32.lib для Borland C++ не подходят и следует использовать только совместимые версии. Чтобы создать такие версии, надо использовать стандартную программу ‘implib, которая находится в каталоге ..\BorlandC\Bin. Для этого надо выполнить команды вида implib ..\BorlandC\Lib\filename.lib ..\filename.dll для перечисленных файлов, которые создают нужный *.lib файл из соответствующего *.dll файла. Кроме того, надо отметить, что компилятор BorlandC не может по неизвестным причинам использовать файл glaux.lib, входящий в состав BorlandC++5.02, при компиляции приложения, использующего библиотеку GLAUX, поэтому возможно от этой библиотеки придется отказаться. Для создания приложения надо выполнить следующие действия:
- Создание проекта: для этого надо выбрать Project->New Project и заполнить поля в окне Target Expert следующим образом: в поле Platform выбрать Win32,в поле Taget Model выбрать Сonsole, нажать Advanced и отменить выбор пунктов *.rc ‘и *.def.
- Подключить к проекту библиотеки OpenGL. Для этого надо выбрать в окне проекта название исполняемого файла проекта (*.exe) и нажав правую кнопку мыши выбрать в контекстном меню пункт Add node. Затем надо определить положение файлов opengl32.lib, glu32.lib, glut32.lib.
- Для компиляции выбрать Project->Build All, для выполнения Debug->Run.
Пример программы
Результатом выполнения этой программы является построение тетраэдра с вращающимися вокруг него кольцами, на которые нанесена текстура. В среде MS Visual C++ программа может компилироваться без изменений, а при компиляции в Borland C++ придется закомментировать вызов и описание функции TextureInit(), после чего не будет проводиться наложение текстур. Как было сказано выше, попытка использовать функции из библиотеки GLAUX приводит к сообщению об ошибке при компиляции программы.
При компиляции программы в MS Visual C++ файл ‘texture.bmp’ надо поместить в каталог проекта или указать полный путь к нему, используя символ ‘/’. Если путь не указан, то при запуске исполняемого файла из операционной системы, файл с текстурой должен находиться в том же каталоге.
#include <GL\glaux.h>
#include <math.h>
#define TETR_LIST 1
GLfloat light_col[] = {1,1,1};
float mat_diff1[]={0.8,0.8,0.8};
float mat_diff2[]={0.0,0.0,0.9};
float mat_amb[]= {0.2,0.2,0.2};
float mat_spec[]={0.6,0.6,0.6};
float shininess=0.7*128, CurAng=0, RingRad=1, RingHeight=0.1;
GLUquadricObj* QuadrObj;
GLuint TexId;
GLfloat TetrVertex[4][3], TetrNormal[4][3];
//--Вычисление нормали к плоскости, задаваемой точками a,b,c----------//
void getnorm (float a[3],float b[3],float c[3],float *n)
{
float mult=0;
n[0]=(b[1]-a[1])*(c[2]-a[2])-(b[2]-a[2])*(c[1]-a[1]);
n[1]=(c[0]-a[0])*(b[2]-a[2])-(b[0]-a[0])*(c[2]-a[2]);
n[2]=(b[0]-a[0])*(c[1]-a[1])-(c[0]-a[0])*(b[1]-a[1]);
//--Определение нужного направления нормали: от точки (0,0,0)---------//
for (int i=0;i<3;i++) mult+=a[i]*n[i];
if (mult<0) for (int j=0;j<3;j++) n[j]=-n[j];
}
//--Вычисление координат вершин тетраэдра-----------------------------//
void InitVertexTetr()
{
float alpha=0;
TetrVertex[0][0]=0;TetrVertex[0][1]=1.3;TetrVertex[0][2]=0;
//--Вычисление координат основания тетраэдра--------------------------//
for (int i=1;i<4;i++)
{
TetrVertex[i][0]=0.94*cos(alpha);
TetrVertex[i][1]=0;
TetrVertex[i][2]=0.94*sin(alpha);
alpha+=120.0*3.14/180.0;
}
}
//--Вычисление нормалей сторон тетраэдра------------------------------//
void InitNormsTetr()
{
getnorm(TetrVertex[0],TetrVertex[1],TetrVertex[2],TetrNormal[0]);
getnorm(TetrVertex[0],TetrVertex[2],TetrVertex[3],TetrNormal[1]);
getnorm(TetrVertex[0],TetrVertex[3],TetrVertex[1],TetrNormal[2]);
getnorm(TetrVertex[1],TetrVertex[2],TetrVertex[3],TetrNormal[3]);
}
//--Создание списка построения тетраэдра------------------------------//
void MakeTetrList()
{
glNewList (TETR_LIST,GL_COMPILE);
//--Задание сторон тетраэдра------------------------------------------//
glBegin(GL_TRIANGLES);
for (int i=1;i<4;i++)
{
glNormal3fv(TetrNormal[i-1]);
glVertex3fv(TetrVertex[0]);glVertex3fv(TetrVertex[i]);
if (i!=3) glVertex3fv(TetrVertex[i+1]);else glVertex3fv(TetrVertex[1]);
}
glNormal3fv(TetrNormal[3]);
glVertex3fv(TetrVertex[1]);
glVertex3fv(TetrVertex[2]);
glVertex3fv(TetrVertex[3]);
glEnd();
glEndList();
}
void DrawRing()
{
//--Построение цилиндра (кольца), расположенного параллельно оси z----//
//--Второй и третий параметры задают радиусы оснований, четвертый-----//
//--высоту,последние два-число разбиений вокруг и вдоль оси z---------//
//--При этом дальнее основание цилиндра находится в плоскости z=0-----//
gluCylinder (QuadrObj,RingRad,RingRad,RingHeight,30,2);
}
void TextureInit()
{
char strFile[]="texture.bmp";
//--Выравнивание в *.bmp по байту-------------------------------------//
glPixelStorei(GL_UNPACK_ALIGNMENT,1);
//--Создание идентификатора для текстуры- ----------------------------//
glGenTextures(1,&TexId);
//--Загрузка изображения в память-------------------------------------//
AUX_RGBImageRec *pImage = auxDIBImageLoad(strFile);
int BmpWidth= pImage->sizeX;
int BmpHeight = pImage->sizeY;
void* BmpBits = pImage->data;
//--Начало описания свойств текстуры----------------------------------//
glBindTexture (GL_TEXTURE_2D,TexId);
//--Создание уровней детализации и инициализация текстуры ------------//
gluBuild2DMipmaps(GL_TEXTURE_2D,3,BmpWidth, BmpHeight,GL_RGB,GL_UNSIGNED_BYTE,BmpBits);
//--Разрешение наложения этой текстуры на quadric-объекты-------------//
gluQuadricTexture(QuadrObj, GL_TRUE);
//--Задание параметров текстуры---------------------------------------//
//--Повтор изображения по параметрическим осям s и t------------------//
glTexParameteri (GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
glTexParameteri (GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
//--Не использовать интерполяцию при выборе точки на текстуре---------//
glTexParameteri (GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
glTexParameteri (GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
//--Совмещать текстуру и материал объекта-----------------------------//
glTexEnvi (GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
}
void Init(void)
{
InitVertexTetr();
InitNormsTetr();
MakeTetrList();
//--Определение свойств материала-------------------------------------//
glMaterialfv (GL_FRONT_AND_BACK,GL_AMBIENT,mat_amb);
glMaterialfv (GL_FRONT_AND_BACK,GL_SPECULAR,mat_spec);
glMaterialf(GL_FRONT,GL_SHININESS,shininess);
//--Определение свойств освещения-------------------------------------//
glLightfv(GL_LIGHT0, GL_DIFFUSE, light_col);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
//--Проводить удаление невидимых линий и поверхностей-----------------//
glEnable(GL_DEPTH_TEST);
//--Проводить нормирование нормалей-----------------------------------//
glEnable(GL_NORMALIZE);
//--Материалы объектов отличаются только цветом диффузного отражения--//
glEnable(GL_COLOR_MATERIAL);
glColorMaterial(GL_FRONT_AND_BACK,GL_DIFFUSE);
//--Создания указателя на quadric-объект для построения колец---------//
QuadrObj=gluNewQuadric();
//--Определение свойств текстуры--------------------------------------//
TextureInit();
//--Задание перспективной проекции------------------------------------//
glMatrixMode(GL_PROJECTION);
gluPerspective(89.0,1.0,0.5,100.0);
//--Далее будет проводиться только преобразование объектов сцены------//
glMatrixMode(GL_MODELVIEW);
}
void DrawFigures(void)
{
//--Включение режима нанесения текстуры-------------------------------//
glEnable(GL_TEXTURE_2D);
//--Задаем цвет диффузного отражения для колец------------------------//
glColor3fv(mat_diff1);
//--Чтобы не проводить перемножение с предыдущей матрицей загружаем единичную матрицу//
glLoadIdentity();
//--Определяем точку наблюдения---------------------------------------//
gluLookAt(0.0, 0.0, 2.5,0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
//--Сохраняем видовую матрицу, так как дальше будет проводиться поворот колец//
glPushMatrix();
//--Производим несколько поворотов на новый угол (это быстрее,--------//
//--чем умножать предыдущую видовую матрицу на матрицу поворота с-----//
//--фиксированным углом поворота)-------------------------------------//
glRotatef (-CurAng,1,1,0);
glRotatef (CurAng,1,0,0);
//--Для рисования колец каждое из них надо преобразовать отдельно,----//
//--поэтому сначала сохраняем видовую матрицу, затем восстанавливаем--//
glPushMatrix();
glTranslatef (0,0,-RingHeight/2);
DrawRing();
glPopMatrix();
glPushMatrix();
glTranslatef (0,RingHeight/2,0);
glRotatef (90,1,0,0);
DrawRing();
glPopMatrix();
glPushMatrix();
glTranslatef (-RingHeight/2,0,0);
glRotatef (90,0,1,0);
DrawRing();
glPopMatrix();
//--Восстанавливаем матрицу для поворотов тетраэдра--------------------//
glPopMatrix();
//--Выключаем режим наложения текстуры--------------------------------//
glDisable(GL_TEXTURE_2D);
//--Проводим повороты-------------------------------------------------//
glRotatef(CurAng,1,0,0);
glRotatef(CurAng/2,1,0,1);
//--Чтобы тетраэдр вращался вокруг центра, его надо сдвинуть вниз по оси oz//
glTranslatef(0,-0.33,0);
//--Задаем цвет диффузного отражения для тетраэдра--------------------//
glColor3fv(mat_diff2);
//--Проводим построение тетраэдра-------------------------------------//
glCallList(TETR_LIST);
}
void Display(void)
{
//--Инициализация (очистка) текущего буфера кадра и глубины-----------//
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//--Построение объектов-----------------------------------------------//
DrawFigures();
//--Перестановка буферов кадра----------------------------------------//
glutSwapBuffers();
}
void Redraw(void)
{
//--Увеличение текущего угла поворота---------------------------------//
CurAng+=1;
//--Сигнал для вызова процедуры создания изображения (для обновления)-//
glutPostRedisplay();
}
int main(int argc, char **argv)
{
//--Инициализация функций библиотеки GLUT-----------------------------//
glutInit(&argc, argv);
//--Задание режима с двойной буферизацией, представление цвета в формате RGB,--//
//--использование буфера глубины --//
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
//--Создание окна приложения-----------------------------------------//
glutCreateWindow("Example of using OpenGL");
//--Регистрация функции построения изображения-----------------------//
glutDisplayFunc(Display);
//--Регистрация функции обновления изображения-----------------------//
glutIdleFunc(Redraw);
//--Инициализация функций OpenGL-------------------------------------//
Init();
//--Цикл обработки событий-------------------------------------------//
glutMainLoop();
return 0;
}
Результат работы программы:
В программе используется только файл glut.h, который содержит обращения к файлам gl.h и glu.h, поэтому отдельно подключать их не нужно.
Большим достоинством OpenGL является независимость большинства команд. Например, чтобы отключить наложение текстуры, достаточно закомментировать вызов функции TextureInit(), а чтобы получить статичное изображение достаточно не регистрировать функцию обновления изображения вызовом функции glutIdleFunc(). В этом случае можно использовать режим с одним буфером, заменив GL_DOUBLE на GL_SINGLE в команде glutInitDisplayMode() и добавив команду glFlush() в конце процедуры Display() для очистки этого буфера.
Использованные материалы:
- Тихомиров Ю. Программирование трехмерной графики. СПб., BHV 1998.
- Visual Introduction in OpenGL, Siggraph’98.
- The OpenGL graphics system: a specification (version 1.1).
- Программирование GLUT: окна и анимация. Miguel Angel Sepulveda, LinuxFocus.
- The OpenGL Utility Toolkit (GLUT) Programming Interface, API version 3, specification.