Текст
                    OPEHfiL
СОЗДАНИЕ РЕАЛИСТИЧЕСКИХ ОБРАЗОВ
ПРОГРАММИРОВАНИЕ
ТРЕХМЕРНОЙ
ГРАФИКИ
РУКОВОДСТВО ДЛЯ ПРОФЕССИОНАЛОВ
1233

УДК 681.3.06 Книга посвящена программированию визуализации трехмерных реалистических обра- зов с использованием системы графических библиотек OpenGL (для Windows 95 и Window's NT) В книге приведена необходимая теоретическая информация (системы координат, геометрические преобразования, проекции), рассмафиваюгся основные принципы создания и обработки графических объектов в OpenGL. работа с цветом наложение текстуры, а также структура консольною приложения Windows для работы с OpenGL и многое друюе В книге содержится много практических примеров, которые потносюю включены в состав прилагаемой дискеты Для программистов на С и С - Группа подготовки издания: Главный редактор Зав редакцией Ответственный редактор Компьютерная верстка Вадим Сергеев А гексен Жданов Татьяна Темкина Корректор Дизайн обложки Производство Ольга Сергиенко, Наталья Богова Зинаида Дмитриева Елена Клыкова Николай Тверских Тихомиров Ю. Программирование трехмерной графики — СПб BHV — Санкт-Петербург, 1998. — 256 с , ил ISBN 5-7791-0074-8 © Ю. Тихомиров, 1998 © Оформление, издательство «BHV — Санкт-Петербург», 1998 Лицензия ЛР № 090141 от 12 02 96 Подписано в печать 19 02 98 Фоомат 70x100 16 Печать офсетная Усл печ л 20.6 Тираж 7000 экз Заказ № 1314 BHV — Санкт-Петербург, 198052 С -Петербург. Измайловский пр 29 Отпечатано с диапозитивов в ГПП «Печатный Двор» Государственного комитета РФ по печати 197110, Санкт-Петербург. Чкаловский пр , 15
Оглавление ВВЕДЕНИЕ................................................3 ГЛАВА 1. ОСОБЕННОСТИ РЕАЛИЗАЦИИ OPENGL В WINDOWS NT И WINDOWS 95............................................6 Установка стиля окна OpenGL ..... 7 Установка формата пикселей ........................... .7 Установка текущего контекста воспроизведения .. .16 Завершение работы с OpenGL ..................17 ГЛАВА 2. СТРУКТУРА КОНСОЛЬНОГО ПРИЛОЖЕНИЯ WINDOWS ДЛЯ РАБОТЫ С OPENGL....................................20 ГЛАВА 3. ВЕРШИНЫ, ПРИМИТИВЫ............................29 Архитектура OpenGL........... .... .....29 Синтаксис команд OpenGL. . 31 Вершины и система координат 32 Примитивы OpenGL 34 Точки ... 37 Линии ... ... .... .... ..... .45 Треугольники.................................... 49 Четырехугольники .... ... ..............53 Многоугольники...... 59 Растровые примитивы................................ 65 Первая трехмерная картинка .................... ... 69 ГЛАВА 4. ГЛУБИНА, ТРАФАРЕТЫ, ТУМАН И МНОГОЕ ДРУГОЕ.......72 Принадлежность пикселей контексту воспроизведения. 74 Отсечение ... . ................. .74 Прозрачность.......................................... 77 Смешение цветов 78 Трафарет 84 Глубина 89 Списки ИЗОБРАЖЕНИЙ 92 Интерполяция цветов 96 Логические операции 97 Один из способов передачи глубины .. .... . 99 ГЛАВА 5. КООРДИНАТЫ, ГЕОМЕТРИЧЕСКИЕ ПРЕОБРАЗОВАНИЯ И ПРОЕКЦИИ............................................105
Оглавление Системы координат в трехмерном пространстве 105 Однородные координаты и матрицы 107 П реобразован и е коорди нат 111 Матрицы . 111 Видовое преобразование ИЗ Проекции 116 О п редел е н п е обл аст! i вы вода 117 ОрЮ1рафическая проекция 119 Перспективная проекция 125 Другие виды проекций 129 Ориентация 131 ГЛАВА 6. ЦВЕТ В КОМПЬЮТЕРНОЙ ГРАФИКЕ....................134 Обработка цветов в OpenGL 137 Палитра Windows в режиме RGBA 139 Заполнение структур PALETTEENTRY. 141 Системные цвета . 142 Гамма-коррекция 143 Обработчики сообщений............................... 143 ГЛАВА 7. ПОСТРОЕНИЕ РЕАЛИСТИЧЕСКИХ ИЗОБРАЖЕНИЙ..........146 Освещение объектов ... 148 Нормали 150 Свойсчва материала 150 Грани 155 Источник свет 161 Модель освещения 168 Поверхности, пропускающие свет 173 Тени . 177 ГЛАВА 8. ТЕКСТУРА.......................................179 Преобразование растрового изображения в формат OpenGL 180 Создание текста ры в памяти 185 Параметры текстуры 191 Взаимодействие текстуры с объектом 194 Координаты текстуры 196 ПРИЛОЖЕНИЕ 1. АРГУМЕНТЫ КОМАНД GLENABLE И GLDISABLE ...206 ПРИЛОЖЕНИЕ 2. ПОЛУЧЕНИЕ ИНФОРМАЦИИ О ВЫБРАННЫХ ПАРАМЕТРАХ..............................................211 ПРИЛОЖЕНИЕ 3. ГЛОССАРИЙ.................................227 БИБЛИОГРАФИЯ............................................235 ЗАКЛЮЧЕНИЕ..............................................237 ПРЕДМЕТНЫЙ УКАЗАТЕЛЬ....................................239
3 Введение Компьютерная графика в настоящее время уже вполне сформировалась как наука Существует аппаратное и программное обеспечение для получения разнообразных изображений — от простых чертежей до реалистичных обра- зов естественных объектов Компьютерная графика используется практиче- ски во всех научных и инженерных дисциплинах для наглядности воспри- ятия и передачи информации. Знание ее основ в наше время необходимо любому ученому или инженеру. Она властно вторгается в бизнес, медицину, рекламу, индустрию развлечений и многие другие области. Особый интерес к компьютерной графике проявляется тогда, когда графи- ческое изображение появляется в результате собственной деятельности программиста Принцип "сделай сам" применим ко всем видам искусства, но в компьютерной графике имеется уникальная возможность получить в собственное распоряжение очень точного и исполнительного помощника, который может создать любые рисованные картинки, — нужно только про- инструктировать его, как это сделать И хотя в последнее время появилось огромное количество программных продуктов, предназначенных для пере- численных и многих других задач, вряд ли появится программное обеспече- ние для всех конкретных случаев, так что и в будущем необходимость про- граммирования графики сохранится Однако программирование программированию рознь: можно самому изу- чать и создавать алгоритмы и потом их реализовывать (что, кстати, всегда полезно), а можно использовать одну из стандартных библиотек (в частно- сти, для программирования графики), тем более, что их достаточно много Графический стандарт OpenGL, разработан и утвержден в 1992 году девятью ведущими фирмами, среди которых Digital Equipment Corporation, Evans & Sutherland, Hewlett-Packard Co. IBM Corp, Intel Corp, Intergraph Corp, Silicon Graphics, Inc , Sun Microsystems, Inc и конечно же Miciosoft В осно- ву стандарта была положена библиотека IRIS GL. разработанная Silicon Graphics Эта достаточно простая в изучении и использовании графическая система (она включает в себя три библиотеки Opengl 32 lib, qlu 32 ab и glaux. lib), обладающая при этом поразительно широкими возможностями. Вот только некоторые из ее достоинств-
4 Введение □ Стабильность OpenGL устоявшийся стандарт, действующий уже 5 лет Все вносимые в него изменения предварительно анонсируются и реали- зуются таким образом, чтобы гарантировать нормальную работу уже на- писанного программного обеспечения □ Надежность Все приложения, использующие OpenGL, гарантируют оди- наковый визуальный результат вне зависимости от используемого обору- дования и операционной системы □ Переносимость Приложения, использующие OpenGL, могут запускаться на персональных компьютерах, рабочих станциях или суперкомпьютерах □ Простота использования OpenGL хорошо структурирована Ее драйверы включают информацию об основном оборудовании, освобождая разра- ботчика приложения от необходимости проектирования для специфиче- ских особенностей графических устройств Команды OpenGL обеспечи- вают необходимый баланс между необходимыми функциональными возможностями и гибкостью Каждая команда OpenGL строго придержи- вается опубликованной спецификации, сохраняя драгоценный цикл раз- работки, который теряется при работе с другим, менее удачно спроекти- рованным интерфейсом Система OpenGL1 — это графический стандарт, который предоставляет ши- рокие возможности несмотря на то, что поддерживает простейшую модель программирования Ее процедурный интерфейс позволяет программистам легко и эффективно описывать как простые, так и комплексные задачи вос- произведения. Поскольку OpenGL применяется только для воспроизведе- ния, то она может включаться в состав любой, не только графической 'например X Windows, Windows 95 и Windows NT), операционной системы Более того, OpenGL спроектирована таким образом, чтобы использовать все 1реимущества любых, даже самых изощренных графических подсистем OpenGL является программным интерфейсом для графических устройств и включает в себя свыше ста функций и процедур, которые позволяют про- раммисту определять объекты и сложные операции для создания высокока- гественных образов С точки зрения программиста OpenGL представляет обой множество команд, одни из которых позволяют определять двумер- ные и трехмерные графические объекты, а другие управляют их отображе- 1ием в буфере кадра В этом состоит основное и единственное предназна- ение OpenGL Она не поддерживает никакое другое периферийное устрой- тво (например, мышь или клавиатуру), которое может быть связано с рафическим устройством. Поэтому программист должен сам обеспечить озможность принимать данные от пользователя, используя для этого другие [еханизмы [еречислим основные возможности, которые OpenGL предоставляет разра- отчикам: I Геометрические примитивы (точки, линии и многоугольники) jL — сокращение от Giaphics Library (графическая библиотека)
Введение 5 □ Растровые примитивы (битовые массивы и прямоугольники пикселей) □ Работа с цветом в RGBA и индексном режимах □ Видовые и модельные преобразования □ Удаление невидимых линий и поверхностей □ Прозрачность □ Использование В-сплайнов для рисования линий и поверхностей □ Наложение текстуры □ Применение освещения □ Использование плавного сопряжения цветов, устранения ступенчатости, "тумана" и других "атмосферных" эффектов □ Использование списков изображений Как видите, возможности "почти безграничны" — трудно придумать задачу, которую нельзя было бы решить с помощью OpenGL Очевидно, что в не- большой по объему книге невозможно рассмотреть все аспекты Поэтому я остановился на варианте подробного, с сильно выраженной практической направленностью, рассмотрения основных, базовых вопросов: □ Настройка параметров Windows для работы с OpenGL □ Примитивы — "строительные блоки" любого объекта □ Видовые и перспективные преобразования □ Работа с источниками света □ Наложение текстуры □ Способы создания реалистических изображений □ И некоторых других. Все приведенные примеры были отлажены в Visual C++ 4 1, за исключени- ем того, что для иллюстрации работы с массивами примитивов (приложение Polygons) необходимо использовать более новую версию OpenGL, которая поставляется с Visual C++ 5 0. В заключение хочется выразить огромную благодарность тем, кто помог мне в работе Прежде всего — Федору Пинежанинову, который не только прора- ботал большую часть материала главы 5, но и помогал советами на всем протяжении написания книги. Особую благодарность хочется выразить ре- дактору Татьяне Темкиной, неоднократное сотрудничество с которой благо- творно влияет на мою работу Хочется также поблагодарить всех сотрудни- ков издательства "BHV — Санкт-Петербург" Поскольку в любом деле есть свои недостатки, я буду благодарен за любые замечания и пожелания, которые можно присылать по адресу □ Издательство "BHV - Санкт-Петербург", 199397, С -Петербург, а/я 194 □ E-mail bhv@mail nevalink.ru
6 Глава 1 Особенности реализации OpenGL в Windows NT и Windows 95 Во введении было сказано, что являясь графическим стандартом, OpenGL может работать прлктически под управлением любой операционной систе- мы. И хотя это действительно так, однако для каждой конкретной системы существует своя реализация этого стандарта, которая может не поддержи- вать некоторые из общих возможностей В полной мере это относится и к операционным системам Windows Строго придерживаясь стандарта, реали- зация OpenGL для Windows NT и Windows 95 требует выполнения некото- рых предварительных настроек, связанных с особенностями этих операци- онных систем. Для того чтобы оконная система могла работать с OpenGL, необходимо провести ее инициализацию и сконфигурировать буфер фрейма Обе эти операции выполняются вне OpenGL Вспомним, как осуществляется рисование в Windows Создается контекст устройства, и весь графический вывод осуществляется через него Нечто по- добное необходимо и для работы с OpenGL, только в отличие от рисования GDI, OpenGL использует концепцию контекста воспроизведения (rendering context), который связывает OpenGL с оконными системами Windows NT и Windows 95. Если контекст устройства содержит информацию, относящуюся к графическим компонентам GDI Windows NT и Windows 95, то контекст воспроизведения содержит информацию, относящуюся к OpenGL Таким образом, чтобы начать работать с командами OpenGL, приложение должно создать один или несколько контекстов воспроизведения для пото- ка, и сделать текущим один из них. Каждый поток при этом может иметь эдин и только один текущий контекст воспроизведения, который ассоции- эован с определенным контекстом устройства И еше одно замечание — контекст воспроизведения может быть текущим только для одного потока Второй момент, связанный с особенностями реализации OpenGL в Windows ЧТ и Windows 95, заключается в том, что перед созданием контекста вос- 1роизведения необходимо установить для него формат пикселей, который шределяет некоторые свойства поверхности рисования OpenGL и должен ювпадать с форматом пикселей соответствующего контекста воспроиз- ведения.
Особенности реализации OpenGL в Windows NT и Windows 95 7 Итак, перед тем как начать пользоваться командами OpenGL, необходимо выполнить некоторые обязательные инициализирующие действия Рассмот- рим их Установка стиля окна OpenGL Прежде всего обращаем внимание на то, что OpenGL требует для своего окна наличия установленных стилей WS CLIPCHILDREN и WSCLIPSIBLINGS Приведенный ниже фрагмент кода иллюстрирует, как и где это можно сделать. BOOL CPrimView::PreCreateWindow(CREATESTRUCT& cs) { // OpenGL требует обязательного наличия стилей // WS_CLIPCHILDREN и WS_CLIPSIBLINGS cs.style |= (WS_CLIPCHILDREN | WS_CLIPSIBLINGS); return CView::PreCreateWindow(cs); Установка формата пикселей Реализация OpenGL фирмы Microsoft для Windows NT и Windows 95 исполь- зует специальную структуру PIXELFORMATDESCRIPTOR, чтобы предста- вить данные формата пикселей' typedef struct tagPIXELFORMATDESCRIPTOR { WORD nSize, WORD nVersiorr, DWORD dwFlags] BYTE iPixelType, BYTE cColorBits-, BYTE cRedBits, BYTE cRedShift. BYTE cGreenBits, BYTE cGreenShift, BYTE cBlueBits-, BYTE cBlueShift, BYTE cAlphaBits-, BYTE cAlphaShift, BYTE cAccumBits, BYTE cAccumRedBits, BYTE cAccumGreenBits, BYTE cAccumBlueBits, BYTE cAccumAlphaBits, BYTE cDepthBits; BYTE cStencilBits-,
8 Глава 1 BYTE cAuxBuffers; BYTE iLayerType, BYTE bReserved; DWORD dwLayerMask, DWORD dwVisibleMask; DWORD dwDamageMask; } PIXELFORMATDESCRIPTOR, Рассмотрим поля этой структуры nSize Размер структуры Должен быть установлен при помощи оператора sizeof(PIXELFORMATDESCRIPTOR) nVersion Номер версии Для текущей реализации OpenGL значение должно быть установлено в 1 dwFlags Множество битовых флагов, определяющих устройство и интерфейс, с которым совместим формат пикселей Значение PFD_DRAW_TO_ WINDOW PFD_DRAW_TO_BITMAP PFD_SUPPORT_GDI PFD_SUPPORT_OPENGL PFD_GENERIC_FORMAT PFD_NEED_PALETTE Назначение Разрешено рисование в окне или на поверхности устройства Разрешено рисование в битовый массив в памяти Поддерживается рисование GDI В текущей реализации OpenGL этот флаг нельзя использовать вместе с флагом PFD_DOUBLEBUFFER Поддерживается рисование OpenGL Формат пикселей поддерживается программной реализацией GDI, на- зываемой также основной реализа- цией Если этот флаг не установлен, то формат пикселей поддерживается драйвером устройства или оборудо- вания Для управления палитрой устройства используются пиксели RGB А Логи- ческая палитра требуется для полу- чения лучшего результата для этого типа представления пикселей (в ча- стности, для проведения гамма- коррекции цветов) Цвета в палитре должны быть определены соответст- венно в полях cRedBits, cRedShift, cGreenBits, cGreenShift, cBlueBits и cBlueShift Палитра должна быть соз- дана и инициализирована в контек- сте устройства до вызова функции wglMakeCurrent
Особенности реализации OpenGL в Windows NT и Windows 95 (продолжение) Значение Назначение PFD_NEED_SYSTEM_PALETTE Используется системой со специаль- ным оборудованием для OpenGL, которое поддерживает только одну палитру устройства Для таких сис- тем используются специальные уско- рители и они должны иметь фикси- рованный размер и порядок, напри- мер 3-3-2, при работе в режиме RGB, или должны совпадать с логической палитрой в режиме индексации цве- тов При установленном этом флаге вызов функции SetSystemPaletteUse приводит к отображению "один к од- ному" логической и системной па- литр Если оборудование для OpenGL поддерживает несколько палитр, и драйвер устройства отводит для них место, нет необходимости устанав- ливать этот флаг точно так же, как он не устанавливается для основного формата пикселей PFD_DOUBLEBUFFER Поддерживается режим двойной бу- феризации В текущей реализации OpenGL этот флаг нельзя использо- вать вместе с флагом PFD_ SUPPORT_GDI PFD_SWAP_LAYER_BUFFERS Показывает, может ли устройство менять местами отдельные уровни с форматами пикселей, которые вклю- чают верхние и нижние плоскости при двойной буферизации В против- ном случае все уровни рассматрива- ются как единая группа При уста- новленном этом флаге можно исполь- зовать функцию wglSwapLayerBuffers iPixelType cColorBits Определяет режим, используемый для изображения цветов Дос- тупны два значения флага PFD_TYPE_RGBA — цвет каждого пикселя определяется четырьмя значениями красным, зеленым, синим и альфа, PFD_TYPE_COLORINDEX — цвет каждого пиксе- ля определяется индексом в специальной таблице цветов Определяет число битовых плоскостей в каждом буфере цвета Для режима RGBA определяет размер буфера цвета, исключая битовую плоскость альфа В режиме индексации цвета — размер буфера индексов
10 Глава 1 cRedBits Определяет число битовых плоскостей красного в каждом буфе- ре RGBA cRedShift Определяет смещение от начала числа битовых плоскостей красного в каждом буфере RGBA cGreenBits Определяет число битовых плоскостей зеленого в каждом буфе- ре RGBA cGreenShift Определяет смещение от начала числа битовых плоскостей зе- леного в каждом буфере RGBA cBlueBits Определяет число битовых плоскостей синего в каждом буфере RGBA cBlueShift Определяет смещение от начала числа битовых плоскостей си- него в каждом буфере RGBA c AlphaBits Определяет число битовых плоскостей альфа в каждом буфере RGBA Эти битовые плоскости не поддерживаются cAlphaShift Определяет смещение от начала числа битовых плоскостей альфа в каждом буфере RGBA Эти битовые плоскости не под- держиваются cAccumBits Определяет общее число битовых плоскостей в буфере аккуму- лятора cAccumRedBits Определяет число битовых плоскостей красного в буфере акку- мулятора cAccumGreenBits Определяет число битовых плоскостей зеленого в буфере акку- мулятора cAccumBlueBits Определяет число битовых плоскостей синего в буфере аккуму- лятора cAccumAlphaBits Определяет число битовых плоскостей альфа в буфере аккуму- лятора cDepthBits Определяет размер буфера глубины (ось z) cStencilBits Определяет размер буфера трафарета cAuxBuffers Определяет число вспомогательных буферов Этот тип буферов не поддерживается iLayerType Может принимать одно из следующих значений PFD_MAIN_PLANE, PFD_OVERLAY PLANE, PFD_UNDERLAY_PLANE bReserved Определяет число плоскостей переднего и заднего плана dwLayerMask Игнорируется Это поле использовали ранние версии OpenGL dw Visible Mask Определяет индекс или цвет прозрачности нижней плоскости dwDamageMask Игнорируется Это поле использовали ранние версии OpenGL Основная реализация OpenGL в Windows поддерживает 24 различных фор- мата пикселей. Несмотря на то что каждый формат идентифицируется ин-
Особенности реализации OpenGL в Windows NT и Windows 95 дексом от 1 до 24, они не являются постоянными, и поэтому никогда нельзя полагаться на порядок индексов Формат пикселей характеризуется некото- рыми свойствами (рис 1 1), основным из которых является число битов на пиксель Рис. 1.1. Основные компоненты формата пикселей Исходно поддерживаются пять битовых плоскостей 32, 24, 16, 8 и 4 бита на пиксель Эти пять и еще три формата пикселей, определяемые драйвером дисплея, рассматриваются как внутренний (native) формат Остальные 16 поровну делятся между организацией других битовых плоскостей и под- держкой битовых массивов (они рассматриваются не как внутренний фор- мат). Все внутренние форматы представлены в таблице Режим Буферизация Размер буфера глубины PFD_TYPE_RGBA Одинарная 32 Одинарная 16 Двойная 32 Двойная 16 PFD_TYPE_COLORINDEX Одинарная 32 Одинарная 16 Двойная 32 Двойная 16 Для работы с этой структурой в Win32 реализованы четыре функции Кон- текст устройства может поддерживать несколько форматов пикселей, кото- рые в Windows NT и Windows 95 идентифицируются по значению индекса При этом текущим может быть только один формат Прежде чем установить некоторый желательный формат пикселей, необхо- димо направить системе запрос — поддерживает ли она его Сделать это можно при помощи функции int ChoosePixelFormat( HDC hdc, const PIXELFORMATDESCRIPTOR* ppfd)
/2 Глава 1 Функция просматривает, в контексте'устройства hdc, поддерживаемые фор- маты пикселей, и выбирает наиболее совпадающий с описанным в структу- ре. на которую указывает ppfd Поля структуры описаны выше, поэтому здесь остановимся только на дополнительных сведениях (ограничениях) об их использовании Если в поле dw Flags структуры установлены какие-либо флаги, то функция учитывает их при поиске лучшего совпадения Для того чтобы в дальнейшем при работе не возникало никаких неожиданностей, лучше всего задавать их в явном виде Если для флагов можно не указывать никакого значения, то в поле iPixelType обязательно должно быть установлено значение либо PFD_TYPE_RGBA, либо PFD TYPE COLORINDEX, определяющее метод, используемый для отображения цветов, — тройка RGB или индекс в палит- ре, соответственно. Аналогично, в поле iLayerType в явном виде следует указать одно из возможных значений — PFD MAIN PLANE, PFD_ OVERLAY_PLANE или PFD UNDERLAY PLANE, определяющих "рабочий слой" (текущая реализация поддерживает только один, основной, слой). Значения, задаваемые в полях cColorBits, cAlphaBits, cAccumBits, с Depth Bits, cStencilBits и cAuxBuffer, должны быть больше или равны нулю Значения в остальных полях при поиске не учитываются. В случае успешного заверше- ния функция возвращает индекс формата пикселей (начиная с 1), наиболее полно удовлетворяющего заданным параметрам В случае ошибки возвраща- ется 0. Вызов этой функции гарантирует, что OpenGL будет работать с форматом пикселей, поддерживаемым устройством, на которое будет осуществляться вывод изображения. Например, если хотелось бы использовать 24- разрядный буфер RGB цветов, а контекст устройства поддерживает только 8-разрядный, то функция возвратит формат пикселей именно с 8-разрядным буфером RGB цветов После того как найден формат пикселей, наиболее полно совпадающий с требуемым, можно (и нужно) установить его в контексте устройства BOOL SetPixelFormat( HDC hdc, int iPixelFormat, CONST PIXELFORMATDESCRIPTOR* ppfd) Параметры функции определяют контекст устройства hdc, для которого ус- танавливается формат пикселей; индекс iPixelFormat, который получен при помощи функции ChoosePixelFormat и определяет конкретный формат пик- селей из имеющегося набора форматов, указатель ppfd на структуру PIXELFORMATDESCRIPTOR, которая содержит логическую спецификацию формата и используется компонентами системного метафайла Рассмотрим фрагмент кода, демонстрирующий установку наиболее типич- ных значений формата пикселей.
Особенности реализации OpenGL в Windows NT и Windows 95 // Объявление функции с аргументами, задаваемыми по умолчанию BOOL Create //Класс COpenGL ( CWnd* pWnd, // Работаем с тройками цветов, а не с палитрами int iPixelType = PFD_TYPE_RGBA, DWORD dwFlags = PFD_DOUBLEBUFFER // Двойная буферизация PFD_SUPPORT_OPENGL // Поддержка OpenGL PFD_DRAW_TO_WINDOW // Рисуем в окно ); BOOL COpenGL Create(CWnd* pWnd, int iPixelType, DWORD dwFlags) { // Если не создано окно, то рисовать будет некуда ASSERT(pWnd); // Обязательно нужно указать, в каком виде представляются цвета ASSERT((iPixelType == PFD_TYPE_RGBA) || (iPixelType == PFD_TYPE_COLORINDEX)); // Создаем структуру PIXELFORMATDESCRIPTOR pfd; // Поскольку не все поля этой структуры значимы, // устанавливаем их в О memset(&pfd,0, sizeof(PIXELFORMATDESCRIPTOR)), // Заполняем значимые поля структуры PIXELFORMATDESCRIPTOR // pfd.nSize pfd.nVersion pfd.dwFlags pfd.iPixelType = sizeof(PIXELFORMATDESCRIPTOR), = 1; = dwFlags; = iPixelType; // Номер версии ! Обратите внимание на значения, , передаваемые в следующие 6 поле pfd.cColorBits = 64; // красный, зеленый и синий цвет pfd.cAlphaBits = 64; // компонента альфа цвета pfd.cAccumBits = 64; // буфер аккумулятора pfd.cDepthBits = 64; // буфер глубины pfd.cStencilBits = 64; // трафарет pfd.cStencilBits = 64; // дополнительный буфер pfd.iLayerType = PFD_MAIN_PLANE; // тип плоскости // Создаем контекст устройства // m_pdc = new CClientDC(pWnd) , // Выбираем наиболее подходящий формат пикселей // int nPixelFormat = ChoosePixelFormat (m_pdc->m_hDC, &pfd);
14 Глава 1 if(nPixelFormat == 0){ MessageBox("Ошибка при выборе формата пикселей"); return FALSE; } // После того как от системы получен формат пикселей, наиболее // точно совпадающий с запрошенным, устанавливаем его // BOOL bResult = SetPixelFormat(m_pdc->m_hDC, nPixelFormat, &pfd); if(!bResult){ MessageBox("Ошибка при установке формата пикселей"); return FALSE; } return TRUE; ) Две оставшиеся функции Describe Pixel Format и GetPixelFormat позволяют по- лучить информацию о текущих параметрах и индексе формата пикселей со- ответственно. int DescribePixelFormat( HDC hdc, int iPixelFormat, UINT nBytes, LPPIXELFORMATDESCRIPTOR ppfd) Функция позволяет получить информацию о параметрах формата пикселей, заданного индексом iPixelFormat, для контекста устройства hdc. Полученная информация записывается в структуру PIXELFORMATDESCRIPTOR, опре- деленную указателем ppfd и имеющую размер nBytes (этот параметр проще всего задавать при помощи оператора s\zeoi(PIXELFORMATDESCRIPTORD При успешном завершении функция возвращает максимальный доступный индекс формата пикселей и нулевое значение в противном случае Если требуется определить только максимальное значение индекса, то в качестве параметра ppfd можно использовать NULL int GetPixelFormat(HDC hdc) При успешном завершении функция возвращает индекс текущего формата пикселей в контексте устройства hdc и 0 — в случае неудачи Ниже приведен фрагмент приложения PixFrmt. которое имеется на прила- гаемой дискете и позволяет более подробно познакомиться с текущим (рис. 1.2 и 1.3) и поддерживаемыми форматами пикселей BOOL CPixForm::OnInitDialog() { PIXELFORMATDESCRIPTOR pfd;
Особенности реализации OpenGL в Windows NT и Windows 95 15 CDialog.:OnInitDialog(); Centerwindow(); // Получаем индекс текущего формата пикселей m_nCurIndex = rn_pOGL->m_hrc ? ::GerPixelFormat(ra_pOGL->m_pdc->m_hDC)• 1; // Запрашиваем его параметры if(:.DescribePixelFormat(m_pOGL->m_pdc->m_hDC, m_nCurIndex, sizeof(PIXELFORMATDESCRIPTOR) , &pfd)){ // Отображаехм данные в окне UpdateDlg(&pfd), } return TRUE; Обратите внимание, что при запросе у системы формата пикселей мы уста- новили в полях цвета, глубины и др значение 64. Посмотрев же на приве- денные рисунки, видим, что ни в одном поле такого значения нет Это свя- зано с тем, что формат пикселей предоставляет система, которая только учитывает наши пожелания, но совершенно не обязательно им следует. По- этому в перечисленных полях можно, а в некоторых случаях и предпочти- тельно, указывать более реальные значения Например, если вы точно знае- те, что ваша видеокарта поддерживает только 256 цветов, нет никакого смысла запрашивать у системы под них даже 16 разрядов — все равно не даст. Подобрать приемлемые значения для полей структуры можно, запус- тив приложение PixFrmt на вашем компьютере Рис. 1.2. Основные составляющие формата пикселей
16 Глава 1 tДругие значения полей структуры PIXELFORMATDESCRIPTOR;: Ж........ йЕйЫйпВЙя |?П ijctSeirtShtt 15" ' сВЮ>Й |Г - еВЬЛб» |Г” к~ |§ """ cAccisiftRedBiH |П еАссъ^теёлВкз |ур~~“ cActtwSlueBitr |lO cAccumAbhaWs |g~~~“ фЛлв<етМа$к |0 d^isibleMask |б“ dwDamageMask Рис. 1.3. Дополнительные составляющие формата пикселей |Г"~Ж~11 После того как в контексте устройства установлен формат пикселей, можно приступать к созданию контекста воспроизведения Установка текущего контекста воспроизведения Для работы с контекстом воспроизведения в Win32 API реализованы сле- дующие функции. HGLRC wglCreateContext(HDC hdc) Функция создает новый контекст воспроизведения OpenGL, который под- ходит для рисования на устройстве, определенном дескриптором hdc При успешном завершении функция возвращает дескриптор созданного контек- ста воспроизведения OpenGL, и NULL — в случае неудачи. Создавать можно произвольное число контекстов воспроизведения, но для того чтобы можно было работать с графикой OpenGL, необходимо устано- вить единственный текущий контекст воспроизведения потока Сделать это можно при помощи функции- BOOL wglMakeCurrent( HDC hdc, HGLRC hglrc) Параметр hdc определяет контекст устройства или, в терминах компьютер- ной графики, — поверхность рисования Это не обязательно тот же самый контекст, который использовался при создании контекста воспроизведения, но он должен иметь тот же самый формат пикселей. Второй параметр hglrc определяет контекст воспроизведения OpenGL Перед тем как установить новый текущий контекст воспроизведения, OpenGL сбрасывает предыду- щий контекст воспроизведения и заменяет его на новый Этот механизм задействован для того, чтобы жестко выполнялось требование о том, что
Особенности реализации OpenGL в Windows NT и Windows 95 текущим в потоке может быть только один контекст воспроизведения Бо юе того, попытка сделать один и тот же контекст воспроизведения текущие нескольких потоков приведет к ошибке Приложение может осхшес.’втя.ь рисование в нескольких потоках При этом оно должно использовать ня каждого из них различные текущие контексты воспроизведения Самое удобное место для создания контекста воспроизведения — это обрабо* чик сообщения WM_CREATE, где эти две функции обычно и исполыую,^ BOOL COpenGL::Create(CWnd* pWnd, int iPixelType, DWORD dwFlags; { // Создаем и делаем текущим контекст воспроизведения if(CreateGLContext(m_pdc)==FALSE) return -1; return 0; } BOOL CPnmView:: CreateGLContext (HDC hdc) ( HGLRC hrc; // Создаем контекст воспроизведения if((hrc =::wglCreateContext(m_pdc->m_hDC)) == NULL) //He удалось создать контекст воспроизведения return FALSE; // Делаем контекст воспроизведения текущим if(::wglMakeCurrent(hdc, hrc) == FALSE) return FALSE; return TRUE; } При желании можно сделать так, что ни один из контекстов воспроизведе- ния не будет текущим. Эта ситуация возникает, например, перед заверше- нием работы — прежде чем удалить контекст воспроизведения, необходимо, чтобы он никем не использовался Для этого достаточно выполни!ь сле- дующий вызов ::wglMakeCurrent(NULL, NULL); Завершение работы с OpenGL Как уже не раз отмечалось, одним из правил программирования для Win- dows является следующее прежде чем закончить работу — "убери" за собой В применении к рассматриваемому вопросу это означает, что пере i оконча-
18 Глава 1 нием работы с потоком необходимо удалить контекст воспроизведения Для этой цели используется функция’ BOOL wglDeleteContext(HGLRC hglrc) В качестве параметра hglrc используется дескриптор контекста воспроизве- дения, который необходимо удалить. После того как удален контекст воспроизведения, следует удалить и ассо- циированный с ним контекст устройства. Текущий контекст воспроизведения можно узнать, если воспользоваться функцией. HGLRC wglGetCurrentContext() которая возвращает дескриптор текущего контекста воспроизведения или NULL, если такового нет Дескриптор контекста устройства, ассоциированного с контекстом воспро- изведения можно получить при помощи функции HDC wgIGetCurrentDCO Если поток не имеет текущего контекста воспроизведения, функция воз- вращает NULL. Ниже приведен фрагмент кода, иллюстрирующий корректное завершение работы с OpenGL. void COpenGLView::Destroy() { if(m_hrc){ // Запрашиваем текущий контекст воспроизведения if(m_hrc ==::wglGetCurrentContext()) // Делаем его не текущим ::wglMakeCurrent(NULL,NULL); // Удаляем контекст воспроизведения ::wglDeleteContext(m_hrc); m_hrc = NULL; } // Удаляем контекст рабочей области if (m_pdc) delete m_pdc; // Для корректного завершения работы // передаем управления вверх по иерархии CView::OnDestroy();
Особенности реализации OpenGL в Windows NT и Windows 95 19 Теперь можно подвести некоторый итог (рис 1 4) Рис. 1.4. Структура приложения, использующего OpenGL Для того чтобы можно было работать с OpenGL в Windows NT и Windows 95, необходимо выполнить следующие требования 1 . В функции PreCreateWindow установить для окна стили WS_ CLIPCHILDREN и WS_CLIPSIBLINGS 2 В обработчике сообщения WM_CREATE необходимо установить формат пикселей (функции ChoosePixelFormat и SetPixelFormaf), создать контекст воспроизведения (функция wglCreateContext) и сделать его текущим (функция wglMakeCurreni) 3 В обработчике сообщения WM_DESTROY обеспечить удаление контек- ста воспроизведения (функции wglMakeCurreni и wglDeleteContext) Кроме перечисленных выше действий необходимо подключить к проекту библиотеки openg!32 lib и glu32 lib, а также включить заголовочные файлы gl\gl h и gl\glu h Лучше всего это сделать в файле stdafx h Еще раз повторим — это обязательные действия, которые должны выпол- няться в любой программе, работающей с OpenGL1 На дискете, прилагаемой к книге, находится проект приложения для создания собственного мастера (wizard), который будет включен в список доступных проектов и позволит автомати- зировать создание каркаса приложения для работы с OpenGL Единственное, что необходи- мо будет сделать, — это подключить к вновь созданному проекту необходимые библиотеки OpenGL Для этого нужно в аналоговом окне установок Pioject Settings выбрать вкладку Link и ввести имена библиотек openg!32 lib и glu32 lib в поле Object/libiary modules
20 Глава 2 Структура консольного приложения Windows для работы с OpenGL Для тех, кто не знаком с основами программирования под Windows, библио- тека предоставляет прекрасную возможность не задумываться об этом Дос- таточно усвоить несколько простых правил, и можно создавать прекрасные приложения, использующие все возможности OpenGL Любая программа на языке С начинается с выполнения функции main() С нее-то мы и начнем void main(int argc, char **argv) { // Если приложение получает дополнительную входную информацию, // необходимо предусмотреть разбор командной строки if(Args(argc, argv) == GL_FALSE){ // Параметры были заданы некорректно — завершаем работу auxQuit();} // Задаем размеры окна, в котором будем рисовать windW — 300; // ширина windH = 300; // высота // Определяем, где будет располагаться окно aux!nitPositior (250, 100, windW, windH); // Поручаем библиотеке произвести необходимые установки // для работы с OpenGL auxInitDisplayMode(AUX_RGB | AUX_DOUBLE); // Выводим окно на экран, определив его заголовок, // который несет дополнительную информацию о приложении if(auxInitWindow("Текст заголовка окна") == GL_FALSE){ //На этот раз не удалось поработать — нужно корректно // завершить работу auxQuit(); }
Структура консольного приложения Windows для работы с OpenGL_____ 21 // Проводим необходимые инициализирующие действия Init(); // Определяем функцию, которая будет вызываться при изменении // размеров окна . . auxReshapeFunc((AUXRESHAPEPROC)Reshape); // .. и, наконец, ту функцию, которая отвечает за рисование auxMainLoop(Draw); } Вряд ли кто-нибудь записывал более короткую функцию main, коюрая к тому же обеспечивает работу достаточно сложных приложений для Windowь И хотя приведенные комментарии разъясняют назначение используемых функций, рассмотрим их более подробно 1 Анализ командной строки Если вы не хотите усложнять программу, пре- доставляя пользователям возможности выбора в процессе работы, по- звольте им сделать это хотя бы при запуске Например, разрешиie ука- зать файл, в котором хранится образ текстуры 2 Параметры окна Поскольку консольное приложение в своей видимой части представляет собой окно, то необходимо определить его размеры и положение на экране Если не сделать этого, библиотека предложит своп параметры После того как размеры и положение окна определены, нуж- но зарегистрировать окно в системе — без этого в Windows не может ра- ботать ни одно приложение Как вы увидите дальше, это можно сделать самим, но и библиотека прекрасно справляется с этой задачей (функци . auxInitWindow) 3 Инициализирующие действия Здесь никто вам не поможет, ведь ни одна самая "умная" система не может знать, что придет в голову тому или иному программисту Ниже описаны некоторые действия, необходимые для корректной работы с библиотекой 4 Изменение размеров окна Вкусы у людей разные Одним нравятся ма- ленькие "окошки", а другие предпочитают развернуть окно на весь экран Чтобы не ущемлять ни тех, ни других, необходимо определить функцию которая будет внимательно следить за этим и при изменении размера да- же на пиксель сразу же предпримет необходимые действия Тем, кт о зна- ком с программированием для Windows, ясно, что речь идет о действиях, выполняемых в ответ на системное сообщение WM_SIZE Здесь мы оп- ределяем эту функцию, вызывая auxReshapeFunc 5 . И, наконец, необходимо указать функцию, выполняющую то, ради чего и писалась программа, — собственно рисование Как и в предыдущем слу- чае, сделать это очень легко — вызвать auxMainLoop с именем нужной функции в качестве аргумента (Она будет выполняться каждый раз, ко- гда приложение получит системное сообщение WM_PAINT ) Вот собственно и все — задайте параметры окна, определите две-три функ- ции и получите готовое приложение Естественно, что эта "легкость" чем-то
22 Гпава 2 обеспечивается. И это "что-то" — библиотека GLAUX, позволяющая не вда- ваться в подробности организации приложений под Windows И поскольку в справочной системе она совершенно не описана, есть смысл рассмотреть некоторые основные ее функции подробно Начнем с тех, которые мы встретили в функции main void APIENTRY auxlnitPosition( jnt x, // Х-координата левого нижнего угла окна jnt у, И Y-координата левого нижнего угла окна jnt сх, // Ширина окна int су) И Высота окна Эта функция позволяет определить размеры и положение окна на экране Если вызов этой функции опустить, система присвоит параметрам окна зна- чения по умолчанию GLenum auxlnitWindow( LPCTSTR pStr) И Указатель на текстовую строку, определяющую заголовок окна Эта функция осуществляет все необходимые действия по регистрации окна в операционной системе и принимает в качестве аргумента строку, содер- жащую текст, который появится в заголовке окна Без вызова этой функции дальнейшая работа с OpenGL невозможна Функция возвращает значение GL_TRUE, если окно успешно проинициализировано и зарегистрировано, и GL_FALSE в противном случае void auxQuitO Эта функция осуществляет корректное завершение работы приложения Для понимания назначения следующей группы функций необходимо пред- ставлять себе, что в отличие от традиционного последовательного програм- мирования любое приложение, написанное под Windows, управляется сооб- щениями. Сообщение — это вход в приложение Оно представляет собой событие, на которое, при необходимости, должно реагировать приложение Происходит это через операционную систему, которая перенаправляет со- общения нужному окну Прием сообщений приложением осуществляется при помощи специальной функции, называемой оконной процедурой, которая передает управление специальной функции, вызываемой для обслуживания сообщения Использование библиотеки GLAUX позволяет освободить про- граммиста от работы по организации оконной процедуры, предоставив ему взамен возможность определения нескольких точек входа для обработки ос- новных типов событий, к каковым относятся сообщения от мыши и клавиа- туры, изменение размеров окна и необходимость перерисовки его содержи- мого, а также "обработка простоя", т е выполнение некоторых фоновых действий в моменты, когда приложение "ничего не делает" Для регистрации перечисленных функций-обработчиков в библиотеке реализованы специ- альные функции, которые перечислены ниже
Структура консольного приложения Windows для работы с OpenGL___ 23 void auxReshapeFunc( AUXRESHAPEPROC)( И Указатель на функцию, вызываемую при изменении И размеров окна int width, И "Новая" ширина окна в пикселях int height) // "Новая" высота окна в пикселях Эта функция позволяет определить ту функцию, которая будет вызываться всякий раз при изменении размеров окна приложения void auxMainLoop( AUXMAINPROC)() И Указатель на функцию, выполняющую роль оконной процедуры В простейших случаях в качестве аргумента этой функции достаточно ука- зать имя функции, которая отвечает за "рисование в окне", как это сделано, например, в приведенном выше фрагменте В более сложных программах здесь можно указать имя функции, которая сама будет обрабатывать посту- пающие сообщения и распределять их по соответствующим обработчикам void auxldleFunc( AUXIDLEPROC)() И Указатель на функцию, работающую в "фоновом" режиме Наиболее часто "фоновую" функцию используют для проведения некоторых вспомогательных действий, которые можно осуществлять в моменты време- ни, когда приложение "ничего не делает" void auxKeyFunc( int key, И Код клавиши AUXKEYPROC)() И Указатель на функцию, // которая должна быть выполнена при нажатии клавиши <кеу> Эта функция позволяет определить процедуру обработки нажатия некоторой конкретной клавиши (key) на клавиатуре Если требуется обрабатывать на- жатия нескольких клавиш, для каждой из них следует вызвать рассматри- ваемую функцию При этом процедура обработки может быть как общей для всех, так и отдельной для каждой клавиши void auxMouseFunc( int button, // Код кнопки мыши int action, И Код действия AUXMOUSEPROC)() И Указатель на процедуру обработки, И которая должна быть выполнена при нажатии клавиши <button> Эта функция обеспечивает подключение мыши к приложению Аргумент button определяет, на нажатие какой кнопки мыши реагирует процедура обработки Возможны следующие значения AUXLEFTBUTTON — ле- вая кнопка мыши, AUXRIGHTBUTTON — правая кнопка и AUX_MIDDLEBUTTON — средняя кнопка мыши Аргумент action задает характер действия — нажата кнопка (AUXMOUSEDOWN) или отпущена (AUXMOUSEUP), или же осуществляется передвижение мыши при нажа- той кнопке (AUXMOUSEMOVE) Процедура обработки (третий ар1умент функции) имеет следующий прототип void CALLBACK FunctionName(AUX_EVENTREC *event)
24 Гпава 2 где event — указатель на структуру typedef struct _AUX_EVENTREC { GLint event; GLmt data[4]; } AUX_EVENTREC; содержащую код события и ассоциированные с ним данные Для работы с мышью данные определяют текущие координаты курсора — AUXMOUSEX и AUX_MOUSEY Все представленные выше функции обеспечивают организацию корректного взаимодействия приложения с операционной системой И вполне может создаться впечатление, что библиотеки OpenGL не имеют к этому никакого отношения Чтобы несколько развеять это впечатление (ведь нас в первую очередь интересует трехмерная графика), приведем еше одну функцию void auxlnitDisplayMode( GLenum mode) И Режим работы графической подсистемы Windows И для корректного взаимодействия с OpenGL Эта функция позволяет установить определенный режим, в котором будет работать OpenGL Более подробно возможные режимы будут рассмотрены ниже, здесь же упомянем только некоторые из них: задание цветов с ис- пользованием RGB-значений или через палитру, будет ли осуществляться работа с буфером глубины, будет ли изображение рисоваться непосредст- венно на экране или сначала в специальном буфере и т д Параметры, ис- пользованные в приведенном фрагменте функции main, определяют, что в качестве цветов будут использоваться тройки RGB, и рисование будет про- изводиться сначала в фоновый буфер, и только после того, как изображение будет полностью подготовлено, оно будет выведено на экран Помимо пред- ставленных функций, отвечающих за организацию работы приложения, в библиотеку включены также некоторые другие, из которых наибольший ин- терес представляют функции, рисующие геометрические объекты, и функ- ция, позволяющая загрузить образ текстуры из файла формата DIB или BMP AUX_RGBImageRec *auxDIBImageLoad( LPCTSTR pFileName) // Указатель на текстовую строку, определяющую имя файла И в котором хранится графический образ Эта функция загружает в специальную структуру typedef struct _AUX_RGBImageRec{ int sizeX, sizeY; // Ширина и высота графического образа unsigned char *data; // Указатель на область памяти, где хранятся сами данные } AUX_RGBImageRec; данные из файла, определяющие графический образ и его параметры, такие как ширина и высота Использование этой функции позволяет существенно
Структура консольного приложения Windows для работы с OpenGL 25 упростить получение образа, например, для текстуры В противном случае необходимо самостоятельно заниматься преобразованием данных из форма- та DIB (или BMP) во внутренний формат OpenGL Каждая из функций, рисующих некоторый геометрический объект, пред- ставлена в двух вариантах Solid — рисование сплошного закрашенного объ- екта, Wire — рисование каркасной модели объекта Аргументы для обоих вариантов функций одни и те же, поэтому вместо имен Solid/Wire в описа- нии функций будем использовать обозначение [*] void aux[*]Sphere(GLdouble radius) — рисует сферу заданного радиуса. void aux[*]Cube(GLdouble width) — рисует куб определенного размера void aux[*]Box(GLdouble width, GLdouble height, GLdouble depth) — рисует параллелепипед, размеры которого задаются соответствующими ар- гументами void aux[*]Torus(GLdouble minRadius, GLdouble maxRadius) — рисует тор, внутренний и внешний диаметры которого определяются соот- ветствующими аргументами .void aux[*]Cylinder(GLdouble radius, GLdouble height), void aux[*]Cone(GLdouble radius, GLdouble height) — рисуют цилиндр и конус, основания которых определяются аргументом ra- dius, а высота — аргументом height void aux[*]lcosahedron(GLdouble width), void aux[*]Octahedron(GLdouble width), void aux[*]Tetrahedron(GLdouble width), void aux[*]Dodecahedron(GLdouble width) Перечисленные функции позволяют изобразить соответственно икосаэдр, октаэдр, тетраэдр и додекаэдр, стороны которых определяются аргументом width void aux[*]Teapot(GLdouble dim) — рисует чайник, что во многих случаях позволяет придать привлекательность тестовым примерам Описание нескольких оставшихся функций вспомогательной библиотеки GLAUX можно найти в файле glaux h И, наконец, последнее, что необходимо сделать для того, чтобы можно бт то получить исполняемый модуль приложения. Необходимо подключить к
26 Гпава 2 программе библиотеки OpenGL и передать компилятору определенные ин- струкции. Шаблон соответствующего файла приведен ниже TARGETOS=WINNT 1 include <ntwin32.mak>. opengllibs = opengl32 lib glu32 lib glaux.lib EXES = Приложение!.exe \ Приложение2 exe \ Приложение!! exe all $(EXES) c exe- $(cc) $(cflags) $(cdebug) $(cvars) $< $(link) $(linkdebug) $(guiflags) -subsystem windows - entry mainCRTStartup -out.$*.exe $* obj $(opengllios) $(guilibs) Обратите внимание на строки, выделенные полужирным шрифтом Для по- лучения полноценного оконного приложения они должны быть именно та- кими Для тех, кто работает в среде Developer Studio, указанные параметры необходимо установить в диалоговом окне Settings, выбрав вкладку Link В заключение приведем полный текст шаблона, общая структура которого приведена на рис 2 1 Заполняя этот шаблон мы в дальнейшем будем созда- вать разнообразные приложения //////Ш//////////////////////////////////////////////// // Модуль Basis h // Copyright (с) 1998 Тихомиров Ю ///////////////////////////////////////////////, ///// /// / Рис. 2.1. Структура консольного приложения Windows для работы с OpenGL // Для работы в Windows необходимо включить этот файл 4include <windows h> // Подключаем библиотеки OpenGL #include <gl\gl h> ^include <gl\glu h>
Структура консольного приложения Windows для работы с OpenGL 27 // Это подключение требуется, в основном, // только для рассматриваемого типа приложений #include <gl\glaux h> // Чтобы не получать "лишних" предупреждений о неприведении типов, // отключаем их вывод ((pragma warning(disable• 4305) // MIPS #pragma warning(disable: 4244) // MIPS III III///////////////////////////////III/// / / / /////////// // Модуль Basis.c // Copyright (c) 1998 Тихомиров Ю /////////////////////////////////////////////III///////// ((include "basis.h" static void Init(void); static void CALLBACK Reshape(int width,int height); static void CALLBACK Draw(void); // Глобальные переменные для хранения текущих размеров окна GLint windW, windH; static void Init(void) { // Здесь располагаем необходимые инициализирующие действия } static void CALLBACK Reshape(int width, int height) { windW = (GLint)width, windH = (GLint)height, // Здесь будем отслеживать изменения размеров окна } static void CALLBACK Draw(void) { // Здесь вызываются команды рисования } void main(int argc, char **argv) { // Эта функция подробно рассмотрена выше } Скомпилировав и запустив приведенную программу, увидим на экране ок- но, показанное на рис 2 2 Осталось только наполнить приведенный шаблон необходимым содержани- ем. И для этого потребуется знакомство с собственно библиотекой OpenGL, к рассмотрению которой можно было бы перейти, если бы не одно обстоя-
Гпава 2 гельсгво Дело в том, что мы не учли интересов большей части программи- стов Ведь как отмечалось в начале главы, "подключение" OpenGL к Win- dows необходимо как для уже рассмотренных консольных приложений, так и для полноценных приложений Windows И чтобы можно было завершить эту тему и обратиться непосредственно к программированию трехмерной графики, покажем, что и как можно настраивать при подключении OpenGL к операционной системе Windows Для этого переходим к следующей главе Рис. 2.2. Общий вид окна шаблона консольного приложения Гем из читателей, кто еще не знаком с программированием для Windows, хогеиось бы порекомендовать не пропускать эту главу, так как она позволит существенно сократить "период адаптации" при переходе к программирова- нию приложений, управляемых событиями
29 Глава 3 Вершины, примитивы... Архитектура OpenGL После того как мы "что-то" подключили к операционной системе и выясни- ли, что можно начинать работать, самое время узнать, что же нам теперь доступно Ведь все, о чем мы говорили до сих пор, имеет к OpenGL весьма отдаленное отношение Если использовать эту библиотеку в другой опера- ционной системе, то весь предыдущий материал окажется бесполезным Но мы используем Windows (не стоит каждый раз повторять Windows 95 и Win- dows NT, это не добавляет новых сведений, а только отнимает место у более интересного материала) и поэтому "подстраиваемся" именно под эту опера- ционную систему. Нетерпеливый читатель наверняка подумал об авторе: "Пишет об экономии места, а сам его бездумно тратит" Действительно, хватит обших слов, пора переходить к делу А на деле все обстоит не так "страшно", как может показаться на первый взгляд. В самом общем случае можно сказать, что OpenGL представляет со- бой обычный набор команд Они определяют координаты объектов, управ- ляют их трансформацией, позволяют задать параметры объектов (такие, на- пример, как цвета) и, наконец, "рисуют" его Все это собрано в некоторую единую структуру, которую разработчики назвали конвейером OpenGL (рис 3 1) Это — практически полная структура OpenGL И хотя, даже глядя на рису- нок трудно представить себе последовательную обработку (я, например, так воспринимаю слово "конвейер"), примем за основу эту терминологию Особняком здесь показан буфер кадра Сделано это для того, чтобы под- черкнуть его особое положение Дело в том, что OpenGL может как записы- вать туда подготовленные данные, так и читать их Однако настраивается буфер кадра совсем не в OpenGL, а в самой операционной системе, этот процесс рассматривался ранее, в первой и второй главах книги Мы же пе- реходим к блокам, обведенным пунктирной линией
30 Гпава 3 Рис. 3.1. Общая структура OpenGL Аппроксимация кривых и поверхностей Название этого блока говорит само за себя и дает надежду, что, выполнив несколько команд, можно будет постро- ить самую замысловатую фигуру Но об этом говорить несколько прежде- временно, т к мы еще "не умеем" рисовать даже точки и прямые линии Обработка вершин и сборка примитивов На мой взгляд это едва ли не основ- ной блок во всей библиотеке OpenGL Научившись пользоваться только его командами, уже можно будет создавать достаточно интересные и сложные композиции Сейчас главное — уяснить только, что можно получить неко- торый набор вершин, из которых образуется нечто единое и неделимое, на- зываемое примитивом Таких примитивов в OpenGL всего десять (и еще один для готовых картинок) Растеризация и обработка фрагментов Двигаясь последовательно мы при- шли к тому, что из примитивов можно создавать некоторые фрагменты, т е уже что-то близкое к интересующим нас фигурам Но перед этим примити- вы подвергаются некоторой операции называемой растеризацией Это необ- ходимый процесс, при котором формируется последовательность адресов буфера кадра и ассоциированных с ними значении с использованием дву- мерного описания примитива Другими словами, на этом этапе осуществля- ется преобразование трехмерного примитива в двумерный! с пересылкой до- полнительных параметров (чтобы не потерять информацию) в определенные области памяти Операции над пикселями Любой графический вывод на экран дисплея свя- зан с подсвечиванием пикселей — его единичных элементов Информация о том, как именно будет "светиться" пиксель, может поступить как из преды- дущих рассмотренных этапов, так и непосредственно со входа OpenGL По- следнее происходит, например, при отображении одиннадцатого примитива
Вершины, примитивы 31 OpenGL — битового массива, а также при использовании текстуры — очень интересной возможности компьютерной графики, позволяющей достаточно простыми средствами существенно повысить реалистичность изображения Подготовка списков изображений За один этот блок я бы поставил памятник разработчикам библиотеки. Чрезвычайно удобное и полезное средство Подготовив один раз некоторое изображение (какие из ранее описанных блоков будут при этом задействованы, зависит только от вашего желания и фантазии), вы получаете, условно говоря, набор пикселей с сопутствующей информацией, который в нужное время достаточно будет переслать в буфер кадра для отображения на экране И осуществляется это вызовом одной единственной команды, о чем будет сказано позже Перед тем как перейти к собственно рисованию, нам осталось договориться о некоторых условных обозначениях, которые будут применяться в остав- шейся части книги Синтаксис команд OpenGL Командами в OpenGL называются функции или процедуры Уже говорилось, что имеется достаточно большая группа команд, отмеченных звездочкой, которые реализованы в нескольких вариантах, различающихся числом и ти- пами аргументов Для описания таких команд будем использовать следую- щий синтаксис1. rtype CommandName[1 2 3 4][b s i f d ub us ui][v] (atype arg) Команда представляется четырьмя компонентами — именем и тремя симво- лами- CommandName [1 234] [b s I f d ub us ui] Имя команды, такое как gIVertex млм glColor. Цифра, показывающая число аргументов команды Символы, определяющие тип аргумента Допустимы следующие типы аргументов Символ Тип OpenGL2 Тип С (C++) Ь GLbyte char S GLshort short i GLint mt 1 Сокращенное обозначение команд, которое применяется в справочной системе, представля- ется не совсем удобным, т к не несет информации о возможных вариантах 2 Типы данных OpenGL введены для облегчения переносимости приложений Полный список можно посмотреть в файле gl h
32 Гпава 3 (продолжение) Символ Тип OpenGL Тип C (C++) f GLfloat float d GLdouble double ub GLubyte unsigned byte us GLushort unsigned short Ul GLumt unsigned mt [v] Буква, показывающая, что в качестве аргумента используется указатель на массив значений Параметр rtype определяет тип возвращаемого значения и для каждой коман- ды указывается в явном виде Параметр atype и аргумент(ы) arg определяются типом и числом аргументов соответственно Чтобы сказанное было более понятно, приведем два примера из списка воз- можных команд для определения вершин Команды void glVertex3f( void glVertex3s( float arg 1, и short arg 1, float arg2, short arg2, float arg3) short arg3) в представленном синтаксисе записываются в виде void glVertex3[f s](atype arg) В заключение скажем, что компоненты, представленные в квадратных скоб- ках. не во всех случаях являются обязательными Вершины и система координат Вершина — это точка в трехмерном пространстве Для ее определения в биб- лиотеке реализована специальная команда: void glVertex[2 3 4][s i f d](type coord) void glVertex[2 3 4][s i f d]v(type coord) Вызов любой команды gIVertex* определяется четырьмя координатами х, у, z и w При этом соблюдается следующее соглашение вызов gIVertex2* задает координаты х и у, координата z полагается равной 0, a w— 1, вызов glVertexo* задает координаты х, у и z, a w полагается равной 1; вызов glVertex4* задает все четыре координаты
Вершины, примитивы Здесь нам не обойтись без некоторого отступления, связанного с системой координат и некоторыми геометрическими понятиями Поскольку нас пре- жде всего интересует изображение трехмерных "картинок", то и рассматри- вать мы будем именно трехмерную систему Посмотрите на рис 3 2 Для определения некоторой точки в трехмерном пространстве необходимо ука- зать три ее координаты х, у и z Соединив начало координат О с точкой V, получим вектор — направленный отрезок прямой линии, соединяющий две точки и характеризуемый только длиной и направлением Математически вектор описывается одним из двух способов ||х у z|| или |у| Но это все, конечно же, вам знакомо из школьного курса геометрии И я напоминаю об этом только для того, чтобы пояснить наличие разного числа аргументов у рассматриваемой команды Причем здесь четыре координаты, если мы хотим создать трехмерный объект? Действительно, для описания трехмерного объекта достаточно трех координат (рис 3 2), и все будет "хорошо" до того момента, пока не возникнет желание создать, например, реалистичное изображение объекта, для чего потребуется учитывать влияние перспективы Для этой, а также для некоторых других целей, о которых речь пойдет дальше, было предложено описывать точку на плоскости трехэле- ментным вектором, а в пространстве — четырехэлементным В этом случае говорят, что точка представлена однородными координатами Более подробно об этом мы поговорим при рассмотрении преобразований объектов, а пока запомним, что по умолчанию вершина на плоскости описывается вектором ||х у 0 1||, а в пространстве — вектором ||х у z 1| Для того чтобы каждый раз не задумываться об этом, и реализованы раз- личные варианты команды gIVertex* До более подробного знакомства с од- нородными координатами мы будем использовать две версии этой коман- ды — gl Vertex? и gl Vertex? Рис. 3.2. Трехмерная ортогональная система координат
34 Гпава 3 Казалось бы, определяй вершины — и получишь требуемый объект Но по- скольку изолированные вершины, которые к тому же не имеют даже цвета, никому не нужны, разработчики библиотеки ввели понятие примитива Только примитивы и можно рисовать Примитивы OpenGL Примитив — это фигура, такая как точка, линия, многоугольник, прямо- угольник пикселей или битовый массив, которая рисуется, хранится и кото- рой манипулируют как единой дискретной сущностью Другими словами, примитивы — это те элементы, из которых строятся графические объекты любой степени сложности Они определяются группами из одной или не- скольких вершин, каждая из которых имеет ассоциированные с ней данные □ Текущий цвет — задает цвет, который вместе с условиями освещения оп- ределяет результирующий цвет вершины Цвет задается командами glColoi* для режима RGBA и glIndex* для индексного режима □ Текущая позиция растра — используется для определения координат рас- тра при работе с пикселями и битовыми массивами Задается командой glRasterPos* □ Текущая нормаль — определяет вектор нормали, ассоциированный с от- дельной вершиной, и задает ориентацию содержащей ее поверхности в трехмерном пространстве Для указания нормали в OpenGL реализована команда glNormal* □ Текущие координаты текстуры — определяют местоположение в карте текстуры, которая ассоциируется с вершиной объекта. Задаются коман- дой glTexCoord* Примитив или группа однотипных примитивов, к которым относятся точки, линии, связанные линии, замкнутые линии, треугольники, связанные тре- угольники с общим ребром, связанные треугольники с обшей вершиной, четырехугольники, связанные четырехугольники и многоугольники, опреде- ляются внутри командных скобок glBegin/glEnd\ void glBegin(GLenum mode) void glEnd() Параметр mode определяет примитивы, которые создаются из вершин, опреде- ленных между этими командами Доступными являются следующие символь- ные константы (л — номер текущей вершины, a N — общее число вершин) Значение mode Описание GL_POINTS Каждая вершина рассматривается как отдельная точка, параметры которой не зависят от параметров осталь- ных заданных точек При этом вершина п определяет точку п Рисуется N точек
Вершины, примитивы 35 (продолжение) Значение mode Описание GLLINES Каждая пара вершин рассматривается как независи- мый отрезок Первые две вершины определяют первый отрезок, следующие две — второй отрезок и т д, вер- шины (2п -1) и 2п определяют отрезок п Всего рисует- ся N/2 линий Если число вершин нечетно, то последняя просто игнорируется GL_LINE_STRIP В этом режиме рисуется последовательность из одного или нескольких связанных отрезков Первая вершина задает начало первого отрезка, а вторая — конец пер- вого, который является также началом второго В об- щем случае, вершина п (п > 1) определяет начало от- резка п и конец отрезка (л— 1) Всего рисуется (N-1) отрезок GLJJNE.LOOP Этот режим аналогичен предыдущему (GL_LINE_STRIP) за исключением того, что последняя вершина является началом отрезка, концом которого служит первая вер- шина Вершины л и (п+1) определяют отрезок л При этом последний отрезок определяется вершинами N и 1. Всего рисуется N отрезков GLTRIANGLES Каждая тройка вершин рассматривается как независи- мый треугольник Вершины (Зп-2), (Зп-1) и Зп (в таком порядке) определяют треугольник л Если число вер- шин не кратно 3, то оставшиеся (одна или две) верши- ны игнорируются Всего рисуется N/3 треугольника GL_TRIANGLE_STRIP В этом режиме рисуется группа связанных треугольни- ков, имеющих общую грань. Первые три вершины опре- деляют первый треугольник, вторая, третья и четвер- тая — второй и т д Для нечетного л вершины л, (п+1) и (п+2) определят треугольник л Для четного л треуголь- ник определяет вершины (п+1), п и (п+2) Всего рисуется (N-2) треугольника GL_TRIANGLE_FAN В этом режиме также рисуется группа связанных тре- угольников Первые три вершины определяют первый треугольник; первая, третья и четвертая — второй и т д Вершины 1, (п+1) и (п+2) определяют треугольник л Всего рисуется (N-2) треугольника GL.QUADS Каждая группа из четырех вершин рассматривается как независимый четырехугольник Вершины (4п -3), (4п-2), (4п -1) и 4п определяют четырехугольник л Если число вершин не кратно 4, то оставшиеся (одна, две или три) вершины игнорируются Всего рисуется N/4 четырех- угольника
36 Глава 3 (продолжение) Значение mode Описание GL.QUAD.STRIP Рисует связанную группу четырехугольников Первые четыре вершины определяют первый четырехугольник, третья, четвертая, пятая и шестая вершины — второй и т д Вершины (2п-1), 2п, (2п+2) и (2п+1) (в таком поряд- ке) определяют четырехугольник п Обратите внимание, что порядок, в котором вершины используются для по- строения последовательности четырехугольников, от- личается от того, который используется для построения независимых четырехугольников Всего рисуется (N-2)/2 четырехугольника GL.POLYGON Рисует отдельный выпуклый многоугольник, заданный вершинами от 1 до N При построении примитивов есть некоторые условия на минимальное число вершин — если для определенного примитива число вершин меньше тре- буемого, то примитив игнорируется Минимальное число вершин в зависи- мости от типа примитива представлено в таблице Тип примитива Минимальное число вершин GL.POINTS 1 GL.LINES 2 GL.LINE.STRIP 2 GL.LINE.LOOP 2 GL.TRI ANGLES 3 GL.TRIANGLE.STRIP 3 GL.TRIANGLE.FAN 3 GL.QUADS 4 GL.QUAD.STRIP 4 GL.POLYGON 3 Чтобы закончить с примитивами и перейти к непосредственно программи- рованию, осталось изучить рис 3 3, на котором представлены основные этапы обработки вершин и ассоциированных с ними текущих значений Мы, естественно, рассмотрим все представленные компоненты, но делать это будем постепенно, по мере знакомства с библиотекой и в соответствии с нашими потребностями Начнем же с подробного изучения имеющихся примитивов
Вершины, примитивы 37 Рис. 3.3. Основные этапы обработки вершин и ассоциированных с ними значений Точки Создать точку очень легко Например, приведенный ниже фрагмент создает и отображает 9 точек различного размера (Фрагмент взят из программы Dots, которая имеется на прилагаемой дискете )1 static void CALLBACK DrawScene(void) { for(int i = 0; i < 10; 1++){ // Задаем размер текущей точки glPointSize((i+l) * 2); // Открываем командную скобку для точек glBegin(GL_POINTS), // Создаем примитив "точка" на плоскости glVertex2f(-windW/4 + (GLfloat)i * windW/20, -windH/2 + (GLfloat)i * windH/10); // Закрываем командную скобку glEnd(); При написании примеров к этой главе я решил остановиться на использовании "классического С", чтобы все было понятно и тем, кто пока еще не программирует на C++ Мне кажется, это не явится препятствием для программистов на C++, тогда как обратный переход потребует значительно больших усилий
38 Гпава 3 Результат выполнения программы показан на рис 3 4 Рис. 3.4. Результат работы программы Dots Рассмотрим эту программу подробнее. Для этого есть несколько причин Во-первых, из приведенного фрагмента совершенно не очевиден результат, представленный на рис 3.4. Во-вторых, для того чтобы нарисовать представ- ленные точки, а в дальнейшем и другие примитивы и фигуры, необходимо выполнить некоторые обязательные действия При рассмотрении вопросов подключения OpenGL к Windows говорилось, что необходимо определить функции инициализации, отслеживания изменения размеров и отображения в окне. ///////////////////////////////////////////////////////// // Модуль Dots.h // Copyright (с) 1998 Тихомиров Ю. ///////////////////////////////////////////////////////// // Инициализация: в большинстве случаев достаточно определить // цвет, которым будут заполняться буферы кадра при очистке static void Init(void) { // Определяем светло-серый цвет glClearColor(0.75f, 0.75f, 0.75f, l.Of); } // Отслеживание размеров: в большинстве случаев здесь задаются // параметры области вывода и устанавливаются начальные // характеристики матриц проекций и видового преобразования static void CALLBACK Reshape(int width, int height) { // Отслеживаем текущие размеры окна. Остальные перечисленные // параметры здесь устанавливать бесполезно, т. к осуществляется // вывод в две различные области одного окна windW = (GLint)width; // ширина окна windH = (GLint)height; // высота окна
Вершины, примитивы // Рисование: Основная функция любой программы static void CALLBACK Draw(void) I // Очищаем буферы цвета glClear(GL_COLOR_BUFFER_BIT); // Устанавливаем параметры области вывода и преобразований InitViewport(О,О, windW/2, windH), // Устанавливаем значение текущего цвета glColor3f(0.О, 0 0, 1.0); // Рисуем в прямоугольник (0,0, windW/2, windH) DrawScene(); // Устанавливаем параметры области вывода и преобразований InitViewport(windW/2, 0, windW/2, windH); // Разрешаем проведение действий по устранению // "зазубренности" или лестничного эффекта glEnable(GL_POINT_SMOOTH); // Переустанавливаем значение текущего цвета glColor3f(1.0, 0.0, 0.0); // Рисуем в прямоугольник (windW/2, 0, windW/2, windH) DrawScene(); // Блокируем режим устранения ступенчатости glDisable(GL_POINT_SMOOTH), // Прежде чем двигаться дальше, необходимо, чтобы закончилось // выполнение всех вызванных команд OpenGL glFinish(); // Поскольку все рисование осуществлялось во внеэкранный // буфер кадра, необходимо переместить его содержимое на экра auxSwapBuffers(); } // Установка параметров области вывода static void CALLBACK InitViewport(int x, int y, int width, int height) { // Назначение приведенных ниже команд OpenGL // будет рассмотрено позднее Пока достаточно перенести // эту часть кода в вашу программу glViewport(х, у, width, height); glMatrixMode(GL_PROJECTION), glLoadldentity(), gluOrtho2D(-windW/4, windW/4, -windH/2, windH/2); glMatrixMode(GL_MODELVIEW);
40 Глава 3 Приведенные комментарии в сочетании с материалом из главы 1 (или главы 2) избавляют от необходимости развернутых пояснений Поэтому буду краток: 1. После того как установлен формат пикселей, т. е библиотека OpenGL присоединена к Windows, необходимо определить такой цвет, который лучше всего воспринимается в качестве фона. Осуществляется это при помощи команды void glClearColor( GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha) Эта команда определяет красный, зеленый, синий и альфа компоненты цве- та, которые используются при очистке буферов цвета Значения аргументов red, green, blue и alpha ограничены диапазоном [0, 1] и по умолчанию уста- новлены в О После того как значение цвета определено, установить его можно командой void glClear(GLbitfield mask); mask — определяет очищаемые буферы, которые могут задаваться при по- мощи поразрядной операции ИЛИ следующих значений- Параметр mask Очищаемый буфер GL_COLOR_BUFFERJBIT GL_DEPTH_BUFFER_BIT GL_ACCUM_BUFFERJBIT GL_STENCIL_BUFFER_BIT Буферы, доступные для записи цвета Буфер глубины Буфер аккумулятора Буфер трафарета Эта команда устанавливает битовые плоскости области окна в значения, предварительно определенные функциями glClearColor, glClearlndex, glClearDepth, glClearStencil и glClearAccum Одновременно можно очистить несколько буферов цветов, предварительно выбрав их при помощи функции glDrawBuffer. На результат выполнения этой команды оказывают влияние операции над фрагментами, такие как тестирование на принадлежность точки, на отсече- ние, на интерполяцию цветов, а также значение маски записи в буфер 2. После того как установлены параметры области вывода и преобразова- ний, можно приступать к рисованию Очистив окно от предыдущего изо- бражения, устанавливаем текущее значение цвета всех вершин, которые будут после этого определены Осуществляется это командами void glColor[3 4][b s i f d](GLtype components) void glColor[3 4][b s i f d] v (GLtype components)
Вершины, примитивы 41 которые устанавливают новое четырехкомпонентное значение цвета Они представлены в двух основных вариантах. glColor3* и glColor4* Во втором варианте явным образом задаются все четыре составляющие цвета, а в вари- анте трех аргументов устанавливаются красный, зеленый и синий компо- ненты цвета, а параметр альфа, который определяет "степень прозрачности" цвета, устанавливается в 1 0 Независимо от типа задаваемых параметров, в OpenGL все они хранятся в формате с плавающей точкой и ограничены диапазоном [0 0, 10]— значение 0 0 соответствует минимальной, а 1 0 — максимальной интенсивности соответствующего компонента При целочис- ленных значениях аргументов происходит линейное отображение в указан- ный диапазон- Тип аргумента Преобразование GLbyte (2c + 1) / (2* 3 4 * * * 8 * * * * * * * — 1) GLshort (2c + 1) / (216 — 1) GLint (2c + 1)/(232- 1) GLfloat c GLdouble c где с — составляющая цвета Однако следует иметь в виду, что отображение в указанный диапазон осу- ществляется только непосредственно перед записью в буфер цвета, а все промежуточные вычисления производятся во входном формате Текущий цвет можно обновлять в любой момент времени На этот счет нет никаких ограничений, в отличие от команды gIVertex* 3. Теперь — непосредственно рисование Для этого мы вызываем свою функцию DrawScene 4. Библиотека OpenGL реализована по модели клиент-сервер, приложение выступает в роли клиента — вырабатывает команды, a OpenGL (сервер} ин- терпретирует и обрабатывает их. При этом сервер может располагаться как на том же компьютере, что и клиент, так и на другом Сервер может под- держивать несколько контекстов OpenGL, каждый из которых инкапсулиру- ет состояние OpenGL Клиент может подключиться к любому из них По этой причине желательно гарантировать, что выполнение всех вызванных команд завершится либо за определенное, либо за конечное время Для обеспечения этого в OpenGL реализованы две команды void gIFinishO— блокирует дальнейшее выполнение программы, пока не будут завершены все вызванные перед ней команды OpenGL, включая изменения состояния и содержимого буфера кадра
42 Гпава 3 void gIFIushO Различные реализации команд OpenGL для работы с буферами размешают- ся в различных местах, включая сетевые буферы и графические акселерато- ры. Команда glFlush освобождает все эти буферы, заставляя все вызванные команды выполниться так быстро, как это требуется фактической реализа- цией воспроизведения. И хотя выполнение не может быть закончено за не- который заданный отрезок времени, оно завершается за конечный период Поскольку любая программа OpenGL может выполняться в сети или на графическом акселераторе, который накапливает команды, убедитесь, что функция glFlush вызвана во всех программах, которые требуют, чтобы все их предварительно вызванные команды были закончены Например, вызовите эту команду перед ожиданием пользовательского ввода, который зависит от сформированного образа. Команда glFlush, в отличие от glFinish, не ожидает завершения выполнения всех предварительно вызванных команд 5. И последнее. Если при установке формата пикселей был определен флаг PFD_DOUBLEBUFFER, для работы в режиме двойной буферизации, необ- ходимо обеспечить перезапись содержимого внеэкранного буфера в основ- ной. Сделать это можно при помощи команды BOOL SwapBuffers(HDC hdc) Параметр hdc определяет дескриптор контекста устройства При успешном завершении команда возвращает TRUE, и FALSE в противном случае По- сле завершения выполнения команды содержимое фонового буфера не оп- ределено (рис. 3.5)
Вершины, примитивы 43 Но вернемся к точкам Для них имеется возможность задавать размер и включать режим устранения ступенчатости Установить размер точки очень легко — достаточно вызвать команду void glPointSize(GLfloat size) Параметр size задает диаметр точки после растеризации По умолчанию — 1 О Эта команда определяет растеризованный диаметр точки как в случае вклю- ченного режима устранения ступенчатости, так и при его отключении Ис- пользование значения диаметра, отличного от 1 0, имеет различный эффект в зависимости от того, разрешено ли устранение ступенчатости Если устранения ступенчатости нет, то действительный размер получается округлением до ближайшего целого (если это значение получается равным нулю, то принимается значение 1.0). Если полученный размер нечетный, то центр точки (х, у) фрагмента пикселей, представляющего эту точку, рассчи- тывается как (Lxw + 0 5_|, Lyw + 0.5J), где индекс w обозначает оконные ко- ординаты. Все пиксели, которые лежат внутри квадратной сетки, имеющего размеры контура с центром в точке (х, у), составляют фрагмент Если же размер — четный, то центр располагается в точке (LxwJ + 0 5, LywJ + 0.5), и центры фрагментов после растеризации являются полуцелыми оконными координатами внутри квадрата, имеющего размеры контура с центром в точке (х, у). Всем фрагментам пикселей, сформированным в растеризован- ной точке без устранения ступенчатости, присваиваются те же самые ассо- циированные данные, что и у вершины соответствующей точки Если установлен режим устранения ступенчатости, то растеризация точки формирует фрагмент для каждого квадрата пикселя, который пересекает регион, лежащий внутри окружности с диаметром, равным текущему разме- ру точки, и центрированный относительно точки (xw, yw) Зоной действия для каждого фрагмента являются оконные координаты области пересечения этой окружности с соответствующим квадратом пикселя Это значение со- храняется и используется на заключительном шаге растеризации. Данные, ассоциированные с каждым фрагментом, те же, что и у растеризованной точки. Когда разрешено устранение ступенчатости точки, поддерживаются не все размеры. Максимальный поддерживаемый размер точки с устранением сту- пенчатости можно узнать при помощи команды glGet*(GL_POINT_SIZE) У меня, например, эта величина равна 10 Если в качестве аргумента задает- ся размер больше максимального, то используется ближайший поддержи- ваемый. Теперь должно быть понятно, почему четыре последние точки в правой части окна на рис 3 4 имеют один и тот же размер Гарантируется поддержка только размера 1 0, а все другие зависят от реализации Размер точки без устранения ступенчатости также может иметь максимум, зависящий от реализации Несмотря на то что на вопрос об этом значении библиотека не отвечает, он должен быть не меньше, чем максимальное зна-
44 Гпава 3 чение для точек с устранением ступенчатости, округленное до ближайшего целого значения Последнее, что осталось рассмотреть, — это устранение ступенчатости Что же такое ступенчатость и из-за чего она возникает? Основная причина ее появления заключается в том, что точки, отрезки, ребра многоугольников, цветовые границы имеют непрерывную природу, тогда как растровое уст- ройство, на которое они отображаются, — дискретно, поэтому изображение Рис. 3.6. Эффект ступенчатости и его устранение При наличии нескольких интенсивностей, т е полутонов серого или оттен- ков цвета, внешний вид примитива может быть улучшен размыванием кра- ев. Простейший метод заключается в том, что интенсивность пикселя уста- навливается пропорционально площади той его части, которая находится внутри примитива (рис 3 6, б) OpenGL поддерживает режим устранения ступенчатости для всех своих примитивов, в том числе и для точек Включение этого режима работы осу- ществляется универсальной командой glEnable, а выключение — командой glDisable, вызываемыми с аргументом GL_POINT_SMOOTH Надо заметить, что эти две команды играют огромную роль в OpenGL С их помощью осуществляется управление всеми возможными эффектами, кото- рые позволяют создавать изумительные изображения Как уже отмечалось, переключатели, представленные на рис 3 3, позволяют переключаться между различными режимами работы конвейера OpenGL Очевидно, что кроме представленных имеется большое количество и других переключателей. Все они управляются при помощи двух команд, одна из которых включает, а вторая выключает некоторый режим void glEnable(GLenum cap) void glDisable(GLenum cap) Обе команды имеют единственный аргумент — cap, который может прини- мать одно значение для каждого вызова команды Полный перечень воз- можных значений аргумента приведен в приложении 1, и если вы хотите создавать действительно привлекательные и эффективные программы, вни- мательно ознакомьтесь с ними и не забывайте использовать
Вершины, примитивы 45 Линии С линиями дело обстоит ненамного сложнее, чем с точками Для наглядно- сти обратимся к примеру (Фрагмент взят из программы Lines, которая име- ется на прилагаемой дискете.) static void CALLBACK Draw(void) { glColor3f(0 0, 00, 1.0); // Рисуем "ломаную кривую" DrawScene(GL_LINE_STRIP); glColor3f(1.0, 0.0, 1.0); // Рисуем два не связанных отрезка DrawLine(); glColor3f(1.0, 0.0, 0.0); // Рисуем замкнутую кривую DrawScene(GL_LINE_LOOP); // Включаем режим устранения ступенчатости glEnable(GL_LINE_SMOOTH); glColor3f(1.0, 0.0, 1.0); // Рисуем два не связанных отрезка DrawLine() ; // Режим устранения ступенчатости требует дополнительных // затрат. Поэтому лучше его отключить и пользоваться только //по мере необходимости glDisable(GL_LINE_SMOOTH); } static void CALLBACK DrawLine(void) { // Задаем ширину линии. // В зависимости от наличия или отсутствия режима // устранения ступенчатости внешний вид линий будет отличаться glLineWidth(20.Of); // Рисуем два не связанных между собой отрезка glBegin(GL_LINES); glVertex2f(-windW/4 + windW/20, -windH/2 + windH/5); glVertex2f( 0, windH/2 — windH/10); glVertex2f(-windW/4 + windW/20 + 30, -windH/2 + windH/5);
46 Глава 3 glVertex2f( 30, windH/2 - windH/Ю) ; glEnd(); // Все следующие линии будут иметь единичную ширину glLineWidth(1.Of); } static void CALLBACK DrawScene(GLenum mode) { // В зависимости от значения mode три вершины определяют // разные примитивы (рис. 3.7) glBegin (mode) ; glVertex2f (-windW/4 +• windW/20, -windH/2 + windH/10) ; glVertex2f( windW/4 — windW/20, -windH/2 + windH/10); glVertex2f ( windW/4 — windW/20, windH/2 — windH/Ю), glEnd(); Результат работы программы показан на рис 3 7 Рис. 3.7. Результат работы программы Lines В этом примере для нас интересны следующие моменты □ Типы примитивов линий □ Параметры линии □ Устранение ступенчатости
Вершины, примитивы 47 Начнем по порядку Тем, кто не помнит, о чем идет речь, следует обратить- ся к описанию команд glBegin/glEnd Простейшим из рассматриваемых при- митивов является тот, который представляет собой группу не связанных ме- жду собой отрезков Для построения примитивов этого типа достаточно оп- ределить командные скобки в виде glBegin(GL_LINES); glEnd(); После этого OpenGL будет брать последовательные пары вершин, заданные командами gIVertex*, и соединять их между собой Утолщенные линии на рис. 3.7 созданы именно таким способом Как видите, это просто Следующий тип примитива представляет собой последовательность связан- ных отрезков, конец одного из которых одновременно является началом следующего glBegin(GL_LINE_STRIP); glEnd(); И если для предыдущего типа примитивов нечетное число вершин в ко- мандных скобках приводит к "потере" последней точки, то в данном случае этого не произойдет Не будет потеряна точка и у последнего типа примитива линии — замкну- тых линий Единственное отличие от предыдущего заключается в том, что библиотека OpenGL сама "замыкает" линию, соединяя последнюю вершину с первой Для получения этого эффекта достаточно вызвать glBegin(GL_LINE_LOOP); glEnd(); Как видите все очень и очень просто, и можно переходить к рассмотрению тех параметров линии, которыми можно манипулировать Таких параметров два — ширина и штриховка Команда void glLineWidth(GLfloat width) служит для определения ширины линии, которая определяется аргументом width и по умолчанию равна 1.0 Эта команда определяет ширину линий и в случае устранения ступенчатости, и без него Используя ширину линии, отличную о г 10, можно добиться различных эффектов, зависящих от того, разрешен ли режим устранения ступенчатости, который в этом случае управляется вызовом команд glEnable и glDisable с аргументом GL_LINE_SMOOTH При заблокированном устранении ступенчатости действительная ширина линии определяется округлением представленного значения до ближайшего Целого. Если значение равно 0, то ширина считается равной 1 0
48 Гпава 3 Когда разрешено устранение ступенчатости линии, поддерживаются не лю- бые значения ширины, а только те, которые не превышают максимальную поддерживаемую Узнать это значение можно при помощи команды glGe^{GL_LINE_WIDTH) У меня, например, эта величина равна 10 0 Если в качестве аргумента задается ширина больше максимальной, то использует- ся это максимальное значение Гарантируется поддержка только значения ширины линии 1 0, а все другие зависят от реализации void gll_ineStipple( GLint factor, GLushort pattern} Эта команда определяет шаблон штриховки линии Маскирование выполня- ется с использованием трех параметров 16-разрядного шаблона pattern, биты которого определяют рисуемые во время растеризации фрагменты, счетчика повторов битов factor и целого счетчика пунктира 5 Этот счетчик сбрасывается в нуль всякий раз, когда вызывается коман- да glBegin, и вызывается для каждого отрезка, заключенного между glBegin(GL_LINES)/glEnd Он увеличивается после каждого фрагмента сфор- мированного отрезка единичной ширины с устранением ступенчатости или после каждого сформированного i-ro фрагмента отрезка ширины i Фраг- менты i, ассоциированные со счетчиком s, замаскированы, если бит пара- метра pattern (s*factor) mod 16 равен нулю, в противном случае эти фрагмен- ты посылаются в буфер кадра Нулевой разряд параметра pattern является наименее значимым При штриховке без устранения ступенчатости линии трактуются как последовательность прямоугольников \*width Штриховка линии может быть разрешена или заблокирована командами glEnable и glDisable с аргументом GL_LINE_STIPPLE Если штриховка раз- решена, то используется заданный шаблон, а в противном случае (режим по умолчанию) все биты шаблона считаются установленными в единицу Обратите внимание на линию, разбивающую окно на две части Фрагмент кода, отвечающий за ее создание, приведен ниже static void CALLBACK DrawAxies(int left) { // Линию будем рисовать черным цветом glColor3f(O.Of, O.Of, 0 Of); // Для получения эффекта волнистой линии задаем разные // шаблоны для левой . if('left){ pattern = 0xlC47, х = windW/4;
Вершины, примитивы 49 // ... и правой половинок окна else{ pattern = 0х471С; х = -windW/4; } // Чтобы жестко не привязываться к текущей ширине линии, // запросим это значение у библиотеки glGetFloatv(GL_LINE_WIDTH, &width), // Штриховая линия будет иметь ширину 2.О glLineWidth(2 Of), // Шаблон определен выше, а каждый его бит // будем повторять два раза glLineStipple(2, pattern); // Разрешаем выполнение штриховки линии glEnable(GL_LINE_STIPPLE) , // Теперь можно рисовать линию glBegin(GL_LINES); glVertex2i(х, -windH/2); glVertex2i(х, windH/2); glEnd() ; // Больше режим штриховки не нужен и мы его блокируем glDisable(GL_LINE_STIPPLE); // Восстанавливаем предыдущую ширину линий glLineWidth(width) ; В заключение осталось обратить внимание на очень интересную и полезную команду glGetFloatv(GL_LINE_WIDTH, &width), которая позволяет узнать текущую ширину линии, что определяется ее аргументом GL_LINE_WIDTH Эта команда имеет четыре варианта glGetBooleanv, glGetDoublev, glGetFloatv, glGetlntegerv и множество значений первого аргу- мента, что позволяет получить у библиотеки информацию практически обо всех параметрах Во многих случаях использование этих команд существен- но облегчает разработку приложения По этой причине в приложении 2 приводится полный список аргументов этих команд Треугольники С треугольниками дело обстоит не сложнее, чем с точками и линиями Но имеются и некоторые интересные нюансы Как обычно начнем с фрагмента кода. (Фрагмент взят из программы Triangles, которая имеется на прилагае- мой дискете)
50 static void CALLBACK DrawScene(void) { int i; GLint aVertex[6][2]; // Описываем координаты вершин aVertex[0] [0] = -windW/32*7; aVertex[0] [1] = windH/3, aVercex[l][0] = -windW/16; aVertex[1][1] = windH/24*ll, aVertex[2][0] = windW/8; aVertex[2][1] = windH/24*10, aVertex[3][0] = windW/32*7, aVertex[3][1] = windH/3, aVertex[4][0] = windW/8; aVertex[4][1] = windH/24*7, aVertex[5][0] = -windW/16; aVertex[5][1] = windH/24*5, // Запоминаем текущее значение цвета glGetFloatv(GL_CURRENT_COLOR, params); glColor3f(l.Of, O.Of, l.Of); // Рисуем два отдельных треугольника glBegin(GL_TRIANGLES); for (i = 0; i < 6; i++) { glVertex2iv(aVertex[i]); } glEnd(); // Чтобы избежать наложения изображений, смещаем вершины for(i = 0; 1 < 6; 1++){ aVertex[i][1] -= windH/3; } glColor3f(l.Of, 0.5f, 0 Of), // Рисуем четыре связанных сторонами треугольника glBegin(GL_TRIANGLE_STRIP); for (i =0; i < 6; i++) { glVertex2iv(aVertex[i]); } glEnd(); // Еще раз смещаем for(i =0; i < 6; i++){ aVertex[i][1] -= windH/3; } glColor3f(O.Of, 0.5f, l.Of); // Рисуем четыре связанных треугольника с общей вершиной glBegin(GL_TRIANGLE_FAN), for (1 = 0; i < 6; i++) { glVertex2iv(aVertex[i]); } glEnd();
Вершины, примитивы 51 // Восстанавливаем значение текущего цвета glColor3f(params[0], params[1], params[2]), } static void CALLBACK Draw(void) { DrawScene() ; // Включаем режим устранения ступенчатости для многоугольника glEnable(GL_POLYGON_SMOOTH); glColor3f(1.0, 0 0, 1.0) ; DrawScene(); // Выключаем режим устранения ступенчатости для многоугольника glDisable(GL_POLYGON_SMOOTH); Рис. 3.8. Результат работы программы Triangles Чем интересна эта программа? Прежде всего тремя различными типами тре- угольных примитивов Обратите внимание определив для всех случаев один и тот же набор из шести вершин, мы получили совершенно разные изобра- жения Проще всего понять разницу между ними, обратившись к рис 3 8, на котором условно показаны используемые шесть вершин И если режим формирования независимых треугольников совершенно очевиден — берутся тройки вершин и соединяются между собой (рис 3.9, а), то два другие типа примитивов требуют некоторого пояснения.
52 Гпава 3 Рис. 3.9. Различные типы треугольных примитивов В режиме GL_TRIANGLE_STRIP (рис 3 9, б) входящие в примитив тре- угольники составляют единую группу, в которой каждая пара соседних тре- угольников имеет общую сторону Может возникнуть вполне резонный во- прос: а зачем это нужно? На данном этапе изложения материала ответить на него не так просто Скажу лишь, что, например, при построении реалисти- ческих изображений часто бывает полезно представить некоторую фигуру именно совокупностью связанных треугольников, чтобы избежать дополни- тельного этапа триангуляции (в англоязычной литературе часто использует- ся термин tessellation, что означает "создание мозаики") Третий тип треугольных примитивов GL TRIANGLE FAN (рис 3 9, в) также образует единую группу, но при этом все входящие в нее треугольни- ки имеют общую вершину. В этом случае вопросов о необходимости такого типа примитивов должно быть существенно меньше — из школьной геомет- рии все знают о конусах и пирамидах, которые очень напоминает фигура на рис 3 9 (в). Рассмотреть нижние треугольники на рис 3 8 невозможно Связано это с тем, что все примитивы залиты одним цветом и границы неразличимы В следующих двух примерах (четырехугольников и многоугольников) будут представлены режимы работы OpenGL, при которых можно будет рассмот- реть, из чего состоит созданная фигура. Что еще можно сказать о треугольных примитивах? Поскольку они, упро- щенно говоря, "строятся из линий", то очевидно, что можно менять их ширину. Кроме того, они являются частным случаем многоугольника, и по- этому к ним можно применять дополнительные, общие для всех много- угольников, настройки. Это в частности относится к режиму устранения ступенчатости, который включается и отключается уже хорошо знакомыми командами glEnable/glDisable е аргументом GL POLYGON SMOOTH Влия- ние этого режима можно наблюдать у двух верхних треугольников на рис 3 8 И последнее Обратите внимание на стрелки, имеющиеся на рис 3 9, кото- рые определяют порядок обхода вершин в примитиве Пока запомните только, что при создании примитивов имеется некоторая информация, свя- занная с этим порядком В дальнейшем это понадобится Следующим типом примитива является четырехугольник — также частный случай многоугольника, на примере которого мы продолжим знакомство с возможностями, заложенными в OpenGL
Вершины, примитивы Четырехугольники Фрагмент кода, поясняющий принцип создания и управления этим типом примитивов, приведен ниже (Фрагмент взят из программы Quadrangles, ко- торая имеется на прилагаемой дискете ) static void CALLBACK DrawScene(GLenum mode) { int i ; GLint aVertex[8] [2] ; // Определяем вершины четырехугольников aVertex[0] [0] = -windW/4 + 10, aVertex[0] [1] = 0; aVertex[1][0] = -windW/8; aVertex[1][1] = windH/8*3, aVertex[2] [0] = windW/20; aVertex[2] [1] = 0, aVertex [3] [0] = -windW/8, aVertex [3] [1] = -windH/4, aVertex[4][0] = windW/8; aVertex[4][1] = windH/8*3; aVertex [5] [0] = windW/4 - 10; aVertex [5] [1] = 0; aVertex[6][0] = windW/8; aVertex[6][1] = -windH/8*3; aVertex[7][0] = windW/20; aVertex[7][1] = -windH/4, // Сохраняем текущие атрибуты: в данном случае нас интересуют // трафарет и грани — лицевая и нелицевая glPushAttrib(GL_POLYGON_BIT); if(mode == GL_QUAD_STRIP) // Для связанных четырехугольников устанавливаем // режим рисования контуров вместо заливки glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); // Рисуем четырехугольник // Обратите внимание на использование векторной версии // команды glVertex* glBegin(mode); for (i = 0; i < 8; i++) { glVertex2iv(aVertex[i]); } glEnd(); // Восстанавливаем сохраненные значения атрибутов glPopAttrib(), } static void CALLBACK Draw(void) { // Определяем трафарет GLubyte pattern[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x80, 0x01, OxCO, 0x06, OxCO, 0x03, 0x60, 0x04, 0x60, 0x06, 0x20, 0x04, 0x30, OxOC, 0x20, 0x04, 0x18, 0x18, 0x20, 0x04, OxOC, 0x30, 0x20, 0x04, 0x06, 0x60, 0x20, 0x44, 0x03, OxCO, 0x22,
54 Гла1 0x44, 0x01, 0x44, 0x01, 0x44, 0x01, 0x66, 0x01, 0x19, 0x81, 0x07, Oxel, 0x03, 0x31, 0x06, 0x64, 0x18, Oxcc, 0x10, 0x63, 0x10, 0x18, 0x80, 0x22, 0x80, 0x22, 0x80, 0x22, 0x80, 0x66, 0x81, 0x98, 0x87, ОхеО, 0x8c, OxcO, 0x26, 0x60, 0x33, 0x18, 0xC6, 0x08, 0x18, 0x08, 0x44, 0x01, 0x44, 0x01, 0x44, 0x01, 0x33, 0x01, OxOC, OxCl, 0x03, 0x3f, 0x03, 0x33, 0x0c, Oxcc, 0x10, 0xc4, 0x10, 0x30, 0x10, 0x00, 0x80, 0x22, 0x80, 0x22, 0x80, 0x22, 0x80, OxCC, 0x83, 0x30, Oxfc, OxcO, Oxcc, OxcO, 0x33, 0x30, 0x23, 0x08, 0x0c, 0x08, 0x00, 0x08 // Устанавливаем трафарет glPolygonStipple(pattern) ; // Разрешаем наложение трафарета glEnable(GL_POLYGON_STIPPLE); DrawScene(GL_QUADS) ; DrawScene(GL_QUAD_STRIP); // Запрещаем дальнейшее наложение трафарета glDisable(GL_POLYGON_STIPPLE); Результат выполнения программы представлен на рис 3 10 Рис. 3.10. Результат работы программы Quadrangles
Вершины, примитивы 55 Как и в предыдущих приведенных примерах, рисунок не производит "сильного" впечатления. Это вполне объяснимо, т к назначение программ заключается только в рассмотрении различных возможностей и режимов работы OpenGL Что же нового можно почерпнуть из представленного при- мера? Прежде всего это два типа четырехугольных примитивов и наложение трафарета Кроме того, представлен общий для всех многоугольников режим вывода примитива Но обо всем по порядку. Как уже упоминалось при описании командных скобок, для четырехуголь- ников реализованы два типа примитивов четырехугольники (рис 3 11, б), которые формируются внутри командных скобок glBegin(GL_QUADS); glEnd(); и связанные четырехугольники (рис 3 11, а), формируемые командами glBegin(GL_QUAD_STRIP); glEnd(); Принцип построения примитивов настолько хорошо проиллюстрирован, что мне остается только обратить ваше внимание на стрелки, показывающие порядок группировки и обхода вершин. Будьте очень внимательны при ис- пользовании одного и того же набора вершин для построения различных примитивов и не повторяйте ошибку, которую я сознательно сделал в при- веденном выше примере’ в правой половине окна отображено сумбурное нагромождение линий вместо предполагаемой группы четырехугольников (рис. 3 12) Рис. 3.12. Фигура, которая "не получилась" на рис 3 10
56 Глава 3 Раньше упоминалось, что и треугольники и четырехугольники являются ча- стными случаями многоугольников Откуда же тогда в правой половине ок- на взялись линии, если в примерах на рис. 3 8 мы не могли различить тре- угольники из-за сплошной заливки? А все дело в единственной команде glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); которую мы использовали в последнем примере Здесь, на мой взгляд, уместно сделать некоторое отступление и рассмотреть несколько важных команд void glPolygonMode( GLenum face, GLenum mode) Команда управляет выводом многоугольников при растеризации Параметр mode задает режим отображения и может принимать следующие значения. GL_POINT В этом режиме отображаются только отдельные вершины много- угольника, у которых установлен флаг грани У точек можно изме- нять размер (атрибут GL_POINT_SIZE) и устанавливать режим уст- ранения ступенчатости (GL_POINT_SMOOTH) GLJJNE Стороны многоугольника отображаются в виде отрезков При штри- ховке они обрабатываются как связанные отрезки — число штрихов и шаблон распространяются на все отрезки многоугольника У них можно изменять ширину (атрибут GL_LINE_WIDTH) и устанавливать режим устранения ступенчатости (GL_LINE_SMOOTH) GL-FILL Внутренняя область многоугольника закрашивается текущим цветом Можно устанавливать режимы наложения трафарета (атрибут GL_POLYGON_STIPPLE) и устранения ступенчатости (GL_POLYGON_SMOOTH) Параметр face определяет многоугольники, к которым применяется режим, заданный параметром mode GL_FRONT — лицевые многоугольники, GL BACK — нелицевые многоугольники, GL FRONT AND BACK — и те и другие По умолчанию режим отображения соответствует заполнению только лице- вых многоугольников glPolygonMode(GL_FRONT, GL_FILL) Описанные здесь режимы или уже обсуждались, или интуитивно понятны (режим GL_POINT иллюстрируется при рассмотрении многоугольников в следующем разделе) Неясно только, откуда взялись и что означают лицевые и Нелицевые многоугольники (грани) Отвечать на этот вопрос без изучения различных систем координат и проекций несколько некорректно, поэтому отложим его до соответствующей главы Здесь же рассмотрим только рисун- ки, которые показывают, что получится при установке режимов glPolygonMode(GL_FRONl\ GLJLINE) (рис 3 13, а) и glPolygonMode(GL_BACK, GL_LINE) (рис 3 13, б)
Вершины, примитивы 57 Рис. 3.13. Лицевые и нелицевые грани OpenGL — хорошо продуманная и спроектированная библиотека, и если она позволяет изменить какие-либо параметры, то предоставляет возмож- ность сохранить текущие значения в специальных стеках Рассмотрим один из них, а именно тот, который служит для хранения значений атрибутов Для того чтобы установленный режим не распространялся на другие прими- тивы, в OpenGL предусмотрены команды сохранения и восстановления те- кущих атрибутов в соответствующем стеке void glPushAttrib(GLbitfield mask) void glPopAttribO Параметр mask определяет тип сохраняемых атрибутов Приведем некоторые из них (полный список можно посмотреть в справочной системе) Тип Сохраняемые атрибуты GL_COLOR_BUFFER_BIT Значения битов тестирования альфа — GL_ALPHA_TEST, смешения цветов — GLJ3LEND, дрожания— GL_DITHER, логических операций- GL_LOGIC_OP, а также параметры буфера рисова- ния - GL_DRAW_BUFFER gl_current_bit Текущие значения цвета, индекса цвета, вектора нормали, координат текстуры и позиции растра; флаг доступности текущей позиции растра — GL_CURRENT_RASTER_POSITION_VALID, с ассоции- рованными значениями, флаг грани — GL_EDGE_ FLAG gldepth_buffer_bit Значения битов тестирования буфера глубины — GL_DEPTH_TEST и маски записи глубины — GL_DEPTH_WRITEMASK
58 Гпава 3 (продолжение) Тип Сохраняемые атрибуты GL_LIGHTING_BIT Биты разрешения GL_COLOR_MATERIAL и GLJJGHTING, значения GL_COLOR_MATERIAL_FACE и GL_LIGHT_MODEL_LOCAL_VIEWER, установки GL_LIGHT_MODEL_TWO_SIDE и GL_SHADE_MODEL GL_LINE_BIT Флаг устранения ступенчатости GL_LINE_SMOOTH и бит разрешения GL_LINE_STIPPLE GL_POINT_BIT Флаг устранения ступенчатости GL_POINT_SMOOTH GL_POLYGON_BIT Биты разрешения GL_CULL_FACE и GL_POLYGON_ STIPPLE, значение GL_CULL_FACE_MODE; режимы GL_POLYGON_MODE, индикатор GL_FRONT_FACE, флаг GL_POLYGON_SMOOTH GL_POLYGON_STIPPLE_BIT Образ трафарета GL_SCISSOR_BIT Флаг GL_SCISSOR_TEST GL_STENCIL_BUFFER_BIT Биты разрешения теста трафарета GL_STENCIL_ TEST GL_TEXTURE_BIT Биты разрешения GL_TEXTURE_GEN_x (х принимает значения S, Т, R и Q); установки GL_TEXTURE_ GEN_MODE для S, Т, R и Q GL_TRANSFORM_BIT Значение режима GL_MATRIX_MODE, флаг GL_NORMALIZE GL_VIEWPORT_BIT Диапазон глубин (ближняя и дальняя отсекающие плоскости); начало и размеры области вывода Для сохранения нескольких доступных атрибутов следует каждый раз вызы- вать команду glPushAttrib с соответствующими аргументами Не все атрибуты состояния можно сохранить в стеке Так, например, не могут быть сохранены параметры упаковки пикселей, режим воспроизведе- ния, состояния выбора и обратной связи. Для сохранения всех доступных состояний можно также задать в качестве параметра значение GL ALL ATTRIB BITS Команда glPopAttrib восстанавливает сохраненные значения, делая их теку- щими Теперь осталось рассмотреть самый приятный глазу момент представленной программы Речь идет о трафарете многоугольника (использование четырех- угольника — только пример) Для того чтобы наложить трафарет, прежде всего необходимо его опреде- лить При этом необходимо соблюдать единственное условие — размер его
Вершины, примитивы 59 должен быть 32x32 Затем его нужно установить в OpenGL Осуществляется это при помощи команды void gIPolygonStipplefconst FLubyte *mask) Наложение трафарета на многоугольник подобно штриховке линии При этом маскируются определенные фрагменты, для которых значение соответ- ствующего разряда маски равно 0 При этом неважно, разрешено устране- ние ступенчатости многоугольника или нет Параметр mask является указателем на шаблон трафарета размером 32x32, который представляется как массив размера 32x32 одноразрядных индексов цвета, упакованных в беззнаковые байты На сборку битов в шаблон влияют параметры команды glPixelStore, такие как G L UN РАС K SWAP BYTES и GL UNPACK LSB FIRST Наложение трафарета на многоугольник разрешается и блокируется соответ- ственно командами glEnable и glDisable с аргументом GLPO LIGON STIPPLE и если это разрешено, растеризованный фрагмент многоугольника с оконными координатами xw и yw посылается на следующий этап обработ- ки OpenGL только в том случае, если разряд xw mod 32 в строке yw mod 32 шаблона штриховки установлен в 1 При заблокированном режиме наложе- ния трафарета все разряды установлены в 1 Стоит затронуть какой-нибудь вопрос и, он сразу же обрастает, как снеж- ный ком, дополнительными К счастью в данном случае можно не отвле- каться, т к можно успешно пользоваться шаблонами, даже ничего не зная о режимах хранения пикселей. Поэтому осталось обратить внимание на то, что наложение трафарета используется только для режима GL_FILL, при котором закрашивается внутренняя область многоугольника Если установ- лен режим GL_LINE, то речь может идти только о штриховке линии, кото- рая определяется совсем другой командой — glLineStipple А о штриховке точки даже говорить бессмысленно Постепенно мы подошли к последнему типу примитивов Многоугольники Чтобы не повторяться сразу привожу фрагмент кода, который взят из про- граммы Polygons, имеющейся на прилагаемой дискете GLfloat aVertex[64][2], GLfloat aColors[64][3], static void Init(void) { // Создаем массив вершин for (mt 1 = 0; i < 64, 1++) { aVertex[i][0] = windW/5*sin(0.1963495*1/2);
60 Глава 3 aVertex[1][1] = windW/5*cos(0.1963495*1/2); aColors[i][О] = O.Olf + 0.015*i; aColors[i][1] = O.Olf + 0 015*i, aColors[i][1] = 0 Olf + 0.015*1, } // Формируем массивы OpenGL вершин и ассоциированных с ними цветов glVertexPointer(2, GL_FLCAT, 0, aVertex); glColorPointer(3, GL_FLOAT, 0, aColors); } static void CALLBACK DrawScene(GLenum mode) { // Запоминаем текущий размер точки ... glGetFloatv(GL_POINT_SIZE, &width); //и текущий цвет glGetFloatv(GL_CURRENT_COLOR, params), glPushAttrib(GL_POLYGON_BIT); if(mode == GL_TRUE){ glPointSize(3 Of); // Для "правого" многоугольника устанавливаем режим // изображения только точек glPolygonMode(GL_FRONT_AND_BACK, GL_POINT), } // Рисуем многоугольник glDrawArrays(GLJPOLYGON, 0, 64); glPopAttrib(); // Восстанавливаем сохраненные значения размера точки и цветов glPointSize(width); glColor3f(params [0], params [1], params [2]); } static void CALLBACK Draw(void) { // Разрешаем работу с массивом вершин .. glEnableClientState(GL_VERTEX_ARRAY), //и цветов glEnableClientState(GL_COLOR_ARRAY); DrawScene(GL_FALSE) ; // Вместо массива цветов теперь будут использоваться // текущие значения цвета. Предварительно их // нужно определить явным образом glDisableClientState(GL_COLOR_ARRAY);
Вершины, примитивы 61 DrawScene(GL_TRUE), // Запрещаем работу с массивом вершин glDisableClientState(GL_VERTEX_ARRAY); На рис 3 14 показан результат работы программы Рис. 3.14. Результат работы программы Polygons Для многоугольников предусмотрен только один тип примитивов (GL_POLYGON), использование которого не может вызвать никаких за- труднений, и поэтому основное внимание мы уделим массивам OpenGL — возможности, которая может оказаться очень полезной во многих случаях Но сначала обратите внимание на правую часть окна Для рисования ис- пользовался тот же массив, что и для левой части Появление точек связано с выполнением команды glPolygonMode(GL_FRONT_AND_BACK, GL POINT), описанной раньше, а действие я обещал продемонстрировать Пока больше нечего сказать о многоугольнике, т к практически все ос- тальные возможности настройки мы уже рассмотрели, поэтому займемся массивами OpenGL До сих пор для установки координат вершин и цветов мы пользовались специальными командами, которые располагались внутри командных ско- бок glBegin/glEnd OpenGL предоставляет возможность расположить данные вершин (координаты, цвета и др) в специальных массивах и затем исполь- зовать блоки из них для определения нескольких геометрических примити- вов, выполнив одну единственную команду Всего предусмотрено шесть та-
62 Глава 3 ких массивов координат вершин, цветов, индексов цветов, координат нор- малей, флагов граней и координат нормалей, т е вершин и ассоциирован- ных с ними данных Принцип построения массивов одинаков во всех случа- ях, поэтому рассмотрим только массив вершин: void glVertexPointer( GLint size, GLenum type, GLsizei stride, void *ptri Эта команда определяет способ размещения и координаты вершин Пара- метр size задает число координат вершины и может быть равен 2, 3 и 4 Тип данных определяется параметром type, который может принимать значения GL_SHORT, GL INT, GL FLOAT и GLDOUBLE Один массив можно использовать как для хранения координат вершин, так и для их атрибутов, что в некоторых случаях более предпочтительно, чем использование отдель- ных массивов. Если используется такой способ хранения, то параметр stride должен задавать смещение (в байтах) от одной вершины до следующей1 Сами данные хранятся по адресу, на который указывает ptr Таким образом, чтобы создать массив OpenGL сначала нужно сформировать данные, а затем выполнить команду gl*Pointer, где символ (*) заменяет на- звания различных вариантов команды для разных типов массивов, что и сделано в представленной выше программе. glVertexPointer(2, // Вершина определяется двумя координатами GL_FLOAT,// Используется формат с плавающей точкой О, // Все элементы расположены последовательно aVertex);// Данные хранятся по этому адресу glColorPointer(3, // Для цвета используем красный, зеленый // и синий компоненты GL_FLOAT, 0, aColors); // см. пред, команду Так же, как для многих других режимов работы OpenGL, необходимо сооб- щить о желании работать с массивом Осуществляется это аналогично вызо- ву glEnable, только почему-то используется другая команда: void glEnableClientState(GLenum array) где параметр array является символьной константой, определяющей тип массива, и может принимать значения GL VERTEX ARRAY, GLCOLOR ARRAY, GL INDEX ARRAY, GL NORMAL ARRAY, GL TEXTURE COORD ARRAY и GL EDGE FLAG ARRAY После окончания работы с массивом желательно заблокировать работу с ним, для чего необходимо выполнить команду void glDisableClientState(GLenum array) Для тех, кто привык доверять справочной системе, сообщаю, что там описывается устарев- шая версия этой команды В последней версии параметр count не используется
Вершины, примитивы 63 где array может принимать те же значения, что и для команды glEnableClientState После того как требуемые массивы сформированы и работа с ними разре- шена, воспроизведение примитива осуществляется вызовом одной команды void glDrawArrays( GLenum mode, GLint first, GLsizei count) Эта команда осуществляет воспроизведение примитива, заданного парамет- ром mode, последовательно выполняя команду glArrayElement для каждого элемента, начиная с указанного параметром first Всего отображается count элементов Тип формируемых примитивов определяется параметром mode, который может принимать значения, перечисленные в таблице описания аргументов команды glBegin. Преимущество использования этой команды заключается в том, что отпада- ет необходимость в дополнительных вызовах команд, определяющих атрибу- ты вершин, — библиотека делает это автоматически Результат выполнения команды glDrawArrays(GL_POLYGON, // Рисуем многоугольник, О, // начиная с нулевого элемента, 64); // всего изображаем 64 элемента можно увидеть в левой половине окна на рис 3 14 На что следует обратить внимание после выполнения команды становятся неопределенными значения атрибутов вершины (цветов, нормалей и т д), массивы которых были разрешены для использования Если же заблокиро- вано использование массива координат вершин, то геометрические прими- тивы не формируются, а только изменяются значения соответствующих раз- блокированных массивов Как уже было сказано, пересылка элементов в контекст воспроизведения осуществляется командой void glArrayElement(GLint index) единственный параметр {index) которой задает номер отображаемого эле- мента Помимо рассмотренных, в библиотеке имеются также другие команды рабо- ты с массивами вершин и их атрибутов glDraw Elements, gllnterleavedArrays, glPushClientAttrib, более подробную информацию по которым можно найти в справочной системе В заключение перечислим команды, которые можно вставлять в командные скобки Их не так много glVertex, glColor, gllndex, glNormal, glTexCoord, glEvalCoord, glEvalPoint, glMaterial и glEdgeFlag Кроме них можно также ис-
64 Глава 3 пользовать glCallList и glCallLists, которые позволяют выполнять последова- тельность только что перечисленных и многих других команд OpenGL При использовании любой другой команды выводится сообщение об ошибке, а сама команда игнорируется Более подробно эти команды мы рассмотрим ниже, а пока вернемся к вершинам Число вершин, которые можно определить между командными скобками, не ограничивается Еще раз подчеркну — результат вызова команды gl Vertex* вне командных скобок glBegin/glEnd не определен Следующий момент, на который необходимо обратить внимание, заключа- ется в следующем С каждой вершиной обязательно ассоциируются некото- рые данные, которые перечислены выше Однако если не предполагается использование некоторых из них, например, нет необходимости наклады- вать текстуру на примитив, то можно воспользоваться значениями, которые устанавливаются для них по умолчанию при инициализации OpenGL (чем я и воспользовался в приведенных примерах) Поскольку эти дополнительные данные будут рассматриваться ниже, приведу здесь используемые по умол- чанию значения □ Все координаты текстуры имеют нулевое значение □ Координатам нормалей присваивается значение (0, 0, 1) □ Цвет вершины имеет значение (1, 1, 1, 1) для режима RGBA или 1 (значение индекса) при использовании палитры □ Флаг грани установлен в GL TRUE, что соответствует связанной стороне Помимо рассмотренных примитивов, определяемых внутри командных ско- бок, в OpenGL предусмотрены команды void glRect[d f i s]( GLtype xt, и GLtype y1, GLtype x2, GLtype y2) void glRect[d f i s] v ( GLtype* v1, GLtype* v2) которые позволяют достаточно эффективно задавать прямоугольник в плос- кости z = О, определяя два его противоположных угла Их действие анало- гично, например, следующей последовательности команд glBegin(GLJPOLYGON), glVertex2*(xl, yl), // * заменяется соответствующим символом, glVertex2*(х2, yl) , // зависящим от типа используемых glVertex2*(х2, у2); // данных glVertex2*(xl, у2) , glEnd(); Параметры xl и у] задают левую нижнюю, а х2 и у2 — противоположную вершину прямоугольника Параметры у 7 и v2 определяют указатели на соот- ветствующие двухточечные массивы вершин
Вершины, примитивы g5 Вот и все о геометрических примитивах Помимо них в OpenGL предусмот- рена возможность создавать так называемые растровые примитивы Растровые примитивы Для создания растровых примитивов, к которым относятся битовые масси- вы, используется команда void glBitmap( GLsizei width, GLsizei height, GLfloat xongin, GLfloat yorigin, GLfloat xmove, GLfloat ymove, const GLubyte* bitmap) Параметры width и height задают, соответственно, ширину и высоту битового образа (в пикселях), xorigin и yorigin определяют место, начиная с левого нижнего угла битового массива, с которого он начинается, xmove и ymove показывают смещение, добавляемое к текущей позиции растра после вывода битового массива; bitmap указывает адрес, начиная с которого хранится соб- ственно битовый образ (рис 3 15) При отображении битовый массив позиционируется относительно текущей позиции растра, и если она не определена, то битовый массив игнорируется При определенной и разрешенной текущей позиции растра битовый массив начинается из точки xw = L xr — xorigin J Yw = L yr - yorigin J, где xr и yr — координаты текущей позиции растра, a xw и yw — координаты образа в окне Рис. 3.15. Битовый массив и ассоциированные с ним параметры
66 Глава 3 Команда формирует фрагменты для каждого пикселя, соответствующего 1 в битовом массиве Помимо текущей позиции растра при формировании фрагмента используются текущие значения z-координаты растра, цвет или индекс цвета, а также текущие координаты растра текстуры (не пугайтесь, если некоторые понятия вам не знакомы очень скоро "белых пятен" в этих вопросах не останется) После того как битовый образ будет отображен в буфере кадра, к значениям координат текущей позиции растра добавляются соответствующие значения xmove и ymove Остальные текущие параметры не изменяются Рассмотрим фрагмент кода1 void COpenGLView.•OnDraw(CDC* pDC) { // Определяем элементы битового массива GLubyte Bitmap_logo[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0хс4, Охеб, 0x31, 0x00, 0x00, 0x00, 0x00, 0x02, 0x24, 0x21, 0x49, 0x00, 0x00, 0x00, 0x00, 0x05, 0x94, 0x21, 0x49, 0x00, 0x00, 0x00, 0x00, 0x05, 0x15, Oxef, 0x31, 0xc0, 0x00, 0x00, 0x00, 0x05, 0x95, 0x29, 0x48, 0x00, 0x00, 0x00, 0x00, 0x02, 0x25, 0x29, 0x48, 0x00, 0x00, 0x00, 0x00, 0x01, 0хс5, Oxef, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x12, 0x89, 0x91, 0x4а, 0x19, 0хе9, 0x31, 0x01, 0x1а, 0x52, 0x51, Охба, 0x25, 0x21, 0x49, 0x01, 0x16, 0x72, 0x55, 0x5b, 0ха5, 0хе1, 0x48, 0x01, 0x12, 0x52, 0x5b, 0x4а, 0ха5, 0x41, 0хс8, 0x01, 0x12, 0x89, 0x91, 0х4Ь, 0x99, 0хс1, 0x48, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x01, 0x48, 0x0f, 0хе0, 0x00, 0x00, 0x00, 0x00, 0x01, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; glClear(GL_COLOR_BUFFER_BIT); glColor3f(0.0f, O.Of, 0 Of); // Прежде всего необходимо указать формат хранения битов в массиве glPixelStorei(GL_UNPACK_LSB_FIRST, GL_FALSE), Поскольку работа с растровыми примитивами не должна вызвать никаких затруднений, я не стал придумывать для них специального примера, а воспользовался своей заготовкой для мастера (wizard) OpenGL, включенной в число примеров на прилагаемой дискете Этим объ- ясняется отличие внешнего вида окна от того, который вы наблюдали раньше
Вершины, примитивы 67 // Затем-применяемый способ выравнивания glPixelStorei(GL_UNPACK_ALIGNMENT, 1), // Пока не задана позиция растра, битовый массив не выводится glRasterPos2i(-16, -10), //И только теперь можно отображать битовый массив glBitmap(64, 20, 0, 3, 0 0, 0 0, Bitmap_logo), glFinish(); SwapBuffers( •wglGetCurrentDC()), Рис. 3.16. Выводим битовый массив Внимательно рассмотрев представленный фрагмент кода, можно заметить, что использования только представленной команды недостаточно для изо- бражения растрового примитива (битового массива) Необходимо сначала определить формат, в котором хранятся пиксели (команда glPixelStore), и текущую позицию растра (команда glRaster Pos) Эти команды можно вызы- вать и в другом порядке void glPixelStore[f i]( GLenum pname, GLtype param) Команда устанавливает режим хранения пикселей в памяти Аргумент pname задается символическим именем устанавливаемого параметра Всего опреде- лено 12 параметров, шесть из которых предназначены для режима чтения, а остальные — для режима записи данных в память Значение параметра pname Пояснение gl_packswap_bytes, gl_unpack_swap_bytes Если параметр param равен TRUE, то байты в многобайтных компонентах цвета, глубины, ин- декса цвета и индекса трафарета упорядочены в обратном порядке Например, для четырехбайт- ных компонентов байты Ь0, Ы, Ь2 и ЬЗ в памяти хранятся в следующем порядке ЬЗ, Ь2, Ы и Ь0
68 Глава 3 (продолжение ) Значение параметра pname Пояснение gl_pack_lsb_first, gl_unpack_lsb_first Если параметр рагат равен TRUE, то биты внут- ри байта упорядочены от младших разрядов к старшим Этот параметр применим только к би- товым массивам GL PACK_ROW_LENGTH, GL_UNPACK_ROW_LENGTH GL_PACK_SKIP_PIXELS, GL PACK_SKIP_ROWS, GL_UNPACK_SKIP_PIXELS, GL_UN PACK_SKIP_ROWS GL_PACK_ALIGNMENT, GL_UNPACK_ALIGNMENT Если значение параметра рагат больше нуля, то оно определяет число пикселей в строке Соответствующее значение параметра рагат предоставляет возможность пропускать задан- ное число пикселей или строк соответственно Соответствующее значение параметра рагат определяет тип выравнивания строки пикселей в памяти 1 — выравнивание по байту, 2 — по це- лому числу байтов, 4 — по слову, 8 — по двойно- му слову Аргумент param определяет pname значение для соответствующего параметра Значение параметра pname Тип Начальное Диапазон значение значений GL_PACK_SWAP_BYTES GL_UNPACK_SWAP_BYTES GL_PACK_LSB_FIRST GL_UNPACK_LSB_FIRST GL_PACK_ROW_LENGTH GL_UN PACK_ROW_LENGTH GL_PACK_S К IP_ROWS GL_UNPACK_SKIP_ROWS GL_PACK_SKIP_PIXELS GL_UNPACK_SKIP_PIXELS GL_PACK_ALIGNMENT GL.UNPACK-ALIGNMENT Булевское false TRUE или FALSE Булевское false TRUE или FALSE Целое 0 [0,оо) Целое 0 [0,х) Целое 0 [0,оо) Целое 4 1,2,4 или 8 Параметры GL_PACK_ используются при работе с командой glReadPixel, а параметры GL_UNPACK_ действительны только для команд glDrawPixel, glTexImagelD, glTexImage2D, glBitmap и gl Polygon Stipple Команды, воздействующие непосредственно на пиксели в буфере кадра, ис- пользуют понятие текущей позиции растра, которое включает три оконные
Вершины, примитивы 69 координаты xw, yw, zw, значение координаты отсечения wc, бит доступности и ассоциированные данные — цвет и координаты текстуры На данном эта- пе нас пока интересуют только оконные координаты Для их установки в OpenGL используются команды void g!RasterPos[2 3 4][s i f d](type coords) void g!RasterPos[2 3 4][s i f d] v (type coords) Вызов любой команды glRasterPos* определяется четырьмя координатами х, у, z и w При этом соблюдается следующее соглашение вызов glRasterPos?* устанавливает координаты х и у, координата z полагается равной 0, a w — 1; вызов glRasterPos3* устанавливает координаты х, у и z, a w полагается равной 1, вызов glRasterPos4* устанавливает все четыре координаты По умолчанию установлены следующие значения параметров, связанных с текущей позицией растра текущая позиция растра — (0, 0, 0, 1), бит дос- тупности — установлен, ассоциированный цвет — (1, 1, 1, 1), индекс — 1, координаты текстуры — (0, 0, 0, 1) Если по каким-либо причинам координаты текущей позиции растра не оп- ределены, то команды, которые работают с растром, игнорируются Вот, собственно, и все, что хотелось сказать о примитивах Уже теперь, пользуясь только полученными сведениями, можно создавать разнообраз- ные "рисунки" Однако еще ни слова не было сказано о "трехмерной графи- ке" — все, что мы рассматривали располагалось на плоскости Поэтому в заключение приведу пример программы, которая создает трехмерное изо- бражение параллелепипеда Первая трехмерная картинка Создать трехмерное изображение, пользуясь знаниями, полученными при чтении этой главы, совсем нетрудно Нужно только выполнить некоторые необходимые настройки, о которых мы еще не говорили Поэтому привожу практически полный текст программы Parallelepiped static void CALLBACK Reshape(int width, int height) { windW = (GLint)width, windH = (GLint)height, glViewport(0, 0, width, height), glMatrixMode(GL_PROJECTION), glLoadldentity(); // Чтобы увидеть трехмерную картинку, определим перспективу gluPerspective(60 Of, 1.5, 1.5f, 20 Of), glMatrixMode(GL_MODELVIEW);
70 static void CALLBACK Draw(void) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_E glColor3f(l.Of, 0.3f, 0 5f) ; // Разрешаем выполнение теста глубины glEnable(GL_DEPTH_TEST); // Пока нам достаточно однотонных цветов //glShadeModel(GL_FLAT), // Этот фрагмент нужен для придания "трехм glTranslatef(0 Of, 0 Of, -4.Of), glRotatef(30.Of, l.Of, 0 Of, O.Of), glRotatef(-30 Of, 0 Of, l.Of, O.Of); // Создаем параллелепипед glBegin(GL_QUAD_STRIP), glColor3f(l.Of, 0 Of, O.Of); glVertex3f(-0.5f, 0.5f, 1.5f); glVertex3f(-0 5f, -0.5f, 1 5f) ; glVertex3f( 0.5f, 0.5f, 1.5f); glVertex3f( 0.5f, -0.5f, 1.5f), glVertex3f( 0 5f, 0.5f, -lO.Of); glColor3f(O.Of, l.Of, O.Of); glVertex3f( 0.5f, -0.5f, -lO.Of); glVertex3f(-0.5f, 0.5f, -lO.Of); glVertex3f(-0.5f, -0 5f, -10 Of); glVertex3f(-0.5f, 0 5f, 1.5f), glVertex3f(-0 5f, -0.5f, 1 5f); glEnd(), glBegin(GL_QUADS); glColor3f(0 Of, 0 Of, 1 Of); glVertex3f(-0.5f, 0 5f, 1.5f); glVertex3f( 0.5f, 0.5f, 1.5f); glVertex3f( 0.5f, 0 5f, -lO.Of); glVertex3f(-0 5f, 0.5f, -lO.Of); glEnd(); glBegin(GL_QUADS); glColor3f(0 Of, l.Of, O.Of); glVertex3f(-0 5f, -0.5f, 1.5f); glVertex3f( 0.5f, -0.5f, 1.5f); glVertex3f( 0.5f, -0 5f, -lO.Of); glVertex3f(-0.5f, -0 5f, -lO.Of); glEnd(); glFinish();
Вершины, примитивы 71 void main(int argc, char **argv) ( windW = 400, windH = 300; auxInitPosition(200, 100, windW, windH); // Для того чтобы можно было работать в трех измерениях, // необходимо сообщить об этом системе (AUX_DEPTH16) auxInitDisplayMode(AUX_RGB I AUX_SINGLE | AUX_DEPTH16), if(auxInitWindow("Первый трехмерный объект") == GL_FALSE){ auxQuit() ; } Init () , auxReshapeFunc((AUXRESHAPEPROC)Reshape), auxMainLoop(Draw); И как обычно иллюстрация того, что получается в результате Рис. 3.17. Рисуем простейший трехмерный объект Если на этом этапе знакомства с OpenGL и трехмерной графикой вы захо- тите начать создавать свои "трехмерные картинки", то внимательно прочтите комментарии к приведенным фрагментам А еще лучше — просто скопируй- те необходимые настройки в свою программу Не задерживаясь больше, переходим к "глубине" и многим другим интерес- нейшим вопросам, ответы на которые дает нам OpenGL
72 Глава 4 Глубина, трафареты, туман и многое другое После того как вершины обработаны и собраны в примитивы, последние поступают на этап растеризации и обработки фрагментов (см рис 3 1) Растеризация — это процесс, при котором примитивы преобразуются в дву- мерный образ (рис 4 1) Каждая точка этого образа содержит такую инфор- мацию, как цвет, глубина и данные текстуры Все вместе — точка и ассо- циированная с ней информация называется фрагментом Каким образом осуществляется растеризация для разных типов примитивов в предыдущей главе при рассмотрении команд gi Point Size, glLineWidht, glLineStipple, glPolygonStipple, glPolygonMode, glPixelStore и glBitmap Поэтому сейчас на- шей задачей является знакомство с тем, как в OpenGL обрабатываются фрагменты С этапа сборки примитивов Команда gIDrawPixel Битовый массив Рис. 4.1 Для начала обратимся к буферу кадра (рис 4 2) Как видите, за этим поня- тием скрывается не один, а несколько буферов — цвета, глубины, трафарета
Гпубина, трафареты, туман и многое другое 73 и аккумулятора, каждый из которых успешно выполняет отведенную ему роль Число буферов и их размеры зависят от реализации библиотеки Более того, буфер цвета сам состоит из нескольких буферов левого и правого ли- цевых, левого и правого фоновых, а также некоторого числа вспомогатель- ных Но это в идеальном случае, который полностью соответствует задумке разработчиков На деле же поддерживаются далеко не все перечисленные буферы Например, в реализации Microsoft в настоящее время поддержива- ются только левые лицевой и фоновый буферы Обычно содержимое лице- вого буфера отображается на экране дисплея, а содержимое фонового неви- димо. Кроме того, буферы цвета состоят из фиксированного числа битовых плоскостей отдельно для красного, зеленого и синего цветов, а часто и для альфа Осталось только сказать, что в исходном состоянии содержимое всех буферов не определено и требует принудительной установки Рис. 4.2 Эта простая картинка (рис 4 2), после того как я ее нарисовал для себя, по- могла мне быстрее разобраться в операциях, выполняемых над фрагмента- ми, к которым мы и переходим Как видно из рис 4 3, основная обработка состоит из серии последовательных тестов принадлежности, отсечения, альфа, трафарета и глубины, а также дополнительных операций смешения и аппроксимация цветов, а также логических операций над фрагментом и со- держимым буфера кадра Рассмотрим каждую из этих операций и тестов Рис. 4.3
74 Гпава 4 Принадлежность пикселей контексту воспроизведения Это первый из серии тестов, и единственный, который выполняется всегда и без нашего участия Он служит для определения того факта, что пиксели фрагмента принадлежат контексту воспроизведения OpenGL В случае по- ложительного результата тестирования фрагмент направляется на следую- щий тест для проверки Если же результат тестирования отрицательный, то фрагмент отбрасывается Основное предназначение этого теста заключается в том, чтобы позволить операционной системе отслеживать поведение OpenGL, например, не проводить дальнейшую обработку, если окно OpenGL невидимо в текущий момент времени Отсечение Этот тест позволяет определить, лежит ли фрагмент внутри прямоугольника отсечения, задаваемого командой void glScissor( GLint х, GLint у, GLsizei width, GLsizei height) которая определяет прямоугольник, в оконных координатах, называемый прямоугольником отсечения Параметры х и у задают левый нижний угол этого прямоугольника, исходное значение (0, 0), a width и height — его ши- рину и высоту Когда контекст воспроизведения OpenGL первый раз при- соединяется к окну, эти параметры устанавливаются по размерам окна Проведение теста отсечения разрешается или блокируется командами glEnable и glDisable с аргументом GL SCISSORTEST Если проведение этого теста разрешено, то модифицированы командами рисования могут быть только пиксели, которые лежат внутри этого прямоугольника Коорди- наты окна имеют целочисленные значения, так что вызов glScissor(0, 0, 1, 1) разрешает модифицировать только левый нижний пиксель в окне, а glScissor(0, 0, 0, 0) запрещает изменение всех пикселей Когда этот тест за- блокирован, в прямоугольник отсечения включаются все компоненты окна Как обычно, легче всего понять, о чем идет речь, на примере (см програм- му Scissor на дискете) static void CALLBACK DrawScene(GLint mode) { GLint prim;
Гпубина, трафареты, туман и многое другое // Проверяем, в какой части окна будет изображаться сцена if(mode){ // если в правой, то // Устанавливаем параметры прямоугольника отсечения glScissor(5*windW/8, windH/4, windW/4, windH/2), // Разрешаем проведение теста отсечения glEnable(GL_SCISSOR_TEST); // Устанавливаем режим однородной заливки glShadeModel(GL_FLAT); // Отображаем границы прямоугольника отсечения (для наглядно glBegin(GL_LINE_LOOP) ; glColor3f(O.Of, O.Of, O.Of), glVertex2i(-windW/8, -windH/4), glVertex2i(-windW/8, windH/4), glVertex2i( windW/8, windH/4), glVertex2i( windW/8, -windH/4), glEnd(); } else //а если в левой . // Устанавливаем режим заливки с плавным переходом цветов glShadeModel(GL_SMOOTH); // Этот фрагмент включен для иллюстрации режима однородной з if(typePrim == GL_FALSE) prim = GL_POLYGON, else prim = GL_QUADS; // Отображаем четырехугольник glBegin(prim) ; // Этот цвет определяет цвет четырехугольника, построенного //из примитива многоугольника glColor3f(0 Of, 0 Of, 1 Of); // синий glVertex2i( 0, windH/З); // первая glColor3f(0.Of, 1 Of, O.Of), // зеленый glVertex2i(windW/6, 0); // вторая glColor3f(1.Of, O.Of, O.Of), // голубой glVertex2i( 0, -windH/З); // третья //А этот — в случае примитива четырехугольника gicoior3f(l.Of, i Of, O.Of), glVertex2i(-windW/6, 0), // четвертая glEnd(); // Блокируем действие теста отсечения, чтобы он не влиял //на другие компоненты сцены glDisable(GL_SCISSOR_TEST);
76 Гпава 4 static void CALLBACK Draw(void) { // Изображаем сцену в левой части окна DrawScene(0), // Изображаем сцену в правой части окна DrawScene(1); } Результаты выполнения программы представлены на рис 4 4 и 4 5 Рис. 4.4 Рис. 4.5 Мне представляется, что никаких дополнительных пояснений к действию теста отсечения не требуется, поэтому рассмотрим действие команды void glShadeModel(GLenum mode) где параметр mode — определяет символическое значение, представляющее способ закрашивания Доступны значения GL FLAT и GLSMOOTH Примитивы OpenGL могут быть закрашены двумя способами однотонным и с плавным переходом цветов Закрашивание с плавным переходом цветов (установлено по умолчанию) заставляет библиотеку рассчитывать цвета вершин, интерполируя их в зависимости от результатов растеризации, при- сваивая, обычно, различные цвета каждому результирующему фрагменту При однотонном закрашивании используется цвет одной вершины, который присваивается всем фрагментам, сформированным растеризацией единст- венного примитива Оба типа закрашивания неразличимы для точек Сравните изображения в левой и правой частях окна (рис 4 6) Результат действия этой команды хорошо виден даже на черно-белом рисунке книги
Гпубина, трафареты, туман и многое другое 77 Цвет примитива при однотонном закрашивании определяет единственная вершина Какая же? Будем считать, что нумерация вершин начинается с единицы Для линий цвет каждого i-ro отрезка распространяется до (i+ 1)-го Для многоугольников цвет закрашивания определяется цветом вершины в соответствии с таблицей Тип многоугольного примитива Номер вершины Многоугольник 1 — (всегда) Треугольники, имеющие общую грань / + 2 Связанные треугольники / + 2 Независимые треугольники 3/ Связанные четырехугольники 2/+ 2 Независимые четырехугольники 4/ Теперь стало ясно, почему на рис 4 7 отсеченный четырехуюльник темнее, а на рис 4 4 — светлее Осталось подвести итог к дальнейшей обработке допускаются только те фрагменты, для которых результат теста положительный По умолчанию этот тест заблокирован Прозрачность Подробный разговор о цвете еще впереди (хотя мы уже широко его исполь- зуем), а пока скажу лишь, что в OpenGL цвет можно задавать двумя спосо- бами: при помощи тройки (четверки) — красный, зеленый, синий (и альфа), или посредством указания индекса в соответствующей палитре Такое вступление здесь необходимо, потому что рассматриваемый тест действует только в режиме RGBA, т. е. при явном указании значений компонентов Цвета. В режиме индексации цвета считается, что фрагмент успешно прошел этот тест Теперь необходимо сказать несколько слов о четвертом параметре цвета — альфа, или прозрачности Как видно из названия, этот компонент определя- ет степень "прозрачности" фрагмента значение альфа, равное 0 0, подразу- мевает полную прозрачность, а значение 1 0 — полную непрозрачность со- ответствующего элемента Выполняется этот тест или нет, определяется, как обычно, командами glEnable/glDisable с аргументом GL ALPHA TEST Если тест не выполняет- ся, то считается, что он прошел успешно.
78 Гпава 4 Управляется тест командой void glAlphaFunc( GLenum func, GLclampf ref) где параметр ref задает значение ссылки, с которым сравнивается посту- пающее значение альфа Это значение приводится к диапазону [О, IJ О представляет самое маленькое, а 1 — самое большое возможное значение По умолчанию ссылка равна 0 Параметр func задает функцию сравнения значений альфа Допустимы следующие символические константы Константа Тест завершается положительно... GLNEVER GL_LESS GL.EQUAL GLLEQUAL GL.GREATER GL.NOTEQUAL GL_GEQUAL GL.ALWAYS Никогда Если поступающее значение меньше, чем ссылочное Если поступающее значение равно ссылочному Если поступающее значение меньше или равно ссылочному Если поступающее значение больше, чем ссылочное Если поступающее значение не равно ссылочному Если поступающее значение больше или равно ссылочному Всегда Установлена по умолчанию Альфа-тест отбрасывает фрагменты в зависимости от результата сравнения между поступающим значением альфа фрагмента и постоянным значением ссылки. Эта команда определяет ссылку и функцию сравнения Если ре- зультат сравнения положительный — поступающий фрагмент рисуется (в зависимости от условий следующих тестов — трафарета и буфера глубины) Если результат отрицательный, то в буфере кадра на месте расположения пикселя не происходит никаких изменений Команда действует на все записываемые пиксели, включая те, которые по- лучаются в результате сканирования точек, линий, многоугольников и бито- вых массивов, а также операций рисования и копирования пикселей, и не влияет на операции по очистке экрана Обычно прозрачность используется для управления смешением цветов, по- этому уместно рассмотреть их вместе Смешение цветов Прежде всего разберемся с самим понятием, которое используется только в режиме RGBA, и не действует при индексном определении цвета При сме- шении цветов поступающие значения красного, зеленого, синего и альфа компонентов цвета комбинируются с соответствующими значениями, хра-
Гпубина, трафареты, туман и многое другое 79 няшимися в буфере кадра Причем результат смешения зависит только от значений прозрачности, а то, каким образом цвета комбинируются, задается командой void glBlendFunc( GLenum sfactor, GLenum dfactor) Эта команда задает метод, используемый при смешении поступающих (источник) значений RGBA с уже хранящимися в буфере кадра (приемник) Параметр sfactor определяет способ расчета красного, зеленого, синего и альфа компонентов источника смешения и может принимать одно из девяти значений, заданных символическими константами GL ZERO, GL ONE, GL_SRC_ALPHA, GL_DST_ALPHA, GL_DST_COLOR, GL_ONE_MINUS_ DST_COLOR, GL_ONE_MINUS_SRC_ALPHA и GL_ONE_MINUS_DST_ ALPHA GL_SRC_ALPHA_SATURATE Аналогично для параметров буфера кадра. Для него допустимы восемь различных значений' GL_ZERO, GL.ONE, GL SRC COLOR, GL SRC ALPHA, GL DST ALPHA, GL_ONE_MINUS_SRC_COLOR, GL_ONE_MINUS_SRC_ALPHA и GL_ONE_MINUS_DST_ALPHA В следующей таблице описаны одиннадцать из возможных методов смеше- ния цветов. Каждый метод определен четырьмя масштабными коэффициен- тами, по одному на каждый компонент цвета. Приняты следующие услов- ные обозначения Rs, Gs, Bs, As — для источника, Rd, Gd, Bd, Ad — для при- емника. Все эти значения лежат в диапазоне от 0 до кс = 2П1с — 1, где с означает R, G, В или A, a m определяет число битовых плоскостей для красного, зеленого, синего и альфа компонентов соответственно Все мас- штабные коэффициенты Sc и Dc, которые зависят от используемых методов смешения цветов, ограничены диапазоном [0, 1] Параметр Метод GL_ZERO gl_one gl_src_color gl_one_minus_src_color gl_dst_color gl_one_minus_dst_color gl_src_alpha gl_one_minus_src_alpha gl_dst_alpha gl_one_minus_dst_alpha gl_src_alpha_saturate (0, 0, 0, 0) (1,1,1,1) (Rs/kR1 Gs/kG, Bs/kB, As/kA) (1, 1, 1, 1) - (Rs/kR, Gs/kG, Bs/kB, As/kA) (Rd/kR, Gd/kc, Bd/kB, Ad/kA) (1, 1, 1, 1) — (Rd/kR, Gd/kG, Bd/kB, Ad/kA) (As/kA, As/kA, As/kA, As/kA) (1, 1, 1, 1) - (As/kA, As/kA, As/kA, As/kA) (Ad/kA, Ad/kA, Ас/кд, A(j/kA) (1, 1. 1, 1) - (Ad/кд, Ad/кд, Ad/кд, Ad/kA) (i, i, i, 1), где i = min(As, kA — Ad/kA)
80 Глава 4 Чтобы определить значения RGBA пикселя после смешения, OpenGL ис- пользует следующие уравнения Rd = min(kR, RsSr + Rddn) Gd = min(kG, G^g + GddG) Bd = min(kB, BsSb + Bdds) Ad = min(kA, AsSA + AddA) где Sc — цвет, когда sfactor = GL SRC ALPHA, a dfactor = GL_ONE_MINUS_ SRC_ALPHA, уравнения упрощаются до простой подстановки Rd = Rs Gd = Gs Bd=Bs Ad Все это конечно здорово, но очень туманно Чтобы все встало на свои мес- та, рассмотрим некоторые примеры Прозрачность лучше реализовывать, используя команду glBlendFunc(GL_SCR_ALPHA, GL_ONE_MINUS_SRC_ ALPHA) с примитивами, отсортированными по глубине от наиболее к наи- менее отдаленным Примечательно, что такой способ расчета прозрачности не требует наличия битовых плоскостей альфа в буфере кадра Такой же вызов команды можно использовать при устранении ступенчато- сти точек и линий Чтобы оптимизировать устранение ступенчатости много- угольников, используйте команду glBlendFunc(GL_SRC_ALPHASATURATE, GLONE), отсортировав их предварительно от наиболее близкого до наибо- лее удаленного Результат хранится в битовых плоскостях альфа приемника, которые должны быть представлены для этой команды смешения, чтобы получить корректный результат Если для рисования доступен более чем один буфер цвета, в каждом из них смешение осуществляется раздельно По умолчанию смешение заблокировано Для его включения и выключения, как обычно, используются команды glEnable и glDisable, на этот раз с аргу- ментом GL BLEND На рис 4 6 и 4 7 показаны результаты выполнения программы Alpha, фраг- менты которой приведены ниже Здесь требуются некоторые пояснения Сначала общее замечание по подключению к программе возможностей об- работки нажатия клавиш на клавиатуре Использование библиотеки GLAUX упрощает эту задачу до тривиальной Посмотрите на фрагмент функции main, и вам сразу все станет понятно (можно также вернуться к главе 2) void main(int argc, char **argv) { auxKeyFunc(AUX_0, Key_0);
Глубина, трафареты, туман и многое другое 81 auxKeyFunc(AUX_9, Кеу_9) , auxMainLoop(Draw); Рис. 4.7 Рис. 4.6 Единственное, что осталось сделать, это написать функции обработки нажа- тия соответствующих клавиш static void CALLBACK Key_0(void) { // Переключаем функцию в тесте прозрачности if(alphaFunc == GL_LESS) alphaFunc = GL_GREATER; // см рис 4 6 else alphaFunc = GL_LESS, // см рис 4.7 } static void CALLBACK Key_6(void) { // При нажатии клавиши <6> устанавливаем следующие функции // смешения цветов blendSrc = GL_SRC_ALPHA_SATURATE, blendDst = GL_SRC_COLOR; } Теперь собственно рисование. static void CALLBACK DrawScene(GLint mode) { glColor4f(l.Of, 0.4f, 0 3f, 0.75f), // Рисуем "нижний" прямоугольник glRecti(-windW/4 + 20, -windH/2 + 20, windW/4 - 20, 20);
82 Глава 4 // Левая половина при заблокированных тесте прозрачности //и смешении цветов, а правая-с разрешенными if(mode){ glEnable(GL_BLEND); glEnable(GL_ALPHA_TEST) ; } // Рисуем "правый" прямоугольник glColor4f(O.Of, 0.5f, l.Of, 0.25f); glRecti( 20, -windH/2 + 20, windW/4 - 10, windH/2 - 20); // Блокируем проведение теста альфа glDisable(GL_ALPHA_TEST); // Рисуем "левый" прямоугольник glColor4f(0.75f, 0.5f, 0.75f, 0.25f), glRecti(-windW/4 + 10, -windH/2 + 20, — 20, windH/2 — 20); // Блокируем режим смешения цветов glDisable(GL_BLEND); } static void CALLBACK Draw(void) { // Устанавливаем функции: // смешения цветов glBlendFunc(blendSrc, blendDst); // и теста прозрачности glAlphaFunc(alphaFunc, 0.5f); // Рисуем в левой части окна DrawScene(0); // Рисуем в правой части окна DrawScene(1); Попробуем разобраться, почему получились такие картинки С тестом про- зрачности все понятно Если значение альфа фрагмента меньше (GL_LESS), чем заданное значение 0 5, фрагмент "рисуется", а если больше (GL_GREATER), то нет Все несколько сложнее и интереснее со смешени- ем цветов Рассмотрим только один случай — наложение "левого" прямоугольника на "нижний" после вызова функции glBlendFunc(GL_SRC_ALPHA_SATURATE, GL SRC COLOR), чтобы продемонстрировать принцип, после чего все
Гпубина, трафареты, туман и многое другое 33 окончательно станет понятно Обратите внимание на существенный момент мы разблокировали режим смешения цветов до рисования прямоугольников Поэтому в рассматриваемом случае сначала цвет "нижнего" прямоугольника будет смешан с цветом фона, после чего результирующий цвет будет смешан с цветом "левого" прямоугольника, т е процесс смешения цветов будет складываться из двух этапов На первом этапе □ Приемник Rj = 1 0, Gd — 1 0, Bj = 1 0, Ad = 1 0 (фон) □ Источник: Rs = 1 0. Gs = 0 4, Bs = 0 3, As = 0 75 ("нижний” прямоуюльник) □ Для красного, зеленого и синего компонентов цвета отведено по 16 бито- вых плоскостей, следовательно, кс = 65 535, где с = {R, G, В}, и его мож- но исключить из вычислений, положив равным 1 □ Битовых плоскостей для альфа нет (см рис 1 2), следовательно, kc = 1 Определим цвет источника после выполнения функции с аргументом GL_SRC_ALPHA_SATURATE Имеем S = (i, i, i, 1), где i = min (0 75, 1 — 1.0/1) = 00 Таким образом, Sr = 0 0, Sq = 0 0, Sb = 0 0, SA = 10 Цвет приемника после выполнения функции GL SRC COLOR, равен цвету источника до преобразования Dr = 1 0, Dg = 0 4, Db = 0 3, Da = 0 75 Теперь можно определить значения красного компонента в результирующем цвете: R = min(l, Rs*Sr + Rd*Dr) = min(l, 1 0*0 0 + 1 0*1 0) = min(l, 1) = 1 0 Аналогично находим остальные значения G = 0 4, В = 0 3, А = 1 0 В результате изменилось только значение альфа На втором этапе' □ приемник Rd = 1 0. Gd = 0 4. Bd = 0 3. Ad = 1 0 □ источник Rs = 0 75, Gs = 0 5, Bs = 0 75, Ач = 0 25 Проделав аналогичные вычисления, получим окончательный результат R = 0.75, G = 0 2, В = 0 225, Ad = 0 5 Вряд ли эти значения о чем-нибудь говорят вам Чтобы убедиться в пра- вильности расчетов, вставьте в программу строки glColor4f(0 75f, 0 2f, 0 225f, 0 5f), glRecti(-20, windH/2 - 30, 20, w^ndE/2 10;; и убедитесь, что действительно цвета после смешения и рассчитанный сов- падают В качестве самостоятельного упражнения можно предложить опре- делить место вызова команды glEnable(GL BLEND), чтобы на результат смешения цветов не влиял цвет фона В отличие от рассмотренного примера, на практике чаще приходится ре- шать обратную задачу какие параметры цветов источника и приемника
84 Гпава 4 нужно выбрать, чтобы получить требуемое изображение Это, конечно, не- сколько более сложная задача, но тоже решаемая Но вернемся к последовательности обработки фрагментов, которую мы не- сколько нарушили Если тест прозрачности завершился положительно, фрагмент направляется на тест трафарета Трафарет Очень мощное средство, поддерживаемое к тому же специальным буфером Здесь мы рассмотрим основные характеристики теста трафарета, а практи- ческое использование его и соответствующего буфера будет продемонстри- ровано при обсуждении вопросов освещенности в главе 7 Наложение трафарета, подобно z-буферизации (о которой речь пойдет дальше), разрешает или блокирует рисование на пиксельном уровне При этом рисование осуществляется в плоскости трафарета, для чего применя- ются примитивы, после чего воспроизведение образов осуществляется с использованием буфера трафарета, чтобы замаскировать часть экрана На- ложение трафарета обычно используется в многопроходных алгоритмах вос- произведения для реализации специальных эффектов, таких как отбелива- ние, оконтуривание и стереовоспроизведение Тест трафарета позволяет отбросить фрагмент, базируясь на результате сравнения значения в буфере трафарета с задаваемым ссылочным значе- нием. Как обычно, выполнение теста разрешается и блокируется коман- дами glEnable/glDisable с аргументом GL STENCIL TEST Для проведения этого тестирования в OpenGL предусмотрены две команды, одна из кото- рых отвечает за сравнение — glStencilFunc, а вторая позволяет определить действия, базирующиеся на результате проверки трафарета — glStencilOp Рассмотрим каждую из них. void glStencilFunc( GLenum func, Glint ref, GLumt mask) Эга команда позволяет определить в параметре func функцию сравнения значения хранящегося в буфере трафарета (stencil) с некоторым задаваемым (параметр ref) Сравнение, однако, осуществляется не прямо, а посредством некоторого дополнительного значения, определяемого дополнительным па- раметром (mask) Функция сравнения может принимать одно из восьми зна- чений Константа Тест завершается положительно... GL.NEVER Никогда GL.LESS Если (ref &. mask) < (stencil & mask)
Глубина, трафареты, туман и многое другое 85 (продолжение) Константа Тест завершается положительно... GL-EQUAL GLJ-EQUAL GL_GREATER GL_NOTEQUAL GL_GEQUAL GL.ALWAYS Если (ref& mask) = (stencil & mask) Если (ref& mask) < (stencil & mask) Если (ref & mask) > (stencil & mask) Если (ref & mask) * (stencil & mask) Если (ref& mask) > (stencil & mask) Всегда Параметр ref задает значение ссылки для тестирования трафарета и може! принимать любые значения из диапазона [О, 2П - 1], где п — число битовых плоскостей в соответствующем буфере Параметр mask задает маску, которая поразрядно умножается (логическое И) на значение ссылки и на хранящее- ся значение трафарета Изначально тест трафарета заблокирован Если буфера трафарета нет, то модификация трафарета не может произойти и это трактуется как положи- тельный результат теста трафарета void glStencilOp( GLenum fail, GLenum zfail, GLenum zpass) Команда позволяет определить действия, которые выполнит OpenGL в слу- чае отрицательного результата теста трафарета, а также те. которые будут выполнены по результатам проведения тестирования глубины Параметр fail задает действие, которое будет выполняться, если тест трафа- рета дал отрицательный результат. Допускаются следующие шесть символи- ческих констант Константа Действие gl.keep GL.ZERO gl_replace Сохранить текущее значение в буфере Установить значение буфера трафарета в нуль Установить значение буфера трафарета в значение ref, определен- ное командой gIStencilFunc gljncr Увеличить текущее значение буфера трафарета и привести в соот- ветствие с максимально допустимым значением 2т -1, где т — число плоскостей в буфере трафарета GL-DECR Уменьшить текущее значение буфера трафарета и привести в со- ответствие с нулем GLJNVERT Поразрядно инвертировать текущее значение буфера трафарета
86 Глава 4 Два других параметра — zfail и zpass определяют действия, которые будут предприняты в случае положительного результата выполнения теста трафа- рета: zfail — в случае отрицательного, и zpass — в случае положительного завершения теста глубины Допускаются те же символические константы, что и перечисленные в предыдущей таблице. Предположим, что нужно "вырезать" в окружности прямоугольное окно, чтобы открыть доступ к находящемуся за ней изображению, аналогично то- му, что изображено на рис. 4 8. Каковы должны быть наши действия? Рассмотрим один из возможных ва- риантов: □ Определить значение в буфере трафарета для фона, например, gl ClearStencil(0x01) □ Нарисовать окружность, изменив для нее значение в буфере трафарета, например, на 0x02 □ Определить прямоугольник "вырезки", установив для него значение в бу- фере глубины равным 0x01 — такое же, как у фона □ Нарисовать желаемый рисунок, определив в качестве параметров для ус- пешного прохождения теста трафарета значение 0x01 Таким образом, в буфере трафарета должно быть "нарисовано" что-то ана- логичное рис 4 9 Для фона устанавливаем 0x01 Рис. 4.9
Гпубина, трафареты, туман и многое другое Обратимся к коду программы Комментарии подробно поясняют все выпол- няемые действия static void Init(void) { // Определяем цвет фона и создаем массивы вершин и цветов // окружности, как это было сделано в программе Polygons // Определяем значение, в которое будет установлен буфер трафарета // Это значение для фона glClearStencil(0x1); // Определяем значение маски трафарета glStencilMask(OxFF), ) static void CALLBACK DrawScene(GLenum mode) { // Разрешаем проведение теста трафарета для изображения // в правой части окна if(mode == GL_TRUE) glEnable(GL_STENCIL_TEST), // Определяем функцию и значения для сравнения // Окружность рисуется всегда glStencilFunc(GL_ALWAYS, 0x02, 0x02); // Определяем действия, которые будут выполняться // в случае положительного результата — второй и третий аргументы, // первый нас не интересует, т к. тест всегда проходит, // в результате замены для окружности в буфере трафарета // будет храниться значение 0x02 — значение второго аргумента // предыдущей команды glStencilOp(GL_KEEP, GL_ REPLACE, GL_REPLACE), // Воспроизводим окружность glDrawArrays(GL_POLYGON, 0, 64); // Меняем параметры теста трафарета, // прямоугольник вырезки рисоваться не должен glStencilFunc(GL_GEQUAL, 3, 2), // Важно только изменить для него значение // в буфере трафарета на 0x01 glStencilOp(GL_DECR, GL_KEEP, GL_KEEP), // Определяем область вырезки glColor3ub(0, 200, 200); glBegin(GL_QUADS); glEnd();
88 // Еще раз меняем параметры теста; // следующий объект будет рисоваться только в тех областях, // для которых значение в буфере трафарета равно 0x01 glStencilFunc(GL_EQUAL, 1, 1); // Рисуем "цветные" однотонные треугольники glShadeModel(GL_FLAT); glBegin(GL_TRIANGLE_FAN); glEnd(); glShadeModel(GL_SMOOTH); // Блокируем тест трафарета glDisable(GL_STENCIL_TEST); } static void CALLBACK Draw(void) { // Очищаем не только буфер цвета, но и буфер трафарета glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // Рисуем в левой части окна — тест трафарета заблокирован DrawScene(GL_FALSE) ; // Рисуем в правой части окна — тест трафарета разрешен glColor3f(0.5f, O.Of, 0.5f); DrawScene(GL_TRUE), Рис. 4.10
Глубина, трафареты, туман и многое другое gg Как видите, все достаточно просто, и в то же время это очень мощное сред- ство. Нужно только аккуратно продумать последовательность действий А теперь, чтобы убедиться в том, что все действительно понятно, попробуйте объяснить результат, который получается при разворачивании окна про- граммы на весь экран (рис 4 10). Осталось только сказать об установках, используемых по умолчанию вы- полнение теста блокировано, значение ссылки (параметр ref) равно 0, все разряды маски установлены в 1, в качестве функции сравнения используется GL_ALWAYS, а все три операции установлены в GL_KEEP При успешном завершении теста трафарета фрагмент направляется на сле- дующую проверку Глубина Глубина, или координата z — основной параметр для получения трехмерных изображений Отождествляя глубину с z-координатой, я несколько упрощаю это понятие, полагая, что начало системы координат находится в точке на- блюдения Может создаться впечатление, что глубину объекта можно пере- дать только при помощи z-координаты Однако существуют и другие спосо- бы. Например, наша интерпретация понятия глубины часто основывается на том, что меньший объект расположен дальше (глубже), чем тот, который имеет большие размеры Глубину объекта можно представить путем измене- ния уровня яркости объекты, которые предположительно находятся ближе к наблюдателю, должны воспроизводиться с увеличенной яркостью (рис. 4.11) Дополнительную информацию о глубине можно получить путем отсечения По оси z Воспроизводимый объект пересекается плоскостью, отсекающей его удаленную часть Если при этом динамически изменять положение зад-
90 Гпава 4 ней отсекающей плоскости, то можно получить больше информации о глу- бине С глубиной мы постоянно будем сталкиваться на протяжении оставшейся части книги, и поэтому от вводных слов перейдем к одному из мощнейших тестов, имеющихся в OpenGL, — тесту глубины Фактически этот простой тест служит основой удаления невидимых линий и поверхностей Каждое поступающее значение глубины фрагмента сравнивается с имеющимся в буфере глубины и выводится на экран (или нет) в зависимости от результа- тов выполнения этого теста Более того, как вы узнали из предыдущего раз- дела, результат этого теста влияет на содержимое буфера трафарета, что в совокупности представляет поразительно мощное средство создания изо- бражений практически любой степени сложности Но перейдем непосредственно к тесту Исходно он заблокирован, как и все остальные, и "включается/выключается" знакомыми командами glEnable/glDisable с аргументом GL DEPTH TEST Функция сравнения, ис- пользуемая в тесте, задается командой void glDepthFunc(GLenum tunc} Вызов этой команды позволяет определить ту функцию, которая будет ис- пользоваться для сравнения каждого поступающего z-значения с тем, кото- рое хранится в буфере глубины Функцию, используемую для сравнения глубин, определяет параметр fane, который может принимать одно из сле- дующих значений: Константа Тест завершается положительно... GL_NEVER Никогда GL.LESS Если поступающее z-значение меньше, чем хранящееся в буфере GLEQUAL Если поступающее z-значение равно хранящемуся в буфере GLLEQUAL Если поступающее z-значение меньше или равно, чем хра- нящееся в буфере GL_GREATER Если поступающее z-значение больше, чем хранящееся в буфере GL_NOTEQUAL Если поступающее z-значение не равно хранящемуся в бу- фере GL.GEQUAL Если поступающее z-значение больше или равно храняще- муся в буфере GL_ALWAYS Всегда По умолчанию используется константа GL LESS Если буфера глубины нет, то считается, что тест всегда завершается положительно
Глубина, трафареты, туман и многое другое 91 Пользоваться им настолько просто, что я ограничусь фрагментом програм- мы и изображением результата ее работы (рис 4 12), не вдаваясь в подроб- ные объяснения1 static void CALLBACK DrawScene(GLenum mode) { // Если разрешено проведение теста глубины, // устанавливаем соответствующую маску if(mode == GL_TRUE){ glEnable(GL_DEPTH_TEST); if(blend) glEnable(GL_BLEND), else glDisable(GL_BLEND); } else{ glDisable(GL_DEPTH_TEST); } // Предусматриваем возможность изменения порядка вывода примитивов if(order){ // Рисуем первый примитив, потом второй glCallList(1); glCallList(2); } else{ /1 Наоборот, сначала второй, а потом первый примитив glCallList(2); glCallList(1); glDisable(GL_BLEND); } static void CALLBACK Draw(void) { // Очищаем не только буфер цвета, но и буфер глубины glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); Помимо работы с буфером глубины здесь же реализованы еще некоторые возможности, Узнать о которых можно из файла Readme, имеющегося на дискете, или экспериментальным пУтем, нажимая клавиши <1> - <4>
92 Гпава 4 // Остальная последовательность действий полностью аналогична // уже неоднократно рассматривавшейся В приведенном фрагменте появились вызовы новых команд — glCallList, ко- торые требуют пояснений, во-первых, чтобы знать, что это такое, а во- вторых, я часто использую их в своих программах, и если вы захотите дочи- тать книгу до конца, то должны представлять, о чем идет речь Названная команда — одна из группы команд, работающих со списком изображений, о котором мы и поговорим, прервав последовательность рассмотрения опера- ций над фрагментами Списки изображений Список изображений представляет собой простую группу команд и аргумен- тов, которые были сохранены для последующего выполнения То есть, в от- личие от непосредственного воспроизведения, список можно подготовить в любой момент времени и выполнять по мере необходимости Формирование списка изображений начинается с вызова команды void glNewl_ist( G Lu । nt list, GLenum mode) Параметр list представляет собой положительное целое число, используемое в дальнейшем для идентификации списка изображений, a mode — символи- ческая константа, определяющая режим сборки GL_COMPILE Команды только накапливаются в списке без вы- полнения GL_COMPILE_AND_EXECUTE Команды сначала выполняются и только потом за- носятся в список изображений
Гпубина, трафареты, туман и многое другое gg Эта команда открывает новый список изображений Все команды, распо- ложенные после нее, включаются в список, до тех пор, пока не встретится команда void glEndl_ist() после которой OpenGL возвращается в свое состояние непосредственного выполнения команд Порядок, в котором команды располагаются в списке, жестко определяет порядок, в котором они будут выполняться Как видите, пара команд glNewList/glEndList представляет собой своего рода командные скобки, подобно рассматривавшимся ранее glBegin/glEnd Следует иметь в виду, что если в качестве параметра list списка изображений использовать уже существующее имя, то новый список не создается, а после вызова команды glEndList существующий список будет заменен на новый Рассмотрим фрагмент кода, взятый из программы Depth, рассматривавшей- ся ранее static void Init(void) { glClearColor(0.66f, 0 66f, 0.66f, 1 Of), // Открываем список изображений под номером 1 glNewList(1, GL_COMPILE); // Задаем цвет создаваемого примитива glColor3f(0.9f, l.Of, O.Of); // Теперь сам примитив — треугольник glBegin(GL_TRIANGLES); glVertex3i(-windW/4 + 30, windH/2 - 20, 0); glVertex3i( windW/4 - 10, 0, 0); glVertex3i(-windW/4 + 30, -windH/2 + 20, 0); glEnd(); // Закрываем список изображений номер 1 glEndList() ; // Открываем список изображений под номером 2 glNewList(2, GL_COMPILE); // Задаем цвет создаваемого примитива; он отличается от того, // который использовался в первом списке glColor3f(0 Of, O.Of, 0 9f); // Теперь сам примитив — треугольник, тоже другой glBegin(GL_TRIANGLES), glVertex3i( windW/4 - 30, windH/2 - 20, -50), glVertex3i( windW/4 - 30, -windH/2 + 20, -10), glVerrex3i(-windW/4 + 10, 0, 75); glEnd();
94 Гпава 4 // Закрываем список изображении номер 2 glEndList (); } Обратите внимание на аргумент GL COMPILE команды glNewList, который говорит о том, что перечисленные ниже команды не будут выполняться в текущий момент После того как список создан, выполнить его команды можно в любой мо- мент времени Для этого достаточно вызвать одну из команд glCallList или glCallLists void glCallList(GLumt list) Параметр list задает целочисленное имя списка изображений, команды ко- торого должны быть выполнены Если подставляемое значение не опреде- лено как список, то вызов команды игнорируется Для выполнения нескольких списков изображений в OpenGL имеется команда void glCallLists( GLsizei n, GLenum type, const GLvoid* lists) Единственный вызов команды glCallLists приводит к выполнению всех спи- сков изображений, имена которых берутся из специального массива Пара- метр и определяет количество списков изображений, которые должны быть выполнены, a lists — указатель на массив смещений Тип смещения опреде- ляется параметром type, который может принимать следующие значения GL_BYTE Параметр lists трактуется как массив байтов из диапазона [-128, 127] GL_UNSIGNED_BYTE Параметр lists трактуется как массив неотрицательных байтов из диапазона [0, 255] GL.SHORT Параметр lists трактуется как массив двухбайтовых целых из диапазона [-32768, 32767] GL_UNSIGNED_SHORT Параметр lists трактуется как массив неотрицательных двухбайтовых целых из диапазона [0, 65536] GLJNT Параметр lists трактуется как массив четырехбайтовых целых GLJJNSIGNEDJNT Параметр lists трактуется как массив неотрицательных четырехбайтовых целых GL_FLOAT Параметр lists трактуется как массив четырехбайтовых вещественных значений
Глубина, трафареты, туман и многое другое 95 GL_2_BYTES GL_3_BYTES GL_4_BYTES Параметр lists трактуется как массив байтов (без знака), причем каждая пара байтов определяет единственное имя списка изображений Параметр lists трактуется как массив байтов (без знака), причем каждая тройка байтов определяет единственное имя списка изображений Параметр lists трактуется как массив байтов (без знака), причем каждая четверка байтов определяет единственное имя списка изображений В трех последних случаях целочисленное смещение формируется в соответ- ствии со следующим алгоритмом offset = 0; for( i=0; i <= b, i++) ( offset = offset « 8; offset += byte; } где b принимает значения 2, 3 или 4, в зависимости от параметра Перед использованием glCallLists можно вызвать команду void glListBase(GLuint base) которая добавляет постоянное целочисленное смещение base к каждому имени списка изображений из массива lists команды glCallLists до того, как он будет выполнен По умолчанию дополнительное смещение равно 0 Если в результате вычислений получится имя несуществующего списка изобра- жений, то не выполняется ничего Допускаются вложенные вызовы команд glCallList и glCallLists из других списков изображений Однако уровень вложенности не может быть больше 64, а его конкретное значение зависит от реализации Как и многие другие команды, вызов glCallList или glCallLists может привес- ти к изменению текущего состояния OpenGL, если в списке располагаются соответствующие команды (например, в приведенном фрагменте после вы- полнения первого списка текущим станет желтый цвет) Для того чтобы со- хранить текущее состояние, следует перед выполнением списка вызвать ко- манды glPushAttrib или glPushMatrix, а после — glPopAttrib или glPop Matrix. соответственно1 Чтобы знакомство с командами списка изображений было полным, приве- дем еще две Первые команды из представленных пар мы уже рассматривали, а ко вторым обязательно, ввиду их важности, обратимся при рассмотрении преобразований координат и проекций
96 Гпава 4 int glGenLists(GLsizei range) Эта команда возвращает значение п (текущее число созданных непрерывных пустых списков изображений), параметр range задает число формируемых непрерывных пустых списков изображений, начиная ели последовательно до (л + range — 7) Если range равно 0, или нет доступной группы непре- рывных имен, или если формируется какая-либо ошибка, то списки изо- бражений не формируются и возвращается нуль void glDeleteLists( GLint list, GLsizei range) Эта команда удаляет непрерывную группу списков изображений Удаляются все списки из диапазона list <d < list + range -1 Имена внутри диапазона, которые не имеют ассоциированных списков изображений, игнорируются Если параметр range равен нулю, то не происходит вообще ничего Параметр list задает целочисленное имя первого удаляемого списка изображений, а range — общее число удаляемых списков В OpenGL есть несколько команд, которые не накапливаются в списке изо- бражений, а сразу же выполняются, независимо от режима создания списка Перечислим их, хотя и не со всеми из них мы успели познакомиться gllsList, gl Gen Lists, glDelete Lists, glFeedbackBuffer, glSelectBuffer, glVertexPomter, glNormalPointer, glColorPointer, gllndexPointer, glTexCoordPointer, glGenTexture, glDeleteTexture, glEdgeFlagPointer, glRender Mode, glRead Pixels, glPixel Store, glFlush, glFinish, gllsEnabled и все команды glGet Тест глубины — последний из серии тестов, выполняемых OpenGL по на- шему желанию Осталось познакомиться с двумя последними этапами обра- ботки фрагментов: интерполяцией цветов (dithering) и логическими опера- циями над цветами Интерполяция цветов Этот этап предназначен для присваивания фрагменту значения цвета (индекса) на основе некоторых двух значений (индексов) q е {max{0, {0,| с | -1} Какой именно алгоритм используется для интерполя- ции, разработчики библиотеки не сообщают, оговаривая только, что в лю- бом случае формируемое значение зависит лишь от поступившего и о г оконных координат фрагмента Включение и выключение работы этого блока осуществляется как обычно При этом используется аргумент GL_DITHER Исходно этот режим забло- кирован Мне не удалось подобрать приемлемый пример для иллюстрации влияния этого режима Но некоторые изменения можно увидеть на экране, запустив программу Depth и нажимая клавишу <5>
Глубина, трафареты, туман и многое другое ду Логические операции Вот мы и подошли к заключительному этапу обработки фрагментов Логи- ческие операции выполняются над поступившим значением фрагмента и тем, которое находится в соответствующем месте буфера кадра Используйте значение GL_COLOR_LOGIC_OP или GL INDEX LOGIC OP для разре- шения или блокировки выполнения этого этапа Действия, выполняемые над значениями цветов задаются командой void gll_ogicOp(GLenum opcode) задающей логическую операцию, которая, когда она разрешена, выполняет- ся над поступающим значением цвета (индексом) и тем, который находится в соответствующем месте буфера кадра В качестве логических операций используются стандартные логические операции языка С При наличии не- скольких буферов цвета операции применяются к каждому из них незави- симо. Сама логическая операция задается параметром opcode и может при- нимать следующие значения (s — цвет источника, ad— соответствующее значение в буфере кадра) Аргумент Результирующее значение GL_CLEAR 0 GL_SET 1 GL-COPE s GL_COPE_INVERTED is GL_NOOP d gljnvert !d GL_AND s&d gl_nand (s&d) GL_OR s | d gl_nor '(s | d) gl_xor s л d gl_equiv i(sAd) GL_AND_REVERSE s& !d gl_and_inverted is &d GL_OR_REVERSE s | !d GLJDRJNVERTED !s | d Для тех, кто знаком с языком С, а без этого трудно понять материал книги, Все перечисленные операции хорошо знакомы и не требуют дальнейших
98 Гпава 4 пояснений. Поэтому посмотрите только на результат работы программы по- сле нажатия клавиши <4> (рис. 4.13) и фрагмент кода, который это осуще- ствляет. Рис. 4.13 static void CALLBACK DrawScene(GLenum mode) { if(mode == GL_TRUE){ glEnable(GL_DEPTH_TEST); if(1 color) // Разрешаем выполнение логических операций glEnable(GL_COLOR_LOGIC_OP); } else{ glDisable(GL_DEPTH_TEST); // Блокируем выполнение логических операций glDisable(GL_COLOR_LOGIC_OP); glDisable(GL_BLEND); } static void CALLBACK Draw(void) { glLogicOp(GL_XOR);
Глубина, трафареты, туман и многое другое 99 Разумеется, я не перебрал всех комбинаций проведения тестов и доступных режимов обработки фрагментов — сделать это просто нереально, да и неза- чем Поэтому советую приостановиться на этом месте и поэкспериментиро- вать, помня при этом, что под "экспериментом" понимается систематизиро- ванное, т е предварительно продуманное изменение параметров, а не бес- системное переключение установок А мы пойдем дальше и добавим немножко "тумана" или "размытости" Один из способов передачи глубины Я уже упоминал различные способы визуализации глубины изображения В этом разделе мы познакомимся с одним из них Взгляните еще раз на рис. 4.1, иллюстрирующий этап растеризации Один из блоков этой диа- граммы как раз и предназначен для реализации тех задач, которые нас сей- час интересуют. Речь идет о "наложении тумана" или внесении нечеткости в изображение1, применяемом, большей частью, в качестве дополнительного средства визуальной передачи информации о глубине Сразу же, чтобы больше не возвращаться, упомяну об аргументе GL FOG команд glEnable/glDisable, для включения и отключения этого режима, по- лагая в дальнейших рассуждениях, что этот режим включен И еще одно замечание этот этап обработки идет вслед за наложением текстуры на объ- ект, которое мы еще не рассматривали и поэтому считаем, что текстуры просто нет В этом случае цвет фрагмента смешивается с цветом тумана (fog), с исполь- зованием специального коэффициента f, который получается в результате вычисления значения одного из трех выражений f = e~d'z f = e~(d*z)2 f _ end - z end - start которые определяют способ учета расстояния от объекта до наблюдателя соответственно экспоненциальный, квадратичный экспоненциальный и ли- нейный Элементы, входящие в эти выражения, задаются командой void glFog[f i][v]( GLenum pname, GLfloat param[s]) При дальнейшем изложении я использую для этого режима три разных названия наложение тумана, размытость и нечеткость изображения Все они обозначают одно и то же
100 Глава 4 которая служит для установки параметров нечеткости изображения при смешивании цвета тумана с цветом фрагментов пикселей, с использованием коэффициента смешивания f, который рассчитывается для каждого из трех случаев, в зависимости от режима. Параметр pname определяет либо единст- венное значение параметра тумана, либо указатель на массив значений(я) Допускается использование следующих символических констант Константа Примечание GL_FOG_MODE Параметр рагат— единственное целое или вещественное значение, определяющее уравнение, которое будет исполь- зоваться при расчете коэффициента нечеткости изображе- ния f Допустимы три символические константы GL LINEAR, GL_EXP и GL_EXP2 По умолчанию установлен режим GL_EXP GL_FOG_DENSITY Параметр рагат— единственное целое или вещественное значение, которое определяет плотность тумана Это значе- ние используется в обоих экспоненциальных уравнениях и может быть только неотрицательным По умолчанию 1 0 GL_FOG_START Параметр рагат— единственное целое или вещественное значение, которое определяет "начало" — ближнее расстоя- ние, используемое в линейном уравнении — s По умолчанию 00 GL_FOG_END Параметр рагат— единственное целое или вещественное значение, которое определяет "конец"— дальнее расстоя- ние, используемое в линейном уравнении По умолчанию 1 0 GL_FOGJNDEX Параметр рагат— единственное целое или вещественное значение, которое определяет i (f) — индекс цвета тумана По умолчанию 0 0 Для векторной версии команды можно использовать также значение GL_FOG_COLOR Параметр params содержит четыре целых или вещественных значения, которые определяют C(f) — цвет тумана Целые значения предварительно линейно приводятся к диапазону [-10, 1 0] таким образом, что наибольшее положительное значение отображается в 1 0, а наибольшее отрицательное — в -1 0 После преобразования все компоненты цвета приво- дятся к диапазону [0, 1] По умолчанию цвет тумана установ- лен в (0, 0, 0, 0) Параметр param задает единственное, a params несколько значений, которые должны быть установлены для pname Для GLFOGCOLOR требуется мас- сив из четырех значений, а для всех остальных массив содержит единствен- ное значение Независимо от режима учета нечеткости изображения коэффициент f после вычислений приводится к диапазону [0, 1] Затем для режима RGBA цвет
Глубина, трафареты, туман и многое другое Ю1 фрагментов Сг заменяется на Cr- = fCr + (1 - f)Cf, а в режиме индексации цвета на ir- = fir + (1 - f)if Установка любых параметров и их значений не влияет на операции очистки буферов Для того чтобы придать изображению некоторый нечеткий вид, необходимо определить следующие значения уравнение, используемое для вычисления степени нечеткости в зависимости от расстояния до объекта, три вещест- венных значения, задающих расстояние, начало и конец учета размытости, цвет RGBA или индекс, определяющий цвет тумана Затем нужно сообщить OpenGL о включении этого режима В исходном состоянии этот режим заблокирован, режим GL FOG MODE установлен в значение GL EXP; d = 1 0, end = 1 0, start = 0 0, а цвет тумана равен (0, 0, 0, 0) для режима RGBA или 0 для индексного режима работы с цветом Как видите, ничего сложного здесь нет, и мне осталось только проиллюст- рировать этот режим (рис 4 14) Для большей наглядности я несколько за- бежал вперед и "создал реалистичное изображение", о чем речь пойдет поз- же в отдельной главе void Init(void) { // Задаем цвет "дымки" GLfloat fogColor[4] = {0.5, 0.5, 0 5, 1 0}, GLfloat position[] ={0.0, 30, 30, 00 GLfloat local_view[] = { 0.0 }, // Разрешаем проведение теста глубины; ' без этого ничего работать не будет glEnable(GL_DEPTH_TEST), // Эта же функция установлена по умолчанию, ' но лучше все определять в явном виде glDepthFunc(GL_LESS), // Следующий фрагмент отвечает за параметры источника света glLightfv(GL_LIGHT0, GL_POSITION, position);
102 glLightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER, local_view); glFrontFace (GL_CW); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glEnable(GL_AUTO_NORMAL); glEnable(GL_NORMALIZE) ; glEnable(GL_FOG); // Определяем параметры тумана fogMode = GL_EXP, // Экспоненциальная функция учета глубины glFogi (GL_FOG_MODE, fogMode); // Устанавливаем цвет "дымки" glFogfv (GL_FOG_COLOR, fogColor); // Плотность или "густота" дымки glFogf (GL_FOG_DENSITY, 0.35); // Цвет фона glClearColor(1.0, 0.96, 0.866, 1.0); } void DrawRedTeapot (GLfloat x, GLfloat y, GLfloat z) { // Определяем параметры материала, из которого сделаны чайник; float mat[3]; glPushMarrix(); glTranslatef (х, у, z); mat[0] = 0 1745; mat[l] = 0.01175; mat[2] = 0.01175; glMaterialfv (GL_FRONT, GL_AMBIENT, mat) ; mat[0] = 0.61424; mat[l] = 0.04136; mat[2] = 0.04136; glMaterialfv (GL_FRONT, GL_DIFFUSE, mat); mat[0] = 0.727811; matfl] = 0.626959; mat[2] = 0.626959; glMaterialfv (GL_FRONT, GL_SPECULAR, mat); glMatenalf (GL_FRONT, GL_SHININESS, 0.6*128.0); auxSolidTeapot(1.0); glPopMatrix(); } void CALLBACK Draw(void) { // Очищаем буферы цвета и глубины glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Рисуем три чайника в различных положениях DrawRedTeapot (-4.0, -0.5, -1.0);
Глубина, трафареты, туман и многое другое юз DrawRedTeapot (0.0, -0.5, -3.0); DrawRedTeapot (4.0, -0.5, -5.0); glFlush(); } Помимо своего основного назначения — иллюстрации визуальной передачи информации о глубине объекта, представленная программа показывает, как можно подключить мышь к программе Для этого достаточно к функции main добавить строку auxMouseFunc(AUX_LEFTBUTTON, AUX_MOUSEDOWN, cycleFog); которая служит для подключения обработчика cycleFog к левой кнопке мы- ши Теперь достаточно определить сам обработчик: void CALLBACK cycleFog (AUX_EVENTREC *event) { // Эта функция вызывается в ответ на нажатие левой кнопки мыши и // обеспечивает переключение между различными уравнениями учета // расстояния от точки наблюдения до чайников if(fogMode == GL_EXP){ fogMode = GL_EXP2; } else if(fogMode == GL_EXP2){ fogMode = GL_LINEAR; // Для линейного уравнения необходимо определить начальное и // конечное значения расстояния, в пределах которых действует туман glFogf (GL_FOG_START, 1.0); glFogf (GL_FOG_END, 5.0); } else if(fogMode == GL_LINEAR){ fogMode = GL EXP; } // Передаем OpenGL информацию о выбранном режиме glFogi (GL_FOG_MODE, fogMode); } На этом заканчивается беглый обзор работы конвейера OpenGL, в котором я попытался проиллюстрировать отдельные моменты богатых возможностей библиотеки на простых примерах, за редким исключением не связывая их между собой Для этого были некоторые причины В том числе и желание продемонстрировать работу отдельных блоков конвейера в "чистом" виде Однако внимательный читатель наверняка заметил, что ближе к концу этого обзора стало появляться все больше ссылок на последующие разделы И это Не случайно, ведь OpenGL единый мощный механизм трехмерной графики,
104 Гпава 4 в котором отдельные части очень тесно связаны (а зачастую и не отделимы) друг с другом Прежде всего это касается работы с координатами, которую я полностью "проигнорировал" в рассмотренных примерах, пообещав подроб- но рассмотреть этот вопрос позже Это было сделано сознательно, посколь- ку создавать приложения с элементами трехмерной графики можно, прак- тически ничего не зная о преобразованиях перспективы, типах проекций и т д., а просто воспользовавшись теми установками, которые были исполь- зованы в примерах И все же, нельзя создавать сложные "трехмерные приложения", не разбира- ясь в системах координат, преобразованиях, проекциях и некоторых других вопросах, к рассмотрению которых мы и переходим
105 Глава 5 Координаты, геометрические преобразования и проекции Для того чтобы понимать и писать серьезные программы для трехмерной графики, необходимо иметь некоторую математическую подготовку, в ос- новном в области геометрии и тригонометрии Причем практически все не- обходимые знания не выходят за рамки обычной школьной программы Это обстоятельство, а также привлекательность того факта, что после некоторых трудов вы получите "симпатичный" трехмерный рисунок, не должно отпуг- нуть тех, кто "не очень хочет разбираться в этих вопросах" Перед тем как начать, хочу обратить ваше внимание, что с этого момента мы переходим к программированию на C++ под Windows, т к именно на эти операционные системы и ориентирована используемая реализация биб- лиотеки OpenGL. Системы координат в трехмерном пространстве Для того чтобы ввести ортогональные (декартовы) координаты, необходимо иметь три взаимно перпендикулярные оси X, Y и Z, которые имеют общую точку — начало координат (рис 5 1). Существует два независимых способа расположения этих осей, известных как левосторонняя и правосторонняя системы координат В обоих случаях удобно расположить оси X и Y так же. как и в двумерном случае, на плос- кости экрана, направив положительную полуось X вправо от начала коор- динат, а положительную полуось Y — вверх Осталось договориться о распо- ложении оси Z Ее можно направить как на наблюдателя, так и вглубь экра- на В первом случае получим правостороннюю систему координат (рис 5 1) Такое название обусловлено гем, что если повернуть ось X на угол 90° во- круг оси Y, чтобы в результате она совпала с осью Z, то это вращение мож- но сравнить с вращением винта с правой (нормальной) резьбой — при та- ком повороте винт будет несколько перемещаться вдоль оси Y Если же по-
106 Гпава 5 ложительную полуось Z направить внутрь экрана, то получим левосторон- нюю систему координат (рис 5.2) Рис. 5.1. Ортогональная система координат Рис. 5.2. Левосторонняя система координат Чаще всего в компьютерной графике используют обе системы — правосто- роннюю, чтобы определить положение некоторого объекта в "мировом про- странстве", а левостороннюю — чтобы соотнести этот же объект с положени- ем наблюдателя Сейчас нет смысла более подробно останавливаться на этом вопросе, т. к он неоднократно будет возникать на протяжении всей главы В третьей главе мы рассмотрели понятие вектора Поэтому здесь напомню только, что с его помощью можно определять направление Например, век- тор j = (О, 1, 0) определяет "верхнее", т. е. положительное направление оси Y (вверх). В этих случаях длина вектора не имеет никакого значения — если хотя бы одна координата вектора отлична от нуля, то такой вектор одно- значно задает направление. Однако удобнее пользоваться единичными век- торами, т. е такими, длина которых (^/х2 +у2 +z2 ) равна 1 На рис 5 2 по- казаны три единичных вектора i, j, к Они взаимно перпендикулярны, име- ют длину, равную 1, и определяют направления координатных осей В этом случае говорят, что i, j, к образуют тройку ортогональных единичных векто- ров, называемую базисом, через линейную комбинацию которых можно вы- разить любой вектор. Например, для вектора Р, показанного на рис 5 1. можно записать р = хр» + Ур) + zpk Вещественные, в общем случае, числа хр, ур и zp определяют координаты конечной точки Р вектора Р. Мы будем, как чаще всего и делается, обозна- чать его либо в виде строки, либо в виде столбца ||х|| Р = ||х у z|| или
Координаты, геометрические преобразования и проекции 107 Однородные координаты и матрицы Как говорилось в третьей главе, в OpenGL вершины всегда задаются че- тырьмя координатами, что связано с использованием так называемых одно- родных координат, основное удобство применения которых заключается в том, что с их помощью все виды преобразований координат могут быть представлены в единой форме. Они были введены в геометрии и впоследст- вии использованы в графике. В однородных координатах положение точки Р(х, у, z) записывается как P(Wxx, Wxy, Wxz, W) или P(X, Y, Z, W), для лю- бого масштабного множителя, причем трехмерные декартовы координаты легко определяются: х = Х/W; у = Y/W; z = Z/W То есть привычная нам ортогональная система координат получается как проекция однородной системы на плоскость W = 1 В матричной форме для обозначения координат точки в некотором трех- мерном пространстве с использованием однородных координат применяется запись ||Х Y Z И/|| Преобразования однородных координат описываются соотношениями ||Х Y Z И/|| = Т где Т — некоторая матрица преобразования. Чтобы все было более понятно, приведу несколько простых примеров1 Рассмотрим систему уравнений: х'=х + / У'=У + т z'-z + n Эти уравнения можно интерпретировать двояким образом: L Все точки трехмерного пространства перемещаются вправо на расстояние I, вверх — на m и по оси z на расстояние п, 2. Точки остаются на месте, а перемещаются координатные оси х, у и z (рис. 5 3). Этот простой пример иллюстрирует принцип, применяемый и в более сложных ситуациях Запишем эту систему уравнений в матричном виде: При использовании вектора однородных координат везде, где это не оговорено особо, будем полагать, что W равно 1
106 Гпава 5 или в более удобной для будущего использования форме Эти матричные уравнения можно интерпретировать как преобразование всех точек в фиксированной системе координат А можно, как только что было сказано, рассматривать его как изменение системы координат Рис. 5.3. Изменение системы координат Рассмотрим теперь трехмерное изменение масштаба В этом случае (рис 5 4) Что получится, если объединить два рассмотренных преобразования? В этом случае получаем композицию двух преобразований (рис 5 5), которая в мат-
Координаты, геометрические преобразования и проекции 109 Рис. 5.5. Композиция преобразований частичного изменения масштаба и переноса В общем виде обобщенная матрица преобразования 4x4 для трехмерных од- нородных координат имеет вид: d h I е i m f j n q r s и может быть представлена в виде четырех отдельных матриц^ к л На d h\\ Матрица ь е / осуществляет линейное преобразование в виде измене- IIе 1 /| ния масштаба, сдвига и вращения Вектор |т| производит перенос, вектор IIP Ч г| — перспективное преобразование, а последний скалярный элемент s — общее изменение масштаба1 Использованная здесь форма записи отличается от той, которая обычно применяется в ли- тературе Связано это с тем, что в OpenGL используется именно такая структура матрицы преобразования Переход от одной системы обозначений к другой осуществляется обычным транспонированием
110 Гпава 5 Полное преобразование, полученное путем воздействия на вектор положе- ния матрицей 4x4 и нормализации полученного вектора, называется били- нейным преобразованием Оно обеспечивает выполнение комплекса операций сдвига, частичного изменения масштаба, вращения, отображения, переноса, а также изменения масштаба объектов в целом Кроме того, эта же матрица обеспечивает и преобразование в перспективе, о котором речь пойдет ниже Определив ряд способов преобразования пространства относительно трех- мерной системы координат (или наоборот), можно переходить к вопросу о том, как начертить объект в трехмерном пространстве. Но сначала перечис- лим системы координат, которые будем использовать. Рассматривая какой-либо трехмерный объект, мы всегда определяем его по- ложение и размеры относительно некоторой привычной, и удобной в на- стоящий момент системы координат, связанной с реальным миром Такая исходная система координат в компьютерной графике является правосто- ронней и называется мировой системой координат Для того чтобы можно было изобразить объект на экране, его необходимо предварительно перевес- ти (или преобразовать) в другую систему координат, которая связана с точ- кой наблюдения и носит название видовой системы координат (рис 5 6) Эта система координат является левосторонней И, наконец, любое трехмерное изображение мы всегда рисуем на двумерном экране, который имеет свою экранную систему координат (рис 5.7) Область вывода Рис. 5.6. Мировая и видовая системы координат Рис. 5.7. Видовые и экранные координаты Таким образом, основная задача, которую необходимо решить, заключается в том, что объекты, описанные в мировых координатах, необходимо изобра- зить на плоской области вывода экрана То есть необходимо проделать неко- торую последовательность преобразований и осуществить проекцию, перево- дящую трехмерные объекты на двумерную проекционную плоскость Эту по- следовательность действий удобно выполнять в несколько этапов, как это и реализовано в OpenGL (рис. 5 8)
Координаты, геометрические преобразования и проекции 111 Рис. 5.8. Модель процесса вывода трехмерной графической информации В процессе вывода трехмерной графической информации необходимо задать объем видимости в мировом пространстве, проекцию на картинную плос- кость и область вывода на экране. В общем случае объекты, определенные в трехмерном мировом пространстве, отсекаются по границам трехмерного объема видимости и после этого проецируются При этом окно само являет- ся проекцией объема видимости на картинную плоскость Очевидно, что в общем случае для конкретной реализации процесса вывода может быть использовано множество моделей Мы рассмотрим ту, которая принята в OpenGL и представлена на рис 5 8 При этом для нас важными являются следующие обстоятельства: □ Для преобразования координат используется матричное представление □ Видовое преобразование отделено от перспективного, которое, в свою очередь, отделено от построения проекций □ Координаты объекта, видовые и усеченные координаты представляются четырьмя упорядоченными значениями х, у, z, w Давайте последовательно рассмотрим каждый этап этого обобщенного пре- образования и перечисленные обстоятельства. Преобразование координат Как уже было сказано, для получения двумерного изображения трехмерного объекта необходимо прежде всего осуществить преобразование системы ми- ровых координат в систему видовых координат Для того чтобы разобраться, как это осуществляется в OpenGL, рассмотрим, какие средства библиотека нам предлагает Матрицы в OpenGL предусмотрены три типа матриц — видовая, проекций и тексту- ры. Все они имеют размер (4x4). Для того чтобы с какой-либо матрицей можно было работать, ее необходимо сделать текущей, для чего предусмотрена команда void glMatrixMode(GLenum mode)
112 Гпава 5 Параметр mode определяет, с каким набором матриц будут выполняться по- следующие операции, и может принимать одно из трех значений GL_MODELVIEW Последовательность операций над к видовой матрице матрицами применяется GLJPROJECTION Последовательность операций над к матрице проекции матрицами применяется GL_TEXTURE Последовательность операций над к матрице текстуры матрицами применяется После того как установлена текущая матрица, необходимо определить ее элементы Для этой цели можно использовать следующие команды void glLoadMatrix[f d](GLtype* т) Параметр т определяет указатель на матрицу 4x4, хранящуюся по порядку расположения столбцов как 16 последовательных вещественных значений одинарной (f) или двойной (d) точности а, а5 а9 Э]-; аз аб аю а14 а3 а7 аП а15 а4 а8 а12 а16 Эта команда заменяет текущую матрицу на ту, которая определена в т Те- кущей является одна из матриц — видовая, проекций или текстуры, в зави- симости от текущего режима void glLoadldentityO Заменяет текущую матрицу на единичную (матрицу идентичности) Семан- тически эта команда эквивалентна glLoadMatrix с параметром, определяю- щим единичную матрицу 10 0 0 0 10 0 0 0 10 0 0 0 1 но выполняется более эффективно При выполнении преобразований объектов, таких как перенос, вращение, масштабирование, могут понадобиться соответствующие операции над мат- рицами OpenGL предоставляет набор команд для решения этой задачи Фундаментальной является команда перемножения матриц void glMultMatrix[f d](GLtype* т) Параметр т определяет указатель на матрицу 4x4, хранящуюся по порядку расположения столбцов как 16 последовательных вещественных значений одинарной (f) или двойной (d) точности Эта команда умножает матрицу.
Координаты, геометрические преобразования и проекции 113 заданную параметром m (М) слева на текущую (Т) таким образом, что теку- щей становится матрица МТ Однако к использованию этой команды следует прибегать только в самом крайнем случае, т к для перемножения требуются значительные вычисли- тельные затраты Видовое преобразование В общем случае для выполнения видовых преобразований должны быть за- даны (в мировых координатах) точка наблюдения и объект (рис 5 6) Гово- рят, что вектор ЕО определяет направление наблюдения При этом из точки наблюдения Е можно видеть точки объекта только внутри некоторого кону- са, ось которого совпадает с линией ЕО, а вершина — с точкой Е Видовое преобразование может быть записано в матричной форме где М — матрица видового преобразования размера 4x4 Для определения этой матрицы удобно использовать специальные оптимизированные коман- ды OpenGL, с помощью которых осуществляются операции переноса и вращения void glRotate[f d]( GLtype angle, GLtype x, GLtype y, GLtype z) Эта команда рассчитывает матрицу для выполнения вращения вектора про- тив часовой стрелки на угол, определяемый параметром angle, осуществляе- мого относительно точки (х, у, z) После выполнения этой команды все объ- екты изображаются повернутыми (рис 5 9) void glTranslate[f d]( GLtype x, GLtype у, GLtype z) При помощи этой команды осуществляется перенос объекта на расстояние х по оси X, на расстояние у по оси Y и на расстояние z по оси Z (рис. 5 10) Еще одна специализированная команда void glScale[f d]( GLtype x, GLtype у, GLtype z)
114 Глава 5 осуществляет частичное масштабирование вдоль каждой из координатных осей на значения, определяемые соответствующими параметрами (рис. 5 11) При использовании перечисленных команд OpenGL умножает текущую матрицу (М) на соответствующую матрицу — вращения (R), переноса (Т) или масштабирования (S) и помешает результат (M*R, М«Т или M*S) на место текущей матрицы Рис. 5.9. Объект до и после выполнения команды gIRotate (45, 0 0,00,1 0) Рис. 5.10. Объект до и после выполнения команды gITranslate (-2 0, 3 0, -2 0)
Координаты, геометрические преобразования и проекции 115 Рис. 5.11. Объект до и после выполнения команды glScale(2, 3, 4) Для того чтобы легче было разобраться в рассматриваемых вопросах, я на- писал специальное приложение Transform, позволяющее в интерактивном режиме (рис 5.12) посмотреть на все перечисленные действия (полный текст приложения можно найти на прилагаемой дискете) Поэтому даль- нейшие исследования преобразований и видовой матрицы можно осущест- вить самостоятельно. А мы переходим к изучению проекций Рис. 5.12. Окно приложения Transform служит для изучения видовых и перспективных преобразований
116 Глава 5 Проекции Несоответствие между пространственными объектами и плоскими изобра- жениями устраняется путем введения проекций, которые отображают объек- ты на двумерной проекционной картинной плоскости Для того чтобы было понятно, о чем идет речь, рассмотрим рис 5 13, на котором представлено двумерное изображение куба вместе с некоторыми дополнительными ли- ниями В привычной для нас евклидовой геометрии отрезки АВ и CD трудно на- звать параллельными Однако они обозначают горизонтальные ребра куба в трехмерном пространстве, и поэтому мы считаем их параллельными По этой терминологии параллельные горизонтальные линии встречаются в так называемой точке схода. Все точки схода лежат на одной прямой, которая называется линией горизонта Отметим, что точки схода и линия горизонта являются особенностью изображения и реально не существуют в трех- мерном пространстве Эта концепция должна быть хорошо вам знакома, т. к на протяжении многих веков ее использовали художники для получе- ния реалистичных изображений трехмерных объектов Такие изображения называются перспективными Следующий момент, на который следует обратить внимание, заключается в том, что очень важное значение имеет расстояние между наблюдателем и объектом, поскольку "эффект перспективы" обратно пропорционален этому расстоянию. Если наблюдатель расположен близко от объекта, то получим сильный эффект перспективы (рис 5 14, а) и легко видеть, что продолжения изображений параллельных линий на картинке пересекаются С другой сто- роны, если наблюдатель расположен далеко от объекта (по сравнению с его размером), то параллельные линии объекта будут казаться параллельными и на картинке (рис. 5.14, б). Таким образом, мы подошли к тому, что вид про- екции зависит от расстояния между наблюдателем и картинной плоскостью, в зависимости от которого различают два основных класса проекций парал- лельные и центральные Если это расстояние бесконечно, то проекция будет параллельной, а если оно конечно, то центральной При описании цен- тральной проекции мы явно задаем центр проекции (рис 5 16), в то время
Координаты, геометрические преобразования и проекции 117 как определяя параллельную проекцию — указываем направление проециро- вания (рис 5 15) Рис. 5.14. Различное восприятие параллельных линий в зависимости от расстояния до объекта Рис. 5.15. Параллельная проекция Рис. 5.16. Центральная проекция Подробное знакомство с различными видами проекций выходит за рамки этой книги Мы рассмотрим только два из них ортографическую и перспек- тивную проекции, которые поддерживаются OpenGL в явном виде Тем, кто захочет подробнее познакомиться с этим вопросом, могу порекомендовать прекрасную книгу Д Роджерса и Дж Адамса "Математические основы ма- шинной графики" Мы же пойдем дальше. Необходимо уметь задавать проекционную (картин- ную) плоскость, видимый объем и окно За исключением определения окна, осуществляется это по-разному для перечисленных видов проекций Поэто- му сначала рассмотрим общий вопрос Определение области вывода Область вывода определяется шириной (рх) и высотой (ру) (в пикселях), а также положением центра (ох, оу) Осуществляется это при помощи команды void glViewport( GLint х, GLint у, GLint width, GLint height)
118 Глава 5 которая определяет преобразование х и у из нормализованных координат устройства в координаты окна Получая нормализованные координаты уст- ройства (xnd, уП(1) команда рассчитывает по ним оконные координаты xw = (xnd + 1) + X У„ = (У„1 + 1) + У Параметры х и у определяют координаты левого нижнего угла прямоуголь- ника вывода и по умолчанию принимаются равными нулю Параметры width и height определяют, соответственно, ширину и высоту области вывода в пикселях. Когда контекст воспроизведения OpenGL первый раз присоеди- няется к окну, значения ширины и высоты устанавливаются по размерам этого окна. Ширина и высота области вывода ограничены значениями, которые зависят от конкретной реализации. Их можно узнать, вызвав команду glGet* с аргу- ментом GL_MAX_VIEWPORT_DIMS После выполнения этой команды параметры области вывода устанавливают- ся в значения рх = width, ру = height, ох = х + width/2 и оу = у + height/2, т е точка с координатами (0, 0, 0) располагается в центре области вывода, а оконные координаты вершины определяются соотношением Ш(рх/2)х + ох II (Ру/2) У + Оу [(f-n)/2]z + (n + f)/2||; где п и f определяют допустимый диапазон глубин, который задается командой void glDepthRange( GLclampd znear, GLclampd zfar) После отсечения и деления на w, z-координаты лежат в диапазоне значений от —1 до 1, соответственно для ближней и дальней плоскостей отсечения (секущих плоскостей) Команда определяет линейное отображение норма- лизованных z-координат из этого диапазона в оконные координаты Неза- висимо от действительной реализации буфера глубины, оконные координа- ты значения глубины трактуются как лежащие в диапазоне от 0 до 1 Пара- метры определяют отображение ближней и дальней плоскостей отсечения в оконные координаты По умолчанию значение znear = 0, a zfar = 1 Используемые по умолчанию значения (0 и 1), соответственно для ближней и дальней плоскостей, позволяют использовать весь диапазон буфера глуби- ны. Совершенно не обязательно, чтобы значение znear было меньше, чем Zfar, т. к. в противном случае происходит простое обращение
Координаты, геометрические преобразования и проекции 119 После того как установлена область вывода, необходимо установить проек- ционную (картинную) плоскость и объем видимости Как уже было сказано, для разных типов проекций это осуществляется по-разному Ортографическая проекция Знакомство с этим типом проекций начнем с представления соответствую- щих команд OpenGL. void glOrtho( GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble fat} Эта команда устанавливает матрицу перспективы, которая создает парал- лельную проекцию и объем видимости (рис 5 17) Параметры left и right оп- ределяют координаты левой и правой вертикальных, a bottom и top — ниж- ней и верхней горизонтальных плоскостей отсечения Расстояние до ближ- ней и дальней плоскостей отсечения определяется параметрами near и far Расстояние будет отрицательным, если плоскость находится позади смотря- щего Рис. 5.17. Усеченный объем видимости (параллелограмм видимости) для параллельной проекции Параметры {left, bottom, -near) и {right, top, -near) определяю! точки на ближней плоскости отсечения, которая сопоставлена соответственно левому нижнему и правому верхнему углам окна При этом считается, что глаз рас- положен в точке (О, 0, 0) Параметр far определяет размещение дальней плоскости отсечения Оба параметра {near и far) могут быть положительны- ми или отрицательными Соответствующая матрица имеет вид1 Представленная ниже запись отличается от той, которая приведена в справочной системе - там опечатка Последний элемент матрицы равен "1", а не "-Г
120 Глава 5 2 right - left О О О О О tx 2 ------ ------ О ty top - bottom О -----------—------ tz far - near О О 1 e _ _ right + left . t = _ top + bottom . _ far + near x right-left ’ y top-bottom ’ z far-near Текущая матрица M умножается на сформированную командой матрицу О и результат М*О помещается на место матрицы М, заменяя текущую Помимо рассмотренной, в дополнительную библиотеку glu32 lib включена еще одна команда1 void gluOrtho2D( GLdouble left, GLdouble right, GLdouble bottom, GLdouble top) действие которой эквивалентно вызову команды glOrtho(left, right, bottom, top, -Л 1) Для того чтобы окончательно разобраться с этим вопросом, рассмотрим пример void CProjectionsView::OnSize(UINT nType, int ex, int су) { COpenGLView::OnSize(nType, ex, cy); m_width = ex; m_height = cy; // Устанавливаем параметры области вывода // на 10 пикселей меньше размеров окна // и с координатами левого нижнего угла — (5, 5) // Центр области вывода совпадает с центром окна glViewport(5, 5, m_width - 10, m_height - 10), // Делаем текущей матрицу проекций glMatrixMode(GL_PROJECTION); // Перед тем как устанавливать какую-либо матрицу, рекомендуется // сбросить ее предыдущее содержимое, поскольку матрица, 1 Для использования этой и других команд, имеющих префикс glu, необходимо подключить к проекту файл glu h Для тех, кто воспользуется предлагаемым Wizard'oM, это действие не требуется
Координаты, геометрические преобразования и проекции 121 // которая будет получена в результате выполнения следующей // команды, автоматически будет умножена на текущую glLoadldentity() , // Задаем параметры объема видимости, // по которым будет рассчитана матрица проекции glOrtho(-2.Of, // left 3.0f, // right -3.0f, // bottom 2.Of, // top -20.Of, // near 20.Of); // far // Делаем текущей видовую матрицу glMatrixMode(GL_MODELVIEW); В ответ на сообщение системы о том, что изменились размеры окна, мы изменяем параметры области вывода. Это можно было сделать и в другом месте, но нельзя забывать отслеживать изменение размеров окна, т к в противном случае увидите совсем не то, что ожидаете Теперь о матрице проекций Наблюдательный читатель наверняка уже заметил, что структура матрицы, создаваемой командой glOrtho, полностью совпадает со структурой рассмотренной ранее матрицы, получившейся в результате последователь- ного выполнения операций частичного изменения масштаба и сдвига пара- метры tx, ty и tz определяют величины сдвигов по соответствующим осям, а оставшиеся коэффициенты — масштабные множители Это хорошо согласу- ется с интуитивным представлением, поскольку параллельная проекция фиксирует истинные размеры (с точностью до скалярного множителя), и параллельные прямые остаются параллельными Обратите внимание на знак "минус" у коэффициента———, который говорит о том, что OpenGL ав- far - near тематически осуществляет перевод координат из правосторонней мировой в левостороннюю видовую систему координат Базис объема видимости опре- деляется как s _ right - left . х - 2 ' S _ top - bottom . у - 2 J Sz - ~(far-near) k . z 2 а координаты центра Я - left + right j | bottom + top . near + far 2 ,+ 2 J 2
122 Глава 5 Перечислим его основные свойства □ Базис является левосторонним □ Это ортогональный, но не ортонормированный базис □ Координаты любых точек внутри объема видимости, записанные в этом базисе, по модулю < 1, т е объем видимости представляет собой куб со стороной 2 После того как определена матрица проекций, необходимо переключиться на видовую матрицу — сделать ее текущей, чтобы иметь возможность осу- ществлять преобразования объектов Посмотрим, что получается на практи- ке, нарисовав параллелепипед со сторонами (1, 1,4) — рис 5 18 void CProjectionsView-:OnDraw(CDC* pDC) { GLfloat x = 2.Of, у = 2 Of, z = 3 Of; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_DEPTH_TEST) , glShadeModel(GL_FLAT); // Перед тем как менять параметры матрицы - запоминаем // предыдущее состояние в специальном стеке glPushMatrix(), // Рисуем "проволочную" модель куба glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); // Поскольку параметры объема видимости не симметричны // относительно 0, для центрирования изображения необходимо // сдвинуть его на 0 5 вправо по оси х и на 0.5 вниз по оси у glTranslatef(0 5f, -0.5f, 0 Of), // Поскольку проекция ортографическая, то, чтобы увидеть трехмерное // изображение, объект необходимо развернуть // Вращаем на 20 градусов вокруг оси х glRotatef( 20.Of, l.Of, O.Of, 0 Of); // Вращаем на -30 градусов вокруг оси у glRotatef(-30.Of, 0 Of, 1 Of, 0 Of), glLineWidth(2 Of), // Рисуем оси координат glBegin(GL_LINES), glColor3f(0 Of, 0 Of, 1 Of), glVertex3f(0 Of, C Of, 0 Of), glVertex3f( x, 0 Of, 0 Of), glColor3f(l.Of, 0 Of, 0 Of); glVertex3f(O.Of, O.Of, O.Of); glVertex3f(0 Of, у, O.Of),
i, геометрические преобразования i glColor3f(O.Of, 0.8f, O.Of); glVertex3f(0 Of, 0 Of, O.Of), glVertex3f(0 Of, 0 Of, z), glEnd(); glLineStipple(2, 0xlC47); glEnable(GL_LINE_STIPPLE) , glBegin(GL_LINES) ; glColor3f(O.Of, O.Of, l.Of); glVertex3f( O.Of, O.Of, O.Of); glVertex3f(-x, 0 Of, O.Of); glColor3f(l.Of, O.Of, O.Of); glVertex3f(0 Of, O.Of, O.Of); glVertex3f(0.Of, -y, O.Of); glColor3f(0 Of, 0.8f, O.Of); glVertex3f(O.Of, OOf, O.Of); glVertex3f(0 Of, O.Of, -z); glEnd(); glDisable(GL_LINE_STIPPLE); glShadeModel(GL_SMOOTH); / Рисуем параллелепипед glBegin(GL_QUAD_STRIP); glColor3f(l Of, O.Of, O.Of); glVertex3f(-0 5f, 0.5f, 1.5f) glVertex3f(-0 5f, -0.5f, 1.5f) glVertex3f( 0 5f, 0.5f, 1.5f) glVertex3f( 0.5f, -0.5f, 1.5f) glVertex3f( 0 5f, 0.5f, -2.5f) glColor3f(O.Of, O.Of, l.Of); glVertex3f( 0 5f, -0 5f, -2.5f); glVertex3f(-0.5f, 0.5f, -2.5f); glVertex3f(-0.5f, -0.5f, -2 5f); glVertex3f(-0.5f, 0.5f, 1.5f); glVertex3f(-0.5f, -0.5f, 1.5f); glEnd(); glBegin(GL_QUADS); glColor3f(0.Of, 1 Of, 0 Of), glVertex3f(-0 5f, 0 5f, 1 5f) , glVertex3f( 0 5f, 0 5f, 1.5f), glVertex3f( 0 5f, 0 5f, -2 5f) ; glVertex3f(-0 5f, 0 5f, -2 5f); glEnd(); glBegin(GL_QUADS), glColor3f(O.Of, 1 Of, O.Of); glVertex3f(-0 5f, -0.5f, 1.5f); glVertex3f( 0.5f, -0.5f, 1.5f);
124 Глава 5 glVertex3f( 0.5f, -0.5f, -2 5f); glVertex3f<-0.5f, -0.5f, -2 5f) ; glEnd(); // Восстанавливаем предыдущие параметры видовой матрицы glPopMatnx () ; // Завершаем процесс рисования glFinish() ; SwapBuffers(::wglGetCurrentDC()); Пусть вас не пугает большой объем представленного листинга — основную его часть составляют комментарии, а также определение параметров объек- та, которые можно "перенести" в список изображений и скомпилировать при инициализации окна Рис. 5.18. Результат работы приложения Projections для ортографической проекции при использовании команды g!Ortho(-2, 3, -3, 2. -20, 20) Как видите, все достаточно просто Осталось обратить ваше внимание на три обстоятельства. На рис 5 18 положительные направления осей изобра- жены сплошной линией. При этом ось Z направлена "на нас", т е OpenGL автоматически выполняет все необходимые преобразования, и мы получаем то изображение, которое и "заказывали" — в мировых правосторонних коор- динатах Второе обстоятельство связано с размерами Параметры объема ви- димости необходимо выбирать таким образом, чтобы при любых преобразо- ваниях, которые вы собираетесь осуществить над объектом, его максималь- ный размер никогда не превышал размеры этого объема Все необходимое масштабирование OpenGL осуществляет автоматически Посмотрите, на- пример, что получится при использовании команды gluOrtho2D с теми же параметрами по осям х и у (рис 5 19) Если вы можете объяснить причины отличия рис 5.18 и 5.19, значит вы действительно разобрались в правилах задания ортографической проекции
Координаты, геометрические преобразования и проекции 125 Рис. 5.19. Результат работы приложения Projections для ортографической проекции при использовании команды gluOrtho2D(-2, 3, -3, 2) Теперь последнее В приведенном фрагменте использованы две новые команды glPushMatrix и glPopMatrix, которые позволяют упрятать в стек и извлечь из него текущую матрицу: void glPushMatrix() void glPopMatrix() Количество матриц, которые можно поместить в стек, зависит от типа матрицы В режиме GL_MODELVIEW наименьшая глубина стека равна 32 В двух других режимах — GL_PROJECTION и GL TEXTURE наименьший размер равен 2. Текущей матрицей в любом режиме называется матрица, находящаяся наверху стека для этого режима Команда glPushMatrix помеша- ет текущую матрицу в стек, a glPopMatrix — извлекает матрицу из стека, де- лая ее текущей Изначально каждый стек содержит одну матрицу — единич- ную (матрицу идентичности) Продуманное использование стеков матриц дает программистам очень ши- рокие возможности по созданию самых разнообразных изображений Перспективная проекция При этом типе проекции необходимо определить не параллелепипед, а усе- ченный конус видимости. Для этого в OpenGL реализованы две команды void glFrustum( GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble znear, GLdouble zfar) Эта команда создает матрицу перспективы. Параметры left и right определя- ют координаты левой и правой вертикальных, bottom и top — нижней и
126 Гпава 5 верхней горизонтальных плоскостей отсечения (относительно ближней по- верхности), a znear и <?/«/• — расстояние до ближней и дальней плоскостей отсечения по глубине, причем оба значения должны быть положительными (рис 5 20) Считается, что наблюдатель располагается в точке с координата- ми (0, 0, 0) Создаваемая матрица имеет вид 2 * near right - left 0 0 0 О АО top - bottom 0 CD 0 -10 где д _ right + left . g _ top + bottom . q _ _ far + near j) = _ 2 ^ar near right-left top - bottom ’ far - near ’ far-near Рис. 5.20. Усеченный объем видимости (конус видимости) для центральной проекции Точность представления глубины зависит от значений, определенных для znear и zfar Чем больше отношение zfar/znear, тем менее эффективно будут различаться в буфере глубины, расположенные рядом поверхности По- скольку при znear = 0 отношение стремится к бесконечности, то это значе- ние использовать не следует Принцип использования этой команды такое же, как и glOrtho Даже пара- метры у этих команд одни и те же Вторая команда находится в библиотеке glu32 lib void gluPerspective( GLdouble angley, GLdouble aspect, GLdouble znear, GLdouble zfar) и определяет усеченный конус видимости в видовой системе координат Па- раметр angley задает угол видимости (в градусах) в напранлении оси у. В на-
Координаты, геометрические преобразования и проекции 127 правлении оси х угол видимости задается через отношение сторон aspect, которое обычно определяется отношением сторон области вывода. Два ос- тавшихся параметра znear и zfar определяют расстояние от наблюдателя до ближней и дальней плоскостей отсечения соответственно и всегда должны быть положительными. Действие этой команды эквивалентно вызову glFrustum, при left = -right, bottom = -top, tg(angley/2) = top/near, aspect = right/top Матрица, создаваемая этими командами, автоматически умножается на те- кущую матрицу Поэтому, чтобы сделать созданную матрицу текущей мат- рицей перспективы, необходимо выполнить, например, следующие действия void CProjectionsView :OnSize(UINT nType, int ex, int су) { // Сделать текущей матрицу проекций glMatrixMode(GL_PROJECTION); // Сделать ее единичной, чтобы при умножении на нее создаваемой // матрицы проекций не исказить последнюю glLoadldentity(); // Создать матрицу перспективной проекции glFrustum(-2.О, 2.0, -2.0, 2.0, 4.0, 25.0); // Вызов этой команды полностью идентичен вызову предыдущей. // Уберите символы комментария и проверьте //gluPerspective(53 13, 10, 4.0, 24 0); // Дальше аналогично } Перед пояснениями необходимо привести и те изменения, которые должны быть внесены в функцию OnDraw void CProjectionsView:OnDraw(CDC* pDC) { // Аналогично предыдущему // Здесь необходимо заменить параметры команды // (см. комментарий в тексте) glTranslatef(0 Of, 0 Of, -9 6f) , // Влияние этих двух команд иллюстрируется рис. 5.22 glRotatef( 20.Of, 1 Of, 0 Of, O.Of), glRotatef(-30 Of, 0 Of, 1 Of, O.Of); // Дальше все аналогично
128 Гпава 5 Результат работы представлен на рис. 5 21, где показаны также точки схода Рис. 5.21. Результат работы приложения Projections для перспективной проекции Чтобы разобраться в необходимости изменения параметров команды glTranslate, рассмотрим координаты центральной точки отображения в слу- чае перспективной проекции1. far • (right + left) far + near far (top + bottom) far + near _ 2far near far + near Теперь должно быть ясно, откуда взялось число —9 6 и почему два других параметра равны 0 чтобы можно было увидеть объект в области вывода (в данном случае в центре окна), необходимо сместить его на расстояние -г пеаг по оси Z Смещений по двум другим осям не будет, т к в при- far - near мере использованы симметричные значения Так же, как и для случая ортографической проекции, усеченный конус ви- димости отображается на куб со стороной 2 Я привожу только окончательный результат Тем, кто интересуется математическими аспек- тами этого и других вопросов трехмерной графики, могу порекомендовать обратиться к книге Дж Фоли, А вэн Дэм "Основы интерактивной машинной графики"
Координаты, геометрические преобразования и проекции 129 Рис. 5.22. Влияние команд вращения на ортографическую (а) и перспективную (6) проекции Другие виды проекций Очевидно, что помимо проекций, поддерживаемых OpenGL непосредствен- но — ортографической и одноточечной перспективной, можно определять и любую другую. Из наиболее известных параллельных проекций можно на- звать диметрическую (две из трех осей во время проецирования одинаково сокращены) и изометрическую (все три оси во время проецирования одина- ково сокращены). Матрица для диметрической проекции приведена во фрагменте функции OnSize, а результат представлен на рис 5 23. void CProjectionsView::OnSize(UINT nType, int ex, int cy) { GLfloat mDim[4][4] = { 0.925820f, O.Of, 0 377964f, 0 Of, 0.133631f, 0 935414f, -0.327321f, 0 Of, -0.353553f, 0.353553f, 0.866025f, 0 Of, O.Of, O.Of, O.Of, 3 0 }; // Сделать текущей матрицу проекций glMatrixMode(GL_PROJECTION); // Загрузить матрицу диметрической проекции glLoadMatrixf(&ш[0][0]);
130 Глава 5 // Дальше все аналогично Рис. 5.23. Диметрическая проекция Ниже приведены аналогичные характеристики для изометрической проек- ции (рис 5 24) GLfloat miso[4][4] = { О 707107f, O.Of, О 707107f, 0 Of,
Координаты, геометрические преобразования и проекции 131 0.408248f, 0.816597f, -0.408248f, O.Of, -0 577353f, 0.577345, 0.577353f, O.Of, 0 Of, 0 Of, O.Of, 3 0 // Дальше все аналогично случаю диметричес?эй проекции } Последний пример, который мы рассмотрим, демонстрирует двухточечную перспективную проекцию (рис 5 25) void CProjectionsView::OnSize(UINT nType, int ex, int cy) { GLfloat mTwo[4][4] = { 0 5f, 0 Of, 0 866f, 0 Of, 0 Of, 1 Of, O.Of, -2 Of, -0.866f, 0 Of, 0 5f, 1 Of, -0.866f, O.Of, 0.5f, 2.Of }; // Дальше все аналогично рассмотренным случаям Рис. 5.25. Двухточечная перспективная проекция Ориентация Для того чтобы однозначно задать положение и ориентацию трехмерного объекта в пространстве, необходимо определить три вектора (рис 5 26) Рас- смотрим назначение каждого из них Первый вектор определяет положение
132 Гпава 5 некоторой контрольной точки объекта (например той, которую хотелось бы разместить в центре экрана) в пространстве Второй — направление наблюде- ния (или взгляда) Третий вектор служит для однозначной ориентации объек- та в пространстве и позволяет указать направление, которое для объекта бу- дет считаться верхним. Рис. 5.26. Положение и ориентация объекта однозначно задаются тремя векторами Чем отличаются объекты на рис 5.26? Только тем, что они повернуты на разный угол вокруг своей оси, и для того, чтобы полностью задать ориента- цию объекта, необходимо дополнительно указать направление, которое для объекта будет определять положительное направление оси Y, и оно не обя- зательно должно совпадать с направлением оси Y в мировых координатах Эти три вектора можно использовать в качестве аргументов специальной команды OpenGL void gluLookAt( GLdouble eyex, GLdouble eyey, GLdouble eyez, GLdouble centerx, GLdouble centery, GLdouble centerz, GLdouble upx, GLdouble upy, GLdouble upz) которая по ним создает видовую матрицу, зависящую от точки наблюде- ния, — параметры eyex, eyey, eyez, контрольной точки, указывающей центр сцены, — параметры centerx, centery, centerz, и вектора вверх — параметры upx, иру, upz, определяющего положительное направление оси Y сцены Матрица отображает контрольную точку на отрицательную ось z, а точку наблюдения — в начало системы видовых координат так, что при использо- вании типичной матрицы проекций центр сцены отображается в центр об- ласти вывода Вектор не должен быть параллельным линии наблюдения из глаза в точку отражения Матрица, сформированная этой командой, потом умножается на текущую матрицу
Координаты, геометрические преобразования и проекции /33 Рассмотрим пример использования этой команды, для чего в последний ва- риант приложения Projections достаточно внести следующие изменения void CProjectionsView:OnSize(UINT nType, int ex, int cy) { // Делаем текущей видовую матрицу glMatrixMode(GL_MODELVIEW); /I Определяем ориентацию параллелепипеда gluLookAt(5.Of, 5.Of, 10.Of, II положение центра видовых // координат в мировых O.Of, O.Of, O.Of, // центр сцены в мировых координатах 1 Of, l.Of, l.Of); // вектор "вверх" направлен под // углом 4 5° ко всем трем осям
134 Глава 6 Цвет в компьютерной графике После того как мы научились создавать различные объекты и правильно располагать их на сцене, можно приступать к приданию им более привлека- тельного вида, чем тот, который они имели до сих пор И начнем, как это ни странно, с цвета Цвет уже не раз упоминался в данной книге и мы им достаточно широко пользовались, не акцентируя на этом внимания. Теперь пора заняться цве- том вплотную Цвет — чрезвычайно сложная проблема как для физики, так и для физио- логии, т к он имеет как психофизиологическую, так и физическую приро- ду. Восприятие цвета зависит от физических свойств света, т е электромаг- нитной энергии, от его взаимодействия с физическими веществами, а также от их интерпретации зрительной системой человека. Другими словами, цвет предмета зависит не только от самого предмета, но также и от источника света, освещающего предмет, и от системы человеческого видения Более того, одни предметы отражают свет (доска, бумага), а другие его пропускают (стекло, вода). Если поверхность, которая отражает только синий свет, ос- вещается красным светом, она будет казаться черной Аналогично, если ис- точник зеленого света рассматривать через стекло, пропускающее только красный свет, от тоже покажется черным Самым простым является ахроматический цвет, т е такой, какой мы видим на экране черно-белого телевизора При этом белыми выглядят объекты, ахроматически отражающие более 80% света белого источника, а черны- ми — менее 3% Промежуточные значения дают различные оттенки серого Единственным атрибутом такого цвета является интенсивность или количе- ство С интенсивностью можно сопоставить скалярную величину, определяя черное как 0, а белое как 1 Тогда среднесерому цвету будет соответствовать значение 0 5 Если воспринимаемый свет содержит длины волн в произвольных неравных количествах, то он называется хроматическим При субъективном описании такого цвета обычно используют три величины цветовой тон, насыщенность
Цвет в компьютерной графике 735 и светлота. Цветовой тон позволяет различать цвета, такие как красный, зеленый, желтый и т д Насыщенность характеризует чистоту, т е степень ослабления (разбавления) данного цвета белым светом, и позволяет отличать розовый цвет от красного, изумрудный от ярко-зеленого и т д Другими словами, по насыщенности судят о том, насколько мягким или резким ка- жется цвет Светлота отражает представление об интенсивности, как о фак- торе, не зависящем от цветового тона и насыщенности Обычно встречаются не чистые монохроматические цвета, а их смеси В ос- нове трехкомпонентной теории света лежит предположение о том, что в центральной части сетчатки глаза находятся три типа чувствительных к цве- ту колбочек Первый воспринимает зеленый цвет, второй — красный, а тре- тий — синий цвет Относительная чувствительность глаза максимальна для зеленого цвета и минимальна для синего Если на все три типа колбочек воздействует одинаковый уровень энергетической яркости, то свет кажется белым Ощущение белого цвета можно получить, смешивая любые три цве- та, если ни один из них не является линейной комбинацией двух других Такие цвета называют основными Человеческий глаз способен различать около 350 000 различных цветов Это число получено в результате многочисленных опытов Четко различимы примерно 128 цветовых тонов Если меняется только насыщенность, то зри- тельная система способна выделить уже не так много цветов мы можем различить от 16 (для желтого) до 23 (для красного и фиолетового) таких цве- тов. Результаты опытов обобщены в законах Грассмана □ Глаз реагирует на три различных стимула, что подтверждает трехмерность природы цвета В качестве стимулов можно рассматривать, например, доминирующую длину волны (цветовой фон), чистоту (насыщенность) и яркость (светлоту) или красный, зеленый и синий цвета □ Четыре цвета всегда линейно зависимы, т е сС = iR + gG + ЬВ, где с, г, g, b ^0 Следовательно, для смеси двух цветов имеет место равенство (cC)j + (сС)2 = (rR)] + (rR)2 + (gG)! + (gG)? + (ЬВ)! + (ЬВ)2 Если цвет Ci равен цвету С и цвет С2 равен цвету С, то цвет С) равен цвету С2 не- зависимо от структуры спектров энергии с, С], С2 □ Если в смеси трех цветов один непрерывно изменяется, а другие остают- ся постоянными, то цвет смеси будет меняться непрерывно, т е трех- мерное цветовое пространство непрерывно В компьютерной графике применяются две системы смешивания основных Цветов’ аддитивная — красный, зеленый, синий (RGB) и субтрактивная — голубой, пурпурный, желтый (CMY) Цвета одной системы являются допол- нительными к цветам другой голубой — к красному, пурпурный — к зеле- ному, а желтый — к синему Дополнительный цвет — это разность белого и данного цветов. Субтрактивная система цветов CMY применяется для отражающих поверх- ностей, например, типографских красок, пленок и несветящихся экранов.
136 Глава 6 Аддитивная цветовая система RGB удобна для светящихся поверхностей, например экранов ЭЛТ или цветных ламп. Цветовые пространства RGB и CMY трехмерны, и их условно можно изо- бразить в виде куба (рис. 6 1) Началом координат в кубе RGB служит чер- ный цвет, а в CMY — белый. Желтый Синий Голубой Рис. 6.1. Цветовые кубы RGB (a), CMY (6) Красный Зеленый Голубой Ахроматические, т. е серые цвета в обеих моделях расположены по диаго- нали от черного до белого, а дополнительные лежат в противоположных вершинах. Преобразование между пространствами RGB и CMY выражается следующим образом: [RG В] = [1 1 1] - [CMY] В OpenGL используется цветовая модель RGB И последнее, что хотелось бы сказать до того, как переходить к практиче- ской работе. Если предполагается, что зависимость между значениями, по- лученными из цветовой модели и напряжением на электронных пушках мо- ниторов, является линейной, то для получения качественного изображения на экране необходимо произвести калибровку Интенсивность свечения экрана монитора зависит от напряжения, подан- ного на электронную пушку. I = const(V)7 Для вывода интенсивности Ik напряжение должно быть Экспериментальным путем получено, что 1 < / < 4 , а для цветного монитора обычно лежит в пределах 2,3 — 2,8 Результаты калибровки используются, как значения в таблице цветов. Такая процедура называется гамма- коррекцией. Кроме того, существует проблема цветовой гармонии, т е. под- бора цветов, радующих глаз Один из основных принципов — упорядочен- ный подбор цветов, например вдоль определенной траектории или в одной плоскости цветовой модели Гармоничные цвета часто заимствуются у при- роды, например, последовательность оттенков зеленого Можно выбирать цвета одинаковой насыщенности или тона, т е более или менее похожие
Цвет в компьютерной графике 137 Обработка цветов в OpenGL Процесс работы с цветами, реализованный в OpenGL, схематично представ- лен на рис 6 2 Так же, как и при работе с графикой GDI, в OpenGL реализованы два ре- жима установки цветов указание индекса в некоторой палитре, либо с по- мощью непосредственного определения значений красного, зеленого и си- него компонентов цвета В основном в приложениях, как с использованием GDI, так и с использованием OpenGL, работают в режиме покомпонентного задания цветов (RGBA), поскольку именно в этом режиме имеется больше возможностей для работы с цветом затенение, освещение, устранение сту- пенчатости и некоторые другие Использование индексного режима задания цвета целесообразно в следующих случаях □ Доступно ограниченное число битовых плоскостей цвета — в этом случае при использовании индексного режима можно создавать более реали- стичные тени
138 Глава 6 □ При переносе приложений, использующих индексный режим, из других операционных платформ □ При использовании цветовой анимации Вот, практически, и все случаи, когда индексный режим работы с цветами более предпочтителен, чем режим RGBA И хотя индексный режим исполь- зуется реже, приведем команды и для него void gllndex[s i f d](GLtype index) void gllndex[s i f d] v(GLtype index) Эти команды устанавливают новое значение индекса цвета в палитре Теку- щее значение индекса цвета хранится в формате с плавающей точкой Режим RGBA использует красный (R), зеленый (G) и синий (В) значения цвета, которые вместе определяют цвет пикселя на экране Каждое значение R, G или В определяет интенсивность соответствующего основного цвета Все значения лежат в диапазоне от 0 0 — низкая, до 1 0 — высокая интен- сивность Значение параметра альфа (А) не принимает участия в формиро- вании результирующего цвета, а определяет степень прозрачности значение альфа, равное 0 0, подразумевает полную прозрачность, а значение 1 0 — полную непрозрачность соответствующего фрагмента Этот компонент цвета используется обычно для управления смешением цветов Для работы в режиме RGBA OpenGL предоставляет команды void giColor[3 4][b s i f d](GLtype components) void glColor[3 4][b s i f d] v (GLtype components) которые устанавливают новое четырехкомпонентное значение цвета Они представлены в двух основных вариантах glColor3* и glColor4* Во втором варианте явным образом задаются все четыре составляющие цвета, а в вари- анте трех аргументов устанавливаются красный, зеленый и синий компо- ненты цвета, а параметр альфа устанавливается в 1 0 Независимо от типа задаваемых параметров, в OpenGL все они хранятся в формате с плавающей точкой и ограничены диапазоном [0 0, 1 0] — значение 0 0 соответствует минимальной, а 1 0 — максимальной интенсивности соответствующего компонента При целочисленных значениях аргументов происходит линей- ное отображение в указанный диапазон GLtype Преобразование GLbyte (2c + 1) / (28 - 1) GLshort (2c + 1)/(216- 1) GLint (2c + 1) / (232 - 1) GLfloat c GLdouble c где с — составляющая цвета
Цвет в компьютерной графике 139 Однако следует иметь в виду, что отображение в указанный диапазон осу- ществляется только непосредственно перед записью в буфер цвета, а все промежуточные вычисления производятся во входном формате Текущий цвет можно обновлять в любой момент времени На этот счет нет никаких ограничений Как видите, все достаточно просто, и осталось обратить внимание на сле- дующие два обстоятельства 1 После того как некоторый цвет установлен в качестве текущего, он рас- пространяется на все создаваемые после этого примитивы 2 В режиме плавного сопряжения цветов, устанавливаемом командой glEnable(GL SMOOTH) OpenGL автоматически рассчитывает переходы цвета между вершинами по формуле С = txC| -г (1 — I) Определяя цвета вершин примитивов, можно получать очень интересные цветовые эффекты, но это уже творчество разработчика Диапазон передачи цветов и их оттенков определяется размерами буферов цвета Здесь нам надо вернуться немного назад и вспомнить формат пиксе- лей При выборе формата пикселей в числе других параметров определяется число битов на пиксель в буфере цвета Так вот, если ваша аппаратура под- держивает формат 16, 24 или 32 бита на пиксель (и вы больше ни о ком не беспокоитесь), то можете смело пропустить весь следующий раздел Сведе- ния, приведенные в нем, необходимы в том случае, если аппаратура (разработчика и/или пользователя) поддерживает только формат пикселей 8 бит на пиксель Палитра Windows в режиме RGBA Как только что было сказано, если ваше приложение должно работать в 256- цветном режиме, необходима поддержка работы с палитрами Обеспечить это не сложно Прежде всего необходимо проверить, что в структуре PIXELFORMATDESCRIPTOR у вас установлен флаг PFDNEEDPALETTE1 Следующим шагом является создание палитры Я не стал придумывать ка- кой-то свой пример, а решил воспользоваться приложением Cube, входя- щим в комплект примеров, поставляемых с Visual C++ Посмотрим как это делается "у них" void CCubeView-:CreateRGBPalette >' PIXELFORMATDESCRIPTOR pfd, LOGPALETTE *pPal; int n, i; 1 Можно воспользоваться моим приложением из первой главы PixFimtChcck, чтобы убедиться в том, что фтаг PFD NEED PALETTE установлен только у тех форматов пикселей, у кото- рых используется 8 битов на пиксель в буфере цвета
140 Глава 6 n =::GetPixelFormat(m_pDC->GetSafeHdc()); ::DescribePixelFormat(m_pDC->GetSafeHdc(), n, sizeof(pfd), &pfd); if(pfd.dwFlags & PFD_NEED_PALETTE){ n = 1 « pfd.cColorBits; pPal = (PLOGPALETTE) new char[sizeof(LOGPALETTE) + n * sizeof(PALETTEENTRY)], ASSERT(pPal '= NULL); // Стандартные установки номера версии и числа элементов палитры pPal->palVersion = 0x300; pPal->palNumEntries = n; // Заполняем палитру цветами for(i=0; 1<п; 1++){ pPal->palPalEntry[i].peRed = ComponentFromlndex(i, pfd.cRedBits, pfd.cRedShift), pPal->palPalEntry[i].peGreen = ComponentFromlndex(i, pfd.cGreenBits, pfd.cGreenShift); pPal->palPalEntry[i].peBlue = ComponentFromlndex(i, pfd.cBlueBits, pfd.cBlueShift); pPal->palPalEntry[i].peFlags = 0; // Восстанавливаем 12 системных цветов if((pfd.cColorBits == 8) && (pfd.cRedBits == 3) && (pfd.cRedShift == 0) && (pfd.cGreenBits == 3) && (pfd.cGreenShift == 3) && (pfd.cBlueBits == 2) && (pfd.cBlueShift == 6)){ for(i =1; i <= 12; i++) pPal->palPalEntry[defaultOverride[i]] = defaultPalEntry[i]; } // Создаем палитру m_cPalette.CreatePalette(pPal); delete pPal; // Устанавливаем ее в контексте устройства m_pOldPalette = m_pDC->SelectPalette(&m_cPalette, FALSE); m_pDC->RealizePalette(); } } Прежде всего функция проверяет, установлен ли флаг PFDNEEDPALETTE, и если нет, то завершает свою работу, т к использование палитры не требуется В противном случае необходимо ее создать, что собственно и делается. После создания палитры нужно заполнить ее цветами и выбрать в контекст устройства Рассмотрим некоторые из этих действий более подробно.
Цвет в компьютерной графике 141 Заполнение структур PALETTEENTRY Для того чтобы OpenGL корректно воспроизводила рисунки, функция должна заполнить 256 структур правильными цветами, для чего использу- ются следующие поля структуры PIXELFORMATDESCRIPTOR cRedBits, cRedShift, cGreenBits, cGreenShift, cBlueBits, cBlueShift, которые определяют, как 8 битов поделены между красным, зеленым и синим компонентами цве- та При этом exBits определяет число битов, отведенных для соответствую- щего цвета, a ex Shift — размещение цвета в этих 8 битах Для основного формата OpenGL имеем следующее распределение cRedBits = 3, cGreenBits = 3, cBlueBits = 2, cRedShift = О cGreenShift = 3 cBlueShift = 6 Такая палитра называется палитрой 3-3-2, и в ней отводятся 3 бита для красного, 3 бита для зеленого и 2 бита для синего цветов (рис 6 3) Синий Зеленый Красный Рис. 6.3. Распределение цветов между 8 битами Полученное 8-битовое значение является индексом в палитре Рассмотрим пример Команда glColor3f(l.О, 014, 0.6667) определяет фиолетовый цвет В этом случае необходимо максимум красного (111 двоичных), 14% зеленого (7x0.14 = 001 двоичных) и две трети синего (Зх 06667 = 10 двоичных) Таким образом, этот цвет в формате 3-3-2 представляется двоичным числом 10001111, или десятичным 143, которое определяет индекс в палитре и, сле- довательно, 143 элемент палитры должен содержать те же самые относи- тельные значения красного, зеленого и синего цветов, т е (255x1 0, 255x0 14, 255x0 6667) = (255, 36, 170) Именно такую работу и выполняет фрагмент кода в функции Create RGB Palette, а точнее функция unsigned char CCubeView: ComponentFromlndex(int i, UINT nbits, UINT shift){ unsigned char val; val = (unsigned char) (i » shift); switch(nbits){ case 1: val &= 0x1; return oneto8[val]; case 2: val &= 0x3; return twoto8[val]; case 3: val &= 0x7; return threeto8[val];
142 Глава 6 default: return О, I совместно с массивами m_threeto8, m_twoto8 и m_oneto8, которые преобразу- ют одно-, двух- и трехбитные числа в 8-битные Например, unsigned char threeto8[8] = { О, 0111»1, 0222»1, ОЗЗЗ»1, 0444»1, 0555»1, 0666»1, 0377 }; или в десятичном виде1 unsigned char threeto8[8] = { 0, 36, 73, 109, 146, 182, 219, 255 }; Эти числа не мифические, а рассчитываются следующим образом например для красного цвета cRedBits = 3, и следовательно 255/(23 - 1) = 36,4, кото- рое последовательно прибавляется к массиву, после чего числа округляются до ближайшего целого Аналогично и для других массивов Если же рассматривать какой-либо другой формат цвета, то и в этом случае функция Create RGB Palette успешно справляется с работой Системные цвета Если приложению требуются все 256 цветов, то в принципе можно их все и задействовать Но при этом нельзя забывать, что все окна, меню, кнопки и т. д окрасятся в непривычные цвета Чтобы этого не происходило Windows резервирует для себя 10 первых и 10 последних цветов, которые включают в себя и 16 стандартных цветов VGA Фрагмент if((pfd.cColorBits == 8) && (pfd.cRedBits == 3) && (pfd.cRedShift == 0) && (pfd.cGreenBits == 3) && (pfd cGreenSnift == 3; && (pfd.cBlueBits == 2) && (pfd cBlueShift == 6)) for(i = 1, i <= 12; i++) pPal->palPalEntry[defaultOverride[i]] = defaultPalEntry[i]; отвечает за восстановление "испорченных" системных цветов, используя для этого массивы static int defaultOverride[13] = t 0, 3, 24, 27, 64, 67, 88, 173, 181, 236, 24d 164, 91 Напомню, что в С и C++ числа, начинающиеся с 0, рассматриваются как восьмеричные
Цвет в компьютерной графике 143 static PALETTEENTR1 { О, О, О, { 0x80,0, О, { 0, 0x80,0, { 0x80,0x80,0, { О, С, 0x80, { их80,0, 3x80, { 0, 0x80,С>83, { OxCO, ОхСО, ОхС'З, { 192, 220, 192, { 166, 202, 240, { 255, 251, 240, { 160, 160, 164, { 0x80,0x80,0x80, { OxFF, О, О, { 0, OxFF, О, { OxFF, OxFF, О, { О, 0, OxFF, { OxFF,0, OxFF, { 0, OxFF, OxFF, { OxFF, OxFF, OxFF, defaultPalEntry [. Первый массив — defaultOverride — содержит индексы элементов палитры, в которых должны храниться системные цвета А второй — defaultPalEntry со- держит RGB значения системных цветов, которые добавляются в палитру В данном случае для палитры 3-3-2 8 из 256 цветов совпадают с системными, поэтому заменить нужно всего 13 элементов палитры Г амма-коррекция В начале этой главы я уже говорил о назначении гамма-коррекции Этот фактор можно учесть в массивах m_threeto8 и m_twoto8 На рис 6 4 представ- лен график, по которому можно производить гамма-коррекцию для значе- ния у = 1 4 Здесь по оси х расположены индексы в массиве m_threeto8 В других случаях следует использовать приведенную выше формулу Значение у = 1 0 соответствует не скорректированному линейному распределению ин- тенсивностей цветов Обработчики сообщений Последнее, что осталось сделать, — эго обеспечить обработку сообщений WM_QUERYNEWPALETTE и WM PALETTECHANGED Сложного здесь ничего нет, поэтому привожу только фрагменты кода с небольшими ком- ментариями
144 Гпава 6 —m_threeto8 Гамма коррекция Рис. 6.4. Графики, показывающие значения в массиве m_threeto8 и массиве с учетом гамма-коррекции void CMainFrame::OnPaletteChanged(CWnd* pFocusWnd){ // Этот обработчик активизируется всегда, когда какое-либо // приложение изменяет системную палитру CFrameWnd::OnPaletteChanged(pFocusWnd); // Если окно приложения активно, то реализуем логическую палитру if(pFocusWnd ’= this) OnQueryNewPalette() ; ) BOOL CMainFrame::OnQueryNewPalette(){ // Это сообщение посылается окну, которое становится активным WORD i; CPalette *pOldPal; CCubeView *pView = (CCubeView *)GetActiveView(); CClientDC de (pView) ,- // В ответ мы должны реализовать свою логическую палитру, т к // пока главное окно не было активным, другое приложение // могло изменить системную палитру pOldPal = de.SelectPalette(&pView->m_cPalette, FALSE); i = dc.RealizePalettef); de.SelectPalette(pOldPal, FALSE); // Если удалось отобразить хоть один цвет в системную палитру, // перерисовываем окно if(i > 0) InvalidateRect(NULL) ; return CFrameWnd::OnQueryNewPalette();
Цвет в компьютерной графике j4$ Вот и все действия, которые необходимо предпринять, чтобы приложение корректно работало не только с 16-, 24- и 32-битными цветами, но и с па- литрой при 256 цветах Я только вскользь упомянул о возможности работы с цветами в индексном режиме. Мне представляется, что приведенного материала вполне достаточ- но, чтобы при желании можно было использовать его немногочисленные преимущества Надеюсь, что теперь ваше общение с цветом будет достаточно продуман- ным, а мы переходим к следующей очень интересной теме
146 Глава 7 Построение реалистических изображений Построение реалистических изображений включает в себя как физические, так и психологические процессы. Свет, после взаимодействия с окружаю- щей средой попадает в глаз, где вырабатываются электроимпульсы, воспри- нимаемые мозгом Восприятие — это приобретаемое свойство Из опытов известно, что чувствительность глаза к яркости света изменяется по лога- рифмическому закону Причем глаз приспосабливается к средней яркости обозреваемой сцены, поэтому область с постоянной яркостью на темном фоне кажется ярче или светлее, чем на светлом Это явление называется од- новременным контрастом (рис. 7.1) Рис. 7.1. Эффект одновременного контраста Яркость центрального квадрата на обоих рисунках равна 0 5, а охватываю- щего — 0.8 на левом и 0 2 на правом рисунке Похожее на одновременный контраст явление существует и для цветов Еще одним свойством глаза, имеющим значение для компьютерной графи- ки, является то, что границы областей постоянной интенсивности кажутся более яркими, в результате чего такие области воспринимаются как имею- щие переменную интенсивность Это явление называется эффектом по.юс Маха и особенно хорошо заметно на полутоновых поверхностях, заданных многоугольниками Увеличивая количество граней многоугольников, его можно ослабить, но полностью устранить нельзя (рис 7 2) Это только несколько вопросов, возникающих при создании реалистических изображений. Мы же займемся вопросами закрашивания видимых поверх- ностей Внешний вид поверхности зависит от вида источника света, кото-
Построение реалистических изображений 14} рым освещается объект, от свойств поверхности (цвет, текстура, отража- тельная способность), а также от положения и ориентации поверхности от- носительно источника света и других поверхностей. Источник света может представлять собой точечный источник, подобный солнцу или лампе нака- ливания, или же распределенный источник, аналогичный группе ламп дневного света На практике во многих случаях присутствует также естест- венный свет, падающий с самых разных направлений Рис. 7.2. Эффект полос Маха Наиболее простым для моделирования источником является рассеянный естественный свет, т к он обеспечивает постоянное освещение всех по- верхностей независимо от их ориентации К сожалению, такой свет дает весьма нечеткие изображения, поскольку достаточно редки ситуации, когда освещение обеспечивается только естественным светом При естественном освещении две смежные стороны куба должны быть закрашены одинаково, а их общее ребро должно быть неразличимо Более сложным (с точки зрения моделирования), но зато более реальным, является точечный источник, при использовании которого освещенность поверхности зависит от ее ориентации если лучи направлены перпендику- лярно к поверхности — она освещена ярко Чем меньше угол падения лучей, тем меньше освещенность Такое изменение освещенности позволяет эф- фективно распознавать трехмерную структуру объекта Повышение реалистичности изображения может быть достигнуто путем воспроизведения теней, которые отбрасывает объект, освещенный точечным источником света Тени не только повышают реалистичность изображения, но и являются дополнительным средством для распознавания глубины Еще одним способом повышения реалистичности изображения является воспроизведение свойств поверхности Одни поверхности являются матовы- ми и рассеивают отраженный свет по разным направлениям, другие — обла- дают блеском и отражают свет только в некоторых направлениях Поверхно- сти могут быть полупрозрачными, т е пропускать часть света и одновре- менно отражать другую часть Другим свойством поверхности является их текстура Очень редко поверх- ности бывают совершенно гладкими, однако именно такими их моделируют
148 Глава 7 с помощью многоугольников и параметрических поверхностей Аналогично, поверхность редко бывает окрашена равномерно чаще на ней бывает какой- либо узор. Реалистичность изображения можно повысить путем нанесения узора на поверхность Само перечисление способов построения реалистичных изображений гово- рит о том, какой большой объем работы необходимо проделать, чтобы полу- чить что-нибудь в самом деле стоящее И это было бы действительно так, если бы не библиотека OpenGL, которая берет на себя большую часть "черновой" работы В этой главе мы займемся моделированием источников света, а в следующей рассмотрим, как можно наложить на поверхность тек- стуру Освещение объектов Световая энергия, падающая на поверхность, частично поглощается и пре- вращается в тепло, а частично отражается и пропускается Объект можно увидеть, только если он отражает или пропускает свет1 Количество погло- щенной, отраженной или пропущенной энергии зависит от длины волны света. Если объект поглощает лишь определенные длины волн, то у света, исходящего от объекта, изменяется распределение энергии — объект выгля- дит цветным Цвет объекта определяется поглощаемыми длинами волн Свойства отраженного света зависят от строения, направления и формы ис- точника света, а также от ориентации и свойств поверхности Отраженный от объекта свет может быть диффузным или зеркальным Свойством диффузного отражения, т е. равномерного по всем направле- ниями рассеивания света, обладают матовые поверхности При этом кажет- ся, что поверхности имеют одинаковую яркость независимо от угла обзора Для таких поверхностей справедлив закон, устанавливающий соответствие между количеством отраженного света и косинусом угла 0 между направле- нием L на точечный источник света и нормалью к поверхности (рис 7 3), т е количество отраженного света не зависит от положения наблюдателя, а определяется материалом объекта и длиной волны света Для представления диффузного отражения от цветных поверхностей расчеты проводятся от- дельно для основных составляющих цвета Рис. 7.3. Падающий свет и нормаль к поверхности 1 Если объект поглощает весь падающий на него свет, то он невидим и называется абсолютно черным телом
Построение реалистических изображений 14[ Зеркальное отражение происходит от внешней поверхности объекта Еслр осветить ярким светом яблоко, то в результате зеркального отражения воз никнет световой блик, а свет, отраженный от остальной части яблока, поя вится вследствие диффузного отражения При этом в том месте, где нахо- дится световой блик, яблоко кажется не красным, а скорее белым, т е ок- рашенным в цвет падающего света. Если изменить положение головы, к сместится и световой блик. Это объясняется тем, что блестящие поверхно сти неодинаково отражают свет по всем направлениям От идеально отпо- лированной поверхности свет отражается только в том направлении, о которого углы падения и отражения совпадают Это означает, что наблюда- тель сможет увидеть зеркально отраженный свет только в том случае, еслг угол а равен нулю (рис 7 4) Таким образом, интенсивность зеркальной отражения зависит от угла падения, длины волны падающего света и свойств вещества К точечному источнику света Направление отраженного света К точке наблюдения Рис. 7.4. Зеркальное отражение Поверхности могут обладать не только свойствами зеркального и диффузного отражения, но и свойствами направленного и диффузного пропускания На- правленное пропускание света происходит сквозь прозрачные вещества (например, стекло). Через них обычно хорошо видны предметы, даже несмот- ря на то что лучи света, как правило, преломляются, т е отклоняются от пер- воначального направления Диффузное пропускание света происходит сквозь просвечивающие материалы (например, замерзшее стекло), в которых поверх- ностные или внутренние неоднородности приводят к беспорядочному пере- мешиванию световых лучей. Поэтому когда предмет рассматривается через просвечивающее вещество, его очертания размыты Очевидно, что если имеется не один, а несколько источников света, то каж- дая из перечисленных составляющих от каждого источника суммируется Все из перечисленных свойств моделей освещенности нашли свое отраже- ние, с той или иной степенью полноты, в OpenGL В конце этого очень короткого теоретического обзора перечислим те пара- метры, которые необходимо учитывать при расчете освещенности □ Текущий вектор нормали □ Свойства материала □ Параметры источника света □ Параметры модели освещения Рассмотрим каждый из них более подробно с точки зрения поддержки этих параметров в OpenGL
150 Глава 7 Нормали В самом общем случае нормаль к поверхности представляет ее локальную кривизну, и, следовательно, направление зеркального отражения (рис 7 5) Применительно к нашим знаниям можно сказать, что нормалью называется вектор, определяющий ориентацию грани (рис 7 6). Вектор нормали явля- ется одной из характеристик, ассоциированных с каждой вершиной, и для его определения в OpenGL предусмотрены специальные команды void glNormal3[b s i f d](type coords) void glNormal3[b s i f d] v(type coords) которые присваивают значения координат, заданных параметрами coords. вектору нормали Аргументы типов GLbyte, GLshort и GLint преобразуются в формат с плавающей точкой и ограничиваются диапазоном [-1.0, 1 0] В об- щем случае нормаль, определяемая этими командами, не имеет единицы длины Однако если включен режим нормализации (выполнена команда glEnable с аргументом GL_NORMALIZE), то нормали, определенные при помощи этой команды, нормализуются после трансформации По умолча- нию нормализация заблокирована, а вектор нормали имеет координаты (0 0, 0.0, 1.0) Рис. 7.5. Векторы нормали Рис. 7.6. Г рань и нормаль к ней Изменять параметры вектора текущей нормали можно в любой момент вре- мени. И, управляя их текущими значениями, можно добиваться очень инте- ресных эффектов, в частности, при освещении объектов К нормалям мы еще не раз будем возвращаться, а пока рассмотрим свойства материалов и связанные с ними вопросы. Свойства материала Различные материалы по-разному отражают свет Металл это делает хоро- шо, а пластиковые поверхности обычно выглядят тусклыми Кроме того, некоторые поверхности сами излучают свет, например, фосфор Задавая различные параметры материала, можно управлять тем, насколько блестя-
Построение реалистических изображений 151 щим он будет выглядеть, и будет ли он сам излучать свет Посмотрите на рис 7 7, на котором изображены 24 чайника, освещаемые одним источни- ком света А теперь посмотрим, как это было сделано /* 1-й столбец, изумруд, желтовато-зеленый, вулканическое стекло, жемчуг, рубин, бирюза 2-й столбец: латунь, бронза, хром, медь, золото, серебро 3-й столбец черный, голубой, зеленый, красный, белый и желты:? пластик 4-й столбец, черный, голубой, зеленый, красный, белый и желтый каучук */ void CMaterialsView OnDraw(CDC* pDC) { CMaterialsDoc* pEoc = GetDocument(); ASSERT_VALID(pDoc). glClear(GL_COLOR_BUFFER_BIT I GL_DEPTH_BUFFER_BIT), // Рисуем изумрудный чайник renderTeapot(2 С, 17.0, 0 3215, 0 1745, 0.0215, 0 07568, 0.61424, 0 07568, 0.633, 0 727811, 0 633, 0 6! ; // Рисуем желтовато-зеленый чайник renderTeapot(2 0, 14 0, 0 135, 0 2225, 0.1575, 0.54, 0 89, 0 63, 0 316228, 0 316228, 0.316228, 0.1) , // Рисуем чайник из желтого каучука renderTeapot(14 0, 20, 0 05, 0 05, 0 0, 05, 05, 04, 0 Л J 7, 04, 07 .25). glFlush(), void CMaterialsView renderTeapot(GLfloat x, GLfloat y, GLfloat arr.br, GLfloat ambg, GLfloat ambb, GLfloat difr, GLfloat difg, GLfloat difb, GLfSoat specr, GLfloat specg, GLfloat specb, GLfloat shine) float mat Г 4] ;
152 Глава 7 // Сохраняем параметры текущей матрицы glPushMatrix() ; glTranslatef(х, у, 0.0); // Определяем фоновый цвет материала mat[0] = ambr, mat[l] = ambg; mat[2] = ambb; mat[3] =10, glMaterialfv(GL_FRONT, GL_AMBIENT, mat); // Определяем диффузный цвет материала mat[0] = difr; mat[l] = difg; mat[2] = difb; glMaterialfv(GL_FRONT, GL_DIFFUSE, mat); // Определяем зеркальный цвет материала mat[0] = specr, mat[l] = specg; mat[2] = specb; glMaterialfv(GL_FRONT, GL_SPECULAR, mat), // Определяем степень зеркального отражения glMaterialf(GL_FRONT, GL_SHININESS, shine*128.0); // Рисуем чайник auxSolidTeapot(1.2) ; glPopMatrix() , Рис. 7.7. Объекты с различными свойствами материала
Построение реалистических изображений 153 Что здесь для нас интересно? Прежде всего — сами свойства В приведен- ном фрагменте мы устанавливаем рассеянный, диффузный и зеркальный цвета материала, причем, только для его лицевой поверхности Можно ли задавать какие-нибудь еще параметры? Для ответа на этот вопрос рассмот- рим команды void glMaterial[i f]( GLenum face, GLenum pname, GLtype param) void glMaterial[i f] v( и GLenum face, GLenum pname, GLtype* params) которые позволяют определить различные свойства материала Какие имен- но — зависит от используемых параметров Параметры материала, как и во многих других случаях, различаются для лицевой и обратной поверхностей Параметры лицевой поверхности используются для точек, линий, битовых массивов и всех многоугольников, когда заблокировано двустороннее осве- щение, или только для лицевых, если двустороннее освещение разрешено Параметры для обратных поверхностей определяют затенение невидимых многоугольников, только когда разрешено двустороннее освещение К ка- ким поверхностям применяются задаваемые параметры, определяется аргу- ментом face: GLFRONT GLBACK GL_FRONT_AND_BACK лицевые обратные и те и другие Аргумент pname определяет, какие параметры материала будут обновляться, и может принимать только одно значение — GL_SHININESS, задающее степень зеркального отражения материала для первой версии команды и следующие значения для второй- Параметр Значение gl_ambient Параметр params содержит четыре целых или ве- щественных значения цветов RGBA, которые опре- деляют рассеянный цвет материала По умолчанию значение рассеянного цвета равно (0 2, 0 2, 0 2, 1 0) gl_diffuse Параметр params содержит четыре целых или ве- щественных значения RGBA, которые определяют цвет диффузного отражения материала По умол- чанию значение диффузного цвета равно (0 8, 0 8, 0 8, 1 0) GL.SPECULAR Параметр params содержит четыре целых или ве- щественных значения RGBA, которые определяют цвет зеркального отражения материала По умол- чанию значение зеркального цвета равно (0 0, 0 0, 0 0, 1 0)
154 Глава 7 (продолжение) Параметр Значение GL_EMISSION Параметр params содержит четыре целых или ве- щественных значения RGBA, которые определяют интенсивность излучаемого света материала По умолчанию значение излучаемого света равно (0 0, 0 0, 0 0, 1 0) GLSHININESS Параметр params содержит одно целое или веще- ственное значение, которые определяют степень зеркального отражения материала Можно исполь- зовать только значения от 0 до 128 По умолчанию установлено значение 0 GL_AMBIENT_AND_DIFFUSE Эквивалентно двум вызовам команды с аргумен- тами pname, равными GL_AMBIENT и GL_DIFFUSE, и одинаковыми значениями параметра params GL.COLORJNDEXES Параметр params содержит три целых или вещест- венных значения, определяющих индексы цветов для рассеянного, диффузного и зеркального отра- жения соответственно Последний аргумент — param (params) содержит значение(я), которые при- сваиваются соответствующему параметру Из приведенного описания видно, что, задавая параметры материала, можно управлять тем, насколько блестящим будет выглядеть материал и будет ли он самостоятельно излучать свет Другими словами, можно определять от- ражающие свойства материала Наиболее часто учитываемыми в различных моделях являются диффузный и зеркальный отраженный свет, поскольку диффузное отражение придает объекту его естественный цвет, а зеркальное определяет отражающие свойства поверхности на очень блестящей поверх- ности зеркальное отражение приводит к появлению резко очерченных бли- ков, а для менее блестящих объектов выглядит более тускло Таким образом, чтобы объект выглядел блестящим, необходимо сузить угол зеркального от- ражения, а для тусклых поверхностей наоборот расширить его Используе- мые при моделировании математические зависимости показывают, что ин- тенсивность отраженного света определяется не углом отражения, а косину- сом этого угла Например, для модели Фонга зависимость имеет вид Is= w(i> A.)cosna, где w(i, X) — кривая отражения, представляющая отношение зеркально отра- женного света к падающему как функцию угла падения i и длины волны ап— степень, аппроксимирующая пространственное распределение зер- кально отраженного света Зачем я все это описываю? Дело в том, что в OpenGL имеется возможность косвенного управления углом зеркального
Построение реалистических изображений 755 отражения Осуществляется это при помощи параметра GLSHININESS, позволяющего определять именно значение п И последнее замечание каждый параметр устанавливается отдельным вызо- вом команды и, следовательно, не зависит от остальных Как видите, сложность здесь только одна — как определить необходимые значения задаваемых цветов, чтобы получить поверхность того материала, который требуется. Некоторые примеры приведены в рассмотренном фраг- менте, а остальные необходимо создавать самостоятельно Наряду с только что рассмотренной командой, для установки параметров материала в OpenGL реализована еще одна- void glColorMaterial( GLenum face, GLenum mode) У этой команды аргумент face может принимать те же значения, что и в команде glMaterial*, а аргумент mode — только значения GL AMBIENT ANDDIFFUSE, GLAMBIENT, GL_EMISSION, GLDIFFUSE и GLSPECULAR. По умолчанию используется GLAMBIENTAND DIFFUSE Если разрешен режим GL COLOR MATERIAL (вызовом коман- ды glEnable с соответствующим аргументом), то параметры поверхностей материала, заданные аргументами mode и face, принимают значения теку- щего цвета В этом случае можно изменять параметры материала для каждой вершины, используя только команду glColor*, без дополнительного вызова glMaterial*, что в некоторых случаях является более удобным Что еще добавить к сказанному? Наверное, именно здесь будет уместно рас- смотреть такие понятия, как лицевая и обратная поверхности, с которыми мы еще неоднократно будем иметь дело. А для этого необходимо вернуться немного назад — к примитивам. Грани Гранью называется сторона многоугольника Причем в качестве многоуголь- ника могут выступать как простейший треугольник, так и сложный много- угольник Единственным условием для них является то, что многоугольник любой степени сложности должен лежать в одной плоскости Отсюда оче- видно, что трехмерные объекты состоят из нескольких плоских многоуголь- ников И для того, чтобы изобразить "гладкий" конус потребуется достаточ- но большое число плоских многоугольников Каждый многоугольник имеет Две стороны, т е две грани, — лицевую и обратную И на обеих можно "рисовать" Причем, если мы "рисуем" на лицевой грани, а она в данный момент не видна, то мы только тратим время. Очевидно, что только одна грань может быть видима в окне в текущий момент времени Для того чтобы Не тратить время на воспроизведение невидимых граней, во всех графиче- ских системах реализованы механизмы, позволяющие различать лицевые и
156 Глава 7 обратные грани Естественно, что такой механизм есть и в OpenGL При- чем, в отличие от многих других систем, — очень гибкий При рассмотре- нии многоугольных примитивов уже говорилось о важности порядка обхода вершин. И связано это именно с гранями — лицевой и обратной Посмот- рите на рис 7 8, на котором представлены два возможных способа обхода вершин Рис. 7.8. Обход вершин по часовой стрелке и против После того как многоугольник спроецирован в окно, библиотека OpenGL "смотрит", в какой последовательности обходятся вершины многоугольника если они обходятся по часовой стрелке, то видимой является одна из гра- ней. если против часовой стрелки, то другая Библиотека OpenGL, в отли- чие от многих других графических библиотек, позволяет программисту са- мому определять, какую грань считать лицевой, а какую обратной Осущест- вляется это при помощи команды void glFrontFace(Glenum mode) которая позволяет определить, какая грань берется в качестве лицевой, в зависимости от направления обхода многоугольника — по часовой стрелке или против Направление обхода задается параметром mode, который может принимать значения GL_CW — по часовой стрелке и GL CCW — против часовой стрелки (установлено по умолчанию) Кроме возможности самостоятельного определения типа грани — лицевая или обратная, OpenGL предоставляет также механизм, позволяющий ис- ключить из процесса вывода грани одного типа Чтобы все стало понятным, рассмотрим еще одну команду, а затем небольшой пример void glCullFace(GLenum mode) Эта команда позволяет указать грань — лицевую или обратную В качестве граней могут использоваться треугольники, квадраты, многоугольники или прямоугольники Являются они лицевыми или обратными, задается коман- дой glFrontFace Какие грани будут отображаться, задается параметром mode, который может принимать одно из двух значений GL_FRONT — для лице- вых и GL_BACK — для обратных граней (последнее используется по умол- чанию). Для того чтобы разрешить OpenGL отбирать изображаемые грани, необхо- димо выполнить команду glEnable с аргументом GL_CULL_FACE. Блоки- ровка этого режима как обычно осуществляется командой glDisable с тем же аргументом
Построение реалистических изображений 157 На рис 7 9 представлено окно приложения Faces, для случая, когда направ- ления обхода вершин и режима выбора граней используются значения по умолчанию Режим отбора граней — заблокирован Рис. 7.9. Объект, у которого видны и лицевая и обратная грани Для того чтобы можно было поэкспериментировать с рассматриваемыми параметрами, я реализовал небольшой немодальный блок диалога (рис 7 10). где можно выбирать направление обхода вершин, определять, какие поверх- ности будут отображаться, а также некоторые параметры материала, отдель- но для лицевой и обратной граней Рис. 7.10. Немодальный блок диалога для управления параметрами отображения Теперь давайте посмотрим, как это все работает (Привожу только те фраг- менты, которые имеют непосредственное отношение к рассматриваемым вопросам полный текст можно найти на дискете ) void CFacesView- OnlnitialUpdate() ( // Действия по инициализации и созданию источника света
158 Гпава 7 // Определяем свойства материала лицевой грани static float front_mat_shininess[] = tlO 0}; static float front_mat_specular[] = t0 5, 04, 04, 3;, static float front_mat_diffuse[] = {0.7, 0 04, 0 04, 0», // Определяем свойства материала обратной грани static float back_mat_shininess[] = {3 2}; static float back_mat_specular[]= {0 07568,0.61424, 0 07568,1 Ob- static float back_mat_diffuse[] = (0.633, 0 0 633, 1 Ob // Устанавливаем параметры материалов glMaterialfv(GL_FRONT, GL_SHININESS, front_mat_shininess), glMaterialfv(GL_FRONT, GL_SPECULAR, front_mat_specular), glMaterialfv(GL—FRONT, GL_DIFFUSE, front_mat_diffuse), glMaterialfv(GL_BACK, GL_SHININESS, back_mat_shininess) , glMaterialfv(GL_BACK, GL_SPECULAR, back_mat_specular), glMaterialfv(GL_BACK, GL_DIFFUSE, back_mat_diffuse), // Создаем объект, который будем изображать quadObj = gluNewQuadric(); //и определяем его параметры radius1 = 10, radius2 = 5; anglel = 90; angle2 = 180; slices = 16; stacks = 10; height = 20, whichQuadric = 0; // Выводим немодальный блок диалога if(m_pDlg == NULL){ CMaterial *m_pDlg = new CMaterial; m_pDlg->Create (this), void CFacesView::OnDraw(CDC* pDC) { glLoadldentiry(); glRotatef(30 0, 1, 0, 0) glRotatef(60.0, 0, 1, 0); // Рисуем объект switch (whichQuadric) t case 0 glTranslatef(0, 0, -height/20 0), gluCylinder(quadObj, radiusl/10 0, radius2/l- 0,
Построение реалистических изображений 159 height/10.0, slices, stacks); break; // Здесь создаются некоторые другие объекты } /./ Стандартное завершение работы void CFacesView::OnSize(UINT nType, int ex, int cy) { COpenGLView::OnSize(nType, ex, cy) ; // Установка параметров области вывода, проекции // и преобразования координат glViewport(0, 0, сх, су); glMatrixMode(GL_PROJECTION) ; glLoadldentity(); glFrustum(-l, 1, -1, 1, 1, 10); gluLookAt(2, 2, 2, 0, 0, 0, 0, 0, 1); glMatrixMode(GL_MODELVIEW); Теперь нам осталось обеспечить смену режимов работы Обработчик сооб- щения для смены направления обхода (рис 7 11) Рис. 7.11. Смена направления обхода вершин по отношению к использованному для рис 7 9 void CMaterial. OnClockwise(UINT nID) { CheckRadioButton(IDC_CW, IDC_CCW, nID) ; // Определяем, какая грань считается лицевой glFrontFace(aFrontFace[nID — IDC_CW]);
160 Глава 7 / Этот фрагмент будет рассмотрен ниже if(nID == IDC_CW) glLightModelf(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE), else glLightModelf(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE); // Перерисовываем окно m pView->Invalidate(FALSE); it. pView->UpdateWindow () , Сравните рис 7 9 и 7 11. Следующий шаг — выбрать изображаемые грани (рис 7 12) Обе грани изо- бражены на рис. 7.9, а лицевыми считаются грани, которые обходятся про- тив часовой стрелки. На рис. 7.13 приведены те же поверхности, но для слу- чая, когда лицевыми считаются грани, которые обходятся по часовой стрелке Рис. 7.12. Вывод только лицевых (а) или только обратных (б) граней для режима GL_CCW void CMaterial::OnCullFace(UINT nID){ CheckPadioButton(IDC_CULL_FRONT, IDC_CULL, nID); // Если хотим увидеть обе грани — лицевую и обратную, // то необходимо блокировать режим отбора if(nID == IDC_CULL) glDisable(GL_CULL_FACE); else{ // В противном случае разрешаем отбор соответствующей грани glCullFace(aCullFace[nID - IDC_CULL_FRONT]), glEnable(GL_CULL_FACE),
Построение реалистических изображений 161 // Перерисовываем окно } Рис. 7.13. Вывод только лицевых (а) или только обратных (б) граней для режима GL_CW После знакомства со свойствами материала переходим к следующим пара- метрам освещения. Источник света Рассмотрим, как и какие параметры источника света можно устанавливать Начнем с команды glLighf, значения аргументов которой зависят от того, работаем мы с ее скалярной или векторной версией. void gILight[i f]( GLenum light, GLenum pname, GLfloat param) Аргумент light определяет номер источника света, общее число которых за- висит от реализации В качестве значения этого параметра следует исполь- зовать символическое имя GL LIGHTi, где i лежит в диапазоне от 0 до GL MAX LIGHTS. которое не может быть больше восьми Аргумент pname является символической константой, определяющей устанавливаемый пара- метр GL_SPOT_EXPONENT Параметр param содержит единственное целое или вещественное значение, которое задает рас- пределение интенсивности света Доступны зна- чения из диапазона [0, 128] Эффективность ин- тенсивности света ослабевает пропорционально
162 Глава 7 GL_SPOT_CUTOFF G L_CON STANT-ATTEN U ATI ON, GL_LINEAR_ATTENUATION, GL_QUADRATIC_ATTENUATION косинусу угла между направлением от источника и нормалью в вершине (рис 7 3) Чем больше это значение, тем более сфокусирован источник све- та По умолчанию установлено значение 0, что соответствует рассеянному свету Параметр param является целым или веществен- ным значением, которое определяет максималь- ный угол разброса источника света Доступны значения из диапазона [0, 90] и 180 По умолча- нию используется значение 180, что соответству- ет рассеянному свету Параметр param задает единственное целое или вещественное значение, определяющее один из трех факторов ослабления — постоянного, линей- ного или квадратичного Допустимыми являются только неотрицательные значения Интенсивность источника света ослабевает в соответствии с суммарным значением постоянного фактора, линейного фактора, умноженного на расстояние между источником и вершиной, и квадратичного фактора, умноженного на квадрат того же рас- стояния ________1_______ kOj + k.jjSi + k2is2 где к, — соответствующие коэффициенты ослаб- ления, a S, — расстояние от источника По умолчанию факторы ослабления имеют значе- ния (1, 0, 0) Аргумент param определяет значение, которое устанавливается для парамет- ра pname источника light Для векторной версии команды void gILight[i f] v( GLenum tight, GLenum pname, GLfloat* params) добавляются только дополнительные значения параметра pname GL.AMBIENT GL.DIFFUSE Параметр params содержит четыре целых или веществен- ных значения RGBA, которые определяют интенсивность фонового освещения По умолчанию значение рассеянного света равно (0 0, 0 0, 0 0, 1 0) Параметр params содержит четыре целых или веществен- ных значения RGBA, которые определяют интенсивность диффузного освещения По умолчанию значение интенсив- ности диффузного света равно (0 0, 0 0, 0 0, 1 0) для всех источников кроме нулевого, для которого эта интенсивность равна (1 0, 1 0, 1 0, 1 0)
Построение реалистических изображений 163 GL_SPECULAR GL_POSITION GL_SPOT_DIRECTION Параметр params содержит четыре целых или веществен- ных значения RGBA, которые определяют интенсивность освещения зеркального отражения По умолчанию значение интенсивности зеркального отражения равно (О О, О О, О О, 1 0) для всех источников кроме нулевого, для которого она равна (1 0,10,10,1 0) Параметр params содержит четыре целых или веществен- ных значения, определяющих положение источника света в однородных мировых координатах Эта позиция преобразу- ется видовой матрицей и сохраняется в видовых координа- тах Если компонент w положения равен 0 0, то свет рас- сматривается как направленный источник, а диффузное и зеркальное освещение рассчитываются в зависимости от направления на источник, но не от его действительного по- ложения, и ослабление заблокировано В противном случае эти параметры рассчитываются на основе действительного расположения источника в видовых координатах и ослабле- ние разрешено По умолчанию источник располагается в точке (0, 0, 1, 0), является направленным и параллельным оси z Параметр params содержит три целых или вещественных значения, задающих направление света в однородных ми- ровых координатах Направление источника света по умол- чанию задается значениями (0, 0, -1) После того как установлены параметры источников света, эти источники можно включать и выключать в любое время Источник света i включается или выключается командами glEnable или glDisable с аргументом GLLIGHTi, который определяется как GLLIGHTi = GL LIGHT0 + i Теперь посмотрим, как эти параметры устанавливаются па практике Фраг- менты взяты из приложения Lighting, имеющегося на дискете Прежде всего я создал специальный класс для работы с источником света class CLight{ public: CLight(), virtual -CLight(J; void listlnit(), void Init(int nNum, GLboolean on, ohar 'nair.e, GLboolean *irask, Point cos, GL^lcrn GLfloat Point GLfloat GLboolean char int GLboolean m_Diffuse Г 4 j m_Pos, m_LighrRotation; m_ShadonMask [ 4 j, m_Name[ 64 ] , m_Number; m On; / / Диффузный свет // Эти два параметра определяют // расположение источника света // Маска для создания тени // Имя источника // Порядковый номер источника света
164 Глава 7 CLightingView Am_pOwner, // Владелец источника света void InitPosition(), }; Как видите, он получился небольшим и не охватывает всех доступных воз- можностей, но с поставленной перед ним задачей справляется вполне ус- пешно Для нас интересны две его функции void CLight::InitPosition() { Point 1, d; glMatrixMode(GL_MODELVIEW); glLoadldentity() ; glMatrixMode(GL_PROJECTION), glLoadldentity(); 1 = m_Pos; // Вычисляем и устанавливаем расположение источника света l.pt[0] = (GLfloat)(m_Pos.pt[0]*cos((double)radians(m_LightRotation))); l.pttl] = (GLfloat)(m_Pos pt[0]*-sin((double)radians(m_LightRotation))), glLightfv(GL_LIGHT0 -r m_Number, GL_POSITION, 1 pt/, // В зависимости от положения объекта задаем // направление действия источника света d = (m_pOwner->m_SpherePosition - l).unit(); glLightfv(GL_LIGHT0 + m_Number, GL_SPOT_DIRECTION, d pt), } void CLight::listlnit() { Color c; // Создаем список изображений для источника света glNewList(listsLights + m_Number, GL_COMPILE), // Если источник "включен", то задаем его параметры if (ir_On) { // "Включаем" источник glCallList(listLightsOn) , // Задаем параметры материала корпуса и положения // источника света. // Затем рисуем источник glCallList(listLightDraw);
Построение реалистических изображений jgg // "Выключаем" источник glCallList(listLightsOff), } //В противном случае считаем, что он излучает черный свет else glColor3f(0, 0, 0) ; glEndList(); } Далее необходимо создать объекты этого класса и дополнительные функции для работы с ним class CLightingView: public COpenGLView { public: // Привожу только то, что имеет отношение к источникам света CLight m_lights[nLights]; public: void lightsOnOff(int which, int bVal); static float dtheta[nLights]; protected: // Функции, отвечающие за перемещения источников света void lightsMoveUpdate(int light, int dr, int dphi, int dtheta); int lightsMove(int light, float dr, float dphi, float dtheta, BOOL update); // Функции, отвечающие за инициализацию источников света void InitOnOff(), void lightDrawListlnit(); void lightslnit(); void squareListlnit(); // Функции, обеспечивающие изображение сцены void sceneRasterize(); void sceneProject(); void listslnit(), //{{AFX_VIRTUAL(CLightingView) public virtual void OnlmtialUpdate () ; protected: virtual void OnDraw(CDC* pDC); //}}AFX VIRTUAL
166 public: // Индикатор вывода изображения источников света BOOL drawLights; }; void CLightingView InitOnOffO { int i; // Создаем список изображений для "включенного" источника свет glNewList(listLightsOn, GL_COMPILE), for(i =0; i < nLights; 1++) if(m_lights[i].m_0n) // Разрешаем работу с i-м источником glEnable(GL_LIGHT0 + i); else // Запрещаем работу с i-м источником glDisable(GL_LIGHT0 P i); glEndList(); // Создаем список изображений для выключения всех источников о glNewList(listLightsOff, GL_COMPILE), for(i = 0; i < nLights, 1++) glDisable(GL_LIGHT0 + i) ; glEndList() ; } void CLightingView lightslnit() { int i; // Задаем параметры всех источников света for (1=0; i < nLights, 1++) t // Источники отличаются только диффузным цветом glLightfv(GL_LIGHT0 + i, GL_DIFFUSE, m_lights[i] m_Diffuse) // Задаем цвета зеркального и рассеянного света glLightfv(GL_LIGHT0 + i, GL_SPECULAR, black c). glLightfv(GL-LIGHTO + i, GL_AMBIENT, black c) // Задаем распределение интенсивности источников, // близкой к рассеянному свету glLightf (GL_LIGHT0 -г i, GL_SPOT_EXPONENT, 4), // Угол разброса источников света равен 90 градусов glLightf(GL_LIGHT0 + i, GL_SPOT_CUTOFF, 90),
Построение реалистических изображений 167 // Отдельно задаем параметры источника рассеянного света glLightfv(GL_LIGHT0 + nLights, GL_DIFFUSE, black c) , glLightfv(GL_LIGHT0 + nLights, GLJSPECULAR, black.c); glLightfv(GL_LIGHT0 + nLights, GL_AMBIENT, worldAmbient.c); // Он "светит" независимо от остальных источников glEnable(GL_LIGHT0 + nLights); // Еще один источник для создания бликов glLightfv(GL_LIGHT0 + nLights + 1, GL_DIFFUSE, black.c); glLightfv(GL_LIGHT0 + nLights + 1, GLJSPECULAR, black.c); glLightfv(GL—LIGHTO + nLights + 1, GL_AMBIENT, white.c); } Приведенные фрагменты показывают, что определение параметров источ- ников света во многом подобно установке свойств материала Каждый па- раметр задается отдельно, а если какой-либо не определен явным образом, то он все равно участвует в процессе освещения со значением, установлен- ным по умолчанию Отличающиеся параметры интуитивно понятны, и на первом этапе работы с источниками света некоторые трудности может вы- звать только тот, который определяет максимальный угол разброса, — GL SPOT CUTOFF Но и он становится совершенно понятным, если по- смотреть на рис 7 14 чем больше это значение, тем более "рассеянным" становится свет от источника Осталось только посмотреть, что получилось при создании трех источников света красного, зеленого и синего (рис 7 15) Но возможности OpenGL по поддержке режима освещения не ограничива- ются заданием параметров материала и источников света Имеется возмож- ность определить модель освещения
168 Глава 7 Рис. 7.15. Три видимых источника света и один невидимый — фоновый Модель освещения Эти параметры задаются командами void gILightModel[i f]( void glLightModel[i f] v( GLenum pname, и GLenum pname, GLenum param) const GLtype* params) Аргумент pname определяет единственное значение параметра модели осве- щения и может принимать следующие значения GL_LIGHT_MODEL_LOCAL_VIEWER Параметр param содержит единственное бу- левское значение, определяющее местополо- жение наблюдателя Если param = FALSE, то направление обзора считается параллельным оси —z и направленным вдоль нее, независимо от действительного положения в видовых ко- ординатах В противном случае (TRUE) пред- полагается, что наблюдатель находится в на- чале видовой системы координат По умолча- нию параметр param установлен в FALSE GL_LIGHT_MODEL_TWO_SIDE Параметр param представляет единственное булевское значение, которое определяет рас- чет освещенности многоугольников для одной или двух граней Это не влияет на расчет ос- вещенности для точек, линий или битовых массивов Если param = FALSE, то задано од- ностороннее освещение, и в уравнении осве-
Построение реалистических изображений 169 щения используются только параметры лице- вой грани материала В противном случае (TRUE) задается двухстороннее освещение В этом случае освещенность вершин обратной грани многоугольников рассчитывается на основе параметров обратной грани материала, и их нормали направлены в противоположные стороны Освещенность вершин лицевой гра- ни многоугольника всегда рассчитывается на основе параметров лицевой грани материала без изменения их нормалей По умолчанию FALSE Аргумент param определяет значение, которое присваивается параметру мо- дели освещения, заданному параметром pname Кроме того, для векторной версии команды доступно еще одно значение параметра pname GL_LIGHT_MODEL_AMBIENT Параметр рагат содержит четыре целых или вещественных значения, которые задают пол- ную фоновую интенсивность света По умол- чанию значение фонового цвета равно (0 2, О 2, 0 2, 1 0) Обратимся к примерам При описании свойств материала мы рассмотрели пример Materials, считая, что наблюдатель располагается в бесконечности (рис 7 7) Изменим его положение void CMaterialsView.:OnlnitialUpdate i) { // Залаем параметры модели освещенности. // Интенсивность рассеянного света GLfloat lmodel_amoient[] = (0 2, 0.2, 0.2, 1.0}, // Наблюдатель находится в начале видовой системы координат GLfloat local_view[ = {GL_TRUE}, // Устанавливаем параметры модели освещенности glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambienr), glLightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER, local_view), Результат внесенных изменений представлен на рис 7 16 Сравнивая его с тем, что изображено на рис 7 7 видим вполне ощутимую разницу даже на черно-белом изображении в книге Связано это с тем, что зеркально отра- женный свет наблюдатель может видеть, только если вектор зеркального отражения направлен точно на него (рис 7.4)
170 Глава 7 Рис. 7.16. Влияние положения наблюдателя на результат расчета освещенности (сравн с рис 7 7) Для того чтобы "почувствовать" влияние двух оставшихся параметров моде- ли освещения, обратитесь к рассмотренному ранее примеру Faces. Подведем некоторый итог В режиме RGBA цвет освещенной вершины оп- ределяется суммой интенсивностей эмиссии материала, рассеянного отра- жения материала и полной фоновой интенсивности модели освещения Вклад каждого источника света складывается из трех составляющих фоно- вой, диффузной и зеркальной Фоновая составляющая складывается из фо- нового отражения материала и фоновой интенсивности света Диффузная составляющая определяется диффузным отражением материала, диффузной интенсивностью источника света и нормализованным вектором нормали И, наконец, зеркальная составляющая определяется зеркальным отражением материала, зеркальной интенсивностью источника света и нормализован- ными векторами от вершины к источнику света и к наблюдателю Вклад всех трех составляющих уменьшается в зависимости от расстояния до ис- точника, от направления на него, экспоненциального разброса и угла раз- броса источника света Компонент альфа результирующего цвета устанавли- вается в значение альфа диффузного отражения материала В индексном режиме значения индекса освещенности вершины для фоно- вого и зеркального значений передаются в команду glMaterial* при помощи параметра GL COLOR INDEXES Диффузный и зеркальный коэффициен-
Построение реалистических изображений 171 ты рассчитываются на основании цвета источника с учетов весовых коэф- фициентов (0 30, 0 59, 0 11) Блеск материала, а также уравнения отражения и ослабления — такие же, как и в случае режима RGBA Для удобства сведем все рассмотренные параметры, связанные с освещенно- стью, в одну таблицу Параметр Число зна- чений Значения по умолчанию Описание Параметры материала GL_AMBIENT 4 (0 2, 0 2, 0 2, 1 0) Рассеянный цвет материала GL_DIFFUSE 4 (0 8,0 8,0 8, 1 0) Диффузный цвет материала GL_SPECULAR 4 (0 0, 0 0, 0 0, 1 0) Зеркальный цвет материала GL_EMISSION 4 (0 0, 0 0, 0 0, 1 0) Излучаемый цвет материала GL_SHININNESS 1 00 Степень зеркально- го отражения мате- риала из диапазона [0 0, 128 0] GL_COLOR_INDEXES 3 0 0; 0 0; 0 0 Индексы рассеян- ного, диффузного и зеркального цветов материала Параметры источника света GL_AMBIENT 4 (0 0, 0 0, 0 0, 1 0) Интенсивность рас- сеянного света i-ro источника GL_DIFFUSE 4 (1 0, 1 0, 1 0, 1 0) (0 0, 0 0, 0 0, 1 0) Интенсивность диффузного света нулевого источника Интенсивность диффузного света i-ro источника GL.SPECULAR 4 (1 0, 1 0, 1 0, 1 0) (0 0, 0 0, 0 0, 1 0) Интенсивность зер- кального света ну- левого источника Интенсивность зер- кального света i-ro источника
172 Глава 7 (продолжение) Параметр Число зна- чений Значения по умолчанию Описание GL.POSITION 4 (0.0, 0 0, 1 0. 1 0) Положение i-ro ис- точника света в ми- ровых координатах GL_SPOT_DIRECTION 3 (0 0, 0 0, -1 0) Направление дей- ствия i-ro источника света в мировых координатах GL_SPOT_EXPONENT 1 0.0 Показатель распре- деления интенсив- ности i-ro источника света из диапазона [0 0, 128 0] GL_SPOT_CUTOFF 1 180 0 Угол разброса i-ro источника света из диапазона [0 0, 128 0] или 180 0 GL_CONSTANT_ATTENUATION 1 1.0 Коэффициент по- стоянного ослабле- ния i-ro источника света из диапазона [0 0,оо) GL-LINEAR-ATTENUATION 1 0.0 Коэффициент ли- нейного ослабления i-ro источника света из диапазона [0 0,оо) GL_QUADRAT1C_ATTENUATION 1 00 Коэффициент квадратичного ослабления i-ro ис- точника света из диапазона [0 0,-с) Параметры модели освещения GL_LIGHT_MODEL_AMBIENT 4 (0 2, 0 2, 0 2, 1 0) Общий рассеянный свет для всех объектов GL_L1GHT_MODEL_LOCAL_ VIEWER 1 FALSE Положение наблю- дателя в видовой системе координат (0, 0, 0) — TRUE или (0, 0,ос) - FALSE
Построение реалистических изображений 173 (продолжение) Параметр Число зна- чений Значения Описание по умолчанию gl_light_model_two_side 1 FALSE Освещение одной (FALSE) или обеих (TRUE) граней Кроме рассмотренных возможностей моделирования освещения объектов, которые непосредственно поддерживаются OpenGL, для придания большей реалистичности следует учитывать также такие факторы, как прозрачность некоторых материалов и, наоборот, полную непрозрачность, которая приво- дит к появлению теней Эти возможности OpenGL непосредственно не под- держиваются, и их учет полностью ложится на плечи программистов Рас- смотрим некоторые аспекты этих вопросов Поверхности, пропускающие свет Как уже отмечалось в начале главы, поверхности могут обладать не только свойствами зеркального и диффузного отражения, но и аналогичными свой- ствами пропускания Направленное (зеркальное) пропускание света проис- ходит сквозь прозрачные вещества, например, воду или стекло Через них предметы обычно видны хорошо, несмотря на го, что лучи света отклоня- ются от своего первоначального направления (рис 7 17) Если же свет при пропускании через вещество рассеивается, то мы имеем диффузное пропус- кание Такие вещества кажутся полупрозрачными или матовыми, а объект, если смотреть на него сквозь такое вещество, будет выглядеть нечетким или искаженным Рис. 7.17. Эффект преломления Именно с преломлением луча связан тот факт, что торчащая из воды палка кажется согнутой Преломление рассчитывается по закону Снеллиуса, кото- рый утверждает, что падающий и преломленный лучи лежат в одной плос- кости, а углы падения и преломления связаны соотношением И1 sine = r]2sine'
174 Гпава 7 где щ и П2 — показатели преломления двух сред, 6 — угол падения, а О' — угол преломления (рис 7 18) Обратите внимание, что ни одно вещество не пропускает весь падающий свет — часть его всегда отражается Нормаль к Среда 1 Граница Среда 2 Рис. 7.18. Геометрия преломления Как видите, учитывать эффекты преломления (даже направленного) доста- точно сложно Но если эту сложность преодолеть, то затраченное время с лихвой окупается чувством удовлетворения от полученных результатов В простейшем случае прозрачные поверхности строятся отдельно от всех остальных, и в буфер записывается линейная комбинация двух ближайших поверхностей При этом результирующая интенсивность рассчитывается по формуле l = th +(1-t)l2 0<t<1 где Ii — видимая поверхность, Ь — поверхность, расположенная непосред- ственно за ней, t — коэффициент прозрачности Ii Для этой книги я написал специальное приложение Lighting, в котором со- брал практически все возможные эффекты для создания реалистичной кар- тинки (рис 7.19). И хотя использовал при этом только один объект и не- сколько источников света, оно получилось в достаточной степени сложным Рис. 7.19. Блок диалога для изменения параметров изображаемых объектов
Построение реалистических изображений 775 Причем эта сложность стала добавляться по мере того, как мне захотелось продемонстрировать эффекты прозрачности и построения теней (о которых речь еще впереди) Объем книги не позволяет подробно остановиться на этом приложении, поэтому я снабдил его большим числом комментариев, чтобы желающие могли сами разобраться, как все это реализовано Здесь же приведу всего несколько фрагментов, демонстрирующих использованный мной принцип, который, я надеюсь, не требует дополнительных пояснений, кроме тех, которые имеются во фрагментах void CLightingView-•sceneDraw() { // Устанавливаем параметры проекции и очищаем буферы // Рисуем объекты сцены sceneRasterize(), // Учитываем эффект преломления sceneDrawRefracted(); // Выводим изображение SwapBuffers(::wglGetCurrentDC()); } void CLightingView:•sceneRasterize() { int i; // Рисуем площадку для отображения теней if(drawSquare){ glCallList(listSquare); } // Рисуем тени, которые рассматриваются ниже // Учитываем эффект преломления if(drawRefraction) for(i =0; i < nLights, i++) if(m_lights[i] m_0n){ glPushMatrix() glRotaref(-m_lights[il m_LightRccacion, 0, 0, // Основную работу "выполняет" специально созданный список изображений glCallList(listsRefraction + i; , glPopMatrix() , } // Рисуем источники света
176 void CLightingView..sceneDrawRefracted() { int i; // Если основной объект, который искажает изображение, //не отображается, то ничего рисовать не нужно if ('drawSphere) return; // Рисуем образ сферы в буфер трафарета glEnable(GL_STENCIL_TEST); glClearStencil(0); glClear(GL_STENCIL_BUFFER_BIT); glStencilFunc(GL_ALWAYS, 0x1, 0x1); glStencilOp(GL_REPLACE, GL_KEEP, GL_REPLACE); // Задаем маски цветов glColorMask(0, 0, 0, 0); // Устанавливаем параметры проекции glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); // Рисуем сферу glCallList(listSphere); glDisable(GL_CULL_FACE); glDisable(GL_DEPTH_TEST); // Восстанавливаем маски цветов glColorMask(1, 1, 1, 1) ; // Устанавливаем параметры материала, // которые будут использоваться при рисовании образа glStencilFunc(GL_NOTEQUAL, 0x0, Oxffffffff); glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // Рисуем площадку if(drawSquare) glCallList(listSquare); // Рисуем тени // Готовим изображения с учетом эффекта преломления if(drawRefraction) for(i =0; 1 < nLights; i++) if (m_lights[i] .m_0n){ glPushMatrix(); glRotatef(-m_lights[i].m_LightRotation, 0, ( glCallList(listsRefraction + 1); glPopMatrix();
Построение реалистических изображений 177 // Рисуем тени с учетом преломления glMatrixMode(GL_PROJECTION), glLoadldentity(); sceneProject(), glCallList(listSpheredisk), // Блокируем проведение теста трафарета glDisable (GL_STENCIL__TEST) ; } Теперь посмотрим, что получилось (рис 7 20) Рис. 7. 20. Учет эффекта прозрачности материала Тени Все согласятся, что изображение с тенями выглядит гораздо реалистичнее Если положения наблюдателя и источника света совпадают, то теней не видно, но они появляются при перемещении наблюдателя в любую другую точку. Наблюдения и жизненный опыт подсказывают, что тень состоит из двух частей’ полутени и полной тени Полная тень — это центральная тем- ная резко очерченная часть, а полутень — окружающая ее более светлая часть Распределенные источники света создают как тень, так и полутень. В полной тени свет вообще отсутствует, а полутень освещается частью распре- деленного источника. Сложность вычислений зависит от положения источ- ника Проще всего, когда источник находится в бесконечности, а тени оп-
178 Глава 7 ределяются с помощью ортогонального проецирования Сложнее если ис- точник расположен на конечном расстоянии, но вне поля зрения, — здесь необходима перспективная проекция И самый трудный случай — когда источник находится в поле зрения, ведь при этом необходимо делить про- странство на секторы и искать тени отдельно для каждого из них Все при- веденные рассуждения легко распространить на несколько источников света Очевидно, что при этом возрастает только сложность учета всех фак- торов Существует несколько способов построения теней Я в своем приложении создал специальный класс Unitdisk, который отвечает за отображение тени В свете вышесказанного, я не привожу здесь фрагментов, т к для поясне- ния принципа работы потребуется слишком много места, а рекомендую внимательно просмотреть комментарии, имеющиеся в приложении на дискете. Здесь же ограничусь только результатом работы приложения (рис 7.21) Рис. 7.21. Тени от различных источников света Все, что мы рассматривали до сих пор, было связано с "гладкими", однород- ными поверхностями, которые отличаются от тех, которые мы видим на са- мом деле Поэтому нашим следующим шагом будет рассмотрение наложе- ния на поверхность текстуры, что позволяет придавать изображению очень привлекательный вид
179 Глава 8 Текстура В компьютерной графике текстурой называется детализация структуры по- верхности Текстура — это одномерное1 или двумерное изображение, кото- рое имеет множество ассоциированных с ним параметров, определяющих, каким образом производится наложение изображения на поверхность Обычно рассматриваются два вида детализации Первый состоит в том, что- бы на гладкую поверхность нанести заранее заданный узор — так называе- мая детализация цветом После этого поверхность все равно остается глад- кой. Второй тип детализации заключается в создании неровностей на поверхности, что реализуется путем внесения возмущений в параметры по- верхности — детализация фактурой Мы рассмотрим только первый способ Что такое текстура, говоря обычным языком? Представьте себе, что вы зака- зали корм для своей любимой кошки И вот вам пришла посылка, глядя на которую, вы сразу понимаете, что в ней (рис 8 1) Таким образом, тексту- ра — это изображение, наложенное на какую-либо поверхность Рис. 8.1. Нанесение рисунка на ящик Совершенно очевидно, что создатели графических библиотек не могли оста- вить в стороне такой способ придания реалистичности создаваемым изо- бражениям и практически во все из них включена возможность нанесения рисунка на поверхность (наложения текстуры) Не стала исключением и 1 Одномерность или двумерность текстуры определяется способом задания матрицы, описы- вающей текстуру Одномерная текстура описывается матрицей размера IxN
180 Гпава 8 библиотека OpenGL Перечислим действия, которые необходимо выпол- нить, чтобы создать привлекательное изображение с помощью текстуры 1 . Прежде всего необходимо получить некоторую заготовку, которую можно будет использовать в качестве текстуры. 2 Затем необходимо определить собственно текстуру — создать ее образ в памяти 3 И наконец задать необходимые параметры как самой текстуры, так и ее взаимодействия с объектом, на который она будет накладываться Рассмотрим все эти действия, обращая внимание как на практические, так и на некоторые вспомогательные аспекты Преобразование растрового изображения в формат OpenGL Форматов графических файлов сейчас достаточно много Естественно, в данной книге привести их все нет ни возможности, ни необходимости По- этому рассмотрим только два из возможных способов получения заготовки для текстуры. OpenGL представляет собой графическую библиотеку, работающую под многими операционными системами, каждая из которых поддерживает свой внутренний формат графических образов. Поэтому разработчики OpenGI создали свой внутренний формат и для того, чтобы воспроизвести какое- либо растровое изображение с помощью OpenGL, необходимо, прежде все- го, осуществить преобразование формата поступающего образа во внутрен- ний формат OpenGL Что же представляет из себя формат OpenGL? Подробно об этом говорилось при рассмотрении команды glDrawImage, поэтому здесь рассмотрим только его графическую иллюстрацию (рис. 8 2.) Выравнивание Рис. 8.2. Формат OpenGL
Текстура 1В1 Как видите, формат очень простой он представляет собой последователь- ность троек, определяющих красный, зеленый и синий компоненты ивеiа Формат Windows DIB очень похож на формат OpenGL Имеется только два отличия компоненты цвета хранятся в другой последовательности (синий зеленый, красный) и выравнивание всегда осуществляется по границе двой- ного слова (рис 8 3) Осталось совсем немного — преобразовать данные образа из одного форма- та в другой Самый простой способ сделать это заключается в использова- нии специально написанной функции AUX_RGBImageRec*auxDIBImageLoad(strFile) которая сделает все за вас и вернет указатель на специальную структур', данных, хранящую образ в формате OpenGL1 Более подробно остановимся на втором способе, при котором все преобра- зования будем выполнять вручную Посмотрите соответствующий фрагмент- кода: void CTextureView- Translate(CDibGL& aDib /*CDIBPal& aPalSrc*1 { int i, j; int nBitsPerPix = aDib.GetBitsPerPixel ; , void* pBits = aDib GetBitsAdaress(); int storagewidth = aDib GetWidthBytes(), // Выделяем место для результирующего образа CGLRGBTRIPLE* pGLRGB = (CGLRGBTRIPLE*Imailoo(m_iWidth*m_iEeignt*sizeof(CGLRGBTRIPLE)), CGLRGBTRIPLE* pDest = pGLRGB, 1 Вам останется только подключить библиотеку GLAUX LIB
182 Гпава 8 switch(nBitsPerPix){ case 24: // Преобразуем формат 24 бита на пиксель break; case 8: // Преобразуем формат 8 битов на пиксель break; case 4. // Преобразуем формат 4 бита на пиксель break; default: // Другие форматы не рассматриваются ASSERT(0); } // Сохраняем указатель на массив образа m_pBits = (BYTE*)pGLRGB; Данные в формате DIB хранятся в массиве специальных структур typedef struct _RGBTRIPLE { BYTE rgbtBlue; BYTE rgbtGreerr, BYTE rgbtRed, } RGBTRIPLE; По аналогии с ним для хранения результирующего образа используется мас- сив структур typedef struct _CGLRGBTRIPLE { BYTE rgbRed, BYTE rgbGreerr, BYTE rgbBlue; } CGLRGBTRIPLE, в котором изменен порядок расположения цветов в соответствии с форма- том OpenGL Самое простое преобразование — для формата 24 бита на пиксель В этом случае необходимо только переписать соответствующие значения цветов и учесть разные типы выравнивания case 24:{ RGBTRIPLE* pSrc = (RGBTRIPLE*) pBics, int widthDiff = storagewidth - m_iWidrh’sineof^RGBTRIPLE);
Текстура 183 for(j = 0, j < m_iHeight, j++){ for(i =0; i < m_iWidth, i++){ // Преобразуем из формата BGR в формат RGB pDest->rgbRed = pSrc->rgbtRed, pDest->rgbGreen = pSrc->rgbtGreen, pDest->rgbBlce = pSrc->rgbtBlue, pDest+-s pSrc++, // Учитываем выравнивание по границе двойного слова pSrc = (RGBTRIPLE*) ' (BYTE*)pSrc + mdthDiff), } 1 break, Для наиболее распространенного 256-цветного формата все несколько сложнее в том плане, что в этом случае, как вы помните, задействуется па- литра, хотя код преобразования практически такой же сазе 8 { BYTE- pSrc = (BYTE*) pBitS, // Необходима таблица цветов RGBQUAD* pClrTab = aDib.GerColorTabAacress() aDio GetColorTable(pClrTab, MAXrALCOLORS), // Дальше аналогично предыдущему int widthDiff = srorageWiath - m_iWidrn; for(j = 0; j < m_iHeight, j++){ for(i = 0, i < m_iWidth; i++>{ // Преобразуем в фоомат RGB, используя значение цвета // в качестве индекса в таблице цветов pDest->rgbRed = pClrTab[*pSrc] rgbRec, pDest->rgbGreen = pClrTab[*pSrc] rgbGreen, pDest->rgbBlce = pClrTab[*pSrc] rgbBlce, pDest-r+, pSrc++, // Учитываем выравнивание по границе двойного слова pSrc += widthDiff, } } break, Подробнее останавливаться на этом не имеет смысла, т к работа с палит- рой рассматривалась при обсуждении цветов
184 Гпава 8 В созданном примере использованы оба подхода для получения образа тек- стуры: при инициализации окна образ загружается из ресурсов и преобразу- ется к формату OpenGL void CTextureView:.OnlnitialUpdate() { COpenGLView::OnlnitialUpdate(); CDibGL aDib; // Загружаем образ из ресурсов BOOL bResult = aDib.Load(AfxGetlnstanceHandle(), IDB_BRICK); if(bResult){ // Преобразуем к формату OpenGL Translate(aDib); } а при изменении образа — загружаем его из файла: void CTextureView::OnOpenBmp() { CDibGL aDib; CString strFile; // Выводим блок диалога для выбора файла CFileDialog dig(TRUE, // Открыть файл NULL, // Расширения по умолчанию нет NULL, // Начального имени файла тоже нет OFN_FILEMUSTEXIST | OFN_HIDEREADONLY, "Image files (*.DIB, *.BMP) I *.DIB;* BMP| All files (*.*) I * *||"), if(dlg.DoModal() == IDOK){ // Получаем имя открытого файла образа strFile = dlg.GetPathName(); // Загружаем образ из файла AUX_RGBImageRec *plmage = auxDIBImageLoad(strFile), // Перерисовываем окно Invalidate(FALSE) ; UpdateWindow();
Текстура 185 Вот, собственно, и все В результате выполненных действий в памяти нахо- дится образ в формате, понятном OpenGL, и мы можем переходить к сле- дующему шагу Создание текстуры в памяти Перед тем как создавать текстуру, необходимо выполнить одно обязательное условие размеры изображения должны быть кратны степени двойки Дру- гими словами, образы размером 32x64, 128x256, 2x2 могут быть использова- ны для создания текстуры, а образ с размером 160x80 — нет Сделано это, скорее всего, для повышения производительности при воспроизведении текстуры Так это или нет, необходимо обеспечить выполнение этого усло- вия Здесь возможны два пути’ разрешить загружать только те изображения, размеры которых удовлетворяют этому условию, или самостоятельно изме- нить размер образа. Очевидно, что первый способ никуда не годится Оста- ется второй, который мы и рассмотрим Прежде всего необходимо найти такие, ближайшие к исходным, размеры, которые удовлетворяют рассматриваемому ограничению Сделать это можно при помощи следующей функции, которую я позаимствовал из исходных текстов хранителей экрана OpenGL BOOL CTextureView TexMapScalePow2() { GLint glMaxTexDim; double xPow2, yPow2; int ixPow2, iyPow2; int xSize2, ySize2; // Получаем от OpenGL максимальный возможный размер текстуры glGetlntegerv(GLJMAX_TEXTURE_SIZE, &glMaxTexDim); //He будем использовать размеры больше 256 пикселей glMaxTexDim = min(256, glMaxTexDim); // Определяем показатели степени двойки по каждому размеру if(m_iWidth <= glMaxTexDim) xPow2 = log((double)m_iWidth) / log(2 0), else xPow2 = log((double)glMaxTexDim) / log (2 0) ; if(m_iHeight <= glMaxTexDim) yPow2 = log((double)m_iHeight) / log(2 0), else yPow2 = log((double)glMaxTexDim) / log(2.0), ixPow2 = (int)xPow2; iyPow2 = (int)yPow2;
186 Гпава 8 // Если произошло усечение, добавляем 1 if(xPow2 ' = (double)ixPow2) ixPow2++, if(yPow2 '= (double)iyPow2) iyPow2++, // Вычисляем новые размеры xSize2 = 1 << izPow2, ySize2 = 1 « iyPow2, // Выделяем память под новый образ BYTE *pData = (BYTE*)malloc(xSize2 * ySize2 * 3 * sizeof(BYTE), if('pData) return FALSE; // Вызываем команду OpenGL для корректного изменения образа if(gluScaleImage(GL_RGB, m_iWidth, m_iHeight, GL_UNSIGNED_BYTE, m_pBits, xSize2, ySize2, GL_UNSIGNED_BYTE, pData) '= 0) return FALSE; // Освобождаем память, занимаемую "старым" образом free(m_pBits), // Переустанавливаем параметры изображения текстуры m_pBits = pData; m_iWidth = xSize2, m_iHeight = ySize2, return TRUE; В приведенном фрагменте ничего сложного нет, и поэтому обратимся к его основной функции1 int gluScalelmage( GLenum format, GLint widthin, GLint heightin, GLenum typein, const void *datain, GLint widthout, GLint heightout, GLenum typeout, void *dataout) Для рассматриваемых целей можно также использовать функцию интерфейса графических устройств StretchDIBits, однако рассматриваемая команда формирует более высококачест- венный образ
Текстура 187 Эта команда масштабирует образ, используя соответствующие режимы хра- нения пикселей Когда образ сжимается, то, чтобы создать результирующий образ, команда использует прямоугольный фильтр, а при растяжении образа осуществляется линейная интерполяция пикселей источника Команда ис- пользует следующие параметры format — задает формат данных пикселя GL_COLOR_INDEX, GLSTENCIL INDEX, GL DEPTH COMPONENT, GL RED, GL GREEN, GL BLUE? GL ALPHA, GL_RGB, GL RGBA, GLLUMINANCE, GL LUMINANCE ALPHA, widthin, heightin — задают, соответственно, ширину и высоту источника мас- штабируемого образа; typein — задает тип данных для datain GLUNSIGNEDBYTE, GLBYTE, GLBITMAP, GLUNSIGNEDSHORT, GL SHORT, GL UNSIGNED INT, GL INT и GL FLOAT; datain — указатель на источник образа; widthout, heightout — задают, соответственно, ширину и высоту приемника масштабируемого образа; typeout — задает тип данных для dataout GL UNSIGNED BYTE, GL BYTE, GL BITMAP, GL_UNSIGNED_SHORT, GL SHORT, GL UNSIGNED INT, GL INT и GL FLOAT; dataout — указатель на приемник образа, Команда возвращает либо нуль, либо код ошибки После того как образ подготовлен, можно создавать текстуру в памяти Для этого в OpenGL предусмотрены две команды одна для одномерного и вто- рая для двумерного вариантов образа (обе работают только в режиме RGBA) void glTexlmage1D( GLenum target, GLint level, GLint components, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid* pixels) void glTexlmage2D( GLenum target, GLint level, GLint components, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid* pixels) Рассмотрим параметры команд target — определяет тип создаваемой текстуры и должен быть равен GL_TEXTURE_1D или GKTEXTURE2D, соответственно, level — определяет число уровней детализации текстуры 0 — базовый уро- вень, а к — уменьшенный в к раз образ,
188 Гпава 8 components — задает число цветовых компонентов текстуры и может прини- мать значения 1, 2, 3 или 4 При значении 1 используется только красный компонент, при 2 — красный и альфа, при 3 — красный, зеленый и синий и при 4 — все четыре компонента цвета; w,idth — определяет ширину образа текстуры и должен составлять 2П + 2х border (на бордюр), height — определяет высоту образа и должен составлять 2nl + 2х border (на бордюр) для команды glTexImage2D и всегда равен 1 для команды glTex- ImagelD, border — определяет ширину границы и должен иметь значение 0 или 1 Последние три перечисленные параметра определяют, как образ представля- ется в памяти (рис 8 4) Образ создается в пространстве текстуры с коорди- натной системой (s, t), Рис. 8.4. Образ текстуры в памяти сп=3ит=2 format — определяет формат данных пикселя и можс! принимать одно из следующих значений GL_COLOR_INDEX GL_RED GL_GREEN GLBLUE GL_ALPHA Каждый элемент представляет индекс цвета Это число преобразуется к формату с фиксированной точкой, после чего к нему добавляются значения смещений, определяе- мые параметрами GL INDEX SHIFT и GL_INDEX_OFFSET Получившийся результат преобразуется в компоненты цвета с помощью таблицы GL_PlXEL_MAP_l_TO_c Каждый элемент представляет собой единственный ком- понент— красный, который преобразуется к веществен- ному формату, после чего собирается элемент RGBA, у которого зеленый и синий компоненты равны 0 0, а аль- фа — 1 О Аналогично GL_RED, только для зеленого цвета Красный и синий компоненты равны О О Аналогично GL_RED, только для синего цвета Красный и зеленый компоненты равны О О Аналогично GL_RED, только для компонента альфа Остальным компонентам цвета присваиваются значения О О
Текстура 189 GL.RGB GL_RGBA GLLUMINANCE GL_LUMINANCE_ALPHA Каждый элемент представляет собой три компонента — красный, зеленый и синий, который преобразуется к ве- щественному формату, после чего собирается RGBA эле- мент, у которого компонент альфа равен 1 О Аналогично GL_RGB, только для всех четырех компонен- тов цвета Каждый элемент представляет собой единственный ком- понент — яркость, который преобразуется к вещественно- му формату, после чего собирается элемент RGBA, у ко- торого компонент равен альфа 1 О Аналогично GL_LUMINANCE, только явно задаются все четыре компонента цвета Каждый элемент (для всех форматов, кроме GL_COLOR_INDEX) умножа- ется на знаковый множитель GL_c_SCALE, добавляется к знаковому сме- щению GL_c_BIAS и приводится к диапазону [0,1]; type — определяет тип данных пикселя и может принимать одно из следую- щих значений- GL_UNSIGNED_BYTE, GL_BYTE, GL_UNSIGNED_INT, GLJNT, GL_UNSIGNED_SHORT, GL_SHORT, GL_BITMAP и GL_FLOAT; pixels — определяет указатель на образ данных в памяти. Каждый байт дан- ных рассматривается как восемь одноразрядных элементов, порядок распо- ложения которых задается аргументом GL_UNPACK_LSB_FIRST команды glPixelStore Как видите, параметров достаточно много, но все они (возможно, кроме первого) важны Рассмотрим более подробно те из них, которые могут вы- звать вопросы Прежде всего это параметр, определяющий уровень детали- зации — level При создании текстуры можно определить несколько образов с различным разрешением. О чем идет речь? Если текстура имеет размер 2nx2m, то можно определить max{n, m} + 1 уменьшенных массивов Первый имеет размер 2nx2m, второй — 2n-1x2m’1, и т д , пока последний не будет иметь размер 1x1. Команды glTexImage*D предоставляют возможность опре- делить р = max{n, m} таких массивов, в каждом из которых хранится уменьшенный образ исходного изображения Наличие таких массивов по- зволит OpenGL использовать меньший образ для меньшего объекта, а боль- ший для большего Другими словами, чем меньше объект, тем меньше его деталей удается рассмотреть (рис 8 5) Уровень 0 Уровень 1 Уровень 2 Рис. 8.5. Несколько уровней детализации
190 Гпава 8 Остальные параметры уже вам знакомы и нуждаются в дальнейших поясне- ниях. Для того чтобы можно было работать с текстурой, необходимо разре- шить соответствующий режим — выполнить команду glEnable с аргументами GL_TEXTURE_1D или GLTEXTURE2D соответственно Вызывать команду можно всякий раз, когда необходимо сменить образ текстуры void CTextureView:-OnlnitialUpdate() { COpenGLView -OnlnitialUpdate(), BOOL bResult = aDib Load (AfxGetlnstanceHandle, ICB_3RICK) , if(bResult){ // Задаем режим хранения пикселей glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // Создаем основной уровень текстуры glTex!mage2D(GL_TEXTURE_2D, 0, 3, m_iWidth, m_iHeight, О, GL_RGB, GL_UNSIGNED_BYTE, m_pBits); // Устанавливаем другие параметры, связанные с текстурой Changesettings() ; // Разрешаем наложение текстуры glEnable(GL_TEXTURE_2D); gluQuadricTexture(m_quadObj, GL_TRUE), } Кроме рассмотренных, в OpenGL имеются еще две команды, в некоторых случаях с меньшими затратами позволяющие решить ту же задачу int gluBuild2DMipmaps( GLenum target, GLint components, GLint width, GLint height, GLenum format, GLenum type, const void *data) int gluBuild1DMipmaps( и GLenum target, GLint components, GLint width, GLenum format, GLenum type, const void ★data) Команды получают входной образ (data) и автоматически формируют образы всех уровней детализации (используя gluScalelmage) Чтобы загрузить каждый из этих образов, вызывается соответствующая команда glTexlmage[ 12]D Если ширина (или высота) входного образа не является степенью 2, то перед фор- мированием образ масштабируется до ближайших степеней 2 При успешном завершении команда возвращает 0 и код ошибки в противном случае Пара- метры команд такие же, как и у команды glTex!mage[12]D
Текстура 191 Параметры текстуры Один элемент на экране может покрывать несколько элементов массива об- раза, и, чтобы избежать проблем, связанных с лестничным эффектом, необ- ходимо учитывать все затрагивающие этот массив элементы Для этого оп- ределяются четыре точки в массиве образа, которые отображаются в четыре угла элемента на экране Эти точки соединяются, и образуется четырехсто- ронний многоугольник Значения попадающих в него элементов взвешива- ются с учетом доли каждого элемента, содержащейся в многоугольнике, и затем суммируются Для учета этих и других особенностей необходимо на- строить параметры текстуры, что можно сделать с помощью команд void glTexParameter[i f]( GLenum target, GLenum pname, GLenum param) void glTexParameter[i f] v( и GLenum target, GLenum pname, GLenum* params) Эти команды устанавливают параметры текстуры и имеют следующие аргу- менты: target — определяет, с какой текстурой предполагается работать, — одномер- ной или двумерной, и может принимать значение GLTEXTURE1D или GL_TEXTURE_2D; pname определяет символическое имя параметра текстуры GL_TEXTURE_MI N_FI LTER GL_TEXTURE_MAG_FILTER GL_TEXTURE_WRAP_S Определяет функцию уменьшения текстуры, использу- ется, когда площадь пикселя, на который она наклады- вается, больше, чем элемент текстуры Всего опреде- лено шесть таких функций Две из них, определенные константами (GL_NEAREST и GL_LINEAR), используют один или четыре ближайшие элемента текстуры для расчета значения текстуры Остальные четыре — уров- ни детализации (mipmapping) Функция увеличения текстуры используется, когда площадь пикселя, на который она накладывается, меньше или равна элементу текстуры Всего опреде- лены две такие функции, задаваемые константами GL_NEAREST и GL_LINEAR По умолчанию использу- ется GLJJNEAR Устанавливает параметр сворачивания координаты s текстуры в значение GL_CLAMP или GL_REPEAT GL_CLAMP фиксирует координату s в диапазоне [0,1] что полезно для предотвращения искусственного сво- рачивания, когда на объект накладывается один образ GL_REPEAT отбрасывает целую часть координаты s, т к OpenGL использует только значения, меньшие 1, и таким образом создается повторяющийся трафарет Бордюр текстуры доступен только при установке этого параметра в GL_CLAMP По умолчанию он установлен в GL_REPEAT
192 Глава 8 gl_texture_wrap_t Аналогично GL_TEXTURE_WRAP_S, но только для координаты t Для второй версии команды доступно еще одно значение аргумента gl_texture_border_color Устанавливает цвет бордюра Параметр params содержит четыре значения, которые определяют цвет RGBA бордюра Целочисленное значение линейно отображается в диапазон [0,1] Исходно цвет бордюра установлен в (0, 0, 0, 0) param определяет единственное значение для параметра pname, params определяет указатель на массив значений для pname, который задает функцию, используемую при "подгонке" текстуры к пикселю и может при- нимав следующие значения- GL.NEAREST Возвращает значение ближайшего от центра пикселя элемента текстуры GLJJNEAR Возвращает среднеарифметическое значение четырех элементов текстуры, расположенных в центре пикселя Сюда также могут входить эле- менты бордюра текстуры, в зависимости от точ- ности наложения текстуры и значений, установ- ленных для GL_TEXTURE_WRAP_S и/или GL_TEXTURE_WRAP_T G L_N EARESTMI PM AP_N EAREST Выбирает уровень детализации, который наибо- лее точно соответствует размеру пикселя, и ис- пользует критерий GL_NEAREST для формиро- вания значения текстуры G L_Ll N EAR_M I PM AP_N E AR EST Выбирает уровень детализации, который наибо- лее точно соответствует размеру пикселя, и ис- пользует критерий GL_LINEAR для формирова- ния значения текстуры GLNEAREST.MIPMAPJJNEAR Выбирает два уровня детализации, которые наи- более точно соответствуют размеру пикселя, и использует критерий GL_NEAREST для форми- рования каждого значения текстуры Оконча- тельное значение текстуры является средне- арифметическим этих двух значений Это значе- ние аргумента установлено по умолчанию G L_LIN E AR_M I PM AP_LI N EAR Выбирает два уровня детализации, которые наи- более точно соответствуют размеру пикселя, и использует критерий GLJJNEAR для формиро- вания каждого значения текстуры Окончатель- ное значение текстуры является среднеарифме- тическим этих двух значений Линейная фильтрация для четырех ближайших элементов текстуры приме- нима только для двумерного образа Для одномерного — линейная фильтра-
Текстура 193 ция применяется только к двум ближайшим элементам В остальном дейст- вия функций аналогичны для одномерного и двумерного случаев В написанном мной приложении Texture я, как обычно, попытался охватить как можно больше возможностей для модификации различных парамет- ров — в данном случае текстуры Поэтому нет смысла приводить фрагменты для каждого из них При завершении рассмотрения всех параметров я при- веду один общий фрагмент кода, а пока посмотрите на рис 8 6 и 8 7, где наглядно показано влияние некоторых из только что рассмотренных пара- метров Рис 8.6. Наложение текстуры на плоскую поверхность с использованием режима фильтрации GL_NEAREST Рис. 8.7. Наложение текстуры на плоскую поверхность с использованием режима фильтрации GLJJNEAR
194 Глава 8 Даже на черно-белом изображении книги заметно повышение реалистично- сти изображения текстуры Не забывайте только, что за любое улучшение надо платить — в данном случае временем, необходимым на фильтрацию Если в качестве текстуры используется изображение достаточно хорошего качества, то лучше применить режим GL_NEAREST. Нами не проиллюстрированы параметры "сворачивания" текстуры Более уместно это будет сделать после того, как мы более подробно познакомимся с ее координатами. А пока . Взаимодействие текстуры с объектом Помимо определения внутренних параметров текстуры необходимо указать, каким образом она "взаимодействует" с фрагментом, на который накладыва- ется Для этой цели в OpenGL реализованы соответствующие команды void glTexEnv[i f]( void glTexEnv[i f] v( GLenum target, и GLenum target, GLenum pname, GLenum pname, GLtype param) GLtype* params) Эти команды устанавливают параметры конфигурации текстуры и имеют следующие аргументы target — определяет конфигурацию текстуры и должен быть равен GL TEXTURE ENV; pname — определяет символическое имя параметра конфигурации текстуры и может принимать значение GL TEXTURE ENV MODE Для векторного варианта команды допустимо также значение GL TEXTURE ENV COLOR; param — символическая константа, для которой допустимы значения GL_MODULATE, GL DECAL или GL BLEND; params — указатель на массив параметров или на единственную символьную константу, или на цвет RGBA Конфигурация текстуры определяет, как интерпретируются значения тек- стуры при ее наложении на фрагмент Принцип формирования цвета RGBA для каждой из трех функций представлен в таблице’ Число компонентов цвета GL_MODULATE GL.DECAL GL_BLEND 1 Cv = LtCf не определен Cv = (1—Lt)Cf + LtCc Av = Af 2 Cv = LtCf Av = Af Aj не определен Cv = (1—Lt)Cf + LtCc Av — Af Af
Текстура 195 (продолжение) Число компонентов цвета GLJVIODULATE GL_DECAL GLJBLEND 3 cv = ctcf cv = ct не определен AV = A{ AV = A{ 4 Cv = C,C{ Cv = (l-At)C{+ AtCt не определен Av = Af At AV = A{ Здесь приняты следующие обозначения С — тройка RGB, А — значение альфа, L — яркость, индексы f — определяет фрагмент, на который накладывается текстура; t — образ текстуры; с — цвет конфигурации текстуры и v — результирующее значение, которое затем приводит- ся к диапазону [0, 1] По умолчанию для этих команд установлены значения GL.MODULATE для GL_TEXTURE_ENV_MODE и (0, 0, 0, 0) для GL_TEXTURE_ENV_COLOR Как и при рассмотрении параметров текстуры, ограничимся здесь только иллюстрацией действия некоторых из этих параметров (рис 8 8 и 8 9) Рис. 8.8. Наложение текстуры на плоскую поверхность с использованием режима конфигурации GL DECAL На черно-белом рисунке трудно увидеть различия, но все же заметно, что при включенном режиме GLMODULATE вместо белой полосы на флаге мы получили несколько более темную (на самом деле — с желтоватым от- тенком). В режиме GL_DECAL текстура как бы покрывает объект, не при- нимая во внимание его цвет, — в результате получаем тот же цвет наложен- ной текстуры, что и у исходной В режиме GLMODULATE значения цве- тов в текстуре модифицируются в зависимости от цвета объекта, на который
196 Гпава 8 она накладывается И если вы используете этот режим при темных цветах текстуры, то в результате получите еще более темное изображение Необхо- димо учитывать это обстоятельство, чтобы не "перетемнить" картинку Тре- тий режим GL_BLEND не представляет особых трудностей и без проблем вы можете освоить его самостоятельно, опираясь на уже полученные знания Рис. 8.9. Наложение текстуры на плоскую поверхность с использованием режима конфигурации GL_MODULATE Координаты текстуры И все же главным при нанесении рисунка на поверхность является отобра- жение, и, следовательно, преобразование систем координат Но прежде чем преобразовывать, необходимо приписать каждой определяемой вершине со- ответствующие точки в координатах текстуры В OpenGL это можно сделать несколькими способами Один из них заключается в явном указании коор- динат текстуры для каждой вершины при создании объекта и реализуется при помощи команд void glTexCoord[1 2 3 4][s i f d](type coord) void glTexCoord[1 2 3 4][s i f d] v(type* coord) Эти команды устанавливают текущие координаты текстуры, являющиеся частью данных, ассоциированных с вершинами многоугольника glTexCo- ordl* устанавливает текущие координаты в значение (s, 0, 0, 1), glTexCo- ord2* — в (s, t, 0,1), glTexCoord3* — в (s. t. r. 1) и glTexCoord4* устанавливает все четыре координаты Координаты задаются в явном виде (первая версия команды), либо доступны через указатель (вторая версия) Изменять значения текущих координат можно в любой момент времени
Текстура 197 void CTextureView BuildPlate(GLdouble size)( // Создаем список изображений площадки glNewList(plate, GL_COMPILE) , glBegin(GL_QUADS) , glTexCoord2d(0.Of, O.Of), glVertex3f(-1 Of, -l.Of, 0.0); glTexCoord2d(size, O.Of), glVertex3f( l.Of, -l.Of, 0 0), glTexCoord2d(size, size); glVertex3f( l.Of, l.Of, 0.0); glTexCoord2d(0.Of, size); glVertex3f(-1.Of, l.Of, 0.0), glEnd(); glEndList(); } Единственный вопрос, который возникает после изучения этого фрагмента, звучит так "А почему координаты текстуры и вершины сопоставлены имен- но так, а не каким-либо другим образом?" Ответ на этот вопрос для случая простой площадки дает рис 8 10, на котором показано, как следует соотно- сить координаты текстуры и вершин Рис. 8.10. Сопоставление координат текстуры и вершин Если же их переставить местами, например, так glTexCoord2d(size, 0 Of); glVertex3f(-l.Of, -1 Of, 0.0); glTexCoord2d(0 Of, 0 Of); glVertex3f( l.Of, -1 Of, 0.0); glTexCoord2d(0.Of, size);
198 Глава 8 glVertex3f( 1 Of, l.Of, О О), glTexCoord2d(size, size), glVertex3f(-l Of, l.Of, 0.0), то получим перевернутое изображение (рис 8 11) Все несколько усложняется, если использовать связанные многоугольники void CTextureView:BuildCube(GLdouble size) { glNewList(cube, GL_COMPILE); glBegin(GL_QUAD_STRIP), glTexCoord2d(0.Of, size); glVertex3f(-1 Of, 1 Of, 1 Of);// 1 glTexCoord2d(0.Of, O.Of); glVertex3f(-1.Of, -l.Of, l.Of);// 2 glTexCoord2d(size, size); glVertex3f( 1 Of, l.Of, l.Of);// 3 glTexCoord2d(size, 0 Of); glVertex3f ( l.Of, -l.Of, l.Of),// 4 glTexCoord2d(0.Of, size); glVertex3f( l.Of, 1 Of, -1 Of);// 5 glTexCoord2d(0 Of, 0 Of), glVertex3f ( l.Of, -l.Of, -l.Of),// 6 glTexCoord2d(size, size); glVertex3f (-1 Of, l.Of, -1 Of),// 7 glTexCoord2d(size, 0 Of); glVertex3f (-1 Of, -l.Of, -1 Of);// 8 glTexCoord2d(0.Of, size); glVertex3f(-1 Of, 1 Of, 1 Of),// 9 glTexCoord2d(0 Of, 0 Of), glVertex3f(-1 Of, -l.Of, 1 Of);// 10 glEnd(); glEndList (); } Но и в этом случае нужно только быть аккуратным и не запутаться в поряд- ке обхода вершин, что мне на первых порах знакомства с текстурами не всегда удавалось
Текстура 199 Оба рассмотренных случая демонстрируют наложение "прямоугольной" тек- стуры на "прямоугольный" же объект А что делать, если объект, например, треугольный? Просто учесть это void CTextureView .BuildPyramid (GLdounle size) { glNewList(pyramid, GL_COMPILE); glBegin(GL_POLYGON); // Лицевая грань glTexCoord2d(size, 0.0); glVertex3d(1.Of, -1 Of, 1 Of), // Берем координату "половины" текстуры glTexCoord2d(size/2.0, size); glVertex3d (0.0, 1 Of, 0 0). glTexCoord2d(0.0, 0.0); glVertex3d(-1 Of, -I Of, 2 of) glEnd(); glBegin(GL_POLYGON); // Обратная грань glTexCoord2d(size, 0.0); glVertex3d(-1.Of, -1 Of, -1 uf) glTexCoord2d(size/2.0, size); glVertex3d(0.0, l.Of, 0.0), glTexCoord2d(0.0, 0 0); glVertex3d(1.Of, -1 Of, -1 Of:. glEnd(); glBegin(GL_POLYGON); // Левая грань glTexCoord2d(size, 0 0), glVertex3d(-1.Of, -1 Of, 1 Of:, glTexCoord2d(size/2 0, size), glVertex3d(0 0, 1 Of, 0 0) glTexCoord2d(0 0, 0 0); glVertex3d(-1.Of, -1 Of, Of) glEnd(); glBegin(GL_POLYGON); // Правая грань glTexCoord2d(size, 0.0); glVertex3d(I.Of, -1 Of, -1 Of' glTexCoord2d(size/2.0, size); glVertex3d(0.0, 1 Of, 0 0), glTexCoord2d(0.0, 0.0); glVertex3d(I.Of, -1 Of, I Of), glEnd(); glEndList(); Даже приведенные простые примеры показывают, что по мере усложнения поверхности, на которую нужно наложить текстуру, количество факторов, подлежащих учету, все больше возрастает Представляете, сколько придется потрудиться, чтобы наложить текстуру, например, на сферу К счастью, де- лать это совсем не обязательно Достаточно воспользоваться теми возмож- ностями, которые заложены в OpenGL. В данном случае речь идет о биб- лиотеке GLU32 LIB, в которой реализована мощная поддержка построения кривых и поверхностей, дисков, цилиндров и сфер, а также многих других "гладких" объектов, задаваемых по вашему желанию Посмотрите, как легко обеспечивается наложение текстуры, например, на сферу void CTextureView OnlnitialUpdate() { // Создаем специальный "квадратичный" объект m_quadObj = gluNewQuadric () ,-
200 Глава 8 // Разрешаем наложение текстуры на него gluQuadricTexture(m_quadObj, GL_TRUE), // Задаем параметры различных "квадратичных" объектов m_radiusl =10; m_radius2 =05, m_anglel = 90, m_angle2 = 180, m_slices = 26.0; m_stacks = 10.0; m_height = 12.0; // Вызываем функцию построения списков изображений //и для сферы тоже BuildList(l.Of) ; m_Object = sphere; ) void CTextureView:BuiIdSphere(GLdouble size) { // Строим список изображений для сферы glNewList(sphere, GL_COMPILE); gluSphere(m_quadObj, // Указатель на объект GLUquadricObj m_radiusl, // Радиус сферы m_slices, // Число разбиений вокруг оси z m_stacks), // Число разбиений вдоль оси в glEndList(); } void CTextureView::DrawScene() { // Рисуем объект, например, сферу ::glCallList(m_Object); // Корректно завершаем рисование А теперь посмотрите, что получилось (рис 8 12) Теперь пришло время вернуться немного назад к команде glTexParameter* и посмотреть, каким образом можно повторить изображение текстуры на по- верхности Ничего сложного здесь также нет Необходимо выполнить всего два условия 1 Разрешить режим повторения текстуры glTexParameteri(GL_TEXTURE_2D, GL__TEXTURE_WRAP_S, GL_REPEAT), glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
Текстура 201 Рис. 8.12. Карта мира, наложенная на сферу 2 Задать координаты текстуры > 1 0, например, 3 0 (рис 8 13), — получи- лись неплохие кирпичные стенки Рис. 8.13. Задав координаты текстуры > 1 0 и разрешив режим повторения, получили кубик из красного кирпича Мы рассмотрели один из способов сопоставления координат текстуры с ко- ординатами вершин объекта Второй способ заключается в использовании некоторой функции, которая в текущий момент времени рассчитывает ко- ординаты текстуры для каждой вершины
202 Глава 8 void glTexGen[i f d]( GLenum coord, и GLenum pname, GLtype param) void glTexGen[i f d] v( GLenum coord, GLenum pname, const GLtype* params) Эти команды устанавливают функцию, которая используется для формиро- вания координат текстуры Параметр coord определяет координату текстуры, к которой будет приме- няться функция, и может принимать одно из значений GL_S, GL_T, GL_R или GL_Q Сама функция, формирующая координаты текстуры, задается параметром pname и может принимать следующие значения GL_TEXTURE_GEN_MODE, GL OBJECT PLANE и GL EYE PLANE (два последних параметра только для второго варианта команды) Параметр param определяет единственное значение формируемой текстуры и может принимать одно из следующих значений GLOBJECTLINEAR, GL_EYE_LINEAR или GL SPHERE МАР, а параметр params — указатель на массив параметров формируемой текстуры Если pname имеет значение GLTEXTUREGENMODE, то массив содержит единственное значение GL_OBJECT LINEAR, GL EYE LINEAR или GL SPHERE MAP. В про- тивном случае в массиве находятся коэффициенты функции, определяемой параметром pname Если текстура формируется функцией GLOBJECTLINEAR, то соответст- вующая координата (s, t, 1 или q) рассчитывается по формуле g = Р1ХО + р2уо + p3zo + p4wo где pi, . , р4 — четыре значения, находящиеся в params, а хо, ., wo — миро- вые координаты вершины. Если текстура формируется функцией GL_OBJECT_LINEAR, то соответст- вующая координата (s, t, г или q) рассчитывается по формуле g = ргхе + Р2’Уе + P3’Ze + Р4’^е где (рр, р2-, рз>, р4>) = (рь р2, рз, р4) М1, М — матрица видового преобразо- вания; а хе, , we — видовые координаты вершины Если pname установлен в GL SPHERE MAP, a coord— в GL S или GL T, то соответствующие координаты текстуры (s или t) формируются следую- щим образом f = и - 2 n' n'T и где и — вектор от начала видовых координат к вершине, п' — текущая нор- маль после преобразования в видовые координаты, f = (fx, fy, fz)T — резуль- тирующий вектор, исходя из которого определяется соответствующая коор- дината- s = fx / m + 0 5
Текстура______________________________________________________________2Q3 t = fy / m + 0 5 где m = 2 x ^fx2 + fy2 +(fz +1)2 Наиболее впечатляющим действие этой команды выглядит на динамичном изображении, что трудно передать статичным рисунком, поэтому ограни- чусь обещанными фрагментами кода, которые отвечают за возможность исследования различных параметров текстуры, за одним маленьким исклю- чением void CTextureView::ChangeSettings() { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, set hrap_s; glTexParameteri(GL_TEXTURE_2D, GL__TEXTURE_WRAP_T, set wrap_t) . glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, set.minfliter); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, set.magfilter); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, set. envmode, // Формирование координат s текстуры ... if(set.s_gen)( glEnable(GL_TEXTURE_GEN_S); glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, set s_mode), if(set.s_mode == GL_OBJECT_LINEAR) glTexGendv(GL_S, GL_OBJECT_PLANE, set s_coeffs), else if(set s_mode == GL_EYE_LINEAR) glTexGendv(GL_S, GL_EYE_PLANE, set s_coeffs); // Для режима GL_SPHERE_MAP коэффициенты не используются ) else glDisable(GL TEXTURE_GEN_S), // Формирование координат t текстуры if(set.t_gen){ glEnable(GL_TEXTURE_GEN_T); // Аналогично тому, что было для координаты s, только для координаты t ) else glDisable(GL_TEXTURE_GEN_T), // Насколько точно будем учитывать перспективу glHint(GL_PERSPECTIVE_CORRECTION_HINT, set.hint); // Здесь хорошо бы вставить проверку на изменение координат // текстуры, чтобы не каждый раз обновлять списки изображений BuildList(set.texcoord);
204 Гпава 8 // Перерисовываем окно, чтобы увидеть результат // выполненных установок Invalidate(FALSE), UpdateWindow() } Посмотрев на рис 8 14 можно заметить, что кирпичи расположились не па- раллельно краям площадки в отличие от того, что мы наблюдали на рис 8 7 Если запустить приложение, то можно увидеть, что при вращении площад- ки изображение текстуры как бы отражается от нее, а не двигается Это так называемый эффект зеркального (или хромового) покрытия, которого мы достигли, выполнив команды glEnable(GL_TEXTURE_GEN_S); glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR); glTexGendv(GL_S, GL_EYE_PLANE, set.s_coeffs); с координатами (1, 0, 0, 1) и аналогичные для координаты t текстуры — (0, 1, 0, 1) Рис. 8.14. Попытка передачи эффекта зеркального (хромового) покрытия Все преобразования координат текстуры осуществляются посредством мат- рицы текстуры, которая устанавливается командой glMatrixMode с аргумен- том GL TEXTURE и управляется аналогично матрице видового преобразо- вания На рис 8 15 приведен блок диалога, где представлены те параметры, кото- рые можно исследовать с помощью приложения Texture Как и для параметров текущей матрицы проекций и видового преобразова- ния, для текстуры тоже предусмотрена возможность ее сохранения в специ- альном стеке, чтобы иметь возможность накладывать различные изображе-
Текстура 205 ния на разные объекты Для этого необходимо воспользоваться рассмотрен- ными командами glPushMatrix и glPopMcitrix, когда в качестве текущей установлена матрица текстуры Рис. 8.15. Блок диалога, позволяющий задавать различные параметры текстуры Для тех, кому надоело смотреть на бесконечные кирпичи, проходящие че- рез всю главу, в заключе- ние хочу продемонстриро- вать, что в качестве тек- стуры можно использовать и фотографию симпатич- ной девушки, что всегда благотворно влияет на ка- чество и количество усваи- ваемого материала Texture Be View Options Help И НЕЗ jNUgf
206 Приложение Аргументы команд glEnable и glDisable Параметр Примечание GL_ALPHA_TEST Проводится тестирование по цветовому па- раметру альфа См команду glAlphaFunc GL_AUTO_NORMAL Разрешается аналитический расчет векто- ров нормалей к поверхности GLBLEND Разрешается смешивание поступающих значений RGBA цветов со значениями, на- ходящимися в буфере цветов См команду gIBIendFunc GL_CLIP_PLANE i Разрешается геометрическое отсечение в определенной пользователем плоскости / См команду glClipPlane GL_COLOR_ARRAY_EXT Разрешается использование расширенного массива цветов в командах glArrayElementEXT, glArrayElementArrayEXT или gIDrawArraysEXT GL_COLOR_MATERIAL Для одного или нескольких параметров ма- териала используется значение текущего цвета См команду glColorMaterial GL_CULL_FACE Разрешается отбирать многоугольники, ба- зируясь на их "изгибах" в оконных коорди- натах См команду glCullFace GL_DEPTH_TEST Разрешается выполнение теста на сравне- ние параметров глубины объектов и произ- водится обновление буфера глубины См команды gIDepthFunc и glDepthRange GL_DITHER Разрешается добавление псевдослучайных значений цвета или индекса к цветовым компонентам до того, как они будут записа- ны в буфер цвета
Приложение 1 Аргументы команд glEnable и glDisable 207 (продолжение) Параметр Примечание GL_EDGE_FLAG_ARRAY_EXT Разрешается использование массивов фла- гов углов в командах glArrayElementEXT, glArrayElementArrayEXT или gIDrawArraysEXT GL_FOG В цвет объекта после наложения текстуры добавляется цвет тумана См команду gIFog GL_INDEX_ARRAY_EXT Разрешается использование расширенных массивов индексов цветов в командах glArrayE lementEXT, glArrayE lemen tArrayEXT или gIDrawArraysEXT GL_LIGHTi Включается i-й источник света в общее уравнение оценки освещенности См команды gILightModel и gILight GL-LIGHTING В результирующем цвете вершины исполь- зуются параметры текущей освещенности См команды gIMaterial, gILightModel и gILight GL_LINE_SMOOTH Включается режим корректной "фильт- рации" при рисовании линий (в противном случае рисуется ступенчатая линия) См команду gILineWidth GLJJNE.STIPPLE Разрешается использование текущего тра- фарета штриховки линии См команду gILineStipple GL_LOGIC_OP Разрешается применение текущей логиче- ской операции к поступающим входным данным См команду gILogicOp GL_MAP1_C0L0R_4 Разрешается генерация значений RGBA цве- тов в командах glEvalCoordl, glEvalMeshl и glEvalPointl См команду gIMap 1 GL_MAP1JNDEX Разрешается генерация индексов цветов в командах glEvalCoordl, glEvalMeshl и glEvalPointl См команду gIMap 1 GL_MAP1_N0RMAL Разрешается генерация нормалей в коман- дах glEvalCoordl, glEvalMeshl и glEvalPointl См команду gIMap 1 GL_MAP1_TEXTURE_COORD_1 Разрешается генерация значений s коорди- наты текстуры в командах glEvalCoordl, glEvalMeshl и glEvalPointl См команду gIMapI
208 Приложения (продолжение) Параметр Примечание GL_MAP1_TEXTURE_C00RD_2 Разрешается генерация значений s и t ко- ординат текстуры в командах glEvalCoordl, glEvalMeshl и glEvalPointl См команду gIMap 1 GL_MAP1_TEXTURE_C00RD_3 Разрешается генерация значений s, t и г координат текстуры в командах glEvalCoordl, glEvalMeshl и glEvalPointl См команду gIMap 1 G L_M AP1 _TEXTU R E_COOR D_4 Разрешается генерация значений s, t, г и q координат текстуры в командах glEvalCoordl, glEvalMeshl и glEvalPointl См команду gIMap 1 GL_MAP1_VERTEX_3 Разрешается генерация значений х, у и z координат вершины в командах glEvalCoordl, glEvalMeshl и glEvalPointl См команду gIMap 1 GL_MAP1_VERTEX_4 Разрешается генерация значений х, у, z и w координат вершины в командах glEvalCoordl, glEvalMeshl и glEvalPointl См команду gIMapI GL_MAP2_COLOR_4 Разрешается генерация значений RGBA цве- тов в командах glEvalCoord2, glEvalMesh2 и glEvalPoint2 См команду glMap2 GL_MAP2JNDEX Разрешается генерация индексов цветов в командах glEvalCoord2, glEvalMesh2 и glEvalPoint2 См команду glMap2 GL_MAP2_N0RMAL Разрешается генерация нормалей в коман- дах glEvalCoord2, glEvalMesh2 и glEvalPoint2 См команду glMap2 GL_MAP2_TEXTURE_COORD_1 Разрешается генерация значений s коорди- наты текстуры в командах glEvalCoord2, glEvalMeshl и glEvalPoint2 См команду glMap2 GL_MAP2_TEXTURE_COORD_2 Разрешается генерация значений s и t ко- ординат текстуры в командах glEvalCoord2, glEvalMesh2 и glEvalPoint2 См команду glMap2 GL_MAP2_TEXTURE_COORD_3 Разрешается генерация значений s, t и г координат текстуры в командах glEvalCoord2, glEvalMesh2 и glEvalPoint2 См команду glMap2
Приложение / Аргументы команд glEnable и glDisable 209 (продолжение ) Параметр Примечание GL_MAP2_TEXTURE_COORD_4 Разрешается генерация значений s, t, г и q координат текстуры в командах glEvalCoord2, glEvalMesh2 и glEvalPoint2 См команду glMap2 GL_MAP2_VERTEX_3 Разрешается генерация значений х, у и z координат вершины в командах glEvalCoordl, glEvalMeshl и glEvalPointl См команду gIMap 1 GL_MAP2_VERTEX_4 Разрешается генерация значений х, у, z и w координат вершины в командах glEvalCoordl, glEvalMeshl и glEvalPointl См команду gIMap 1 GL_NORMAL_ARRAY_EXT Разрешается использование расширен- ных массивов нормалей в командах glArrayElementEXT, glArrayElementArrayEXT или gIDrawArraysEXT GL_NORMALIZE Разрешается нормализация векторов нор- малей, полученных при помощи команды gINormal GL_POINT_SMOOTH Включается режим корректной "фильт- рации" при рисовании точек См команду gIPointSize GL_POLYGON_SMOOTH Включается режим корректной "фильт- рации" при рисовании многоугольников См команду gIPolygonMode GL_POLYGON_STIPPLE Разрешается использование текущего шаб- лона штриховки при рисовании многоуголь- ников См команду gIPolygonStipple GL_SCISSOR_TEST Разрешается отсечение тех фрагментов объекта, которые находятся вне прямо- угольника "вырезки" См команду gIScissor GL_STENCIL TEST Разрешается выполнение теста трафарета и производится обновление соответствующе- го буфера См команды glStencilFunc и glStencilOp GL_TEXTURE_COORD_ARRAY_EXT Разрешается использование расширенных массивов координат текстуры в командах glArrayElementEXT, glArrayElementArrayEXT или gIDrawArraysEXT GL_TEXTURE_1 D Разрешается наложение одномерной тек- стуры См команду gITexImagelD
210 Приложения (продолжение) Параметр Примечание GL_TEXTURE_2D Разрешается наложение двумерной тексту- ры См команду glTexlmage2D GL_TEXTURE_GEN_Q Координата q текстуры рассчитывается при помощи функции генерации текстуры gITexGen В противном случае используется текущая координата q GL_TEXTURE_GEN_R Координата г текстуры рассчитывается при помощи функции генерации текстуры gITexGen В противном случае используется текущая координата г GL_TEXTURE_GEN_S Координата s текстуры рассчитывается при помощи функции генерации текстуры gITexGen В противном случае используется текущая координата s GL_TEXTURE_GEN_T Координата t текстуры рассчитывается при помощи функции генерации текстуры gITexGen В противном случае используется текущая координата t GL_VERTEX_ARRAY_EXT Разрешается использование расширен- ных массивов вершин в командах glArrayElementEXT, glArrayElementArrayEXT или gIDrawArraysEXT
211 Приложение 2 Получение информации о выбранных параметрах Для того чтобы узнать текущее состояние OpenGL, можно воспользоваться специальными командами void glGetBooleanv( GLenum pname, GLboolean * params) void glGetFloatv( GLenum pname, GLboolean * params) void glGetDoublev( GLenum pname, GLboolean *params) void glGetlntegerv( GLenum pname, GLboolean * params) pname — задает параметр, значение которого требуется получить Доступны следующие символические константы: GL_ACCUM_ALPHA_BITS Число битовых плоскостей альфа в буфере аккумулятора GL_ACCUM_BLUE_BITS Число битовых плоскостей синего в буфере аккумулятора GL_ACCUM_CLEAR_VALUE Значения красного, зеленого, синего и аль- фа, используемые для очистки буфера акку- мулятора Если требуется целое значение, то оно линейно отображается из внутреннего представления GL_ACCUM_GREEN_BITS Число битовых плоскостей зеленого в буфе- ре аккумулятора GL_ACCUM_RED_BITS Число битовых плоскостей красного в буфе- ре аккумулятора GL_ALPHA_BIAS Коэффициент смещения альфа, используе- мый при перемещении пикселей GL_ALPHA_BITS Число битовых плоскостей альфа в каждом буфере цвета GL_ALPHA_SCALE Масштабный множитель альфа, используе- мый при перемещении пикселей
212 Приложения gl_alpha_test gl_alpha_test_func GLALPHA-TEST.REF gl_attrib_stack_depth GL_AUTO_NORMAL GL_AUX_BUFFERS GLBLEND GL_BLEND_DST GL_BLEND_SRC GL_BLUE_BIAS GL-BLUE-BITS GL_BLUE_SCALE GL_CLIP_PLANEi GL_COLOR_CLEAR_VALUE GL_COLOR_MATERIAL GL_COLOR_MATERIAL_FACE GL_COLOR_MATERIAL_PARAMETER GL_COLOR_WRITEMASK GL_CULL_FACE Булевское значение, показывающее, разре- шен или заблокирован альфа-тест Символическое имя функции альфа-теста Значение ссылки для альфа-теста Глубина стека атрибутов Если стек пустой — возвращается нуль Булевское значение, показывающее, форми- руются ли автоматически нормали к поверх- ности при вычислении двумерного отобра- жения Число вспомогательных буферов цвета Булевское значение, показывающее, разре- шено ли смешение цветов Символическая константа, идентифицирую- щая функцию смешивания приемника Символическая константа, идентифицирую- щая функцию смешивания источника Коэффициент смещения синего, используе- мый при перемещении пикселей Число битовых плоскостей синего в каждом буфере цвета Масштабный множитель синего, используе- мый при перемещении пикселей Булевское значение, показывающее, разре- шена ли определенная плоскость отсечения Значения красного, зеленого, синего и альфа, используемые при очистке буферов цвета Булевское значение, показывающее, один или больше параметров материала отслежи- вается текущим цветом Символическая константа, показывающая, какие материалы имеют параметр, который отслеживается текущим цветом Символическая константа, показывающая, какие параметры материала отслеживаются текущим цветом Четыре булевских значения, показывающих разрешенные для записи буферы цвета красный, зеленый, синий и альфа Булевское значение, показывающее, разре- шена ли "отбраковка" многоугольника
Приложение 2 Получение информации о выбранных параметрах 213 GL_CULL_FACE_MODE GL_CURRENT_COLOR GL_CURRENTJNDEX GL_CURRENT_NORMAL GL_CURRENT_RASTER_COLOR GL_CURRENT_RASTER_DISTANCE GL_CURRENT_RASTER_INDEX GL_CURRENT_RASTER_POSITION GL_CURRENT_RASTER_TEXTURE_ COORDS GL_CURRENT_RASTER_POSITION_ VALID GL_CURRENT_TEXTURE_COORDS GL_DEPTH_BIAS GL_DEPTH_BITS GL_DEPTH_CLEAR_VALUE GL_DEPTH_FUNC GL_DEPTH_RANGE GL_DEPTH_SCALE GL_DEPTH_TEST GL_DEPTH_WRITEMASK GL.DITHER Символическая константа, показывающая, какие поверхности многоугольника "отбрако- вываются" Значения красного, зеленого, синего и аль- фа для текущего цвета Индекс текущего цвета Значения х, у и z текущей нормали Значения красного, зеленого, синего и аль- фа текущей позиции растра Расстояние от наблюдателя до текущей по- зиции растра Индекс цвета текущей позиции растра Компоненты х, у, z и w текущей позиции рас- тра Значения х, у и z даются в оконных ко- ординатах, а значение w — в координатах отсечения Значения s, t, г и q текущих растровых коор- динат текстуры Булевское значение, показывающее, разре- шена (действительна, достоверна) ли теку- щая позиция растра Значения s, t, г и q текущих координат тек- стуры Коэффициент смещения глубины при опера- циях перемещения пикселя Число битовых плоскостей в буфере глубины Значение, которое используется для очистки буфера глубины Символическая константа, показывающая функцию сравнения глубин Ближний и дальний пределы отображения для буфера глубины Масштабный коэффициент глубины, исполь- зуемый при операциях перемещения пиксе- лей Булевское значение, показывающее, разре- шена ли проверка глубины фрагментов Булевское значение, показывающее, разре- шена ли запись в буфер глубины Булевское значение, показывающее, разре- шено ли "наложение" цветов и индексов фрагментов
214 Приложения gl_doublebuffer gl.draw.buffer GL.EDGE.FLAG GL.FOG GL.FOG.COLOR GL.FOG.DENSITY GL.FOG.END GLFOG-HINT GL.FOG.INDEX GL.FOG.MODE GL.FOG.START GL.FRONT.FACE GL.GREEN.BIAS GL.GREEN _BITS GL.GREEN .SCALE GLJNDEX.BITS GLJNDEX.CLEAR.VALUE GLJNDEX.MODE GLJNDEX.OFFSET GL.INDEX.SHIFT Булевское значение, показывающее, под- держивается ли режим двойной буферизации Символическая константа, показывающая, в какие буферы будет осуществляться рисо- вание Булевское значение, показывающее текущее значение флага грани Булевское значение, показывающее, разре- шена ли работа с "туманом" Компоненты красного, зеленого, синего и альфа цвета "тумана" Значение плотности "тумана" Фактор конца линейного уравнения "тумана" Символическая константа, показывающая режим оттенка "тумана" Индекс цвета "тумана" Символическая константа, показывающая выбранное уравнение "тумана" Фактор начала линейного уравнения "тумана" Символическая константа, показывающая, по часовой стрелке или против многоугольник обходится как лицевая грань Коэффициент смещения зеленого, исполь- зуемый при перемещении пикселей Число битовых плоскостей зеленого в каж- дом буфере цвета Масштабный множитель зеленого, исполь- зуемый при перемещении пикселей Число битовых плоскостей в каждом индекс- ном буфере цвета Индекс цвета, используемый при очистке индексных буферов цвета Булевское значение, показывающее, нахо- дится ли OpenGL в индексном режиме (TRUE) или в режиме RGBA (FALSE) Смещение индекса, добавляемое к индексам цвета и трафарета во время перемещения пикселей Величина, на которую сдвигаются индексы цвета и трафарета во время перемещения пикселей
Приложение 2 Получение информации о выбранных параметрах 215 GLJNDEX.WRITEMASK Маска, показывающая, в какие битовые плоскости каждого индексного буфера цвета можно записывать GLJJGHTi Булевское значение, показывающее, разре- шен ли определенный источник света GLLIGHTING Булевское значение, показывающее, разре- шена ли работа с освещением GL_LIGHT_MODEL_AMBIENT Компоненты красного, зеленого, синего и альфа интенсивности рассеянного света элемента сцены GL_LIGHT_MODEL_LOCAL_VIEWER Булевское значение, показывающее, рассчи- тывается ли зеркальное отражение при про- смотре локальной сцены gl_light_model_two_side Булевское значение, показывающее, ис- пользуются ли раздельные материалы для расчета освещенности лицевой и обратной граней GL_LINE_SMOOTH Булевское значение, показывающее, разре- шено ли устранение ступенчатости линий GLJJNE_SMOOTH_HINT Символическая константа, показывающая режим устранения ступенчатости линии gl_une_stipple Булевское значение, показывающее, разре- шена ли штриховка линий GL_LINE_STIPPLE_PATTERN GL_LINE_STIPPLE_REPEAT GL_LINE_WIDTH 16-разрядный шаблон для штриховки линий Фактор повтора штриховки линии Ширина линии, определенная командой gILineWidht gl_line_width_granularity Диапазон поддерживаемых значений шири- ны линий в режиме устранения ступенчато- сти G L_LI N E_WI DTH_R ANG E Наименьшее и наибольшее значения под- держиваемой ширины линии с устранением ступенчатости GL_LIST_BASE Базовое смещение, добавляемое ко всем именам в списке массивов представленных командой glCallLists GLJJSTJNDEX Имя списка изображений, создаваемого (конструируемого) в текущий момент GLJJSTMODE Символическая константа, показывающая режим конструирования списка изображе- ний, создаваемого в текущий момент GLJ.OGICOP Булевское значение, показывающее, слива- ются (объединяются) ли индексы в буфере кадра с использованием логических операций
216 Приложения GLLOGIC-OP-MODE Символическая константа, показывающая выбранный режим логических операций GL_MATRIX_MODE Символическая константа, показывающая, какой стек матриц является текущим для всех операций над матрицами GL_MAX_ATTRIB_STACK_DEPTH Максимальная поддерживаемая глубина сте- ка атрибутов GL_MAX_CLIP_PLANES Максимальное число плоскостей отсечения, определенных в приложении GL_MAX_EVAL_ORDER Максимальный поддерживаемый порядок вычислений при одномерных и двумерных вычислениях GL_MAX_LIGHTS GL_MAX_LI ST_N ESTI NG Максимальное число источников света Максимальная глубина рекурсии, допустимая во время обхода (прохождения) списка изо- бражений GL_MAX_MODELVIEW_STACK_ DEPTH Максимальная поддерживаемая глубина сте- ка матриц видового преобразования GL_MAX_NAME_STACK_DEPTH Максимальная поддерживаемая глубина сте- ка выбора имени GL_MAX_PIXEL_MAP_TABLE Максимальный поддерживаемый размер по- исковой таблицы gIPixelMap GL_MAX_PROJECTION_STACK_ DEPTH Максимальная поддерживаемая глубина сте- ка матриц проекций GL_MAX_TEXTURE_SIZE Максимальные ширина и высота любого об- раза текстуры (без бордюра) GL_MAX_TEXTURE_STACK_DEPTH Максимальная поддерживаемая глубина сте- ка матриц текстуры GL_MAX_VIEWPORT_DIMS Максимальные ширина и высота области вывода GL_MODELVIEW_MATRIX Содержимое матрицы видового преобразо- вания, находящейся наверху соответствую- щего стека GL_MODELVIEW_STACK_DEPTH Число матриц в стеке матриц видового пре- образования GL_NAME_STACK_DEPTH GL.NORMALIZE Число имен в стеке выбора имени Булевское значение, показывающее, мас- штабируются ли автоматически нормали по единице длины после того, как они были трансформированы в видовые координаты GL_PACK_ALIGNMENT Байтовое выравнивание, используемое для записи данных пикселей в память
Приложение 2 Получение информации о выбранных параметрах 217 GL_PACK_LSB_FIRST GL_PACK_ROW_LENGTH Булевское значение, показывающее, как знаковые пиксели записываются в память (пишется ли первым менее значимый разряд беззнакового байта) Длина строки, используемая для записи дан- ных пикселя в память GL_PACK_SKIP_PIXELS GL_P ACK_SKI P.ROWS GL_P ACK_SWAP_BYTES GL_PERSPECTIVE_CORRECTION HINT GL_PIXEL_MAP_A_TO_A_SIZE Число мест пикселей, пропущенных до пер- вого пикселя, записанного в память Число строк пикселей, пропущенных до пер- вого пикселя, записанного в память Булевское значение, показывающее, 2 или 4 байта пикселей индексов и компонентов об- мениваются до записи в память Символическое имя, показывающее режим перспективной коррекции оттенка Размер таблицы альфа-альфа передачи пик- селей GL_PIXEL_MAP_B_TO_B_SIZE GL_PIXEL_MAP_G_TO_G_SIZE GL_PIXEL_MAP_I_TO_A_SIZE GL_PIXEL_MAP_I_TO_B_SIZE Размер таблицы синий-синий передачи пик- селей Размер таблицы зеленый-зеленый передачи пикселей Размер таблицы индекс-альфа передачи пикселей Размер таблицы индекс-синий передачи пик- селей GL_PIXEL_MAP_I_TO_G_SIZE GL_PIXEL_MAP_I_TOJ_SIZE GL_PIXEL_MAP_I_TO_R_SIZE GL_PIXEL_MAP_R_TO_R_SIZE GL_PIXEL_MAP_S_TO_S_SIZE GL_POINT_SIZE G L_PO I NT_SIZE_G R AN U LAR ITY GL_POINT_SIZE_RANGE Размер таблицы индекс-зеленый передачи пикселей Размер таблицы индекс-индекс передачи пикселей Размер таблицы индекс-красный передачи пикселей Размер таблицы красный-красный передачи пикселей Размер таблицы трафарет-трафарет переда- чи пикселей Размер точки, заданный командой gIPointSize Диапазон поддерживаемых размеров точек в режиме устранения ступенчатости Наименьший и наибольший поддерживаемые размеры для точек с устранением ступенча- тости
218 Приложения GL-POINT-SMOOTH gl_point_smooth_hint GL_POLYGON_MODE GL_POLYGON_SMOOTH GL_POLYGON_SMOOTH_HINT GL_POLYGON_STIPPLE GL_PROJECTION_MATRIX GL_PROJECTION_STACK_DEPTH GL_READ_BUFFER GL_RED_BIAS GL_RED_BITS GL_RED_SCALE GL_RENDER_MODE GL_RGBA_MODE GL_SCISSOR_BOX GL_SCISSOR_TEST GL_SHADE_MODEL GL_STENCIL_BITS Булевское значение, показывающее, разре- шено ли устранение ступенчатости для точек Символическая константа, показывающая режим оттенка точки с устранением ступен- чатости Символические константы, показывающие, растеризуются ли лицевая и обратная грани как точки, линии или заполненные много- угольники Булевское значение, показывающее, раз- решено ли устранение ступенчатости много- угольников Символическая константа, показывающая режим оттенка устранения ступенчатости многоугольников Булевское значение, показывающее, разре- шена ли штриховка многоугольников 16 значений матрицы проекций, находящей- ся наверху стека матриц проекций Число матриц в стеке матриц проекций Символическая константа, показывающая, какой буфер цвета выбран для чтения Коэффициент смещения красного, исполь- зуемый во время передачи пикселей Число битовых плоскостей красного в каж- дом буфере цвета Масштабный множитель красного, исполь- зуемый во время передачи пикселей Символическая константа, показывающая, в каком режиме находится OpenGL воспроиз- ведения, выбора или обратной связи Булевское значение, показывающее, нахо- дится ли OpenGL в режиме RGBA (TRUE) или индексном (FALSE) Оконные координаты х и у прямоугольника вырезки Булевское значение, показывающее, разре- шена ли вырезка Символическая константа, показывающая, находится ли построение теней в режиме плоской или однородной тени Число битовых плоскостей в буфере трафа- рета
Приложение 2 Получение информации о выбранных параметрах 219 GL_STENCIL_CLEAR_VALUE Индекс значения, которым заполняются би- товые плоскости трафарета GL_STENCIL_FAIL Символическая константа, показывающая, какое действие выполняется, когда не прохо- дит тест трафарета GL_STENCIL_FUNC Символическая константа, показывающая, какая функция используется для сравнения ссылочного значения со значением в буфере трафарета GL_STENCIL_PASS_DEPTH_FAIL Символическая константа, показывающая, какое действие выполняется, когда проходит тест трафарета, но не проходит тест глубины GL_STENCIL_PASS_DEPTH_PASS Символическая константа, показывающая, какое действие выполняется, когда проходят тесты трафарета и глубины GL_STENCIL_REF Значение ссылки, которое сравнивается с содержимым буфера трафарета GL_STENCIL_TEST Булевское значение, показывающее, разре- шено ли тестирование трафарета GL_STENCIL_VALUE_MASK Маска, которая используется в качестве мас- ки и ссылочного значения трафарета и зна- чения буфера трафарета до их сравнения GL_STENCIL_WRITEMASK Маска, которая контролирует запись битовых плоскостей трафарета GL_STEREO Булевское значение, показывающее, под- держиваются ли буферы стерео (левый и правый) GL_SUBPIXEL_BITS Приблизительная оценка числа разрядов разрешения фрагмента пикселя, которое используется при геометрии растеризован- ной позиции в оконных координатах GL_TEXTURE_1D Булевское значение, показывающее, разре- шено ли одномерное наложение текстуры GL_TEXTURE_2D Булевское значение, показывающее, разре- шено ли двумерное наложение текстуры GL_TEXTURE_ENV_COLOR Значения красного, зеленого, синего и аль- фа цвета окружения текстуры GL_TEXTURE_ENV_MODE Символическая константа, показывающая, какая функция окружения текстуры выбрана в текущий момент GL_TEXTURE_GEN_S Булевское значение, показывающее, разре- шена ли автоматическая генерация коорди- наты s текстуры
220 Приложения GL_TEXTURE_GEN_T gl_texture_gen_r GL_TEXTURE_GEN_Q GL_TEXTURE_MATRIX GL_TEXTURE_STACK_DEPTH GL.UNPACK-ALIGNMENT GL_UNPACK_LSB_FIRST GL_UNPACK_ROW_LENGTH GL_UNPACK_SKIP_PIXELS GL_UNPACK_SKIP_ROWS GL_UNPACK_SWAP_BYTES GL.VIEWPORT GL_ZOOM_X GLZOOM.Y Булевское значение, показывающее, разре- шена ли автоматическая генерация коорди- наты t текстуры Булевское значение, показывающее, разре- шена ли автоматическая генерация коорди- наты г текстуры Булевское значение, показывающее, разре- шена ли автоматическая генерация коорди- наты q текстуры 16 значений матрицы текстуры, находящейся наверху соответствующего стека Число матриц в стеке матриц текстуры Выравнивание байтов, используемое для чтения данных пикселей из памяти Булевское значение, показывающее наиме- нее значимый бит для каждого беззнакового байта, читаемого из памяти Длина строки, используемая для чтения дан- ных пикселей из памяти Число пикселей, пропускаемых до первого пикселя, читаемого из памяти Число строк пикселей, пропускаемых до пер- вого пикселя, читаемого из памяти Булевское значение, показывающее, 2 или 4 байта пикселей индексов и компонентов, обмениваются до записи в память Оконные координаты х и у области вывода Коэффициент изменения масштаба по оси х пикселя Коэффициент изменения масштаба по оси у пикселя params — возвращаемое значение или значения для заданного параметра Эти четыре команды возвращают значения для простых переменных со- стояния OpenGL Параметр pname — символическая константа, показываю- щая, состояние какой переменной возвращается, a params — указатель на массив, в котором размещаются возвращаемые данные Если params имеет тип, отличный от требуемого значения переменной состояния, то выполня- ется преобразование типов Используя эту команду, можно запросить несколько булевских параметров, это будет значительно проще, чем при использовании команды gllsEnable GLenum gIGetErrorO
Приложение 2 Получение информации о выбранных параметрах 221 Эта команда возвращает значение флага ошибки Каждой детектируемой ошибке присвоен числовой код и символическое имя Когда возникает ошибка, флаг ошибки устанавливается в соответствующее кодовое значение Пока вызывается glError, другая ошибка не записывается После того как получен код ошибки, флаг сбрасывается в GL NO ERROR На текущий момент определены следующие ошибки GL_NO_ERROR GL_INVALID_ENUM GLJNVALIDVALUE GLJNVALID_OPERATION GLSTACKJDVERFLOW GL_STACK_UNDERFLOW GL_OUT_OF_MEMORY void glGetLight[f i]v( GLenum light, GLenum pname, GLtype * params) Ошибка отсутствует Значение этой символической кон- станты гарантированно равно нулю Недопустимое значение, заданное для перечисляемого аргумента Числовой аргумент за границами диапазона Заданная операция недопустима в текущем состоянии Переполнение стека Попытка прочитать значение из пустого стека Недостаточно памяти для выполнения команды Когда установлен этот флаг, результат операции OpenGL не определен light — источник света Число возможных источников света зависит от реа- лизации, но поддерживается не меньше восьми источников Они идентифи- цируются символическими именами GL_LIGHTi, где 0<i < GL_MAX_ LIGHTS; pname — определяет параметры источника света Допустимы следующие символические имена: GL.AMBIENT GL.DIFFUSE GL_SPECULAR GL_POSITION Параметр params возвращает четыре целых или вещественных значения, представляющие интен- сивность фонового источника света Параметр params возвращает четыре целых или вещественных значения, представляющие интен- сивность рассеянного источника света Параметр params возвращает четыре целых или вещественных значения, представляющие интен- сивность отраженного источника света Параметр params возвращает четыре целых или вещественных значения, представляющие пози- цию источника света Возвращаемые значения сохраняются в видовых координатах Эти значения не обязательно равны значениям, заданным командой gILight
222 Приложения GL_SPOT_DIR ECTION GL_SPOT_EXPONENT GL_SPOT_CUTOFF GLCONSTANT-ATTENUATION GL_LINEAR_ATTENUATION GL_QUADRATIC_ATTENUATION Параметр params возвращает три целых или ве- щественных значения, представляющие направ- ление на источник света Возвращаемые значения сохраняются в видовых координатах Параметр params возвращает одно целое или ве- щественное значение, представляющее показа- тель источника света Параметр params возвращает одно целое или ве- щественное значение представляющее угол раз- броса источника света Параметр params возвращает одно целое или ве- щественное значение, представляющее постоян- ное (не зависящее от расстояния) ослабление источника света Параметр params возвращает одно целое или ве- щественное значение, представляющее линейное ослабление источника света Параметр params возвращает одно целое или ве- щественное значение, представляющее квадра- тичное ослабление источника света params — возвращаемые данные Эта команда возвращает (в параметре params) значение или значения пара- метров источника света Параметр pname задает один из десяти параметров источника света посредством символического имени. void glGetMaterial[f i]v( GLenum face, GLenum pname, GLtype * params) face — задает, какой из двух материалов был запрошен Допустимы значения GL_FRONT и GL_BACK, представляющие, соответственно, материалы ли- цевой и обратной граней; pname — определяет возвращаемый параметр материала Допускаются сле- дующие значения- GL_AMBIENT Параметр params получает четыре значения соответствую- щего типа, представляющие фоновое отражение материала GL_DIFFUSE Параметр params получает четыре значения соответствую- щего типа, представляющие диффузное отражение мате- риала GL.SPECULAR Параметр params получает четыре значения соответствую- щего типа, представляющие зеркальное отражение мате- риала
Приложение 2 Получение информации о выбранных параметрах 223 GLEMISSION Параметр params получает четыре значения соответствую- щего типа, представляющие испускаемую интенсивность освещения материала GLSHININESS Параметр params получает одно значение соответствующе- го типа, представляющее экспоненциальное отражение ма- териала GL_COLOR_INDEXES Параметр params получает три значения соответствующего типа, представляющие фоновый, диффузный и зеркальный индексы материала Эти индексы используются только для индексного режима освещенности params — возвращаемые данные Команда возвращает значение (в параметре params} или значения параметра pname поверхности материала Если генерируется ошибка — содержимое params не изменяется void glGetPixelMap[f I us]v( GLenum map, GLtype * values) map — имя возвращаемого отображения (карты) пикселя Допустимы следующие значения GL_PIXEL_MAP_I_TO_I, GL PIXEL MAP S TO S, GLPIXELMAPITOR, GL_PIXEL_MAP_I_TO_G, GL_PIXEL_MAP_I_ TO_B, GLPIXELMAPITOA, GL_PIXEL_MAP_R_TO_R, GL_PIXEL_ MAPGTOG, GL_PIXEL_MAP_B_TO_B и GL_PIXEL_MAP_A_TO_A values — возвращаемое содержимое Команда возвращает (в параметре values} содержимое карты (отображения) пикселя, определенного параметром map Используйте карты (отображения) пикселя во время выполнения команд glReadPixels, glDrawPixels, glCopyPixels, glTexImagelD и glTexImage2D, чтобы отобразить индексы цвета, трафарета, компоненты цвета и глубины в другие значения Чтобы определить требуемый размер карты, необходимо вызвать glGet с со- ответствующей символической константой Если генерируется ошибка, содержимое параметра values не изменяется void glGetPolygonStipple(GLubyte *mask) mask — возвращаемый шаблон штриховки Команда возвращает маску шаблона штриховки многоугольника размера 32x32 Шаблон упакован в памяти, как определено командой glReadPixels Если генерируется ошибка, содержимое параметра mask не изменяется void glGetTexEnv[f i]v( GLenum target, GLenum pname, GLtype * params) target — окружение текстуры Должен быть равен GL TEXTURE ENV;
224 Приложения pname — символическое имя параметров окружения текстуры Допустимы следующие значения gl_texture_env_mode Символическая константа, определяющая режим окружения текстуры GL_TEXTURE_ENV_COLOR Четыре значения, определяющие цвет окружения текстуры params — возвращаемые данные Команда возвращает выбранные значения окружения текстуры, которые были определены командой glTexEnv В текущей реализации определено только одно окружение Если генерируется ошибка, содержимое параметра params не изменяется void glGetTexGen[d f i]v( GLenum coord, GLenum pname, GLtype * params) coord — координаты текстуры Должен быть одним из GL_S. GL_T. GL R или GLQ, рпате — символическое имя возвращаемого значения Должно быть одним из следующих GL_TEXTURE_GEN_MODE Символическая константа, определяющая функцию генерации текстуры GL_OBJECT_PLANE Четыре коэффициента уравнения плоскости, опреде- ляющего генерацию линейных координат объекта в мировых координатах GL_EYE_PLANE Четыре коэффициента уравнения плоскости, опреде- ляющего генерацию линейных координат объекта в видовых координатах params — возвращаемые данные Команда возвращает параметры функции, генерирующей координаты гек- стуры Если генерируется ошибка, содержимое параметра params не изменяется void glGetTexlmage( GLenum target, GLint level, GLenum format, GLenum type, GLvoid * pixels) target — определяет, какую текстуру получить — GL TEXTURE ID или GLTEXTURE2D level — число уровней детализации образа Базовый — нулевой
Приложение 2 Получение информации о выбранных параметрах 225 format — формат пикселей возвращаемых данных Поддерживаемые форма- ты GLRED, GL GREEN, GL_BLUE, GLALPHA, GL RGB, GL RGBA GLLUMINANCE, GLBGREXT, GL BGRA EXT и GL LUMINANCE ’ ALPHA. type — тип пикселей возвращаемых данных Поддерживаемые типы GL UNSIGNED BYTE, GL_BYTE, GL UNSIGNED SHORT, GL SHORT GL UNSIGNED INT, GL INT и GL FLOAT pixels — возвращаемый образ текстуры Должен быть указателем на массив определенного типа Команда возвращает образ текстуры в пикселях Если генерируется ошибка, содержимое параметра pixels не изменяется void glGetTexLevelParameter[f i]v( GLenum target, GLint level, GLenum pname, GLtype * params) target — символическое имя желаемого образа — GL TEXTURE ID или GLTEXTURE2D level — число уровней детализации желаемого образа pname — символическое имя параметра текстуры Допустимы следующие значения GL_TEXTURE_WIDTH GL_TEXTURE_HEIGHT GL_TEXTURE_COMPONENTS GL_TEXTURE_BORDER Ширина образа текстуры, включая бордюр Высота образа текстуры, включая бордюр Число компонентов в образе текстуры Ширина бордюра образа текстуры в пикселях params — возвращаемые данные Команда возвращает параметры текстуры для заданного в параметре level уровня детализации Если генерируется ошибка, содержимое параметра params не изменяется void glGetTexParameter[f i]v( GLenum target, GLenum pname, GLtype * params) target — символическое имя желаемого образа — или GL TEXTURE ID, или GL_TEXTURE_2D pname — символическое имя параметра текстуры Допустимы следующие значения GL_TEXTURE_MAG_FILTER Символическая константа — фильтр увеличения GL_TEXTURE_MIN_FILTER Символическая константа — фильтр уменьшения
226 Приложения gl_texture_wrap_s Символическая константа функции сворачивания координаты s текстуры GL_TEXTURE_WRAP_T Символическая константа функции сворачивания координаты t текстуры GL_TEXTURE_BORDER_COLOR Четыре числа соответствующего типа, которые включают в себя цвет бордюра текстуры params — возвращаемые параметры текстуры Команда возвращает значение (или значения) параметра(ов) текстуры, за- данного(ых) параметром pname Если генерируется ошибка, содержимое параметра params не изменяется
227 Приложение 3 Глоссарий Альфа (alpha) Четвертый цветовой компонент, используемый обычно для управле- ния смешением цветов Сам этот компонент никогда непосредственно не отобража- ется и по соглашению, принятому в OpenGL, соответствует степени непрозрачности значение 1 0 подразумевает полную непрозрачность, 0 0 — полную прозрачность Анимация (animation) Генерация повторяющихся изображений образов с плавно из- меняющейся точкой наблюдения и/или позициями объекта, достаточно быстрая, чтобы получить иллюзию движения Анимация в OpenGL почти всегда реализуется с использованием двойной буферизации Битовая плоскость (bitplane) Прямоугольный массив битов, отображаемый на пиксе- ли "один к одному" Буфер фрейма является стеком битовых плоскостей Битовый массив (bitmap) Прямоугольный массив битов, а также примитив, воспро- изводимый командой gl Bitmap, которая использует параметры битового массива в качестве маски Буфер (buffer) Группа битовых плоскостей, которая хранит единственный компо- нент (такой как глубина или красный) или индекс (такой как индекс цвета или ин- декс трафарета) Иногда красный, зеленый, синий и альфа буферы вместе ссылают- ся на один буфер цвета, а не на несколько Буфер фрейма (frame buffer) Все буферы данного окна или контекста воспроизведе- ния Иногда включают всю память графического акселератора Вектор нормали (normal vector) То же, что Нормаль (См ) Вершина (vertex) Точка в трехмерном пространстве Видовая матрица — матрица видового преобразования (modelview matrix) Матрица 4x4, которая преобразует точки, прямые, многоугольники и позиции растра из ко- ординат объекта в видовые координаты Видовые координаты (eye coordinates) Координаты, которые появляются после трансформации мировых координат с помощью матрицы видового преобразования, и которые предшествуют трансформации с помощью матрицы проекции Освеще- ние и специализированные отсечения выполняются в видовых координатах Восприятие глубины (depth-cueing) Техника воспроизведения, при которой при- сваиваемый точке (или вершине) цвет основывается на расстоянии от точки наблю- дения
228 Приложения Воспроизведение (rendering) Преобразование примитивов, заданных в координатах объекта, к образу в буфере фрейма Является первичной операцией в OpenGL Выполняемая команда (execute) Команда OpenGL, выполняющаяся при вызове в непосредственном режиме или из списка изображений Выпуклая оболочка (convex hull) Самая маленькая выпуклая область, включающая в себя определенную группу точек В двумерном пространстве выпуклая оболочка концептуально находится растягиванием резиновой ленты вокруг точек так, чтобы все точки лежали внутри нее Выпуклость (convex) Многоугольник является выпуклым, если не существует пря- мой, в плоскости многоугольника, которая пересекает его стороны более чем два раза Вычисление (evaluation) В OpenGL это процесс генерации объектно-координатных вершин и параметров из заданных уравнений Безье Гамма-коррекция (gamma correction) Функция, применяемая к цветам, хранящимся в буфере фрейма, для корректировки нелинейной чувствительности глаза, а иногда и монитора, к линейным изменениям в значениях интенсивности цвета Геометрическая модель (geometric model) Вершины, в координатах объекта, и пара- метры, которые этот объект описывают OpenGL определяет синтаксис не для гео- метрических моделей, а скорее синтаксис и семантику их преобразований Геометрический объект (geometric object) См Геометрическая модель Геометрический примитив (geometric primitive) Точка, линия или многоугольник Глубина (depth) Обычно относится к z-координате окна Грань (face) Одна сторона многоугольника Каждый многоугольник имеет две гра- ни — переднюю и заднюю При этом только одна грань всегда видима в окне Какая именно грань видима — передняя или задняя, эффективно определяется после того, как многоугольник спроецирован в окно После этой проекции, если ребра много- угольника обходятся по часовой стрелке, то видимой является одна из граней, если против часовой стрелки, то другая Соответствие направления по часовой стрелке передней или задней грани (соответственно, против часовой стрелки — задней или передней) определяется программистом OpenGL Группа (group) Каждый пиксель образа в памяти клиента представляется группой из одного, двух, трех или четырех элементов Таким образом, в контексте образа памя- ти клиента, группа и пиксель это одно и то же Движущееся очертание (motion blurring) Техника, которая имитирует то, что получа- ется при съемке на пленку движущегося объекта или при перемещении камеры, снимающей стационарный объект В анимации без движущегося очертания переме- щающиеся объекты могут появляться отрывисто Двойная буферизация (double-buffering) Контексты OpenGL с основным и вспомога- тельным буферами цветов являются контекстами с двойной буферизацией Для вы- полнения гладкой анимации изображение выводится только во вспомогательный буфер (который не отображается), после чего осуществляется обмен с основным буфером
Приложение 3 Гпоссарий____________________________________________ 229 Задняя грань (back face) См Грань Индекс (index) Отдельное значение, которое рассматривается как абсолютное, а не нормализованное, значение в определенном диапазоне Индексы цвета являются названиями цветов, которые используются при применении карты цветов Индексы обычно являются маскируемыми, а не фиксированными значениями Например, индекс 0xf7 маскируется в 0x7, при записи в 4-разрядный буфер (цвета или трафаре- та) Индексы цветов и трафаретов всегда трактуются как индексы, а не как компо- ненты Индекс цвета (color index) Отдельное значение, которое определяет цвет по имени, а не по значению Индексы цветов в OpenGL трактуются как непрерывные значения (например, числа с плавающей точкой), пока над ним выполняются такие операции, как интерполяция и комбинирование цветов Однако в буфере фрейма они всегда хранятся в виде целых чисел Индексы формата с плавающей точкой преобразуются в целые числа округлением до ближайшего целого Каркасная модель (wireframe) Изображение объекта, которое содержит только пря- мые Обычно они показывают стороны многоугольника Карта цветов (color map) Таблица отображения индексов в значения RGB, которые доступны для используемого типа дисплея Каждый индекс цвета читается из буфе- ра цвета, преобразуется в три значения RGB и посылается на монитор Клиент (client) Компьютер, от которого поступают команды OpenGL Он может быть соединен сетью с различными компьютерами, на которых эти команды выпол- няются, или это может быть один и тот же компьютер См также Сервер Комбинирование цветов (dithering) Техника для увеличения воспринимаемого диапа- зона цветов в образе, достигаемого ценой пространственного разрешения Приле- гающим пикселям присваиваются различные значения цветов При просмотре с рас- стояния, эти цвета кажутся смешанными в один промежуточный цвет Эта техника подобна полутонированию, используемому в черно-белых изображениях для дости- жения оттенков серого Компонент (component) Отдельное непрерывное значение (например, число с пла- вающей точкой) Обычно нулевое значение компонента представляет минимальное значение интенсивности, а единица — максимальное, хотя иногда используются и другие способы нормализации Так как значения компонента задаются в нормализо- ванном виде, то они определяются независимо от действительного разрешения На- пример, тройка RGB (1, 1, 1) — это белый цвет вне зависимости от того, использу- ется ли в буфере для хранения 4, 8 или 12 битов на каждый цвет Компоненты вне диапазона (out-of-range components) Такие компоненты, как прави- ло, зафиксированы в нормализованном диапазоне, не усечены или интерпретируют- ся некоторым другим способом Например, тройка RGB (14, 1 5, 0 9) перед исполь- зованием для обновления буфера цвета преобразуется в значения (10, 1 0, 0 9) Па- раметры красный, зеленый, синий, альфа и глубина всегда трактуются как компоненты, а не как индексы Контекст (context) Полное множество переменных состояния OpenGL При этом буфер фрейма содержит не часть состояния OpenGL, а только параметры конфигу- рации
230 Приложения Конус отсечения (frustum) Объем видимости, деформированный при помощи пер- спективного деления Координаты объекта (object coordinates) Система координат перед каким-либо пре образованием OpenGL Координаты отсечения (clip coordinates) Система координат, которая сопровождает перспективное преобразование матрицей проекции и предшествует делению пер- спективы Объемное отсечение выполняется в координатах отсечения, но это не относится к специализированному отсечению Матрица (matrix) Двумерный массив значений Все матрицы OpenGL имеют размер 4x4, хотя при хранении в памяти клиента они представлены как одномерные масси- вы 1x16 Матрица проекции (projection matrix) Матрица 4x4, которая служит дня преобразова- ния точек, линий, многоугольников и позиции растра из видовых координат в ко- ординаты отсечения Матрица текстуры (texture matrix) Матрица 4x4. преобразующая координаты тексту- ры в координаты, которые используются для интерполяции и поиска текстуры Многоугольник (polygon) Плоская поверхность, ограниченная сторонами, которые заданы вершинами Прямоугольник, определенный функцией glRect\ также являет- ся многоугольником Монитор (monitor) Устройство, которое отображает образ из буфера фрейма Наложение текстуры (texture mapping) Процесс применения изображения (текстуры) к примитиву Отображение текстуры часто используется для добавления "реализма" изображению Например, можно применить рисунок фасада здания к многоуголь- нику, представляющему стену Невыпуклость (nonconvex) Многоугольник является невыпуклым, если в плоскости многоугольника существует прямая, которая пересекает его стороны больше двух раз Непосредственный режим (immediate mode) Выполнение команд OpenGL при их не- посредственном вызове, а не из списка изображении Этот режим скорее относится к использованию OpenGL, чем к определенному биту состояния Нормализация (normalize) Деление каждого компонента нормали на квадратный ко- рень из суммы их квадратов (factor) Таким образом, если нормаль представлена как вектор из начала к точке (пх', пу', пх'), то этот вектор имеет единичную длину nx' = nx/factor пу' = ny/factor nz' = nz/factor Нормаль (normal) Уравнение плоскости с тремя компонентами, которые определяю- угловую ориентацию, но не позицию плоскости или поверхности Обработка полутонами (shading) Процесс интерполяции цвета, при котором исполь- зуется внутреннее содержимое многоугольника или цвета между вершинами линии во время растеризации
Приложение 3 Гпоссарий________________________________________________ 231 Образ (image) Прямоугольный массив пикселей либо в памяти Клиента, либо в бу- фере фрейма Объект (object) Объектно-координатная модель, представляемая как набор прими- тивов Объем видимости (view volume) Объем в координатной системе отсечения, координа- ты которого удовлетворяют трем условиям — W < X < W — W < у < W — W < Z < W Однократная буферизация (single-buffering) Контексты OpenGL, которые не имеют дополнительных буферов цветов, используют режим однократной буферизации Однородные координаты (homogenous coordinates) Множество (п+1) координат, ис- пользуемых для представления точек в n-мерном пространстве Точки в пространст- ве могут рассматриваться как точки евклидового пространства, вместе с некоторыми точками в бесконечности Координаты являются однородными, поскольку масшта- бирование каждой из координат одной и той же ненулевой константой не изменяет точку, к которой они относятся Однородные координаты полезны в проекционной геометрии и, таким образом, в компьютерной графике, где рисунки должны быть спроецированы в окно Окно (window) Подобласть буфера фрейма, обычно прямоугольная, пиксели которой имеют одинаковую конфигурацию буфера Контекст OpenGL воспроизводит отдель- ное окно за один раз Оконное выравнивание (window-aligned) Во время ссылки на линейные сегменты или грани многоугольника подразумевается, чю они параллельны границам окна В OpenGL окно является прямоугольным, с горизонтальными и вертикальными сто- ронами Во время ссылки на многоугольный шаблон подразумевается, что он при- вязан к началу окна Оконные координаты (window coordinates) Система координат окна Open GL Она важна, чтобы отличать координаты пикселей, которые являются дискретными, и оконных координат, которые является непрерывными Например, пиксель левого нижнего угла окна — это пиксель (0, 0), оконные координаты центра этого пиксе- ля — (0 5, 0 5, z) включают также глубину, или компоненту z Ортографическая проекция (orthographic) Проекция без учета перспективы, как, на- пример, в инженерных чертежах Освещенность (lighting) Вычисление цвета вершины производится на основе теку- щих параметров освещенностей, свойств материала и режима модели освещенности Отбор (culling) Процесс удаления передней или задней граней многоугольника, так что они не рисуются Отсечение (clipping) Исключение части геометрического примитива, которая нахо- дится вне пространства, определенного плоскостями отсечения Если точки нахо- дятся снаружи, они отбрасываются Часть линии или многоугольника, которая на- ходится вне этого пространства, исключается и формируются дополнительные вер-
232 Приложения шины, необходимые для дополнения примитива внутри пространства отсечения Геометрические примитивы и текущая позиция растра (raster) (если она определена) всегда отсекается шестью плоскостями, заданными левой, правой, верхней, нижней, ближней и дальней плоскостями объема видимости Приложения могут определять необязательные специализированные плоскости отсечения, применяемые в мировых координатах Память клиента (client memory) Основная память клиента, в которой хранятся пере- менные приложения Параметр (parameter) Значение, передаваемое в качестве аргумента в команду OpenGL Передняя грань (front face) См Грань (face) Перспективное деление (perspective division) Деление х, у и z на w, выполненное в координатах отсечения Пиксель (pixel) Элемент рисунка Биты в точке (х, у) во всех битовых плоскостях буфера фрейма составляют единичный пиксель (х, у) В образе в памяти клиента пиксель является группой элементов В оконных координатах OpenGL, каждый пик- сель соответствует области экрана 1 0x1 0 Координаты нижнего левого угла пикселя названного х, у являются (х, у), а верхнего правого — (х+1, у+1) Плоская обработка полутонов (flat shading) Имеет отношение к окрашиванию при- митива единственным, постоянным цветом на всем его протяжении, вместо гладкой интерполяции цветов этого примитива Примитив (primitive) Фигура (такая как точка, линия, многоугольник, битовый мас- сив или образ), которая рисуется, хранится и с которой манипулируют как с единой дискретной сущностью Элементы, из которых создаются большие графические проекты Примитив образа (image primitive) Битовый массив или рисунок Прямая (line) Прямая область ограниченной ширины между двумя вершинами (В отличие от математической прямой, в OpenGL прямые имеют ограниченную длину и ширину). Каждый отрезок участка прямой сам является прямой Равномерная обработка полутонами (gouraud shading) Равномерная интерполяция цветов многоугольника или отрезка прямой Цвета определяются вершинами и ли- нейной интерполяцией их соседних значений у всего примитива, чтобы получить равномерную вариацию цвета Растеризация (rasterize) Преобразование спроецированной точки, прямой, много- угольника, пикселя битового массива или образа во фрагменты, каждый из которых соответствует пикселю в буфере фрейма Растеризуются все примитивы, а не только точки, прямые и многоугольники Режим RGBA (RGBA mode) Контекст OpenGL находится в режиме RGBA, если его буферы цветов хранят не индексы, а красный, зеленый, синий и альфа компоненты цвета Режим индексации цвета (color-index mode) Контекст OpenGL работает в режиме индексации цвета, если его буферы цвета хранят индексы, а не компоненты цветов
Приложение 3 Гпоссарий_________________________________________________ 233 Сервер (server) Компьютер, на котором выполняются команды OpenGL Он может быть отличным от компьютера, который "выдает" команды См Клиент Система координат (coordinate system) В n-мерном пространстве — это набор линей- но независимых векторов, привязанных к точке называемой началом Группа коор- динат задает точку в пространстве (или вектор от начала), показывающих как далеко от начала, вдоль каждого вектора, располагается эта точка (или вершина вектора) Смешивание (blending) Уменьшение двух компонентов цвета до одного, обычно с использованием линейной интерполяции между двумя компонентами Специализированное отсечение (application-specific clipping) Отсечение примитивов плоскостями в мировых координатах Список изображений (display list) Поименованный список команд OpenGL Этот список всегда хранится на сервере и может использоваться для уменьшения трафика сети Содержимое списка изображений может быть предварительно обработано и, следовательно, выполняется более эффективно, чем набор тех же команд OpenGL, выполняющихся в непосредственном режиме Такая предварительная обработка особенно важна для команд, интенсивно использующих вычисления, таких как glTexImage Ступенчатость (jaggies) Искусственные средства ступенчатого преобразования Ребра примитивов, которые воспроизводятся со ступеньками, чаще всего не являются гладкими Ступенчатость — дефект изображения линий (aliasing) Техника воспроизведения, при которой пикселю присваивается цвет изображаемого примитива независимо от того, покрывает ли этот примитив весь пиксель или только его часть Результатом этого является появление зазубренных краев или ступенек Тексель (texel) Элемент текстуры, который получается из текстуры в памяти, и представляет цвет текстуры, применяемой к соответствующему фрагменту Текстура (texture) Одно- или двумерное изображение, используемое для модифика- ции цвета фрагментов, произведенных растеризацией Текущая матрица (current matrix) Матрица, которая служит для преобразования ко- ординаты из одной системы координат в другую В OpenGL существует три текущие матрицы матрица видового преобразования (modelview matrix) трансформирует ми- ровые координаты (определяемые программистом) в видовые координаты, матрица перспективного преобразования (perspective matrix) преобразует видовые координаты в координаты отсечения, матрица текстуры (texture matrix) преобразует заданные или сгенерированные координаты текстуры в соответствии с параметрами матрицы Все эти матрицы имеют свои стеки, каждым из которых можно управлять при по- мощи специальных команд манипуляции матрицами OpenGL Текущая позиция растра (current raster position) Позиция оконных координат, кото- рая задает положение образа примитива после его растеризации Текущая позиция растра и другие текущие параметры растра обновляются вызовом функции glRasterPos Тесселяция — разбиение (tessellation) Преобразование части аналитической поверх- ности к связанным многоугольникам или части аналитической кривой к последова- тельности линий
234 Приложения Точка наблюдения (viewpoint) Начало либо видовой координатной системы, либо координатной системы отсечения, в зависимости от контекста Например, если речь идет об освещении, точка наблюдения — это начало видовой координатной систе- мы Если же речь идет о проекции, то точка наблюдения является началом коорди- натной системы отсечения Обычно начала обеих координатных систем совпадают Трансформация (transformation) Деформация пространства В OpenGL они ограниче- ны трансформациями проекций, включающими вращение, перемещение, (непро- порциональное) масштабирование вдоль координатных осей, перспективные транс- формации, и их комбинациями Туман (fog) Техника воспроизведения, которая может использоваться для моделиро- вания этого атмосферного явления путем постепенного перехода цветов объекта к цвету фона, в зависимости от расстояния до смотрящего Такой подход помогает также при восприятии расстояния от смотрящего, дает эффект глубины Устранение ступенчатости (antialiasing) Техника воспроизведения, при которой пик- селю присваивается цвет на основе размера площади пикселя, покрытой прими- тивом. Фрагмент (fragment) Фрагменты, сформированные после растеризации примитивов Каждый фрагмент соответствует отдельному пикселю и включает цвет, глубину и другие текстурно-координатные значения Шрифт (font) Группа представлений графических символов, обычно использующих- ся для отображения строк текста Символы могут быть латинскими буквами, матема- тическими символами, азиатскими идеограммами, египетскими иероглифами и т д Штриховка (stipple) Одно- или двумерный бинарный шаблон, нулевые значения элементов которого отменяют генерацию фрагментов Линейные штрихи являются одномерными и применяются относительно начала линии Многоугольные штрихи являются двумерными Элемент (element) Отдельный компонент или индекс Яркость (luminance) Видимая яркость поверхности Часто относится к взвешенному среднему красного, зеленого и синего значений цветов, которые дают видимую яр- кость комбинации NURBS (Non-Uniform Rational B-spIine, неоднородный рациональный В-сплайн) Общий способ для задания параметрических кривых и поверхностей RGBA Красный, зеленый, синий и альфа компоненты цвета X Window System Оконная система, используемая многими компьютерами, на кото- рых выполняется OpenGL
235 Библиография Поскольку представленная книга носит практическую направленность, а програм- мировать создание и движение трехмерных объектов без некоторой теоретической подготовки очень трудно, приведу список некоторых книг, которые на мой взгляд наиболее удачно отражают представленную тематику Роджерс Д. Алгоритмические основы машинной графики: Пер. с англ. — М.: Мир, 1989. — 512 с., ил. Книга представляет собой замечательное введение в двух- и трехмерную компью- терную графику Подробнейшим образом рассматриваются многочисленные алго- ритмы растровой графики Начиная с алгоритма Брезенхема и устранения ступенча- тости, в ней последовательно рассматриваются двух- и трехмерное отсечения, удале- ние невидимых линий и поверхностей, после чего подробно освещаются вопросы построения реалистических изображений Роджерс Д., Адамс Дж. Математические основы машинной графики: Пер. с англ. — М.: Машиностроение, 1980. — 240 с., ил. Как я неоднократно упоминал в тексте книги, писать программы для трехмерной графики, не опираясь на определенный математический аппарат, невозможно Дан- ная книга, хотя уже и старенькая, является прекрасным введением в эту область Ее несомненным достоинством является легкость изложения, что позволяет быстро разобраться в незнакомом материале Аммерал Л. Машинная графика на языке С: В 4-х книгах. — Сол Систем, 1992. Прекрасный четырехтомник как для начинающих программировать на языке С, так и для желающих познакомиться с основами программирования двух- и трехмерной графики Несомненным достоинством является то, что она содержит много практи- ческих примеров программ Томпсон Н. Секреты программирования трехмерной графики для Windows 95: Пер. с англ. — СПб.: Питер, 1997. — 352 с., ил. Написанная прекрасным языком и поэтому легко читаемая книга довольно извест- ного автора, у которого я позаимствовал некоторые идеи при написании своих при- ложений
236 Библиография Г над ков С. А., Фролов Г. В. Программирование в Microsoft Windows: В 2-х ч. — М.: "ДИАЛОГ-МИФИ", 1992.— 320 с., 288 с. На мой взгляд это лучшая книга, написанная для начинающих программировать в Microsoft Windows Если при чтении моей книги вы полностью сумели разобраться только в приведенных консольных приложениях, — зайдите в "Старую книгу", мо- жет быть вам повезет и вы сможете окунуться в мир программирования под Win- dows, руководствуясь именно этой книгой Мешков А., Тихомиров Ю. Visual C++ и MFC. Программирование для Windows NT и Windows 95: В трех томах. — СПб.: BHV — Санкт-Петербург, 1997. — 464 с., 464 с., 365 с. Не мог не упомянуть о нашей книге, посвященной программированию для Windows, и в большей степени — прекрасной библиотеке классов MFC Тем. кто еще не начал писать программы на C++, рекомендую эту довольно удачную книгу, которая существенно "облегчит жизнь" в начале этого нелегкого, но очень интерес- ного пути Естественно, что представленный список не охватывает всю имеющуюся литературу Я привел здесь только те, которые особенно понравились мне
237 Заключение Очевидно, что в небольшой книге невозможно рассмотреть все вопросы по такой безграничной теме, какой является трехмерная компьютерная графи- ка И хотя в нее вошли многие интересные разделы, а некоторые были рас- смотрены косвенно — например вопросы анимации, от многого, к сожале- нию, пришлось отказаться Но мне хочется надеяться, что те сведения, ко- торые я попытался изложить, послужат хорошей основой для дальнейших, уже самостоятельных, исследований Мне все же представляется, что про- граммировать объекты трехмерной графики несложно, особенно когда под рукой такой мощный аппарат, каковым является библиотека OpenGL Ведь не случайно компании Microsoft и Silicon Graphics заключили долгосрочное соглашение о развитии и дальнейшей поддержке именно этой библиотеки Так же как и общие вопросы трехмерной графики, в этой книге затронута только вершина айсберга, называемого OpenGL Действительно, только вершина Рассмотренные вопросы построения объектов из примитивов, ра- боты со светом, наложения текстуры, конечно же не исчерпали всех воз- можностей, которые заложены в эту библиотеку (систему) Несмотря на ог- раниченность представленного материала, мне представляется, что он, наря- ду с добротным введением в принципы программирования трехмерной графики с использованием OpenGL, должен пробудить интерес к самостоя- тельным исследованиям в этой безусловно интересной области Богатейшие возможности этой системы в некоторых случаях имеют и об- ратную сторону — использование одних команд вместо других может при- вести к снижению эффективности создаваемого графического приложения Поэтому в заключении приведу некоторые рекомендации по использованию OpenGL: □ Для отображения всех геометрических объектов на единственной плоско- сти используйте матрицу проекций, т. к матрицы видового преобразова- ния служат для преобразования в видовые координаты □ Не выполняйте слишком много операций над матрицами. Например, ес- ли необходимо выполнять вращение объекта, то не следует последова- тельно вызывать команду glRotate. Лучше использовать glLoadldentity для инициализации матрицы для каждого кадра и затем один раз вызвать glRotate с окончательным (текущим) значением угла поворота
238 Заключение □ При работе со списком изображений не следует включать в него провер- ку ошибок, т к команды внутри списка генерируют ошибки только по- сле того, как список будет выполнен □ Чтобы оптимизировать операции с буфером глубины, разместите ближнюю плоскость конуса отсечения как можно дальше от точки наблюдения □ Для ускорения выполнения команд OpenGL используйте команду glFlush □ Выключите режим смешения цветов при воспроизведении образа до смешения, например, при вызове команды glCopyPixel □ Чтобы получить точную двумерную растеризацию, внимательно опреде- лите и ортографическую проекцию и вершины примитивов При задании ортографической проекции лучше использовать целочисленные коорди- наты, например gluOrtho2D(0, сх, 0, су), □ Старайтесь не использовать отрицательные значения для координат w вершин и координат q текстуры, т к OpenGL в таких случаях не может корректно провести отсечение □ Где только возможно по логике работы, старайтесь использовать команду glColorMaterial вместо glMaterial. □ Для инициализации матриц лучше всего использовать команду glLoadldentity □ Для сохранения и восстановления текущих значений переменных со- стояния OpenGL используйте команды glPushAttrib и glPopAttrib □ При работе с текстурой старайтесь пользоваться списком изображений □ Если не требуется создание однородной тени, явным образом установите модель в значение GL FLAT, вызвав команду glShadeModel □ Старайтесь не использовать очистку небольших участков буферов — лучше очищать весь буфер целиком □ При создании треугольников и четырехугольников следует использовать соответственно специальные значения GL TRIAGLES и GL QUADS, а не GL POLYGON □ При создании примитивов старайтесь минимизировать количество ис- пользуемых командных скобок glBegin/glEnd □ Использование векторной формы команд более предпочтительно по сравнению со скалярной версией □ При выполнении операций рисования и копирования образов заблоки- руйте растеризацию и операции над фрагментами, которые расходуют много ресурсов Желаю вам успехов на этом интересном поприще
239 Предметный указатель С CView PreCreateWindow, 7 G GK_TEXTURE_2D, 187 GL2BYTES, 95 GL3BYTES, 95 GL_4 BYTES, 95 GLALLATTRIBBITS, 58 GLALPHA. 188 G LA L PH AT EST, 57, 206 GL AMBIENT, 153, 162 GL AMBIENT AND DIFFUSE, 154 GL AUTO NORMAL, 206 GL BACK, 56, 153 GL BITMAP, 189 GLBLEND, 57, 194, 206 GL BLUE, 188 GL BYTE, 94, 189 GLcBIAS, 189 GLcSCALE, 189 GLCLAMP, 191 GL_CLIP_PLANE i, 206 GL_COLOR_ARRAY_EXT, 206 GL COLOR-BUFFER BIT, 57 GL COLORJNDEX, 188, 189 GL_COLOR_INDEXES, 154 GL COLOR MATERIAL, 58, 206 GL COLOR MATERIAL FACE, 58 GL-COMPILE, 92 GL_COMPILE_AND_EXECUTE, 92 GL CONSTANT ATTENUATION, 162, 172 GL CULL FACE, 58, 206 GL CULL FACE MODE, 58 GL CURRENT BIT, 57 GL_CURRENT_RASTER_ POSITION-VALID, 57 GL_DECAL, 194 GL_DEPTH_BUFFER BIT, 57 GL_DEPTH_TEST, 57, 206 GL_DEPTH_WRITEMASK, 57 GL DIFFUSE, 153, 162 GL DITHER, 57, 206 GL_DRAW BUFFER, 57 GL_EDGE_FLAG, 57 GL_EDG E FLAG ARRAY EXT, 207 GL-EMISS1ON, 154 GL_EYE_LINEAR, 202 GL_EYE_PLANE, 202 GL_FILL, 56 GL FLOAT, 94, 189 GL-FOG, 207 GL-FRONT, 56, 153 GL FRONT AND BACK, 56, 153 GL_FRONT_FACE, 58 GL GREEN, 188 GL_INDEX_ARRAY EXT, 207 GLJNDEX-OFFSET, 188 GLINDEX-SHIFT, 188 GL INT, 94, 189 GL LIGHT MODEL_AMBIENT, 169 GL_LIGHT_MODEL LOCAL- VIEWER, 58, 168, 172 GL LIGHT MODEL TWO SIDE, 58, 168 GL LIGHTi, 207 GL_LIGHTING, 58, 207 GL LIGHTING BIT, 58 GL_LINE, 56 GL_LINE_BIT, 58 GL_LINE_LOOP, 35, 36
240 Предметный указатель GL-LINE-SMOOTH, 56, 58, 207 GL-LINE-STIPPLE, 58, 207 GL_LINE_STRIP, 35, 36 gl_line_width, 56 GLJJNEAR, 191, 192 gl_linear_attenuation. 162; 172 gl_linear_mipmap_linear, 192 GL_LINEAR_MIPMAP_NEAREST, 192 GLLINES, 35, 36 GL_LOGIC_OP, 57, 207 GLJJJMINANCE, 189 GLLUMINANCEALPHA, 189 GL_MAP1_COLOR_4, 207 GL_MAP1_INDEX, 207 GL_MAP1_NORMAL, 207 GL_MAP1_TEXTURE_COORD_1, 207 GL_MAP1_TEXTURE_COORD_2, 208 GL_MAP1_TEXTURE_COORD_3, 208 GL_MAP1_TEXTURE_COORD_4, 208 GL_MAP1_VERTEX_3, 208 GL_MAP1_VERTEX_4, 208 GL_MAP2_COLOR_4, 208 GLMAP2INDEX, 208 GLMAP2NORMAL, 208 GL_MAP2_TEXTURE_COORD_1, 208 GL_MAP2_TEXTURE_COORD_2, 208 GL_MAP2_TEXTURE_COORD_3, 208 GL_MAP2_TEXTURE_COORD_4, 209 GL_MAP2_VERTEX_3, 209 GL_MAP2_VERTEX_4, 209 GL MATRIX MODE, 58 GLMODELVIEW, 112 GLMODULATE, 194; 195 GLNEAREST, 191, 192, 193, 194 GL_NEAREST_MIPMAP_LINEAR, 192 GL NEAREST MIPMAP NEAREST, 192 GLNORMALARRAYEXT, 209 GLNORMALIZE, 58, 209 GLOBJECTLINEAR, 202 GLOBJECTPLANE, 202 GLPACKALIGNMENT, 68 GLPACKLSBFIRST, 68 GLPACKROWLENGTH, 68 GL_PACK_SKIP_PIXELS, 68 GL-PACK SKIP ROWS, 68 GL_PACK_SWAP_BYTES, 67, 68 GL PIXEL MAP I TO A, 188 GL-POINT, 56 GL_POINT_BIT, 58 GL_POINT_SIZE, 56 GL_POINT_SMOOTH, 56, 58, 209 GL-POINTS, 34; 36 GL_POLYGON, 36 GL_POLYGON_BIT, 58 GL_POLYGON_MODE, 58 GL_POLYGON_SMOOTH, 56, 58, 209 GL POLYGON STIPPLE, 56, 58, 209 GL_POLYGON_STIPPLE_BIT, 58 GL_POSITION, 163 GL_PROJECTION, 112 GL_Q, 202 GL-QUAD_STRIP, 36 GL_QUADRATIC_ATTENUAT1ON, 162 GL-QUADS, 35; 36 GL R, 202 GL_RED, 188 GL_REPEAT, 191 GL_RGB, 189 GL_RGBA, 189 GL_S, 202 GL_SCISSOR_BIT, 58 GL SCISSOR-TEST, 58, 209 GL_SHADE_MODEL, 58 GL_SHININESS, 153, 154
Предметный указатель 241 GLSHORT, 94, 189 GLSPECULAR, 153, 163 GL SPHERE MAP, 202 GLSPOTCUTOFF, 162 GLSPOTDIRECTION, 163 GLSPOTEXPONENT, 161 GL STENCIL BUFFER BIT, 58 GL STENCIL TEST. 58. 209 GL_T, 202 GLTEXTURE, 112, 205 GL TEXTURE1D, 187, 190, 191, 209 GL TEXTURE 2D, 190, 191,210 GL TEXTURE BIT, 58 GL TEXTURE BORDER COLOR, 192 GL TEXTURE COORD ARRAY EXT, 209 GLTEXTUREENV, 194 GL TEXTURE ENV COLOR, 194 GL TEXTURE ENV MODE, 194 GL TEXTURE GEN MODE, 58, 202 GL TEXTURE GEN Q, 210 GL TEXTURE GEN R, 210 GL TEXTURE GEN S, 210 GL TEXTURE GEN T, 210 GL TEXTURE GEN x, 58 GL TEXTURE MAG FILTER, 191 GL TEXTURE MIN FILTER, 191 GL TEXTURE WRAP S, 191, 192 GL TEXTURE WRAP T, 192 GL TRANSFORM BIT. 58 GL TRIANGLE FAN, 35, 36 GL TRIANGLE STRIP, 35. 36 GLTRIANGLES, 35, 36 GL UNPACK ALIGNMENT, 68 GL_UNPACK_LSB_FIRST, 68, 189 GL_UNPACK_ROW_LENGTH, 68 GLUNPACKSKIPPIXELS, 68 GL_UNPACK_SKIP_ROWS, 68 GL UNPACK SWAP BYTES, 67, 68 GL_UNSIGNED_BYTE, 94, 189 GL UNSIGNED INT, 94, 189 GL_UNSIGNED_SHORT, 94, 189 GL VERTEX ARRAY EXT, 210 GL_VIEWPORT_BIT, 58 GLbyte, 31, 41, 138 Gldouble, 32, 41, 138 GLfloat, 32, 41, 138 GLint, 31, 41. 138 GLshort, 31, 41, 138 Glubyte, 32 GLuint, 32 Gliishort, 32 о OpenGL Choose Pixel Format, 11, 19 Desci ibePixelFormat, 14 GetPixelFoimat, 14 glAlphaFunc, 206 glArrayElementAnayEXT, 206; 207, 209, 210 glAnayElementEXT, 206, 207, 209, 210 glBegin, 34 glBitmap, 65, 68 glBlendFunc, 206 glCallList, 64, 94 glCallLists, 64, 94 glClipPlane, 206 glColor, 63 glColor[3 4]|b s i f dj, 40, 138 glColor[3 4J[b s i fd] v, 40, 138 glColoiMatenal, 155, 206 glColoi Pointer, 96 glCullFace, 206 glDeleteLists, 96 glDeleteTexture, 96 glDepthFunc, 206 glDepthRange, 206 glDisable, 44, 163 glDiawArraysEXT. 206, 207, 209, 210 glDrawPixel, 68
242 Предметный указа тел t glEdgeFlag, 63 glEdgeFlagPointer, 96 glEnable, 44, 163, 190 glEnd, 34 glEndList, 93 glEvalCoord, 63 glEvalCoordl, 207, 208. 209 glEvalCoord2, 208; 209 glEvalMeshl, 207; 208, 209 glEvalMesh2, 208, 209 glEvalPoint, 63 glEvalPointl, 207, 208; 209 glEvalPoint2, 208, 209 glFeedbackBuffer, 96 glFinish, 41, 96 glFlush, 42; 96 glFog, 207 glFrustum, 125 glGenLists, 96 glGenTexture, 96 gllndex, 63 gllndexfs i f d], 138 gllndex[s i f dj v, 138 gllndexPointer, 96 gllsEnabled, 96 gllsList, 96 gILight, 207 glLight[i f], 161 glLightji f] v, 162 gILightModel, 207 glLightModelfi f], 168 gILightModel[i f] v, 168 glLineStipple, 207 glLineWidth, 207 glListBase, 95 glLoadldentity, 112 glLoadMatrixff dj. 112 glLogicOp, 207 gIMap 1, 207; 208, 209 glMap2, 208, 209 glMaterial, 63, 207 glMaterialfi f], 153 glMaterial [i f] v, 153 glMatrixMode, 111 glMultMatrix[f d], 112 glNewList, 92 glNounal, 63, 209 glNoimal3(b s i f d|, 150 glNormal3|b s i f d| v. 150 glNormalPointer, 96 glOrtho, 119 glPixelStore, 96, 189 glPixelStote[f i], 67 glPointSize, 209 glPolygonMode, 56, 209 glPolygonStipple, 68, 209 glPopAttrib, 57, 95 glPopMatrix, 95, 205 glPushAttrib, 57, 95 glPushMatrix, 95, 205 glRasterPos[2 3 4||s i f d|, 69 glRasterPos|2 3 4||s i f d| v, 69 gl Read Pixel, 68 gl Read Pixels, 96 glRectfd f i sj, 64 glRectjd f i sj v, 64 glRenderMode, 96 glRotate[f d], 113 glScaleffd], 113 glScissor, 209 glSelectBuffei, 96 glStencilFunc, 209 glStencilOp, 209 glTexCoord, 63 glTexCoord[l 2 3 4||s i fd|, 196 glTexCoord! 1 2 3 4||s i f d] v, 196 glTexCoordPointer, 96 glTexEnvji fj, 194 glTexEnvji f| v. 194 gITexGen, 210 glTexGen|i f d|, 202 gITexGen[i f d] \, 202 glTexImagel D, 68, 187, 209 glTex!mage2D. 68, 187, 210 glTexParametei|i f] v, 191 glTianslate|f dj, 113 gluOrtho2D, 120 glu Perspective, 126 glVertex, 63 glVertex!2 3 4][s i f d|, 32
Предметный указатель 2* glVeilex[2 3 4][s i f d] v, 32 glVertex3f, 32 glVertexPointer, 96 glViewport, 117 SetPixelFormat, 12; 19 SwapBuffers, 42 wglCreateContext, 16, 19 wglDeleteContext, 18; 19 wglGetCurrentContext, 18 wglGetCurrentDC, 18 wglMakeCurent, 19 wglMakeCurrent, 16, 19 P PFD_DOUBLEBUFFER, 9 PFD DRAW TO BITMAP, 8 PFD_DRAW_TO_WINDOW, 8 PFDGENERICFORMAT, 8 PFD NEED PALETTE, 8 PFD_NEED_SYSTEM PALETTE, 9 PFDSUPPORTGDI, 8 PFDSUPPORTOPENGL, 8 PFD SWAP LAYER BUFFERS, < PFDTYPECOLORINDEX, 11 PFDTYPERGBA, 11 PIXELFORMATDESCRIPTOR, 8 w WM CREATE, 19 WM-DESTROY, 19 WS CLIPCHILDREN, 7, 19 WS CLIPSIBLINGS, 7, 19
РУКОВОДСТВО ДЛЯ ПРОФЕССИОНАЛОВ Книги серии “мастер” написаны экспертами в области разработки и эксплуатации программных и аппаратных комплексов. Только в книгах этой серии Вы найдете систематическое и полное изложение специальных аспектов современных компьютерных технологий и сможете выбрать оптимальные и эффективные решения проблем прикладного программирования. Серия “мастер” предлагает незаменимый инструмент в работе, дает уникальный шанс расширить границы Ваших профессиональных возможностей, передает Вам опыт и знания специалистов высшей квалификации ОРЕЖХ СОЗДАНИЕ РЕАЛИСТИЧЕСКИХ ОБРАЗОВ «.ТИХОМИРОВ ПРОГРАММИРОВАНИЕ ТРЕХМЕРНОЙ ГРАФИКИ Компьютерная графика широко используется для наглядности восприятия и передачи информации. Знание ее основ в наше время необходимо любому программисту Эта книга является практическим пособием по созданию и обработке трехмерных образов с использованием графического стандарта OpenGL, который предоставляет широкие возможности, поддерживая при этом простейшую модель программирования Щ Особенности реализации OpenGL в Windows NT и Windows 95 Структура консольного приложения Windows для работы с OpenGL • Основные принципы создания графических объектов в OpenGL (вершины, примитивы, глубина, трафареты, "туман" ит. д.) Необходимые теоретические сведения: системы координат, геометрические преобразования и проекции Построение реалистических изображений Цвет в компьютерной графике; наложение текстуры Scanned: x-code for NataHaus.ru