8.2. И снова анализ    

и оптимизация программы

 

Проанализируем теперь программу studex35. Для нее диаграмма исключения ппепяпий имеет г.пелуюптий ткыт\ Гпис. 8.4рис

 

Как мы видим, операции записи пикселов в растр DIB составляют уже не­большую часть общего цикла рисования. Также уменьшилась доля времени для записи всех пикселов (setPixMyz). Необходимо проанализировать другие операции.

Ранее, в ходе анализа программы studex34, мы отмечали, что нельзя времен­но исключать операцию MyPolyqon, так как изменится логика заполнения

            Z-буфера — кроме полигонов в нашей программе рисуется и шар. И все же давайте сделаем отдельный анализ для цикла вывода многогранников, ис­ключив на весь период измерений функцию Sphere: :Draw. Понятно, что без шара процесс создания изображения будет проистекать по-другому. Однако мы вынуждены пойти на это — когда сложно анализировать весь процесс в целом, необходимо попробовать разделить его на более простые состав­ляющие.

Снова воспользуемся методом временного исключения. Замеры времени от­ражены на следующей диаграмме исключения для многогранников (рис. 8.5).

 

 

 

 

 

 

 

                     Глядя на эти цифры, можно предположить, что необходимо оптимизировать функцию My Polygon, которая вносит наибольший вклад в общее время. Одна­ко, для того чтобы выяснить, как именно ее оптимизировать, следует проана­лизировать структуру программы, а точнее— иерархию вложенных функ­ций. Вложенной функцией мы будем для краткости называть функцию, кото­рая вызывается в теле другой функции. Таким образом, общее время работы некоторой функции складывается из вызовов вложенных функций (если они есть) плюс время работы операторов собственно этой функции

Т функции общее = Т собственное + Т вложенных функций.

Для анализа каждой функции будем в первую очередь оценивать время соб­ственной работы, а не время работы вложенных функций. Это время, оче­видно, равно

Т собственное = Т функции общее - Т вложенных функций.

 

 

 

Из этой схемы видно, что собственное время работы функции MyPoiygon со ставляет разность между общим временем работы этой функции и временем работы функций Ref lectionColor И MyLineHor:

 

 

Обратим внимание на эти цифры. Подозрительно много времени занимают операции тела функции MyLineHor. Возможно, ее следует оптимизировать в первую очередь. Следующим в этом списке фигурирует тело функции SetPixMyZ— 15 сек. Собственное время работы операторов тела функции MyPoiygon относительно невелико.

План оптимизации программы может быть таким:

1.  Совершенствование функции MyLineHor.

             2.  Уменьшение количества уровней вложения функций — можно сэконо­мить время, которое расходуется на вызовы вложенных функций и передачу им значений аргументов. Для этого код функции SetPixRastrMem расположим непосредственно в теле функции SetPixMyz. Отдельная функ­ция SetPixRastrMem нам уже не нужна.

Приведем текст третьего варианта программы (studex36) только для оптими­зированных функций. Другие функции остались без изменений.

                      Часть файла studex3 6. срр:

 

 

 

 

 

 

 

 

 

 

                        

 

 

       В результате второго шага оптимизации программа осуществляет полный цикл вывода многогранников за 49 секунд (вместо 73 для предыдущего вари­анта). Замена типов double на float дает немного — где-то одну-две секун­ды. Повышения скорости в основном достигнуто за счет усовершенствования функции MyLineHor и включения кода записи пикселов DIB в тело функции SetPixMyZ. Функция SetPixRastrMem ликвидирована (ее делать inline не имеет смысла).                                                                                                       

Восстановим работу программы в полном объеме — разблокируем Sphere:: Draw  и сделаем измерения времени. Полный цикл рисования составляет 65 секунд  (предыдущий вариант был 88 секунд).                                                             

Таким образом, мы прошли два шага от первого варианта (studex34) и  уменьшили время графического вывода от 807 до 65 секунд — то есть более 1 чем в 12 раз. Оптимизировать можно бесконечно любую программу, в том числе и последний вариант. Например, использовать ассемблер для программирования некоторых критических функций. Однако это тема уже для  другого разговора...                                                                                         

Главное, что здесь желательно увидеть, — мы находились в рамках только  одной технологии создания графических программ (программирование на основе функций API Windows), хотя результаты программирования могут  существенно отличаться.                                                                                 

Подобная методика анализа может использоваться не только для графических программ.                                                                                                

 

 

ГЛАВА 9

 

Пример анимации

 

