Планета знаний
Скачать 1.68 Mb.
|
1) В чем, по вашему мнению, заключается необходимость со- здания стандартной графической библиотеки? 2) Кратко опишите архитектуру библиотек OpenGL и органи- зацию конвейера. 3) В чем заключаются функции библиотек, подобных GLUT или GLX? Почему они формально не входят в OpenGL? 4) Назовите категории команд (функций) библиотеки. 2.6. Контрольные вопросы 37 5) Почему организацию OpenGL часто сравнивают с конеч- ным автоматом? 6) Зачем нужны различные варианты команд OpenGL, отли- чающиеся только типами параметров? 7) Что можно сказать о количестве и типе параметров коман- ды glColor4ub() ? glVertex3fv() ? Глава 3. Рисование геометрических объектов 3.1. Процесс обновления изображения Как правило, задачей программы, использующей OpenGL, является обработка трехмерной сцены и интерактивное отобра- жение в буфере кадра. Сцена состоит из набора трехмерных объ- ектов, источников света и виртуальной камеры, определяющей текущее положение наблюдателя. Обычно приложение OpenGL в бесконечном цикле вызывает функцию обновления изображения в окне. В этой функции и сосредоточены вызовы основных команд OpenGL. Если исполь- зуется библиотека GLUT, то это будет функция с обратным вы- зовом, зарегистрированная с помощью вызова glutDisplayFunc(). GLUT вызывает эту функцию, когда операционная система ин- формирует приложение о том, что содержимое окна необходимо перерисовать (например, если окно было перекрыто другим). Создаваемое изображение может быть как статичным, так и анимированным, т.е. зависеть от каких-либо параметров, изме- няющихся со временем. В этом случае лучше вызывать функ- 39 40 Глава 3. Рисование геометрических объектов цию обновления самостоятельно. Например, с помощью коман- ды glutPostRedisplay() . За более подробной информацией можно обратиться к главе 10. Приступим, наконец, к тому, чем занимается типичная функ- ция обновления изображения. Как правило, она состоит из трех шагов: € очистка буферов OpenGL; € установка положения наблюдателя; € преобразование и рисование геометрических объектов. Очистка буферов производится с помощью команды: void g l C l e ar C ol or ( clampf r , clampf g , clampf b , clampf a ) void g l C l e a r ( b i t f i e l d buf ) Команда glClearColor устанавливает цвет, которым будет за- полнен буфер кадра. Первые три параметра команды задают R,G и B компоненты цвета и должны принадлежать отрезку [0, 1] . Четвертый параметр задает так называемую альфа ком- поненту (см. п. 7.1). Как правило, он равен 1. По умолчанию цвет черный (0,0,0,1). Команда glClear очищает буферы, а параметр buf определя- ет комбинацию констант, соответствующую буферам, которые нужно очистить (см. главу 7). Типичная программа вызывает команду g l C l e a r (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) для очистки буферов цвета и глубины. Установка положения наблюдателя и преобразования трех- мерных объектов (поворот, сдвиг и т.д.) контролируются с помо- щью задания матриц преобразования. Преобразования объектов и настройка положения виртуальной камеры описаны в главе 4. 3.2. Вершины и примитивы 41 Сейчас сосредоточимся на том, как передать в OpenGL опи- сания объектов, находящихся в сцене. Каждый объект является набором примитивов OpenGL. 3.2. Вершины и примитивы В OpenGL вершина (vertex) является атомарным графиче- ским примитивом и определяет точку, конец отрезка, угол мно- гоугольника и т.д. Все остальные примитивы формируются с помощью задания вершин, входящих в данный примитив. На- пример, отрезок определяется двумя вершинами, являющимися концами отрезка. С каждой вершиной ассоциируются ее атрибуты. В число основных атрибутов входят положение вершины в пространстве, цвет вершины и вектор нормали. 3.2.1. Положение вершины в пространстве Положение вершины определяются заданием ее координат в двух-, трех-, или четырехмерном пространстве (однородные ко- ординаты). Это реализуется с помощью нескольких вариантов команды glVertex : 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. Команда glVertex2* получает значения x и y. Координата z в таком случае устанавливается по умолчанию равной 0, ко- ордината w равной 1. glVertex3* получает координаты x, y, z и заносит в координату w значение 1. glVertex4* позволяет задать все четыре координаты. Для ассоциации с вершинами цветов, нормалей и текстур- ных координат используются текущие значения соответствую- щих данных, что отвечает организации OpenGL как конечного 42 Глава 3. Рисование геометрических объектов автомата. Эти значения могут быть изменены в любой момент с помощью вызова соответствующих команд. 3.2.2. Цвет вершины Для задания текущего цвета вершины используются коман- ды void glColor [ 3 4 ] [ b s i f ] ( GLtype components ) void glColor [ 3 4 ] [ b s i f ] v ( GLtype components ) Первые три параметра задают R, G, B компоненты цвета, а последний параметр определяет коэффициент непрозрачности (так называемая альфа-компонента). Если в названии команды указан тип ѕfї (oat), то значения всех параметров должны при- надлежать отрезку [0,1], при этом по умолчанию значение аль- фа-компоненты устанавливается равным 1.0, что соответствует полной непрозрачности. Тип ѕubї (unsigned byte) подразумевает, что значения должны лежать в отрезке [0,255]. Вершинам можно назначать различные цвета, и, если вклю- чен соответствующий режим, то будет проводиться линейная ин- терполяция цветов по поверхности примитива. Для управления режимом интерполяции используется ко- манда void glShadeModel (GLenum mode) вызов которой с параметром GL_SMOOTH включает интерполя- цию (установка по умолчанию), а с GL_FLAT отключает. 3.2.3. Нормаль Определить нормаль в вершине можно, используя команды void glNormal3 [ b s i f d ] ( type coords ) void glNormal3 [ b s i f d ] v ( type coords ) 3.3. Операторные скобки GLBEGIN / GLEND 43 Для правильного расчета освещения необходимо, чтобы век- тор нормали имел единичную длину. В OpenGL существует специальный режим, при котором задаваемые нормали будут нормироваться автоматически. Его можно включить командой glEnable(GL_NORMALIZE) Режим автоматической нормализации должен быть включен, если приложение использует модельные преобразования растя- жения/сжатия, так как в этом случае длина нормалей изменя- ется при умножении на модельно-видовую матрицу. Однако применение этого режима уменьшает скорость ра- боты механизма визуализации OpenGL, так как нормализация векторов имеет заметную вычислительную сложность (взятие квадратного корня). Поэтому лучше сразу задавать единичные нормали. Отметим, что команды void glEnable (GLenum mode) void g l D i s a b l e (GLenum mode) производят включение и отключение того или иного режима ра- боты конвейера OpenGL. Эти команды применяются достаточно часто, и их возможные параметры будут рассматриваться в каж- дом конкретном случае. 3.3. Операторные скобки glBegin / glEnd Мы рассмотрели задание атрибутов одной вершины. Однако чтобы задать атрибуты графического примитива, одних коорди- нат вершин недостаточно. Эти вершины надо объединить в одно целое, определив необходимые свойства. Для этого в OpenGL используются так называемые операторные скобки, являющи- еся вызовами специальных команд OpenGL. Определение при- митива или последовательности примитивов происходит между вызовами команд 44 Глава 3. Рисование геометрических объектов 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 последовательно задаются вершины вы- пуклого многоугольника. 3.3. Операторные скобки GLBEGIN / GLEND 45 Например, чтобы нарисовать треугольник с разными цветами в вершинах, достаточно написать: GLfloat BlueCol [ 3 ] = { 0 , 0 , 1 } ; glBegin (GL_TRIANGLES) ; g l C o l o r 3 f ( 1 . 0 , 0 . 0 , 0 . 0 ) ; /* красный */ g l V e r t e x 3 f ( 0 . 0 , 0 . 0 , 0 . 0 ) ; glColor3ub ( 0 , 2 5 5 , 0 ) ; /* зеленый */ g l V e r t e x 3 f ( 1 . 0 , 0 . 0 , 0 . 0 ) ; g l C o l o r 3 f v ( BlueCol ) ; /* синий */ g l V e r t e x 3 f ( 1 . 0 , 1 . 0 , 0 . 0 ) ; glEnd ( ) ; Как правило, разные типы примитивов имеют различную скорость визуализации на разных платформах. Для увеличения производительности предпочтительнее использовать примити- вы, требующие меньшее количество информации для передачи на сервер, такие как GL_TRIANGLE_STRIP , GL_QUAD_STRIP , GL_TRIAGLE_FAN Кроме задания самих многоугольников, можно определить метод их отображения на экране. Однако сначала надо опреде- лить понятие лицевых и обратных граней. Под гранью понимается одна из сторон многоугольника, и по умолчанию лицевой считается та сторона, вершины которой обходятся против часовой стрелки. Направление обхода вершин лицевых граней можно изменить вызовом команды void glFrontFace (GLenum mode) со значением параметра mode равным GL_CW (clockwise), а вер- нуть значение по умолчанию можно, указав GL_CCW (counter- clockwise). Чтобы изменить метод отображения многоугольника исполь- зуется команда void glPolygonMode (GLenum face , Glenum mode) 46 Глава 3. Рисование геометрических объектов Параметр mode определяет как будут отображаться много- угольники, а параметр face устанавливает тип многоугольников, к которым будет применяться эта команда и может принимать следующие значения: GL_FRONT для лицевых граней; GL_BACK для обратных граней; GL_FRONT_AND_BACK для всех граней. Параметр mode может быть равен: GL_POINT отображение только вершин многоугольников; GL_LINE многоугольники будут представляться набором отрезков; GL_FILL многоугольники будут закрашиваться текущим цветом с учетом освещения, и этот режим установлен по умолчанию. Также можно указывать какой тип граней отображать на экране. Для этого сначала надо установить соответствующий ре- жим вызовом команды glEnable (GL_CULL_FACE) , а затем вы- брать тип отображаемых граней с помощью команды void glCullFace (GLenum mode) Вызов с параметром GL_FRONT приводит к удалению из изображения всех лицевых граней, а с параметром GL_BACK обратных (установка по умолчанию). Кроме рассмотренных стандартных примитивов в библиоте- ках GLU и GLUT описаны более сложные фигуры, такие как сфера, цилиндр, диск (в GLU) и сфера, куб, конус, тор, тетра- эдр, додекаэдр, икосаэдр, октаэдр и чайник (в GLUT). Автома- тическое наложение текстуры предусмотрено только для фигур из библиотеки GLU (создание текстур в OpenGL будет рассмат- риваться в главе 6). 3.4. Дисплейные списки 47 Например, чтобы нарисовать сферу или цилиндр, надо снача- ла создать объект специального типа GLUquadricObj с помощью команды GLUquadricObj* gluNewQuadric ( void ) ; а затем вызвать соответствующую команду: void gluSphere ( GLUquadricObj * qobj , GLdouble radius , GLint s l i c e s , GLint s t a c k s ) void gluCylinder ( GLUquadricObj * qobj , GLdouble baseRadius , GLdouble topRadius , GLdouble height , GLint s l i c e s , GLint s t a c k s ) где параметр slices задает количество разбиений вокруг оси z, а stacks вдоль оси z. Более подробную информацию об этих и других командах построения примитивов можно найти в приложении А. 3.4. Дисплейные списки Если мы несколько раз обращаемся к одной и той же группе команд, то их можно объединить в так называемый дисплей- ный список (display list) и вызывать его при необходимости. Для того, чтобы создать новый дисплейный список, надо поместить все команды, которые должны в него войти, между следующими операторными скобками: void glNewList ( GLuint l i s t , GLenum mode) void glEndList ( ) 48 Глава 3. Рисование геометрических объектов Для различения списков используются целые положитель- ные числа, задаваемые при создании списка значением парамет- ра list . Параметр mode определяет режим обработки команд, входящих в список: GL_COMPILE команды записываются в список без выпол- нения; GL_COMPILE_AND_EXECUTE команды выполняют- ся, а затем записываются в список. После того, как список создан, его можно вызвать командой void g l C a l l L i s t ( GLuint l i s t ) указав в параметре list идентификатор нужного списка. Чтобы вызвать сразу несколько списков, можно воспользо- ваться командой void g l C a l l L i s t s ( GLsizei n , GLenum type , const GLvoid * l i s t s ) вызывающей n списков с идентификаторами из массива lists, тип элементов которого указывается в параметре type. Это мо- гут быть типы GL_BYTE , GL_UNSIGNED_BYTE , GL_SHORT , GL_INT , GL_UNSIGNED_INT и некоторые другие. Для удале- ния списков используется команда void g l D e l e t e L i s t s ( GLint l i s t , GLsizei range ) которая удаляет списки с идентификаторами ID из диапазона list ? ID ? list + range ? 1 Пример: glNewList (1 , GL_COMPILE) ; glBegin (GL_TRIANGLES) ; g l V e r t e x 3 f ( 1 . 0 f , 1. 0 f , 1 . 0 f ) ; g l V e r t e x 3 f ( 1 0 . 0 f , 1. 0 f , 1 . 0 f ) ; g l V e r t e x 3 f ( 1 0 . 0 f , 10.0 f , 1 . 0 f ) ; 3.5. Массивы вершин 49 glEnd ( ) ; glEndList ( ) g l C a l l L i s t ( 1 ) ; Дисплейные списки в оптимальном (скомпилированном) ви- де хранятся в памяти сервера, что позволяет рисовать прими- тивы в такой форме максимально быстро. В то же время боль- шие объемы данных занимают много памяти, что влечет, в свою очередь, падение производительности. Такие большие объемы (больше нескольких десятков тысяч примитивов) лучше рисо- вать с помощью массивов вершин. 3.5. Массивы вершин Если вершин много, то, чтобы не вызывать для каждой ко- манду glVertex , удобно объединять вершины в массивы, исполь- зуя команду void glVertexPointer ( GLint s i z e , GLenum type , GLsizei s t r i d e , void* ptr ) которая определяет способ хранения и координаты вершин. При этом size определяет число координат вершины (может быть равен 2, 3, 4), type определяет тип данных (может быть равен GL_SHORT , GL_INT , GL_FLOAT , GL_DOUBLE ). Иногда удобно хранить в одном массиве другие атрибуты вершины, тогда па- раметр stride задает смещение от координат одной вершины до координат следующей; если stride равен нулю, это значит, что координаты расположены последовательно. В параметре ptr ука- зывается адрес, где находятся данные. Аналогично можно определить массив нормалей, цветов и некоторых других атрибутов вершины, используя команды void glNormalPointer ( GLenum type , GLsizei s t r i d e , 50 Глава 3. Рисование геометрических объектов void * p o i n t e r ) void g l C o l o r P o i n t e r ( GLint s i z e , GLenum type , GLsizei s t r i d e , void * p o i n t e r ) Для того, чтобы эти массивы можно было использовать в дальнейшем, надо вызвать команду void g l E n a b l e C l i e n t S t a t e (GLenum array ) с параметрами GL_VERTEX_ARRAY , GL_NORMAL_ARRAY , GL_COLOR_ARRAY соответственно. После окончания работы с массивом желательно вызвать ко- манду void g l D i s a b l e C l i e n t S t a t e (GLenum array ) с соответствующим значением параметра array Для отображения содержимого массивов используется ко- манда void glArrayElement ( GLint index ) которая передает OpenGL атрибуты вершины, используя эле- менты массива с номером index. Это аналогично последователь- ному применению команд вида glColor , glNormal , glVertex c соот- ветствующими параметрами. Однако вместо нее обычно вызы- вается команда void glDrawArrays (GLenum mode , GLint f i r s t , GLsizei count ) рисующая count примитивов, определяемых параметром mode , используя элементы из массивов с индексами от first до first + count ? 1 . Это эквивалентно вызову последовательности команд glArrayElement() с соответствующими индексами. В случае, если одна вершина входит в несколько примитивов, вместо дублирования ее координат в массиве удобно использо- вать ее индекс. Для этого надо вызвать команду 3.6. Контрольные вопросы 51 void glDrawElements (GLenum mode , GLsizei count , GLenum type , void* i n d i c e s ) где indices это массив номеров вершин, которые на- до использовать для построения примитивов, type опреде- ляет тип элементов этого массива: GL_UNSIGNED_BYTE , GL_UNSIGNED_SHORT , GL_UNSIGNED_INT , а count задает их количество. Важно отметить, что использование массивов вершин поз- воляет оптимизировать передачу данных на сервер OpenGL, и, как следствие, повысить скорость рисования трехмерной сцены. Такой метод определения примитивов является одним из самых быстрых и хорошо подходит для визуализации больших объемов данных. 3.6. Контрольные вопросы 1) Что такое функция обратного вызова и как функции об- ратного вызова могут быть использованы для работы с |