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), хотя результаты программирования могут существенно отличаться.
Подобная методика анализа может использоваться не только для графических программ.
Пример анимации
Анимация — это создание иллюзии движения, изменения чего-то во времени. Для этого генерируется последовательность кадров путем моделирования развития во времени определенных процессов. В предыдущих главах в некоторых примерах программ мы уже использовали такой способ показа, например, обзор движущейся камерой неподвижных объектов (см. главу 8).
Морфингом (morphing) называются методы моделирования изменений формы объекта. Обычно стадии преобразования формы объекта определяются с помощью множества опорных точек. Потом выполняется некоторая интерполяция и показ всех этапов трансформации [47].
Рассмотрим пример "живой" поверхности Безье, задаваемой точками-ориентирами. Если плавно перемещать в пространстве эти точки-ориентиры, то поверхность Безье будет соответственно видоизменяться. Выберем кубическую поверхность Безье.
Для описания изменения расположения точек-ориентиров используем пространственные кубические кривые Безье, заданные в параметрической форме. Параметром будет время (или номер кадра). Для описания формы кубических кривых требуется четыре опорные точки.
Таким образом, тут используются кривые Безье для описания стадий трансформаций и поверхности Безье в качестве объектов трансформации. Можно это назвать вариацией на тему Безье. Кроме того, здесь мы попрактикуемся в закрашивании поверхностей методом Гуро.
Для упрощения программы поверхности рисуются симметрично по четырем квадрантам.
Всего создается сто кадров. Номер кадра определяет значение параметра t от 0 до 1. Этот параметр используется для расчета текущих координат точки каждой из 16 кубических кривых Безье. Вдоль этих кривых скользят опорные точки-ориентиры поверхностей Безье (рис. 9.1). Каждая поверхность отображается полигональной сеткой 10 х 10.
Результат работы программы изображен на рис. 9.2.
На рис. 9.3 отображена начальная форма и три промежуточные стадии развития объекта.
9.2. Градиентное закрашивание
При разработке и тестировании функций предыдущей программы почти случайно был получен интересный рисунок— пример градиентного закрашивания поверхностей Безье (рис. 9.4 и на обложке книги).
. На этом рисунке поверхность выглядит текстурированной рельефными ячейками, будто пуховое одеяло. Каждая ячейка— это грань поверхности Безье, которая рисуется так, что цвет закрашивания темнеет слева направо.
Для каждой грани вычисляется наклон нормали, задающий цвет градиентного закрашивания. Объект в целом напоминает, скорее всего, капусту.
Текст программы приведем только для тех функций, которые изменены. Поскольку метод Гуро здесь уже не используется, то функция MyPolygonGouraud Переименована В MyPolygon, a MyLineHorGouraud — В MyLineHor. В теле функции MyPolygon вычисляется цвет грани с помощью новой функции Reflection Color. В остальном текст программы идентичен тексту предыдущей программы..
Графическая библиотека 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 для моделирования освещения.
Для того чтобы поручить 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)).
В 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.