Анимация — это создание иллюзии движения, изменения чего-то во време­ни. Для этого генерируется последовательность кадров путем моделирования развития во времени определенных процессов. В предыдущих главах в неко­торых примерах программ мы уже использовали такой способ показа, на­пример, обзор движущейся камерой неподвижных объектов (см. главу 8).

Морфингом  (morphing)   называются   методы   моделирования   изменений формы объекта. Обычно стадии преобразования формы объекта определяются с помощью множества опорных точек. Потом выполняется некоторая ин­терполяция и показ всех этапов трансформации [47].

 

9.1. Поверхность Безье

 

Рассмотрим пример "живой" поверхности Безье, задаваемой точками-ориен­тирами. Если плавно перемещать в пространстве эти точки-ориентиры, то поверхность Безье будет соответственно видоизменяться. Выберем кубиче­скую поверхность Безье.

Для описания изменения расположения точек-ориентиров используем про­странственные кубические кривые Безье, заданные в параметрической фор­ме. Параметром будет время (или номер кадра). Для описания формы куби­ческих кривых требуется четыре опорные точки.

Таким образом, тут используются кривые Безье для описания стадий транс­формаций и поверхности Безье в качестве объектов трансформации. Можно это назвать вариацией на тему Безье. Кроме того, здесь мы попрактикуемся в закрашивании поверхностей методом Гуро.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

                                                                

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

            Для упрощения программы поверхности рисуются симметрично по четырем квадрантам.

Всего создается сто кадров. Номер кадра определяет значение параметра t от 0 до 1. Этот параметр используется для расчета текущих координат точки каждой из 16 кубических кривых Безье. Вдоль этих кривых скользят опорные точки-ориентиры поверхностей Безье (рис. 9.1). Каждая поверхность отображается полигональной сеткой 10 х 10.

       Результат работы программы изображен на рис. 9.2.

 

 

 

                    На рис. 9.3 отображена начальная форма и три промежуточные стадии разви­тия объекта.

 

 

                         

 

 

 

 

 

 

 

 

 

9.2. Градиентное закрашивание

 

При разработке и тестировании функций предыдущей программы почти случайно был получен интересный рисунок— пример градиентного   закрашивания   поверхностей   Безье (рис. 9.4 и на обложке книги).

.           На этом рисунке поверхность выглядит текстурированной рельефными ячейками, будто пуховое одеяло. Каждая ячейка— это грань поверхности Безье, которая рисуется так, что цвет закрашивания  темнеет слева  направо.

Для каждой грани вычисляется наклон нормали, задающий цвет градиентно­го закрашивания. Объект в целом напоминает, скорее всего, капусту.

Текст программы приведем только для тех функций, которые изменены. По­скольку метод Гуро здесь уже не используется, то функция MyPolygonGouraud Переименована В MyPolygon, a MyLineHorGouraud — В MyLineHor. В теле функции MyPolygon вычисляется цвет грани с помощью новой функции Reflection Color. В остальном текст программы идентичен тексту предыду­щей программы..                          

 

 

 

                                                                                                                      

 

 

 

 

 

 

 

                                                                                                             

 

 

ГЛАВА 10

 

Графическая библиотека OpenGL

 

Библиотека OpenGL (Open Graphic Library), разработанная фирмой Silicon Graphics, стала индустриальным стандартом. Интерфейс OpenGL поддержи­вается многими операционными системами для разнообразных аппаратных платформ — от персональных компьютеров до сверхмощных суперкомпью­теров. Важным аспектом является также поддержка интерфейса OpenGL производителями аппаратных графических акселераторов. Поэтому OpenGL позволяет достаточно просто создавать быстродействующие графические программы и часто используется разработчиками компьютерных игр, напри­мер, Quake. Библиотека OpenGL поддерживается в операционной системе Windows, начиная с Windows 95 версии OSR 2, — были добавлены соответ­ствующие модули DLL, а также включены несколько функций и структур данных в API Win32.

Интерфейс OpenGL реализован в виде набора функций, которые можно ис­пользовать в прикладных программах. Известно также расширение для OpenGL — библиотека классов Open Inventor.

Разработка графических программ OpenGL для среды Windows подобна про­граммированию графики GDI функций API, которое мы рассмотрели в гла­вах 5—8. Однако есть особенности, некоторые из которых мы изучим. Для получения более подробных сведений можно порекомендовать литературные источники— прежде всего, это документация Windows SDK [61]. В значи­тельной мере этот источник был использован в книге [25].

