Планета знаний
Скачать 1.68 Mb.
|
1) Какие буферы изображений используются в OpenGL и для чего? 2) Для чего используется команда glBlendFunc ? 3) Почему для корректного вывода прозрачных объектов тре- буется соблюдение условий упорядоченного вывода прими- тивов с прозрачностью? 4) Для чего используется буфер-накопитель? Приведите при- мер работы с ним. 5) Как в OpenGL можно наложить маску на результирующее изображение? 6) Объясните, для чего применятся команда glHint() 7) Каков эффект выполнения команды glHint(GL_FOG_HINT, GL_DONT_CARE) ? Часть II Приемы работы с OpenGL 99 Глава 8. Графические алгоритмы на основе OpenGL В этой главе мы рассмотрим как с помощью OpenGL со- здавать некоторые интересные визуальные эффекты, непосред- ственная поддержка которых отсутствует в стандарте библиоте- ки. 8.1. Устранение ступенчатости Начнем с задачи устранения ступенчатости (antialiasing). Эф- фект ступенчатости (aliasing) возникает в результате погрешно- стей растеризации примитивов в буфере кадра из-за конечного (и как правило, небольшого) разрешения буфера. Есть несколько подходов к решению данной проблемы. Например, можно при- менять фильтрацию полученного изображения. Также этот эф- фект можно устранять на этапе растеризации, сглаживая образ каждого примитива. Здесь мы рассмотрим прием, позволяющий устранять подобные артефакты для всей сцены целиком. Для каждого кадра необходимо нарисовать сцену несколько 101 102 Глава 8. Графические алгоритмы на основе OPENGL раз, на каждом проходе немного смещая камеру относительно начального положения. Положения камер, например, могут об- разовывать окружность. Если сдвиг камеры относительно мал, то погрешности дискретизации проявятся по-разному, и, усред- няя полученные изображения, мы получим сглаженное изобра- жение. Проще всего сдвигать положение наблюдателя, но перед этим нужно вычислить размер сдвига так, чтобы приведенное к коор- динатам экрана значение не превышало, скажем, половины раз- мера пикселя. Все полученные изображения сохраняем в буфере-накопите- ле с коэффициентом 1/n, где n число проходов для каждого кадра. Чем больше таких проходов тем ниже производитель- ность, но лучше результат. for ( i =0; i { ShiftCamera ( i ) ; // сдвигаем камеру RenderScene ( ) ; i f ( i ==0) // на первой итерации загружаем изображение glAccum (GL_LOAD, 1 / ( float ) samples_count ) ; else // добавляем к уже существующему glAccum (GL_ACCUM, 1 / ( float ) samples_count ) ; } // Пишем то, что получилось , назад в исходный буфер glAccum (GL_RETURN, 1 . 0 ) ; Следует отметить, что устранение ступенчатости сразу для всей сцены, как правило, связано с серьезным падением произво- дительности визуализации, так как вся сцена рисуется несколько раз. Современные ускорители обычно аппаратно реализуют дру- гие методы. 8.2. Построение теней 103 8.2. Построение теней В OpenGL нет встроенной поддержки построения теней на уровне базовых команд. В значительной степени это объясня- ется тем, что существует множество алгоритмов их построения, которые могут быть реализованы через функции OpenGL. При- сутствие теней сильно влияет на реалистичность трехмерного изображения, поэтому рассмотрим один из подходов к их по- строению. Большинство алгоритмов, предназначенных для построения теней, используют модифицированные принципы перспективной проекции. Здесь рассматривается один из самых простых мето- дов. С его помощью можно получать тени, отбрасываемые трех- мерным объектом на плоскость. Общий подход таков: для всех точек объекта находится их проекция параллельно вектору, соединяющему данную точку и точку, в которой находится источник света, на некую заданную плоскость. Тем самым получаем новый объект, целиком лежа- щий в заданной плоскости. Этот объект и является тенью исход- ного. Рассмотрим математические основы данного метода. Пусть: P точка в трехмерном пространстве, которая отбрасывает тень. L положение источника света, который освещает данную точ- ку. S = a(L ? P ) ? P точка, в которую отбрасывает тень точка P , где a параметр. Предположим, что тень падает на плоскость z = 0. В этом случае a = z p /(z l ? z p ) . Следовательно, 104 Глава 8. Графические алгоритмы на основе OPENGL x s = (x p z l ? z l z p )/(z l ? z p ) y s = (y p z l ? y l z p )/(z l ? z p ) z s = 0 Введем однородные координаты: x s = x 0 s /w 0 s y s = y 0 s /w 0 s z s = 0 w 0 s = z l ? z p Отсюда координаты S могут быть получены с использовани- ем умножения матриц следующим образом: x 0 s y 0 s 0 w 0 s = x s y s z s 1 ? ? ? ? z l 0 0 0 0 z l 0 0 ?x l ?y l 0 1 0 0 0 z l ? ? ? ? Для того, чтобы алгоритм мог рассчитывать тень, падающую на произвольную плоскость, рассмотрим произвольную точку на линии между S и P , представленную в однородных координатах: aP + bL , где a и b скалярные параметры. Следующая матрица задает плоскость через координаты ее нормали: G = ? ? ? ? x n y n z n d ? ? ? ? Точка, в которой луч, проведенный от источника света через данную точку P , пересекает плоскость G, определяется парамет- рами a и b, удовлетворяющими следующему уравнению: 8.2. Построение теней 105 (aP + bL)G = 0 Отсюда получаем: a(P G)+b(LG) = 0. Этому уравнению удо- влетворяют a = (LG), b = ?(P G) Следовательно, координаты искомой точки S = (LG)P ? (P G)L . Пользуясь ассоциативностью матричного произведения, получим S = P [(LG)I ? GL] (8.1) где I единичная матрица. Матрица (LG)I ? GL используется для получения теней на произвольной плоскости. Рассмотрим некоторые аспекты практической реализации данного метода с помощью OpenGL. Предположим, что матрица oorShadow была ранее получена нами из формулы (LG)I ? GL. Следующий код с ее помощью строит тени для заданной плоскости: /* Визуализируем сцену в обычном режиме */ RenderGeometry ( ) ; /* Делаем тени полупрозрачными с использованием смешивания цветов ( b l e n d i n g ) */ glEnable (GL_BLEND) ; glBlendFunc (GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA) ; g l D i s a b l e (GL_LIGHTING) ; g l C o l o r 4 f ( 0 . 0 , 0 . 0 , 0 . 0 , 0 . 5 ) ; glPushMatrix ( ) ; /* Проецируем тень */ glMultMatrixf ( ( GLfloat *) floorShadow ) ; /* Визуализируем сцену в проекции */ 106 Глава 8. Графические алгоритмы на основе OPENGL RenderGeometry ( ) ; glPopMatrix ( ) ; glEnable (GL_LIGHTING) ; g l D i s a b l e (GL_BLEND) ; Матрица oorShadow может быть получена из уравнения 8.1 с помощью следующей функции: /* параметры : plane ? коэффициенты уравнения плоскости l i g h t p o s ? координаты источника света возвращает : matrix ? результирующая матрица */ void shadowmatrix ( GLfloat matrix [ 4 ] [ 4 ] , GLfloat plane [ 4 ] , GLfloat l i g h t p o s [ 4 ] ) { GLfloat dot ; dot = plane [ 0 ] * l i g h t p o s [ 0 ] + plane [ 1 ] * l i g h t p o s [ 1 ] + plane [ 2 ] * l i g h t p o s [ 2 ] + plane [ 3 ] * l i g h t p o s [ 3 ] ; matrix [ 0 ] [ 0 ] = dot ? l i g h t p o s [ 0 ] * plane [ 0 ] ; matrix [ 1 ] [ 0 ] = 0 . f ? l i g h t p o s [ 0 ] * plane [ 1 ] ; matrix [ 2 ] [ 0 ] = 0 . f ? l i g h t p o s [ 0 ] * plane [ 2 ] ; matrix [ 3 ] [ 0 ] = 0 . f ? l i g h t p o s [ 0 ] * plane [ 3 ] ; matrix [ 0 ] [ 1 ] = 0 . f ? l i g h t p o s [ 1 ] * plane [ 0 ] ; matrix [ 1 ] [ 1 ] = dot ? l i g h t p o s [ 1 ] * plane [ 1 ] ; matrix [ 2 ] [ 1 ] = 0 . f ? l i g h t p o s [ 1 ] * plane [ 2 ] ; matrix [ 3 ] [ 1 ] = 0 . f ? l i g h t p o s [ 1 ] * plane [ 3 ] ; matrix [ 0 ] [ 2 ] = 0 . f ? l i g h t p o s [ 2 ] * plane [ 0 ] ; matrix [ 1 ] [ 2 ] = 0 . f ? l i g h t p o s [ 2 ] * plane [ 1 ] ; matrix [ 2 ] [ 2 ] = dot ? l i g h t p o s [ 2 ] * plane [ 2 ] ; matrix [ 3 ] [ 2 ] = 0 . f ? l i g h t p o s [ 2 ] * plane [ 3 ] ; 8.2. Построение теней 107 matrix [ 0 ] [ 3 ] = 0 . f ? l i g h t p o s [ 3 ] * plane [ 0 ] ; matrix [ 1 ] [ 3 ] = 0 . f ? l i g h t p o s [ 3 ] * plane [ 1 ] ; matrix [ 2 ] [ 3 ] = 0 . f ? l i g h t p o s [ 3 ] * plane [ 2 ] ; matrix [ 3 ] [ 3 ] = dot ? l i g h t p o s [ 3 ] * plane [ 3 ] ; } Заметим, что тени, построенные таким образом, имеют ряд недостатков: € Описанный алгоритм предполагает, что плоскости беско- нечны, и не отрезает тени по границе. Например, если неко- торый объект отбрасывает тень на стол, она не будет отсе- каться по границе, и, тем более, ѕзаворачиватьсяї на боко- вую поверхность стола. € В некоторых местах теней может наблюдаться эффект ѕдвойного смешиванияї (reblending), т.е. темные пятна в тех участках, где спроецированные треугольники перекры- вают друг друга. € С увеличением числа поверхностей сложность алгоритма резко увеличивается, т.к. для каждой поверхности нужно заново строить всю сцену, даже если проблема отсечения теней по границе будет решена. € Тени обычно имеют размытые границы, а в приведенном алгоритме они всегда имеют резкие края. Частично избе- жать этого позволяет расчет теней из нескольких источни- ков света, расположенных рядом и последующее смешива- ние результатов. Имеется решение первой и второй проблемы. Для этого ис- пользуется буфер маски (см. п. 7.2). Итак, задача отсечь вывод геометрии (тени) по границе некоторой произвольной области и избежать ѕдвойного смеши- 108 Глава 8. Графические алгоритмы на основе OPENGL ванияї. Общий алгоритм решения с использованием буфера мас- ки таков: 1) очищаем буфер маски значением 0; 2) отображаем заданную область отсечения, устанавливая значения в буфере маски в 1; 3) рисуем тени в тех областях, где в буфере маски установ- лены значения; если тест проходит, устанавливаем в эти области значение 2. Теперь рассмотрим эти этапы более подробно. 1. /* очищаем буфер маски*/ g l C l e a r S t e n c i l (0 x0 ) g l C l e a r (GL_STENCIL_BUFFER_BIT) ; /* включаем тест */ glEnable (GL_STENCIL_TEST) ; 2. /* условие всегда выполнено и значение в буфере будет равно 1*/ g l S t e n c i l F u n c (GL_ALWAYS, 0x1 , 0 x f f f f f f f f ) ; /* в любом случае заменяем значение в буфере маски*/ glStencilOp (GL_REPLACE, GL_REPLACE, GL_REPLACE) ; /* выводим геометрию , по которой затем будет отсечена тень*/ RenderPlane ( ) ; 3. /* условие выполнено и тест дает истину только если значение в буфере маски равно 1 */ 8.3. Зеркальные отражения 109 g l S t e n c i l F u n c (GL_EQUAL, 0x1 , 0 x f f f f f f f f ) ; /* значение в буфере равно 2 , если тень уже выведена */ glStencilOp (GL_KEEP, GL_KEEP, GL_INCR) ; /* выводим тени */ RenderShadow ( ) ; Строго говоря, даже при применении маскирования остают- ся некоторые проблемы, связанные с работой z-буфера. В част- ности, некоторые участки теней могут стать невидимыми. Для решения этой проблемы можно немного приподнять тени над плоскостью c помощью модификации уравнения, описывающего плоскость. Описание других методов выходит за рамки данного пособия. 8.3. Зеркальные отражения В этом разделе мы рассмотрим алгоритм построения отра- жений от плоских объектов. Такие отражения придают боль- шую достоверность построенному изображению и их относитель- но легко реализовать. Алгоритм использует интуитивное представление полной сце- ны с зеркалом как составленной из двух: ѕнастоящейї и ѕвир- туальнойї находящейся за зеркалом. Следовательно, процесс рисования отражений состоит из двух частей: 1) визуализации обычной сцены и 2) построения и визуализации виртуальной. Для каждого объекта ѕнастоящейї сцены строится его отражен- ный двойник, который наблюдатель и увидит в зеркале. Для иллюстрации рассмотрим комнату с зеркалом на стене. Комната и объекты, находящиеся в ней, выглядят в зеркале так, как если бы зеркало было окном, а за ним была бы еще одна та- кая же комната с тем же объектами, но симметрично отражен- ными относительно плоскости, проведенной через поверхность 110 Глава 8. Графические алгоритмы на основе OPENGL Рис. 8.1. Зеркальное отражение зеркала. Упрощенный вариант алгоритма создания плоского отраже- ния состоит из следующих шагов: 1) Рисуем сцену как обычно, но без объектов-зеркал. 2) Используя буфер маски, ограничиваем дальнейший вывод проекцией зеркала на экран. 3) Визуализируем сцену, отраженную относительно плоскости зеркала. При этом буфер маски позволит ограничить вывод формой проекции объекта-зеркала. Эта последовательность действий позволит получить убеди- тельный эффект отражения. Рассмотрим этапы более подробно. Сначала необходимо нарисовать сцену как обычно. Не бу- дем останавливаться на этом этапе подробно. Заметим только, что, очищая буферы OpenGL непосредственно перед рисовани- ем, нужно не забыть очистить буфер маски: 8.3. Зеркальные отражения 111 g l C l e a r (GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT| GL_STENCIL_BUFFER_BIT) ; Во время визуализации сцены лучше не рисовать объекты, которые затем станут зеркальными. На втором этапе необходимо ограничить дальнейший вывод проекцией зеркального объекта на экран. Для этого настраиваем буфер маски и рисуем зеркало glEnable (GL_STENCIL_TEST) ; /* условие всегда выполнено и значение в буфере будет равно 1*/ g l S t e n c i l F u n c (GL_ALWAYS, 1 , 0 ) ; glStencilOp (GL_KEEP, GL_KEEP, GL_REPLACE) ; RenderMirrorObject ( ) ; В результате мы получили: € в буфере кадра корректно нарисованная сцена, за исклю- чением области зеркала; € в области зеркала (там, где мы хотим видеть отражение) значение буфера маски равно 1. На третьем этапе нужно нарисовать сцену, отраженную от- носительно плоскости зеркального объекта. Сначала настраиваем матрицу отражения. Матрица отраже- ния должна зеркально отражать всю геометрию относительно плоскости, в которой лежит объект-зеркало. Ее можно получить, например, с помощью такой функции (попробуйте получить эту матрицу самостоятельно в качестве упражнения): void r e f l e c t i o n m a t r i x ( GLfloat r e f l e c t i o n _ m a t r i x [ 4 ] [ 4 ] , GLfloat plane_point [ 3 ] , G l f l o a t plane_normal [ 3 ] ) { GLfloat * p ; 112 Глава 8. Графические алгоритмы на основе OPENGL GLfloat * v ; float pv ; GLfloat * p = ( G l f l o a t *) plane_point ; G l f l o a t * v = ( G l f l o a t *) plane_normal ; float pv = p [ 0 ] * v [0]+ p [ 1 ] * v [1]+ p [ 2 ] * v [ 2 ] ; r e f l e c t i o n _ m a t r i x [ 0 ] [ 0 ] = 1 ? 2 * v [ 0 ] * v [ 0 ] ; r e f l e c t i o n _ m a t r i x [ 1 ] [ 0 ] = ? 2 * v [ 0 ] * v [ 1 ] ; r e f l e c t i o n _ m a t r i x [ 2 ] [ 0 ] = ? 2 * v [ 0 ] * v [ 2 ] ; r e f l e c t i o n _ m a t r i x [ 3 ] [ 0 ] = 2 * pv * v [ 0 ] ; r e f l e c t i o n _ m a t r i x [ 0 ] [ 1 ] = ? 2 * v [ 0 ] * v [ 1 ] ; r e f l e c t i o n _ m a t r i x [ 1 ] [ 1 ] = 1? 2 * v [ 1 ] * v [ 1 ] ; r e f l e c t i o n _ m a t r i x [ 2 ] [ 1 ] = ? 2 * v [ 1 ] * v [ 2 ] ; r e f l e c t i o n _ m a t r i x [ 3 ] [ 1 ] = 2 * pv * v [ 1 ] ; r e f l e c t i o n _ m a t r i x [ 0 ] [ 2 ] = ? 2 * v [ 0 ] * v [ 2 ] ; r e f l e c t i o n _ m a t r i x [ 1 ] [ 2 ] = ? 2 * v [ 1 ] * v [ 2 ] ; r e f l e c t i o n _ m a t r i x [ 2 ] [ 2 ] = 1 ? 2 * v [ 2 ] * v [ 2 ] ; r e f l e c t i o n _ m a t r i x [ 3 ] [ 2 ] = 2 * pv * v [ 2 ] ; r e f l e c t i o n _ m a t r i x [ 0 ] [ 3 ] = 0 ; r e f l e c t i o n _ m a t r i x [ 1 ] [ 3 ] = 0 ; r e f l e c t i o n _ m a t r i x [ 2 ] [ 3 ] = 0 ; r e f l e c t i o n _ m a t r i x [ 3 ] [ 3 ] = 1 ; } Настраиваем буфер маски на рисование только в областях, где значение буфера равно 1: /* условие выполнено и тест дает истину только если значение в буфере маски равно 1 */ g l S t e n c i l F u n c (GL_EQUAL, 0x1 , 0 x f f f f f f f f ) ; /* ничего не меняем в буфере */ glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP) ; и рисуем сцену еще раз (без зеркальных объектов) 8.4. Контрольные вопросы 113 glPushMatrix ( ) ; glMultMatrixf ( ( float *) r e f l e c t i o n _ m a t r i x ) ; RenderScene ( ) ; glPopMatrix ( ) ; Наконец, отключаем маскирование: g l D i s a b l e (GL_STENCIL_TEST) ; После этого можно опционально еще раз вывести зеркальный объект, например, с альфа-смешением для создания эффекта замутнения зеркала и т.д. Обратите внимание, что описанный метод корректно рабо- тает, только если за зеркальным объектом нет других объектов сцены. Поэтому существует несколько модификаций этого ал- горитма, отличающихся последовательностью действий и имею- щих разные ограничения на геометрию. 8.4. Контрольные вопросы € В результате чего возникает эффект ступенчатости изоб- ражения? Опишите алгоритм устранения ступенчатости. € Почему в OpenGL нет встроенной поддержки построения теней? € Кратко опишите предложенный метод визуализации зер- кальных объектов. Почему он не работает, если за зеркалом находятся другие объекты сцены? Что будет отражаться в этом случае? Подумайте, как обойти это ограничение? Глава 9. Оптимизация программ 9.1. Организация приложения На первый взгляд может показаться, что производительность графических приложений, основанных на OpenGL, определяется в первую очередь производительностью реализации самой биб- лиотеки. Это верно, однако организация всего приложения так- же очень важна. 9.1.1. Высокоуровневая оптимизация Обычно от программы под OpenGL требуется визуализация высокого качества на интерактивных скоростях. Но как правило, и то и другое сразу получить не удается. Следовательно, необ- ходим поиск компромисса между качеством и производительно- стью. Существует множество различных подходов, но их подроб- ное описание выходит за пределы этого пособия. Приведем лишь несколько примеров. € Можно отображать геометрию сцены с низким качеством во время анимации, а в моменты остановок показывать ее с 115 116 Глава 9. Оптимизация программ наилучшим качеством. Во время интерактивного вращения (например, при нажатой клавише мыши) визуализировать модель с уменьшенным количеством примитивов. При ри- совании статичного изображения отображать модель пол- ностью. € Аналогично, объекты, которые располагаются далеко от наблюдателя, могут быть представлены моделями пони- женной сложности. Это значительно снизит нагрузку на все ступени конвейера OpenGL. Объекты, которые нахо- дятся полностью вне поля видимости, могут быть эффек- тивно отсечены без передачи на конвейер OpenGL с по- мощью проверки попадания ограничивающих их простых объемов (сфер или кубов) в пирамиду зрения. 9.1.2. Низкоуровневая оптимизация Объекты, отображаемые с помощью OpenGL, хранятся в некоторых структурах данных. Одни типы таких структур бо- лее эффективны в использовании, чем другие, что определяет скорость визуализации. Желательно, чтобы использовались структуры данных, ко- торые могут быть быстро и эффективно переданы на конвейер OpenGL. Например, если мы хотим отобразить массив треуголь- ников, то использование указателя на этот массив значительно более эффективно, чем передача его OpenGL поэлементно. Пример. Предположим, что мы пишем приложение, которое реализует рисование карты местности. Один из компонентов ба- зы данных список городов с их шириной, долготой и названи- ем. Соответствующая структура данных может быть такой: struct c i t y { float l a t i t u t e , l o n g i t u d e ; /* положение города */ char *name ; /* название */ 9.1. Организация приложения 117 int l a r g e _ f l a g ; /* 0 = маленький , 1 = большой */ } ; Список городов может храниться как массив таких структур. Допустим, мы пишем функцию, которая рисует города на карте в виде точек разного размера с подписями: void draw_cities ( int n , struct c i t y c i t y l i s t [ ] ) { int i ; for ( i =0; i < n ; i++) { i f ( c i t y l i s t [ i ] . l a r g e _ f l a g ) g l P o i n t S i z e ( 4. 0 ) ; else g l P o i n t S i z e ( 2. 0 ) ; glBegin ( GL_POINTS ) ; g l V e r t e x 2 f ( c i t y l i s t [ i ] . longitude , c i t y l i s t [ i ] . l a t i t u d e ) ; glEnd ( ) ; /* рисуем название города */ DrawText ( c i t y l i s t [ i ] . longitude , c i t y l i s t [ i ] . l a t i t u d e , c i t y l i s t [ i ] . name ) ; } } Эта реализация неудачна по следующим причинам: € glPointSize вызывается для каждой итерации цикла; € между glBegin и glEnd рисуется только одна точка; € вершины определяются в неоптимальном формате. Ниже приведено более рациональное решение: void draw_cities ( int n , struct c i t y c i t y l i s t [ ] ) { 118 Глава 9. Оптимизация программ int i ; /* сначала рисуем маленькие точки */ g l P o i n t S i z e ( 2. 0 ) ; glBegin ( GL_POINTS ) ; for ( i =0; i < n ; i++) { i f ( c i t y l i s t [ i ] . l a r g e _ f l a g==0) { g l V e r t e x 2 f ( c i t y l i s t [ i ] . longitude , c i t y l i s t [ i ] . l a t i t u d e ) ; } } glEnd ( ) ; /* большие точки рисуем во вторую очередь */ g l P o i n t S i z e ( 4. 0 ) ; glBegin ( GL_POINTS ) ; for ( i =0; i < n ; i++) { i f ( c i t y l i s t [ i ] . l a r g e _ f l a g==1) { g l V e r t e x 2 f ( c i t y l i s t [ i ] . longitude , c i t y l i s t [ i ] . l a t i t u d e ) ; } } glEnd ( ) ; /* затем рисуем названия городов */ for ( i =0; i < n ; i++) { DrawText ( c i t y l i s t [ i ] . longitude , c i t y l i s t [ i ] . l a t i t u d e , c i t y l i s t [ i ] . name ) ; } } В такой реализации мы вызываем glPointSize дважды и уве- личиваем число вершин между glBegin и glEnd Однако остаются еще пути для оптимизации. Если мы поме- 9.1. Организация приложения 119 няем наши структуры данных, то можем еще повысить эффек- тивность рисования точек. Например: struct c i t y _ l i s t { int num_cities ; /* число городов в списке */ float * p o s i t i o n ; /* координаты города */ char **name ; /* указатель на названия городов */ float s i z e ; /* размер точки , обозначающей город */ } ; Теперь города разных размеров хранятся в разных списках. Положения точек хранятся отдельно в динамическом массиве. После реорганизации мы исключаем необходимость в условном операторе внутри glBegin / glEnd и получаем возможность исполь- зовать массивы вершин для оптимизации. В результате наша функция выглядит следующим образом: void draw_cities ( struct c i t y _ l i s t * l i s t ) { int i ; /* рисуем точки */ g l P o i n t S i z e ( l i s t ?>s i z e ) ; glVertexPointer ( 2 , GL_FLOAT, 0 , l i s t ?>num_cities , l i s t ?>p o s i t i o n ) ; glDrawArrays ( GL_POINTS, 0 , l i s t ?>num_cities ) ; /* рисуем название города */ for ( i =0; i < l i s t ?>num_cities ; i++) { DrawText ( c i t y l i s t [ i ] . longitude , c i t y l i s t [ i ] . l a t i t u d e c i t y l i s t [ i ] . name ) ; } } 120 Глава 9. Оптимизация программ 9.2. Оптимизация вызовов OpenGL 9.2.1. Передача данных в OpenGL В данном разделе рассмотрим способы минимизации времени на передачу данных о примитивах в OpenGL. Используйте связанные примитивы Связанные примитивы, такие как GL_LINES , GL_LINE_LOOP , GL_TRIANGLE_STRIP , GL_TRIANGLE_FAN и GL_QUAD_STRIP требуют для определения меньше вершин, чем отдельные линия или многоугольник. Это уменьшает количество данных, передаваемых OpenGL. Используйте массивы вершин На большинстве архитектур замена множественных вызовов glVertex / glColor / glNormal на механизм массивов вершин может быть очень выигрышной. Используйте индексированные примитивы В некоторых случаях даже при использовании связанных примитивов GL_TRIANGLE_STRIP ( GL_QUAD_STRIP ) верши- ны дублируются. Чтобы не передавать в OpenGL дубли, увели- чивая нагрузку на шину, используйте команду glDrawElements() Задавайте необходимые массивы одной командой Вместо использования команд glVertexPointer , glColorPointer , glNormalPointer лучше пользоваться одной командой void g l I n t e r l e a v e d A r r a y s ( Glint format , G l s i z e i s t r i d e , void * ptr ) ; 9.2. Оптимизация вызовов OPENGL 121 Так, если имеется структура typedef struct tag_VERTEX_DATA { float c o l o r [ 4 ] ; float normal [ 3 ] ; float vertex [ 3 ] ; }VERTEX_DATA; VERTEX_DATA * pData ; то параметры можно передать с помощью следующей команды g l I n t e r l e a v e d A r r a y s (GL_C4F_N3F_V3F, 0 , pData ) ; что означает, что первые четыре oat относятся к цвету, затем три oat к нормали, и последние три oat задают координаты вершины. Более подробное описание команды смотрите в специ- фикации OpenGL. Храните данные о вершинах в памяти последовательно Последовательное расположение данных в памяти улучшает скорость обмена между основной памятью и графической под- системой. Используйте векторные версии glVertex , glColor , glNormal и glTexCoord Функции glVertex , glColor и т.д., которые в качестве аргу- ментов принимают указатели (например, glVertex3fv(v) ), могут работать значительно быстрее, чем их соответствующие версии glVertex3f(x,y,z) Уменьшайте сложность примитивов Во многих случаях будьте внимательны, чтобы не разбивать большие плоскости на части сильнее, чем необходимо. Поэкс- периментируйте, например, с примитивами GLU для определе- ния наилучшего соотношения качества и производительности. 122 Глава 9. Оптимизация программ Текстурированные объекты, например, могут быть качественно отображены с небольшой сложностью геометрии. Используйте дисплейные списки Используйте дисплейные списки для наиболее часто выво- димых объектов. Дисплейные списки могут храниться в памя- ти графической подсистемы и, следовательно, исключать частые перемещения данных из основной памяти. Не указывайте ненужные атрибуты вершин Если освещение выключено, не вызывайте glNormal . Если не используются текстуры, не вызывайте glTexCoord , и т.д. Минимизируйте количество лишнего кода между опера- торными скобками glBegin / glEnd Для максимальной производительности на high-end системах важно, чтобы информация о вершинах была передана графиче- ской подсистеме максимально быстро. Избегайте лишнего кода между glBegin / glEnd Пример неудачного решения: glBegin (GL_TRIANGLE_STRIP) ; for ( i =0; i < n ; i++) { i f ( l i g h t i n g ) { glNormal3fv (norm [ i ] ) ; } glVertex3fv ( vert [ i ] ) ; } glEnd ( ) ; 9.2. Оптимизация вызовов OPENGL 123 Эта конструкция плоха тем, что мы проверяем переменную lighting перед каждой вершиной. Этого можно избежать, за счет частичного дублирования кода: i f ( l i g h t i n g ) { glBegin (GL_TRIANGLE_STRIP) ; for ( i =0; i < n ; i++) { glNormal3fv (norm [ i ] ) ; glVertex3fv ( vert [ i ] ) ; } glEnd ( ) ; } else { glBegin (GL_TRIANGLE_STRIP) ; for ( i =0; i < n ; i++) { glVertex3fv ( vert [ i ] ) ; } glEnd ( ) ; } 9.2.2. Преобразования Преобразования включают в себя трансформации вершин от координат, указанных в glVertex , к оконным координатам, отсе- чение, освещение и т.д. Освещение € Избегайте использования точечных источников света. € Избегайте использования двухстороннего освещения (two- sided lighting). 124 Глава 9. Оптимизация программ € Избегайте использования локальной модели освещения. € Избегайте частой смены параметра GL_SHININESS € Рассмотрите возможность заранее просчитать освещение. Можно получить эффект освещения, задавая цвета вершин вместо нормалей. Отключайте нормализацию векторов нормалей, когда это не является необходимым Команда glEnable / Disable(GL_NORMALIZE) управляет норма- лизацией векторов нормалей перед использованием. Если вы не используете команду glScale , то нормализацию можно отключить без посторонних эффектов. По умолчанию эта опция выключена. Используйте связанные примитивы Связанные примитивы, такие как GL_LINES , GL_LINE_LOOP , GL_TRIANGLE_STRIP , GL_TRIANGLE_FAN , и GL_QUAD_STRIP уменьшают нагрузку на конвейер OpenGL, а также уменьшают количество данных, передаваемых графи- ческой подсистеме. 9.2.3. Растеризация Растеризация часто является узким местом программных ре- ализаций OpenGL. Отключайте тест на глубину, когда в этом нет необходи- мости Фоновые объекты, например, могут быть нарисованы без те- ста на глубину, если они визуализируется первыми. 9.2. Оптимизация вызовов OPENGL 125 Используйте отсечение обратных граней полигонов Замкнутые объекты могут быть нарисованы с установленным режимом отсечения обратных граней glEnable(GL_CULL_FACE) Иногда это позволяет отбросить до половины многоугольников, не растеризуя их. Избегайте лишних операций с пикселями Маскирование, альфа-смешивание и другие попиксельные операции могут занимать существенное время на этапе расте- ризации. Отключайте все операции, которые вы не используете. Уменьшайте размер окна или разрешение экрана Простой способ уменьшить время растеризации уменьшить число пикселей, которые будут нарисованы. Если меньшие раз- меры окна или меньшее разрешение экрана приемлемы, то это хороший путь для увеличения скорости растеризации. 9.2.4. Текстурирование Наложение текстур является дорогой операцией, как в про- граммных, так и в аппаратных реализациях. Используйте эффективные форматы хранения изобра- жений Формат GL_UNSIGNED_BYTE обычно наиболее всего подхо- дит для передачи текстуры в OpenGL. Объединяйте текстуры в текстурные объекты или дис- плейные списки. Это особенно важно, если вы используете несколько текстур, и позволяет графической подсистеме эффективно управлять 126 Глава 9. Оптимизация программ размещением текстур в видеопамяти. Не используйте текстуры большого размера Небольшие текстуры быстрее обрабатываются и занимают меньше памяти, что позволяет хранить сразу несколько текстур в памяти графической подсистемы. Комбинируйте небольшие текстуры в одну Если вы используете несколько маленьких текстур, то можно объединить их в одну большего размера и изменить текстурные координаты для работы с нужной подтекстурой. Это позволяет уменьшить число переключений текстур. Анимированные текстуры Если вы хотите использовать анимированные текстуры, не используйте команду glTexImage2D чтобы обновлять образ тек- стуры. Вместо этого рекомендуется вызывать glTexSubImage2D или glTexCopyTexSubImage2D 9.2.5. Очистка буферов Очистка буферов цвета, глубины, маски и буфера-накопите- ля может требовать значительного времени. В этом разделе опи- саны некоторые приемы, которые могут помочь оптимизировать эту операцию. Используйте команду glClear с осторожностью Очищайте все нужные буферы с помощью одной команды glClear. Неверно: 9.2. Оптимизация вызовов OPENGL 127 g l C l e a r (GL_COLOR_BUFFER_BIT) ; i f ( s t e n c i l i n g ) /* очистить буфер маски? */ { g l C l e a r (GL_STENCIL_BUFFER_BIT) ; } Верно: i f ( s t e n c i l i n g ) /* очистить буфер маски? */ { g l C l e a r (GL_COLOR_BUFFER_BIT | STENCIL_BUFFER_BIT) ; } else { g l C l e a r (GL_COLOR_BUFFER_BIT) ; } 9.2.6. Разное Проверяйте ошибки GL во время написания программ Вызывайте команду glGetError() для проверки, не произошло ли ошибки во время вызова одной из функций OpenGL. Как правило, ошибки возникают из-за неверных параметров команд OpenGL или неверной последовательности команд. Для финаль- ных версий кода отключайте эти проверки, так как они могут существенно замедлить работу. Для проверки можно использо- вать, например, такой макрос: #include #define CHECK_GL \ a s s e r t ( glGetError ( ) != GL_NO_ERROR) ; Использовать его можно так: glBegin (GL_TRIANGLES) ; g l V e r t e x 3 f ( 1 , 1 , 1 ) ; glEnd ( ) ; 128 Глава 9. Оптимизация программ CHECK_GL; Используйте glColorMaterial вместо glMaterial Если в сцене материалы объектов различаются лишь одним параметром, команда glColorMaterial может быть быстрее, чем glMaterial Минимизируйте число изменений состояния OpenGL Команды, изменяющие состояние OpenGL ( glEnable , glDisable , glBindTexture и другие), вызывают повторные внутренние про- верки целостности, создание дополнительных структур данных и т.д., что может приводить к задержкам. Избегайте использования команды glPolygonMode Если вам необходимо рисовать много незакрашенных мно- гоугольников, используйте glBegin с GL_POINTS , GL_LINES , GL_LINE_LOOP или GL_LINE_STRIP вместо изменения режима рисования примитивов, так как это может быть намного быст- рее. Конечно, эти рекомендации охватывают лишь малую часть возможностей по оптимизации OpenGL-приложений. Тем не ме- нее, при их правильном использовании можно достичь суще- ственного ускорения работы ваших программ. 9.3. Контрольные вопросы 1) Перечислите известные вам методы высокоуровневой опти- мизации OpenGL-приложений. |