Быстродействие графических программ, использующих OpenGL, существен­но зависит от видеоадаптера. Аппаратная реализация всех базовых функций OpenGL — залог высокого быстродействия. В настоящее время многие ви­деоадаптеры содержат специальный графический процессор (один или не­сколько) для поддержки функции графики. Кроме того, что видеоадаптер должен аппаратно выполнять все базовые функции OpenGL (такие как пре­образования координат, расчеты освещения, наложение текстур, отсечение,вывод полигонов), для достижения высокого быстродействия должен быть установлен специальный драйвер. Драйверы типа ICD (Installable Client Driver) обеспечивают интерфейс, способствующий эффективному использо­ванию аппаратных возможностей видеоадаптера. Другой тип драйвера — MCD — устанавливается обычно тогда, когда не все функции поддержаны аппаратно, и в этом случае они выполняются программно центральным про­цессором, что существенно медленнее.

Рассмотрим создание программ OpenGL на языке С, C++ в среде Windows. В главе 6 при рассмотрении графики GDI мы определили ключевой мо­мент— это создание контекста графического устройства (device context). Графика OpenGL в этом плане похожа — необходимо сначала создать кон­текст, который здесь назван контекстом отображения (rendering context), и направить текущий вывод графики на него. Потом следует закрыть этот контекст, освободить память.

Будем программировать в стиле программ studEx предыдущих глав данной книги. Этот стиль заключается в непосредственном вызове функций API Windows без каких-либо посредников типа MFC (или иных подобных биб­лиотек). Во-первых, это уменьшает выполняемый код (поскольку каждому посреднику нужно платить — вот только здесь за что?), а во-вторых, позво­лит нам более детально ознакомиться с OpenGL как таковой. Дадим общую схему программы OpenGL.

1.  Создание окна программы. Здесь необходимо обязательно установить стиль окна ws_clipchildren и ws_clipsiblings . Это осуществляется зада­нием значений аргументов функции Createwindow.

2.  После создания окна можно открывать контекст отображения.   Рекомен­дуется  открытие  этого контекста делать во время обработки сообщения

WM_CREATE.

3.   Чтобы создать контекст отображения, сначала необходимо открыть кон­текст окна (hdc), например, функцией Getoc.

4.  Для выяснения характеристик контекста отображения устанавливаем со­ответствующие значения полей структуры pixelformatdescriptor и вызы­ваем функцию choosePixelFormat. Эта функция возвращает номер пиксельного формата, который можно использовать. Если это номер 0, то созда­ние нужного контекста отображения невозможно.

5.   Вызовом функции SetPixelFormat задаем соответствующий пикселный формат в контексте hdc.                                                                              

           6.   На основе контекста hdc создаем контекст отображения hgirc вызовом  функции wgicreatecontext. Для переадресации текущего вывода графики  OpenGL в hglrc необходимо вызывать функцию wglMakeCurrent.              

7.  В ходе работы программы выводим графические объекты в текущий кон­текст отображения. Графический вывод можно осуществлять во время об­работки сообщения wmpaint или других сообщений. Для этого использу­ются функции для работы с графическими примитивами OpenGL.

8.  Перед закрытием окна программы необходимо закрыть все открытые кон­тексты отображения. Также следует закрыть все контексты графического устройства. Это можно сделать в ходе обработки сообщения wmdestroy

ВЫЗОВОМ функций ReleaseDC И wglDeleteContext.

Чтобы использовать библиотеку OpenGL, в среде разработки программ на С и C++ необходимо подключить соответствующие файлы заголовков. Напри­мер, в среде Borland C++ 5.02 для этого достаточно включить в текст про­граммы строки:

 

10.1. Пример программы OpenGL

 

Текст программы здесь представлен в виде двух файлов — winOpGL.cpp и studexSO.cpp. В этой программе использованы те же файлы ресурсов (studex.rc) и общего описания (studex.def), что и во всех предыдущих при­мерах программ.

 

 

                        

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

                                                             

 

 

 

 

 

 

 

                                          

 

 

         Текст этой программы составлен их двух частей— winOpGL.cpp и studex50.срр. В файле WinOpGL.cpp сосредоточены функции, необходимые для создания окна, оконные функции, функции инициализации графики. Этот  файл будет использован и в следующих примерах программ OpenGL. Файл  studexso. срр содержит текст, описывающий графическое отображение конкретных объектов (функция DrawMyExampleOpenGL). Как вы, наверное, уже заметили, все это подобно использованию в главах 5—8 наших собственных файлов winmain.cpp, winmainl.cpp И studexXX.cpp.

Запустите программу, затем выберите меню "Графика". На экране в окне программы появляется картинка, показанная на рис. 10.1.

 

Изображение в окне программы studexSO создается из нескольких графиче­ских примитивов. В данном случае рисовались точки, линии и полигоны. Вывод каждого такого примитива в OpenGL оформлен парой функций

 

 

Аргументом функции glBegin является код типа объекта.

Координаты вершины объекта задаются функцией givertexxx. Эта функция имеет много разновидностей (суффиксов хх). Отличия обусловлены типом и количеством аргументов givertex. Количество аргументов соответствует числу измерений систем координат. Тип координат-аргументов может быть целым или вещественным (с плавающей точкой) в нескольких разновидно­стях. Например:

 

задает также вещественные, но трехмерные координаты вершины.

       Перечисление всех вершин объекта в программе завершает вызов функции glEnd. Это означает запись примитива в очередь графического вывода. В зависимости от аргумента функции giBegin(mode) список вершин может трак­товаться OpenGL по-разному (табл. 10.1).

 

 

 

Можно считать существенным недостатком ограничение для полигонов (glpolygon)              вывода только выпуклых фигур. Функция API Windows Polygon в этом плане намного совершеннее — она рисует и невы­пуклые полигоны. В OpenGL для рисования произвольных полигонов преду­смотрена триангуляция.

 

причем аргументами функции giLinestippieO являются количество повто­ров пикселов и шаблон пунктира.

          Стиль заполнения фигур может быть задан растровым образцом в массиве 32x32 бит.

 

 

            Обратите внимание, мы уже несколько раз использовали функции glEnabie () и gioisabieO. Это многоцелевые функции. Они предназначены для управле­ния многими разнообразными режимами отображения.

 

 

 

 

10.2. Координаты и матрицы

 

     В OpenGL используются три типа матриц -— видовая матрица, матрица про­екции и матрица текстуры. Все они имеют размер 4x4 и определяют преобра­зования координат так, как описано в главе 2 этой книги.

                                                  

     которые копируют элементы массива m [ ] в текущую матрицу.

Для некоторых часто используемых преобразований предусмотрены функ­ции, которые автоматически заполняют значения коэффициентов. Функция giLoadidentityO устанавливает единичную матрицу текущего преобразо­вания:

 

 

 

            Для того чтобы матрица определенного типа стала текущей, следует вы­звать   функцию    glMatrixMode(mode),    где   Значение   mode = GL_MODELVIEW,

gl_projection или gljtexture. Видовая матрица определяет преобразования мировых координат в координаты проецирования (видовые координаты). Матрица проекции отвечает за преобразование видовых координат проекции в экранные координаты. Матрица текстуры предназначена для наложения проективных текстур.

Для задания проекций отображения предусмотрены несколько функций. Ак­сонометрическая проекция (здесь называется ортографической) задается функциями giortho( ) или gluOrtho2D(). Центральная проекция устанавли­вается ВЫЗОВОМ функции gluPerspective ().

Для задания области отсечения графического вывода используется функция

glViewport().

 

 

 

 

10.3. Пример трехмерной графики

 

 

 

 

 

 

 

 

 

 

 

Для иллюстрации работы Z-буфера (в OpenGL он называется буфером глу­бины — depth buffer) сначала рисуем грани пирамиды, а потом шахматное поле, которое расположено ниже. Грани пирамиды также рисуем все пять (хотя здесь видны только две), причем так, чтобы при отсутствии Z-буфера получался бы заведомо неправильный результат. Проверьте это, попробуйте отключить  Z-буфер.  Для  этого  достаточно  удалить  из  текста  оператор

glEnable (GL_DEPTH_TEST) . Что получится

         Программа studexSi может служить простейшим примером построения изо­бражения трехмерных объектов в заданной проекции. Изображение на рис. 10.2 вполне правдоподобно передает форму объектов. Однако в данной программе отсутствуют многие важные элементы построения реалистичных изображений. В первую очередь это относится к освещению. Так, например, грани пирамиды здесь закрашены разными цветами — передняя грань более светлым цветом, а боковая грань более темным. Эти цвета мы задали вызо­вом функций gicoior3f перед выводом соответствующих граней. Значения цветов выбраны произвольно. Здесь никак не были использованы возможно­сти, которые предоставляет OpenGL для моделирования освещения.

 

10.4. Моделирование освещения

 

Для того чтобы поручить OpenGL изображать объекты в соответствии с некоторой моделью освещения, необходимо вызвать функцию giEnabie(GL_LiGHTiNG). Может быть использовано несколько источников света. Максимальное количество источников света зависит от версии реали­зации OpenGL. Источники света имеют номера от 0 до некоторого макси­мального   значения.   Включение   i-го   источника   выполняется   функцией

glEnable (GLJLIGHTi), а выключениефункцией glDisable (GL_LIGHTi). Так,

например, чтобы заставить светить нулевой источник, необходимо вызвать

функцию glEnable (GL_LIGHT0) .

Каждый источник света можно (и нужно) настроить индивидуально. Делает­ся это вқзовом функций glLightf, glLighti  или glLightfv, glLightiv (векторные разновидности). Функции различаются типами и количеством аргу­ментов. Рассмотрим некоторые примеры настройки.

Определить положение источника света gl_lighto    следующим об­разом:

GLfloat lightpos[4];

    glLightfv(GL_LIGHT0,GL_POSITION,lightpos) ;

В массив lightpos [ ] необходимо записать однородные мировые координаты источника света в виде (х, у, z, w). При вызове glLightfv эти координаты бу­дут преобразованы в соответствии с ракурсом показа, определяемом видовой матрицей. При w=1 будет обеспечено правильное соответствие закрашивания объектов расположению источника.

Направление действия источника света задается так:

    GLfloat lightdirection[3];

    glLightfv(GL_LIGHT0,GL_SPOT_DIRECTION,lightdirection);

В массиве lightdirection [] должны быть указаны координаты радиус-век­тора в относительных единицах (х, у, z), направленного от источника света.

Угол распространения света от точечного направленного источника можно задать так:

glLightf(GL_LIGHT0,GL_SPOT_CUTOFF, angle);

где angle — угол в градусах в диапазоне от 0 до 90°. При этом свет в про­странстве ограничивается конусом. Также допустимо значение angle = 180, которое соответствует равномерному распространению света во все стороны.

Этим далеко не исчерпываются возможности функций glLightf, glLighti, glLightfv и glLightiv для установки параметров источников света. Для наи­более полного ознакомления обратитесь к документации по Win32 SDK [61].

Необходимо учитывать, что многие параметры, в том числе и для источни­ков света, в OpenGL установлены по умолчанию. Так, например, для источ­ника gl_lighto установлен по умолчанию равномерный рассеянный свет (angle = 180). Кроме того, установки по умолчанию неодинаковы для раз­личных источников GL_LIGHTi.

Кроме параметров настройки источника света еще одним важнейшим аспек­том моделирования освещения является задание нормалей к поверхностям. При выводе каждого объекта OpenGL определяет взаимную ориентацию век­тора направления источника света и текущего вектора нормали. По умолча­нию установлено направление вектора нормали как (0, 0, 1), то есть вектор направлен вдоль оси z мировых координат. Направление текущего вектора нормали не изменяется до тех пор, пока не будет вызвана функция giNormai, например:                    

glNormal3f(nx,ny, nz) ;

где nx, ny и nz — это координаты радиус-вектора нормали. Для данной функции они должны быть в диапазоне (-1.0, 1.0). Существуют и другие разновидности для giNormai, отличающиеся типами числовых значений коор­динат.

Таким образом, при выводе каждой отдельной полигональной грани необхо­димо вначале устанавливать направление текущего вектора нормали. Для корректного расчета взаимного расположения векторов нормалей и направ­лений источников света при различных ракурсах показа необходимо устано­вить режим нормализации векторов

glEnable(GL_NORMALIZE) ;

                   Для правильного вывода освещаемых объектов нужно также определять свойства материала поверхности. Если предполагается изображать освещен­ными цветные объекты, то следует установить

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

                                    

 

 

 

 

 

 

 

 

 

 

10.5. Стандартные объемные формы

 

В OpenGL предусмотрены некоторые стандартные, наиболее часто исполь­зуемые трехмерные объекты. Набор таких форм представлен в библиотеке GLU (Utility Library), которая реализована в виде модуля giu32.dll и являет­ся неотъемлемой частью OpenGL. Она включает в себя несколько функций управления проекциями (одну из которых — gluPerspective мы уже исполь­зовали), функции работы с полигонами, кривыми и поверхностями типа В-сплайнов и другие функции.

Рассмотрим функции gluCylinder, gluSphere, gluDisk И gluPartialDisk (рис. 10.4—10.7).

            Перечисленные объекты названы "quadric objects". Параметры slices и stacks определяют количество плоских граней, используемых для аппрокси­мации поверхности. Для того чтобы нарисовать подобный объект, необхо-

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

                                  

            По умолчанию каждый объект рисуется со сплошным заполнением. Изме­нить стиль показа можно вызовом функции gluQuadricDrawStyle. Можно за­дать такие стили показа — в виде точек, расположенных в вершинах много­угольника, каркасное изображение, сплошное заполнение и силуэт (разно­видность каркасного). Например, вызов 

gluQuadricDrawStyle (quadriceps, GLU_LINE);

дает каркасное изображение.

Объекты данного типа располагаются в пространстве в центре координат (О, 0, 0) с учетом матрицы gl_modelview. Поэтому, чтобы нарисовать изобра­жение объекта в требуемом месте, нужно соответствующим образом изме­нить эту матрицу, например, С ПОМОЩЬЮ функций glTranslate и glRotate. Нижеприведенный пример иллюстрирует показ объектов типа "quadric objects".

 

 

 

 

 

 

 

gluSphere (quadricObj, 1, 32, 32);

glRotatef(-90,  1,  0, 0);

glRotatef(-15,  0,  1, 0);

gluDisk (quadricObj, 1.5, 2.5, 32, 16);

 

 

 

При подготовке этого рисунка к печати был изменен цвет фона наглый. Однако на экране монитора синий цвет выглядит значительно лучше (в тек­сте программы: giciearCoior(0,   0,   0.5,   l.Of)).

 

10.6. Текстура

 

В OpenGL имеется достаточно полный набор средств для построения текстурированных изображений.

В качестве текстуры можно использовать растровое изображение. Оно может быть одномерным или двумерным. Для наложения текстуры необходимо вы­полнить несколько операций.

            1. Сначала открыть в памяти массивов котором будет храниться растр тек­стуры. Число байт массива рассчитывается исходя из количества бит на пиксел текстуры. Размеры растра текстуры обязательно должны быть равны степени двойки (плюс несколько пикселов на бордюр). Это требо­вание создает некоторые неудобства для программиста, особенно в слу­чае, когда текстура может быть прочитана из произвольного растрового файла. Впрочем, это не является неразрешимой проблемой — любой растр текстуры можно либо обрезать, либо растянуть (сжать) до требуемых раз­меров.

2.   Заполнить массив текстуры. Здесь следует учитывать то, в каком формате представлен растр текстуры. Если пикселы текстуры представляются в формате RGB (24 бита на пиксел), то байты в массиве должны распола­гаться в виде троек (R, G, В). Заметим, что в массивах DIB Windows API цветовые компоненты располагаются в обратном порядке, то есть (В, G, R).

3.  После того как массив открыт, нужно передать OpenGL адрес массива и другие его параметры. Делается это вызовом функции glTeximage2D для двумерной текстуры и glTeximagelD для одномерной

4.  Затем можно задать параметры фильтрации текстуры (вызовом функции glTexParameter) для качественного отображения объектов различных раз­меров.

5.  Перед   непосредственным   рисованием   объектов   необходимо   устано­вить режим использования текстуры. Делается это вызовом  функции glEnable(gl_texture_2D).   Для   объектов  типа   "quadric   objects(шар, цилиндр,    диск)    нужно    также    вызвать    еще    и    функцию

gluQuadricTexture(quadricObj, GLj_TRUE).

           6.  При выводе полигональных граней (gljfriangles, gl_qoads и им подоб­ных) необходимо указывать соответствие текстурных координат и коор­динат в пространстве объектов. Сделать это можно вызовом функций из семейства giTexCoord. Так, например, функция giTexCoord2f (s,t) указы­вает на точку с текстурными координатами s и t. Последующий вызов функции givertex3f (x,y,z)  одновременно с заданием координат грани также ставит в соответствие координаты (s,t) координатам (x,y,z). На рис. 10.9 показан пример отображения координат четырехугольной грани.

 

Запрограммировать такое отображение текстуры можно таким образом:

glTexCoord2f(0,   0);   glVertex3f(Plx,   Ply,   Plz) ;

glTexCoord2f(0,   1);   glVertex3f(P2x,   P2y,   P2z) ;

glTexCoord2f(1,   1);   glVertex3f(P3x,   P3y,   P3z);

glTexCoord2f(1,   0);   glVertex3f(P4x,   P4y,   P4z) ;

 

Рассмотрим пример использования текстуры в следующей программе.

Файл Studex54. срр:

 

//---------(С) Copyright Порев В.Н.-----------

 

tinclude "winopgl.срр"

#define horTexture 64

ttdefine vertTexture 32

BYTE pixels[horTexture*vertTexture*3]; //24 бит/пиксел

void InitMyTexture(void);

void DrawMyExampleOpenGL(HWND hWnd)

{

RECT rc;

 

GLfloat lightpos[4] = {9,4,15,1};

GLfloat vX[5] = {2, 2,-2,-2, 2};  //координаты стен                          ....

GLfloat vYZ[5] = {2,-2,-2, 2, 2};

GLfloat nX[4]  = {1,0,1,0};       //нормали

GLfloat nZ[4]  = {0,1,0,1};

GLUquadricObj *quadricObj;

glClearColor(1.0f, l.Of, l.Of, l.Of);

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glClearDepth(1.0);

glEnable(GL_DEPTH_TEST);

//---------задаем ракурс показа------------------

GetClientRect(hWnd,&rc);

gluPerspective(50, (double)re.right/rc.bottom, 1, 40);

glMatrixMode(GL_MODELVIEW);

glLoadldentity();

glTranslatef(0, 0, -6);

glRotatef(21,  1,  0, 0);

glRotatef(-40, 0, 1, 0);

//-------задаем параметры источника света--------

glLightfv(GL_LIGHT0,GL_POSITION,lightpos); glLightf(GL_LIGHT0,GL_SPOT_EXPONENT, 1);

                                              

 

 

glLightf(GL_LIGHTO,GL_SPOT_CUTOFF, 180); glEnable(GL_LIGHT0); glEnable(GL_LIGHTING); glEnable(GL_COLOR_MATERIAL); glEnable (GL__NORMALIZE) ;

//—'-----устанавливаем отображение текстуры-----

InitMyTexture() ; glEnable(GL_TEXTURE_2D);

//------------рисуем элементы сцены--------------

glColor3f(0.8, 0.8, 0.8);

for (int i=0;i<4;i++)                              //4 стены

(

glNorma!3f(nX[i], 0, nZ[i]);

glBegin(GL__QUADS) ;

glTexCoord2f(0, 0); glVertex3f(vX[i] ,   0, vYZ[iJ);

glTexCoord2f(1, 0); glVertex3f(vX[i+1], 0, vYZ[i+l]);

glTexCoord2f(1, 1); glVertex3f(vX[i+l] , 1, vYZ[i+l]);

glTexCoord2f(0, 1); glVertex3f(vX[i],   1, vYZ[iJ);

glEnd();

}

quadricObj = gluNewQuadric();

 if (quadricObj)

{

for (int i=0;i<4;i++)         //башни

{

glPushMatrixf);

glRotatef(-90,  1,  0, 0);

glTranslatef(vX[i], vYZ[i], 0);

glColor3f(0.8, 0.8, 0.8);     //цвет стен башни

 glEnable(GL_TEXTURE_2D);

gluQuadricTexture(quadricObj, GL_TRUE);

 gluCylinder(quadricObj, 0.4, 0.35, 1.2, 16, 1);

 gluQuadricTexture(quadricObj , GL_FALSE);

 glDisable(GL_TEXTURE_2D);     //крыша без текстуры

glTranslatef(0, 0, 1.2);

glColor3f(l, 0.5, 0);        //цвет крыши

 gluCylinder(quadricObj, 0.38, 0, 0.7, 16, 1);

glPopMatrix(J; }

gluDeleteQuadric (quadricObj); }

                        glFinish();

 

 

 

 

            //------заполняем массив "шахматной" текстуры-----

void InitMyTexture(void)

(

long krd;

for   (long j=0;j<vertTexture;j++) for   (long  i=0;i<horTexture;i++)

{

krd = 3*horTexture*j + 3*i;

pixels[krd]  =255;

pixels[krd+1]=255;

pixels[krd+2]=255;                                  ' '

if ( (i/8 + j/8) % 2 == 0)   //голубые клетки

{

pixels[krd]=0;                         //R

pixels[krd+l]=128;      //G

}

} glTexImag|2D(GL_TEXTURE_2D, 0, 3,

horTexture,vertTexture, 0, GL_RGB, GL_UNSIGNED_BYTE, pixels); glTexParameterf(GL_TEXTURE_2D,

GL_TEXTURE_MIN_FILTER, GL_NEAREST);

                 }

                                                               

 

Можно предположить, что хозяин этого замка решил покрыть плиткой сте­ны, дабы уберечь свою недвижимость от разрушительного воздействия аг­рессивной окружающей среды. Так это было или нет, но здесь мы еще раз использовали шахматный узор — уже для текстуры. Растр текстуры генери­руется здесь "на лету" и сохраняется в массиве pixels. Это пример синтети­ческой текстуры, узор создается простейшим алгоритмом. Для создания реа­листичных изображений в качестве текстур обычно используются цифровые фотографии.

В общем случае, текстуры удобнее хранить в файлах на диске. Это могут быть достаточно сложные изображения, изготовленные заблаговременно. В программах для Windows растры можно создавать в виде ресурсов, кото­рые после компиляции записываются в выполняемые файлы, например, в файлы ЕХЕ. А можно использовать и отдельные файлы стандартных фор­матов. В последнем случае текстуры удобно многократно редактировать. Рассмотрим, как можно использовать текстуры, записанные в файлах BMP. Такие файлы хранят растр в формате DIB (Device Independent Bitmap). Фор­мат DIB похож на формат текстуры OpenGL, однако есть некоторые отличия. Так, в DIB используется выравнивание строк на границу двойного слова. Иными словами, количество байт в строке растра всегда должно быть кратно четырем — если это не так, то добавляют лишние байты. В нашем случае благоприятным фактором является то, что размеры текстур OpenGL должны быть равны степени двойки. Начиная с размеров по горизонтали, равных че­тырем, 24-битные растры DIB автоматически располагаются в памяти так же, как и текстуры OpenGL — выравнивание отсутствует.

Если использовать 24-битную глубину цвета, то более существенным отли­чием DIB от формата текстур OpenGL является порядок расположения бай­тов R, G и В. Для массивов текстур OpenGL должно быть R-G-B, в то время как в DIB наоборот: B-G-R. Поэтому после чтения файла необходимо пере­ставлять байты R и В.

Наша следующая программа (studex55) иллюстрирует чтение текстуры из файла BMP. Эта программа является модификацией предыдущей программы (studex54). Изменения коснулись только функции initMyTexture. В ее тело встроена функция чтения файлов BMP, которая названа ReadTextureBMP.

 

void InitMyTexture(void)

{

ReadTextureBMPCfilename.bmp",   pixels,

                  horTexture, vertTexture);

 glTexImage2D(GL_TEXTURE_2D, 0, 3,

                 horTexture,vertTexture,

0, GL_RGB, GL_UNSIGNED_BYTE, pixels);

glTexParameterf(GL_TEXTURE_2D,

                 GL_TEXTURE_MIN_FILTER,    '

                  GL_NEAREST);

}                                                                                                    '

BOOL ReadTextureBMP(char *filename,BYTE *lp,

                           DWORD hor,DWORD vert) {

BITMAPFILEHEADER bfhdr;

 BITMAPINFOHEADER bmihdr;

 FILE *fin;

DWORD i,j,pos,byteshor;

 BYTE bt;

fin = fopen(filename,"rb");

if (fin == NULL) return FALSE;

fread(&bfhdr,sizeof(BITMAPFILEHEADER),1,fin) ;

fread(&t|tiihdr,sizeof (BITMAPINFOHEADER) ,l,fin) ;

if ( (bmihdr.MBitCount != 24)||                         //только 24-битные растры,

           (bmihdr.biWidth != hor)||                                   //соответствующие размерам

              (bmihdr.biHeight != vert))                        //массива текстуры

{

fclose(fin);                                    //этот файл не подходит

return FALSE;

}

byteshor = 3*bmihdr.biWidth;                    //число байт в строке растра

 fseek(fin,   bfhdr.bfOffBits,   SEEK_SET)

; pos =0;                                                                    /

for   (i=0;   Kbmihdr.biHeight;   i++)

{

fread(lp+pos, byteshor, l,fin);  //читаем строку растра

pos += byteshor;

}

fclose(fin);

for (j=0;j<vertTexture;j++)                     //меняем местами байты R и В

{

pos = 3*horTexture*j;

 for (i=0;i<horTexture;i++)

{                                                                     ,

bt = lpfpos];

lp[pos]=lp[pos+2];

            lp[pos+2]=bt;

 

 

                            

 

pos  += 3; }

}                                                       

return TRUE; }

 

Текстура здесь читается из файла "filename.bmp".

В этой программе текстура загружается в массив всякий раз при создании кадра. В данном примере это вполне допустимо, однако в других случаях та­кой подход может быть плохим по быстродействию. Поскольку дисковые операции являются медленными (особенно при чтении нескольких различ­ных файлов больших размеров), то чтение файлов текстур нужно стараться делать как можно реже. Например, при построении кадров "облета замка" — то есть при показе» одних и тех же объектов с разных ракурсов, текстуры лучше всего загружать перед началом цикла показа. Если используются не­сколько текстур, то для каждой можно создать в памяти отдельный массив.

На рис. 10.11 показан результат работы studex55.

Возможно, в этом замке мало дверей и окон. Но их несложно добавить в тек­стуру с помощью любого растрового графического редактора, не так ли? Хотя, вероятно, понадобится использовать уже несколько текстур для разных стен.

          Следует отметить, что приведенная выше функция ReadTextureBMP не являет­ся универсальной— она не рассчитана на другие разновидности формата файлов BMP. Эту функцию необходимо существенно видоизменить, если предусматривается чтение, например, и 256-цветных растров. Такие растры читать несколько сложнее, поскольку требуется загружать и устанавливать палитру. В качестве универсального решения для чтения файлов формата BMP можно порекомендовать использовать функцию auxDiBimageLoad из библиотеки glaux.