Текст
                    
Ims
OpenGL
СУПЕРКНИГА
ТРЕТЬЕ ИЗДАНИЕ


СУПЕРКН И ГА ТРЕТЬЕ ИЗДАНИЕ Ричард С. Райт-мл. и Бенджамин Липчак Москва • Санкт-Петербург • Киев 2006
ББК 32.973.26-018.2.75 Р18 УДК 681.3.07 Издательский дом “Вильямс” Зав редакцией С Н Тригуб Перевод с английского и редакция А В Назаренко По общим вопросам обращайтесь в Издательский дом “Вильямс” по адресу info@williamspublishmg.com, http://www williamspublishing com 115419, Москва, a/я 783; 03150, Киев, а/я 152 Райт, Ричард С.-мл., Липчак, Бенджамин. Р18 OpenGL. Суперкнига, 3-е издание. : Пер. с англ — М. : Издательский дом “Вильямс”, 2006. — 1040 с. : ил. — Парал. тит. англ. ISBN 5-8459-0998-8 (рус.) Авторы доступно излагают основные принципы, требуемые для разработки приложений, ис- пользующих OpenGL. Текст написан понятно, четко и без лишних отступлений, материал иллюст- рируется с помощью прекрасных примеров. Книга удачно структурирована, ее удобно использо- вать и как учебник, и как справочник; каждая глава завершается справочным разделом, в котором конспективно представлены все функции OpenGL, имеющие отношение к рассмотренной теме. Книга дополнена компакт-диском, содержащим как примеры, разобранные в тексте, так и проекты, рекомендуемые для самостоятельного изучения. Много внимания уделено тому, чтобы рекомен- дуемый код не просто работал, но работал эффективно, быстро надежно и на всех основных плат- формах. Примеры, приведенные в книге, будут работать во всех наиболее популярных операцион- ных системах — Linux, Mac OS и Windows. ББК 32.973.26-018.2.75 Все названия программных продуктов являются зарегистрированными торговыми марками соответствующих фирм Никакая часть настоящего издания ни в каких целях не может быть воспроизведена в какой бы то ни было фор- ме и какими бы то ни было средствами, будь то электронные или механические, включая фотокопирование и запись на магнитный носитель, если на это нет письменного разрешения издательства Sams Publishing Authorized translation from the English language edition published by Sams Publishing, Copyright © 2005 All rights reserved No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechani- cal, including photocopying, recording or by any information storage retneval system, without pennission from the Publisher Russian language editioirpubhsherf-by Williams Publishing House according to the Agreement with R&I Enterprises International, Copyright © 2006 . , • r ISBN 5-8459-0998-8 (pyc.) । r it j;: । < ip 1н ИНОГО © Издательский дом “Вильямс”, 2006 ISBN 0-672-32601-9 (англ)1 ,iilw |п-и1рг-( © by Sams Publishing, 2005 1441410
Оглавление I. Классический OpenGL 37 1. Введение в трехмерную графику и OpenGL 39 2. Используя OpenGL 61 3. Рисование в пространстве: геометрические примитивы и буферы 115 4. Геометрические преобразования: конвейер 177 5. Цвет, материалы и освещение: основы 225 6. Подробнее о цвете и материалах 285 7. Воспроизведение изображений с помощью OpenGL 311 8. Наложение текстуры: основы 379 9. Наложение текстуры: следующий шаг 433 10. Кривые и поверхности 471 11. Все о конвейере: быстрое прохождение геометрии 535 12. Интерактивная графика 593 II. OpenGL повсюду 619 13. Wiggle: OpenGL в системе Windows 621 14. OpenGL в системе MacOS X 709 15. GLX: OpenGL в системе Linux 747 III. OpenGL: следующее поколение 801 16. Буферные объекты: это ваша видеопамять; используйте ее! 803 17. Запросы о преградах: зачем делать больше работы, чем требуется? 827 18. Текстуры глубины и тени 841 19. Программируемый конвейер: это не тот OpenGL, который помнит ваш отец 863 20. Низкоуровневое затенение: кодирование в металле 877 21. Высокоуровневое затенение: жизненно необходимая “мелочь” 913 22. Затенение вершин: настраиваемое преобразование, освещение и генерация текстуры 947 Приложение А. Что еще почитать? 1021 Приложение Б. Словарь терминов 1023 Приложение В. OpenGL для внедренных систем 1027 Предметный указатель 1031
Содержание Об авторах 26 Посвящение 27 Благодарности 27 От издательства 29 Введение 29 Что читатель найдет в этой книге 31 Часть I. Классический OpenGL 31 Часть II OpenGL повсюду 33 Часть III. OpenGL- следующее поколение 33 Типографские особенности 35 Компакт-диск 35 Компоновка программ-примеров 35 Бросаем вызов 3D! 36 I. Классический OpenGL 37 1. Введение в трехмерную графику и OpenGL 39 О чем все это? 39 Краткая история компьютерной графики 39 Встреча с ЭЛТ 40 Переходя в трехмерный мир 41 2D + перспектива = 3D 41 Трехмерные артефакты 43 Обзор трехмерных эффектов 43 Перспектива 44 Цвет и затенение 44 Свет и тени 45 Наложение текстуры 46 Туман 46 Смешение и прозрачность 46 Защита от наложения 47 Области применения трехмерной графики 48 Трехмерный мир реального времени 48 Компьютерная графика не реального времени 50 Основные принципы трехмерного программирования 52 Немедленный режим и режим удержания (графы сцены) 52 Системы координат 53
Содержание Проектирование: получение 2D из 3D 56 Резюме 59 2. Используя OpenGL 61 Что такое OpenGL 61 Эволюция стандарта 62 Войны API 64 Как работает OpenGL? 70 Общие реализации 70 Аппаратные реализации 71 Конвейер 72 OpenGL — не язык, а программный интерфейс 73 Библиотеки и заголовки 73 Специфические особенности программного интерфейса 74 Типы данных 74 Правила именования 75 Независимость от платформы 77 Используя GLUT 77 Настройка среды программирования 78 Ваша первая программа 78 Рисование форм с помощью OpenGL 83 Анимация с помощью OpenGL и GLUT 90 Двойная буферизация 93 Машина состояний OpenGL 94 Запись и восстановление состояний 95 Ошибки OpenGL 96 То плохое, что случается с хорошим кодом 96 Определение версии 97 Получение подсказки с помощью glHint 98 Использование расширений 98 Проверка расширения 99 Чье это расширение? 100 Как в среде Windows работать с OpenGL после версии 1.1 100 Резюме 101 Справочная информация 102 glClearColor 102 gIDisable, glEnable 102 glFmish 103 gIFIush 103 gIGetXxxxv 104 glGetError 104 glGetString 104 glHint 105 gllsEnabled 105
8 Содержание glOrtho 106 gIPushAttrib/gIPopAttrib 106 gIRect 107 gIViewport 108 gluErrorString 108 glutCreateWindow 108 glutDisplayFunc 109 glutlnitDisplayMode 109 glutKeyboardFunc 110 glutMainLoop 110 glutMouseFunc 110 glutReshapeFunc 111 glutPostRedisplay 111 glutSolidTeapot, glutWireTeapot 111 glutSpecialFunc 112 glutSwapBuffers 113 glutTimerFunc 113 3. Рисование в пространстве: геометрические примитивы и буферы 115 Рисование точек в трехмерном пространстве 116 Установка трехмерной канвы 116 Трехмерная точка: вершина 118 Нарисуйте что-нибудь! 119 Рисование точек 119 Задание размера точки 122 Рисование линий в трехмерном пространстве 125 Ломаные и замкнутые линии 127 Аппроксимация кривых прямолинейными отрезками 127 Задание ширины линии 129 Фактура линии 130 Рисование треугольников в трехмерном пространстве 133 Треугольники’ ваши первые многоугольники 133 Обход 134 Ленты треугольников 135 Вееры треугольников 136 Построение сплошных объектов 136 Установка цвета многоугольника 139 Удаление скрытых поверхностей 140 Отбор: повышение производительности за счет скрытых поверхностей 141 Многоугольники: режимы 144 Другие примитивы 144 Четырехугольники 145 Многоугольники общего вида 145 Заполнение многоугольников, или возвращаясь к фактуре 146
Содержание 9 Правила построение многоугольников 149 Деление и стороны 151 Другие трюки с использованием буферов 152 Использование целей — буферов 153 Работа с буфером глубины 155 Разрезание с помощью ножниц 155 Использование буфера трафарета 157 Создание узоров-трафаретов 158 Резюме 161 Справочная информация 161 gIBegin 161 glClearDepth 162 glClearStencil 163 glCullFace 163 gIDepthFunc 164 gIDepthMask 165 gIDepthRange 165 glDrawBuffer 166 glEdgeFlag 167 glEnd 167 glFrontFace 168 gIGetPolygonStipple 168 gILineStipple 169 gILineWidth 169 gIPointSize 170 gIPolygonMode 171 gIPolygonOffset 172 gIPolygonStipple 172 glScissor 173 gIStencilFunc 173 gIStencilMask 174 gIStencilOp 174 gIVertex 176 4. Геометрические преобразования: конвейер 177 Это глава со страшной математикой? 177 Понимая преобразования 178 Координаты наблюдения 179 Преобразования наблюдения 180 Преобразования модели 180 Дуализм проекции модели 180 Преобразование проектирования 182 Преобразования поля просмотра 184 Матрица: математическая “валюта” трехмерной графики 184 Что такое матрица? 184 Конвейер преобразований 185
10 Содержание Матрица наблюдения модели 186 Единичная матрица 190 Стеки матриц 192 Пример ядра 193 Использование проекций 195 Ортографические проекции 195 Перспективная проекция 196 Пример 199 Нетривиальное умножение матриц 201 Загрузка матрицы 203 Выполнение собственных преобразований 204 Складывание преобразований 207 Создание в OpenGL движения с использованием камер и актеров 208 Система актеров 208 Углы Эйлера’ “Используй систему, Люк’” 209 Управление камерой 210 Собираем все вместе 211 Справочная информация 217 gIFrustum 217 glLoadldentity 218 glLoadMatnx 218 gILoadTransposeMatrix 219 glMatrixMode 219 glMultMatrix 220 glMultTransposeMatrix 220 glPopMatrix 221 gIPushMatnx 221 gIRotate 221 gIScale 222 gITranslate 222 gluLookAt 223 gluOrtho2D 223 gluPerspective 224 5. Цвет, материалы и освещение: основы 225 Что такое цвет? 226 Свет — это волна 226 Свет как частица 226 Детектор фотонов 228 Компьютер как генератор фотонов 228 Цветовоспроизводящая аппаратура ПК 229 Режимы отображения ПК 231 Разрешение экрана 231 Насыщенность цвета 231
Содержание 11 Использование цвета в OpenGL 233 Куб цвета 233 Задание цвета рисования 234 Затенение 235 Выбор модели затенения 237 Цвет в реальном мире 238 Рассеянный свет 239 Диффузный свет 239 Отраженный свет 239 Собираем все вместе 240 Материалы в реальном мире 241 Свойства материалов 241 Добавление света к материалам 242 Расчет эффектов рассеянного света 242 Диффузные и отражательные эффекты 243 Добавление света к сцене 243 Активизация освещения 243 Настройка модели освещения 244 Установка свойств материалов 245 Использование источников света 247 “Вверх" — это куда? 248 Нормали к поверхности 249 Задание нормали 249 Единичные нормали 251 Нахождение нормали 252 Устанавливаем источник света 254 Устанавливаем свойства материала 255 Устанавливаем многоугольники 255 Эффекты освещения 257 Отраженные блики 257 Отраженный свет 257 Зеркальное отражение 258 Коэффициент зеркального отражения 259 Усреднение нормалей 260 Собираем все вместе 262 Создание прожектора 263 Рисование прожектора 264 Тени 268 Что такое тень? 268 Код наложения проекции 270 Пример тени 271 Возвращаясь к миру сфер 274 Резюме 274 Справочная информация 275 glColor 275
12 Содержание glColorMask 277 glColorMaterial 277 gIGetLight 278 gIGetMaterial 278 gILight 280 gILightModel 281 gIMaterial 282 gINormal 283 gIShadeModel 284 6. Подробнее о цвете и материалах 285 Смешение 285 Объединение цветов 286 Изменение уравнения смешивания 289 Сглаживание 290 Туман 295 Буфер накопления 298 Другие операции с цветом 301 Маскировка цвета 301 Логические операции с цветом 301 Альфа-тест 301 Сглаживание 303 Резюме 303 Справочная информация 304 glAccum 304 gIBIendColor 304 gIBIendEquation 305 gIBIendFunc 305 gIBIendFuncSeparate 306 glClearAccum 306 glColorMask 307 gIFog 307 gILogicOp 308 glSampleCoverage 309 7. Воспроизведение изображений с помощью OpenGL 311 Растровые изображения 312 Пример битового образа 313 Упаковка пикселей 317 Пиксельные образы 318 Пиксельные форматы с упаковкой 319 Более яркий пример 320 Перемещение пикселей 324 Запись пикселей 325 Развлекаемся с помощью пикселей 327
Содержание 13 Изменение масштаба пикселей 332 Передача пикселей 334 Отображение пикселей 337 “Подмножество” воспроизведения изображений 339 Конвейер воспроизведения изображений 343 Резюме 354 Справочная информация 355 gIBitmap 355 glColorSubTable 355 glColorTable 356 glColorTableParameter 357 glConvolutionFilterl D 358 glConvolutionFilter2D 359 glConvolutionParameter 359 glCopyColorSubTable 360 glCopyColorTable 361 glCopyConvolutionFilterl D 361 glCopyConvolutionFilter2D 362 glCopyPixels 363 gIDrawPixels 363 gIGetConvolutionFilter 364 gIGetConvolutionParameter 365 gIGetColorTable 365 gIGetColorTableParameter 366 gIGetHistogram 366 gIGetHistogramParameter 367 gIGetMinmax 368 gIGetSeparableFilter 368 glHistogram 369 glMmmax 370 gIPixelMap 370 gIPixelStore 371 gIPixelTransfer 371 gIPixelZoom 372 gIRasterPos 373 gIReadPixels 374 gIResetHistogram 375 gIResetMinmax 375 glSeparableFilter2D 376 glWindowPos 377 8. Наложение текстуры: основы 379 Загрузка текстур 380 Использование буфера цвета 382 Обновление текстуры 383 Отображение текстур на геометрические объекты 384 Матрица текстуры 385
14 Содержание Пример наложения двухмерной текстуры 386 Текстурная среда 391 Параметры текстуры 393 Основная фильтрация 393 Намотка текстуры 395 Мультфильмы с текстурами 396 Сокращенные текстуры 400 Текстурные объекты 405 Управление несколькими текстурами 406 Резюме 413 Справочная информация 414 glAreTexturesResident 414 gIBindTexture 415 glCopyTexImage 415 glCopyTexSublmage 416 gIDeleteTextures 417 gIGenTextures 418 gIGetTexLevelParameter 419 gIGetTexParameter 419 gIGetTexImage 421 gllsTexture 421 gIPrioritizeTextures 422 gITexCoord 423 gITexEnv 424 gITexImage 426 gITexParameter 427 glTexSublmage 429 gluBuildMipmapLevels 430 gluBuildMipmaps 431 9. Наложение текстуры: следующий шаг 433 Дополнительный цвет 433 Анизотропная фильтрация 436 Сжатие текстуры 438 Техника сжатия текстуры 439 Загрузка сжатых текстур 440 Генерация текстурных координат 441 Отображение, линейное по объектам 446 Отображение, линейное относительно точки наблюдения 447 Сферическое отображение 448 Кубическое отображение 450 Множественная текстура 452 Множественные текстурные координаты 454 Пример использования нескольких текстур 454 Объединение текстур 459
Содержание 15 Резюме 461 Справочная информация 461 glActiveTexture 461 glClientActiveTexture 462 glCompressedTexImage 462 glCompressedTexSubimage 463 gIGetCompressedTexImage 464 gIMultiTexCoord 465 gISecondaryColor 467 gITexGen 468 10. Кривые и поверхности 471 Встроенные поверхности 472 Настройка квадратичных состояний 472 Рисование поверхностей второго порядка 474 Моделирование с помощью квадратичных поверхностей 478 Кривые и поверхности Безье 480 Параметрическое представление 480 Функции оценки 483 Трехмерная поверхность 488 NURBS 492 От кривых Безье к би-сплайнам 492 Узлы 493 Создание поверхности NURBS 493 Свойства NURBS 494 Определение поверхности 494 Подрезка 495 Кривые NURBS 498 Мозаика 498 Функция мозаики 499 Обратные вызовы функции составления мозаики 500 Задание информации о вершинах 501 Собираем все вместе 502 Резюме 507 Справочная информация 507 glEvalCoord 507 glEvalMesh 508 glEvalPomt 508 gIGetMap 509 gIMap 510 gIMapGrid 512 gluBeginCurve 513 gluBeginSurface 513 gluBeginTrim 513 gluCylinder 514 gluDeleteNurbsRenderer 514
16 Содержание gluDeleteQuadric 515 gluDeleteTess 515 gluDisk 515 gluEndCurve 516 glu EndSurface 516 gluEndTrim 517 gluGetNurbsProperty 517 gluLoadSamplingMatrices 518 gluNewNurbsRenderer 518 gluNewQuadric 519 gluNewTess 519 gluNurbsCallback 519 gluNurbsCurve 521 gluNurbsProperty 522 gluNurbsSurface 524 gluPartialDisk 525 gluPwICurve 526 gluQuadricCallback 527 gluQuadricDrawStyle 528 gluQuadricNormals 528 gluQuadricOrientation 529 gluQuadricTexture 530 gluSphere 530 gluTessBeginContour 531 gluTessBeginPolygon 531 gluVessCallback 531 gluTessEndContour 532 gluTessEndPolygon 532 gluTessProperty 534 gluTessVertex 534 11. Все о конвейере: быстрое прохождение геометрии 535 Сборка модели 535 Кусочки и части 536 Таблицы отображений 547 Пакетная обработка данных 548 Предварительно обработанные пакеты 549 Разъяснения по поводу таблиц отображения 551 Преобразование кода в таблицы отображения 551 Измерение производительности 552 Лучший пример 553 Массивы вершин 558 Загрузка геометрии 561 Активизация массивов 562 Где данные? 562 Рисуем! 564 Индексирование массивов вершин 565
Содержание 17 Резюме 577 Справочная информация 577 glArrayElement 577 glCallList 578 glCallLists 578 glColorPointer 579 gIDeleteLists 579 gIDrawArrays 580 glDrawElements 580 gIDrawRangeElements 581 glEdgeFlagPointer 582 glEnableClientState/gIDisableClientState 582 glEndList 583 gIFogCoordPointer 583 gIGenLists 584 gl Interleaved Arrays 584 gllsList 585 gIListBase 585 gIMultiDrawElements 587 gINewList 588 gINormalPointer 589 gISecondaryColorPointer 589 gITexCoordPointer 590 gIVertexPointer 591 12. Интерактивная графика 593 Выбор 594 Называем примитивы 594 Работа с режимом выбора 596 Буфер выбора 597 Отбор 598 Иерархический отбор 601 Обратная связь 604 Буфер обратной связи 605 Данные обратной связи 605 Маркеры Passthrough 606 Пример обратной связи 607 Помечаем объекты для обратной связи 607 Этап 1 • выбор объекта 609 Этап 2: реализация обратной связи 610 Резюме 612 Справочная информация 613 gIFeedbackBuffer 613 gllnitNames 614 gILoadName 614 gIPassThrough 614
18 Содержание gIPopName 615 gIPushName 615 glRenderMode 616 gISelectBuffer 616 gluPickMatrix 617 II. OpenGL повсюду 619 13. Wiggle: OpenGL в системе Windows 621 Реализации OpenGL в системе Windows 622 Общий OpenGL 622 ICD 623 MOD 623 Мини-драйвер 624 Расширенный OpenGL 624 Стандартная визуализация Windows 626 Контекст устройства GDI 626 Пиксельные форматы 628 Контекст визуализации OpenGL 635 Собираем все вместе 636 Создание окна 637 Использование контекста визуализации OpenGL 641 Другие сообщения 644 Палитры Windows 646 Согласование цветов 646 Разрешение конфликтов палитр 647 Создание палитры для OpenGL 649 Создание палитры и управление ею 654 OpenGL и шрифты Windows 655 Трехмерные шрифты и текст 655 Двухмерные шрифты и текст 658 Полноэкранная визуализация 660 Создание окна без рамки 660 Создание окна на весь экран 661 Многопоточная визуализация 663 OpenGL и расширения WGL 665 Простые расширения 665 Использование новых точек входа 666 Расширения WGL 667 Резюме 692 Справочная информация 692 ChoosePixelFormat 692 DescnbePixelFormat 694 GetPixelFormat 696 SetPixelFormat 696 SwapBuffers 697
Содержание 19 wgICreateContext 697 wgICreateLayerContext 698 wgICopyContext 698 wgIDeleteContext 699 wgIDescribeLayerPlane 699 wgIGetCurrentContext 702 wgIGetCurrentDC 702 wgIGetProcAddress 703 wglMakeCurrent 703 wglShareLists 704 wglSwapLayerBuffers 705 wglllseFontBitmaps 705 wglllseFontOutlines 706 14. OpenGL в системе MacOS X 709 Основы 709 Каркас 710 Использование программного интерфейса GLUT 710 Использование программных интерфейсов AGL и Carbon 710 Пиксельные форматы 711 Управление контекстами 711 Выполнение визуализации с двойной буферизацией 713 Первая программа AGL 713 Использование растровых шрифтов 722 Использование программного интерфейса Cocoa 733 Класс NSOpenGL 733 Первая программа Cocoa 736 Резюме 743 Справочная информация 743 aglChoosePixelFormat 743 agICreateContext 744 agIDestroyContext 744 aglSetCurrentContext 745 aglSetDrawable 745 aglSwapBuffers 746 aglUseFont 746 15. GLX: OpenGL в системе Linux 747 ОСНОВЫ 747 Использование библиотек OpenGL и Х11 747 Использование библиотеки GLUT 749 OpenGL в системе Linux 750 Эмуляция OpenGL' Mesa 750 Расширение OpenGL для X Window System 751 Основы X Window System 751 Выбор режима визуализации 751
20 Содержание Управление контекстами OpenGL 753 Создание окна OpenGL 753 Окна с двойной буферизацией 754 Собираем все вместе 754 Создание растровых шрифтов для OpenGL 762 Закадровая визуализация 770 Использование пиксельного отображения GLX 770 Использование Р-буферов 776 Использование библиотеки Motif 781 GLwDrawingArea и GLwMDrawingArea: элементы управления OpenGL 781 Обратные вызовы 782 Функции 784 Собираем все вместе 784 Резюме 793 Справочная информация 793 glXChooseFBConfig 793 glXChooseVisual 793 glXCreateContext 794 glXCreateGLXPixmap 794 glXCreateNewContext 795 glXCreatePbuffer 796 gIXDestroyContext 796 gIXDestroyGLXPixmap 796 gIXDestroyPbuffer 797 glXGetFBConfigs 797 glXGetVisualFromFBConfig 798 gIXMakeCurrent 798 gIXSwapBuffers 799 gIXUseXFont 799 III. OpenGL: следующее поколение 801 16. Буферные объекты: это ваша видеопамять; используйте ее! 803 Для начала нужны массивы вершин 804 Генерация сферического облака частиц 805 Активизация массивов вершин 806 Побольше сфер, пожалуйста1 806 Перемещение в буферные объекты 809 Управление буферными объектами 810 Визуализация с помощью буферных объектов 811 Загрузка данных в буферные объекты 811 Копирование данных в буферный объект 811 Непосредственное отображение буферных объектов 812 Несказанное 817 Резюме 818
Содержание 21 Справочная информация 818 glBindBuffer 818 gIBufferData 819 gIBufferSubData 820 gIDeleteBuffers 821 gIGenBuffers 821 gIGetBufferParameteriv 822 gIGetBufferPointerv 822 gIGetBufferSubData 823 gllsBuffer 824 gIMapBuffer 824 glUnmapBuffer 825 17. Запросы о преградах: зачем делать больше работы, чем требуется? 827 Мир до запросов о преградах 827 Рамки 831 Запрос объекта запроса 834 Резюме 837 Справочная информация 837 glBeginQuery 837 gIDeleteQueries 838 glEndQuery 838 glGenQuenes 839 glGetQueryiv 839 glGetQueryObject 840 gllsQuery 840 18. Текстуры глубины и тени 841 Да будет свет! 842 Подгоняем сцену под окно 842 Без излишеств 843 Новый тип текстуры 846 Сперва рисуются тени?! 846 И был свет 847 Проектирование карты тени: “зачем'?” 848 Проектирование карты тени- "как?” 849 Сравнение с тенью 851 Два из трех — неплохо 857 Несколько слов о смещении многоугольника 857 Резюме 858 Справочная информация 859 glAlphaFunc 859 glColorMask 860
22 Содержание glCopyTexSublmage 860 g I Polygon Offset 861 19. Программируемый конвейер: это не тот OpenGL, который помнит ваш отец 863 Начнем со старого 864 Фиксированная обработка вершин 864 Фиксированная обработка фрагментов 866 Добавим нового 868 Программируемые шейдеры вершин 869 “Клей" конвейера с фиксированными функциональными возможностями 871 Программируемые шейдеры фрагментов 872 Введение в расширения шейдеров 873 Низкоуровневые расширения 873 Высокоуровневые расширения 875 Резюме 876 20. Низкоуровневое затенение: кодирование в металле 877 Управление низкоуровневыми шейдерами 878 Создание и связывание шейдеров 878 Загрузка шейдеров 878 Удаление шейдеров 880 Настройка расширений 880 Наборы команд 881 Общие команды 882 Команды, существующие только для вершин 882 Команды, существующие только для фрагментов 884 Типы переменных 884 Временные переменные 885 Параметры 886 Атрибуты 888 Выходные аргументы 890 Псевдонимы 892 Адреса 892 Модификаторы входных и выходных атрибутов 893 Инверсия входа 893 Настройка входа по адресам 893 Маска записи выходного аргумента 893 Ограничение выходного аргумента 894 Потребление ресурсов и запросы 894 Ограничения программы грамматического разбора 894 “Родные пределы” 896 Другие запросы 898 Опции шейдера 898
Содержание 23 Опция инвариантности относительно положения (шейдер вершин) 898 Опция наложение тумана (шейдер фрагментов) 898 Подсказка точности (шейдер фрагментов) 899 Резюме 899 Справочная информация 899 gIBindProgramARB 899 gIDeleteProgramsARB 900 gIDisableVertexAttribArrayARB 900 glEnableVertexAttnbArrayARB 901 gIGenProgramsARB 901 gIGetProgramivARB 901 glGetProgramEnvParameter*vARB 904 glGetProgramLocalParameter*vARB 904 glGetProgramStringARB 905 glGetVertexAttrib*vARB 906 glGetVertexAttribPomtervARB 906 gllsProgramARB 907 glProgramEnvParameter*ARB 907 glProgramLocalParameter*ARB 908 glProgramStnngARB 909 glVertexAttnb*ARB 910 glVertexAttribPomterARB 912 21. Высокоуровневое затенение: жизненно необходимая “мелочь” 913 Управление высокоуровневыми шейдерами 914 Объекты шейдера 914 Создание и удаление 91 Объекты программы 916 Настройка расширений 918 Переменные 919 Стандартные типы 920 Структуры 920 Массивы 921 Спецификаторы 922 Встроенные переменные 922 Выражения 922 Операторы 923 Доступ к массиву 924 Конструкторы 925 Селекторы компонентов 926 Поток управления 927 Циклы 927 if/else 927 discard 928 Функции 928
24 Содержание Резюме 931 Справочная информация 931 glAttachObjectARB 931 glBindAttnbLocationARB 932 glCompileShaderARB 932 glCreateProgramObjectARB 933 glCreateShaderObjectARB 933 gIDeleteObjectARB 934 gIDetachObjectARB 934 gIGetActiveAttribARB 934 gIGetActiveUniformARB 936 gIGetAttachedObjectsARB 937 glGetAttribLocationARB 938 gIGetHandleARB 938 glGetlnfoLogARB 939 glGetObjectParameter*vARB 939 gIGetShaderSourceARB 941 gIGetUniform'vARB 941 gIGetUniformLocationARB 942 gILinkProgramARB 942 gIShaderSourceARB 943 glUniform*ARB 943 glUseProgramObjectARB 945 gIValidateProgramARB 946 22. Затенение вершин: настраиваемое преобразование, освещение и генерация текстуры 947 Пробуем воду 947 Диффузное освещение 949 Отраженный свет 952 Улучшенное отражение 955 Туман 962 Размер точки 966 Пользовательские преобразования вершин 968 Смешение вершин 970 Резюме 975 23. Затенение фрагментов: поддерживаем обработку пикселей 977 Преобразование цвета 978 Полутона 978 Сепия 979 Инверсия 980 Моделирование теплового излучения 982 Реализация тумана для фрагментов 984 Обработка изображений 986 Размывание 986
Содержание 25 Усиление резкости 989 Расширение и эрозия 991 Детектирование краев 993 Освещение 995 Диффузное освещение 996 Несколько источников отраженного света 999 Процедурное наложение текстуры 1004 Шахматная текстура 1004 Текстура “пляжный мяч” 1009 Текстура “игрушечный мяч” 1014 Резюме 1019 Приложение А. Что еще почитать? 1021 Другие хорошие книги по OpenGL 1021 Книги по трехмерной графике 1021 Web-сайты 1021 Приложение Б. Словарь терминов 1023 Приложение В. OpenGL для внедренных систем 1027 Сокращение количества типов данных 1027 Ушли совсем 1028 Сильно сокращенные функциональные возможности 1029 Наложение текстуры 1029 Растровые операции 1029 Освещение 1029 Вывод 1030
Об авторах Ричард С. Райт мл. (Richard S. Wright, Jr.) работает с OpenGL около 10 лет (с мо- мента появления этого интерфейса на платформе Windows) и преподает программи- рование игр с помощью OpenGL в школе Full Sail (Орландо, Флорида). В настоящее время Ричард является президентом Starstone Software Systems, Inc., где занимается разработкой на основе OpenGL мультимедийных программных имитаторов для ПК и Macintosh. Работая ранее в Real 3D/Lockheed Martin, Ричард внес вклад в разработку и тести- рование спецификации OpenGL 1.2. После этого Ричард работал в сфере визуализа- ции многомерных баз данных, разработки игр, медицины (визуализация диагностики) и астрономии (имитация космоса). Впервые Ричард начал программировать в 1978 году на перфокарте, учась в то вре- мя в восьмом классе. В возрасте 16 лет родители разрешили ему купить компьютер, и менее чем через год он продал свою первую компьютерную программу (причем это была программа с графикой!). После окончания средней школы его первой работой стало обучение программированию и компьютерной грамотности сотрудников мест- ной компании. Он изучал информатику и электротехнику в Speed Scientific School университета Луисвилля и перевалил за половину срока обучения, когда его “позвала карьера”, и он отправился во Флориду. Ныне он, уроженец Луисвилля (Кентукки), с женой и тремя детьми живет между Орландо и Дейтона Бич1. Свободное от работы время Ричард уделяет астрономии и преподаванию в воскресной школе. Бенджамин Липчак (Benjamin Lipchak) закончил Уорчестерский политехниче- ский институт по двум профильным дисциплинам — технический набор и информа- тика. “Почему человек, имеющий степень по информатике, вдруг становится писа- телем?” Этот вопрос задали Бенджу, когда он проходил собеседование на должность наборщика в Digital Equipment Corporation. Собеседование продлилось дольше, чем было запланировано, и вечером того же дня он получил работу в команде разработки программного обеспечения, отвечающей за драйверы OpenGL для Alpha Workstation (DEC). После того как DEC была куплена компанией Compaq и большая часть гра- фической группы уволена, Бендж уволился в пятницу, а в понедельник вернулся в более привлекательном (с точки зрения контракта) статусе. Несмотря на новый ста- тус Бендж сохранил за собой лидирующие позиции в коллективе, выезжая на места и управляя проектами, связанными с драйверами OpenGL для PowerStorm UNIX и NT. Через два года во время Internet-бума Бендж основал фирму по поиску изображе- ний в Internet. Последовавшее банкротство предприятия привело к тому, что Бендж на коленях вернулся к своей первой любви — OpenGL, умоляя принять его обратно. Он был прощен, и примирение обрело форму контракта с ATI Research в Мальборо (Массачусетс). В последнее время основное поле деятельности Бенджа — фрагмен- тарные шейдеры, встраиваемые в драйверы OpenGL для видеокарт Radeon. В свое время Бендж занимал пост руководителя рабочей группы OpenGL ARB, занимавшей- ся разработкой расширения GL_ARB_fragment_prograin, и участвовал в работах по OpenGL Shading Language (ATI). Редкие моменты свободного времени Бендж стара- Города во Флориде — Примеч перев
Введение 27 ется проводить на воздухе, посвящая их туризму и хождению на байдарке. Кроме того, он заведует независимой студией звукозаписи Wachusett Records, специализи- рующейся на сольных фортепианных записях. Посвящение Посвящается памяти Ричарда С. Райта ст. Ричард С. Райт-мл. Моим родителям Дороти и Майку, давшим мне генетический материал высочай- шего качества. Благодаря им я стучал пальцами по клавиатуре TRS-80 в том возрасте, когда должен был бы стучать палкой по деревьям. Грубые монохромные изображения размером 128 х 48 пикселей были началом долгого пути. Мама и папа, спасибо за то, что видели будущее в этих неуклюжих рисунках. Бенджамин Нейсон Липчак Благодарности С чего начать? Я благодарен Богу за все возможности, предоставленные мне на протяжении жизни, и за то, что вразумил воспользоваться ими. Я благодарен своей жене и своей семье за то, что они были рядом в наиболее трудный период моей жизни, когда я в третий раз пытался опубликовать эту книгу. Ли-Энн, Сара, Стефан и Алекс были заброшены, обещания нарушались, уик-энды пропадали, недовольство росло. Папочка немного помешался, но когда все закончилось, они по-прежнему ждали. Книга должна была бы выйти под их именами, а не под моим. О сотрудниках издательства Sams у меня остались самые приятные воспоминания. Хотелось бы поблагодарить Лоретту Йетс (Loretta Yates) за снисходительное отноше- ние к срыву сроков и поздравить ее с рождением “реального” ребенка; это произошло сразу после завершения работы над “детищем”, которое вы держите в руках. Над тем, чтобы сделать меня похожим на литературного и технического гения (каким я в дей- ствительности не являюсь), очень много потрудились редактор Син Диксон (Sean Dixon), технический рецензент Ник Хэмел (Nick Haemel), редактор рукописи Чак Хатчинсон (Chuck Hutchinson) и редактор проекта Дэн Кнотт (Dan Knott). Отдельная благодарность Бенджамину Липчаку (Benjamin Lipchak) и ATI Tech- nologies, Inc. Бенджамин присоединился ко мне позже как соавтор и выручил в труд- ных для меня местах. Спасибо ATI за то, что позволила Бенджу потратить часть своего рабочего времени, за возможность использования кодов-примеров и за до- ступ на ранних этапах к драйверам OpenGL. Спасибо также за вашу приверженность стандарту OpenGL. Наконец, огромное спасибо Full Sail за поддержку в последние несколько лет, поз- волившую мне преподавать OpenGL с частичной занятостью. Я приходил на работу на несколько часов, говорил о том, что мне нравилось, а слушатели должны были вести себя так, будто наслаждаются этим процессом и внимательно следят за ним. И за это мне еще и платили! Многие в Full Sail очень мне помогли — либо непосред- ственно с книгой, либо поддерживая и облегчая мне жизнь. Я благодарю Роба Катто
28 Введение (Rob Catto) за понимание и поддержку многих идей. Тони Уайтекер (Tony Whitaker) предоставил несколько студенческих проектов для компакт-диска и помог проверить работоспособность кода на Linux. Спасибо Биллу Гелбрезу (Bill Galbreath), Ричарду Левинсу (Richard Levins) и Стефану Картеру (Stephen Carter) за демонстрационные материалы. В заключение я хотел бы поблагодарить Троя Хамфриса (Troy Humphreys) и Бреда Леффлера (Brad Leffler) за то, что они — два лучших специалиста, к которым я мог обращаться, — практически не делали мне замечаний при различных авариях, вызванных по неосторожности. Ричард С. Райт-мл. Вначале мне хотелось бы поблагодарить коллег из ATI, бескорыстно тративших свое драгоценное время и дававших советы при редактировании текстов, гонорар за которые получал я. Эти ребята — мастера OpenGL, и я счастлив работать рядом с ними (или, как иногда бывало, за сотни миль от них). В частности, хотелось бы поблагода- рить Дена Гинзбурга (Dan Ginsburg), Рика Хаммерстоуна (Rick Hammerstone), Эвана Харта (Evan Hart), Билла Лайси-Кейна (Bill Licea-Kane), Гленна Ортнера (Glenn Ort- ner) и Джереми Сандмела (Jeremy Sandmel). Спасибо техническому редактору Нику Хэмелу (Nick Haemel), также работающему в ATI, за беспристрастное чтение моего материала. Хотелось бы также поблагодарить руководителя и друга Кэрри Уилкинсо- на (Kerry Wilkinson) и ATI за то, что нашли время и предоставили мне оборудование для работы над этой книгой. Я надеюсь, что результат получился взаимовыгодным. Ричард, спасибо за возможность работать вместе с тобой над этим проектом. Я горд, что мое имя указано перед твоим и приветствую любое дальнейшее сотруд- ничество Райта и Липчака. Выражаю признательность команде редакторов и другим сотрудникам Sams Pub- lishing за преобразование написанного мною текста в то, чем я могу гордиться. Спасибо профессорам WPI Майку Геннерту (Mike Gennert), Карену Лемоуну (Karen Lemone), Джону Тримбуру (John Trimbur), Сьюзан Вик (Susan Vick), Мэтту Уорду (Matt Ward), Норме Уиттелс (Norm Wittels) и другим за основательные зна- ния, на которые я опирался. Искренне признателен всем моим друзьям из GweepNet за возможность расслабиться за компьютерными играми по локальной сети, когда я начинал “сгорать” в процессе написания книги. Отдельное спасибо Райану Беттс (Ryan Betts) за придуманное им название главы 212. Мою семью: Бет (Beth), Тима (Tim), Алисию (Alicia), Мэтта (Matt) и Джен (Jen) я благодарю за снисходительное отношение к тому, что я приклеивался к экрану лэптопа на долгие зимние месяцы. Брат Пол (Paul), твои успехи будили во мне дух здорового соперничества. Сестра Мэгги (Maggie), ты радовала меня всякий раз, когда я тебя видел. Вы оба являетесь предметом моей гордости. Хочу поблагодарить Джессику (Jessica); она была настоль- ко занята, что мои редкие походы на пиво становились тяжелейшим испытанием. Когда выдавался момент перевести дух, мы понимали, что есть время для еще одного побочного проекта. Может быть, когда-нибудь мы еще поработаем вместе! Бенджамин Липчак 2 В оригинале — “High-Level Shading The Real Slim Shader” — Примеч nepee
Введение 29 От издательства Вы, читатель этой книги, и есть главный ее критик. Мы ценим ваше мнение и хотим знать, что было сделано нами правильно, что можно было сделать лучше и что еще вы хотели бы увидеть изданным нами. Нам интересно услышать и любые другие замечания, которые вам хотелось бы высказать в наш адрес. Мы ждем ваших комментариев и надеемся на них. Вы можете прислать нам бу- мажное или электронное письмо, либо просто посетить наш Web-сервер и оставить свои замечания там. Одним словом, любым удобным для вас способом дайте нам знать, нравится или нет вам эта книга, а также выскажите свое мнение о том, как сделать наши книги более интересными для вас. Посылая письмо или сообщение, не забудьте указать название книги и ее авто- ров, а также ваш обратный адрес. Мы внимательно ознакомимся с вашим мнением и обязательно учтем его при отборе и подготовке к изданию последующих книг. E-mail: info@williamspublishing. com WWW: http://www.williamspublishing.com Информация для писем из: России: 115419, Москва, а/я 783 Украины: 03150, Киев, а/я 152 Введение Должен кое в чем признаться. Впервые я услышал об OpenGL в 1992 на конферен- ции разработчиков Win32 в Сан-Франциско. Система Windows NT 3.1 была на стадии ранней бета-версии (или поздней альфа-версии), и присутствовало множество произ- водителей, заверявших в своей будущей поддержке новой впечатляющей графической технологии. В их числе была компания Silicon Graphics, Inc. (SGI). Представители SGI представили свои графические рабочие станции и воспроизводили демонстра- ционные ролики спецэффектов из некоторых популярных фильмов. Основной же составляющей их выставочного стенда была реклама нового графического стандар- та, названного OpenGL. Он был основан на принадлежавшем SGI стандарте IRIS GL и заявлялся как стандарт графики. Показательным было то, что Microsoft на этой конференции заявила о будущей поддержке OpenGL в Windows NT. Чтобы составить собственное мнение об OpenGL, мне пришлось дождаться бета- версии NT 3.5. Первые заставки, основанные на OpenGL, всего лишь несмело каса- лись области возможного с данным графическим интерфейсом. Подобно множеству людей я продирался через написанные Microsoft файлы справки и купил копию “Ру- ководства по программированию в OpenGL” (“OpenGL Programming Guide”; сейчас большинство называет его просто “Красной книгой”). Эта “Красная книга” не была учебником в классическом смысле этого слова, она предполагала наличие обширных знаний по предмету, которых я просто не имел. Вернемся к признанию, которое я обещал сделать. Так как я все-таки освоил OpenGL? Я учился в процессе написания этой книги. Правда: первое издание Су- перкниги OpenGL — моя попытка в сжатые сроки обучиться созданию трехмерной графики! Каким-то чудом у меня это получилось, и в 1996 году вышло первое из- дание книги, которую вы держите в руках. Самообучение OpenGL с использованием
30 Введение множества подобранных без определенной системы источников позволило мне лучше объяснить данный программный интерфейс другим, причем так, что, похоже, это мно- гим понравилось. Весь проект чуть не заглох на полпути, когда Waite Group Press было куплено другим издательством. Предыдущий владелец, Митчел Вейт (Mitchell Waite), отстоял эту книгу, убедив всех, что OpenGL — это “гениальная идея” в компьютерной графике. В доказательство его мнения первый тираж книги был раскуплен, не успев даже попасть на склад, из-за чего потребовалось выпускать дополнительный тираж. Три года спустя основным продуктом в данной отрасли стали графические карты с ускорителями трехмерной графики, которыми оборудовались даже самые дешевые ПК. Начались и завершились “войны API3”, политические баталии между Microsoft и SGI; OpenGL прочно занял свое место в мире ПК; аппаратные ускорители трехмер- ной графики стали такими же распространенными, как и CD-ROM и звуковые карты. Работая в Lockheed Martin/Real 3D, я даже смог направить свою карьеру в русло OpenGL и внести небольшую лепту в разработку спецификации OpenGL 1.2. Второе издание книги, вышедшее в конце 1999, было существенно расширено и исправлено. Мы даже попытались, используя библиотеку GLUT, обеспечить работоспособность всех программ-примеров на не-Windows платформах. Теперь, по прошествии пяти лет (восьми, если считать от первого издания), ваше- му вниманию предлагается следующее (третье) издание книги. Несомненно, в насто- ящее время OpenGL является основным межплатформенным программным интер- фейсом приложений компьютерной графики. Превосходная устойчивость и произво- дительность OpenGL доступны теперь даже на самых дешевых персональных ком- пьютерах. Кроме того, OpenGL является стандартом для операционных систем UNIX и Linux, а компания Apple сделала OpenGL основной технологией новой операцион- ной системы MacOS X. OpenGL даже начал вторгаться на чужую территорию, когда новая спецификация OpenGL ES вошла в сферу внедренных и мобильных систем. Кто мог пять лет назад предполагать, что в Quake можно играть на мобильном телефоне? В наше время очень впечатляет то, что даже портативные компьютеры снабже- ны ускорителями трехмерной графики, и OpenGL действительно стал вездесущим стандартом, представленным на основных вычислительных платформах. Еще боль- ше впечатляет продолжающаяся эволюция аппаратного обеспечения компьютерной графики. В настоящее время большая часть такого аппаратного обеспечения является программируемым, и OpenGL даже имеет собственный язык затенения, позволяю- щий создавать удивительно реалистичную графику, о которой нельзя было и мечтать в предыдущем столетии. Я рад, что в третьем издании в качестве соавтора ко мне присоединился Бенджа- мин Липчак. Он является членом групп ARB, отвечающих за данный аспект OpenGL, так что он — один из самых квалифицированных специалистов по данному вопросу. Бенджу принадлежат главы, в которых рассматриваются шейдеры OpenGL. Кроме того, мы полностью отказались от ориентации на Microsoft (как было в пер- вом издании) и использовали более универсальный (межплатформенный) подход. Все примеры в книге проверялись в системах Windows, MacOS X и по крайней мере од- ной из версий Linux. Мало того, были написаны даже отдельные главы, посвященные использованию OpenGL с родными приложениями данных систем. Application Piogiamnnng Interface — программный интерфейс приложения
Введение 31 Что читатель найдет в этой книге OpenGL. Суперкнига разделена на три части. В первой мы рассматриваем то, что считается классическим OpenGL. Это стандартная конвейерная схема работы, явля- ющаяся отличительной чертой OpenGL. Многие главы этой части существенно уве- личились со времени выхода в свет второго издания, поскольку OpenGL несколько раз перерабатывался со времени версии 1.2. С программированием в рамках фикси- рованного конвейера мы будем работать очень много; этой простой модели програм- мирования верны многие программисты. Из первой части книги вы узнаете основы программирования трехмерной графики реального времени с помощью OpenGL. Вы обучитесь строить программы, которые используют OpenGL, увидите, как настраивать собственную среду трехмерной визу- ализации и создавать основные объекты, освещая и затеняя их. Затем мы глубже ис- следуем применение OpenGL и некоторых его нетривиальных особенностей, а также различные специальные эффекты. Данные главы являются введением в программиро- вание трехмерной графики с помощью OpenGL и предлагают концептуальную основу, на которую опираются более сложные элементы, рассмотренные далее в книге. Вторая часть состоит из трех глав, в которых собрана специфическая информация, касающаяся применения OpenGL в трех основных семействах операционных систем: Windows, MacOS X и Linux/UNIX. Наконец, в третьей части рассмотрены новейшие возможности не только OpenGL, но и распространенного аппаратного обеспечения трехмерной графики. В частности, основной особенностью OpenGL 2.0 является язык OpenGL Shading Language, который представляет собой наибольшее за последние годы достижение в сфере компьютерной графики. Часть I. Классический OpenGL Глава 1. Введение в трехмерную графику и OpenGL Эта вводная глава написана для новичков в сфере трехмерной графики. В ней вводятся основные концепции и представляется необходимый словарь. Глава 2. Используя OpenGL В данной главе приводятся сведения о том, чем является OpenGL, откуда он при- шел, куда направляется. Вы напишете первую программу с использованием OpenGL, узнаете, какие заголовки и библиотеки вам требуются, как настраивать среду, а также познакомитесь с распространенными договоренностями, которые помогут запомнить вызовы функций OpenGL. Кроме того, мы рассмотрим конечный автомат OpenGL и механизм обработки ошибок. Глава 3. Рисование в пространстве: геометрические примитивы и буферы Здесь представлены стандартные компоновочные блоки программирования трехмер- ной графики. По сути, вы научитесь сообщать компьютеру, что с помощью OpenGL требуется создать трехмерный объект. Кроме того, вы узнаете основы удаления скры- тых поверхностей и способы использования буфера трафарета.
32 Введение Глава 4. Геометрические преобразования: конвейер Теперь вы можете создавать трехмерные формы в виртуальном мире, но как добиться, чтобы они двигались? Как переместить наблюдателя? Ответы на эти вопросы вы получите в данной главе. Глава 5. Цвет, материалы и освещение: основы В этой главе вы научитесь создавать трехмерные “контуры” и раскрашивать их. Также вы узнаете, как наделить объект свойствами определенного материала и придать ему реалистичный вид. Глава 6. Подробнее о цвете и материалах Пришло время рассмотреть смешение объектов с фоном с целью создания прозрач- ных объектов. Кроме того вы узнаете о специальных эффектах, создание которых возможно за счет работы с туманом и буфером накопления. Глава 7. Воспроизведение изображений с помощью OpenGL Эта глава посвящена манипуляциям с информацией об изображении. В частности, рассмотрено считывание файла TGA и отображение его в окне. Также вы узнаете о некоторых мощных возможностях OpenGL, связанных с обработкой изображений. Глава 8. Наложение текстуры: основы Наложение текстуры является одной из важнейших функций любого набора для рабо- ты с трехмерной графикой. Из этой главы вы узнаете, как оборачивать изображением многоугольники, и как одновременно загружать несколько текстур и управлять ими. Глава 9. Наложение текстуры: следующий шаг В этой главе рассказывается, как автоматически генерировать текстурные координаты, использовать сложные режимы фильтрации и встроенную аппаратную поддержку сжатия текстуры. Кроме того, вы узнаете о мощных функциональных возможностях схемы объединения текстур OpenGL. Глава 10. Кривые и поверхности Мощнейшим геометрическим строительным блоком является простой треугольник. В этой главе предлагаются инструменты управления этой простой, но мощной струк- турой. Вы узнаете о встроенных функциях генерации поверхностей второго порядка и способах использования автоматического мозаичного представления с целью раз- биения сложных форм на меньшие, более удобные при обработке фрагменты. Кроме того, вы познакомитесь со вспомогательными функциями, рассчитывающими кривые и поверхности Безье и NURBS. Эти функции позволяют задавать сложные формы с помощью поразительно короткого кода.
Введение 33 Глава 11. Все о конвейере: быстрое прохождение геометрии В этой главе показано, как строить сложные трехмерные объекты из меньших, ме- нее сложных трехмерных объектов. Здесь вводятся таблицы отображения и массивы вершин OpenGL, позволяющие повысить производительность и удобно организовать ваши модели. Кроме того, вы научитесь анализировать большие сложные модели и решать, как их представить наилучшим способом. Глава 12. Интерактивная графика В этой главе объясняются основные возможности OpenGL: выбор и обратная связь. Соответствующие группы функций позволяют пользователю взаимодействовать с объектами на сцене, а также узнавать подробности визуализации объекта на сцене. Часть II. OpenGL повсюду Глава 13. Wiggle: OpenGL в системе Windows В данной главе рассказывается, как написать реальную программу для Windows, в которой используется OpenGL. Вы узнаете о функциях Microsoft, которые связы- вают код визуализации OpenGL с аппаратным контекстом Windows. Кроме того, вы узнаете, как отвечать на сообщения Windows. Глава 14. OpenGL в системе MacOS X Изучив эту главу, вы научитесь использовать OpenGL в приложениях, родных для MacOS X. На примерах будет показано, как начать работу в Carbon или Cocoa, ис- пользуя среду разработки Xcode. Глава 15. GLX: OpenGL в системе Linux В этой главе обсуждается GLX — расширение OpenGL, используемое для поддержки приложений OpenGL через систему X Window System в Unix и Linux. Вы научитесь создавать и управлять контекстом OpenGL, а также создавать области рисования OpenGL с помощью распространенных наборов GUI. Часть III. OpenGL: следующее поколение Глава 16. Объекты в буфере: это ваша видеопамять; используйте ее! Прочитав эту главу, вы узнаете о свойствах буферных объектов OpenGL 1.5. Буфер- ные объекты позволяют хранить информацию массива вершин в памяти, к которой можно весьма эффективно обращаться, — локальной видеопамяти или системной памяти AGP. Глава 17. Запросы о преградах: зачем делать больше работы, чем требуется Здесь вы узнаете о механизме запросов о преградах OpenGL 1 5 Данная возможность позволяет выполнять недорогую проверку-визуализацию объектов на сцене, чтобы
34 Введение выяснить, не скрыты ли объекты другими элементами сцены, потому что в таком случае можно сэкономить время и не рисовать их. Глава 18. Текстуры глубины и тени Здесь изучаются текстуры глубины и сравнение с тенью, реализованные в OpenGL 1.4. Вы узнаете, как в реальном времени вводить на сцену тени независимо от сложно- сти геометрии. Глава 19. Программируемый конвейер: это не тот OpenGL, который помнит ваш отец Хватит изучать старое, займемся новым. В начале этой главы напоминаются ос- новные сведения о конвейере с фиксированными функциональными возможностями, а затем вводятся новые программируемые каскады обработки вершин и фрагмен- тов Возможность программирования позволяет настроить визуализацию по вашему желанию так, как нельзя было сделать раньше. Глава 20. Низкоуровневое затенение: кодирование в металле В этой главе вы познакомитесь с низкоуровневыми расширениями шейдеров: ARB_vertex__program и ARB_fragment__program. Вы можете использовать их для настройки визуализации с помощью программ-шейдеров, написанных на языке, на- поминающем ассемблер, и предлагающих полный контроль над возможностями ап- паратного обеспечения Глава 21. Высокоуровневое затенение: жизненно необходимая “мелочь” Здесь мы обсуждаем OpenGL Shading Language — высокоуровневый аналог низко- уровневых расширений. Язык GLSL похож на С и предлагает мощные функциональ- ные возможности и повышенную производительность. Глава 22. Затенение вершин: настраиваемое преобразование, освещение и генерация текстуры В этой главе иллюстрируется использование шейдеров вершин. Рассмотрено мно- жество примеров, включающих применение освещения, тумана, растяжения/сжатия и натягивания кожи. Глава 23. Затенение фрагментов: поддерживаем обработку пикселей И снова вы учитесь на примерах — на этот раз иллюстрирующих фрагментарные шейдеры. В этих примерах рассмотрено освещение, преобразование цвета, обработ- ка изображений и процедурное наложение текстуры Кое-где применяются шейде- ры вершин, эти примеры ближе к реальности, поскольку схемы затенения вершин и фрагментов вам часто придется использовать совместно.
Введение 35 Типографские особенности В книге используются следующие типографские особенности • Строки кода, команды, операторы, переменные и текст, который вы набираете или видите на экране, представлены моноширинным шрифтом. • Заполнители в описании синтаксиса набраны наклонным моноширинным шриф- том. При программировании заполнитель заменяется действительными именами файлов, параметрами или другими элементами, представляющими заполнители. • Курсивом выделены технические термины при первом появлении и определении. Компакт-диск Прилагаемый к данной книге компакт-диск заполнен программами-примерами, на- борами инструментов, исходными кодами и документацией — всем, что может вам понадобиться! Мы отбирали материал для этого диска в ходе всей работы над книгой вплоть до момента ее печати, поэтому внимательно прочтите файл readme. txt в кор- невой папке — в нем перечислено, какой материал вошел в окончательную версию. Компакт-диск имеет определенную структуру. В корневой папке находятся следу- ющие папки. • \Examples — внутри находятся папки, отвечающие главам данной книги с при- мерами кода. Каждая программа имеет собственное имя (а не просто порядковый номер), поэтому вы легко можете найти на компакт-диске требуемую программу и запустить код, который вас заинтересовал. • \Tools — набор инструментов и библиотек от сторонних производителей. Каждый элемент помещен в отдельную папку и снабжен документацией от производителя Некоторые из этих средств (в частности, GLUT) используются в программах- примерах, приводимых в книге. • \Demos — здесь расположен набор демо-программ OpenGL. Во всех этих про- граммах демонстрируются возможности визуализации OpenGL. Некоторые из них распространяются свободно, другие являются коммерческими демо-версиями. По вопросам поддержки, касающимся материала на компакт-диске, обращайтесь в Sams Publishing (www.samspublishing.com). Другие вопросы по OpenGL, приме- ры и обучающие программы плюс, разумеется, неизбежный список опечаток можно найти на Web-сайте книги http://www.starstonesoftware.com/OpenGL. Компоновка программ-примеров Все примеры программ в книге написаны на С. При необходимости все они легко переписываются на C++, но обычно читатели подобных книг лучше знакомы с С, кроме того, люди, предпочитающие C++, обычно хорошо разбираются и в С. Следу- ет также отметить, что в большинстве программ задействована библиотека glTools, представляющая собой набор написанных авторами вспомогательных функций, адап-
36 Введение тированных под стандарт OpenGL. Файл заголовка (gltools.h) и исходные файлы . с приводятся в папке \conunon в папке \Examples на компакт-диске. Материал в папке \Examples рассортирован по папкам согласно поддерживаемым платформам Файлы проектов Windows написаны на Visual C++ 6.0. Такой выбор объ- ясняется тем, что многие решили не переходить на новую .NET-версию данного про- дукта, а те, кто все же решились на это (например, это я, и часть из приведенных про- ектов написана мною), могут легко импортировать свои наработки в нужный формат. Файлы проектов MacOS X создавались с помощью Xcode 1.1. Если вы все еще используете Project Builder — переходите на более новый продукт, это бесплатно и сэкономит вам несколько лет жизни. Все примеры для Мас тестировались на OS X версии 10.3.3. На момент выхода этой книги Apple еще не выпустила драйверы, поддерживающие язык затенения OpenGL. Когда это произойдет, могут проявиться скрытые недочеты Мас. Чтобы получить необходимые обновления, обращайтесь на Web-сайт, указанный в предыдущем разделе. Кроме того, на компакт-диске представ- лены файлы для Linux. Существует 10 392 444 224 229 349 244 281 999,4 различ- ных способа конфигурирования среды Linux. Ладно, может и не так много. А если говорить серьезно, вы практически предоставлены сами себе. Поскольку Xcode ис- пользует компилятор gnu, который применяется во многих средах Linux, у вас не должны возникать проблемы. Со временем я планирую выложить на Web-сайт книги дополнительные замечания и руководства. Бросаем вызов 3D! Однажды в начале 1980-х я выбирал компьютер в магазине электроники. Ко мне по- дошел продавец и завел обычную песню. Я сообщил ему, что только обучаюсь про- граммированию и предпочитаю Amiga. Тут же мне рассказали, что нужно овладевать компьютером, которым пользуется большинство. Amiga, как мне сказали, подходит исключительно для “создания симпатичных картинок”. По мнению продавца, никто не сможет добывать средства к существованию, создавая на компьютере симпатичные картинки. Этот совет, пожалуй, заслуживает первого места в моем личном рейтинге плохих советов. Несколько лет спустя я отказался от карьеры “респектабельного” разра- ботчика баз данных. Теперь я занимаюсь созданием крутых графических программ, обучаю программированию графики и получаю от своей карьеры настолько много удовольствия, что кажется это переходит грань дозволенного! Надеюсь, что сегодня я смогу предложить вам более удачный совет, чем данный мне когда-то. Хотите ли вы писать игры, создавать военные авиатренажеры или визу- ализировать большие корпоративные базы данных, самым лучшим прикладным ин- терфейсом приложения является OpenGL. Он подойдет вам, если вы только начинаете знакомиться с программированием, а если вы уже стали признанным гуру трех изме- рений, он еще больше увеличит ваши возможности. Да, кстати, средства к существо- ванию можно добывать, всего лишь создавая на компьютере симпатичные картинки! Ричард С Райт-мл.
ЧАСТЬ I Классический OpenGL Следующие 12 глав посвящены стандартному конвейеру визуализации трехмерной графики. В последние годы аппаратное обеспечение персональных компьютеров существенно возмужало, и сложная графика реального времени стала широко распространенной Основной подход при этом, правда, не изменился. Мы по-прежнему соединяем точки и используем треугольники для получения объемных тел. Производительность стала ошеломляющей, но, используя технологии, представленные в части I книги, вы сможете создавать потрясающие эффекты трехмерной графики. Хотя многие нетривиальные эффекты строятся на основе более гибких возможностей, рассмотренных в части III, “OpenGL: следующее поколение”, возможности фиксированного конвейера OpenGL остаются фундаментом, на котором строится все остальное. Мы знаем, что специализированное аппаратное обеспечение обычно быстрее, чем гибкая программируемая аппаратура, и можем ожидать, что для основных нужд визуализации некоторое время еще будут требоваться функции фиксированного конвейера OpenGL.

ГЛАВА 1 Введение в трехмерную графику и OpenGL Ричард С. Райт-мл. (Richard S Wright, Jr) О чем все это? Эта книга об OpenGL — программном интерфейсе для создания трехмерной графики в реальном времени Перед тем как мы начнем обсуждать, что такое OpenGL и как он работает, вы должны получить хотя бы поверхностное представление о трехмер- ной графике реального времени. Возможно, вы купили эту книгу потому, что хотите научиться использовать OpenGL, но вы уже хорошо представляете принципы работы в трехмерными объектами в реальном времени. Если это так — прекрасно, пропускай- те приведенный ниже материал и переходите сразу к главе 2, “Используя OpenGL”. Если же вы купили эту книгу потому, что ее иллюстрации выглядят круто, и вы хотите делать такие же на своем компьютере, стоит начать с первой главы. Краткая история компьютерной графики Первые компьютеры состояли из длинных рядов переключателей и ламп. Техни- ки и инженеры работали днями или даже неделями, чтобы запрограммировать эти машины и прочесть результаты расчетов. Полезную информацию пользователям ком- пьютеров несли узоры лампочек, иногда предусматривались грубые средства вывода данных на печать Вы можете сказать, что первой формой компьютерной графики была панель мигающих лампочек (Эта мысль поддерживается рассказами первых программистов, писавших программы только для того, чтобы создавать узоры из мигающих огней!) Времена изменились. От первых “думающих машин”, как их иногда называли, произошли полноценные программируемые устройства, которые печатали на руло- нах бумаги с использованием механизма, подобного телетайпу. Данные можно было эффективно хранить на магнитной ленте или диске, или даже в рулонах перфолент или стопках перфокарт “Хобби” под названием “компьютерная графика” родилось в дни, когда компьютеры впервые начали печатать Поскольку каждый символ ал- фавита имел фиксированный размер и форму, творческие программисты в 1970-х развлекались созданием артистических узоров и изображений, созданных с помо- щью “звездочки” (*)
40 Часть I Классический OpenGL Встреча с ЭЛТ Бумага в качестве среды вывода для компьютеров используется до сих пор. Лазерные принтеры и цветные струйные принтеры заменили грубое ASCII-art4 и позволили получить почти фотографическую точность воспроизведения художественных работ. Однако, если бумагу использовать регулярно, это удовольствие может обойтись до- рого, а постоянный вывод всего на печать является бездумным растрачиванием при- родных ресурсов, особенно если учесть, что большую часть времени нам совсем не нужны твердые копии всех расчетов или запросов к базе данных. Чрезвычайно полезным добавлением к компьютеру стала электронного-лучевая трубка (Cathode Ray Tube — CRT, ЭЛТ). Первые компьютерные мониторы на ЭЛТ представляли собой видеотерминалы, которые отображали ASCII-текст почти так же, как первые бумажные терминалы, но помимо этого ЭЛТ могли рисовать точки и ли- нии, изображения букв. Вскоре в дополнение к буквам стало возможным отображение других символов и графики. Программисты использовали компьютеры и мониторы для создания графики, которая дополнялась текстовым или табличным выводом. Бы- ли разработаны и опубликованы первые алгоритмы создания прямых и кривых линий; компьютерная графика стала больше наукой, чем развлечением. Первые элементы компьютерной графики, отображенные на таких терминалах, были двухмерными (2D). С помощью линий, окружностей и многоугольников созда- валась плоская графика для различных сфер. Графы и графики могли так визуализиро- вать научные или статистические данные, как нельзя было сделать с помощью таблиц и рисунков. Более смелые программисты даже создавали аркадные игры, подобные Lunar Lander и Pong, используя простую графику, состоящую из рисунков-чертежей, которые обновлялись (перерисовывались) несколько раз в секунду. Термин реальное время применяется к анимированной компьютерной графике. Широкое распространение этого слова в теории вычислительных системы означает, что компьютер может обрабатывать ввод так же быстро, как он поставляется, или еще быстрее. Например, разговор по телефону является действием реального време- ни, в котором участвуют два человека Вы говорите, ваш собеседник слышит вашу речь сразу же, отвечает не нее, вы слышите ответ и снова что-то говорите, и т.д. В действительности существует небольшая задержка вследствие работы электрони- ки, но обычно она неощутима для собеседников. В качестве примера действия не реального времени можно привести общение с помощью писем. Применение термина “реальное время” к компьютерной графике означает, что компьютер создает анимацию или последовательность изображений непосредственно в ответ на какой-то ввод — движение джойстика, ввод с клавиатуры и т.д Компьютер- ная графика реального времени может отображать сигнал, измеряемый электронным оборудованием, цифровыми считывающими устройствами или средствами управле- ния интерактивных игр и визуальных имитаторов (тренажеров). 4 Букв “искусство ASCII” — создание графических изображений с помощью ASCII-символов — Примеч перев
Глава 1. Введение в трехмерную графику и OpenGL 41 Рис. 1.1. Измерение двух- и трехмерных объектов -<— Ширина Рис. 1.2. Простой каркасный трехмерный куб Переходя в трехмерный мир Термин трехмерный (3D) означает, что описываемый или отображаемый объект имеет три измерения: ширину, высоту и глубину. Примером двухмерного объекта является лист бумаги, на котором что-то нарисовано или написано, и который не имеет ощути- мой глубины. Трехмерным объектом является изображенная рядом банка газировки. Эта банка круглая (имеет ширину и глубину) и высокая (имеет высоту). В зависимо- сти от точки зрения можно назвать шириной и высотой другие размеры, но в любом случае их останется три. На рис. 1.1 показано, как можно измерять размеры банки и листа бумаги. Многие века художники знают, как создать иллюзию глубины. Рисунок являет- ся двухмерным объектом, поскольку это не более чем холст, на который нанесены краски. Трехмерная компьютерная графика — это в действительности двухмерные изображения на плоском компьютерном экране, которые создают иллюзию глубины или третьего измерения. 2D + перспектива = 3D Первая компьютерная графика, несомненно, была похожа на рис. 1.2, где вы можете наблюдать простой трехмерный куб, изображенный с помощью 12 отрезков. Трехмер- ным куб выглядит благодаря перспективе, или углу между линиями, который создает иллюзию глубины. Чтобы наблюдать трехмерные объекты в действительности, нужно смотреть на них обоими глазами или предоставить каждому глазу отдельное и уникальное изоб- ражение объекта. Посмотрите на рис. 1.3. Каждый глаз получает двухмерное изоб- ражение, очень похожее на современную фотографию и отображаемое на сетчатке (задняя часть вашего глаза). Эти два изображения немного отличаются, поскольку получены под разными углами. (Глаза разнесены в пространстве.) Затем мозг объ- единяет эти слегка отличающиеся изображения в единую трехмерную картину.
42 Часть I. Классический OpenGL Левый глаз Изображение на сетчатке 1 Правый глаз Изображение на сетчатке 2 Рис. 1.3. Вот так мы видим три измерения На рис. 1.3 угол между изображением становится меньше при удалении от объекта. Трехмерный эффект можно усилить, увеличив угол между двумя изображениями. На этом эффекте построены ручные стереоскопические проекторы, с которыми вы, воз- можно, играли в детстве, и трехмерные фильмы: каждый глаз окружается отдельной линзой или очками с цветными светофильтрами, которые разделяют два наложенных изображения. Эти изображения обычно обработаны для создания драматического или кинематографического эффекта. В последние годы этот эффект стал популярен и в сфере персональных компьютеров. Очки виртуальной реальности, работающие с видеокартой и программным обеспечением, переключаются между двумя глазами, а на экране отображается разная перспектива для каждого глаза, формируя ощущение “истинного” трехмерного изображения. К сожалению, многие жалуются на то, что от этого эффекта у них болит голова или появляется головокружение! Экран компьютера представляет собой одно плоское изображение на плоской по- верхности, а не два изображения с различными перспективами, подающиеся в два глаза. Отсюда следует что то, что считается трехмерной компьютерной графикой — это на самом деле аппроксимация реального трехмерного наблюдения. Данная ап- проксимация реализована так же, как художники создают картины с кажущейся глу- биной, т.е. используя те же уловки, которые природа воплотила в наших глазах. Возможно, вы замечали, что, если закрыть один глаз, мир не становится плос- ким! Что же при этом происходит? Вы можете думать, что по-прежнему смотрите на трехмерный мир, но попробуйте провести эксперимент: поместите стакан или дру- гой объект слева, дальше, чем можете дотянуться рукой. (Если предмет будет близко, трюк не получится.) Закройте правый глаз правой рукой и дотянитесь до стакана. (Пожалуй, в опыте стоит использовать пустой пластиковый стакан!) Обратите вни- мание на то, что вам трудно оценить расстояние, на которое нужно подойти, чтобы дотронуться до стакана. Откройте теперь правый глаз и дотроньтесь до стакана — вы легко вычислите, как это сделать. Возможно, теперь вы поймаете, почему одноглазые люди часто испытывают трудности с восприятием расстояния.
Глава 1. Введение в трехмерную графику и OpenGL 43 Рис. 1.4. Трехмерный куб, изображенный с помощью отрезков Одной перспективы достаточно, чтобы сымитировать три измерения. (Обратите внимание, например, на куб, показанный ранее на рис. 1.2.) Даже если не использует- ся ни цвет, ни затенение, куб выглядит трехмерным объектом. Однако, если достаточ- но долго пристально смотреть на куб, передняя и задняя грани поменяются местами. Человеческий мозг приходит в замешательство в отсутствие на рисунке окрашенных поверхностей. На рис. 1.4 для примера показан результат выполнения простой про- граммы BLOCK, которая находится в папке компакт-диска, соответствующей данной главе. Запустите эту программу и посмотрите, как мы постепенно получаем все более и более реалистичный куб. Видно, что куб, лежащий на плоскости, имеет преувели- ченную перспективу, но все еще может дать эффект “всплывания”, если пристально на него смотреть. Щелкая на клавише <пробел>, вы будете получать все более и более достоверные изображения. Трехмерные артефакты Почему же, когда мы закрываем один глаз, мир не становится плоским? Дело в том, что когда вы закрываете один глаз, при двухмерном наблюдении остается слиш- ком много эффектов трехмерного мира. Этих эффектов достаточно, чтобы включить способность вашего мозга распознавать глубину.. Наиболее очевидной подсказкой является то, что ближние объекты кажутся больше удаленных. Данный эффект пер- спективы называется ракурсом. Он, а также изменение цвета, текстур, освещения, затенения и интенсивности цветов (вызванных освещением) добавляются к нашему восприятию трехмерного изображения. В следующем разделе мы кратко рассмотрим все эти подсказки. Обзор трехмерных эффектов Теперь вы имеете представление о том, как на плоском экране компьютера созда- ется иллюзия трехмерного пространства — посредством множества перспективных и художественных эффектов. Рассмотрим некоторые из этих эффектов, чтобы потом в книге мы могли к ним обратиться, и вы понимали, о чем идет речь. Первый термин, который вы должны знать, — визуализация. Визуализация — это превращение гео- метрического описания трехмерного объекта в изображение этого объекта на экране. При визуализации объектов или сцен применяются следующие трехмерные эффекты.
44 Часть I. Классический OpenGL Рис. 1.5. Добавление цвета может еще больше запутать ситуацию Рис. 1.6. Более привычный сплошной куб Перспектива Перспективой называются углы между линиями, которые создают иллюзию трехмер- ного мира. На рис. 1.4 показан трехмерный куб, нарисованный с помощью отрезков. Это мощная иллюзия, тем не менее, как отмечалось выше, она может приводить к проблемам восприятия. (Сконцентрируйте внимание на этом кубе, и он “поплы- вет”.) С другой стороны, рис. 1.5 дает мозгу больше подсказок относительно истин- ной ориентации куба, поскольку удалены невидимые линии. Естественно ожидать, что передняя грань объекта заслоняет заднюю. Для объемных тел выполненная мо- дификация называется удалением скрытых поверхностей. Цвет и затенение Если внимательно присмотреться к кубу на рис. 1.5, можно убедить себя, что смот- ришь на утопленный куб (вырезанную полость в виде куба), а не на внешние поверх- ности куба. Чтобы убедить наши чувства в противоположном, при создании объемных объектов мы должны использовать не только отрезки и цвет. На рис. 1.6 показано, что произойдет, если наивно раскрасить куб красным цветом — теперь изображение вообще не выглядит как куб. Раскрашивая все грани в разные цвета, как показано на рис. 1.7, мы восстанавливаем ощущение объемности объекта.
Глава 1. Введение в трехмерную графику и OpenGL 45 Рис. 1.7. Использование различных цветов усиливает иллюзию трехмерного изображения Рис. 1.8. Правильное затенение создает иллюзию освещения Рис. 1.9. Добавление тени еще больше повышает реалистичность Свет и тени Раскрашивая грани куба в разные цвета, мы помогаем глазу различать различные стороны объекта. Должным образом затеняя каждую сторону, можно придать кубу вид тела, раскрашенного одним цветом (или сделанного из одного материала), а так- же показать, что он освещается под углом, как показано на рис. 1.8. Следующий шаг представлен на рис. 1.9: куб отбрасывает тень. Теперь мы имитируем эффекты падения света на один или несколько объектов и их взаимодействия. В таком виде иллюзия весьма убедительна.
46 Часть I. Классический OpenGL Рис. 1.10. Наложение текстуры без использования дополнительной геометрии Наложение текстуры Получение высокой степени реалистичности с использованием всего лишь тысяч или миллионов крохотных освещенных и затененных многоугольников является делом техники, хотя и трудоемким. К сожалению, чем больше геометрической информации вы передадите графическому аппаратному обеспечению, тем больше времени зай- мет визуализация. Используя качественную технику, можно задействовать простую геометрию, но получать при этом весьма достоверные изображения. При таком под- ходе берется изображение, например фотография реальной поверхности или детали, и накладывается на поверхность многоугольника. Вместо одноцветных материалов вы можете изображать волокна древесины, ткань, кирпичи и т.д. Подобное отображение изображения на многоугольник с целью по- каза дополнительных деталей называется наложением текстуры. Рисунок, который вы накладываете, называется текстурой, а отдельные элементы текстуры именуют- ся текс елями. Наконец, процесс растягивания или сжатия текселей на поверхности объекта известен как фильтрация. Пример все того же куба с текстурой, наложенной на все грани, приведен на рис. 1.10. Туман Большинство из нас знает, что туман — это атмосферный эффект, добавляющий неопределенность к объектам на сцене, обычно в зависимости от того, насколько объекты удалены от наблюдателя и насколько плотен туман. Удаленные объекты (или близкие объекты, если туман очень плотный) могут даже вообще быть не видны. На рис. 1.11 показан демонстрационный ролик GLUT полета в небе (имеется в поставке GLUT на компакт-диске) с включенным туманом. Обратите внимание на то, насколько существенно туман повышает реалистичность ландшафта. Смешение и прозрачность Смешение — это комбинирование цветов или объектов на экране. Эффект смешения похож на то, что вы получаете на двухэкспозиционной фотографии при наложении
Глава 1. Введение в трехмерную графику и OpenGL 47 Рис. 1.11. Эффект тумана создает убедительную иллюзию обширного открытого пространства Рис. 1.12. Получение эффекта отражения с помощью смешения двух изображений. Этот эффект можно применять в различных случаях. Меняя сте- пень того, насколько каждый объект смешивается со сценой, можно сделать объекты прозрачными, при этом будет виден не только объект, но и то, что находится за ним (как взгляд через стекло или призрачное изображение). Кроме того, как показано на рис. 1.12, с помощью смешения можно получить иллюзию отражения. На этой иллюстрации вы видите текстурный куб, визуализи- рованный дважды. Вначале куб был визуализирован сверху вниз ниже уровня пола. Затем мраморный пол был смешан со сценой, что позволило кубу проглядывать че- рез пол. Наконец, куб был визуализирован с правильной ориентацией над полом. Результат имитирует отражение в блестящей мраморной поверхности. Защита от наложения Наложение — это проявляющийся на экране эффект, вызванный тем, что изображение состоит из дискретных пикселей. На рис. 1.13 видно, что линии, образующие куб, имеют зазубренные края. Как показано на рис. 1.14, рваных краев можно избежать,
48 Часть I. Классический OpenGL Рис. 1.13. Куб с зазубренными линиями Рис. 1.14. Куб со сглаженными сторонами после применения защиты от наложения аккуратно смешивая линии с цветом фона и придавая всем отрезкам гладкость. По- добная технология смешения называется защитой от наложения. Соответствующую процедуру можно применить к сторонам многоугольника, повысив реалистичность объекта или сцены. Эта возможность графики реального времени часто называется фотореалистичной визуализацией. Области применения трехмерной графики Трехмерная графика реального времени применяется во многих современных ком- пьютерных приложениях: от интерактивных игр до визуализации данных в науке, медицине или бизнесе. Также не стоит забывать и о том, что мощная трехмерная графика вошла в фильмы и технические и образовательные публикации. Трехмерный мир реального времени Как мы определили выше, трехмерная графика реального времена связана с ани- мацией и интерактивным взаимодействием с пользователем. Одной из первых сфер применения трехмерной графики реального времени были военные авиатренажеры. Даже сейчас имитаторы полета являются популярным развлечением в среде потре- бителей. На рис. 1.15 показана копия экрана популярного пилотажного тренажера, в котором при визуализации используется OpenGL (www.flightgear.org).
Глава 1. Введение в трехмерную графику и OpenGL 49 Рис. 1.15. Популярный имитатор полета от Flight Gear, основанный на OpenGL Рис. 1.16. Трехмерная графика, используемая в автоматизированном проектировании (Computer-Aided Design — CAD, АП) Применение трехмерной графики на персональных компьютерах практически без- гранично. Пожалуй, самой популярной сферой сегодня являются компьютерные игры. Трудно назвать современную игру, которая бы не требовала графической карты 3D. Кроме того, трехмерный мир всегда был популярен в области научной визуализа- ции и инженерных приложениях, но взрывоподобное развитие дешевого аппаратно- го обеспечения с возможностью трехмерной визуализации стимулировало развитие этих областей как никогда ранее. Бизнес-приложения также выиграли от доступно- сти нового аппаратного обеспечения, на основе которого сейчас строятся все бо- лее и более сложные бизнес-графики и технологии визуализации информационной проходки по базе данных. Были затронуты даже современные графические пользо- вательские интерфейсы, которые стали развиваться с учетом преимуществ нового аппаратного обеспечения. В новой операционной системе Macintosh OS X, например, OpenGL используется для визуализации всех окон и средств управления, за счет че- го создан мощный и сногсшибательно привлекательный визуальный интерфейс. На рис. 1.16-1.20 показаны лишь несколько из множества приложений трехмерной гра- фики реального времени на современных ПК. Все эти изображения визуализированы с использованием OpenGL.
50 Часть I. Классический OpenGL Рис. 1.17. Трехмерная графика, используемая для архитектурного или гражданского планирования. Изображение предоставлено Real 3D, Inc. Рис. 1.18. Трехмерная графика, используемая в медицинских приложе- ниях (продукт VolView от Kitware) Компьютерная графика не реального времени Приложения трехмерной графики реального времени требуют некоторых компромис- сов. Если выделить больше времени на обработку, можно сгенерировать трехмерную графику более высокого качества. Обычно разрабатываются модели и сцены, а схема построения хода лучей обрабатывает определения и выдает высококачественное трех- мерное изображение. Типичный процесс выглядит следующим образом: приложение моделирования с помощью трехмерной графики реального времени взаимодействует
Глава 1. Введение в трехмерную графику и OpenGL 51 Рис. 1.19. Трехмерная графика, используемая в научной визуализации Рис. 1.20. Трехмерная графика, используемая в индустрии развлечений (Descent 3 от Outrage Entertainment, Inc.) с художником для создания содержимого. После этого кадры передаются другому приложению (программа построения хода лучей), которое визуализирует изображе- ние. Визуализация отдельного кадра такого фильма, как Toy Story может, например, потребовать нескольких часов на весьма мощном компьютере. Процесс визуализации и записи многих тысяч кадров дает анимационную последовательность, предназна- ченную для воспроизведения. Хотя воспроизведение может выполняться и в реальном времени, содержимое не является интерактивным, поэтому к нему неприменим тер- мин реального времени, точнее нужно говорить — предварительно визуализированное. На рис. 1.21 приведен еще один пример с компакт-диска. Данное вращающееся изображение демонстрирует буквы-кристаллы, выстроившиеся в слово “OpenGL”. Буквы прозрачны, к ним применена защита от наложения и множество эффектов отражения и затенения. Файл, с помощью которого вы можете воспроизвести эту анимацию, называется opengl. avi; он находится в корневом каталоге компакт-диска, прилагающегося к книге. Визуализация этого большого файла (более 35 Мбайт!) потребовала нескольких часов.
52 Часть I. Классический OpenGL Рис. 1.21. Высококачественная предварительно визуализированная анимация Основные принципы трехмерного программирования Итак, теперь вы хорошо представляете основы трехмерной графики реального вре- мени. Мы ввели некоторые термины и представили приложения для ПК. Как же со- здавать эти изображения на домашнем ПК? Ответу на этот вопрос посвящена остав- шаяся часть книги! Впрочем, вам все же понадобятся некоторые основы, и ниже мы их представим. Немедленный режим и режим удержания (графы сцены) Существует два различных подхода к программированию программных интерфейсов приложений (Application Programming Interface — API) для трехмерной графики ре- ального времени. Первый подход называется режимом удержания (retained mode). В режиме удержания вы передаете интерфейсу или набору инструментов описание объектов и сцены. После этого графический пакет создает изображение на экране. Единственными дополнительными манипуляциями, которые вы можете совершить, является ввод команд с целью изменения положения и ориентации пользователя (ка- меры) или других объектов сцены. Подобный подход типичен для программ построения хода лучей и многих коммер- ческих имитаторов полета и генераторов изображений. С точки зрения программиро- вания создаваемая структура относится к графам сцены. Граф сцены — это структура данных (часто это ориентированный ациклический граф, Directed Acyclic Graph — DAG), которая содержит все объекты сцены и их связи между собой. Данный под- ход применяется во многих высокоуровневых наборах инструментов или “игровых движках”. Программисту не обязательно понимать тонкости визуализации, он дол- жен иметь модель или базу данных, которая передается графической библиотеке, отвечающей за визуализацию. Второй подход к трехмерной визуализации называется немедленным режимом (immediate mode). Большинство программных интерфейсов режима удержания или графы сцены используют немедленный режим для внутреннего выполнения визуали- зации. В немедленном режиме вы не описываете модели и среду на высоком уровне. Вместо этого вы вводите команды непосредственно в программу обработки графики, и они немедленно влияют на ее состояние и состояние всех последующих команд. В немедленном режиме новые команды не влияют на уже выполненные команды визуализации. Это дает возможность мощного низкоуровневого контроля над про- цессом визуализации. Например, вы можете визуализировать ряд текстурированных неосвещенных многоугольников, представляющих небо. Затем вы вводите команду,
Глава 1 Введение в трехмерную графику и OpenGL 53 отключающую текстурирование, и команду, включающую освещение. В результате все геометрические объекты, которые вы визуализировали ранее, подвергаются воз- действию света, но на них не налагается текстура, представляющая небо Системы координат Рассмотрим, как описываются объекты в трех измерениях. Прежде чем вы сможете задавать положение и размер объекта, потребуется система отсчета, относительно которой можно измерять положения тел. При рисовании линий или расстановке то- чек на простом экране компьютера вы задаете положение через строки и столбцы. Например, стандартный экран VGA имеет 640 позиций пикселей слева направо и 480 позиций сверху вниз. Чтобы задать точку в центре экрана, вы указываете, что ее нужно поместить в позицию (320, 240) — т.е. на 320 пикселей от левого края экрана вправо и на 240 пикселей от верхнего края экрана вниз. В OpenGL (да и практически в любом программном интерфейсе приложения трех- мерной графики) при создании окна, в котором вы будете рисовать, необходимо задать систему координат, которую вы желаете использовать; следует также указать, как за- данные координаты будут отображаться в физические пиксели экрана. Рассмотрим вначале, как это выглядит для двухмерных изображений, а затем расширим сформу- лированные принципы на три измерения. Двухмерные декартовы координаты Для представления двухмерных рисунков чаще всего используется декартова систе- ма координат. Декартовы координаты представляются координатой х — положением в горизонтальном направлении и у — положением в вертикальном направлении. Начало декартовой системы координат находится в точке х = 0, у = 0. Декартовы координаты записываются как пары значений в круглых скобках; вначале указыва- ется координата х, затем координата у. Например, начало координат записывается как (0,0) На рис. 1 22 представлена декартова система координат в двух измерени- ях Снабженные метками линии х и у называются осями, и они могут следовать от минус до плюс бесконечности На данном рисунке представлена истинная декарто- ва система координат — такая, какую вы привыкли использовать еще в начальной школе. Сейчас же, выбирая режимы отображения окон, вы будете указывать, что ко- ординаты, заданные для рисунков, будут интерпретироваться иначе. Далее в книге мы покажем, как это пространство координат можно различными способами отображать в координаты окна. Оси х и у перпендикулярны (пересекаются под прямым углом) и вместе опре- деляют плоскость ху. В любой системе координат две оси, которые пересекаются под прямым углом, определяют плоскость. В системе, где существует всего две оси, естественным образом можно изобразить только одну плоскость Отсечение координат Физически окно измеряется в пикселях. Прежде чем вы сможете начать изображать в окне точки, линии и формы, вы должны сообщить OpenGL, как переводить указан- ные пары значений в экранные координаты. Для этого вы задаете область декартова
54 Часть I. Классический OpenGL Рис. 1.23. Две области отсечения пространства, которую занимает окно; она называется областью отсечения. В двух- мерном пространстве область отсечения — это минимальное и максимальное значения хну, которые принадлежат окну. Мы можем описать это и по-другому, задав поло- жение начала системы координат относительно окна. Два распространенных выбора областей отсечения показаны на рис. 1.23 В первом примере на рис. 1.23 (слева) координаты х в окне меняются слева на- право от 0 до +150, а координаты у — снизу вверх от 0 до +100. Точка в центре экрана будет представлена как (75, 50). На втором рисунке показана область отсече- ния, координата х которой меняется слева направо от —75 до +75, а координата у — снизу вверх от —50 до +50. В этом примере точка в центре экрана будет совпадать с началом координат — точкой (0,0). Кроме того, используя функции OpenGL (или
Глава 1 Введение в трехмерную графику и OpenGL 55 Рис. 1.24. Поле просмотра, определенное как удвоенная область отсечения обычные функции Windows для рисования с помощью GDI), можно перевернуть си- стему координат сверху вниз или справа налево Отображению по умолчанию в окнах Windows соответствует положительное направление оси у сверху вниз Хотя такая ор- ганизация и удобна при рисовании сверху вниз, для рисования графики и графиков отображение по умолчанию непривычно. Поля просмотра: отображение координат рисунка в координаты окна Очень редко ширина и высота области отсечения точно совпадает с шириной и высо- той окна в пикселях. Следовательно, нужно преобразовать систему координат: отоб- разить логические декартовы координаты в физические координаты пикселей экрана. Такое отображение задается с помощью поля просмотра. Поле просмотра — это об- ласть внутри клиента окна, которая используется для рисования области отсечения. Поле просмотра просто отображает область отсечения в область окна. Обычно поле просмотра определяется как все окно, но это не является строго необходимым, напри- мер, вы можете указать, что рисовать разрешается только в нижней половине окна. На рис 1.24 показано большое окно, насчитывающее 300 х 200 пикселей, с полем просмотра, определенным как вся область клиента. Если установить, что область отсечения этого окна простирается от 0 до 150 вдоль оси х и от 0 до 100 вдоль оси у, логические координаты будут отображены в большую систему экранных координат в окне наблюдения. Каждый элемент в логической системе координат будет удвоен в физической системе координат (пиксели) окна. На рис. 1.25 показано поле просмотра, равное области отсечения. Окно наблюде- ния по-прежнему равно 300 х 200 пикселей, поэтому область наблюдения занимает левую нижнюю область окна. С помощью поля просмотра можно сжимать или растягивать изображения внут- ри окна, а также отображать только часть области отсечения (в таком случае поле просмотра задается большим области клиента окна).
56 Часть I Классический OpenGL Рис. 1.25. Поле просмотра, определенное с размерами области отсечения Вершина — точка в пространстве При рисовании двух- или трехмерного объекта вы составляете его из нескольких меньших форм, именуемых примитивами. Примитивы — это такие одно- или двух- мерные объекты или поверхности, как точки, линии и многоугольники (плоские фор- мы с несколькими сторонами), собираемые в трехмерном пространстве для созда- ния трехмерных объектов Например, трехмерный куб состоит из шести двухмерных квадратов, размещенных в разных плоскостях. Каждый угол квадрата (или любо- го примитива) называется вершиной. Этим вершинам сопоставляются координаты в трехмерном пространстве. Вершина — это всего лишь набор координат в двух- или трехмерном пространстве. Создание трехмерной геометрии твердых тел не силь- но отличается от игры “соедини точки”! Подробно о примитивах OpenGL и их использовании рассказывается в главе 3 “Рисование в пространстве: геометриче- ские примитивы”. Трехмерные декартовы координаты Расширим теперь двухмерную систему координат в третье измерение, добавив ком- поненту глубины. На рис. 1.26 показана декартова система координат с новой осью z, перпендикулярной как оси х, так и оси у. Новая ось представляет линию, про- веденную перпендикулярно от центра экрана к наблюдателю (Систему координат, показанную на рис. 1.22, мы повернули влево относительно оси у и назад и вниз относительно оси у. Если бы мы этого не сделали, ось z была бы направлена прямо на вас, поэтому вы бы ее не видели) Теперь можно задавать точку в трехмерном пространстве с помощью трех координат: х, у и z. На рис. 1.26 для конкретности показана точка (—4,4,4) Проектирование: получение 2D из 3D Итак, мы показали, как задавать точку в трехмерном пространстве, используя декар- товы координаты. При этом не имеет значения, насколько вы сможете убедить свои глаза — пиксели на экране являются двухмерными. Так как же OpenGL переводит
Глава 1. Введение в трехмерную графику и OpenGL 57 Рис. 1.26. Декартовы координаты в трех измерениях Рис. 1.27. Трехмерное изображение, спроектированное на двухмерную поверхность эти декартовы координаты в двухмерные координаты, которые можно изобразить на экране? Короткий ответ звучит как “тригонометрия и простая работа с матрицами”. Просто? Вы правы, не очень; нам придется объяснять эту “простую” технику на многих страницах, теряя по пути читателей, которые не знают или не помнят основ линейной алгебры. Подробнее об этом вы прочтете в главе 4, “Геометрические преоб- разования: конвейер”, а более глубокое обсуждение можно найти в книгах, указанных в приложении А, “Что еще почитать?” К счастью, чтобы использовать OpenGL для создания графики, не нужно глубокое знание математики. Однако чем лучше вы разберетесь в математике, тем более мощным инструментом станет для вас OpenGL! Первая концепция, которую вы действительно должны понять, называется проек- тированием. Трехмерные координаты, с помощью которых вы создаете геометрию, опускаются (или проектируются) на двухмерную плоскость (фон окна). Этот про- цесс похож на обрисовывание контуров предмета, находящегося за стеклом. Если потом убрать объект или стекло, у вас останутся контуры объекта. Посмотрите на рис. 1.27, где контуры дома, находящегося на заднем плане, переносятся на плоскую поверхность стекла. Задавая проекцию, вы задаете наблюдаемый объем, который вы желаете отобразить в своем окне, и то, как он должен преобразовываться.
58 Часть I Классический OpenGL Рис. 1.28. Объем отсечения ортографической проекции Ортографические проекции В OpenGL мы будем работать с двумя основными типами проекций. Первый на- зывается ортографической. или параллельной проекцией Для использования такой проекции вы задаете квадратный или прямоугольный наблюдаемый объем. Все, что находится вне этого объема, не изображается. Более того, все объекты, которые имеют одинаковые размеры, будут при показе иметь одинаковую величину вне зависимости от того, близко или далеко они расположены. Проекции такого типа (одна из них показана на рис. 1 28) чаще всего используются в архитектурном дизайне, автома- тизированном проектировании или двухмерных графах. Довольно часто с помощью ортографической проекции добавляется текст или двухмерные рисунки поверх трех- мерной графической сцены. Наблюдаемый объем в ортографической проекции задается через ближнюю, даль- нюю, левую, правую, верхнюю и нижнюю отсекающие плоскости. Объекты и ри- сунки, которые вы помещаете внутрь этого наблюдаемого объема, проектируются (с учетом ориентации) на двухмерное изображение, которое вы видите на экране. Перспективные проекции Второй из наиболее распространенных проекций является перспективная Данная проекция добавляет эффект уменьшения удаленных объектов. Наблюдаемый объем (рис. 1 29) напоминает пирамиду с обрезанной верхушкой. Данная форма называется усеченной пирамидой. Объекты, ближайшие к передней грани наблюдаемого объема, ближе к истинному размеру, а объекты, расположенные вблизи дальней грани сжи- маются при проектировании на переднюю грань объема. Проекции подобного типа являются наиболее реалистичными в моделировании и трехмерной анимации.
Глава 1. Введение в трехмерную графику и OpenGL 59 Рис. 1.29. Объем отсечения перспективной проекции (усеченная пирамида) Резюме В этой главе мы ввели основы трехмерной графики. Было показано, почему для вос- приятия трехмерного пространства нужно два изображения объекта, полученных под различными углами наблюдения. Кроме того, было описано, как на двухмерных ри- сунках создается иллюзия глубины посредством перспективы, удаления невидимых линий, раскрашивания, затенения и других техник. Была введена декартова система координат для двух- и трехмерных рисунков, и вы узнали о двух методах, применяю- щихся в OpenGL для проектирования трехмерных изображений на двухмерный экран. Мы специально не вдавались в детали того, как эти эффекты создаются OpenGL. В следующих главах мы расскажем, как это реализовать и извлечь максимум поль- зы из мощи OpenGL. На прилагаемом к книге компакт-диске вы найдете програм- мы, рассмотренные в данной главе, на основе которых демонстрируются описанные трехмерные эффекты. Запустив программу BLOCK и нажимая клавишу <пробел>, вы сможете наблюдать, как каркасный куб постепенно превращается в полноценную освещенную и текстурированную иллюзию куба, отбрасывающего тень. Пока что вы можете не понять код, но данная иллюстрация подскажет вам, чего следует ожи- дать. Закончив чтение книги, вы сможете снова обратиться к этому примеру и даже написать его самостоятельно.

ГЛАВА 2 Используя OpenGL Ричард С. Райт-мл. (Richard S. Wright, Jr.) ИЗ ЭТОЙ ГЛАВЫ ВЫ УЗНАЕТЕ . . • Откуда пришел OpenGL и куда он идет • Какие заголовки нужно включить в ваш проект • Как использовать GLUT с OpenGL для создания окна и рисования в нем • Как задать цвет, используя компоненты RGB (красный, зеленый, синий) • Как на размеры изображения влияют поля просмотра и объемы наблюдения • Как реализовать простую анимацию с использованием двойной буферизации • Как действует аппарат состояний (конечный автомат) OpenGL • Как выявить ошибки OpenGL • Как использовать расширения OpenGL Что такое OpenGL Строго OpenGL определяется как программный интерфейс к графической аппара- туре. По сути же, это очень мобильная и очень эффективная библиотека трехмер- ной графики и моделирования. Используя OpenGL, вы можете создавать элегантную и прекрасную трехмерную графику практически с таким же визуальным качеством, что дает программа построения хода лучей. Самое большое преимущество исполь- зования OpenGL заключается в том, что скорость обработки здесь на порядки выше, чем у программ построения хода лучей. Первоначально в OpenGL использовались ал- горитмы, тщательно разработанные и оптимизированные компанией Silicon Graphics, Inc. (SGI), признанного мирового лидера в сфере компьютерной графики и анимации. Со временем OpenGL эволюционировал; свой опыт и интеллектуальную собствен- ность в него вложили многие производители, разработав собственные высокопроиз- водительные реализации. OpenGL — это не язык программирования, как С или C++. Он больше похож на библиотеку времени выполнения С, которая предлагает предопределенные функцио- нальные возможности Строго говоря, “программ OpenGL” не существует, правильно говорить о программах, которые пишутся с учетом того, что в качестве одного из их программных интерфейсов приложения (Application Programming Interfaces — API) будет использован OpenGL. Для доступа к файлу или Internet вы можете использовать API Windows (хотя и не обязаны это делать), точно так же вы можете использовать OpenGL для создания трехмерной графики реального времени.
62 Часть I Классический OpenGL OpenGL предназначен для использования с аппаратным обеспечением компью- тера, которое разработано и оптимизировано под отображение трехмерной графи- ки и манипуляцию ею. Возможны также только программные, “общие” реализации OpenGL, и к этой категории относятся реализации, выполненные Microsoft. С помо- щью только программной реализации визуализацию невозможно выполнить так же быстро, как с помощью аппаратной, а некоторые нетривиальные спецэффекты вообще могут быть недоступны В то же время применение программной реализации озна- чает, что программа теоретически сможет запускаться на множестве разнообразных компьютерных систем, в которых могут отсутствовать графические карты 3D OpenGL используется для решения множества задач, возникающих в различных сферах — от архитектурных и инженерных приложений до программ моделирова- ния, используемых для создания компьютерных монстров в фильмах со спецэффек- тами. Введение трехмерного программного интерфейса приложений, являющегося промышленным стандартом, в такие операционные системы, как Microsoft Windows и Macintosh OS X, имело впечатляющие последствия С распространением аппаратно- го ускорения и быстродействующих микропроцессоров для персональных компьюте- ров трехмерная графика стала типичной составляющей пользовательских и коммер- ческих приложений, а не только игр и научных приложений, как было раньше. Эволюция стандарта Предшественником OpenGL был IRIS GL от Silicon Graphics. Вначале это была двух- мерная графическая библиотека, развившаяся в трехмерный программный интерфейс приложения для графических рабочих станций IRIS, производимых данной компа- нией. Эти аппараты были не просто универсальными компьютерами: они имели специализированное аппаратное обеспечение, оптимизированное для отображения сложной графики. Аппаратное обеспечение позволяло выполнять сверхбыстрые пре- образования матриц (необходимое условие работы с трехмерной графикой), имелась аппаратная поддержка буферизации кодов глубины и другие возможности. Иногда, впрочем, развитию технологии мешает необходимость поддержки унасле- дованных систем. IRIS GL не был изначально разработан как интерфейс обработки геометрии на основе вершин, и из соображений совместимости версий стало понятно: чтобы пойти дальше, SGI нужно кардинально новое решение. Результатом работ SGI по развитию и улучшению переносимости IRIS GL стал OpenGL. Новый графический программный интерфейс мог предложить возможно- сти GL, но он должен был быть “открытым” стандартом, принимать информацию от других производителей графического аппаратного обеспечения и разрешать лег- кую адаптацию под другие аппаратные платформы и операционные системы Для обработки трехмерной геометрии OpenGL надо было разрабатывать с нуля. OpenGL ARB Открытый стандарт в действительности не является открытым, если его контролиру- ет один производитель. На то время основной сферой интересов SGI была мощная компьютерная графика. Однако, взобравшись на вершину, вы обнаруживаете, что воз- можности для роста несколько ограничены Руководство SGI осознало, что компании будет выгодно сделать что-то для отрасли вообще, чтобы стимулировать развитие
Глава 2 Используя OpenGL 63 рынка мощной аппаратуры компьютерной графики. Действительно открытый стан- дарт, принятый несколькими производителями, поможет программистам создавать приложения и содержимое, которое будет доступно для огромного множества плат- форм. Программное обеспечение — вот то, что способствует продаже компьютеров, и если SGI желала продать больше компьютеров, ей требовалось больше программ- ного обеспечения, которое бы запускалось на ее компьютерах Другие поставщики также поняли этот факт, поэтому был основан Совет наблюдения за архитектурой (Architecture Review Board — ARB) OpenGL. Хотя SGI контролировала лицензирование программного интерфейса OpenGL, ос- нователями OpenGL ARB было несколько компаний: SGI, Digital Equipment Corpora- tion, IBM, Intel и Microsoft. 1 июля 1992 года была представлена версия 1.0 специ- фикации OpenGL. Позже в состав ARB вошли другие члены, многими из которых являются производители аппаратного обеспечения для ПК, и в настоящее время Совет собирается четыре раза в год, поддерживая и улучшая спецификацию и стимулируя развитие стандарта OpenGL. Встречи открыты для общественности, и компании, не являющиеся членами Со- вета, могут участвовать в обсуждении и даже голосовать при опросах общественного мнения. Разрешение на присутствие нужно оформлять заранее, поскольку собрания стараются делать небольшими. Компании, не являющиеся членами Совета, вносят су- щественный вклад в спецификацию, участвуя в различных подкомитетах и выполняя серьезную работу на аттестационных испытаниях. Лицензирование и согласование Реализация OpenGL — это в некотором смысле программная библиотека, создающая трехмерные изображения в ответ на вызовы функций OpenGL, или драйвер аппарат- ного устройства (обычно это видеокарта), который делает то же самое. Аппаратные реализации во много раз быстрее программных и в настоящее время распространены даже на недорогих компьютерах. Поставщик, желающий создавать и продавать реализации OpenGL, должен вна- чале получить от SGI лицензию на использование OpenGL. SGI предоставляет полу- чателю лицензии простую реализацию (чисто программную) и комплект драйверов устройств, если получателем является производитель аппаратного обеспечения для ПК. После этого производитель создает собственную оптимизированную реализа- цию, возможно, повышая ее ценность за счет собственных расширений. Конкуренция в среде производителей обычно опирается на производительность, качество изобра- жений и устойчивость драйвера. Кроме того, реализация производителя должна пройти аттестационные испыта- ния OpenGL. Испытания разработаны так, чтобы гарантировать завершенность реа- лизации (т.е. то, что реализация содержат все необходимые вызовы функций) и дает визуализированный трехмерный результат, приемлемый для данного набора функций. Разработчикам программного обеспечения не нужно получать лицензию на ис- пользование OpenGL или платить за использование драйверов OpenGL. OpenGL из- начально поддерживается операционной системой, а лицензионные драйверы постав- ляются производителями аппаратного обеспечения. На компакт-диске, прилагаемом к книге, имеется бесплатная открытая программная реализация OpenGL под назва- нием MESA. По правовым причинам MESA не является “официальной” реализацией
64 Часть I Классический OpenGL OpenGL, но этот программный интерфейс приложения идентичен тому, что мы знаем как OpenGL! Многие аппаратные драйверы OpenGL под Linux с открытым исходным кодом фактически основаны на исходном коде MESA. Войны API Стандарты хороши для всех, исключая производителей, которые считают, что поль- зователи должны выбирать только их, поскольку они лучше знают, что нужно пользо- вателям. Производителей, пытающихся добиться этого статуса, называют специаль- ным словом — монополисты. В большинстве компаний понимают, что конкуренция хороша для всех, поэтому одобряют, поддерживают и даже вносят вклад в промыш- ленные стандарты. Отклонением от этого идеала была юность OpenGL на платфор- ме Windows. Вступая в DirectX Мало кто сейчас помнит, что такое “Windows Accelerator”, но было время, когда вы могли купить специальную графическую карту, которая ускоряла команды двухмер- ной графики, используемые Microsoft Windows. Хотя ускоренные графические карты с интерфейсом графических устройств (Graphics Device Interface — GDI) были боль- шой радостью для пользователей текстовых редакторов или настольных издательских систем, их было недостаточно для программистов игр для ПК. Ранние игры под Win- dows состояли преимущественно из головоломок или карточных игр, которые, в отли- чие от игр жанра “action”, не требовали быстрой анимации, оставаясь программами, основанным на DOS, которые могли контролировать весь экран, и которым не нужно было делить ресурсы системы с другими программами, запущенным в то же время. Компания Microsoft предприняла несколько попыток склонить программистов к разработке игр под Windows, но первые попытки обеспечить быстрый доступ к дис- плею (программный интерфейс Wind) были слишком далеки от идеала, поэтому про- граммисты игр продолжали писать программы под DOS, дававшие им прямой доступ к аппаратуре управления графикой. Когда Microsoft начала поставлять на потреби- тельский рынок Windows 95, свою первую квазитридцатидвухбитовую операционную систему, компания намеревалась раз и навсегда уничтожить DOS. Первоначальный Game Developers Kit (“набор инструментов разработчика игр”) под Windows 95 вклю- чал несколько новых программных интерфейсов для Windows, предназначенных для разработчиков игр. Важнейшим из них был DirectDraw. Чтобы оставаться конкурентоспособными, производителям видеокарт теперь тре- бовались драйверы GDI и DirectDraw, однако основа драйвера была сделана таким образом, чтобы обеспечивать такой же (и более быстрый) доступ к графическому аппаратному обеспечению под Windows, который поставщики использовали для ра- боты с DOS. На этот раз Microsoft повезло больше, и начали производиться игры под Windows 95, которые использовали преимущества новых “игровых” программных интерфейсов. Позже данная группа интерфейсов получила название DirectX В на- стоящее время DirectX содержит все семейство программных интерфейсов, предна- значенных для того, чтобы разработчики мультимедийной продукции могли работать на платформе Windows В этом плане DirectX в Windows будет справедливым срав- нить с QuickTime в Macintosh’ оба набора предлагают программисту разнообразные мультимедийные услуги и программные интерфейсы приложений.
Глава 2. Используя OpenGL 65 Первоначальной целью DirectX был непосредственный низкоуровневый доступ к функциям аппаратного обеспечения. Первоначальная цель несколько видоизмени- лась со временем, и DirectX стал включать функциональные возможности высоко- уровневого программного обеспечения и дополнительные уровни над низкоуровне- выми устройствами. То, что изначально называлось Windows Game Developers Kit, эволюционировало в “DirectX Media API”. Многие программные интерфейсы DirectX независимы, поэтому их можно смешивать и согласовывать по собственному жела- нию. Например, вы можете применять звуковую библиотеку третьей стороны с игрой, визуализированной с помощью Directs D, или даже использовать DirectSound с игрой, визуализированной с помощью OpenGL. OpenGL приходит на ПК Практически в то же время, когда одна группа из Microsoft сфокусировала внимание на установлении Windows в качестве жизнеспособной игровой платформы, в OpenGL (который в это время был самым молодым программным интерфейсом) наметились тенденции к переходу на ПК. Microsoft начала рекламировать его как предпочтитель- ный программный интерфейс для научных и технических приложений. Компания Microsoft была даже одним из основателей Совета ARB OpenGL. Поддержка OpenGL на платформе Windows позволила Microsoft конкурировать с рабочими станциями на основе UNIX, которые традиционно были средой нетривиальной визуализации и приложений моделирования. Первоначально аппаратное обеспечение трехмерной графики для ПК было очень дорогим, поэтому оно нечасто использовалось для компьютерных игр. Только до- статочно состоятельные промышленные предприятия могли позволить себе такое аппаратное обеспечение, и в результате OpenGL вначале стал популярным в сферах АП, моделирования и научной визуализации. В этих областях основным параметром качества часто является производительность, поэтому OpenGL разрабатывался и эво- люционировал как программный интерфейс с упором на производительность. Когда на ПК стали популярны трехмерные игры, OpenGL стал применяться и в этой^сфере, причем в некоторых случаях весьма удачно. Ко времени, когда аппаратная поддержка трехмерной графики на ПК стала до- статочно дешевой, чтобы привлечь внимание любителей компьютерных игр, OpenGL был развитым программным интерфейсом трехмерной визуализации с мощным на- бором функций. Этот момент совпал с попыткой Microsoft провозгласить свой новый продукт Directs D в качестве программного интерфейса трехмерной визуализации для игр. Очевидно, такой новый, сложный в использовании и имеющий относительно ма- ло функций программный интерфейс, как Directs D, не мог выжить на рынке. Многие ветераны-программисты трехмерных игр неофициально назвали время борьбы Microsoft и SCI за умы разработчиков трехмерных игр “войнами API”. Mi- crosoft была одним из основателей OpenGL ARB и желала видеть OpenGL в системе Windows, чтобы поставщики программного обеспечения для рабочих станций UNIX могли легко переносить свои научные и инженерные приложения в Windows NT. В то же время переносимость была палкой о двух концах; она также означала, что разработчики, которые ориентируются на систему Windows, могут позже перенести свои приложения на другие операционные системы. Персональные компьютеры хо- рошо закрепились в деловом мире. Теперь пришло время выйти на гораздо больший потребительский рынок.
66 Часть I Классический OpenGL OpenGL для игр? Когда Джон Кармак (John Carmack), автор одной из самых популярных трехмерных игр, за один уикенд переписал свой Quake под использование OpenGL, его работа за- ставила зашевелиться весь игровой мир. Джон продемонстрировал, что десяток строк кода OpenGL требует двух-трех страниц кода Directs D, выполняющего ту же работу (визуализацию нескольких треугольников). Многие программисты игр стали внима- тельнее присматриваться к OpenGL с позиции программного интерфейса трехмерной визуализации, подходящего для игр, а не только для “научных” приложений. Компа- ния Джона Кармака ID Software также проводила политику создания своих игр для нескольких различных платформ и операционных систем. OpenGL просто позволял сделать это гораздо проще. К этому моменту Microsoft вложила слишком много ресурсов в свой интерфейс DirectsD, чтобы отступиться от плода собственных усилий. Компания не могла пре- кратить рекламу Directs D для игр, поскольку это означало бы потерю фактора влия- ния, удерживающего разработчиков, создающих игры исключительно под Windows. Потребителям понравилось играть в игры, а тот, кто удерживает рынок потреби- тельских операционных систем, занимает первое место в категории потребительских приложений Поэтому Microsoft не могла отказаться от поддержки OpenGL на рынке рабочих станций, так как это означало бы потерю необходимого фактора влияния, от- влекающего разработчиков приложений от конкурирующей операционной системы. Microsoft начала формировать мнение, будто OpenGL предназначен для точной визуализации, a DirectSD — для визуализации в реальном времени. В официальной литературе от Microsoft OpenGL описывался как что-то наподобие программы по- строения хода лучей, а не как программный интерфейс визуализации в реальном времени (а именно в таком качестве разрабатывался OpenGL). Почему Microsoft не вышвырнули из Совета ARB за подобную дезинформацию остается тайной, скрытой за семью замками и десятками неоглашенных соглашений. Компания SGI подняла вопрос о провозглашении OpenGL альтернативой DirectSD, и большинство разработ- чиков пожелало выбирать, какую технологию использовать в своих играх. Direct3D: рывок на старте Во времена, когда аппаратная поддержка трехмерной визуализации не получила еще широкого распространения, при создании трехмерных игр разработчики были вы- нуждены применять программную визуализацию. Оказалось, что программная визу- ализация DirectSD (Microsoft) выполнялась во много раз быстрее, чем визуализация OpenGL. Согласно Microsoft, причиной этого было то, что OpenGL предназначен для автоматизированного проектирования; к сожалению, программисты игр многого не знали об АП, а пользователи АП обычно не занимались тем, чтобы уделять все свое внимание вращению рисунков. Microsoft предположила, что OpenGL будет исполь- зоваться только с дорогими графическими SD-картами в сфере АП, и не выделила ресурсов на создание быстрой программной реализации. Без аппаратного ускоре- ния OpenGL был фактически бесполезен в Windows для задач, отличных от простой статической трехмерной графики и визуализации Сказанное не имеет никакого отно- шения к отличиям программных интерфейсов OpenGL и DirectSD — речь идет только о том, как эти интерфейсы реализованы Microsoft.
Глава 2 Используя OpenGL 67 Задачу показать, что причиной недостатков является не конструкция программно- го интерфейса OpenGL, а его реализация, взяла на себя компания Silicon Graphics. В 1996 на конференции SigGraph в Новом Орлеане SGI продемонстрировала соб- ственную реализацию OpenGL под Windows. Переведя несколько демонстрационных роликов Direct3 D в OpenGL, компания легко показала, что OpenGL воспроизводит ту же анимацию при равных или больших скоростях. Фактически же обе программные реализации были слишком медленными, чтобы создавать хорошие игры. Разработчики игр могли писать собственные оптимизиро- ванные трехмерные коды, давать им какие угодно названия (что никак не влияло на игры) и получать гораздо лучшую производительность. Жизнеспособными игро- выми программными интерфейсами и OpenGL, и Direct3D стали только с распро- странение дешевых ЗО-ускорителей для ПК. То, что произошло дальше, заслужива- ет отдельной истории. Грязная игра Разработчики игр начали создавать продукты с использованием OpenGL, предназна- ченные к выпуску на Рождество 1997 года. Microsoft поощряла поставщиков аппа- ратного обеспечения с поддержкой трехмерной визуализации к разработке драйверов Direct3D, а если они хотели работать с OpenGL под Windows 98, то должны бы- ли использовать набор драйверов, который поставлялся Microsoft. В данном наборе применялся Mini-Client Driver (MCD), который позволял поставщикам аппаратного обеспечения легко создавать аппаратные драйверы OpenGL под Windows 98. В ответ на выпущенную SGI реализацию OpenGL растерянные владельцы Microsoft удели- ли много времени настройке собственной реализации, и MCD позволил поставщикам втиснуть в этот код все, исключая собственно команды рисования, которые обрабаты- вались графической картой. Однако Microsoft по-прежнему настаивала, что OpenGL не подходит для разработки игр, a MCD — это средство для стимулирования рынка автоматизированного проектирования на ПК. Практически все основные поставщи- ки компьютеров с поддержкой 3D имели драйверы на основе MCD, которые были продемонстрированы в 1997 года на Конференции разработчиков компьютерных игр (Computer Game Developers Conference). Большинство было успокоено этим фак- том, поскольку было известно, что производители аппаратного обеспечения, которые интенсивно поддерживали использование OpenGL в играх, имели трудности с полу- чением необходимой поддержки своих работ с Direct3D. Поскольку большинство игр разрабатывалось с помощью Direct 3D, выход с таким продуктом на рынок был бы равноценен самоубийству. z Летом 1997 Microsoft заявила, что она не получила лицензии на код MCD после его разработки, а следовательно, поставщики не имеют права выпускать свои драй- веры для Windows 98. Впрочем, они могут перевести основанные на MCD драйверы на Windows NT — платформу рабочих станций, где властвовал OpenGL. В результате производители программного обеспечения, потратившие время на создание OpenGL- версий игр, не смогли выпустить их к Рождеству, производители аппаратного обес- печения не получили нормальных драйверов OpenGL, a Direct3D получил годичное преимущество перед OpenGL в качестве аппаратного стандарта интерфейса для игр. Между тем Microsoft снова начала заявлять, что OpenGL предназначен только для приложений не реального времени, запущенных на рабочих станциях на основе NT, а вот Direct 3D идеально подходит для потребительских игр под Windows 98.
68 Часть I. Классический OpenGL К счастью, ситуация не продлилась слишком долго. Silicon Graphics выпустила собственный набор драйверов OpenGL, основанный на быстрой программной реали- зации под Windows. В наборе драйверов SGI применялся более сложный механизм, чем в MCD, названный Installable Client Driver (ICD). Microsoft мешала произво- дителям аппаратного обеспечения для пользователей использовать ICD, поскольку применять MCD им было гораздо легче (это было до того, как Microsoft выдернула ковер из-под ног этих производителей). В то же время, набор SGI имел легкий в ис- пользовании интерфейс, что делало разработку драйверов не сложнее применения простой модели MCD, при этом улучшалась производительность драйвера. После того как начали появляться аппаратные драйверы для OpenGL под Win- dows 98, разработчики игр снова стали подумывать об использовании OpenGL в про- изводстве игр. Выиграв на старте и теперь имея возможность и дальше тормозить использование OpenGL в пользовательских приложениях, Microsoft уступила и опять согласилась поддерживать OpenGL под Windows 98. В этот раз, однако, SGI отказа- лась от всех маркетинговых работ, направленных на рекламу OpenGL разработчикам игр. Вместо этого две компании стали работать вместе над новым объединенным программным интерфейсом, названным Fahrenheit. Fahrenheit должен был унасле- довать лучшие черты Direct3D и OpenGL в новом унифицированном трехмерном программном интерфейсе (доступном только под Windows на аппаратном обеспе- чении SGI). На тот момент SGI проиграла битву с NT за рынок рабочих станций, поэтому была вынуждена согласиться с требованиями Microsoft. Одновременно ком- пания выпустила новую рабочую станцию NT под торговой маркой SGI при полной поддержке Microsoft. В результате OpenGL потерял своего наибольшего приверженца на платформе пользовательских ПК. Будущее OpenGL Многие в промышленности решили, что соглашение по Fahrenheit — это начало кон- ца OpenGL. Однако на пути к забвению OpenGL случилась забавная вещь: без SGI OpenGL зажил собственной жизнью. Когда OpenGL снова стал широко доступен на потребительском аппаратном обеспечении, разработчикам уже не была нужна SGI или любая другая компания, рекламирующая достоинства OpenGL. OpenGL было лег- ко использовать, он многие годы присутствовал на рынке. Это означало широчайший выбор документации (включая первое издание данной книги), примеров программ, материалов SigGraph и т.д. OpenGL начал процветать. По мере того как все больше разработчиков начинало использовать OpenGL, ста- новилось ясно, кто действительно отвечал за развитие отрасли: разработчики. Чем больше поставлялось приложений с поддержкой OpenGL, тем большим было давле- ние не производителей аппаратного обеспечения с требованием производить лучшее аппаратное обеспечение OpenGL и высококачественные драйверы. Потребителям нет дела до технологии программного интерфейса приложений, они просто хотят работа- ющее программное обеспечение и купят любую графическую карту, лишь бы на ней лучше запускалась их любимая игра. Разработчики беспокоились о времени выхода на рынок, переносимости и повторном использовании кода. Использование OpenGL позволило многим разработчикам лучше удовлетворять требованиям потребителей, а ведь в конечном счете именно потребители платят за все.
Глава 2. Используя OpenGL 69 Время шло, Microsoft прибрала к рукам проект Fahrenheit и со временем совсем сняла его с производства. Остается только гадать, было ли это целью Microsoft с са- мого начала. Direct3D развивался, в него включались новые функциональные воз- можности, аналогичные существующим в OpenGL. Продолжала расти популярность OpenGL как альтернативы технологии визуализации Windows, и теперь этот стандарт широко поддерживался всеми основными операционными системами и аппаратными устройствами. Даже сотовые телефоны с технологией трехмерной графики поддер- живали подмножество OpenGL, названное OpenGL ES. В настоящее время все гра- фические карты для ПК с ЗО-ускорителем имеют драйверы OpenGL и Direct3D. Это объясняется тем, что многие разработчики по-прежнему предпочитают использовать в новых разработках OpenGL. Сейчас OpenGL широко признается и принимается как промышленный стандарт программного интерфейса приложений для трехмерной графики реального времени. OpenGL по-прежнему привлекает разработчиков, и несмотря на политическое давление производители аппаратного обеспечения должны удовлетворять требова- ния разработчиков, которые создают программное обеспечение, работающее на их аппаратуре. В конечном счете доллары потребителей определяют, какой стандарт вы- живет, а разработчики, использующие OpenGL, могут предложить лучшие игры и приложения на большем числе платформ, чем их конкуренты. Всего лишь несколько лет назад разработчики игр вначале создавали свою продукцию на основе Direct 3D от Microsoft, поскольку это был единственный доступный программный интерфейс с аппаратной моделью драйвера под потребительскую систему Windows, а лишь затем (иногда) переносили игру на OpenGL, чтобы она могла запускаться под Windows NT (где не поддерживался Direct3D). В настоящее время многие компании-производители игр и программного обеспечения вначале создают OpenGL-версии своей продукции, а затем переносят их на другие платформы, например Macintosh. Из этого следует, что конкуренция более выгодна, чем политические альянсы. Сейчас OpenGL является предпочтительным программным интерфейсом для ши- рокого диапазона приложений и аппаратных платформ. Это поставило OpenGL в вы- годную позицию с точки зрения преимуществ будущих изобретений в сфере ком- пьютерной графики. Благодаря механизму расширения OpenGL поставщики могли исследовать новые особенности аппаратного обеспечения, не ожидая решения ARB или Microsoft, а передовые производители могли эксплуатировать эти особенности сразу же после появления обновленных драйверов. С добавлением к OpenGL языка затенения (см. часть III книги) OpenGL продемонстрировал свою приспособляемость к требованиям развивающегося конвейера программирования трехмерной графики. Наконец, OpenGL — это спецификация, продемонстрировавшая, что ее можно при- менять к широкому диапазону парадигм программирования. В создании игр для ПК с использованием OpenGL сейчас применяются языки от C/C++ до Java и Visual Basic, и даже такие новые языки, как С#. OpenGL прочно вошел в наш мир.
70 Часть I Классический OpenGL Рис. 2.1. Место OpenGL в типичной программе- приложении Как работает OpenGL? OpenGL — скорее процедурный, чем описательный графический программный ин- терфейс приложений. Вместо того чтобы описывать сцену и то, как она должна выглядеть, программист прописывает шаги, необходимые для получения определен- ного внешнего вида или эффекта. Эти “шаги” включают вызовы многих команд OpenGL. Команды, используются для рисования таких графических примитивов, как точки, линии и многоугольники в трех измерениях. Кроме того, OpenGL поддержива- ет освещение и затенение, наложение текстуры, смешение, прозрачность, анимацию и многие другие специальные эффекты и возможности. OpenGL не включает никаких функций управления окнами, взаимодействия с пользователем или ввода в файл/вывода из файла. Каждая среда хоста (такая, как Microsoft Windows) имеет собственные функции для этой цели и отвечает за реали- зацию средств передачи OpenGL управления рисованием в окне Не существует такого понятия, как “файловый формат OpenGL” для моделей или виртуальных сред Программисты создают среды, подходящие для собственных нужд, а затем аккуратно программируют их, используя низкоуровневые команды OpenGL. Общие реализации Как отмечалось выше, общей является программная реализация Аппаратные реали- зации создаются для конкретного аппаратного устройства — графической карты или генератора изображений. Общая реализация может запускаться практически где угод- но при условии, что система может отображать генерируемые графические образы. На рис. 2.1 показано типичное место OpenGL и общей реализации в запущен- ном приложении. Типичная программа вызывает множество функций, часть кото- рых создает программист, а другие предоставляются операционной системой или библиотекой времени выполнения языка программирования. Приложения Windows, ожидающие вывода на экран, обычно вызывают программный интерфейс Windows, именуемый GDI (Graphics Device Interface — интерфейс графических устройств). GDI содержит методы, позволяющие писать текст в окне, рисовать простые двухмер- ные линии и т.д Обычно производители графических карт поставляют драйвер аппаратного устройства, с которым интерфейсы GDI выводят изображение на монитор Программ-
Глава 2 Используя OpenGL 71 Рис. 2.2. Место OpenGL с аппаратным ускорением в типичной программе- приложении ная реализация OpenGL получает графические запросы от приложений и строит (растеризирует) цветное изображение трехмерной графики. Затем она передает это изображение GDI для отображения на монитор. В другой операционной системе описанный процесс по сути такой же, но вместо GDI применяются присущие этой операционной системе службы дисплея. OpenGL имеет несколько распространенных общих реализаций. Microsoft постав- ляет свою программную реализацию с каждой версией Windows NT, начиная с вер- сии 3 5 и Windows 95 (Service Release 2 и более поздние). Windows 2000 и ХР также поддерживают OpenGL. SGI выпустила программную реализацию OpenGL для Windows, которая суще- ственно превосходила реализацию Microsoft. Хотя официально эта реализация не поддерживается, разработчики периодически ее используют. Другой “неофициаль- ной” программной реализацией OpenGL является MESA 3D, широко поддерживаемая в сообществе пользователей открытого исходного кода. Mesa 3D не имеет лицензии OpenGL, т е. это “что-то OpenGL-подобное”, а не официальная реализация. Если же не учитывать правовые вопросы, это можно считать реализацией OpenGL. Сотрудни- ки Mesa даже сделали хорошую попытку пройти аттестационные испытания OpenGL. Аппаратные реализации Аппаратная реализация OpenGL обычно имеет вид драйвера графической карты. На рис. 2 2 показана ее связь с приложением (подобно тому, как на рис. 2.1 иллюстриру- ется программная реализация) Обратите внимание на то, что вызовы программного интерфейса OpenGL передаются драйверу аппаратного устройства Этот драйвер не передает свой выход GDI Windows с целью вывода на экран, драйвер сопрягается непосредственно с аппаратным обеспечением графического дисплея. Аппаратная реализация часто называется ускоренной реализацией, поскольку трех- мерная графика с аппаратной поддержкой обычно существенно превосходит по ха- рактеристикам только программные реализации. На рис. 2 2 не показано, что иногда часть функциональных возможностей OpenGL реализуется в программном обеспече- нии как часть драйвера, и что другие особенности и функциональные возможности могут непосредственно передаваться аппаратному обеспечению. Данная идея приво- дит нас к следующей теме — конвейеру OpenGL
72 Часть I. Классический OpenGL Вызовы программного интерфейса OpenGL Рис. 2.3. Упрощенная версия конвейера OpenGL Конвейер Слово конвейер используется для описания процесса, который может захватывать несколько отдельных этапов или каскадов. На рис. 2.3 показана упрощенная вер- сия конвейера OpenGL. Когда приложение инициирует вызов функции интерфейса OpenGL, команды помещаются в буфер команд. Со временем этот буфер заполняет- ся командами, данными о вершинах, текстурах и т.д. Когда буфер заполняется (или программно или согласно структуре драйвера), команды и данные передаются на следующий каскад конвейера. Данные о вершинах обычно преобразуются и изначально освещаются. В последу- ющих главах мы больше расскажем о том, что это означает. Пока же просто считайте “преобразование и освещение” математически трудоемким этапом, на котором для данного положения и ориентации объекта пересчитываются точки, используемые для описания геометрии объекта. Расчет освещения выполняется для того, чтобы указать, насколько яркими должны быть цвета в каждой вершине. Когда этот этап завершен, данные подаются на каскад растеризации конвейера. На этом этапе по геометрическим, цветным и текстурным данным создается цветное изображение. Затем изображение помещается в буфер кадров. Буфер кадров — это память устройства графического вывода, посредством которой изображение отобра- жается на экране. На диаграмме конвейер OpenGL представлен упрощенно, но на данный момент этого достаточно, чтобы вы поняли концепцию визуализации трехмерной графики. На высоком уровне данное представление точное, но на низком уровне внутри каждого изображенного прямоугольника имеется множество других составляющих. Кроме того, имеется несколько исключений, например стрелка на рисунке, указывающая, что некоторые команды вообще пропускают этап преобразования и освещения (например, это отображение на экране необработанных данных изображения). Ранние аппаратные ускорители OpenGL были не более чем устройствами быстрой растеризации. Они ускоряли только участок растеризации конвейера. Центральный процессор системы хоста выполнял преобразование и освещение в программной ре- ализации этого фрагмента конвейера. В более мощных (т.е. дорогих) ускорителях преобразование и освещение встраивалось в графический ускоритель. В такой схеме выполнение большей части конвейера OpenGL возлагалось на аппаратное обеспече- ние, что гарантировало более высокую производительность. В настоящее время даже маломощное потребительское аппаратное обеспечение имеет каскад преобразования и освещения на аппаратном уровне. Суммарный эффект этой установки заключает- ся в том, что теперь возможны модели с большей детализацией и создание более сложной графики в реальном времени на недорогом потребительском аппаратном обеспечении. Разработчики игр и приложений могут усиливать этот эффект, предла- гая более детальные и визуально богатые окружения.
Глава 2. Используя OpenGL 73 OpenGL — не язык, а программный интерфейс Большей частью OpenGL не является языком программирования; это программный интерфейс приложения (Application Programming Interface — API). Когда мы говорим “программа основана на OpenGL” или “приложение OpenGL”, то подразумеваем, что она была написана на каком-то языке программирования (например, на С или C++) с вызовами, обращенными к одной или нескольким библиотекам OpenGL. Мы не говорим, что программа использует OpenGL исключительно для рисования. В ней могут объединяться лучшие качества двух графических пакетов. Также OpenGL в программе может использоваться только для решения нескольких конкретных задач, а все остальное будет выполняться посредством графических приложений (напри- мер, Windows GDI) конкретной среды. Единственным исключением из этого эври- стического правила, разумеется, является OpenGL Shading Language (“язык затенения OpenGL”), рассмотренный далее в этой книге. Как программный интерфейс библиотека OpenGL должна выполнять правила вы- зовов, принятых в С, поэтому в этой книге примеры программ написаны на С. Про- граммы на C++ могут легко обращаться к функциям и программным интерфейсам С, так же как в С, с небольшими уточнениями. Большинство программистов на C++ могут также программировать на С, а мы не желаем ущемлять чьи-то возможно- сти или вводить дополнительные преграды для читателя (например, использованием синтаксиса C++). Другие языки программирования (такие как Visual Basic), кото- рые могут вызывать функции из библиотек С, также могут использовать OpenGL, кроме того, возможно связывание OpenGL со многими другими языками программи- рования. Однако использование OpenGL с помощью этих языков ненадежно и здесь не рассматривается. Итак, чтобы сделать концепции максимально простыми и легко переносимыми, в примерах мы будем работать с языком С. Библиотеки и заголовки Хотя OpenGL является “стандартной” программной библиотекой, она имеет мно- го реализаций Microsoft Windows поставляется с поддержкой OpenGL как средства программной визуализации. Это означает, что, когда программа, написанная с исполь- зованием OpenGL, вызывает функции OpenGL, реализация Microsoft выполняет функ- ции трехмерной визуализации, и вы видите результаты в окне приложения. Реальная программная реализация Microsoft находится в динамически подключаемой библио- теке opengl32 . dll, расположенной в системном каталоге Windows. На большинстве платформ библиотека OpenGL сопровождается библиотекой GLU (OpenGL Utility — набор программ OpenGL), которая в Windows располагается в файле glu32 . dll так- же в системном каталоге. Эта библиотека является набором служебных функций, которые выполняют распространенные (но иногда сложные) задачи, например спе- циальные матричные операции, или предоставляют поддержку распространенных типов кривых и поверхностей. Этапы настройки ваших инструментов компиляции для связывания с нужными библиотеками OpenGL могут отличаться для разных инструментов и разных плат- форм. Пошаговые инструкции для Windows, Macintosh и Linux можно найти в соот- ветствующих главах части II книги.
74 Часть I Классический OpenGL Прототипы всех функций, типов и макросов OpenGL содержатся (по договоренно- сти) в файле заголовка gl. h С этим файлом поставляются инструменты программи- рования Microsoft, а также большинство других сред программирования для Windows или других платформ (по крайней мере те, что исконно поддерживают OpenGL) Про- тотипы функций библиотеки GLLJ содержатся в файле glu. h Оба указанных файла обычно расположены в специальной папке, прописанной в команде include. На- пример, в следующем коде представлен типичный исходный заголовок, включаемый в типичную программу Windows, в которой используется OpenGL. #include<windows.h> #include<gl/gl.h> #include<gl/glu.h> Для этой книги мы создали собственный файл заголовка OpenGL. h, который имеет макросы, определенные для различных платформ и операционных систем и включа- ющие правильные заголовки и библиотеки OpenGL Все примеры программ в этой книге включают этот исходный файл. Специфические особенности программного интерфейса OpenGL был разработан умными людьми, имевшими богатый опыт разработки гра- фических программных интерфейсов приложений. Они применили стандартные пра- вила к способу именования функций и объявления переменных Программный ин- терфейс получился простым, понятным и расширяемым. OpenGL пытается макси- мально избежать каких-либо политических мер Политическими мерами (политикой) в данном контексте называются предположения, которые разработчики делают от- носительно того, как программисты будут использовать программный интерфейс. В качестве примеров политики можно привести предположение, что вы всегда будете задавать данные о вершинах в виде значений с плавающей запятой, предположение, что до начала визуализации всегда активизирован туман, или предположение, что ко всем объектам на сцене применяются одинаковые параметры освещения Отме- тим, что, если бы подобной политики не было, многие из развившихся популярных технологий визуализации так и не появились бы. Типы данных Чтобы облегчить переносимость кода OpenGL с одной платформы на другую, в OpenGL определены собственные типы данных Эти типы данных легко отобразить в нормальные типы данных С, с которыми вы можете работать. Однако различные компиляторы и среды имеют собственные правила, касающиеся размера и схем рас- пределения памяти для различных переменных С. Используя определенные OpenGL типы переменных, вы отделяете код от подобных изменений. В табл. 2.1 перечислены типы данных OpenGL, соответствующие им типы данных С в 32-битовых средах Windows (Win32) и подходящие суффиксы для литералов. В книге мы будем использовать суффиксы для всех литеральных значений. Позже вы увидите, что эти суффиксы применяются во многих именах функций OpenGL
Глава 2 Используя OpenGL 75 ТАБЛИЦА 2.1. Типы переменных OpenGL и соответствующие типы данных С Тип данных OpenGL Внутреннее представление Определение в форме типа С Суффикс литералов С GLbyte 8-битовое целое signed char b GLshort 16-битовое целое short s GLint, GLsizei 32-битовое целое long I GLfloat, GLclampf 32-битовое с плавающей запятой float f GLdouble, GLclampd 64-битовое с плавающей запятой double d GLubyte, GLboolean 8-битовое целое без знака unsigned char ub GLushort 16-битовое целое без знака unsigned short us GLuint, GLenum, GLbitfield 32-битовое целое без знака unsigned long Ul Все типы данных начинаются с GL, что обозначает “OpenGL”. После большин- ства из них указываются соответствующие типы данных С (byte, short, int, float и т.д.) Перед некоторыми имеется и, что указывает тип данных без знака. Например, ubyte обозначает тип byte без знака В некоторых случаях приведено более описа- тельное имя, например size, указывающее значение длины или глубины. Например, GLsizei — это переменная OpenGL, обозначающая параметр размера, который пред- ставляется целым числом. Обозначение clamp является подсказкой; предполагается, что значение будет ограничено согласно допустимому диапазону 0,0-1,0 (“зажато”, clamped). Переменные типа GLboolean используются для обозначения условий true (“истина”) и false (“ложь”), GLenum — для перечислимых переменных, a GLbit- f ield — для переменных, содержащих поля двоичных разрядов. Указатели и массивы не имеют специальных обозначений. Массив из 10 перемен- ных типа GLshort объявляется просто как GLshort shorts[10]; Массив из 10 указателей на переменные типа GLdouble определяется следую- щим образом’ GLdouble *doubles[10]; Несколько других типов указателей используются для сплайнов NURBS и поверх- ностей второго порядка. Они требуют более подробного рассмотрения и описываются в следующих главах. Правила именования Для большинства функций OpenGL используются правила именования, позволяющие сообщить, из какой библиотеки пришла функция, сколько она принимает аргумен- тов и какого типа Все функции имеют корень, представляющий соответствующую функции команду OpenGL. Например, glColor3f имеет корень Color. Префикс gl представляет библиотеку gl, а суффикс 3f означает, что функция принимает три ар- гумента с плавающими запятыми. Все функции OpenGL имеют следующий формат. <Префикс библиотекиХКоманда основной библиотекиХНеобязательный счетчик аргументовХНеобязательный тип аргументов>
76 Часть I. Классический OpenGL glColor3f(...) T^i Библиотека GL Основная Число Тип команда аргументов аргументов Рис. 2.4. Разбор функции OpenGL Элементы имени функции OpenGL иллюстрируются на рис. 2.4. Эта простая функ- ция с суффиксом 3f принимает три аргумента с плавающей запятой. Другие разновид- ности этой функции принимают три целых числа (glColor3i), три числа двойной точности (glColor3d) и т.д. Такое добавление числа и типа аргументов (см. табл. 2.1) к концу функций OpenGL облегчает запоминание списка аргументов. Некоторые вер- сии glColor принимают четыре аргумента, дополнительно задавая компонент альфа (прозрачность). В справочных разделах книги эти “семейства” функций перечислены с указани- ем префикса библиотеки и корня. Все вариации glColor (glColor3f, glColor4f, glColor3i и т.д.) представлены одним объектом — glColor. Многие компиляторы C/C++ для Windows предполагают, что любое литеральное значение с плавающей запятой относится к типу double, если с помощью суффикса явно не указано иное. Когда литералы используются для представления аргументов с плавающей запятой, а вы не указали, что эти аргументы относятся к типу float, а не double, компилятор выдаст предупреждение при обработке, обнаружив, что вы передаете величину двойной точности функции, которая по определению принимает только величины с плавающей запятой. Отметим, что это может привести к снижению точности. По мере того как растет программа OpenGL, число предупреждений быст- ро станет измеряться сотнями, и среди них будет трудно заметить синтаксические ошибки. Предупреждения можно отключить, используя подходящую опцию компи- лятора, но мы не рекомендуем так поступать. Гораздо лучше с первого раза написать четкий, переносимый код. Поэтому, чтобы убрать эти предупреждения, скорректи- руйте лучше код (в данном случае, явно используя тип float), но не отключайте потенциально полезные предупреждения. Кроме того, можно поддаться соблазну использовать функции, принимающие ар- гументы двойной точности с плавающей запятой, вместо того, чтобы возиться с ука- занием литералов как величин типа float. Однако во внутреннем представлении OpenGL использует именно величины с плавающей запятой, а использование всего, что отличается от функций обычной точности с плавающей запятой, снижает про- изводительность, поскольку в процессе обработки OpenGL значения преобразуются в тип float. Кроме того, любая величина двойной точности требует вдвое больше памяти, чем величина обычной точности. Для программы, в которой “плавает” очень много чисел, такой удар по производительности может быть очень ощутимым!
Глава 2. Используя OpenGL 77 Независимость от платформы OpenGL — мощный и сложный программный интерфейс, предназначенный для созда- ния трехмерной графики, имеющий более 300 команд, охватывающих все — от уста- новки цвета и отражательных свойств материала до выполнения поворотов и сложных преобразований координат. Возможно, вы удивитесь, узнав, что OpenGL не имеет ни одной функции или команды, связанной с управлением окнами или экраном. Кроме того, не существует функций для ввода с клавиатуры или взаимодействия с помощью мыши. Однако вспомните, что одной из основных целей при разработке OpenGL была независимость от платформы. Вы создаете и открываете окно по-разному на различных платформах. Даже если бы OpenGL имел команду для открытия окна, ис- пользовали бы вы ее или собственный встроенный в операционную систему вызов? Другим вопросом, касающимся платформы, является обработка событий ввода с помощью клавиатуры или мыши в различных операционных системах и средах. Если бы все среды обрабатывали эти события одинаково, мы бы рассматривали толь- ко одну среду, не нуждаясь в “открытом” программном интерфейсе. Однако это не так, и скорее всего на нашем веку этого не будет. Поэтому за независимость OpenGL от платформы приходится платить отсутствием функций операционных систем и гра- фических пользовательских интерфейсов. Используя GLUT Вначале был AUX — дополнительная библиотека OpenGL (OpenGL auxiliary library). Библиотека AUX была создана для содействия обучению и такому написанию про- грамм OpenGL, чтобы программист не занимался деталями конкретной среды — будь то UNIX, Windows или что-либо еще. Используя AUX, вы не сможете написать “окон- чательный” код; скорее он подходит для подготовки фундамента для проверки ваших идей. Нехватка базовых функций GUI ограничивает использование библиотеки для создания полезных приложений. Еще несколько лет назад большинство бродивших по Web примеров использова- ния OpenGL (и в том числе книги по OpenGL!) были написаны с использованием библиотеки AUX. Реализация Windows библиотеки AUX имела чрезвычайно мно- го ошибок и часто сбоила. Другим недостатком с точки зрения современного мира графических пользовательских интерфейсов было отсутствие каких бы то ни было элементов GUI. Вскоре на смену AUX пришла библиотека GLUT, предназначенная для межплат- форменных примеров программирования и демонстраций. GLUT — это сокращение от OpenGL Utility Toolkit (набор инструментов OpenGL, не путайте с GLU — биб- лиотекой инструментов OpenGL). Марк Килгард (Mark Kilgard), работавший в SGI, написал GLUT как более искусную замену библиотеки AUX и включил в нее несколь- ко элементов графического пользовательского интерфейса, по крайней мере для того, чтобы сделать программы-примеры более удобными в системе X Windows. Эта заме- на включала использование всплывающих меню, управление другими окнами и даже обеспечение поддержки джойстика. GLUT не являются публичным достоянием, но она бесплатна и бесплатно ее распространение. Последний дистрибутив GLUT, до- ступный на момент выхода книги, находится на прилагаемом компакт-диске.
78 Часть I. Классический OpenGL В большей части этой книги мы будем использовать GLUT как основу программ. Такое решение объясняется двумя причинами. Во-первых, так большая часть книги будет доступна широкой аудитории, а не только программистам в среде Windows. Немного потрудившись, опытные пользователи Linux или Macintosh смогут настро- ить GLUT в своих средах программирования и исследовать большинство примеров, приведенных в книге. Напомним, что специфические особенности отдельных плат- форм рассмотрены в части II. Во-вторых, используя GLUT, не требуется знания основ программирования гра- фических пользовательских интерфейсов на конкретных платформах Хотя мы объ- ясняем общие концепции, книга написана не о программировании GUI, а об OpenGL. Используя GLUT для представления основ программных интерфейсов приложений, мы также немного облегчаем жизнь неопытным пользователям Win- dows/Macintosh/Linux. Маловероятно, что все функциональные возможности коммерческого приложения будут целиком воплощены в коде, используемом для рисования трехмерной графики, поэтому вы не можете полагаться целиком на GLUT в любых вопросах. Тем не менее библиотеке GLUT принадлежит особая роль в отношении обучающих и демонстраци- онных примеров. Даже опытному программисту в среде Windows легче использовать библиотеку GLUT, чтобы отладить код трехмерной графики перед интеграцией его в завершенное приложение. Настройка среды программирования “Правильной” среды программирования для этой книги не существует. Охватить все возможные способы конфигурирования компилятора и среды программирования нереально Как отмечалось ранее, целью написания книги является обучение OpenGL и программированию на С, а не использованию Visual С+^/С-н-, Project Builder и тд. Если вы не являетесь гуру в использовании конкретной среды и инструментов, об- ратитесь к “платформенно-зависимым” главам части И. Там вы найдете указания по быстрой настройке для любой операционной системы. Помимо библиотек соответствующих платформ потребуется файл заголовка glut.h. Он расположен в папке \tools\glut-3.7\include\GL на компакт-диске. Логичным будет скопировать этот заголовок в ту же папку, где хранятся файлы gl. h и glu.h вашей среды разработки Ваша первая программа Чтобы лучше понять библиотеку GLUT, рассмотрим, пожалуй, самую короткую в ми- ре программу OpenGL, которая была написана с использованием библиотеки GLUT. В листинге 2.1 представлена программа SIMPLE Результат ее выполнения показан на рис. 2 5. Кроме того, по ходу дела вы узнаете несколько вещей об OpenGL! Листинг 2.1. Исходный код SIMPLE — очень простой программы OpenGL #include <OpenGL.h> /////////////////////////////////////////////////////////////////// // Вызывается процедура рисования сцены void RenderScene(void)
Глава 2. Используя OpenGL 79 Рис. 2.5. Результат выполнения программы SIMPLE { // Окно очищается текущим цветом очистки glClear(GL_COLOR_BUFFER_BIT); //В буфер вводятся команды рисования glFlush(); } /////////////////////////////////////////////////////////////////// // Устанавливается состояние визуализации void SetupRC(void) { glClearColor(0.0f, O.Of, l.Of, l.Of); } Illi III III Illi III III III III III III III III Illi Illi III Illi Illi III III Illi // Точка входа основной программы void main(void) { glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); glutCreateWindow("Simple"); glutDisplayFunc(RenderScene); SetupRC(); glutMainLoop() ; } Программа SIMPLE делает немного. При запуске из командной строки (или среды разработки) она создает стандартное окно GUI с надписью Simple и синим фоном. Если вы работаете с Visual C++, то при остановке программы увидите в окне консоли сообщение Press any key to continue. Для завершения программы потребуется нажать любую клавишу. Такая стандартная особенность IDE Microsoft запуска кон- сольных приложений гарантирует, что вы увидите все, что программа выдает на экран (окно консоли) перед закрытием окна. Запуская программу из командной строки, та- кого поведения вы не получите. Если вы запускаете программу двойным щелчком на ее файле в Проводнике, вы мельком увидите окно консоли, которое исчезнет после завершения программы. Указанная простая программа содержит четыре функции библиотеки GLUT (с пре- фиксом glut) и три “настоящих” функции OpenGL (с префиксом gl). Рассмотрим программу построчно, а затем введем новые функции и существенно улучшим пер- вый пример.
80 Часть I Классический OpenGL Заголовок Листинг 2.1 содержит только один включаемый файл: ♦include <OpenGL.h> Файл включает заголовки gl. h и glut. h, вводящие прототипы функций, исполь- зуемых в программе. Тело Переходим к точке входа всех программ С: void main(void) { Программы С и C++ консольного режима работы всегда начинают выполнение с функции main. Опытных фанатов работы с Windows может заинтересовать, где в этом примере находится команда WinMain Ее здесь нет, поскольку мы начали с консольного приложения, и нет необходимости начинать с создания окна и цикла обработки сообщений. С помощью Win32 вы можете создавать графические окна из консольных приложений так же, как создаете консольные окна из приложений GUI. Эти детали спрятаны в недрах библиотеки GLUT. (Помните, что библиотека GLUT разработана так, чтобы скрывать подобные мелочи, зависящие от платформы ) Режим отображения: один буфер Первая строка приведенного кода сообщает библиотеке GLUT, какой тип режима отображения использовать при создании окна: glutlnitDisplayMode(GLUT_SINGLE | GLUT_RGBA); Приведенные метки указывают задействовать окно с простой буферизацией (с од- ним буфером) (GLUT_S INGLE) и режим цвета RGBA (GLUT_RGBA). Простая буфе- ризация означает, что все команды рисования выполняются в отображенном окне Альтернативой является двойная буферизация, когда команды рисования выполня- ются в буфере вне экрана, а затем быстро отображаются в окне. Этот метод часто применяется для создания эффектов анимации и демонстрируется позже в данной главе. Далее по всей книге мы будем использовать режим двойной буферизации. Ре- жим цвета RGBA означает, что вы задаете цвета, указывая различные интенсивности красного, зеленого и синего компонентов. Альтернативой является режим индекси- рования цвета, довольно распространенный и заключающийся в том, что вы задаете цвета с помощью указателей на палитру цветов. Создание окна OpenGL Следующий вызов к библиотеке GLUT создает окно на экране. С помощью приве- денного ниже кода создается окно Simple. glutCreateWindow("Simple"); Единственным аргументом glutCreateWindow является надпись в строке заголовка окна.
Глава 2 Используя OpenGL 81 Отображение обратного вызова Следующая строка кода, относящаяся к GLUT, имеет такой вид: glutDisplayFunc(RenderScene); В этой строке ранее определенная функция RenderScene устанавливается как функция обратного вызова дисплея. Это означает, что GLUT вызывает функцию, указывающую в то место, где нужно нарисовать окно. Такой вызов выполняется, например, при первом отображении окна, его изменении и разворачивании из пикто- граммы. Именно в этом месте мы помещаем вызовы функций визуализации OpenGL. Настроить контекст и вперед! Следующая строка не относится ни к GLUT, ни к OpenGL, — это договоренность, которую мы используем на протяжении всей книги: SetupRC(); В этой функции мы выполняем всю инициализацию OpenGL, которую нужно вы- полнить перед визуализацией. Многие состояния OpenGL устанавливаются только один раз, и их не нужно обновлять при каждой визуализации кадра (экрана, запол- ненного графикой) Последний вызов функции GLUT выполняется в конце программы. glutMainLoop(); Эта функция запускает оболочку GLUT. Определив обратные вызовы для экрана дисплея и других функций (скоро вы их узнаете), вы освобождаете GLUT. Функция glutMainLoop не имеет обратного хода — после того, как вы ее вызвали, она вы- полняется до завершения программы; приложение должно вызывать ее только один раз. Эта функция обрабатывает все сообщения, связанные с операционной системой, нажатием клавиш и т.д., пока вы не завершите программу. Другие функции графического вызова Функция SetupRC содержит единственный вызов функции OpenGL. glClearColor(0.Of, O.Of, l.Of, l.Of); Эта функция устанавливает цвет, используемый для очистки окна. Прототип функ- ции выглядит так: void glClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); В большинстве реализаций OpenGL GLclampf определяется как величина типа float В OpenGL единый цвет представляется как смесь красного, зеленого и синего компонентов. Каждый компонент может представляться величиной, принадлежащей диапазону 0,0-1,0. Это похоже на спецификацию цветов в Windows с использованием макроса RGB для создания значения COLORREF. Различие заключается в том, что в Windows каждый компонент цвета в COLORREF принадлежит диапазону 0-255, что в сумме дает 256 х 256 х 256 или более 16 миллионов цветов. В OpenGL значением каждого компонента могут быть приемлемые значения с плавающей запятой от 0 до 1, следовательно, число возможных цветов практически бесконечно. На практике
82 Часть I Классический OpenGL ТАБЛИЦА 2.2. Некоторые распространенные составные цвета Составной цвет Красный компонент Зеленый компонент Синий компонент Черный 00 00 00 Красный 1 0 00 00 Зеленый 0.0 1 0 00 Желтый 1 0 1.0 00 Синий 00 00 1 0 Пурпурный 1 0 00 1 0 Голубой 00 1 0 1 0 Темно-серый 0 25 0 25 0 25 Светло-серый 0 75 0 75 0 75 Коричныеый 0 60 0 40 0 12 Тыквенно-оранжевый 0 98 0 625 0 12 Пастельный розовый 0 98 0 04 07 Мягкий пурпурный 0 60 0 40 0 70 Белый 1.0 1 0 1 0 цветовые возможности большинства устройств ограничены величиной 24 бит (16 миллионов цветов). Отметим, что и Windows, и OpenGL, получая код цвета, во внутреннем пред- ставлении преобразовывают его в ближайшую точную величину, согласующуюся с возможностями доступной видеоаппаратуры. Распространенные цвета и их коды приведены в табл. 2.2. Эти значения можно использовать в любой из функций OpenGL, связанных с цветом. Последний аргумент функции glClearColor — это компонент альфа, который используются при смешении и создании таких специальных эффектов, как просвечи- вание Просвечиванием называется способность объекта пропускать свет. Предполо- жим, требуется создать кусок красного стекла, за которым находится источник синего света. Синий свет влияет на вид красного стекла (синий + красный = фиолетовый). Для генерации красного цвета полупрозрачного объекта можно использовать компо- нент альфа, при этом объект будет выглядеть как кусок цветного стекла; кроме того, будут видны объекты, находящиеся за ним. В создании эффектов такого типа участ- вуют не только значения альфа. Прочитав главу 6, “Подробнее о цвете и материалах”, вы глубже ознакомитесь с этой темой; до этого момента просто оставляйте значение альфа равным 1. Очистка буфера цвета Все, что мы сделали до этого момента, — это указали OpenGL использовать в качестве цвета очистки синий. В функции Renderscene нам требуется команда, выполняющая собственно очистку: glClear(GL_COLOR_BUFFER_BIT); Функция glClear очищает определенный буфер или комбинацию буферов Бу- фер — это область хранения информации об изображении Красный, зеленый и синий компоненты рисунка обычно объединяются под общим названием буфера цветов или буфера пикселей.
Глава 2 Используя OpenGL 83 В OpenGL существует несколько типов буферов (цвета, глубины, трафарета и на- копления). Все они будут рассмотрены ниже Для понимания следующих глав вам нужно только понять, что буфер цветов — это место, в котором хранится отобража- емое изображение, и что очистка буфера с помощью команды glClear удаляет из окна последний отображенный рисунок. Освобождение очереди Последним идет завершающий вызов функции OpenGL. giFiush(); В этой строке указывается выполнить все невыполненные команды OpenGL; у нас есть только одна такая команда — glClear Во внутреннем представлении OpenGL использует конвейер, последовательно об- рабатывающий команды Команды OpenGL часто выстраиваются в очередь, чтобы потом драйвер OpenGL обработал несколько “команд” одновременно. Такая схема увеличивает производительность, поскольку связь с аппаратным обеспечением проис- ходит медленно. Поставка аппаратному обеспечению за один раз “грузовика” данных гораздо быстрее, чем несколько “маленьких ходок” с каждой командой или инструк- цией Эту особенность работы OpenGL мы рассмотрим в главе 11, “Все о конвейере: большая пропускная способность геометрии”. В короткой программе, приведенной в листинге 2.1, функция giFiush просто сообщает OpenGL, что он должен обрабо- тать команды рисования, переданные на этот момент, и ожидать следующих команд. SIMPLE может не быть самой интересной программой OpenGL по сути, но она демонстрирует основы создания окна с помощью библиотеки GLUT, и на ее примере объясняется, как задавать цвет и очищать окно. Далее мы приукрасим программу, добавив в нее больше функций библиотеки GLUT и OpenGL. Рисование форм с помощью OpenGL Программа SIMPLE создает пустое окно с синим фоном. Сделаем теперь на этом фоне какой-нибудь рисунок. Кроме того, хотелось бы иметь возможность перемещать и изменять размеры окна, на что соответствующим образом реагирует код визуали- зации Модифицированный вариант программы (GLRect) приведен в листинге 2.2, а результат ее выполнения изображен на рис. 2 6 Листинг 2.2. Изображение центрированного прямоугольника с помощью OpenGL #include <OpenGL.h> /////////////////////////////////////////////////////////////////// // Вызывается для рисования сцены void RenderScene(void) { // Очищаем окно, используя текущий цвет очистки glClear(GL_COLOR_BUFFER_BIT); // В качестве текущего цвета рисования задает красный //RGB glColor3f(1.Of, O.Of, O.Of); // Рисует прямоугольник, закрашенный текущим цветом
84 Часть I. Классический OpenGL glRectf(-25.Of, 25.Of, 25.Of, -25.Of); // Очищает очередь текущих команд glFlush(); } /////////////////////////////////////////////////////////////////// // Задает состояние визуализации void SetupRC(void) { // Устанавливает в качестве цвета очистки синий glClearColor(0.Of, O.Of, l.Of, l.Of); } /////////////////////////////////////////////////////////////////// // Вызывается библиотекой GLUT при изменении размеров окна void Changesize(GLsizei w, GLsizei h) { GLfloat aspectRatio; // Предотвращает деление на нуль if(h == 0) h = 1; // Устанавливает поле просмотра с размерами окна glViewport(0, 0, w, h); // Обновляет систему координат glMatrixMode(GL_PROJECTION); glLoadldentity(); // С помощью плоскостей отсечения (левая, правая, нижняя, // верхняя, ближняя, дальняя) устанавливает объем отсечения aspectRatio = (GLfloat)w / (GLfloat)h; if (w <= h) glOrtho (-100.0, 100.0, -100/aspectRatio, 100.0/aspectRatio, 1.0, -1.0); else glOrtho (-100.0 * aspectRatio, 100.0 * aspectRatio, -100.0, 100.0, 1.0, -1.0); glMatrixMode(GL_MODELVIEW); glLoadldentity(); } /////////////////////////////////////////////////////////////////// // Точка входа основной программы void main(void) { glutlnitDisplayMode(GLUT_SINGLE | GLUT_RGB); glutCreateWindow("GLRect"); glutDisplayFunc(RenderScene); glutReshapeFunc(Changesize); SetupRC(); glutMainLoop(); } Рисование прямоугольника Ранее все наши программы очищали экран. Теперь мы добавили к коду рисования следующие строки:
Глава 2. Используя OpenGL 85 Рис. 2.6. Результат выполнения? программы GLRect // В качестве текущего цвета рисования задает красный //RGB glColor3f(l.Of, O.Of, O.Of); // Рисует прямоугольник, окрашенный текущим цветом glRectf(-25.Of, 25.Of, 25.Of, -25.Of); В этих строках с помощью вызова glColor3f задается цвет, используемый для бу- дущих операций рисования (цвет линий и заполнения). После этого функция glRectf отображает окрашенный прямоугольник. Функция glColor3f выбирает цвет так же, как и glClearColor, но при этом не требуется указывать компонент прозрачности альфа (по умолчанию это значение равно 1.0 и соответствует непрозрачному объекту): void glColor3f(GLfloat red, GLfloat green, GLfloat blue); Функция glRectf принимает аргументы с плавающей запятой, на что указывает суффикс f. Число аргументов в имени функции не используется, поскольку все разно- видности glRect принимают четыре аргумента. Использованные в нашем примере аргументы функции glRectf представляют две пары координат— (т1, т/1) и (т2, ?/2): void glRectf(GLfloat xl, GLfloat yl, GLfloat x2, GLfloat y2); Первая пара представляет левую верхнюю вершину прямоугольника, вторая — правую нижнюю. Как OpenGL преобразует эти координаты в реальные точки окна? Это делается в функции обратного вызова Changesize. Функция задается как функция обратного вызова для любого изменения размера окна (когда оно растягивается, увеличивается до максимального размера и т.д.). Это задается так же, как устанавливается функция обратного вызова дисплея. glutReshapeFunc(ChangeSize); При каждом изменении размеров окна необходимо обновлять систему координат. Масштабирование под размеры окна Практически во всех средах с управлением окнами пользователь может в любой момент изменить размеры окна. Даже если вы пишете игру, которая всегда запус- кается в полноэкранном режиме, все равно считается, что окно меняет размер один
86 Часть I. Классический OpenGL 250 Рис. 2.7. Эффекты изменения размера окна при фиксированной системе координат раз — при создании. Когда это происходит, окно обычно отвечает перерисовыванием своего содержимого с учетом новых размеров. Иногда нужно просто вместить ил- люстрацию в окно меньших размеров или отобразить весь рисунок в большем окне с исходными размерами. В нашем случае требуется масштабировать рисунок, что- бы он вмещался в окно независимо от размеров окна или рисунка. Следовательно, очень маленькое окно будет иметь полное, но крошечное изображение, а в большем окне будет демонстрироваться похожий, но больший рисунок. Этот эффект можно наблюдать в большинстве программ рисования, когда вы растягиваете окно, не уве- личивая изображение. Растягивание окна обычно не меняет размер расположенной в нем иллюстрации, но увеличение изображения приводит к ее росту. Установка поля просмотра и отсекающего объема Мы уже обсуждали, как поле просмотра и наблюдаемый объем влияют на диапазон координат и масштабирование двух- и трехмерных рисунков в двухмерном окне на экране компьютера. Теперь мы исследуем установку поля просмотра и координат отсекающего объема в OpenGL. Хотя наш рисунок — это двухмерный плоский прямоугольник, в действительности мы рисуем в трехмерном координатном пространстве. Функция glRectf изображает прямоугольник на плоскости ху при z = 0. Наблюдение ведется вдоль положительно- го направления оси z, в результате при z = 0 вы видите квадрат. (Если вам не совсем понятно сказанное, вернитесь к главе 1, “Введение в трехмерную графику и OpenGL”.) При любом изменении размера окна нужно переопределить поле просмотра и от- секающий объем, учитывая размеры нового окна. В противном случае вы увиди- те эффект, подобный показанному на рис. 2.7, где отображение системы координат в экранные координаты остается постоянным для любых размеров окон.
Глава 2 Используя OpenGL 87 Окно и поле просмотра совпадают Рис. 2.8. Отображение поля просмотра в окно ЖЕ] glViewport(0,0,125,125) 250 Поле просмотра составляет 1/2 размера окна Поскольку изменения размеров окна детектируется и обрабатывается по-разному в разных средах, библиотека GLUT предлагает функцию glutReshapeFunc, реги- стрирующую обратный вызов, который библиотека GLUT запускает при любом из- менении размеров окна. Функция, которую вы передаете glutReshapeFunc, имеет такой прототип: void ChangeSize(GLsizei w, GLsizei h); В качестве описательного имени этой функции мы выбрали ChangeSize и будем его использовать в дальнейших примерах. Функция ChangeSize принимает новую ширину и высоту после любого изме- нения размера окна. Используя две функции OpenGL glViewport и glOrtho, эту информацию можно применить для модификации отображения системы координат в действительные экранные координаты. Определение поля просмотра Чтобы понять, как получается определение поля просмотра, рассмотрим более внима- тельно функцию ChangeSize. Вначале она вызывает glViewport с новой шириной и высотой. Функция glViewport определена следующим образом: void glViewport(GLint х, GLint у, GLsizei width, GLsizei height); Параметры x и у задают левый нижний угол поля просмотра внутри окна, а пара- метры ширины и высоты определяют эти размеры в пикселях. Обычно х и у равны О, но поля просмотра можно использовать для визуализации нескольких рисунков в раз- личных областях окна. Поле просмотра определяет область внутри окна в реальных экранных координатах, которые OpenGL может использовать для рисования (рис. 2.8). После этого текущий отсекающий объем отображается в новое поле просмотра. Если задать поле просмотра, меньшее окна, визуализация будет соответствующим образом масштабирована, как показано на рис. 2.8.
88 Часть I Классический OpenGL +у ~У Рис. 2.9. Декартово пространство Определение отсеченного наблюдаемого объема Последнее, что требуется от функции ChangeSize, — переопределить объем отсе- чения, чтобы характеристическое отношение по-прежнему соответствовало квадрату. Характеристическое отношение — это отношение числа пикселей вдоль единицы длины в вертикальном направлении к числу пикселей вдоль такой же единицы дли- ны в горизонтальном направлении Характеристическое отношение 1 0 определяет квадрат; 0.5 — задает, что на каждые два пикселя в горизонтальном направлении на единицу длины приходится один пиксель в вертикальном направлении на такую же единицу длины. Если вы задаете поле просмотра, не являющееся квадратом, но которое отобра- жается в квадратный объем отсечения, изображение будет искажено. Например, поле просмотра, соответствующее размерам окна, но отображенное в квадратный отсечен- ный объем, приведет к тому, что изображения будут казаться высокими и тонкими в высоких и узких окнах и широкими и короткими в широких и коротких окнах. В по- добном случае квадрат будет выглядеть квадратом только в окне квадратного размера. В нашем примере для представления объема отсечения используется ортографиче- ская проекция. Для создания этой проекции применяется команда OpenGL glOrtho: void glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far); В трехмерном декартовом пространстве значения left и right задают мини- мальную и максимальную координату точек, отображаемых вдоль оси rr; bottom и top делают то же для оси у. Параметры near и far предназначены для оси z, обычно удалению от наблюдателя соответствуют отрицательные значения (рис. 2.9). Многие графические библиотеки используются в командах рисования координаты окна (пиксели). Применение для визуализации системы координат с действительны- ми величинами с плавающей запятой является для новичков одним из сложнейших моментов. Впрочем, после написания нескольких программ к этому привыкаешь
Глава 2 Используя OpenGL 89 Обратите внимание на два вызова функций сразу после кода с glOrtho. // Обновляет систему координат glMatrixMode(GL_PROJECTION); glLoadldentity!); Матрицы и стеки матриц OpenGL рассмотрены в главе 4, “Геометрические пре- образования: конвейер”, где мы подробно обсуждаем эту тему. Сейчас же просто отметим, что наблюдаемый объем вы фактически определяете в проекционной мат- рице. Единственный вызов glLoadldentity необходим, поскольку glOrtho в дей- ствительности не устанавливает объем отсечения, а модифицирует существующий. Эта функция перемножает свои аргументы — матрицу, описывающую текущий объем отсечения, и матрицу, представляющую другой объем отсечения. Сейчас вам нужно знать только то, что прежде чем можно будет выполнять действия с матрицами, с по- мощью glLoadldentity “обновляется” система координат. Без этого “обновления” каждый последующий вызов glOrtho может дать дальнейшее искажение целевого объема отсечения, который, возможно, даже не будет отображать прямоугольник. Последние две строки кода, приведенные ниже, сообщают OpenGL, что все по- следующие преобразования повлияют на модель (то, что мы рисуем). glMatrixMode(GL_MODELVIEW); glLoadldentity(); Мы специально не будем касаться преобразования модели до главы 4. Однако вам нужно знать, как задать необходимые элементы со значениями по умолчанию. В противном случае, если вы начнете экспериментировать, результат выполнения вашей программы может существенно отличаться от ожидаемого. Как удержать квадрат квадратным? За то, чтобы “квадрат” действительно был квадратным, отвечает следующий код: // С помощью плоскостей отсечения (левая, правая, нижняя, верхняя, // ближняя, дальняя) устанавливает объем отсечения aspectRatio = (GLfloat)w / (GLfloat)h; if (w <= h) glOrtho (-100.0, 100.0, -100/aspectRatio, 100.0/aspectRatio, 1.0, -1.0); else glOrtho (-100.0 * aspectRatio, 100.0 * aspectRatio, -100.0, 100.0, 1.0, -1.0); Объем отсечения (видимое координатное пространство) модифицируется так, что левый край всегда проходит по линии х = —100, а правый простирается до 100, если ширина окна не больше его высоты. В таком случае горизонтальные размеры мас- штабируются согласно характеристическому значению окна. Подобным образом низ окна всегда проходит по линии у = —100, а верх простирается до 100, если высота окна не больше его ширины. В таком случае верхняя координата также масштаби- руется с коэффициентом, равным характеристическому отношению. Это позволяет поддерживать квадратную область 200 х 200 (с центром в точке 0,0) вне зависимости от формы окна. Принцип действия таких установок показан на рис. 2.10.
90 Часть I. Классический OpenGL Рис. 2.10. Область отсечения для трех различных окон Анимация с помощью OpenGL и GLUT До этого момента мы обсуждали основы применения библиотеки GLUT для создания окна и использования команд OpenGL для рисования. Часто нужно перемещать или поворачивать сцены, создавать анимационные эффекты. Вернемся к рассмотренно- му выше примеру с нарисованным квадратом и сделаем так, чтобы он рикошетом отскакивал от сторон окна. Можно создать цикл, который непрерывно меняет коор- динаты объекта, перед вызовом функции Renderscene. В результате квадрат будет перемещаться в пределах окна. Библиотека GLUT позволяет регистрировать функцию обратного вызова, ко- торая облегчает установку простых анимированных последовательностей: glut- TimerFunc принимает имя функции, которую нужно вызывать, и время ожидания до вызова функции. void glutTimerFunc(unsigned int msecs, void (*func)(int value), int value); В данном коде указывается, что GLUT должна ожидать msecs миллисекунд перед вызовом функции func. Параметру value можно передать определенное пользо- вателем значение. Функция, вызываемая с помощью этого таймера, имеет следую- щий прототип: void TimerFunction (int value); В отличие от таймера Windows, эта функция срабатывает только один раз. Чтобы со- здать непрерывную анимацию, следует обновить таймер в соответствующей функции. В нашей программе GLRect мы можем заменить жестко запрограммированное положение прямоугольника переменными, а затем постоянно модифицировать эти переменные в функции-таймере. В результате будет казаться, что прямоугольник движется по окну. Рассмотрим пример анимации такого типа. В листинге 2.3 мы модифицировали листинг 2.2, чтобы квадрат отскакивал от внутренних границ окна. Нужно отслеживать положение и размер прямоугольника, а также учитывать любые изменения размера окна.
Глава 2 Используя OpenGL 91 Листинг 2.3. Анимированный прыгающий квадрат ♦include <OpenGL.h> // Исходное положение и размер прямоугольника GLfloat xl = O.Of; GLfloat yl = O.Of; GLfloat rsize = 25; // Величина шага в направлениях х и у (число пикселей, // на которые на каждом шаге перемещается прямоугольник) GLfloat xstep = l.Of; GLfloat ystep = l.Of; // Отслеживание изменений ширины и высоты окна GLfloat windowwidth; GLfloat windowHeight; /////////////////////////////////////////////////////////////////// // Вызывается для рисования сцены void RenderScene(void) { // Очищаем окно, используя текущий цвет очистки glClear(GL_COLOR_BUFFER_BIT); // В качестве текущего цвета рисования задает красный //RGB glColor3f(l.Of, O.Of, O.Of); // Рисует прямоугольник, закрашенный текущим цветом glRectf(xl, yl, xl + rsize, yl - rsize); // Очищает очередь текущих команд и переключает буферы glutSwapBuffers(); } /////////////////////////////////////////////////////////////////// // Вызывается библиотекой GLUT в холостом состоянии (окно не // меняет размера и не перемещается) void TimerFunction(int value) { // Меняет направление на противоположное при подходе // к левому или правому краю if(xl > windowWidth-rsize || xl < -windowwidth) xstep = -xstep; // Меняет направление на противоположное при подходе // к верхнему или нижнему краю if(yl > windowHeight || yl < -windowHeight + rsize) ystep = -ystep; // Перемещает квадрат xl += xstep; yl += ystep; // Проверка границ. Если окно меньше прямоугольника, // который прыгает внутри, и прямоугольник обнаруживает // себя вне нового объема отсечения if(xl > (windowWidth-rsize + xstep)) xl = windowWidth-rsize-1; else if(xl < -(windowwidth + xstep)) xl = - windowsWidth -1; if(yl > (windowHeight + ystep)) yl = windowHeight-1; else if(yl < -(windowHeight - rsize + ystep)) yl = -windowHeight + rsize -1; // Перерисовывает сцену с новыми координатами
92 Часть I Классический OpenGL glutPostRedisplay(); glutTimerFunc(33,TimerFunction, 1); } /////////////////////////////////////////////////////////////////// // Задает состояние визуализации void SetupRC(void) { // Устанавливает в качестве цвета очистки синий glClearColor(O.Of, O.Of, l.Of, l.Of); } /////////////////////////////////////////////////////////////////// // Вызывается библиотекой GLUT при изменении размеров окна void Changesize(GLsizei w, GLsizei h) { GLfloat aspectRatio; // Предотвращает деление на нуль if(h == 0) h = 1; // Устанавливает поле просмотра с размерами окна glViewport(0, 0, w, h); // Обновляет систему координат glMatrixMode(GL_PROJECTION); glLoadldentity(); //С помощью плоскостей отсечения (левая, правая, нижняя, // верхняя, ближняя, дальняя) устанавливает объем отсечения aspectRatio = (GLfloat)w / (GLfloat)h; if (w <= h) { windowWidth = 100; windowHeight = 100 / aspectRatio; glOrtho (-100.0, 100.0, -windowHeight, windowHeight, 1.0, -1.0); } else { windowWidth = 100 * aspectRatio; windowHeight = 100; glOrtho (-windowWidth, windowWidth, -100.0, 100.0, 1.0, -1.0); } glMatrixMode(GL_MODELVIEW); glLoadldentity(); } /////////////////////////////////////////////////////////////////// // Точка входа основной программы void main(void) { glutlnitDisplayMode(GLUT_DOUBLE | GLUT_RGB); glutCreateWindow("Bounce"); glutDisplayFunc(RenderScene); glutReshapeFunc(Changesize); glutTimerFunc(33, TimerFunction, 1); SetupRC();
Гпава 2 Используя OpenGL 93 glutMainLoop() ; } Двойная буферизация Одной из наиболее важных особенностей любого графического пакета является под- держка двойной буферизации. Это позволяет выполнять код рисования, визуализируя при этом закадровый буфер. Затем с помощью команды замены рисунок мгновенно выводится на экран. Двойная буферизация может служить двум целям. Первая: отображение сложных рисунков требует немалого времени, и возможно, вы не хотите видеть каждый этап построения изображения. С помощью двойной буферизации можно сформировать изображение и отобразить его только после завершения. Пользователь никогда не увидит частичного изображения — только после того, как иллюстрация будет готова целиком, она будет показана на экране. Вторая цель двойной буферизации проявляется при анимации. Каждый кадр стро- ится в закадровом буфере и, когда он готов, быстро переключается на экран. Отметим, что библиотека GLUT поддерживает окна с двойной буферизацией. Итак, обратите внимание на следующую строку в листинге 2.3: glutlnitDisplayMode(GLUT_DOUBLE | GLUT_RGB); Мы изменили GLUT_SINGLE на GLUT_DOUBLE. В результате этой модификации весь код, относящийся к рисованию, визуализируется в закадровом буфере. Далее мы также изменили конец функции RenderScene. // Очищает очередь текущих команд и переключает буферы glutSwapBuffers(); } Функцию glFlush мы уже не вызываем. Она уже не нужна, поскольку, выполняя замену буферов, мы неявно выполняем операцию очистки буфера Вследствие указанных изменений получаем анимированный прямоугольник, пока- занный на рис. 2.11. Функция glutSwapBuffers по-прежнему выполняет операцию очистки буфера, даже если вы работаете в режиме одного буфера. Просто восстано- вите вместо GLUT_DOUBLE значение GLUT_SINGLE в примере с прыгающий прямо- угольником, чтобы посмотреть анимацию без двойной буферизации. Вы увидите, что прямоугольник постоянно мигает и запинается — при единственном буфере анимация получается весьма некачественной. Библиотека GLUT является разумно полным каркасом для создания сложных программ-примеров, а возможно, даже завершенных коммерческих приложений (предполагается, что вам не требуются особенности, имеющие отношение к операци- онной системе или графическому пользовательскому интерфейсу). Впрочем, данная книга не посвящена всем нюансам библиотеки GLUT, ее блеску и славе. Выше и в приведенном ниже справочном разделе мы ограничились небольшим подмноже- ством GLUT, необходимым для демонстрации особенностей OpenGL.
94 Часть i. Классический OpenGL Рис. 2.11. Следуй за прыгающим квадратом Машина состояний OpenGL Рисование трехмерной графики является сложным делом. В последующих главах мы рассмотрим множество функций OpenGL. Если дан геометрический объект, на его рисование может повлиять множество фактов. Освещен ли он? Каковы свойства света? Каковы свойства материала? Какую текстуру следует применить (или вообще никакой)? Список можно продолжать очень долго. Такой набор переменных мы называем состоянием конвейера. Машина состоя- ний (или конечный автомат) — это абстрактная модель набора переменных состо- яния, имеющих разные значение, включенные и выключенные и т.д. Задавать все переменные состояния, когда мы пытаемся нарисовать что-то в OpenGL, непрактич- но. Вместо этого в OpenGL реализована модель состояний (или конечный автомат), предназначенная для отслеживания всех переменных состояния OpenGL. Установ- ленное значение состояния остается до тех пор, пока другая функция его не изменит. Многие состояния — это просто метки “включено” или “выключено”. Например, освещение (см. главу 11) либо включено, либо выключено. Геометрия, нарисован- ная без освещения, рисуется без применения к набору цветов расчетов освещения. Любая геометрия, нарисованная после включения освещения, изображается согласно расчетам освещенности. Чтобы включать и выключать переменные состояния подобного типа используется следующая функция OpenGL: void glEnable(GLenum capability); Для отключения переменной используется соответствующая функция: void glDisable(GLenum capability); Освещение, например, можно включить с помощью следующей команды: glEnable(GL_LIGHTING) ; Отключается освещения с помощью такой функции: glDisable(GL_LIGHTING); Если требуется проверить переменную состояния — активизирована она или от- ключена, — OpenGL предлагает следующий удобный механизм: Glboolean gl!sEnabled(GLenum capability);
Глава 2 Используя OpenGL 95 Однако не все переменные состояний могут быть просто включенными или вы- ключенными. Многие из функций OpenGL задают значения, “связанные” до момента изменения. Эти значения также можно в любой момент проверить. Существует це- лый набор функций запроса, позволяющих узнавать значения переменных булевого типа, целых, с плавающей запятой и двойной точности. Эти четыре функции имеют следующие следующие прототипы: void glGetBooleanv(GLenum pname, GLboolean *params); void glGetDoublev(GLenum pname, GLdouble *params); void glGetFloatv(GLenum pname, GLfloat *params); void glGet!ntegerv(GLenum pname, GLint *params); Каждая функция возвращает одно значение или массив значений, хранящий ре- зультаты искомого запроса Различные параметры приведены в справочном разделе ниже (их много). Большинство из них будет для вас сейчас малопонятно, но по мере обучения вы начнете понимать мощь и простоту машины состояний OpenGL. Запись и восстановление состояний OpenGL также имеет удобный механизм для хранения диапазона значений состояния с возможностью их последующего восстановления. Здесь следует ввести понятие стек — удобной структуры данных, позволяющей вталкивать (записывать) значения в стек и впоследствии выталкивать (извлекать) их из стека. Элементы извлекаются из стека в порядке, противоположном тому, в каком они туда помещались. Подобная структура данных называется “последним поступил — первым обслужен” (Last In First Out — LIFO). Такой стек является простым способом сказать: “Запиши, пожалуйста, вот это” (поместить элемент в стек), а немного позже сказать “Дай мне то, что я только что записал” (извлечь элемент из стека) Когда вы доберетесь до главы 4, то увидите, что концепция стека играет важную роль в действиях с матрицами. Одну переменную состояния OpenGL или целый набор связанных значений переменных состояния можно поместить в стек атрибутов с помощью следую- щей команды: void glPushAttrib(GLbitfield mask); Приведенная ниже команда позволяет извлекать соответствующие значения. void glPopAttrib(GLbitfield mask); Обратите внимание на то, что аргумент этих функций — битовое поле. Это означа- ет, что вы используете побитовую маску, позволяющую с помощью операции поби- тового ИЛИ (в С — используя оператор | ) указывать несколько значений переменных состояния в одном вызове функции. Например, выполнив приведенную ниже коман- ду, вы запишите состояния освещения и текстуры. glPushAttrib(GL_TEXTURE_BIT | GL_LIGHTING_BIT) ; Полный список всех переменных состояния OpenGL, которые можно записывать и восстанавливать с помощью указанных функций, приводится в справочных разделе.
96 Часть I Классический OpenGL Ошибки OpenGL В любом проекте требуется написать надежную и устойчивую программу, которая бы учтиво общалась с пользователями и была достаточно гибкой. Графические про- граммы, использующие OpenGL, не являются исключением, и если вы хотите, чтобы программа выполнялась гладко, следует учесть ошибки и непредвиденные обстоя- тельства. OpenGL предлагает полезный механизм, позволяющий выполнить “сани- тарную проверку” кода. Эта возможность бывает полезной, поскольку невозможно сказать, является результат выполнения программы расчетом орбитальной станции Freedom или расчетом орбитальной станции Melted Crayon! То плохое, что случается с хорошим кодом Во внутреннем представлении OpenGL поддерживает шесть признаков ошибки. Каж- дый признак представляет свой тип ошибки. Когда происходит одна из этих ошибок, устанавливается соответствующая метка Проверить, установлены ли какие-либо мет- ки, можно с помощью вызова glGetError. Glenum glGetError(void); Функция glGetError возвращает одно из значений, перечисленных в табл. 2 3. Библиотека GLU определяет еще три ошибки, но все они отображаются в две уже су- ществующих метки. Если установлено несколько меток, glGetError возвращает од- но значение. Это значение очищается при вызове glGetError, после чего glGetEr- ror будет возвращать либо другую метку ошибки, либо GL_NO_ERROR. Обычно glGetError удобно вызывать в цикле, продолжающем поиск флагов ошибки, по- ка не будет возвращено значение GL_NO_ERROR. Чтобы получить строку, описывающую метку ошибки, можно использовать дру- гую функцию библиотеки GLU — gluErrorString. const GLubyte* gluErrorString(GLenum errorCode); Данная функция принимает в качестве единственного аргумента метку ошибки (которая возвращается функцией glGetError) и возвращает статическую строку, описывающую эту ошибку. Например, метка ошибки GL_INVALID_ENUM возвращает следующую строку: invalid enumerant Сделайте себе заметку на память: если ошибка вызвана неверным вызовом OpenGL, вызов команды или функции будет проигнорирован. Единственным ис- ключением являются функции (описаны в следующих главах), которые в качестве аргументов принимают указатели на ячейки памяти (если указатель неверный, это может вызвать сбой программы), и сообщения о недостаточной памяти Если вы по- лучаете сообщение “out of memory”, можно только гадать о том, что в этот момент будет изображено на экране!
Глава 2. Используя OpenGL 97 ТАБЛИЦА 2.3. Коды ошибок OpenGL Код ошибки Описание GL_INVALID_ENUM The enum argument is out of range (“Аргумент перечня вне диапазона”) GL_INVALID_VALUE The numeric argument is out of range (“Численный аргумент вне диапазона”) GL_INVALID_OPERATION The operation is illegal in its current state ("Операция неприемлема в текущем состоянии”) GL_STACK_OVERFLOW The command would cause a stack overflow (“Команда вызовет переполнение стека”) GL_STACK_UNDERFLOW The command would cause a stack underflow (“Команда вызовет опустошение стека”) GL_OUT_OF_MEMORY Not enough memory is left to execute the command (“Недостаточно памяти для выполнения программы") GL_TABLE_TOO_LARGE The specified table is too large ("Заданная таблица слишком большая”) GL_NO_ERROR No error has occurred (“Ошибок нет”) Определение версии Как отмечалось ранее, иногда выгодно знать поведение определенной реализации. Если вы знаете, что работаете с графической картой определенного производителя, то можете полагаться на известные операционные характеристики, улучшая за их счет свою программу Также вы можете обуславливать использование версий драйверов определенных производителей, не старее указанных. Таким образом, вам нужно за- просить OpenGL о производителе и номере версии аппарата визуализации (драйвера OpenGL). Библиотеки GL и GLU могут возвращать версии и информацию о произ- водителе, касающуюся их самих Для запроса информации о библиотеке GL можно вызывать функцию glGet- String. const GLubyte *glGetString(GLenum name); Эта функция возвращает статическую строку, описывающую запрошенный ас- пект библиотеки GL Приемлемые значения параметров перечислены под заголовком glGetString в справочном разделе; также там указаны аспекты библиотеки GL, которые они представляют Соответствующая функция gluGetString имеется и в библиотеке GLU const GLubyte *gluGetString(GLenum name); Она возвращает строку, описывающую затребованные аспекты библиотеки GLU. Приемлемые параметры перечислены под заголовком gluGetString в справочном разделе, также там указаны аспекты библиотеки GLU, которые они представляют.
98 Часть I Классический OpenGL Получение подсказки с помощью glHint Старая поговорка гласит: “Существует более одного способа содрать шкуру с кош- ки”. То же справедливо и для алгоритмов трехмерной графики. Часто для выигрыша в производительности необходим компромисс, а возможно, более важным вопро- сом является визуальная точность, а производительность — лишь один из многих параметров. Часто реализация OpenGL содержит два способа выполнения постав- ленной задачи — быстрый способ, несколько ухудшающий качество, и медленный — улучшающий качество. Функция glHint позволяет задавать для различных типов операций предпочтения, касающиеся качества или скорости Эта функция определя- ется следующим образом: void glHint(GLenum target, GLenum mode); Параметр target позволяет задавать типы поведения, которые вы желаете мо- дифицировать. Эти значения, перечисленные под заголовком glHint в справочном разделе, включают подсказки относительно точности тумана и защиты от наложения. Параметр mode сообщает OpenGL, что для вас важнее — более быстрая визуализация или более красивое изображение — или что этот вопрос вас не волнует (единственный способ вернуться к поведению по умолчанию) Однако помните, что для принятия на обработку вызовов в glHint не требуются все реализации, данная функция является единственной в OpenGL, поведение которой целиком определяется производителями. Использование расширений Поскольку OpenGL является “стандартным” программным интерфейсом приложе- ний, вы могли подумать, что поставщики аппаратного обеспечения могут конкури- ровать только на основе производительности и, возможно, качества предлагаемого изображения. Тем не менее сфера трехмерной графики очень конкурентоспособна, и поставщики аппаратного обеспечения постоянно изобретают что-то новое, при- чем не только в областях производительности и качества, но и в том, что касается графики и специальных эффектов. OpenGL способствует инновационным порывам поставщиков посредством механизма расширений Этот механизм действует двояко. Во-первых, производители могут добавлять новые функции к программному интер- фейсу OpenGL. Во-вторых, могут добавляться новые токены, которые распознаются такими существующими функциями OpenGL, как glEnable. Использование новых токенов является вопросом добавления поставляемого про- изводителем файла заголовка к своему проекту Поставщики должны регистрировать свои разрешения в OpenGL ARB, благодаря чему один поставщик не сможет ис- пользовать значение, кем-то уже использованное. На компакт-диске, прилагаемом к книге, имеется файл заголовка glext.h, содержащий наиболее распространен- ные разрешения.
Глава 2 Используя OpenGL 99 Проверка расширения Уже давно прошли те дни, когда игры перекомпилировались на конкретные гра- фические карты. Вы уже видели, что можно получить строку, идентифицирующую поставщика и версию драйвера OpenGL Кроме того, вы можете получить строку, со- держащую идентификаторы всех расширений OpenGL, поддерживаемых драйвером. С помощью одной строки кода вы получаете символьный массив имен расширений. const char *szExtensions = glGetString(GL_EXTENSIONS); Эта строка содержит разделенные пробелами имена всех расширений, поддер- живаемых драйвером. Получив эту строку, вы можете найти в ней идентификатор нужного разрешения Например, чтобы быстро найти расширение, применяющееся в Windows, можно использовать такой код: if (strstr(extensions, "WGL_EXT_swap_control" != NULL)) { wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC) wglGetProcAddress("wglSwapIntervalEXT"); if(wglSwapIntervalEXT != NULL) wglSwapIntervalEXT(1); ) Применяя этот метод, вы также должны гарантировать, что символ, следующий за именем расширения, — пробел или NULL. Что будет если, например, это расширение заменено расширением WGL_EXT_swap_control2? В таком случае функция времени выполнения С, именуемая strstr, по-прежнему найдет первую строку, но вы не сможете предположить, что второе расширение ведет себя точно так же, как первое. В папке \common на компакт-диске имеется более надежная функция: int gltIsExtSupported(const char *extension); Эта функция возвращает 1, если именованное расширение поддерживается, и 0 — в противном случае. Папка \common содержит полный набор вспомогательных и по- лезных функций, используемых с OpenGL, многие из которых описываются в этой книге. Прототипы всех функций записаны в файле gltools.h. Приведенный пример также объясняет, как узнать о новых функциях OpenGL под Windows. Функция wglGetProcAddress возвращает указатель на имя функции (рас- ширения) OpenGL. Получение указателей на расширение отличается для разных опе- рационных систем и более подробно рассмотрено в части II. Указанные расширения для Windows и определение (PFNWGLSWAPINTERVALEXTPROC) типа функции располо- жены в файле заголовка wglext.h, также имеющемся на компакт-диске. Это весьма важное расширение мы рассмотрим в главе 13, “Wiggle. OpenGL в системе Windows”. Между тем нам снова пригодится библиотека gltools, имеющая такую функцию: void *gltGetExtensionPointer(const char *szExtensionName); Эта функция предлагает платформенно-независимый упаковщик (wrapper), возвра- щающий указатель на именованное расширение OpenGL. Реализация этой функции имеется в файле GetExtensionPointer.c, который следует включать в проекты, если вы желаете ее использовать.
100 Часть I. Классический OpenGL ТАБЛИЦА 2.4. Примеры префиксов расширений OpenGL Префикс Поставщик SGI_ АТ1_ NV_ 1ВМ_ WGL_ ЕХТ_ ARB_ Silicon Graphics ATI Technologies NVidia IBM Microsoft Совместимо с продукцией разных производителей Одобрено ARB Чье это расширение? Используя расширения OpenGL, вы можете улучшать производительность визуали- зации и визуальное качество или даже добавлять специальные эффекты, поддержива- емые только аппаратурой определенного производителя. Но как узнать, кому принад- лежит расширение? Обычно для этого достаточно посмотреть на имя расширения. Каждое расширение имеет префикс из трех букв, идентифицирующий его источник. Несколько идентификаторов расширений представлены в табл. 2.4. Довольно обычной является ситуация, когда один производитель поддерживает расширения другого. Например, некоторые расширения NVidia весьма популярны и поддерживаются на аппаратном обеспечении ATI. Когда это происходит, конкури- рующие производители должны придерживаться спецификации (деталям того, как должно работать расширение) первоначального производителя. Часто все производи- тели соглашались, что расширение — это полезная штука, и в таких случаях расши- рение имеет префикс ЕХТ_, указывающий, что (предположительно) расширение не зависит от поставщика и широко поддерживается в различных реализациях. Наконец, имеются также расширения, одобренные ARB. Спецификация этих рас- ширений была рассмотрена OpenGL ARB. Обычно такие расширения указывают, что некоторые новые технологии или функции находятся на последнем этапе апробации перед включением в основную спецификацию OpenGL. Более подробно принципы противостояния основного интерфейса OpenGL и расширений OpenGL мы рассмот- рим в части III. Как в среде Windows работать с OpenGL после версии 1.1 Большинство программистов, работающих под Windows, используют средства раз- работки Microsoft, т.е. среды разработки Visual C++ или более новую Visual C++ .NET. Программная реализация OpenGL под Windows, сделанная Microsoft, вклю- чает только функции и токены, определенные в спецификации OpenGL версии 1.1. С того времени вышли версии 12, 1.3, 14, 1 5 и 2 0 Это означает, что некоторые особенности OpenGL недоступны пользователям файлов заголовков OpenGL, вклю- ченных в средства разработки Microsoft. Производители других операционных си- стем и инструментов лучше следят за обновлением версий, поэтому при компиляции различных программ в среде Macintosh OS X и Linux возникнет меньше проблем
Глава 2 Используя OpenGL 101 Тем не менее временами OpenGL кажется движущимся объектом, особенно когда вопросы межплатформенной совместимости сталкиваются с последними улучшени- ями OpenGL. Файл заголовка glext.h, расположенный в папке \common, содержит константы и прототипы функций для большинства функциональных возможностей OpenGL, введенных после версии 1 1, и уже является частью стандартных инструмен- тов разработки на некоторых платформах. Этот заголовок включен специально для разработчиков программ под Windows, тогда как разработчики, использующие Мас, например, будут использовать заголовки glext.h, включенные в среды разработки XCode или Project Workbench Кроме сложностей, связанных с файлом заголовка glext.h и функцией glt- GetExtensionPointer в gltools, ничто не помешает вам повторить примеры из этой книги и запустить их с помощью множества компиляторов и сред разработки. Резюме Мы рассмотрели множество тем. Был представлен OpenGL, немного рассказано о его истории, описан набор инструментов OpenGL (GLUT) и основы написания программ, в которых применяется OpenGL. С помощью GLUT мы продемонстрировали самый простой способ создания окна и рисования в нем с помощью команд OpenGL. Вы узнали, как применять библиотеку GLUT для создания окон, размер которых можно менять, и как создавать простую анимацию. Кроме того, был представлен процесс использования OpenGL для рисования — выбора и смешения цветов, очистки экрана, изображения прямоугольника и задания поля просмотра и объема отсечения для такого масштабирования изображений, чтобы они согласовывались с размером окна. Мы обсудили различные типы данных OpenGL и заголовки, требуемые для создания программ, использующих OpenGL. Имея лишь немного кода, вы можете испробовать другие идеи. В основе прак- тически всего, что вы будете делать, лежит машина состояний OpenGL, а механизм расширений гарантирует, что доступны все особенности OpenGL, поддерживаемые драйвером аппаратного обеспечения, вне зависимости от средств разработки. Кроме того, из этой главы вы узнали, как проверить ошибки OpenGL в процессе работы, чтобы убедиться, что вы не выполняете никаких неприемлемых изменений состо- яния или команд визуализации Имея такую базу, можете приступать к изучению следующих глав.
102 Часть I Классический OpenGL Справочная информация glClearColor Цель: Установить цвет и значение альфа, используемые при очистке буферов цвета Включаемый файл: <gl.h> Синтаксис: void glClearColor(GLclampf red, GLclampf green, Описание: GLclampf blue, GLclampf alpha); Эта функция задает значения заполнения, которые будут Параметры: red применяться при очистке красного, зеленого, синего и альфа-буферов (в сумме называются буфером цвета). Заданные значения принадлежат диапазону [O.Of, 1 Of] Красный компонент значения заполнения (тип GLclampf) green Зеленый компонент значения заполнения (тип GLclampf) blue Синий компонент значения заполнения (тип GLclampf) alpha Альфа-компонент значения заполнения (тип GLclampf) Что возвращает: Ничего gIDisable, glEnable Цель: Деактивизировать или активизировать элементы состояния OpenGL Включаемый файл: <GL/gl.h> Синтаксис: void gIDisable(GLenum feature); glEnable Описание: gIDisable деактивизирует функцию рисования OpenGL, a glEnable активизирует функцию рисования OpenGL Параметры: feature (тип GLenum) Элемент для активизации или дсактивизации Полный список состояний представлен в спецификации OpenGL и постоянно дополняется новыми элементами от ARB и новых расширений OpenGL от поставщиков аппаратного обеспечения Для примера в табл 2 5 приведено несколько состояний, которые можно включать и выключать Что возвращает: Ничего См. также: gllsEnabled, glPopAttrib, glPushAttrib
Глава 2 Используя OpenGL 103 ТАБЛИЦА 2.5. Элементы, активизируемые/деактивизируемые с помощью glEnable/glDisable Элемент Описание GL_BLEND GL_CULL_FACE GL_DEPTH_TEST GL_DITHER GL_FOG GL_LIGHTING GL_LIGHTx GL_POINT_SMOOTH GL_LINE_SMOOTH GL_LINE_STIPPLE GL_POLYGON_SMOOTH GL_SCISSOR_TEST GL_STENCIL_TEST GL_TEXTURE_xD GL_TEXTURE_CUBE_MAP GL_TEXTURE_GEN_x Смешение цветов Отбор многоугольников Проверка глубины Добавление псевдослучайного шума Режим тумана OpenGL Освещение OpenGL х-й источник света OpenGL (минимум. 8) Защита от наложения точек Защита от наложения линий Придание шероховатости линии Защита от наложения многоугольников Разрешено отсечение Проверка шаблона х-мерная текстура (1, 2 или 3) Наложение текстуры на куб Генерация текстуры для х (S, Т, R или О) gIFinish Цель: Инициирует завершение всех предыдущих команд OpenGL Синтаксис: void gIFinish (void) ; Описание: Команды OpenGL обычно выстраиваются в очередь и выполняются пакетами с целью оптимизации производительности. Команда gIFinish указывает выполнить все незаконченные команды OpenGL. В отличие от glFlush, эта функция не возвращается, пока не завершатся все операции визуализации Что возвращает: Ничего См. также: glFlush (); glFlush Цель: Опорожнить все очереди команд и буферы OpenGL Синтаксис: void glFlush (void) ; Описание: Команды OpenGL обычно выстраиваются в очередь и выполняются пакетами с целью оптимизации производительности Этот принцип может варьироваться для различного аппаратного обеспечения, драйверов и реализаций OpenGL. Команда glFlush инициирует выполнение всех ожидающих команд. Все действия должны завершаться “в конечное время”. По сути, это то же, что асинхронное выполнение графических команд, поскольку возвращается glFlush Что возвращает: Ничего См. также: gIFinish
104 Часть I Классический OpenGL gIGetXxxxv Цель: Извлечь числовое значения состояния или массив значений Варианты: void glGetBooleanv(GLenum value, Glboolean Mata); void glGetlntegervfGLenum value, int Mata); void glGetFloatvfGLenum value, float Mata); void glGetDoublev(GLenum value, float Mata); Описание: Многие переменные состояния OpenGL определяются символьными константами. Значения этих переменных состояния можно извлечь с помощью команды gIGetXxxxv. Полный список значений переменных состояния OpenGL занимает более 28 страниц и его можно найти в табл. 6.6 спецификации OpenGL, включенной на компакт-диск Что возвращает: Соответствующий буфер заполняется информацией о состоянии OpenGL gIGetError Цель: Проверить ошибки OpenGL Синтаксис: GLenum gIGetError(void); Описание: Возвращает коды ошибок OpcnGL, перечисленные в табл 2 3 Коды ошибок очищаются при проверке, одновременно могут быть активными несколько меток ошибки. Чтобы извлечь все ошибки, функцию следует вызывать повторно до тех пор, пока она не вернет значение gl_no_error Что возвращает: Один из кодов ошибки OpenGL, перечисленных в табл 2 3 gIGetString Цель: Извлечь описательную строку с информацией о реализации OpenGL Синтаксис: const GLubyte* gIGetString(GLenum name); Описание: Возвращает массив символов, описывающих некоторый аспект текущей реализации OpenGL Параметр GL_vendor возвращает название поставщика. gl_renderer зависит от реализации и может содержать название торговой марки или поставщика. GL_version возвращает номер версии, за которым после пробела следует информация поставщика. gl_extensions возвращает список разделенных пробелами имен расширений, поддерживаемых реализацией Что возвращает: Постоянный массив байтов (строка символов), содержащий затребованную информацию
Глава 2 Используя OpenGL 105 glHint Цель: Разрешить необязательный контроль над определенными аспектами поведения при визуализации Синтаксис: void glHint(GLenum target, GLenum hint); Описание: Некоторые аспекты поведения GL можно контролировать с помощью подсказок. Приемлемы следующие подсказки: GL_NICEST, GL_FASTEST И GL_DONT_CARE. Подсказки позволяют программистам задавать приоритеты: качество (GL_NICEST), производительность (GL_FASTEST) или использовать поведение, принятое по умолчанию (GL_DONT_CARE) GL_PERSPECTIVE_CORRECTION_HINT — желаемое качество параметрической интерполяции GL_POINT_SMOOTH_HINT — желаемое качество дискретизации точек GL_line_smooth_hint — желаемое качество дискретизации линий GL_POLYGON_SMOOTH_HINT — желаемое качество дискретизации многоугольников GL_FOG_HINT — расчет тумана по вершинам (GL_FASTEST) или по пикселям (GL_NICEST) GL_GENERATE_MIPMAP_HINT — качество и производительность автоматической генерации множественных изображений GL_TEXTURE_COMPRESSION_HINT — качество И производительность сжатия текстурных изображений Что возвращает: Ничего gllsEnabled Цель: Проверить, активизирована ли переменная состояния OpenGL Синтаксис: void glIsEnabled(GLenum feature); Описание: Многие переменные состояния OpenGL можно включить и выключить с помощью glEnable или gIDisable. Позволяет запрашивать, активизирована ли переменная состояния. Список переменных состояния, которые можно запрашивать, приведен в табл. 2.5 Что возвращает: Ничего См. также: glEnable, gIDisable
106 Часть I Классический OpenGL glOrtho Цель: Установить или модифицировать границы объема отсечения Синтаксис: void glOrtho(GLdouble left, GLdouble right, Описание: GLdouble bottom, GLdouble top, GLdouble near, GLdouble far); Описывает параллельный объем отсечения. Такая проекция Параметры: left означает, что объекты, удаленные от пользователя, не кажутся меньше (в противоположность перспективной проекции). Если рассматривать объем отсечения в декартовых координатах, left и right будут минимальным и максимальным значениями х; top и bottom — минимальным и максимальным значениями у, a near и far — минимальным и максимальным значениями z Крайняя левая координата объема отсечения (тип GLdouble) right Крайняя правая координата объема отсечения (тип GLdouble) bottom Крайняя нижняя координата объема отсечения (тип GLdouble) top Крайняя верхняя координата объема отсечения (тип GLdouble) near Максимальное расстояние от начала координат к наблюдателю (тип GLdouble) far Максимальное расстояние от начала координат от наблюдателя (тип GLdouble) Что возвращает: Ничего См. также: glViewport glPushAttrib/glPopAttrib Цель: Записать и восстановить набор связанных переменных состояния OpenGL Синтаксис: void glPushAttrib(GLbitfield mask); void glPopAttrib(GLbitfield mask); Описание: OpenGL позволяет записывать и извлекать целые группы переменных состояния. Приведенные функции помещают эти группы в стек атрибутов и позволяют выталкивать их из стека. Полный список групп атрибутов приведен в табл. 2.6 Что возвращает: Ничего
Глава 2 Используя OpenGL 107 ТАБЛИЦА 2.6. Группы атрибутов OpenGL Константа Атрибуты GL_ACCUM_BUFFER_BIТ GL_COLOR_BUFFER_BIT GL_CURRENT_BIT GL_DEPTH_BUFFER—BIT GL_ENABLE—BIT GL_EVAL_BIT GL_FOG_BIT GL_HINT_BIT All GL_LIGHTING—BIT GL—LINE—BIT GL_LIST_BIT GL—MULTISAMPLE—BIT GL_PIXEL—MODE—ВIT GL_POINT_BIT GL_POLYGON—BIT GL_POLYGON—STIPPLE_BIT GL—SCISSOR—BIT GL_STENCIL—BUFFER—BIT GL_TEXTURE—BIT GL_TRANSFORM—BIT GL_VIEWPORT—BIT GL_ALL—ATTRIB—BITS Установки буфера накопления Установки буфера цвета Текущий цвет и координаты Установки буфера глубины Все активизированные метки Установки устройства оценки Установки тумана Подсказки OpenGL Установки освещения Установки линий Установки таблиц отображений Множественная выборка Пиксельный режим Установки точек Установки режима многоугольников Установки фактуры многоугольников Установки тестов разрезания Установки буфера шаблонов Установки текстур Установки преобразований Установки поля просмотра Все состояния OpenGL gIRect Цель: Нарисовать плоский прямоугольник Варианты: void glRectd(GLdouble xl, GLdouble yl, GLdouble x2, GLdouble y2); void glRectf(GLfloat xl, GLfloat yl, GLfloat x2, GLfloat y2); void glRecti(GLint xl, GLint yl, GLint x2, GLint y2); void glRects(GLshort xl, GLshort yl, GLshort xl, GLshort y2); void glRectdv(const GLdouble *vl, const GLdouble *v2); void glRectfv(const GLfloat *vl, const GLfloat *v2); void glRectiv(const GLint *vl, const GLint *v2); void glRectsv(const GLshort *vl, const GLshort *v2); Описание: Приведенная функция предлагает простой метод задания прямоугольника через противоположные вершины Прямоугольник изображается на плоскости ху при z = 0 Параметры: xl, yl Задаст левый верхний угол прямоугольника х2, у2 Задает правый нижний угол прямоугольника *vl Массив двух значений, задающих левый верхний угол Также может описываться как v 1 [2] *v2 Массив двух значений, задающих правый нижний угол Также может описываться как v2[2] Что возвращает: Ничего
108 Часть I Классический OpenGL glViewport Цель: Задать участок окна, где OpenGL может выполнять рисование Синтаксис: void glViewport(GLint х, GLint у, GLsizei width, GLsizei height); Описание: Задает область в окне для отображения координат объема отсечения в физические координаты окна Параметры: х (тип GLint) Число пикселей от левого края окна до начала поля просмотра у (тип GLint) Число пикселей от низа окна до начала поля просмотра width Ширина поля просмотра в пикселях (тип GLsizei) height (тип GLsizei) Высота поля просмотра в пикселях Что возвращает: Ничего См. также: glOrtho gluErrorString Цель: Возвращает объяснение кода ошибки OpenGL в виде строки символов Синтаксис: const GLubyte* gluErrorString(GLenum errCode); Описание: Приведенная функция возвращает строку ошибки, соответствующую коду ошибки, который вернула функция glGetError Параметры: errCode Код ошибки OpenGL Что возвращает: Постоянный указатель на строку ошибки OpenGL См. также: glGetError glutCreateWindow Цель: Создать окно, в котором OpenGL может выполнять рисование Синтаксис: int glutCreateWindow(char ★лате); Описание: Создаст в GLUT окно верхнего уровня Созданное окно становится текущим Параметры: пате (тип char*) Что возвращает: Надпись на окне Целое число, единственным образом определяющее созданное окно См. также: glutlnitDisplayMode
Глава 2 Используя OpenGL 109 ТАБЛИЦА 2.7. Значения масок для характеристик окна Значение маски Значение GLUT_SINGLE GLUT_DOUBLE GLUT_RGBA ИЛИ GLUT_RGB GLUT_DEPTH GLUT_LUMINANCE GLUT_MULTISAMPLE GLUT_STENCIL GLUT_STEREO GLUT_ACCUM GLUT_ALPHA Задает окно с обычной буферизацией Задает окно с двойной буферизацией Задет окно с режимом RGBA Задет 32-битовый буфер глубины Указывает освещать только буфер цвета Задает буфер цвета с множественной выборкой Задает буфер шаблонов Задает буфер стереоцвета Задает буфер накопления Задает целевой альфа-буфер glutDisplayFunc Цель: Задать функцию обратного вызова дисплея для текущего окна Синтаксис: void glutDisplayFunc(void (*func)(void); Описание: Сообщает GLUT, какую функцию вызывать, когда нужно рисовать содержимое окна. Это может произойти при изменении размера окна, открытии окна или когда от GLUT потребуют обновить содержимое в ответ на вызов функции glutPostRedisplay. Обратите внимание на то, что GLUT не вызывает явно glFlush или glutSwapBuf fers после вызова приведенной функции Параметры: func Имя функции, выполняющей визуализацию Что возвращает: Ничего См. также: glFlush, glutSwapBuffers, glutReshapeFunc glutlnitDisplayMode Цель: Инициализировать режим отображения окна OpenGL библиотеки GLUT Синтаксис: void glutlnitDisplayMode(unsigned int mode); Описание: Этой первая функция, которую нужно вызывать в программе, основанной на GLUT, для задания окна OpenGL. Задает характеристики окна, которое OpenGL будет использовать в операциях рисования Параметры: mode (тип unsigned int) Маска или побитовая комбинация масок из табл. 2 7. Приведенные значения масок могут объединяться с побитовой операцией ИЛИ Что возвращает: Ничего См. также: glutCreateWindow
110 Часть I Классический OpenGL glutKeyboardFunc Цель: Задать функцию обратного вызова клавиатуры для текущего окна Синтаксис: void glutKeyboardFunc(void (*func)(unsigned char key, int x, int y); Описание: Устанавливает функцию обратного вызова, вызываемую GLUT при нажатии одной из клавиш, генерирующих ASCII-код. Клавиши, не генерирующие ASCII-коды (такие как <Shift>), обрабатываются с помощью обратного вызова glutSpecialFunc. Помимо ASCII-кода нажатой клавиши возвращается текущее положение хну курсора мыши Параметры: func Что возвращает: Имя функции, вызываемой GLUT при нажатии клавиши Ничего glutMainLoop Цель: Запустить основной цикл обработки GLUT Синтаксис: void glutMainLoop(void); Описание: Начинает основной цикл GLUT обработки событий. В цикле событий обрабатываются все сообщения клавиатуры, мыши, таймера, перерисовывания и другие сообщения окна. Функция не возвращает ничего, пока программа не завершится Параметры: Нет Что возвращает: Ничего glutMouseFunc Цель: Задает функцию обратного вызова мыши для текущего окна Синтаксис: void glutMouseFunc(void (*func)(int button, int state, int x, int y); Описание: Устанавливает функцию обратного вызова GLUT, вызываемую GLUT при наступлении события, связанного с мышью Параметр button может иметь три значения- GLUT_LEFT_BUTTON, GLUT_MIDDLE_BUTTON И GLUT_RIGHT_BUTTON. Параметрами состояния являются GLUT_UP ИЛИ GLUT_DOWN Параметры: func Имя функции, которую GLUT будет вызывать при наступлении события, связанного с мышью Что возвращает: Ничего См. также: glutSpecialFunc, glutKeyboardFunc
Глава 2. Используя OpenGL 111 glutReshapeFunc Цель: Задать функцию обратного вызова изменения формы для текущего окна Синтаксис: void glutReshapeFunc(void (*func)(int width, int height); Описание: Устанавливает функцию обратного вызова, вызываемую GLUT при любом изменении размера или формы окна (включается по крайней мере один раз — при создании окна). Функция обратного вызова получает новую ширину и высоту окна Параметры: func Имя функции, вызываемой GLUT при изменении размеров окна Что возвращает: Ничего См. также: glutDisplayFunc glutPostRedisplay Цель: Указать GLUT обновить текущее окно Синтаксис: void glutPostRedisplay(void) ; Описание: Информирует библиотеку GLUT о том, что текущее окно нужно обновить. Несколько вызовов этой функции перед следующим обновлением приводят только к однократному перерисовыванию окна Параметры: Нет Что возвращает: Ничего См. также: glutDisplayFunc glutSolidTeapot, glutWireTeapot Цель: Нарисовать сплошной или каркасный чайник Синтаксис: void glutSolidTeapot(GLdouble size); void glutWireTeapot(GLdouble size); Описание: Изображает сплошной или каркасный чайник (Знаменитый чайник часто используется в примерах компьютерной графики ) Помимо поверхности генерируются нормали для координат освещения и текстуры Параметры: size (тип GLdouble) Что возвращает: Приблизительный радиус чайника В сфере такого радиуса модель поместится полностью Ничего
112 Часть I Классический OpenGL ТАБЛИЦА 2.8. Значения клавиш, не генерирующих ASCII-код, которые принимаются функцией glutSpecialFunc Значение key Клавиша GLUT_KEY_F1 GLUT_KEY_F2 GLUT_KEY_F3 GLUT_KEY_F4 GLUT_KEY_F5 GLUT_KEY_F6 GLUT_KEY_F7 GLUT_KEY_F8 GLUT_KEY_F9 GLUT_KEY_F10 GLUT_KEY_F11 GLUT_KEY_F12 GLUT_KEY_LEFT GLUT_KEY_RIGHT GLUT_KEY_UP GLUT_KEY_DOWN GLUT_KEY_PAGE_UP GLUT_KEY_PAGE_DOWN GLUT_KEY_HOME GLUT_KEY_END GLUT_KEY_INSERT Клавиша <F1> Клавиша <F2> Клавиша <F3> Клавиша <F4> Клавиша <F5> Клавиша <F6> Клавиша <F7> Клавиша <F8> Клавиша <F9> Клавиша <F10> Клавиша <F11 > Клавиша <F12> Клавиша co стрелкой влево Клавиша со стрелкой вправо Клавиша со стрелкой вверх Клавиша со стрелкой вниз Клавиша <Page Up> Клавиша <Раде Down> Клавиша <Ноте> Клавиша <End> Клавиша <lnsert> glutSpecialFunc Цель: Синтаксис: Описание: Параметры: func Что возвращает: См. также: Установить специальную функцию обратного вызова клавиатуры для текущего окна для не-ASCII клавиш void glutSpecialFunc(void (*func)(int key, int x, int y); Устанавливает функцию обратного вызова, вызываемую GLUT при нажатии одной из клавиш, не генерирующих ASCII-код (такие клавиши, как <Shift>, которые нельзя идентифицировать значением ASCII key) Помимо этого возвращаются текущие координаты х иу курсора мыши. Приемлемые значения параметра key перечислены в табл 2 8 Имя функции, которая будет вызвана GLUT при нажатии клавиши, нс генерирующей ASCII-код Ничего glutKeyboardFunc, glutMouseFunc
Глава 2 Используя OpenGL 113 glutSwapBuffers Цель: Переключить буферы в режиме двойной буферизации Синтаксис: void glutSwapBuffers(void); Описание: Когда текущее окно GLUT работает в режиме двойной буферизации, функция выполняет очистку конвейера OpenGL и переключение буферов (помещает скрытое визуализированное изображение на экран). Если текущее окно не использует режим двойной буферизации, очистка конвейера также выполняется Параметры: Нет Что возвращает: Ничего См. также: glutDisplayFunc glutTimerFunc Цель: Зарегистрировать функцию обратного вызова, вызываемую GLUT после обнуления счетчика Синтаксис: void glutTimerFunc(unsigned int msecs, (*func)(int value), int value); Описание: Параметры: Регистрирует функцию обратного вызова, которую нужно вызывать по прошествии msecs миллисекунд Функция обратного вызова передает значение userspecified в параметре value msecs (тип Число миллисекунд ожидания перед вызовом заданной unsigned int) функции fun с Имя функции, вызываемой после обнуления счетчика value (тип int) Задаваемое пользователем значение, передаваемое функции обратного вызова при ее выполнении Что возвращает Ничего

ГЛАВА 3 Рисование в пространстве: геометрические примитивы и буферы Ричард С. Райт-мл. ИЗ ЭТОЙ ГЛАВЫ ВЫ УЗНАЕТЕ . . Действие Функция glBegin/glEnd/glVertex glPolygonMode glPointSize glLineWidth glCullFace/glClear glLineStipple Рисование точек, линий и форм Рисование каркасных контуров объекта Установка размеров точек Задание ширины линии при рисовании Удаление скрытых поверхностей Задание узоров для прерывистых линий Установка узоров заполнения многоугольников glPolygonStipple Использование линии разреза OpenGL glScissor Использование буфера трафарета glStencilFunc/glStencilMask./ glStencilOp Если вы посещали уроки химии (и даже если вы на них не ходили), то знаете, что вещество состоит из атомов, а атомы — из протонов, нейтронов и электронов. Все материалы и субстанции, с которыми вы когда-либо сталкивались, — от лепестков розы до песка на пляже — являются всего лишь разными сочетаниями этих трех фун- даментальных строительных кирпичиков. Хотя такое объяснение является немного упрощенным, оно демонстрирует мощный принцип: используя всего лишь несколько строительных кирпичиков, можно создавать очень сложные структуры. Связь сказанного с основной темой книги достаточно очевидна. Объекты и сце- ны, которые вы создаете с помощью OpenGL, также состоят из маленьких, простых форм, упорядоченных различными способами. В данной главе исследуются компоно- вочные элементы трехмерных объектов, называемые примитивами. Все примитивы в OpenGL, начиная от точек и линий и заканчивая сложными многоугольниками, являются одно- или двухмерными объектами. Из этой главы вы узнаете все, что необходимо для рисования объектов в трех измерениях с помощью простых форм.
116 Часть I Классический OpenGL Рисование точек в трехмерном пространстве Когда вы учились рисовать с помощью компьютера, то, возможно, начинали с пик- селей. Пиксель — это наименьший элемент на мониторе, в цветной системе пиксели могут раскрашиваться одним из множества доступных цветов. Эта разновидность компьютерной графики является простейшей: нарисовать точку на экране и присво- ить ей определенный цвет. Далее на основе этой простейшей концепции, используя ваш любимый язык программирования, можно создавать линии, многоугольники, окружности и другие формы. Возможно, таким средствами можно даже создать гра- фический интерфейс пользователя .. При использовании OpenGL, однако, рисование на экране компьютера отличается фундаментально. Вы работаете не с физическими экранными координатами и пиксе- лями, а с позиционными координатами в выбранном объеме наблюдения. Вы пору- чаете OpenGL заботиться о том, как спроектировать точки, линии и все остальное из заданного вами трехмерного пространства на двухмерное изображение, формируемое на экране компьютера. В этой и следующей главах рассмотрены наиболее фундаментальные концепции OpenGL и различных наборов инструментов для работы с трехмерной графикой. В следующей главе мы более подробно расскажем о том, как происходит преобразова- ние из трехмерного пространства в двухмерный ландшафт на мониторе и как преоб- разовывать (поворачивать, транслировать и масштабировать) объекты. Сейчас же мы примем эту возможность как должное и сосредоточимся на рисовании в трехмерном пространстве. Может показаться, что мы излагаем материал в обратном направле- нии, но если вы вначале узнаете, как рисовать что-либо, а затем — как манипулиро- вать рисунками, вам будет легче и интереснее читать четвертую главу, “Геометриче- ские преобразования- конвейер”. Если вы хорошо понимаете графические примитивы и преобразования координат, то сможете быстро овладеть любым языком трехмерной графики или программным интерфейсом приложения. Установка трехмерной канвы На рис. 3.1 показан простой наблюдаемый объем, который мы используем в примерах данной главы. Область, обособленная этим объемом, является декартовым координат- ным пространством, простирающимся от —100 до +100 по всем трем осям — х, у и z. (Обзор декартовых координат см. в главе 1, “Введение в трехмерную графи- ку и OpenGL”.) Наблюдаемый объем удобно воспринимать как трехмерную канву, в которой вы рисуете с помощью команд и функций OpenGL. Объем устанавливается с помощью вызова glOrtho почти так же, как мы делали в других случаях в предыдущих главах. В листинге 3.1 приведен код функции Chan- geSize, которая вызывается при изменении размеров окна (в том числе в момент его первоначального создания). Этот код немного отличается от программ предыду- щей главы, встретится несколько незнакомых функций (glMatrixMode, glLoadl- dentity). Более подробно эти функции рассмотрены в главе 4.
Глава 3 Рисование в пространстве: геометрические примитивы и буферы 117 Рис. 3.1. Декартов наблюдаемый объем размером 100 х 100 х 100 Листинг 3.1. Код, устанавливающий наблюдаемый объем, показанный на рис. 3.1 // Меняет наблюдаемый объем и поле просмотра // Вызывается при изменении размеров окна void ChangeSize(GLsizei w, GLsizei h) { GLfloat nRange = 100.Of; // Предотвращает деление на нуль if(h == 0) h = 1; // Устанавливает поле просмотра по размерам окна glViewport(0, 0, w, h); // Обновляет стек матрицы проектирования glMatrixMode(GL_PROJECTION); glLoadldentity(); // Устанавливает объем отсечения с помощью отсекающих // плоскостей (левая, правая, нижняя, верхняя, // ближняя, дальняя) if (w <= h) glOrtho (-nRange, nRange, -nRange*h/w, nRange*h/w, -nRange, nRange); else glOrtho (-nRange*w/h, nRange*w/h, -nRange, nRange, -nRange, nRange); // Обновляется стек матриц проекции модели glMatrixMode(GL_MODELVIEW); glLoadldentity();
118 Часть I. Классический OpenGL ПОЧЕМУ ЛОШАДЬ ВПРЯЖЕНА ПОЗАДИ ТЕЛЕГИ? Изучив любой исходный код в этой главе, вы заметите возможности функций Ren- derScene: glRotate, glPushMatrix и glPopMatrix. Хотя они более подробно рассмотрены в главе 4, мы введем их сейчас. В этих функциях реализованы важ- ные механизмы, которыми нужно овладеть как можно быстрее. Функции позволяют рисовать в трехмерном пространстве и помогают легко визуализировать внешний вид рисунков под различными углами. Во всех примерах программ в данной главе для поворота рисунков вокруг осей х и у используются клавиши со стрелочками. Посмотрите на любое трехмерное изображение вдоль оси z, и оно, возможно, бу- дет выглядеть двухмерным. Но если рисунок вращать в пространстве, наблюдать трехмерные эффекты гораздо легче. Вам нужно многое узнать о рисунках в трех измерениях, и в этой главе мы рас- смотрим именно этот круг вопросов. Меняя только код рисования в любом из при- веденных ниже примеров, вы можете поэкспериментировать с трехмерными изобра- жениями прямо сейчас и получить весьма интересные результаты. Позже вы узнаете, как манипулировать рисунками с помощью других функций. Трехмерная точка: вершина Чтобы задать нарисованную точку на трехмерной “палитре”, используем функцию OpenGL glVertex, — несомненно, наиболее распространенную функцию во всех программных интерфейсах OpenGL. Это “наименьший общий знаменатель” всех примитивов OpenGL: отдельная точка в пространстве. Функция glVertex может принимать от одного до четырех параметров любого численного типа (от byte до double) с учетом договоренностей относительно именования, рассмотренных в гла- ве 2, “Используя OpenGL”. В следующей простой строке кода задается точка, в нашей системе координат расположенная на 50 единиц по положительному направлению оси х, на 50 единиц по положительному направлению оси у и на 0 единиц по положительному направле- нию оси z. gLVertex3f(50.Of, 50.Of, O.Of); Эта точка показана на рис. 3.2. Здесь (как и далее) мы решили представить коор- динаты как значения с плавающей запятой. Кроме того, использованная нами форма glVertex принимает три аргумента — координаты х, у и z, соответственно. Две другие формы glVertex принимают, соответственно, два и четыре аргумента. Ту же точку, что и на рис. 3.2, мы можем представить с помощью такого кода: glVertex2f(50.Of, 50.Of); Эта форма glVertex принимает только два аргумента, задающих значения хну и предполагающих, что координата z всегда равна 0.0. В форме glVertex, принимающей четыре аргумента (glVertex4) последняя ко- ордината w (по умолчанию установленная равной 1.0) используется при масштабиро- вании. Более подробно об этой координате рассказывается в главе 4, где разбираются преобразования координат.
Глава 3 Рисование в пространстве- геометрические примитивы и буферы 119 Рис. 3.2. Точка (50, 50, 0), заданная с помощью команды glVertex3f(50.Of, 50.Of, O.Of) Нарисуйте что-нибудь! Итак, мы можем задавать точку в пространстве. Как это использовать, и как сообщить OpenGL, что делать? Является ли данная вершина точкой, которую просто нужно изобразить? Может быть это конечная точка отрезка или вершина куба? Согласно геометрическому определению вершины, она не является просто точкой в простран- стве, а точкой пересечения двух прямых или кривых линий Именно это и является сутью примитивов. Примитив — это просто интерпретация набора или списка вершин в виде неко- торой формы, изображенной на экране. В OpenGL существует 10 примитивов — от простой точки, изображенной в пространстве, до замкнутого многоугольника с лю- бым числом сторон. Один из способов рисования примитивов — с помощью коман- ды glBegin сообщить OpenGL, что список вершин нужно интерпретировать как определенный примитив. Затем закрыть список вершин, определяющих примитив, воспользовавшись командой glEnd. Все интуитивно понятно, вы не находите? Рисование точек Начнем с первого и простейшего примитива: точек. Рассмотрим приведенный ниже код. glBegin(GL_POINTS); // Выбираем примитив "точки" glVertex3f(0.Of, O.Of, O.Of); // Задаем точку glVertex3f(50.Of, 50.Of, 50.Of); // Задаем другую точку glEnd(); // Рисуем точки Аргумент glBegin (gl_points) сообщает OpenGL, что следующие далее верши- ны нужно интерпретировать и рисовать как точки В нашем примере перечислены две вершины, которые транслируются как две отдельные точки, точно так же они и рисуются. Из данного примера можно понять один важный момент относительно glBe- gin и glEnd: можно перечислить несколько примитивов в одном вызове, если они принадлежат к одному типу примитивов. Таким образом, с помощью одной после- довательности glBegin/glEnd вы можете включить столько примитивов, сколько
120 Часть I Классический OpenGL хотите. В следующем фрагменте кода ресурсы расходуются неэкономно, и он будет выполняться медленнее, чем код, приведенный выше. glBegin(GL_POINTS); // Задаем точку glVertex3f(O.Of, O.Of, O.Of); glEnd(); glBegin(GL_POINTS); // Задаем другую точку glVertex3f(50.Of, 50.Of, 50.Of); glEnd() ИСПОЛЬЗОВАНИЕ ОТСТУПОВ В КОДЕ Обратили ли вы внимание на использование в коде отступов (например, см. выше вызов функции glVertex)? Большинство программистов OpenGL используют эту возможность для повышения читабельности кода. Это не обязательно, но так проще заметить, где начинаются и заканчиваются примитивы. Первый пример Код, представленный в листинге 3.2, рисует несколько точек в трехмерной среде. С помощью простой тригонометрии изображается ряд точек, формирующих спира- левидную траекторию вдоль оси z. Код взят из программы POINTS, имеющейся на компакт-диске в папке, соответствующей данной главе. Во всех примерах программ используется каркас, определенный в главе 2. Обратите внимание на то, что в функ- ции SetupRC в качестве текущего цвета рисования задан зеленый. Листинг 3.2. Код визуализации, дающий спиралеподобную точечную траекторию // Определяется константа со значением "пи" #define GL_PI 3.1415f // Данная функция выполняет необходимую инициализацию в контексте // визуализации void SetupRC() { // Черный фон glClearColor(O.Of, O.Of, O.Of, l.Of ); // Цвет рисования выбирается зеленым glColor3f(O.Of, l.Of, O.Of); } // Вызывается для рисования сцены void RenderScene(void) { GLfloat x,у,z,angle; // Здесь хранятся координаты и углы // Окно очищается текущим цветом очистки glClear(GL_COLOR_BUFFER_BIT); // Записываем состояние матрицы и выполняем поворот glPushMatrix(); glRotatef(xRot, l.Of, O.Of, O.Of); glRotatef(yRot, O.Of, l.Of, O.Of); // Вызываем один раз для всех оставшихся точек glBegin(GL_POINTS);
Глава 3. Рисование в пространстве: геометрические примитивы и буферы 121 Рис. 3.3. Результат выполнения программы POINTS z = -50.Of; for(angle = O.Of; angle <= (2.0f*GL_PI)*3.Of; angle += O.lf) { x = 50.Of*sin(angle); у = 50.0f*cos(angle); // Задаем точку и немного смещаем значение z glVertex3f(х, у, z); z += 0.5f; } // Рисуем точки glEnd(); // Восстанавливаем преобразования glPopMatrix(); // Очищаем стек команд рисования glFlush(); } Сейчас нас интересует только код между функциями glBegin и glEnd. В этом коде рассчитываются координаты х и у угла, трижды проходящего от 0° до 360°. В программе этот угол выражается не в градусах, а в радианах; если вы не знае- те тригонометрии, примите сказанное на веру. Если вам это интересно, обратитесь к врезке “Тригонометрия радиан/градусов”. При каждом изображении точки значение z немного увеличивается. При запуске данной программы вы видите только окруж- ность из точек, поскольку первоначально смотрите вдоль оси z. Чтобы наблюдать предполагаемый эффект, используйте клавиши со стрелками для поворота объекта вокруг осей х и у. Эффект этого проиллюстрирован на рис. 3.3. ПОСЛЕДОВАТЕЛЬНОСТЬ Повторимся, не рассеивайте внимание на функции, которые мы еще не разобра- ли (glPushMatrix, glPopMatrix и glRotate). Эти функции применяются для такого поворота изображения, чтобы удобнее было наблюдать расположение точек, помещенных в трехмерное пространство. Подробно эти функции рассмотрены в гла- ве 4. Не введи мы их сейчас в программу, вы бы не видели эффектов трехмерных рисунков, а программы не выглядели бы интересными. Еще отметим, что до конца этой главы в примерах мы будем показывать только код, включающий операторы glBegin и glEnd.
122 Часть I. Классический OpenGL ТРИГОНОМЕТРИЯ РАДИАН/ГРАДУСОВ На рисунке, представленном ниже данной врезки, приведена окружность на плоско- сти ху. Отрезок, проходящий от начала координат (0,0) к любой точке на окружно- сти, образует угол а с осью х. Для любого угла тригонометрические функции синус и косинус дают значения х и у точки на окружности. Пошагово меняя перемен- ную, представляющую угол, и выполнив полный оборот вокруг начала координат, можно рассчитать все точки на окружности. Обратите внимание на то, что функции С времени выполнения sin () и cos () принимают значения углов, измеряемый в радианах, а не в градусах. В окружности имеется 2тг радиан, где тг (произносит- ся “пи”) — иррациональное число, приблизительно равное 3,1415. {Иррациональное означает, что после десятичной запятой имеется бесконечное число значений.) х = cos(a) y = sin(a) Задание размера точки Когда вы рисуете одну точку, ее размер по умолчанию равен одному пикселю. Изме- нить величину точки можно с помощью функции glPointSize. void glPointSize(GLfloat size); Функция glPointSize принимает один параметр, задающий приблизительный диаметр в пикселях рисуемой точки. Поддерживаются не все размеры, поэтому сле- дует проверять, доступен ли размер, который вы задаете для точки. Чтобы найти диа- пазон размеров и наименьший интервал между ними, применяется следующий код: GLfloat sizes[2]; // Записываем диапазон размеров // поддерживаемых точек GLfloat step; // Записываем поддерживаемый // инкремент размеров точек // Получаем диапазон размеров поддерживаемых точек и размер шага glGetFloatv(GL_POINT_SIZE_RANGE,sizes); glGetFloatv(GL_POINT_SIZE_GRANULARITY,&step);
Глава 3. Рисование в пространстве: геометрические примитивы и буферы 123 Рис. 3.4. Результат выполнения программы POINTSZ Здесь массив размеров будет содержать два элемента — наименьшее и наиболь- шее возможное значение glPointsize. Кроме того, шаг переменной будет равен наи- меньшему шагу, возможному между размерами точек. Спецификация OpenGL требует поддержки только одного размера точек — 1,0. Программная реализация OpenGL от Microsoft, например, позволяет менять размер точек от 0,5 до 10,0 с минимальным размером шага 0,125. Задание размера, не входящего в диапазон, не интерпретируется как ошибка. Вместо этого используется наибольший или наименьший поддерживае- мый размер, ближайший к заданному значению. Точки, в отличие от других геометрических объектов, не меняются при делении на коэффициент перспективы. Те. они не становятся меньше при удалении от точки наблюдения, и не становятся больше при приближении к наблюдателю. Точки все- гда являются квадратными. Даже используя glPointsize для увеличения размера точек, вы просто получите большие квадраты! Чтобы увидеть круглые точки, нужно использовать технику защиты от наложения (см. следующий раздел). ПЕРЕМЕННЫЕ СОСТОЯНИЯ OPENGL Как обсуждалось в главе 2, OpenGL хранит состояние множества своих переменных и настроек. Такой набор настроек называется конечным автоматом OpenGL. Вы можете направить конечному автомату запрос, чтобы определить состояние любой переменной или настройки. Используя множество вариаций glGet, можно запраши- вать любую особенность или возможность, которую вы активизировали или деакти- визировали с помощью glEnable/glDisable. Это относится и к численным уста- новкам, заданным с помощью glSet. Рассмотрим пример, в котором используется несколько таких функций. Код, приведенный в листинге 3.3, дает ту же спиральную форму, что и первый пример, но на этот раз размер точек постепенно увеличивается от наименьшего возможного размера до наибольшего. Этот пример взят их про- граммы POINTSZ, приведенной на компакт-диске в папке, соответствующей данной главе. Результат выполнения программы POINTSZ, показанный на рис. 3.4, получен с помощью программной реализации Microsoft. На рис. 3.5 показана та же программа, запущенная на аппаратном ускорителе, поддерживающем большие точки.
124 Часть I Классический OpenGL Листинг 3.3. Код из программы pointsz, отвечающий за создание спирали из точек постепенно увеличивающегося размера // Определяем константу со значением "пи" #define GL_PI 3.1415f // Вызывается для рисования сцены void RenderScene(void) { GLfloat x,у,z,angle; // Место хранения координат и углов GLfloat sizes[2); // Запоминаем диапазон размеров // поддерживаемых точек GLfloat step; // Запоминаем поддерживаемый // инкремент размеров точек GLfloat curSize; // Записываем размер текущих точек // Получаем диапазон размеров поддерживаемых точек // и размер шага glGetFloatv(GL_POINT_SIZE_RANGE,sizes); glGetFloatv(GL_POINT_SIZE_GRANULARITY,&step); // Задаем исходный размер точки curSize = sizes[0]; // Задаем начальную координату z z = -50.Of; // Циклический проход по окружности три раза for(angle = O.Of; angle <= (2.0f*GL_PI)*3.Of; angle += O.lf) { // Расчет значений x и у точек окружности х = 50.Of*sin(angle); у = 50.0f*cos(angle); // Задаем размер точки перед указанием примитива glPointSize(curSize); // Рисуем точку glBegin(GL_POINTS); glVertex3f(х, у, z); glEnd(); // Увеличиваем значение z и размер точки z += 0.5f; curSize += step; } } Пример иллюстрирует несколько важных моментов. Для начала обратите внима- ние на то, что функцию glPointSize нужно вызывать вне пары операторов glBe- gin/glEnd. В такой “обложке” приемлемы не все функции OpenGL. Хотя glPoint- Size влияет на все точки, рисуемые после нее, вы не начинаете рисовать точки, не вы- звав функцию glBegin (GL_POINTS). Полный перечень функций, которые можно вы- звать внутри пары glBegin/glEnd, приводится в справочном разделе в конце главы.
Глава 3. Рисование в пространстве: геометрические примитивы и буферы 125 Рис. 3.5. Результат выполнения программы POINTSZ на аппаратном обеспечении, поддерживающем большие точки Задав размер точки, больший того, что возвращает переменная размера, вы также сможете наблюдать (это зависит от аппаратного обеспечения), что OpenGL использу- ет наибольший доступный размер точки, но не увеличивает ее на изображении. Этот момент является общим для всех параметров функций OpenGL, имеющих диапазон приемлемых значений. Значения, не попадающие в этот диапазон, принудительно вводятся в него. Слишком маленькие значения превращаются в наименьшее прием- лемое значение, а слишком большие — в наибольшее. Наиболее очевидным моментом, который вы, возможно, отметили при запуске про- граммы POINTSZ, является то, что точки большего размера представляются просто большими кубиками. Это поведение по умолчанию, но обычно во многих приложе- ниях оно нежелательно. Кроме того, у вас может возникнуть вопрос, что произойдет, если увеличить размер точки на значение, большее единицы. Если величина 1.0 представляет один пиксель, то как нарисовать меньше одного пикселя или, скажем, 2,5 пикселя? Размер, заданный в glPointSize, не является точным размером точки в пикселях, а приблизительным диаметром окружности, содержащей все пиксели, используемые для рисования точки. Вы указываете OpenGL рисовать точки как улучшенные (т.е. ма- ленькие, закрашенные окружности), разрешая их сглаживание. Эта технология вместе со сглаживанием линий относятся к категории защиты от наложения (antialiasing). Защита от наложения — это технология, позволяющая сглаживать зазубренные края и округлять углы; подробно она рассмотрена в главе 6, “Подробнее о цвете и мате- риалах”. Рисование линий в трехмерном пространстве Примитив GL_POINTS, использованный выше, является разумно прямолинейным; для каждой заданной вершины он рисует точку. Следующий логический этап — задать две вершины и нарисовать отрезок между ними. Именно для этого предназначен примитив GL_LINES. Приведенный ниже короткий фрагмент кода рисует отрезок, соединяющий точки (0,0,0) и (50,50, 50).
126 Часть I. Классический OpenGL Рис. 3.6. Результат выполнения программы LINES glBegin(GL_LINES); glVertex3f(O.Of, O.Of, O.Of); glVertex3f(50.Of, 50.Of, 50.0f); glEnd(); Обратите внимание на то, что две вершины задают один примитив. Для двух за- данных вершин рисуется одна линия. Если вы зададите в gl_lines нечетное число вершин, последняя из них будет проигнорирована. В листинге 3.4 (программа LINES на компакт-диске) приведен более сложный пример, в котором рисуется ряд линии, веером расходящихся из одной точки. Каждой точке в этом примере соответствует парная ей на противоположной стороне окружности. Результат выполнения програм- мы показан на рис. 3.6. Листинг 3.4. Код программы lines, которая отображает набор линий, веером расходящихся по окружности // Вызывается один раз для всех оставшихся точек glBegin(GL_LINES); // Все линии принадлежат плоскости ху z = O.Of; for(angle = O.Of; angle <= GL_PI; angle += (GL_PI/20.Of)) { // Верхняя половина окружности x = 50.0f*sin(angle); у = 50.Of*cos(angle); glVertex3f(x, y, z); // Первая конечная точка отрезка
Глава 3. Рисование в пространстве: геометрические примитивы и буферы 127 И2(50,100,0) И(50,50,0) Ио(О,О,О) Рис. 3.7. Пример использования функции GL_LINE_STRIP с тремя вершинами // Нижняя половина окружности х = 50.Of*sin(angle + GL_PI); у = 50.Of*cos(angle + GL_PI); glVertex3f(x, y, z); // Вторая конечная точка отрезка } // Рисуются точки glEnd(); Ломаные и замкнутые линии Следующие два примитива OpenGL построены на основе GL_LINES и позволяют задавать список вершин, по которым рисуется линия. С помощью gl_line_strip линия рисуется непрерывно от одной вершины до другой. Приведенный ниже код рисует два отрезка на плоскости ху, определяемые тремя точками. Соответствующее изображение показано на рис. 3.7. glBegin(GL_LINE_STRIP); glVertex3f(O.Of, O.Of, O.Of); // VO glVertex3f(50.Of, 50.Of, O.Of); // VI glVertex3f(50.Of, 100.Of, O.Of); // V2 glEnd(); Последний примитив линий называется GL_LINE_LOOP. Этот примитив ведет се- бя так же, как GL_LINE_STRIP, только последний отрезок соединяет последнюю точку с первой. Таким образом можно рисовать замкнутые линии. Пример использо- вания GL_LINE_LOOP показан на рис. 3.8, где задействован тот же набор вершин, что и на рис. 3.7. Аппроксимация кривых прямолинейными отрезками Программа points, результат выполнения которой приводился на рис. 3.3, демон- стрирует, как выстроить точки вдоль спиралеобразной траектории. Вы можете по- мещать точки ближе (уменьшая шаг по углу), создавая гладкую спиральную кривую вместо разорванных точек, которые только аппроксимируют ее форму. Отметим, что такая операция возможна, но для более сложных кривых из тысяч точек она будет выполняться очень медленно.
128 Часть I. Классический OpenGL Рис. 3.8. Те же вершины, что и на рис. 3.7, используются примитивом GL_LINE_LOOP Рис. 3.9. Результат выполнения программы LSTRIPS, аппроксимирующей гладкую кривую Гораздо лучшим способом аппроксимации кривой является имитация промежу- точных точек с помощью функции GL_LINE_STRIP. При сближении точек получится более плавная кривая, и вам не придется явно задавать все ее точки. В листинге 3.5 показан код из листинга 3.2, в котором вместо GL_POINTS используется функция GL_LINE_STRIP. Результат выполнения этой новой программы LSTRIPS показан на рис. 3.9. Видно, что аппроксимация кривой достаточно хороша, так что данная удоб- ная техника практически незаменима при работе с OpenGL. Листинг 3.5. Код программы LSTRIPS, демонстрирующей ломаные линии // Вызывается один раз для всех точек glBegin(GL_LINE_STRIP); z = -50.Of; for(angle = O.Of; angle <= (2.0f*GL_PI)*3.0f; angle += O.lf) { x = 50.Of*sin(angle); у = 50.0f*cos(angle); // Задаем точку и немного смещаем значение z glVertex3f(х, у, z); z += 0.5f; } // Рисует точки glEndO;
Гпава 3. Рисование в пространстве: геометрические примитивы и буферы 129 Рис. 3.10. Демонстрация функции glLineWidth в программе LINESW Задание ширины линии Точно так же, как вы задавали различные размеры точек, при рисовании с помощью функции glLineWidth можно указывать различную ширину линий: void glLineWidth(GLfloat width); Функция glLineWidth принимает один параметр, задающий приблизительную ширину в пикселях изображаемой линии. Подобно размерам точек, поддерживается не любая ширина линий, и нужно убедиться, что необходимая ширина доступна. Чтобы определить диапазон ширин линий и наименьший интервал между ними, используйте следующий код. GLfloat sizes[2]; // Записывает диапазон поддерживаемой // ширины линий GLfloat step; // Записывает поддерживаемый // инкремент ширины линий // Получает диапазон и шаг поддерживаемой ширины линий glGetFloatv(GL_LINE_WIDTH_RANGE,sizes); glGetFloatv(GL_LINE_WIDTH_GRANULARITY,&step); Здесь массив размеров будет содержать два элемента — наименьшее и наиболь- шее приемлемое значение glLineWidth. Кроме того, переменная step будет содер- жать наименьший размер шага, допустимый между шириной линий. Спецификация OpenGL требует, чтобы поддерживалась только одна ширина линий —1.0. Реализа- ция OpenGL, выполненная Microsoft, позволяет использовать ширину линий от 0.5 до 10.0 с наименьшим размером шага —0.125. В листинге 3.6 приведен код более интересного примера использования glLineWidth. Он взят из программы LINESW, и при его выполнении рисуется 10 линий переменной ширины. Выполнение программы начинается с низа окна при —90 по оси у и каждая следующая линии располагается на 20 единиц выше и имеет ширину на 1 больше. Результат выполнения этой программы приведен на рис. 3.10.
130 Часть I. Классический OpenGL Листинг 3.6. Рисование линий различной ширины // Вызывается для рисования сцены void RenderScene(void) { GLfloat у; 11 Здесь хранится меняющаяся координата Y GLfloat fSizes[2]; // Метрики диапазона ширины линий GLfloat fCurrSize; // Запись текущего состояния // Получение метрик размера линий и запись наименьшего значения glGetFloatv(GL_LINE_WIDTH_RANGE,fSizes); fCurrSize = fSizes[0]; // Пошаговый проход оси у по 20 единиц за раз for(у = -90.Of; у < 90.Of; у += 20.Of) { // Задается ширина линии glLineWidth(fCurrSize) ; // Рисуется линия glBegin(GL_LINES); glVertex2f(-80.Of, у); glVertex2f(80. Of, у); glEnd(); // Увеличивается ширина линии fCurrSize += l.Of; } } Обратите внимание на то, что, задавая координаты линий, мы использовали glVertex2f вместо glVertex3f. Как говорилось ранее, это просто договоренность, поскольку мы рисуем все объекты на плоскости ху при значении координаты z рав- ном 0. Чтобы убедиться, что вы по-прежнему рисуете линии в трех измерениях, поверните рисунок с помощью клавиш со стрелками. Вы сразу заметите, что все линии лежат в одной плоскости. Фактура линии Помимо изменения ширины отрезков можно создавать линии со штриховыми или пунктирными узорами, называемыми фактурой (stippling). Чтобы использовать фак- туру линий, вначале ее нужно активизировать с помощью следующей команды: glEnable(GL_LINE_STIPPLE); После этого функция glLineStipple устанавливает структуру, которая будет при- меняться при рисовании: void glLineStipple(GLint factor, GLushort pattern);
Глава 3. Рисование в пространстве: геометрические примитивы и буферы 131 Шаблон =OXOOFF = 225 О О F F Двоичное представление = 000000001 1 1 1 1 1 1 1 Шаблон линии = 1 1 1 1 ГТ 1 1 Линия = >| Один сегмент Рис. 3.11. Шаблон фактуры, используемый для построения отрезка НАПОМИНАНИЕ Любая функция или возможность, активизированная с помощью вызова glEnable, должна деактивизироваться с помощью вызова glDisable. Параметр pattern — это 16-битовое значение, задающее шаблон, который нуж- но использовать при рисовании линии. Каждый бит представляет участок линии, включенный либо выключенный. По умолчанию каждый бит соответствует одному пикселю, а параметр factor используется как множитель, увеличивающий ширину шаблона. Например, если установить factor равным 5, каждый бит шаблона будет представлять пять включенных или выключенных пикселей подряд. Более того, вна- чале для задания линии используется нулевой бит (самый младший разряд) шаблона. Пример применения битового шаблона к отрезку показан на рис. 3.11. ПОЧЕМУ ШАБЛОНЫ ИСПОЛЬЗУЮТСЯ С КОНЦА В НАЧАЛО? Может возникнуть вопрос, почему при рисовании линии шаблоны фактуры ис- пользуются задом наперед. Это объясняется тем, что для OpenGL гораздо быстрее смещать шаблон на одну позицию влево всякий раз, когда требуется следующее значение маски. Поскольку OpenGL разрабатывался для высокопроизводительной графики, подобные трюки встречаются в нем довольно часто. В листинге 3.7 приведен пример использования шаблона фактуры, являющего- ся просто последовательностью чередующихся включенных и выключенных битов (0101010101010101). Данный код взят из программы LSTIPPLE, которая снизу вверх изображает 10 линий, параллельных оси х. Фактура каждой линии представлена шаблоном 0x5555, но для каждой следующей линии множитель шаблона увеличи- вается на 1. Влияние множителя на уширяющийся фактурный шаблон можно ви- деть на рис. 3.12.
132 Часть I. Классический OpenGL Рис. 3.12. Результат выполнения программы LSTIPPLE Листинг 3.7. Код программы lstipple, демонстрирующей влияние множителя на битовый шаблон // Вызывается для рисования сцены void RenderScene(void) { GLfloat у; // Здесь хранятся меняющиеся координаты у GLint factor =1; // Множитель фактуры GLushort pattern = 0x5555; // Шаблон фактуры // Активизируется фактура glEnable(GL_LINE_STIPPLE); // Пошаговый проход оси у по 20 единиц за раз for(у = -90.Of; у < 90.Of; у += 20.Of) { // Обновляется множитель повтора и шаблон glLineStipple(factor,pattern); // Рисуется линия glBegin(GL_LINES); glVertex2f(-80.Of, у); glVertex2f(80. Of, y) ; glEnd(); factor++; } } Даже единственная возможность рисования точек и линий в трехмерном простран- стве дает существенный набор инструментов для создания собственных трехмерных шедевров. Для примера на рис. 3.13 показано созданное автором коммерческое прило- жение. Обратите внимание на то, что карта, визуализированная средствами OpenGL, целиком состоит из сплошных и фактурных фрагментов линий.
Глава 3. Рисование в пространстве: геометрические примитивы и буферы 133 Рис. 3.13. Трехмерная карта, визуализированная с помощью сплошных и фактурных линий Рисование треугольников в трехмерном пространстве Итак, мы показали, как с помощью gl_line_loop рисовать точки, линии и даже замкнутые многоугольники. Используя только эти примитивы, вы можете легко изоб- разить любую возможную форму в трехмерном пространстве. Например, можете на- рисовать шесть квадратов, упорядочив их так, чтобы они формировали стороны куба. Вы, возможно, заметили, что любые формы, которые вы создаете с помощью этих примитивов, не закрашены никаким цветом; в конечном счете вы рисуете толь- ко линии. Упорядочив в пространстве шесть квадратов, вы получите не сплошной, а каркасный куб. Чтобы нарисовать сплошную поверхность, одних точек и линий мало; нужны многоугольники. Многоугольник — это замкнутая форма, которая может быть (а может и не быть) закрашена текущим выбранным цветом и является основой всех твердотельных композиций в OpenGL. Треугольники: ваши первые многоугольники Простейшим многоугольником является треугольник. Примитив GL_TRIANGLES ри- сует треугольники, соединяя три вершины. С помощью приведенного ниже кода, например, рисуется два треугольника, показанных на рис. 3.14. glBegin(GL_TRIANGLES); glVertex2f(O.Of, O.Of); // VO glVertex2f(25.Of, 25.Of); // VI glVertex2f(50.Of, O.Of); // V2 glVertex2f(-50.Of, O.Of); // V3 glVertex2f(-75.Of, 50.Of); // V4 glVertex2f(-25.Of, O.Of); // V5 glEnd();
134 Часть I. Классический OpenGL Рис. 3.14. Два треугольника, изображенных с помощью функции GL_TRIANGLES Заметание против часовой стрелки (вид спереди) Заметание по часовой рис 315 два треугольника стрелке (вид сзади) с различным обходом ПРИМЕЧАНИЕ Треугольники будут заполнены текущим цветом. Если вы не задали ранее цвет ри- сования, результат может быть непредсказуем. Обход На рис. 3.14 иллюстрируется важная характеристика любого многоугольного при- митива. Обратите внимание на стрелочки на линиях, соединяющих вершины. Когда рисуется первый треугольник, линии идут от V0 к VI, затем — к V2, и, наконец, — обратно к V0, чтобы замкнуть треугольник. Этот путь проходится в порядке задания вершин, в данном примере — по часовой стрелке. Та же направленная характеристика приведена для второго треугольника. Комбинация порядка и направления, в котором задаются вершины, называется обходом (winding). О треугольниках, подобных изображенным на рис. 3.14, говорят, что они обходятся по часовой стрелке. Если поменять местами положения вершин V4 и V5 на левом треугольнике, получим обход против часовой стрелки. На рис. 3.15, например, показаны треугольники с противоположным обходом.
Глава 3. Рисование в пространстве: геометрические примитивы и буферы 135 Рис. 3.16. Ход функции GL_TRIANGLE_STRIP В OpenGL по умолчанию считается, что передняя грань многоугольника обходится против часовой стрелки. Это означает, что треугольник на рис. 3.15 слева — передняя грань треугольника, а справа показана задняя грань треугольника. Почему это важно? Как вы вскоре увидите, передним и задним граням много- угольников часто нужно приписать различные физические характеристики. Заднюю грань многоугольника можно вообще скрыть или наделить другими отражательными свойствами и цветом (см. главу 5, “Цвет, материалы и освещение: основы”). При этом важно следить за согласованностью всех многоугольников сцены и использо- вать направленные вперед многоугольники для изображения внешних поверхностей сплошных объектов. В следующем далее разделе, посвященном сплошным объектам, мы продемонстрирует этот принцип на примере более сложных моделей. Если поведение OpenGL по умолчанию требуется изменить на противоположное, вызывается следующая функция. glFrontFace(GL_CW); Параметр GL_CW сообщает OpenGL, что передней гранью нужно считать много- угольники с обходом по часовой стрелке. Чтобы вернуться к обходу против часовой стрелки, следует указать параметр GL_CCW. Ленты треугольников Для представления многих поверхностей и форм приходится рисовать несколько со- единенных треугольников. Вы можете существенно сэкономить время, рисуя полосы соединенных треугольников с помощью примитива GL_TRIANGLE_STRIP. Процесс рисования ленты из трех треугольников, заданных пятью вершинами (от V0 до V4), показан на рис. 3.16. Здесь вы видите, что вершины не обязательно обходятся в том порядке, в каком задаются. Это делается для того, чтобы сохранить обход (против часовой стрелки) всех треугольников. Структура процесса выглядит так: VO, VI, V2; затем V2, VI, V3; после этого V2, V3, V4; и т.д. Далее при обсуждении многоугольных примитивов мы не будем приводить фраг- менты кода, демонстрирующие вершины и операторы glBegin. Сейчас вы уже долж- ны представлять себе положение вещей. Позже, когда у нас для работы будет хорошая программа, мы возобновим разбор примеров.
136 Часть I. Классический OpenGL Существует два преимущества использования ленты треугольников перед зада- нием каждого треугольника отдельно. Прежде всего, задав первые три вершины ис- ходного треугольника, для задания каждого следующего нужно указать всего лишь одну вершину. Если нарисовать нужно много треугольников, такое решение позволя- ет существенно сэкономить место. Вторым преимуществом является математическая производительность и экономия полосы пропускания. Меньшее количество вершин означает более быструю передачу из памяти компьютера в графическую карту и мень- ше преобразований вершин (см. главы 2 и 4). ПОДСКАЗКА Другое преимущество составления больших плоских поверхностей из нескольких меньших треугольников заключается в том, что при применении к сцене освещения OpenGL может лучше воспроизвести смоделированные эффекты. Более подробно об освещении рассказано в главе 5. Вееры треугольников Помимо лент треугольников можно, используя функцию GL_triangle_fan, созда- вать вееры треугольников, расходящихся из центральной точки. Веер из трех тре- угольников, заданных четырьмя вершинами, показан на рис. 3.17. Первая вершина, V0, формирует начало веера. Затем на основе первых трех вершин рисуется исход- ный треугольник, каждая последующая вершина образует треугольник с началом (V0) и вершиной, непосредственно ей предшествующей (Vn — 1). Построение сплошных объектов Составление сплошных объектов из треугольников (или любых других многоуголь- ников) включает не только сборку наборов вершин в трехмерном координатном про- странстве. Рассмотрим, например, простую программу TRIANGLE, в которой с по- мощью двух вееров треугольников создается конус в наблюдаемом объеме. Первый веер задает форму конуса, используя первую точку в качестве вершины конуса, а все остальные — как точки на окружности, удаленной от наблюдателя в направлении оси
Глава 3. Рисование в пространстве: геометрические примитивы и буферы 137 Рис. 3.18. Первоначальный результат выполнения программы TRIANGLE z. Второй веер формирует окружность и целиком лежит в плоскости ху, образуя основание конуса. Результат выполнения программы triangle показан на рис. 3.18. Здесь вы смот- рите вдоль оси z и можете видеть только окружность, составленную из веера тре- угольников. Отдельные треугольники выделены цветом, поэтому при запуске про- граммы мы наблюдаем чередующиеся фрагменты зеленого и красного цвета. Код функций SetupRC и RenderScene продемонстрирован в листинге 3.8. (Вы встретили несколько незнакомых переменных, — не волнуйтесь, они будут объясне- ны ниже.) На примере данной программы мы демонстрируем несколько аспектов со- ставления трехмерных объектов. Щелкая правой кнопкой на окне, вы получите меню Effects, с помощью которого можно активизировать и деактивизировать опреде- ленные особенности трехмерного рисунка, что поможет нам исследовать некоторые характеристики создания трехмерных объектов. Эти особенности мы разберем ниже. ЛИСТИНГ 3.8. КОД ФУНКЦИЙ SetupRC И RenderScene программы TRIANGLE // Функция выполняет необходимую инициализацию // в контексте визуализации void SetupRC() { // Черный фон glClearColor(0.Of, O.Of, O.Of, l.Of ); // Цвет рисования выбирается зеленым glColor3f(O.Of, l.Of, O.Of); // Цвет модели затенения выбирается неструктурированным glShadeModel(GL_FLAT); // Многоугольники с обходом по часовой стрелке считаются // направленными вперед; поведение изменено на обратное, // поскольку мы используем вееры треугольников glFrontFace(GL_CW); 1 // Вызывается для рисования сцены void RenderScene(void) { GLfloat x,у,angle; // Здесь хранятся координаты и углы int iPivot =1; // Используется, чтобы отмечать // чередующиеся цвета // Очищаем окно и буфер глубины
138 Часть I. Классический OpenGL glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Включаем отбор, если установлена метка if(bCull) glEnable(GL.CULLJFACE); else glDisable(GL_CULL_FACE); // Если установлена метка, активизируем проверку глубины if(bDepth) glEnable(GL_DEPTH_TEST) ; else glDisable(GL_DEPTH_TEST); // Если установлена метка, рисуем заднюю сторону // в форме каркаса if(bOutline) glPolygonMode(GL_BACK,GL_LINE); else glPolygonMode(GL_BACK,GL_FILL); // Записываем состояние матрицы и выполняем поворот glPushMatrix(); glRotatef(xRot, l.Of, O.Of, O.Of); glRotatef(yRot, O.Of, l.Of, O.Of); // Начинаем веер треугольников glBegin(GL_TRIANGLE_FAN); // Вершина конуса является общей вершиной веера. Перемещаясь // вверх по оси z, вместо окружности получаем конус glVertex3f(O.Of, O.Of, 75.Of); //По циклу проходим окружность и задаем четные точки вдоль // окружности как вершины веера треугольников for(angle = O.Of; angle < (2.Of*GL_PI); angle += (GL_PI/8.Of)) { // Рассчитываем положения x и у следующей вершины х = 50.Of*sin(angle); у = 50.0f*cos(angle); // Чередуем красный и зеленый цвет if((iPivot %2) == 0) glColor3f(O.Of, l.Of, O.Of); else glColor3f(l.Of, O.Of, O.Of); // Увеличиваем pivot на 1, чтобы в следующий раз // изменить цвет iPivot++; // Задаем следующую вершину веера треугольников glVertex2f(х, у); } // Рисуем веер, имитирующий конус glEnd(); // Начинаем новый веер треугольников, имитирующий основание // конуса glBegin(GL_TRIANGLE_FAN); // Центром веера является начало координат glVertex2f(O.Of, O.Of); for(angle = O.Of; angle < (2.Of*GL_PI); angle += (GL_PI/8.Of))
Глава 3. Рисование в пространстве: геометрические примитивы и буферы 139 { // Рассчитываем координаты х и у следующей вершины х = 50.Of*sin(angle); у = 50.0f*cos(angle); // Чередуем красный и зеленый цвета if((iPivot %2) == 0) glColor3f(O.Of, l.Of, O.Of); else glColor3f(l.Of, O.Of, O.Of); // Увеличиваем pivot на единицу, чтобы в следующий раз // поменять цвета iPivot++; // Задаем следующую вершину веера треугольников glVertex2f(х, у); } // Рисуем веер, имитирующий основание конуса glEnd(); // Восстанавливаем преобразования glPopMatrix(); // Очищаем стек команд рисования giFiush(); } Установка цвета многоугольника До этого момента мы устанавливали текущий цвет только один раз и рисовали един- ственную форму. Теперь, когда мы работаем с несколькими многоугольниками, ситу- ация становится интереснее. Мы желаем использовать несколько различных цветов, чтобы результаты работы выглядели нагляднее. В действительности цвета задаются для вершин, а не для многоугольников. Согласно модели затенения, многоугольник может закрашиваться сплошным цветом (используется текущий цвет, выбранный при задании последней вершины) или гладко затеняться с переходом друг в друга цветов, заданных для различных вершин. Строка glShadeModel(GL_FLAT); сообщает OpenGL заполнить многоугольники сплошным цветом, который был теку- щим на момент задания последней вершины многоугольника. Именно поэтому мы могли так просто менять текущий цвет на красный или зеленый перед указанием новой вершины веера треугольников. С другой стороны, строка glShadeModel(GL_SMOOTH); указывает OpenGL затенить треугольники плавно, пытаясь интерполировать цвета точек, находящихся между вершинами заданного цвета. Более подробно о цвете и за- тенении будет рассказано в главе 5.
140 Часть I Классический OpenGL Рис. 3.19. Вращающийся конус кажется качающимся взад-вперед Удаление скрытых поверхностей Нажмите одну из клавиш со стрелкой, чтобы начать вращение конуса, и больше не выбирайте ничего из меню Effects. Вы заметите кое-что тревожное: конус качается взад-вперед на плюс и минус 180°, а основание конуса всегда смотрит на вас, и не поворачивается на 360°. Более отчетливо эффект виден на рис. 3.19. Эффект качки создается из-за того, что основание конуса рисуется после боковых граней. Не имеет значения, как ориентирован конус, основание рисуется поверх него, создавая иллюзию “качки”. Этот эффект не ограничивается различными сторонами и частями объекта. Если нарисовано несколько объектов, причем один расположен перед другим (с позиции наблюдателя), последний нарисованный объект кажется расположенным перед изображенными ранее. Это можно исправить, добавив проверку глубины. Проверка глубины — это эффек- тивная технология удаления скрытых (или невидимых) поверхностей, и в OpenGL имеются соответствующие функции, выполняющие необходимые уточнения рисун- ка. Принцип прост: когда пиксель рисуется, ему присваивается значение (глубина z), характеризующее расстояние от положения наблюдателя. Когда позже в этой же точке нужно будет нарисовать другой пиксель, глубина z этого нового пикселя сравнивает- ся с записанным значением. Если новое значение больше, значит, точка расположена ближе к наблюдателю, те. перед предыдущей точкой, а следовательно, старая точка затеняется новой. Если новое значение меньше, соответствующая точка расположена позади существующего пикселя, а следовательно, не наблюдается. Данный “маневр” выполняет внутренний буфер глубины, где хранятся глубины всех пикселей экрана. Отметим, что проверка глубины используется в большинстве примеров, приводи- мых в книге. Чтобы активизировать проверку глубины, нужно вызывать такую функцию: glEnable(GL_DEPTH_TEST); В программе, приведенной в листинге 3.8 проверка глубины была активизиро- вана при присвоении переменной bDepth значения True и деактивизирована при присвоении той же функции bDepth значение False. // Если установлена метка, активизировать проверку глубины if(bDepth) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST);
Глава 3. Рисование в пространстве: геометрические примитивы и буферы 141 Рис. 3.20. При указанной ориентации основание конуса теперь правильно помещается за сторонами Нужное значение переменной bDepth устанавливается при выборе команды Depth Test из меню Effects. Кроме того, при каждой визуализации сцены нужно очищать буфер глубины. Этот буфер глубины является аналогом буфера цвета в том смысле, что содержит информацию о расстоянии пикселей от наблюдателя. На осно- ве этой информации определяется, не скрыты ли какие-то пиксели другими точками, расположенными ближе к наблюдателю. // Очищаем окно и буфер глубины glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); Щелчок правой кнопкой мыши открывает меню, позволяющее включать и вы- ключать проверку глубины. На рис. 3.20 показан результат выполнения программы TRIANGLE с активизированной проверкой глубины. Здесь также видно, как осно- вание конуса правильно заслоняется боковыми гранями. Из приведенного примера видно, что проверка глубины просто необходима при создании трехмерных объектов из сплошных многоугольников. Отбор: повышение производительности за счет скрытых поверхностей Из сказанного должно быть понятно, что с точки зрения визуального восприятия не нужно рисовать поверхность, заслоняемую другой. Тем не менее даже в этом случае вы получаете некоторые издержки, поскольку каждый нарисованный пиксель должен сравниваться со значением z предыдущего пикселя. В то же время иногда известно, что поверхность ни при каких условиях не будет рисоваться, так зачем ее вообще задавать? Отбором (culling) называется технология удаления геометрических элементов, которые мы никогда не увидим. Не передавая эти элементы аппаратному обеспечению и драйверу OpenGL, мы существенно повышаем производительность. Одной из разновидностей отбора является отбор задних граней — удаление задних сторон поверхности. В рассматриваемом примере конус является замкнутой поверхностью, и мы нико- гда не увидим его внутреннюю часть. В действительности OpenGL (внутренне) рисует задние стенки дальней стороны конуса, а затем — передние стенки многоугольников, направленных на нас. Следовательно, на основании сравнений значений в буфере глу-
142 Часть I. Классический OpenGL Рис. 3.21. Пирамида: (а) с проверкой глубины; (б) без проверки глубины бины либо игнорируется дальняя сторона конуса, либо поверх нее что-то рисуется. На рис. 3.21 показан конус с определенной ориентацией с включенной (а) и выключенной (б) проверкой глубины. Обратите внимание на то, что при активизированной провер- ке глубины меняются зеленые и красные треугольники, составляющие конус. Без проверки глубины просматриваются стороны треугольников дальней грани конуса. Ранее объяснялось, как OpenGL с помощью обхода определяет передние и задние стороны многоугольников, и отмечалось, что важно поддерживать согласованность многоугольников, определяющих внешнюю сторону объектов. Согласованность поз- воляет указывать OpenGL, что визуализировать нужно только переднюю часть, толь- ко часть или обе стороны многоугольника. Удаляя задние стороны многоугольников, можно существенно уменьшить объем обработки при визуализации изображения. Хо- тя вследствие проверки глубины внутренняя часть объектов будет в любом случае удалена, при обработке OpenGL должен их учитывать, если мы явно не укажем, что этого делать не нужно. Отбор задних граней активизируется или деактивизируется с помощью следую- щего кода (см. листинг 3.8). // Многоугольники с обходом по часовой стрелке считаются // направленными вперед; поведение изменено на обратное, // поскольку мы используем вееры треугольников glFrontFace(GL_CW); // Включаем отбор, если установлена метка if(bCull) glEnable(GL_CULL_FACE); else glDisable(GL_CULL_FACE); Обратите внимание на то, что мы вначале меняем определение направленных вперед многоугольников, предполагая обход по часовой стрелке (так как все рассмат- риваемые вееры треугольников обходятся по часовой стрелке).
Глава 3. Рисование в пространстве: геометрические примитивы и буферы 143 Рис. 3.22. Основание конуса отбирается, поскольку треугольники, направленные вперед, находятся внутри Треугольники, направленные наружу Треугольники, направленные наружу Рис. 3.23. Как конус собирается из двух треугольных вееров На рис. 3.22 показано, что при активизированном отборе основание конуса исчеза- ет. Причина такого поведения кроется в том, что мы не придерживаемся собственно- го правила, что все поверхностные многоугольники обходятся в одном направлении. Веер треугольников, образующий основание конуса, обходится по часовой стрелке, подобно вееру, образующему стороны конуса, но в таком случае передняя сторона фрагмента основания конуса направлена внутрь (см. рис. 3.23). Чтобы исправить эту ошибку, мы могли бы изменить правило обхода glFrontFace(GL_CCW); непосредственно перед рисованием второго веера треугольников. Однако в данном примере требовалось показать отбор в действии и подготовить вас к следующему этапу обработки многоугольников. ЗАЧЕМ НУЖЕН ОТБОР ЗАДНИХ ГРАНЕЙ? Может возникнуть вопрос: “Если отбор задних граней—такая полезная штука, зачем нужна возможность его включения и выключения?” Отбор задних граней полезен при рисовании сплошных объектов или твердых тел, но вы не всегда будете визуа- лизировать только эти геометрические фигуры. Некоторые плоские объекты (напри- мер, бумагу), можно наблюдать с обеих сторон. Если бы изображаемый конус был сделан из стекла или пластика, вы смогли бы увидеть и передние, и задние стороны объекта. (Подробнее о рисовании прозрачных объектов рассказывается в главе 6.)
144 Часть I. Классический OpenGL Рис. 3.24. Использование glPolygonMode для визуализации сторон треугольников в виде контуров Многоугольники: режимы Многоугольники не обязательно должны заполняться текущим цветом. По умолчанию многоугольники рисуются как сплошные объекты, но это можно изменить, указав, что многоугольники должны рисоваться в виде контуров или даже точек (изображаются только вершины). Функция glPolygonMode позволяет визуализировать многоуголь- ники в форме сплошных объектов, контуров или точек-вершин. Кроме того, можно применить такой режим визуализации к обеим сторонам многоугольников или только к передним или задним. В приведенном ниже коде (фрагмент листинга 3.8) показа- но, как в зависимости от состояния булевой переменной bOutline устанавливается режим контурного или сплошного представления. // Если метка установлена, рисуются только задние стороны // многоугольника if(bOutline) glPolygonMode(GL_BACK,GL_LINE); else glPolygonMode(GL_BACK,GLJFILL); На рис. 3.24 показаны задние стороны всех многоугольников, визуализированных в режиме контурного представления. (Чтобы получить это изображение, мы отключи- ли отбор; в противном случае внутренние элементы были бы удалены, и контуры не получились бы.) Обратите внимание на то, что основание конуса теперь является не сплошным, а каркасным, и можно видеть внутренние элементы конуса: внутренние стенки также представлены каркасными треугольниками. Другие примитивы Треугольники являются предпочтительными примитивами при формировании объек- тов, поскольку большая часть аппаратного обеспечения OpenGL ускоряет обработку треугольников, однако это не единственные доступные примитивы. Некоторое ап- паратное обеспечение предлагает ускорение и других форм, а с точки зрения про- граммирования использование универсальных графических примитивов может быть проще. Остальные примитивы OpenGL позволяют быстро задавать четырехугольники или ленты четырехугольников, а также универсальные многоугольники.
Глава 3 Рисование в пространстве: геометрические примитивы и буферы 145 Рис. 3.25. Пример использования функции GL_QUADS Рис. 3.26. Процесс построения ленты четырехугольников с помощью функции GL_QUAD_STRIP Ч еты реху гол ьн и ки Если к треугольнику добавить еще одну сторону, получится четырехугольник или фигура с четырьмя сторонами. Для рисования четырехугольников в OpenGL приме- няется примитив GL_QUADS. На рис. 3.25 по четырем вершинам нарисован квадрат. Обратите внимание на то, что все изображенные здесь квадраты обходятся по часовой стрелке. Важно помнить, что при рисовании квадратов все четыре вершины четырех- угольника должны лежать на одной плоскости (квадрат не должен быть изогнутым!). Ленты четырехугольников Точно так же, как ленты треугольников, вы можете задавать ленты четырехугольни- ков, применяя примитив gl_quad_strip. На рис. 3.26 показан процесс построения ленты четырехугольников, заданной шестью вершинами. Обратите внимание на то, что все эти фигуры обходятся по часовой стрелке. Многоугольники общего вида Последним примитивом OpenGL является GL_POLYGON, с его помощью вы може- те рисовать многоугольники, имеющие любое число сторон. На рис. 3.27 показан многоугольник, содержащий пять вершин. Как и четырехугольники, все фигуры, на- рисованные с помощью GL_POLYGON, должны лежать в одной плоскости. Обойти это правило проще простого — подставить gl_triangle_fan вместо gl_polygon! А КАК НАСЧЕТ ПРЯМОУГОЛЬНИКОВ? Все 10 примитивов OpenGL для рисования различных многоугольных форм исполь- зуются внутри glBegin/glEnd. Хотя в главе 2 мы использовали функцию glRect как простой и удобный способ задания двухмерных прямоугольников, с этого мо- мента мы будем применять GL_QUADS.
146 Часть I. Классический OpenGL Рис. 3.27. Процесс изображения GL_POLYGON Заполнение многоугольников, или возвращаясь к фактуре Существует два метода наложения узора на сплошные многоугольники. Обычно при- меняется наложение текстуры, когда изображение отображается на поверхность мно- гоугольника (подробнее см. в главе 8, “Наложение текстуры: основы”). Другой способ заключается в задании фактурного узора, как мы делали для линий. Фактурный узор многоугольника — это не более, чем монохромное растровое изображение 32 х 32, ис- пользуемое как узор-заполнитель. Чтобы активизировать заполнение многоугольника фактурой, нужно вызывать следующую функцию: glEnable(GL_POLYGON_STIPPLE); После этого вызывается такая функция: glPolygonStipple(pBitmap); pBitmap — это указатель на область данных, содержащую узор-заполнитель. Далее все многоугольники заполняются с помощью узора, заданного функцией pBitmap (GLubyte *). Этот узор подобен используемому в наложении фактуры на линию, только в этот раз буфер должен быть достаточно большим, чтобы вместить узор 32 х 32. Кроме того, биты считываются начиная со старшего разряда, т.е. в обратном, по сравнению с линиями, порядке. На рис. 3.28 показан растровый образ костра, который мы использовали как узор-заполнитель. ХРАНЕНИЕ ПИКСЕЛЕЙ Как будет рассказано в главе 7, “Построение изображений с помощью OpenGL”, вы можете, применив функцию glPixelStore, задать, как должны интерпретировать- ся пиксели узора-заполнителя. Пока же мы рассмотрим только простое заполнение с настройками по умолчанию. Чтобы построить маску, представляющую данный узор, мы записываем его снизу вверх по одной строке. К счастью, в отличие от узоров-заполнителей линий данные по умолчанию интерпретируются так, как записаны: первым читается самый стар- ший бит. Таким образом, слева направо можно прочитать все байты и сохранить их в массиве величин типа GLubyte, достаточно большом, чтобы вместить 32 строки по 4 байт в каждой. В листинге 3.9 приведен код, использованный для записи указанного узора. Каж- дая строка массива представляет строку рис. 3.28. Первая строка массива является последней строкой рисунка, а последняя строка массива — первой строкой рисунка.
Глава 3. Рисование в пространстве: геометрические примитивы и буферы 147 Рис. 3.28. Построение узора-заполн ителя многоугольника Листинг 3.9. Определение маски костра, изображенного на рис. 3.28 // Растровый образ костра GLubyte fire[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, Oxlf, 0x80, OxOf, 0xc0, 0x07, OxeO, 0x03, OxfO, 0x03, 0xf5, 0x07, Oxfd, Oxlf, Oxfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, OxcO, 0x01, OxfO, 0x07, OxfO, Oxlf, OxeO, Oxlf, OxcO, 0x3f, 0x80, 0x7e, 0x00, Oxff, 0x80, Oxff, OxeO, Oxff, 0xf8, Oxff, 0xe8,
148 Часть I. Классический OpenGL Рис. 3.29. Результат выполнения программы PSTIPPLE Oxff, ОхеЗ, Oxbf, 0x70, Oxde, 0x80, 0xb7, 0x00, 0x71, 0x10, 0x4a, 0x80, 0x03, 0x10, 0x4e, 0x40, 0x02, 0x88, 0x8c, 0x20, 0x05, 0x05, 0x04, 0x40, 0x02, 0x82, 0x14, 0x40, 0x02, 0x40, 0x10, 0x80, 0x02, 0x64, Oxla, 0x80, 0x00, 0x92, 0x29, 0x00, 0x00, 0xb0, 0x48, 0x00, 0x00, 0xc8, 0x90, 0x00, 0x00, 0x85, 0x10, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00 }; Чтобы использовать узор-заполнитель, мы должны вначале активизировать запол- нение многоугольников, а затем указать этот узор в качестве узора-заполнителя. Все это делается в начале программы PSTIPPLE, после чего с помощью узора-заполнения рисуется восьмиугольник. Соответствующий код представлен в листинге 3.10, а ре- зультат выполнения программы PSTIPPLE показан на рис. 3.29. Листинг 3.10. Код программы pstipple, отвечающий за рисование восьмиугольника с фактурой // Эта функция выполняет инициализацию // в контексте визуализации void SetupRC() { // Черный фон glClearColor(O.Of, O.Of, O.Of, l.Of ); // Устанавливается красный цвет рисования glColor3f(l.Of, O.Of, O.Of); // Активизируется заполнение многоугольника glEnable(GL_POLYGON_STIPPLE); // Задается узор-заполнитель glPolygonStipple(fire);
Глава 3. Рисование в пространстве: геометрические примитивы и буферы 149 Рис. 3.30. Результат выполнения функции PSTIPPLE с повернутым многоугольником, из которого видно, что узор-заполнитель не поворачивается вместе с объектом } // Вызывается для рисования сцены void RenderScene(void) { // Очищаем окно glClear(GL_COLOR_BUFFER_BIT); // Начинает рисовать форму знака "Стоп", // используя для простоты правильный восьмиугольник glBegin(GLJPOLYGON); glVertex2f(-20.Of, 50.0f); glVertex2f(20.Of, 50.0f); glVertex2f(50.Of, 20.0f); glVertex2f(50.Of, -20.0f); glVertex2f(20.Of, -50.0f); glVertex2f(-20.Of, -50.0f); glVertex2f(-50.Of, -20.0f); glVertex2f(-50.Of, 20.Of); glEnd(); // Выводим команды рисования из стека glFlush(); } На рис. 3.30 показан немного повернутый восьмиугольник. Обратите внимание на то, что узор-заполнитель все еще используется, но он не поворачивается вместе с многоугольником. Узор-заполнитель применяется только для простого заполнения многоугольников на экране. Если вам нужно отобразить рисунок в многоугольник так, чтобы он имитировал поверхность многоугольника, следует использовать технологию наложения текстуры (см. главу 8). Правила построение многоугольников Когда для построения сложной поверхности вы используете большое число много- угольников, нужно помнить два важных правила.
150 Часть I. Классический OpenGL Рис. 3.31. Плоские и неплоские многоугольники Рис. 3.32. Приемле- мые и неприемлемые примитивные многоугольники Рис. 3.33. Вогнутая четырехконечная звезда, составленная из шести треугольников Первое правило заключается в том, что все многоугольники должны быть плос- кими. Т.е. все вершины многоугольника должны лежать на одной плоскости, как показано на рис. 3.31. Многоугольник не может перекручиваться или сворачиваться в пространстве. В связи с этим можно сформулировать еще одну причину для того, чтобы ис- пользовать треугольники. Ни один треугольник нельзя перекрутить так, чтобы три его вершины не лежали на одной плоскости, — математически для задания плоскости нужно именно три точки. (Если вы сможете нарисовать “неправильный” треугольник, то вас уже давно разыскивает Комитет по Нобелевским премиям!) Второе правило построения многоугольников заключается в том, что стороны многоугольника не должны пересекаться, и сам он должен быть выпуклым. Мно- гоугольник самопересекается, если пересекаются две любых его стороны. Немного сложнее проверка выпуклости: через многоугольник проводятся линии, и если какая- то из них входит и выходит из многоугольника несколько раз, многоугольник является вогнутым. Примеры “хороших” и “плохих” многоугольников приведены на рис. 3.32.
Глава 3. Рисование в пространстве: геометрические примитивы и буферы 151 ПОЧЕМУ МЫ ОГРАНИЧИВАЕМСЯ МНОГОУГОЛЬНИКАМИ? Вы можете спросить, почему OpenGL вводит ограничение на построение мно- гоугольных форм. Обработка многоугольников может быть достаточно сложной, а условия OpenGL позволяют использовать очень быстрые алгоритмы визуализа- ции определенных многоугольников. По нашему мнению, наложенные ограниче- ния совсем не обременительны, и вы можете создавать любые формы или объек- ты, задействовав только существующие примитивы. Несколько технологий разби- ения сложной формы на меньшие треугольники обсуждаются в главе 10, “Кривые и поверхности”. Деление и стороны Несмотря на то что OpenGL может рисовать только выпуклые многоугольники, су- ществует способ создания любых многоугольных форм, помещая рядом несколько выпуклых многоугольников. Например, рассмотрим четырехконечную звезду, изоб- раженную на рис. 3.33. Очевидно, форма не является выпуклой, а следовательно, нарушаются правила OpenGL, касающиеся построения простых многоугольных кон- струкций. Тем не менее звезда на рисунке справа составлена из шести отдельных треугольников, являющихся дозволенными формами. При заполнении многоугольника вы не увидите краев фигуры, которая будет ка- заться на экране единой формой. Однако, если вы используете glPolygonMode для переключения на контурный режим рисования, забавно увидеть маленькие треуголь- ники, образующие большую поверхность. Чтобы убрать эти ненужные стороны, OpenGL предлагает специальную метку, на- зываемую меткой стороны (edge flag). Устанавливая и очищая метку стороны при задании списка вершин, вы сообщаете OpenGL, какие участки линии считаются гра- ничными (линии, идущие по границе формы), а какие — нет (внутренние линии, которые не должны быть видимыми). Функция glEdgeFlag принимает один пара- метр, который устанавливает метку стороны равной True или False. Когда функция имеет значение True, любая следующая за ней вершина, отмечает начало участка гра- ничной линии. Пример, иллюстрирующий эту концепцию, приведен в листинге 3.11 (программа STAR на компакт-диске). Листинг 3.11. Пример использования функции glEdgeFlag из программы STAR // Формирование треугольников glBegin(GL_TRIANGLES); glEdgeFlag(bEdgeFlag) ; glVertex2f(-20. Of, O.Of); glEdgeFlag(TRUE) ; glVertex2f(20.Of, O.Of); glVertex2f(O.Of, 40.Of); glVertex2f(-20.Of, O.Of); glVertex2f(-60.Of,-20.Of); glEdgeFlag(bEdgeFlag) ; glVertex2f(-20.Of,-40.Of) ; glEdgeFlag(TRUE); glVertex2f(-20.Of, -40.Of);
152 Часть I. Классический OpenGL Рис. 3.34. Программа STAR с активизированными (а) и деактивизированными (б) сторонами glVertex2f(O.Of, -80.0f); glEdgeFlag(bEdgeFlag); glVertex2f(20.Of, -40.0f); glEdgeFlag(TRUE); glVertex2f(20.Of, -40.Of); glVertex2f(60.Of, -20.Of); glEdgeFlag(bEdgeFlag); glVertex2f(20.Of, O.Of); glEdgeFlag(TRUE); // Центральный квадрат, представленный двумя треугольниками glEdgeFlag(bEdgeFlag); glVertex2f(-20.Of, O.Of); glVertex2f(-20.Of,-40.Of) ; glVertex2f(20.Of, O.Of); glVertex2f(-20.Of,-40.Of); glVertex2f(20.Of, -40.Of); glVertex2f(20.Of, O.Of); glEdgeFlag(TRUE); // Рисование треугольников glEnd(); Чтобы проявить и убрать стороны, через опцию меню включается и выключается булева переменная bEdgeFlag. Если значение метки — True, все стороны считаются граничными и проявляются, когда режим многоугольников имеет значение GL_LINES. На рис. 3.34 показан результат выполнения программы STAR, демонстрирующий кар- касную звезду с показанными сторонами и без них. Другие трюки с использованием буферов В главе 2 рассказывалось, что OpenGL не визуализирует (рисует) примитивы непо- средственно на экране. Вместо этого визуализация выполняется в буфере, который позже переключается на экран. Эти два буфера мы будем называть передним (экран) и задним буфером цвета. По умолчанию команды OpenGL визуализируются в заднем буфере, а когда вы вызываете glutSwapBuffers (или специфическую для вашей
Глава 3. Рисование в пространстве: геометрические примитивы и буферы 153 операционной системы функцию переключения буферов), передний и задний буфе- ры меняются местами, так что можно видеть результаты визуализации. Впрочем, если хотите, картинку можно визуализировать непосредственно в переднем буфере. Эта возможность бывает полезной, если требуется отобразить ряд команд рисования, демонстрирующих процесс изображения объекта или формы. Сделать намеченное можно двумя способами, и оба они рассмотрены в следующем разделе. Использование целей — буферов Первый способ визуализации: непосредственно в переднем буфере сообщить OpenGL, что вы желаете рисовать именно в нем. Для этого вызывается следующая функция: void glDrawBuffer(Glenum mode); Если задано GL_FRONT, OpenGL будет визуализировать в переднем буфере, а при указании параметра GL_BACK визуализация вернется в задний буфер. Реализации OpenGL могут поддерживать более одного переднего и заднего буферов при визуали- зации. Например, при стереовизуализации поддерживаются левый и правый буферы, также существуют вспомогательные буферы. Информация, касающаяся всех этих бу- феров, представлена в справочном разделе в конце главы. Второй способ визуализации в переднем буфере: не запрашивать визуализацию с двойной буферизацией при инициализации OpenGL. Инициализация OpenGL от- личается для всех операционных систем, но с помощью GLUT мы следующим об- разом инициализируем режим отображения с RGB-цветом и двойной буферизацией при визуализации: glutlnitDisplayMode(GLUT_DOUBLE | GLUT_RGB); Чтобы получить визуализацию с одним буфером, вы просто опускаете метку GLUT_DOUBLE и набираете команду, приведенную ниже. glutlnitDisplayMode(GLUT_RGB); Если же вы используете двойную буферизацию, важно вызывать glFlush или gIFinish всякий раз, когда вам нужно увидеть не экране результаты рисования. Выполнению команды переключения буфера неявно предшествует очистка конвей- ера и завершение визуализации. Подробно механика этого процесса рассмотрена в главе 11, “Все о конвейере: быстрое продвижение геометрии”. В листинге 3.12 приведен код программы SINGLE. В этом примере с помощью единственного буфера визуализации рисуется набор точек, спиралью расходящихся от центра окна. Последовательно вызывается функция RenderScene (), и для цикли- ческого показа простой анимации используются статические переменные. Результат выполнения программы single показан на рис. 3.35.
154 Часть I. Классический OpenGL Рис. 3.35. Результат выполнения программы визуализации с одном буфером Листинг 3.12, Код примера single /////////////////////////////////////////////////////////////////// // Вызывается для рисования сцены void RenderScene(void) { static GLdouble dRadius = 0.1; static GLdouble dAngle = 0.0; // Очищает синее окно glClearColor(O.Of, O.Of, l.Of, O.Of); if(dAngle == 0.0) glClear(GL_COLOR_BUFFER_BIT); glBegin(GL_POINTS) ; glVertex2d(dRadius * cos(dAngle), dRadius * sin(dAngle)); glEnd(); dRadius *= 1.01; dAngle += 0.1; if(dAngle > 30.0) { dRadius = 0.1; dAngle = 0.0; } giFiush();
Глава 3. Рисование в пространстве: геометрические примитивы и буферы 155 Работа с буфером глубины Буферы глубины являются не единственными, в которых OpenGL визуализирует изоб- ражения. В предыдущей главе мы, упоминая другие буферы — цели, рассматривали буфер глубины. Тем не менее буфер глубины заполняется значениями глубины, а не кодами цвета. Потребовать использования буфера глубины с помощью GLUT так же просто, для этого нужно всего лишь добавить битовую метку GLUT_DEPTH в команду инициализации режима отображения. glutlnitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH); Вы уже видели, что активизация буфера глубины для проверки глубины проста и выполняется следующим образом: glEnable(GL_DEPTH_TEST) ; Даже если проверка глубины не активизирована, то при создании буфера глу- бины OpenGL будет записывать соответствующие значения глубин для всех цвет- ных фрагментов в буфер цвета. Иногда, впрочем, требуется временно отключить запись значений в буфер глубины и проверку глубин. Для этого вызывается функция glDepthMask. void glDepthMask(GLboolean mask); Устанавливая значение маски равным GL_FALSE, вы отключаете запись в буфер глубины, но не проверку глубины, которая выполняется с использованием значе- ний, помещенных ранее в буфер глубины. Вызывая данную функцию с параметром GL_TRUE, вы снова разрешаете запись в буфер глубины (состояние по умолчанию). Также возможна запись масок цвета, но эта тема несколько сложнее, и мы рассмотрим ее в главе 6. Разрезание с помощью ножниц Чтобы повысить производительность визуализации, можно обновлять только изме- нившиеся участки экрана. Возможно, также потребуется ограничить визуализацию OpenGL меньшей прямоугольной областью внутри окна. OpenGL позволяет задавать внутри окна вырезаемый ножницами прямоугольник, в котором выполняется визуа- лизация. По умолчанию этот прямоугольник совпадает с окном, и проверка выреза- ния не выполняется. Чтобы включить проверку вырезания, используется вездесущая функция glEnable. glEnable(GL_SCISSOR_TEST) ; Разумеется, вы можете отключить проверку вырезания с помощью соответствую- щего вызова функции glDisable. Прямоугольник в пределах окна, где выполняется визуализация, называется рамкой вырезания (scissor box) и задается в координатах окна (пикселях) с помощью следующей функции: void glScissor(GLint х, GLint у, GLsizei width, GLsizei height); Параметры x и у задают левый нижний угол рамки вырезания, a width и height представляют собой соответствующие размеры этой рамки. В листинге 3.13 приве- ден код визуализации из программы SCISSOR. Эта программа трижды очищает буфер цвета, постепенно уменьшая размер рамки вырезания, а затем очищает окно. В ре- зультате получается набор накладывающихся цветных прямоугольников (рис. 3.36).
156 Часть I. Классический OpenGL Рис. 3.36. Уменьшение прямоугольников вырезания Листинг 3.13. Использование прямоугольника вырезания для визуализации набора прямоугольников void Renderscene(void) { // Очищаем синее окно glClearColor(O.Of, O.Of, l.Of, O.Of); glClear(GL_COLOR_BUFFER_BIT); // Задаем вырезание в меньшей красной подобласти glClearColor(l.Of, O.Of, O.Of, O.Of); glScissor(100, 100, 600, 400); glEnable(GL_SCISSOR_TEST); glClear(GL_COLOR_BUFFER_BIT); // Еще меньший зеленый треугольник glClearColor(O.Of, l.Of, O.Of, O.Of); glScissor(200, 200, 400, 200); glClear(GL_COLOR_BUFFER_BIT); // Отключаем вырезание glDisable(GL_SCISSOR_TEST); glutSwapBuffers();
Глава 3. Рисование в пространстве: геометрические примитивы и буферы 157 Рис. 3.37. Использование трафарета для рисования формы Использование буфера трафарета Рамка вырезания OpenGL позволяет ограничивать визуализацию одним прямоуголь- ником в окне. Однако часто требуется не прямоугольник, а область неправильной формы, заданная с помощью трафарета. В реальном мире трафарет — это кусок картона или другого материала, в котором вырезан шаблон. Художники используют трафареты для нанесения на холст рисунка в форме вырезанного шаблона (рис. 3.37). В мире OpenGL с той же целью используется буфер трафарета (stencil buffer). Буфер трафарета предлагает похожие возможности, но он гораздо мощнее, позволяя нам самостоятельно создавать трафареты с помощью команд визуализации. Чтобы работать с трафаретами OpenGL, мы должны вначале затребовать использование буфера трафарета с помощью процедур установки OpenGL (зависят от платформы). Если вы используете GLUT, необходимые команды включаются при инициализации режима отображения. Например, с помощью следующей строки задается двойной буфер RGB-цвета с трафаретом: glutlnitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_STENCIL); Операция рисования под трафарет является относительно быстрой на современ- ных реализациях OpenGL с аппаратным ускорением, но ее также можно активи- зировать и деактивизировать с помощью glEnable/glDisable. Например, проверку трафарета можно включить с помощью следующей строки: glEnable(GL_STENCIL_TEST); Когда проверка трафарета активизирована, элементы рисуются только в тех ме- стах, которые проходят сличение с трафаретом. Вы сами указываете, какую проверку трафарета нужно задействовать, вводя в программу следующую функцию: void glStencilFunc(GLenum func, GLint ref, GLuint mask); Функция трафарета, которую вы собираетесь использовать (func), может прини- мать одно из следующих значений: gl_never, gl_always, gl_less, gl_lequal, GL_EQUAL, GL_GEQUAL, GL_GREATER И GL_NOTEQUAL. Эти Значения сообщают OpenGL как сравнивать значение, уже записанное в буфере трафарета, со значением, которое вы задаете как эталон. Приведенные значения соответствуют следующему поведению: никогда не пропускать или всегда пропускать, если эталонное значение меньше, меньше или равно, больше, больше или равно и не равно значению, уже
158 Часть I Классический OpenGL записанному в буфере трафарета. Кроме того, перед сравнением вы можете задать значение маски — результат применения побитовой операции И к эталонному, и за- писанному значению. БИТЫ ТРАФАРЕТА Вы должны понимать, что буфер трафарета может иметь ограниченную точность. Бу- феры трафарета обычно имеют емкость от 1 до 8 бит. В любой реализации OpenGL могут использоваться собственные пределы, а каждая операционная система или среда имеет собственные методы установки данного значения и организации запро- сов по нему. В GLUT вы просто получаете максимально разрешенную емкость, но для более тонкого контроля нужно обратиться к главам, описывающим особенности различных операционных систем. Если значения, указываемые как эталонные и мас- ки, превышают доступную емкость буфера, они просто усекаются, и используется максимальное число младших битов. Создание узоров-трафаретов Итак, вы теперь знаете, как выполняется сличение с трафаретом, но как перед этим ввести значения в буфер трафарета? Вначале нужно убедиться, что буфер трафарета очищен. Для этого мы поступаем так же, как при очистке буферов цвета и глубины с помощью glClear: используем битовую маску GL_STENCIL_BUFFER_BIT. Напри- мер, при выполнении следующей строчки кода одновременно очищаются буферы цвета, глубины и трафарета. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); Перед этим с помощью указанного ниже вызова задается значение, используемое в операции очистки. glClearStencil(GLint s); Если сличение с трафаретом активизировано, сверяются значения команд визуа- лизации с величинами в буфере трафарета; используются параметры функции gis- tencilFunc (см. выше). Фрагменты (коды цвета, записанный в буфере цвета) ли- бо записываются, либо отбрасываются, согласно результату сличения с трафаретом. Сам буфер трафарета также модифицируется в ходе этой операции, и то, что по- мещается в буфер трафарета, зависит от того, с какими параметрами была вызвана функция glStencilOp. void glStencilOp(GLenum fail, GLenum zfail, GLenum zpass); Приведенные значения сообщают OpenGL, как нужно менять значение буфера трафарета, если сличение с трафаретом даст результат fail, причем содержимое бу- фера может меняться даже в том случае, если сличение дает положительный резуль- тат, но проверка глубины даст отрицательный (zfail) или положительный (zpass) результат. Приемлемы следующие значения этих аргументов: GL_KEEP, gl_zero, GL_REPLACE, GL_INCR, GL_DECR, GL_INVERT, GL_INCR_WRAP И GL_DECR_WRAP. Эти значения соответствуют сохранению текущего значения, установке его равным ну- лю, замене эталонным значением (из glStencilFunc), увеличению или уменьше-
Глава 3. Рисование в пространстве: геометрические примитивы и буферы 159 нию значения на 1, замене на обратное и увеличению/уменыиению на 1 с заменой значений соответственно. Параметры GL_INCR и GL_DECR увеличивают и уменьшают значение в трафарете на 1, но не могут выходить за минимальное и максимальное зна- чения, которые можно представить в буфере при данной его емкости. GL_INCR_WRAP и GL_DECR_WRAP просто меняют значения местами, когда они выходят за верхний и нижний переделы при данном представлении. В программе STENCIL мы создаем в буфере трафарета (но не в буфере цвета) узор в виде спиральной линии. Мы снова обращаемся к прыгающему прямоугольнику из главы 2, но в этот раз сличение с трафаретом не позволяет рисовать красный прямо- угольник за пределами области, которая в буфере трафарета определяется значениями 0x1. Соответствующий код рисования приведен в листинге 3.14. Листинг 3.14. Код визуализации программы stencil void RenderScene(void) { GLdouble dRadius =0.1; // Исходный радиус спирали GLdouble dAngle; // Переменная цикла glClearColor(O.Of, O.Of, l.Of, O.Of);// Очистка синего окна // Для очистки трафарета используется значение 0, // активизируется сличение с трафаретом glClearStencil(O.Of); glEnable(GL_STENCIL_TEST); // Очистка буфера цвета и трафарета glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // Все команды рисования не проходят сличение с шаблоном и не // рисуются, но значение в буфере трафарета увеличивается на 1 glStencilFunc(GL_NEVER, 0x0, 0x0); glStencilOp(GL_INCR, GL_INCR, GL_INCR); // Спиральный узор создает узор-трафарет // С помощью линий рисуется спиральный узор. Цвет линий выбран // белым, чтобы продемонстрировать, что функция трафарета // не дает их рисовать glColor3f(l.Of, l.Of, l.Of); glBegin(GL_LINE_STRIP); for(dAngle = 0; dAngle < 400.0; dAngle += 0.1){ glVertex2d(dRadius * cos(dAngle), dRadius * sin(dAngle)); dRadius *= 1.002; } glEnd(); // Теперь рисование разрешено, исключая те места трафарета, // где узор-трафарет имеет значение 0x1, // и новые изменения в буфере трафарета запрещены glStencilFunc(GL_NOTEQUAL, 0x1, 0x1); glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // Далее рисуется красный прыгающий квадрат // (х и у) модифицируются функцией-таймером glColor3f(l.Of, O.Of, O.Of); glRectf(x, у, x + rsize, у - rsize); // Все сделано, буферы переключаются glutSwapBuffers();
160 Часть I. Классический OpenGL Рис. 3.38. Прыгающий красный квадрат с маскирующим узором-трафаретом Из-за приведенных ниже двух строк все фрагменты не проходят сличение с тра- фаретом. В этом случае значения эталона и маски несущественны и не используются. glStencilFunc(GL_NEVER, 0x0, 0x0); glStencilOp(GL_INCR, GL_INCR, GL_INCR); Аргументы glStencilOp, однако, приводят к записи значений в буфер трафарета (фактически увеличиваются на 1) вне зависимости от того, имеется ли что-то на экране. По этим линиям рисуется белая спиральная кривая, и даже несмотря на то, что ее цвет — белый, благодаря чему она видна на синем фоне, в буфере цвета она не рисуется, поскольку никогда не проходит сличение с трафаретом (GL_NEVER). По сути, вы выполняете визуализацию только в буфере трафарета! Далее мы выбираем операцию с трафаретом. glStencilFunc(GL_NOTEQUAL, 0x1, 0x1); glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); Теперь рисование выполняется везде, где значение в буфере трафарета не равно (GL_NOTEQUAL) 0x1, т.е. там, где не нарисована спиральная линия. Последующий вы- зов glStencilOp в этом примере является необязательным, но он сообщает OpenGL оставить буфер трафарета без изменений на протяжении всех последующих опера- ций рисования. Хотя данный пример лучше смотрится “в действии”, на рис. 3.38 показано неподвижное изображение того, как через прыгающий красный квадрат просматривается изображение спирали. Используя функцию glStencilMask, вы можете с помощью маски записывать значения в буфер трафарета, а не в буфер глубины. void glStencilMake(GLboolean mask); Значение маски false не деактивизирует сличение с трафаретом, но запрещает операциям запись в буфер трафарета.
Глава 3 Рисование в пространстве: геометрические примитивы и буферы 161 Резюме В данной главе продолжено рассмотрение основ. Сейчас вы умеете создавать соб- ственное трехмерное пространство для визуализации и знаете, как собирать из мень- ших элементов все — от точек и отрезков до сложных многоугольников. Также мы показали, как собирать указанные двухмерные примитивы в форме поверхностей трехмерных объектов. Вы узнали о существовании других буферов OpenGL. В данной книге мы будем часто использовать буферы глубины и трафарета как составляющие многих более сложных технологий, в частности применять их для создания различных специальных эффектов. Из главы 6 вы узнаете о еще одном буфере OpenGL — буфере накопления. Позже вы увидите, что совместная работа всех этих буферов может порождать очень реалистичные трехмерные изображения. Предлагаем немного поэкспериментировать с тем, что вы узнали из этой главы. Прежде чем читать книгу дальше, используйте свое воображение для создания соб- ственных трехмерных объектов. Так вы получите несколько своих примеров, которые можно будет улучшать по мере обучения другим техникам, описанным в книге. Справочная информация glBegin Цель: Отметить начало группы вершин, определяющих один или несколько примитивов Включаемый файл: <gl. h> Синтаксис: void glBegin(GLenum mode); Описание: Применяется вместе c glEnd для разграничения вершин примитива OpenGL. В одну пару glBegin/glEnd можно включить несколько вершин при условии, что они относятся к одному типу примитивов. Кроме того, можно выполнять другие настройки, используя дополнительные команды OpenGL, которые влияют на следующие за ними вершины. Внутри пары glBegin/glEnd можно вызывать только такие функции OpenGL: glVertex, glColor, glNormal, glEvalCoord, glCallList, glCallLists, glTexCoord, glEdgeFlag и glMaterial. Обратите внимание на то, что таблицы отображения (glCallList(s)) могут содержать только другие перечисленные здесь функции Параметры: mode (тип GLenum) Что возвращает: Задает создаваемый примитив. Оно может иметь любое из перечисленных в табл. 3.1 значений Ничего См. также: glEnd, glVertex
162 Часть I Классический OpenGL ТАБЛИЦА 3.1. Примитивы OpenGL, поддерживаемые glBegin Режим Тип примитива GL_POINTS GL_LINES Все заданные вершины используются для создания отдельных точек Заданные вершины применяются для создания отрезков Каждая пара вершин задает отдельный фрагмент линии. Если число вершин нечетно, последняя из них игнорируется GL_LINE_STRIP Заданные вершины используются для создания ленты отрезков (ломаной линии). После первой вершины каждая последующая точка задает следующее положение, до которого продлевается линия GL_LINE_LOOP Ведет себя похоже на GL_LINE_STRIP, только последний отрезок строится между последней и первой заданными вершинами Применяется для рисования замкнутых областей, нарушающих правила, подходящие для использования GL_POLYGON GL_TRIANGLES Заданные вершины используются для построения треугольников. Каждая тройка вершин задает новый треугольник Если число вершин не делится нацело на три, лишние вершины игнорируются GL_TRIANGLE _STRIP Заданные вершины применяются для создания ленты треугольников После задания первых трех вершин каждая последующая вершина плюс две предшествующие ей формируют треугольник. Каждая тройка вершин (кроме первого набора) автоматически переупорядочивается так, чтобы гарантировать непротиворечивый обход треугольников GL_TRIANGLE _FAN Заданные вершины используются для построения веера треугольников Первая вершина служит началом, а каждая вершина после третьей комбинируется в предшествующей ей и началом Подобным образом в веер может выстраиваться любое число треугольников GL_QUADS Каждый набор из четырех вершин применяется для построения четырехугольника. Если число вершин не делится нацело на четыре, лишние вершины игнорируются GL_QUAD—STRIP Заданные вершины используются для построения ленты четырехугольников Для каждой пары вершин после первой пары определяется один четырехугольник В отличие от упорядочения вершин при обработке примитива GL_quads, каждая пара вершин применяется в обратном к заданному порядке, чтобы гарантировать непротиворечивый обход GL_POLYGON Заданные вершины используются для построения выпуклого многоугольника Стороны многоугольника не должны пересекаться Последняя вершина автоматически соединяется с первой, чтобы гарантировать замкнутость многоугольника glClearDepth Цель: Задать код глубины, который будет использоваться для очистки буфера глубины Включаемый файл: <gl.h> Синтаксис: void glClearDepth(GLclampd depth); Описание: Устанавливает код глубины, который используется при очистке буфера глубины с помощью glClear (GL_DEPTH—BUFFER—BIT) Параметры: depth (тип GLclampd) Что возвращает: Код очистки буфера глубины Ничего См. также: glClear, glDepthFunc, glDepthMask, glDepthRange
Глава 3. Рисование в пространстве: геометрические примитивы и буферы 163 glClearStencil Цель: Задать код трафарета для очистки буфера трафарета Включаемый файл: <gl.h> Синтаксис: void glClearStencil(GLint value); Описание: Задает код трафарета, который используется при очистке буфера трафарета с помощью glClear (GL_STENCIL_BUFFER_BIT) Параметры: value (тип GLint) Код очистки буфера трафарета Что возвращает: Ничего См. также: glStencilFunc, glStencilOp glCullFace Цель: Задать, нужно ли исключать из рисования переднюю или заднюю часть многоугольников Включаемый файл: <gl.h> Синтаксис: void glCullFace(GLenum mode); Описание: Эта функция устраняет все операции рисования для передних Параметры: mode или задних частей многоугольника. Таким образом удаляются ненужные расчеты при визуализации, когда задняя сторона многоугольников никогда не будет видимой вне зависимости от поворотов или трансляции объектов. Подобный отбор активизируется или деактивизируется посредством вызовов функций glEnable или glDisable с аргументом GL_CULL_FACE. Передняя и задняя стороны многоугольника определяются с помощью glFrontFace, а также по порядку, в котором задаются вершины (обход по часовой стрелке или против нее) Задает, какая сторона многоугольников должны отбираться. (тип GLenum) Может иметь значение gl_front либо GL_BACK Что возвращает: Ничего См. также: glFrontFace, glLightModel
164 Часть I. Классический OpenGL ТАБЛИЦА 3.2. Возможные аргументы функции сравнения глубин Функция Значение GL_NEVER GL_LESS Фрагменты никогда не проходят проверку по глубине Фрагменты проходят только тогда, когда поступающее значение z меньше значения z, уже присутствующего в буфере глубины (z-буфере). Данное значение принято по умолчанию GL_LEQUAL Фрагменты проходят только тогда, когда поступающее значение z меньше или равно значению г, уже присутствующему в буфере глубины GL_EQUAL Фрагменты проходят только тогда, когда поступающее значение z равно значению г, уже присутствующему в буфере глубины GL_GREATER Фрагменты проходят только тогда, когда поступающее значение z больше значения г, уже присутствующего в буфере глубины GL_NOTEQUAL Фрагменты проходят только тогда, когда поступающее значение z не равно значению z, уже присутствующему в буфере глубины GL.GEQUAL Фрагменты проходят только тогда, когда поступающее значение z больше или равно значению z, уже присутствующему в буфере глубины GL_ALWAYS Фрагменты проходят всегда, независимо от значения z gIDepthFunc Цель: Задать функцию сравнения по глубине, применяемую к буферу Включаемый файл: глубины, чтобы определить, нужно ли визуализировать цветные фрагменты <gl.h> Синтаксис: void gIDepthFunc(GLenum func); Описание: Проверка по глубине является основным средством удаления Параметры: func (тип GLenum) невидимых поверхностей в OpenGL. При записи кода цвета в буфер цветов соответствующий код глубины пишется в буфер глубины. Если проверка глубины активизирована с помощью glEnable (GL_DEPTH_TEST), коды цвета не пишутся в буфер цветов, если соответствующий код глубины не пройдет сравнение по глубине с кодом, уже присутствующем в буфере. Эта функция позволяет настраивать функции, применяемые при сравнении кодов глубины. По умолчанию выбрана функция GL_LESS Задает, какую функцию сравнения глубин использовать. Что возвращает: Приемлемые значения перечислены в табл. 3.2 Ничего См. также: glClearDepth, glDepthMask, glDepthRange
Глава 3. Рисование в пространстве: геометрические примитивы и буферы 165 gIDepthMask Цель: Избирательно разрешить или запретить изменения в буфере глубины Включаемый файл: <gl.h> Синтаксис: void gIDepthMask(GLBoolean flag); Описание: Если буфер глубины создается для контекста визуализации OpenGL, OpenGL рассчитает и запишет коды глубины. Даже если проверка по глубине деактивизирована, коды глубины все равно будут по умолчанию рассчитываться и записываться в буфер глубины. Данная функция позволяет избирательно активизировать или деактивизировать запись в буфер глубины Параметры: flag Когда данный параметр имеет значение GL_true, запись (тип GLboolean) в буфер глубины разрешена (по умолчанию). Присваивая данному параметру значение GL_FALSE, мы запрещаем изменения буфера глубины Что возвращает: Ничего См. также: glClearDepth, glDepthFunc, gIDepthRange gIDepthRange Цель: Отобразить значения z (коды глубины) в координаты окна из нормированных координат устройства Включаемый файл: <gl.h> Синтаксис: void gIDepthRange(GLclampd zNear, GLclampd zFar); Описание: Обычно значения буфера глубины записываются в диапазоне от — 1.0 до 1.0. Данная функция позволяет задавать линейное отображение кодов глубины в нормированный диапазон координат z окна. Диапазон кодов z окна по умолчанию равен от 0 до 1 и соответствует промежутку между ближней и дальней плоскостями отсечения Параметры: zNear (тип GLclampd) zFar (тип GLclampd) Что возвращает: Значение, представляющее ближайшее возможное значение z окна Значение, представляющее наибольшее возможное значение z окна Ничего См. также: glClearDepth, gIDepthMask, glDepthFunc
166 Часть I Классический OpenGL ТАБЛИЦА 3.3. Цели буфера цветов Константа Описание GL NONE Ничего не записывать в буфер цветов GL FRONT LEFT Записывать только в левый передний буфер цвета GL_FRONT_RIGHT Записывать только в правый передний буфер цвета GL_BACK_LEFT Записывать только в левый задний буфер цвета GL_BACK_RIGHT Записывать только в правый задний буфер цвета GL_FRONT Записывать только в передний буфер цвета. Значение по умолчанию в контексте простой (с одним буфером) визуализации GL_BACK Записывать только в задний буфер цвета Значение по умолчанию в контексте двойной буферизации GL_LEFT Записывать только в левый буфер цвета GL_RIGHT Записывать только в правый буфер цвета GL_FRONT_AND_BACK Записывать в передний и задний буферы цвета GL_AUXi Записывать только во вспомогательный буфер i, где i — значение между 0 И GL_AUX_BUFFERS - 1 gIDrawBuffer Цель: Перенаправить визуализацию OpenGL в конкретный буфер цвета Включаемый файл: <gl.h> Синтаксис: void gIDrawBuffer(GLenum mode); Описание: По умолчанию OpenGL выполняет визуализацию в заднем буфере цвета при двойной буферизации и в переднем — при обычной. Данная функция позволяет направлять визуализацию OpenGL в любой доступный буфер цвета. Обратите внимание на то, что многие реализации не поддерживают левый и правый (стерео) или дополнительные буферы цветов. Что касается стереоконтекста, то в режимах, в которых не упоминаются левый и правый каналы, визуализация будет выполняться в обоих. Например, задавая GL_FRONT в стереоконтексте, мы в действительности будем выполнять визуализацию в левом и правом передних буферах, а задавая GL_FRONT_AND_BACK, получим визуализацию одновременно в четырех буферах Параметры: mode (тип GLenum) Метка, задающая, какой буфер цвета должен быть целью визуализации Допустимые значения этого параметра перечислены в табл. 3.3 Что возвращает: Ничего См. также: glClear, glColorMask
Глава 3 Рисование в пространстве: геометрические примитивы и буферы 167 glEdgeFlag Цель: Пометить стороны многоугольника как граничные или неграничные. Функцию можно использовать для определения того, видимы или нет внутренние линии поверхности Включаемый файл: <gl. h> Варианты: void glEdgeFlag(GLboolean flag); void glEdgeFlagv(const GLboolean *flag); Описание: Когда несколько многоугольников объединяются, формируя большую область, внешние стороны формируют границы новой области. Функция помечает внутренние стороны как неграничные и используется только с режимами многоугольников GL_LINE или GL_POINT Параметры: flag Устанавливает метку края с указанным значением True или (тип GLboolean) False ★flag (тип const Указатель на значение, используемое в качестве метки края GLboolean*) Что возвращает: Ничего См. также: glBegin, glPolygonMode glEnd Цель: Завершить начатый glBegin список вершин, который задает примитив Включаемый файл: <gl.h> Синтаксис: void glEnd(); Описание: Используется вместе с glBegin для разграничения вершин примитива OpenGL. В одну пару glBegin/glEnd можно включить несколько вершин при условии, что они относятся к одному типу примитивов. Кроме того, можно выполнять другие настройки, используя дополнительные команды OpenGL, которые влияют на следующие за ними вершины. Внутри пары glBegin/glEnd можно вызывать только такие функции OpenGL: glVertex, glColor, glNormal, glEvalCoord, glCallList, glCallLists, glTexCoord, glEdgeFlag и glMaterial Что возвращает: Ничего См. также: glBegin
168 Часть I. Классический OpenGL gIFrontFace Цель: Определить, какая сторона многоугольника будет считаться передней или задней Включаемый файл: <gl.h> Синтаксис: void gIFrontFace(GLenum mode); Описание: Когда сцена формируется замкнутыми объектами (вы не можете видеть их внутренние части), расчет цвета или освещения для внутренних поверхностей не требуется. Функция glCullFace отключает подобные расчеты для передних или задних поверхностей многоугольников. Функция gIFrontFace определяет, какая сторона многоугольников рассматривается как передняя. Если вершины многоугольника при наблюдении спереди заданы так, что обход вершин многоугольника производится по часовой стрелке, говорят, что это обход по часовой стрелке. Данная функция позволяет считать передней или задней грань с обходом по часовой стрелке или против часовой стрелки Параметры: mode Задает ориентацию многоугольников, “направленных вперед”: (тип GLenum) по часовой стрелке (GL_CW) или простив часовой стрелки (GL_CCW) Что возвращает: Ничего См. также: glCullFace, glLightModel, glPolygonMode, glMaterial gIGetPolygonStipple Цель: Определить текущий шаблон закрашивания многоугольников Включаемый файл: <gl.h> Синтаксис: void gIGetPolygonStipple(GLubyte *mask); Описание: Копирует шаблон 32 на 32 бит, представляющий шаблон закрашивания многоугольников, в заданный пользователем буфер. Шаблон копируется в память, на которую указывает маска (mask). Схема упаковки пикселей подчиняется настройкам, заданным в последнем вызове glPixelStore Параметры: ★mask Указатель на место, в которое копируется шаблон (тип GLubyte) закрашивания многоугольников Что возвращает: Ничего См. также: glPolygonStipple, glLineStipple, glPixelStore
Глава 3. Рисование в пространстве: геометрические примитивы и буферы 169 gILineStipple Цель: Задать шаблон закрашивания линии для соответствующих примитивов: GL_LINES, GL_LINE_STRIP И GL_LINE_LOOP Включаемый файл: <gl.h> Синтаксис: void gILineStipple(GLint factor, GLushort pattern); Описание: Использует битовый шаблон для рисования штриховых и штрих-пунктирных линий. Битовый шаблон начинается с бита 0 (самый правый бит), так что фактическое рисование по шаблону выполняется в порядке, обратном к тому, в котором шаблон задавался. Параметр factor применяется для увеличения ширины пикселей, рисуемых или не рисуемых вдоль линии и представленных битами шаблона. По умолчанию каждый бит шаблона задает один пиксель. Чтобы использовать наложение фактуры на линию, данную возможность нужно вначале активизировать с помощью glEnable (GL_LINE_STIPPLE); по умолчанию эта возможность деактивизирована. При рисовании нескольких отрезков для каждого нового сегмента шаблон обновляется. Т.е. если отрезок рисуется так, что он заканчивается на половине шаблона, следующий заданный отрезок будет все равно рисоваться с начала Параметры: factor (тип GLint) Задает множитель, определяющий, сколько пикселей будет задействовано при отображении каждого бита шаблона. Следовательно, ширина шаблона умножается на это значение. По умолчанию параметр равен 1, а максимальное значение не превышает 255 pattern (тип GLushort) Устанавливает 16-битовый шаблон фактуры. Вначале используется младший бит (бит 0). По умолчанию шаблон представлен набором единиц Что возвращает: Ничего См. также: glPolygonStipple gILineWidth Цель: Установить ширину линий, рисуемых с помощью GL_LINES, GL_LINE_STRIP ИЛИ GL_LINE_LOOP Включаемый файл: <gl.h> Синтаксис: void gILineWidth(GLfloat width ); Описание: Устанавливает ширину пикселей линий, рисуемых с помощью соответствующего примитива. Чтобы узнать текущую установленную ширину линии, можно вызвать функцию
170 Часть I. Классический OpenGL Параметры: width (тип GLfloat) Что возвращает: См. также: GLfloat fSize; glGetFloatv(GL_LINE_WIDTH, & fSize); Текущие установки ширины линии будут возвращены в переменной fSize. Кроме того, можно найти минимальную и максимальную поддерживаемые ширины линии, вызвав GLfloat fSizes[2J; glGetFloatv(GL_LINE_WIDTH_RANGE,fSizes); В данном случае минимальная поддерживаемая ширина линии будет возвращена в f Sizes [ 0 ], а максимальная будет записана в fSizes[l]. Наконец, можно найти наименьший поддерживаемый прирост между ширинами линий, вызвав функцию GLfloat fStepsize; glGetFloatv(GL_LINE_WIDTH_GRANULARITY, SfStepSize); В любой реализации OpenGL единственная гарантированно поддерживаемая ширина линии равна 1.0. В типичных реализациях Microsoft Windows поддерживается ширина линий от 0.5 до 10.0 с шагом 0.125 Устанавливает ширину линий, которые рисуются с помощью примитивов линий Значение по умолчанию равно 1.0 Ничего glPointsize glPointsize Цель: Включаемый файл: Синтаксис: Описание: Установить размер точек, рисуемых с помощью gl_points <gl.h> void glPointsize(GLfloat size); Устанавливает диаметр в пикселях точек, изображаемых с помощью примитива GL_POINTS. Текущий размер пикселя можно получить, вызвав функцию GLfloat fSize; glGetFloatv(GL_POINT_SIZE, &fSize); Текущий размер пикселя возвращается в переменную fSize. Кроме того, можно найти минимальный и максимальный поддерживаемый размер пикселей, вызвав GLfloat fSizes[2]; glGetFloatv(GL_POINT_SIZE_RANGE, fSizes); В этом случае минимальный поддерживаемый размер точки будет возвращен в fSizes [0], а максимальный поддерживаемый размер будет записан в f Sizes [ 1 ]. Наконец, можно найти наименьший поддерживаемый инкремент между размерами пикселей, вызвав
Глава 3 Рисование в пространстве: геометрические примитивы и буферы 171 GLfloat fStepSize; glGetFloatv (GL_POINT_SIZE_GRANULARITY, &fStepSize), В любой реализации OpenGL единственная гарантированно поддерживаемая ширина линии равна 1.0. В типичных реализациях Microsoft Windows поддерживается ширина линий от 0.5 до 10.0 с шагом 0.125 Параметры: size Устанавливает диаметр рисуемых точек. Значение по (тип GLfloat) умолчанию равно 1.0 Что возвращает: Ничего См. также: glLineWidth glPolygonMode Цель: Установить режим растеризации, используемый для рисования многоугольников Включаемый файл: <gl.h> Синтаксис: void glPolygonMode(GLenum face, GLenum mode); Описание: Позволяет менять способ визуализации многоугольников. По умолчанию многоугольники закрашиваются или затеняются с использованием текущего цвета или свойств материала. Однако также можно задать рисование только контуров объекта или только вершин. Более того, данную спецификацию можно применить к передней, задней или обеим сторонам многоугольников Параметры: face (тип GLenum) Задает, на какие стороны многоугольника распространяется изменение режима: GL_FRONT, GL_BACK или GL_FRONT_AND_BACK mode (тип GLenum) Задает новый режим рисования. По умолчанию значение равно GL_FILL, т.е. создаются закрашенные многоугольники. При выборе режима gl_line получим контуры многоугольников, а задав режим GL_POINT — только вершины. На линии и точки, рисуемые с помощью gl_line и gl_point, влияет метка сторон, установленная с помощью glEdgeFlag Что возвращает: Ничего См. также: glEdgeFlag, glLineStipple, glLineWidth, glPointSize, glPolygonStipple
172 Часть I. Классический OpenGL g I PolygonOffset Цель: Установить масштаб и единицы, используемые для расчета кодов глубины Включаемый файл: <gl.h> Синтаксис: void glPolygonOffset(GLfloat factor, GLfloat units); Описание: Позволяет добавлять (вычитать) к рассчитанному коду глубины фрагмента смещение. Величина смещения вычисляется по следующей формуле: offset = (m * factor) + (г * units). Значение m рассчитывается OpenGL и представляет максимальная глубина многоугольника; г — минимальное значение, при котором создается разумная разница в буфере глубины (зависит от реализации). Смещение применяется только к многоугольникам, но также влияет на линии и точки при визуализации с помощью вызова функции glPolygonMode. Для активизации/деактивизации смещения применяются функции glEnable/glDisable с параметром GL_POLYGON_OFFSET_FILL, GL_POLYGON_OFFSET_LINE ИЛИ GL_POLYGON_OFFSET_POINT Параметры: factor (тип GLfloat) Масштабный коэффициент, с помощью которого для каждого многоугольника создается смещение буфера глубины. Значение по умолчанию равно нулю units (тип GLfloat) Величина, на которую множится значение, зависящее от реализации, с целью создания смещения глубины. Значение по умолчанию равно нулю Что возвращает: Ничего См. также: glDepthFunc, glDepthRange, glPolygonMode glPolygonStipple Цель: Установить шаблон, используемый для наложения фактуры на многоугольник Включаемый файл: <gl.h> Синтаксис: void glPolygonStipple(const GLubyte *mask ); Описание: Для заполнении многоугольников можно применять шаблон 32 х 32 бит, вызвав данную функцию и активизировав наложение фактуры на многоугольник с помощью glEnable (GL_POLYGON_STIPPLE). Пиксели, которым в шаблоне соответствуют единицы, закрашиваются текущим цветом; пиксели, которым соответствуют нули, не рисуются
Глава 3. Рисование в пространстве: геометрические примитивы и буферы 173 Параметры: ★mask (тип Точки области памяти 32 х 32 бит, содержащей шаблон const GLubyte) заполнения. На упаковку битов в данной области памяти влияет glPixelStore. По умолчанию при определении шаблона вначале считывает старший бит Что возвращает: Ничего См. также: glLineStipple, gIGetPolygonStipple, glPixelStore gIScissor Цель: Определить зону вырезания в координатах окна, вне которой рисование не выполняется Включаемый файл: <gl.h> Синтаксис: void gIScissor(GLint х, GLint у, GLint width, GLint height); Описание: Эта функция определяет в координатах окна прямоугольник, именуемый зоной вырезания (scissor box). Если проверка вырезания активизирована с помощью glEnable (GL_SCISSOR_TEST), команды рисования OpenGL и операции с буферами происходят только внутри зоны вырезания Параметры: х, у (тип GLint) Левый нижний угол зоны вырезания в координатах окна width (тип GLint) Ширина зоны вырезания в пикселях height (тип GLint) Высота зоны вырезания в пикселях Что возвращает: Ничего См. также: glStencilFunc, glStencilOp gIStencilFunc Цель: Включаемый файл: Синтаксис: Описание: Установить функцию сравнения, эталонное значение и маску для сличения с трафаретом <gl.h> void glStencilFunc(GLenum func, GLint ref, GLuint mask); Когда сличение с трафаретом активизировано с помощью glEnable (GL_STENCIL_TEST), с помощью функции трафарета определяется, что следует делать с цветным фрагментом — отбрасывать или оставлять (рисовать). Значение в буфере трафарета сравнивается с эталонным значением ref при участии функции сравнении, заданной в func. Как к эталонному значению, так и к значению в буфере трафарета может применяться битовая операция И с маской. Допустимые функции сравнения указаны в табл. 3.4. Результат сличения с трафаретом также приводит к модификации буфера трафарета согласно поведению, заданному в функции glStencilOp
174 Часть I Классический OpenGL Параметры: func (тип GLenum) Функция сличения с трафаретом. Допустимые значения перечислены в табл. 3.4 ref (тип GLint) Эталонное значение, с которым сравнивается величина в буфере трафарета mask (тип GLuint) Бинарная маска, применяемая к эталонному значению и значению в буфере трафарета Что возвращает: Ничего См. также: gIStencilOp, glClearStencil gIStencilMask Цель: Избирательно разрешить или запретить изменения в буфере трафарета Включаемый файл: <gl.h> Синтаксис: void gIStencilMask(GLboolean flag); Описание: Если буфер трафарета создается для контекста визуализации OpenGL, OpenGL будет рассчитывать, записывать и использовать коды трафарета при активизированном сличении с трафаретом. Если сличение с трафаретом деакгивизировано, значения по-прежнему рассчитываются и записываются в буфере трафарета. Данная функция позволяет избирательно активизировать или деактивизировать запись в буфер трафарета Параметры: flag Когда данному параметру присвоено значение GL_TRUE, (тип GLboolean) разрешена запись в буфер трафарета (поведение по умолчанию). После присвоения данному параметру значения GL_FALSE, изменения в буфере глубины запрещаются Что возвращает: Ничего См. также: gIStencilOp, glClearStencil, gIStencilMask gIStencilOp Цель: Задать, какое действие будет применено по отношению к значениям, записанным в буфере трафарета для визуализированного фрагмента Включаемый файл: <gl.h> Синтаксис: void gIStencilOp(GLenum sfail, GLenum zfail, GLenum zpass); Описание: Описывает, какие действия будут предприняты, если фрагмент не пройдет сличение с трафаретом. Даже если фрагменты не пройдут проверку трафаретом и не будут занесены в буфер цветов, информацию в буфере трафарета можно модифицировать, задав подходящее действие в параметре sfail. Кроме того, даже если сличение с трафаретом завершится успешно, в параметрах zfail и zpass можно
Глава 3. Рисование в пространстве: геометрические примитивы и буферы 175 ТАБЛИЦА 3.4. Функции, допустимые при сличении с трафаретом Константа Значение GL NEVER Сличение с трафаретом всегда считается непройденным GL ALWAYS Сличение с трафаретом проходится всегда GL_LESS Сличение с трафаретом проходится, только если эталонное значение меньше значения в буфере трафарета GL_LEQUAL Сличение с трафаретом проходится, только если эталонное значение меньше или равно значению в буфере трафарета GL_EQUAL Сличение с трафаретом проходится, только если эталонное значение равно значению в буфере трафарета GL_GEQUAL Сличение с трафаретом проходится, только если эталонное значение больше или равно значению в буфере трафарета GL_GREATER Сличение с трафаретом проходится, только если эталонное значение больше значения в буфере трафарета GL_NOTEQUAL Сличение с трафаретом проходится, только если эталонное значение не равно значению в буфере трафарета ТАБЛИЦА 3.5. Константы операций с трафаретом Константа Описание GL KEEP Сохранить текущее значение в буфере трафарета GL ZERO Установить текущее значение в буфере трафарета равным 0 GL_REPLACE Заменить значение в буфере трафарета эталонным значением, заданным в переменной glStencilFunc GL_INCR Увеличить на единицу значение в буфере трафарета. Окончательное значение ограничивается согласно допустимому диапазону величин в буфере трафарета GL_DECR Уменьшить на единицу значение в буфере трафарета Окончательное значение ограничивается согласно допустимому диапазону величин в буфере трафарета GL_INVERT Битовая инверсия текущего значения в буфере трафарета GL_INCR_WRAP Увеличить на единицу текущее значение в буфере трафарета При достижении максимального значения, которое можно представить при данной глубине (в битах) буфера глубины, значение возвращается в 0 GL_DECR_WRAP Уменьшить на единицу текущее значение в буфере трафарета. Если значение ниже 0, оно превращается в максимальное возможное при данной глубине буферам описать желаемое действие, основываясь на результате сличения глубины данного фрагмента с текущим значением в буфере глубины. Допустимые действия и соответствующие константы приведены в табл. 3.5 Параметры: sfail Операция, которая будет выполнена при отрицательном результате (тип GLenum) сличения с трафаретом zfail Операция, которая будет выполнена, если значение не пройдет (тип GLenum) проверку глубины zPass Операция, которая будет выполнена при прохождении проверки (тип GLenum) глубины Что возвращает: Ничего См. также: glStencilFunc, glClearStencil
176 Часть I Классический OpenGL glVertex Цель: Задать трехмерные координаты вершины Включаемый файл: <gl.h> Варианты: Описание: void glVertex2d(GLdouble х, GLdouble у); void glVertex2f(GLfloat x, GLfloat y); void glVertex2i(GLint x, GLint y); void glVertex2s(GLshort x, GLshort y); void glVertex3d(GLdouble x, GLdouble y, GLdouble z); void glVertex3f(GLfloat x, GLfloat y, GLfloat z); void glVertex3i(GLint x, GLint y, GLint z); void glVertex3s(GLshort x, GLshort y, GLshort z); void glVertex4d(GLdouble x, GLdouble y, GLdouble z, GLdouble w); void glVertex4f(GLfloat x, GLfloat y, GLfloat z, GLfloat w); void glVertex4i(GLint x, GLint y, GLint z, GLint w); void glVertex4s(GLshort x, GLshort y, GLshort z, GLshort w); void glVertex2dv(const GLdouble *v); void glVertex2fv(const GLfloat *v); void glVertex2iv(const GLint *v); void glVertex2sv(const GLshort *v); void glVertex3dv(const GLdouble *v); void glVertex3fv(const GLfloat *v); void glVertex3iv(const GLint *v); void glVertex3sv(const GLshort *v); void glVertex4dv(const GLdouble *v); void glVertex4fv(const GLfloat *v); void glVertex4iv(const GLint *v); void glVertex4sv(const GLshort *v); Эта функция используется для задания координат вершин точек, линий и многоугольников, заданных ранее при вызове glBegin. Данную функцию нельзя вызывать вне пары glBegin/glEnd Параметры: X, у, Z W Что возвращает: См. также: Координаты х, у и z вершин. Если координата z не задана, принимается значение по умолчанию, 0.0 Координата w вершины Данная координата используется при масштабировании, и по умолчанию она равна 1.0. Масштабирование выполняется путем деления трех других координат на данное значение Массив значений (два, три или четыре элемента), требуемых для задания вершины Ничего glBegin, glEnd
ГЛАВА 4 Г еометрические преобразования: конвейер Ричард. С. Райт-мл ИЗ ЭТОЙ ГЛАВЫ ВЫ УЗНАЕТЕ . . Действие Функция Установка положения на сцене Расположение объектов на сцене Масштабирование объектов Задание перспективного преобразования Выполнение произвольных матричных преобразований Использование камеры для обзора сцены с разных позиций gluLookAt glTranslate/glRotate glScale gluPerspective glLoadMatrix/glMultMatrix gluLookAt В главе 3, “Рисование в пространстве: геометрические примитивы и буферы”, мы рассказывали о рисовании точек, линий и различных примитивов в трехмерном пространстве. Чтобы превратить набор форм в гармоничную сцену, их следует упо- рядочить относительно друг друга и наблюдателя. Прочитав эту главу, вы сможете двигать формы и объекты в выбранной системе координат (В действительности вы не двигаете объекты, а смещаете систему координат, создавая на экране требуемую проекцию.) Возможность располагать объекты на сцене и выбирать их ориентацию является необходимым инструментом для тех, кто занимается программированием трехмерной графики. Как будет показано ниже, размеры объекта удобнее описывать относительно начала координат, а затем перенести объекты в нужное положение Это глава со страшной математикой? В большинстве книг по трехмерному программированию это действительно так- дан- ная тема освещается с привлечением громоздких математических выкладок Однако можете расслабиться — в данной книге использован более современный подход к этим принципам, чем в большинстве учебников. Ключом к преобразованию объектов и координат являются две матрицы, под- держиваемые OpenGL Чтобы познакомить вас с этими матрицами, в данной главе принят компромиссный подход между двумя крайностями, бытующими в философии
178 Часть I. Классический OpenGL компьютерной графики. С одной стороны мы могли бы предупредить вас: “Прежде чем приступить к изучению данной главы, прочтите учебник по линейной алгебре”. С другой стороны, мы могли бы упрочить обманчивое мнение, что вы можете “изу- чить трехмерную графику без всех этих сложных математических формул”. Однако мы не относимся к сторонникам ни одного из этих подходов. В реальном мире вы можете прекрасно себя чувствовать, не понимая тонкостей трехмерной графики, точно так же, как можно ежедневно водить машину, не зная строения двигателя внутреннего сгорания. Однако знания о машине лучше иметь, чтобы понимать, что нужно регулярно менять масло, заполнять бак бензином (газом) и менять “облысевшие” покрышки. Знания делают вас ответственным (и защищен- ным от опасности!) владельцем автомобиля. Сказанное можно перефразировать для ответственных программистов OpenGL. Нужно понимать по крайней мере основы, чтобы знать, что можно сделать и какие инструменты лучше всего подходят для этого. Если вы всего лишь начинающий, то обнаружите, что, немного попрактико- вавшись, начинаете глубже понимать математику матриц и векторов. Со временем вы разовьете более интуитивную (и мощную) способность полноценно использовать концепции, представленные в этой главе. Таким образом, даже если вы сейчас не умеете в уме перемножать две матрицы, нужно знать, что такое матрицы, и что они значат для трехмерной магии OpenGL. Однако прежде чем отправляться на поиски старого учебника линейной алгебры (только не говорите, что у вас его нет!), знайте: OpenGL выполняет всю математику за вас. Думайте об использовании OpenGL как о выполнении деления в столбик с помощью калькулятора, не зная, как это делается на бумаге. Хотя вам и не нужно делать это собственными руками, все же следует знать, что это такое и как его применять. Смотрите: вы действительно можете совместить несовместимое! Понимая преобразования Возможно, вы задумывались о том, что большая часть “трехмерной графики” в дей- ствительности не является трехмерной. Мы используем концепции и терминологию трехмерного мира, чтобы описать, на что похожи некоторые базовые понятия; за- тем эти трехмерные данные “расплющиваются” на двухмерном экране компьютера. Мы назвали этот процесс проектированием и в главе 1, “Введение в трехмерную графику и OpenGL” представили ортографическую и перспективную проекции. Мы будем обращаться к проекции всякий раз, когда потребуется описать преобразование (ортографическое или перспективное), происходящее при проектировании, но про- екция является лишь одним из типов преобразований, возможных в OpenGL. Пре- образования также позволяют поворачивать объекты, переносить с места на место, растягивать, сжимать и деформировать. Между заданием вершин и появлением их на экране проходит три типа преоб- разований: наблюдения, модели и проектирования. В данном разделе мы исследуем принципы преобразований всех типов, обобщенные в табл. 4.1.
Глава 4 Геометрические преобразования: конвейер 179 ТАБЛИЦА 4.1. Терминология преобразований OpenGL Преобразование Использование Наблюдения Модели Наблюдения модели Проектирования Поля просмотра Задает положение наблюдателя или камеры Перемещает объекты по сцене Описывает дуализм преобразований наблюдения и модели Обрезает и задает размеры наблюдаемого объема Псевдопреобразование, масштабирующее конечный результат согласно размерам окна Рис. 4.1. Координаты наблюдения с двух точек зрения Координаты наблюдения В данной главе используется важная концепция координат наблюдения. Координаты наблюдения отсчитываются от точки, в которой расположен глаз наблюдателя, неза- висимо от любых возможных преобразований; эти координаты можно рассматривать как “абсолютные” экранные координаты. Таким образом, координаты системы наблю- дения не являются действительными; они представляют виртуальную неподвижную систему координат, которая используется как внешняя система отсчета. Все преобра- зования, рассматриваемые в данной главе, описываются с точки зрения их эффекта в системе координат наблюдения. На рис. 4.1 показана система координат наблюдения с двух точек обзора: а — коор- динаты представлены так, как они видятся наблюдателю сцены (т.е. перпендикулярно монитору); б — система координат немного повернута, чтобы лучше была видна ось z. Положительные направления осей х и у идут вправо и вверх, соответственно, с точки зрения наблюдателя. Положительное направлении оси z идет от начала ко- ординат к пользователю, а отрицательные значения z растут от точки наблюдения вглубь экрана. При рисовании в трехмерном пространстве с помощью OpenGL применяется де- картова система координат. При отсутствии преобразований используемая система идентична описанной выше системе координат наблюдения.
180 Часть I Классический OpenGL Преобразования наблюдения Первым к сцене будет применено преобразование наблюдения. С его помощью опре- деляется положение камеры, “смотрящей” на сцену. По умолчанию при перспектив- ной проекции точка наблюдения находится в начале координат (0,0,0), а “взгляд” ка- меры направлен по отрицательному направлению оси z (“внутрь” экрана монитора). Чтобы получить выгодное положение камеры, эта точка перемещается относитель- но системы координат наблюдения. Если точка наблюдения расположена в начале координат, объекты, изображаемые с положительным значением координаты z, рас- полагаются позади наблюдателя. Преобразование наблюдения позволяет разместить точку наблюдения в любом удобном месте и выбирать любое направление взгляда. Определение преобразования наблюдения подобно расположению и выбору ориентации камеры на сцене. В “великой общей картине” преобразование наблюдения нужно задавать до остальных преобразований. Это объясняется тем, что выбор влияет на положение текущей рабочей системы координат относительно системы координат наблюдения. Все последующие преобразования основаны на уже модифицированной системе ко- ординат. Позже, изучая задание этих преобразований, мы подробнее объясним, как работает этот принцип. Преобразования модели Преобразования модели позволяют манипулировать моделью и конкретными объек- тами, ее составляющими. С помощью этих преобразований объекты расставляются по местам, поворачиваются и масштабируются. На рис. 4 2 иллюстрируются три наиболее распространенных преобразования модели, которые вы будете применять к объектам: a — трансляция, когда объект перемещается вдоль указанной оси; б — поворот, когда объект поворачивается вокруг одной из осей; в — эффект масшта- бирования, когда размеры объекта увеличиваются или уменьшаются на заданную величину. Масштабирование может быть неравномерным (с разными коэффициен- тами изменения размеров в разных направлениях), поэтому с его помощью можно растягивать и сжимать объекты. Окончательный внешний вид сцены или объекта может сильно зависеть от поряд- ка применения преобразований модели. Особенно это справедливо для трансляции и поворота. На рис. 4.3, а показано, что получается из квадрата, вначале повернуто- го вокруг оси z, а затем транслированного по положительному направлению новой оси х. На рис. 4.3, б тот же квадрат был вначале транслирован по оси х, а за- тем повернут вокруг оси z. Разница в конечных положениях квадрата объясняется тем, что каждое преобразование выполняется относительно последнего выполнен- ного преобразования. На рис. 4.3, а квадрат сперва повернут относительно начала координат. На рис. 4.3, б после трансляции квадрата был выполнен поворот вокруг нового начала координат. Дуализм проекции модели Преобразования наблюдения и модели, по сути, аналогичны с точки зрения их эф- фекта, а также общего влияния на конечный внешний вид сцены. Эти преобразования
Глава 4 Геометрические преобразования: конвейер 181 Рис. 4.2. Преобразования модели различаются исключительно для удобства программиста. Реального различия между движением объекта вперед и движением системы отсчета назад нет; как показано на рис. 4.4, суммарный эффект получается одинаковым. (Данный эффект вы могли прочувствовать на собственном опыте, когда, сидя в машине на перекрестке, наблю- даете, как соседняя машина трогается вперед; при этом может показаться, что ваша машина движется назад.) Термин проекция модели (modelview) означает, что данное преобразование можно считать либо преобразованием модели, либо преобразовани- ем наблюдения (проектирования), но фактически разницы нет; следовательно, это преобразование проекции модели (преобразование наблюдения модели). Таким образом, преобразование наблюдения — это, по сути, преобразование моде- ли, применяемое к виртуальному объекту (наблюдателю) перед рисованием объектов. Как будет показано ниже, по мере добавления объектов на сцену последовательно задаются новые преобразования. По договоренности исходное преобразование дает точку отсчета для всех остальных преобразований.
182 Часть I. Классический OpenGL Исходный квадрат а) Поворот вокруг оси z, Теперь трансляция дающий новую ось х1 вдоль х выполняется как трансляция вдоль х1 Теперь поворачивается транслированная система координат Трансляция начала координат вдоль оси х Рис. 4.4. Два определения концепции преобразования наблюдения Рис. 4.3. Преобразования наблюдения модели поворот/трансляция и трансляция/поворот Движение системы координат Преобразование проектирования Преобразование проектирования применяется к вершинам после преобразования на- блюдения модели. Данное проектирование в действительности определяет наблюдае- мый объем и устанавливает плоскости отсечения. Плоскостями отсечения называют- ся уравнения плоскости в трехмерном пространстве, на основании которых OpenGL
Глава 4. Геометрические преобразования: конвейер 183 Все объекты одинакового размера при удалении Рис. 4.5. Ортографическая и перспективная проекции определяет, какие геометрические объекты увидит наблюдатель. Говоря более кон- кретно, преобразование проектирования задает, как законченная сцена (окончательно смоделированная) проектируется в конечное изображение на экране. В этой главе рассказывается о двух типах проекций: ортографической и перспективной. В ортографической (или параллельной) проекции все многоугольники, нарисован- ные на экране, сохраняют заданные точные относительные размеры. Отрезки и мно- гоугольники отображаются непосредственно на двухмерный экран с помощью парал- лельных линий, а это означает, что независимо от того, насколько далеко находится объект, он будет изображен с первоначальными размерами (только в виде плоского рисунка на экране). Такой тип проектирования обычно используется в сфере авто- матизированного проектирования или для визуализации двухмерных изображений (например, проектов и чертежей), а также такой двухмерной графики, как текст или меню на экране. При перспективной проекции сцены выглядят более приближенно к реальной жизни, а не к чертежу. Товарным знаком перспективной проекции является ракурс, из-за которого удаленные объекты кажутся меньше более близких объектов тако- го же размера. Линии, которые были параллельны в трехмерном пространстве, не всегда кажутся параллельными наблюдателю. Рельсы железной дороги, например, параллельны, но, если использовать перспективную проекцию, покажется, что они сходятся в удаленной точке. Достоинством перспективной проекции является то, что не нужно беспокоиться о том, где сходятся линии или насколько малы удаленные объекты. Все, что от вас требуется, — просто задать сцену, используя преобразования наблюдения модели, а затем применить перспективную проекцию. Все остальное сделает за вас OpenGL. Для примера на рис. 4.5 на двух различных сценах сравниваются ортографическая и перспективная проекции. Ортографические проекции используются чаще всего для двухмерного рисования, когда требуется точное соответствие между пикселями и элементами изображения. Они могут применяться для схематических чертежей, текста или в двухмерных гра-
184 Часть I. Классический OpenGL фических приложениях Кроме того, ортографическая проекция используется для трехмерной визуализации, когда глубина визуализации очень мала по сравнению с расстоянием от точки наблюдения. Перспективные проекции применяются для ви- зуализации сцен, содержащих широкие открытые пространства или объекты, требу- ющие изображения с учетом ракурса. Большей частью в сфере трехмерной графики используются именно перспективные проекции. Изображение трехмерного объекта в ортографической проекции выглядит, мягко говоря, странно. Преобразования поля просмотра После того как все сказано и сделано, вы получаете двухмерную проекцию сцены, которая будет отображаться в окно на экране Это отображение в физические коорди- наты окна является последним преобразованием, именуемым преобразованием поля просмотра. Обычно (но не всегда) существует взаимно однозначное соответствие между буфером цвета и пикселями окна. В некоторых случаях преобразование поля просмотра переводит “нормированные” координаты устройства в координаты окна. К счастью, вам об этом беспокоиться не надо. Матрица: математическая “валюта” трехмерной графики Теперь, когда вы вооружены базовым словарем и определениями преобразований, вы готовы понять простую математику матриц. Рассмотрим, как OpenGL выполняет эти преобразования, и выучим функции, которые вызывают для достижение искомо- го эффекта. Математика, лежащая в основе данных преобразований, сильно упрощается с вве- дением матричной формы записи. Все описанные выше преобразования можно пред- ставить умножением матрицы, содержащей вершины (обычно это просто вектор), на матрицу, описывающую преобразование. Таким образом, все преобразования, до- ступные с помощью OpenGL, можно описать как произведение двух или нескольких перемноженных матриц. Что такое матрица? “Матрица” — это не только популярная голливудская трилогия, но и исключительно мощный математический инструмент, существенно упрощающий процесс решения уравнения (или системы уравнений), переменные которого сложным образом связаны между собой. Одним из распространенных примеров таких операций (близким и до- рогим сердцам программистов) являются преобразования координат. Например, если в пространстве имеется точка, представленная координатами х, у и z, и нужно узнать, куда она сместится при повороте на несколько градусов в определенном направлении вокруг произвольной точки, вы используете матрицу. Почему? Поскольку новая ко- ордината х зависит не только от старой координаты х и других параметров поворота, но и от старых координат у и z. Зависимость подобного рода между переменными и решением является именно тем типом задач, для которых идеально подходят мат-
Глава 4 Геометрические преобразования'конвейер 185 Рис. 4.6. Три примера матриц рицы. Фанатам фильма “Матрица”, имеющим математические наклонности, понятно, что термин матрица действительно был подходящим названием. Математически матрица — это просто набор чисел, упорядоченных в правильные строки и столбцы (или в двухмерный массив, если использовать программистскую терминологию). Матрица не обязательно должна быть квадратной, но все строки или столбцы матрицы должны иметь одинаковое число элементов. На рис. 4.6 пред- ставлено несколько примеров матриц. Они не несут никакой смысловой нагрузки и показаны просто как иллюстрация матричной структуры. Обратите внимание на то, что матрица может иметь единственную строку или столбец. Единственная стро- ка или столбец чисел часто называется просто вектором, и векторы также имеют интересные и полезные сферы применения. Матрица и вектор являются двумя важными терминами, которые часто встре- чаются в литературе по программированию трехмерной графики. Оперируя этими понятиями, вам также придется столкнуться с термином скаляр. Скаляр — это просто число, представляющее амплитуду или некоторую величину (ну вы знаете — ста- рое доброе простое число с тем же значением, каким вы пользовались до того, как добавили в свой словарь весь этот жаргон). Матрицы можно перемножать и складывать, но их также можно множить на век- торы и скалярные значения. Умножение точки (вектор) на матрицу (преобразования) дает новую преобразованную точку (вектор). Матричные преобразования в действи- тельности не так сложно понять, они страшны только на первый взгляд. Поскольку понимание матричных преобразований является необходимым условием выполнения многих трехмерных задач, вы должны хотя бы попытаться понять их. К счастью, чтобы с помощью OpenGL делать невероятные вещи, достаточно минимального по- нимания. Со временем, практикуясь и обучаясь (см. приложение А, “Что еще почи- тать”), вы овладеете этим математическим инструментом. Пока же вы можете найти множество полезных матричных и векторных функций с исходным кодом в библио- теке glTools (см. папку \common в папке samples) на компакт-диске, прилагаемом к этой книге. Конвейер преобразований Чтобы реализовать преобразования, описанные в данной главе, вы, в частности, мо- дифицируете две матрицы: матрицу наблюдения модели и матрицу проектирования. Не пугайтесь: OpenGL предлагает несколько функций высокого уровня, которые вы можете вызывать для выполнения этих преобразований. Овладев основами программ- ного интерфейса OpenGL, вы, несомненно, попытаетесь использовать более сложные технологии трехмерной визуализации. Только тогда вам понадобятся низкоуровневые
186 Часть I Классический OpenGL по вершине наблюдения устройства Преобразование поля просмотра Координаты окна Рис. 4.7. Конвейер преобразования вершины функции, которые в действительности устанавливают значения, содержащиеся в мат- рицах. Дорога от данных о вершинах к экранным координатам является долгой и на рис. 4.7 приведена карта этого процесса. Вначале вершины преобразуются в матрицу 1x4, тремя первыми значениями которой являются координаты х, у и z. Четвертое число — это масштабный коэффициент, который вы можете задавать вручную, исполь- зуя функции вершин, принимающие четыре значения. Эта координата обозначается ш, и по умолчанию ее значение равно 1.0. Менять это значение требуется редко. После этого вершина умножается на матрицу наблюдения модели, что дает преоб- разованные координаты системы наблюдения. Затем координаты системы наблюдения множатся на матрицу проекции и дают координаты отсечения. Таким образом, эффек- тивно устраняются данные, не входящие в наблюдаемый объем. Далее координаты отсечения делятся на координату w и дают нормированные координаты устройства. В зависимости от выполненных преобразований значение ш может модифицировать- ся матрицей проекции или матрицей наблюдения модели. Как и ранее, за подробности этого процесса отвечают OpenGL и высокоуровневые матричные функции. Наконец, с помощью преобразования поля просмотра тройка координат отобра- жается на двухмерную плоскость. Матрица наблюдения модели Матрица наблюдения модели — это матрица 4x4, представляющая преобразованную систему координат, в которой вы располагаете и ориентируете объекты. Вершины, указанные вами в примитивах, используются как матрица-столбец и умножаются на матрицу наблюдения модели, в результате чего получаются новые преобразованные координаты относительно системы наблюдения.
Глава 4 Геометрические преобразования: конвейер 187 Рис. 4.8. Матричное уравнение, выражающее г у у 7 wl 4x4 = Г X Y Z W 1 применение преобразования наблюдения [л / z q м L е е е е J модели к одной вершине L J На рис. 4.8 матрица, содержащая данные по одной вершине, множится на мат- рицу наблюдения модели, в результате чего получаются новые координаты системы наблюдения. Данные по вершине — это в действительности четыре элемента с допол- нительным значением w, которое представляет коэффициент масштабирования. По умолчанию это значение установлено равным 1.0, и вы редко будете менять его. Трансляция Рассмотрим пример с модификацией матрицы наблюдения модели. Скажем, требуется нарисовать куб, используя функцию glutWireCube библиотеки GLUT. Вы просто вводите в свою программу строку glutWireCube(10.Of) ; Создается куб с ребром 10 единиц и центром в начале координат. Чтобы под- нять куб по оси у на 10 единиц перед его выводом на экран, вы умножаете матрицу наблюдения модели на матрицу, описывающую трансляцию на 10 единиц по поло- жительному направлению оси у, а затем рисуете объект. “Каркас” кода выглядит следующим образом: // Строится матрица трансляции на 10 единиц в положительном // направлении оси у // Эта матрица множится на матрицу наблюдения модели // Рисуется куб glutWireCube(10.Of); Подобную матрицу очень просто построить, но это требует нескольких стро- чек кода. К счастью, OpenGL предоставляет высокоуровневые функции, выполня- ющие это за вас: void glTranslatef(GLfloat х, GLfloat у, GLfloat z); Функция принимает в качестве параметров величину трансляции по осям х, у и z. После этого она строит подходящую матрицу и выполняет умножение. Соответству- ющий псевдокод приведен ниже, а общий эффект показан на рис. 4.9. // Трансляция на 10 единиц в положительном направлении оси у glTranslatef(0.Of, 10.Of, O.Of); // Рисуется куб glutWireCube(10.Of);
188 Часть I. Классический OpenGL Рис. 4.9. Куб, транслированный на 10 единиц по положительному направлению оси у ВСЕГДА ЛИ ТРАНСЛЯЦИЯ ЯВЛЯЕТСЯ МАТРИЧНОЙ ОПЕРАЦИЕЙ Прилежный читатель может отметить, что трансляция не всегда требует полного матричного умножения, и ее можно упростить до прибавления скаляра к положению вершины. Однако в более сложных преобразованиях, включающих одновременное выполнение нескольких операций, трансляцию правильно описывать как матричную операцию. К счастью, если вы поручаете OpenGL выполнить за вас всю сложную работу (как мы сейчас и поступаем), он обычно находит оптимальный метод. Поворот Чтобы повернуть объект вокруг одной из трех координатных осей или заданного про- извольного вектора, нужно определить матрицу поворота. Здесь нас снова выручает высокоуровневая функция. glRotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); С помощью этой функции мы выполняем поворот вокруг вектора, определяемого аргументами х, у и z. Угол поворота (против часовой стрелки) измеряется в градусах и задается аргументом angle. В простейшем случае поворот выполняется только вокруг одной координатной оси. Кроме того, вы можете выполнить поворот вокруг произвольной оси, задав значе- ния х, у и z направляющего вектора этой оси. Чтобы увидеть ось вращения, можете просто нарисовать линию от начала координат до точки, представленной коорди- натами (x,y,z). В приведенном ниже коде куб поворачивается на 45° вокруг оси, заданной как (1,1,1) (соответствующая иллюстрация приведена на рис. 4.10). // Выполняется преобразование glRotatef(45.Of, l.Of, l.Of, l.Of); // Рисуется куб glutWireCube(10.Of);
Глава 4. Гэометрические преобразования: конвейер 189 Масштабирование Масштабирование увеличивает размер объекта, отодвигая все его вершины по трем осям от начала координат согласно заданным масштабным коэффициентам. Напри- мер, приведенная ниже функция умножает значения х, у и z на заданные масштаб- ные коэффициенты. glScalef(GLfloat х, GLfloat у, GLfloat z); Масштабирование не обязательно должно быть пропорциональным, с его помо- щью можно растягивать и сжимать объект вдоль разных направлений. Например, следующий код дает куб, вдвое больший вдоль осей х и z, чем кубы, рассмотренные в предыдущем примере, но такой же по величине вдоль оси у. Результат подобного масштабирования показан на рис. 4.11. // Выполняется преобразование масштабирования glScalef(2.Of, l.Of, 2.Of); // Рисуется куб glutWireCube(10.О f);
190 Часть I. Классический OpenGL Рис. 4.12. Сферы, нарисованные на осях х и у Единичная матрица Дочитав до этого места, вы можете поинтересоваться, зачем вообще связываться с матрицами? Почему нельзя просто вызывать указанные функции преобразований, которые будут модифицировать объекты желаемым образом? Действительно ли нуж- но знать, что мы модифицируем именно матрицу наблюдения модели? Ответом будет “и да, и нет” (причем только “нет”, если вы рисуете на сцене единственный объект). Дело в том, что влияние этих функций кумулятивно. Всякий раз, когда вы вызываете одну из них, подходящая матрица строится и умножается на текущую матрицу наблюдения модели. После этого полученная матрица становит- ся текущей матрицей наблюдения модели, на которую будет множиться следующее преобразование и т.д. Предположим, требуется нарисовать две сферы — одну с центром в точке с коор- динатой у = 10, а другую — с центром в точке х = 10, как показано на рис. 4.12. Возможно, для решения этой задачи вы напишете примерно такой код. // Пройти на 10 единиц вдоль оси у glTranslatef(0.Of, 10.Of, O.Of); // Нарисовать первую сферу glutSolidSphere(1.Of, 15,15); // Пройти на 10 единиц вдоль оси х glTranslatef(10.Of, O.Of, O.Of); // Нарисовать вторую сферу glutSolidSphere(1.Of); Однако, учтите, что все вызовы glTranslate накапливаются в матрице наблю- дения модели, поэтому второй вызов задает трансляцию в 10 единиц вдоль оси х от положения, полученного при предыдущей трансляции в положительном направлении оси у. В результате получается изображение, показанное на рис. 4.13. Чтобы решить эту проблему, можно вызвать дополнительно функцию glTrans- late и пройти 10 единиц в отрицательном направлении оси у, но так будет трудно кодировать и отлаживать сложные сцены (не говоря уже об увеличении нагрузки
Глава 4. Геометрические преобразования: конвейер 191 на процессор, связанной с математикой преобразований). Гораздо проще обновить матрицу наблюдения модели до известного состояния — в нашем случае с центром в начале системы координат наблюдения. Подобное обновление заключается в загрузке единичной матрицы вместо текущей матрицы наблюдения модели. Единичная матрица указывает, что преобразование не происходит, сообщая, что все заданные координаты указаны в координатах системы наблюдения. Единичная матрица содержит все нули, исключая диагональ, где рас- положены единицы. При умножении такой матрицы на любую матрицу вершины результат не отличается от исходной матрицы. Соответствующий пример показан на рис. 4.14. Далее в главе мы более подробно обсудим, почему в обозначенных местах находятся именно указанные числа. Как мы уже говорили, детали процесса умножения матриц мы рассматривать не будем. Просто запомните, что загрузка единичной матрицы означает, что вершины не преобразовываются. По сути, таким образом вы обновляете матрицу наблюдения мо- дели, возвращая ее к состоянию, соответствующему положению в начале координат. Выполнение следующих двух строк кода загружает единичную матрицу в матрицу наблюдения модели. glMatrixMode(GL_MODELVIEW); glLoadldentity(); В первой строке указывается, что текущей обрабатываемой матрицей является матрица наблюдения модели. После того как вы установили текущую обрабатывае- мую матрицу (матрицу, к которой применяются последующие матричные функции), она остается активной, пока вы ее не измените. Во второй строке вместо текущей матрицы (в данном случае матрицы наблюдения модели) загружается единичная. Приведем теперь код, дающий результат, показанный на рис. 4.12.
192 Часть I Классический OpenGL Рис. 4.15. Стек матриц в действии // Матрица наблюдения модели становится текущей и обновляется glMatrixMode(GL_MODELVIEW); glLoadldentity(); // Проходим 10 единиц по положительному направлению оси у glTranslatef(O.Of, 10.Of, O.Of); // Рисуем первую сферу glutSolidSphere(1.Of, 15, 15); // Снова обновляем матрицу наблюдения модели glLoadldentity(); // Проходим 10 единиц по положительному направлению оси х glTranslatef(10.Of, O.Of, O.Of); // Рисуем вторую сферу glutSolidSphere(1.Of, 15, 15); Стеки матриц Обновление матрицы наблюдения модели (превращение ее в единичную) перед по- мещением на сцену нового объекта не всегда желательно. Часто требуется сохра- нить текущее состояние преобразования, а затем восстановить его после размещения нескольких объектов. Такой подход удобнее, когда в качестве преобразования на- блюдения вы используете исходную преобразованную матрицу наблюдения модели (а следовательно, уже не привязаны к началу координат). Чтобы облегчить подобную процедуру, OpenGL поддерживает стек матриц как для матрицы наблюдения модели, так и для матрицы проекции. Стек матриц дей- ствует так же, как привычный стек программы. Можете поместить текущую матрицу в стек, запомнив ее, а затем менять текущую матрицу. Выталкивание матрицы из стека восстанавливает ее. Принцип действия стека иллюстрируется на рис. 4.15. СТЕК МАТРИЦ ТЕКСТУРЫ Еще одним стеком матриц является стек текстуры, который используется для пре- образования текстурных координат. Подробно наложение текстуры и текстурные координаты рассмотрены в главе 8, “Наложение текстуры: основы”. Там же обсуж- дается стек матриц текстуры.
Глава 4 Геометрические преобразования: конвейер 193 Максимальную глубину стека вы можете узнать, вызывая одну из приведенных ниже функций: glGet(GL_MAX_MODELVIEW_STACK_DEPTH); ИЛИ glGet(GL_MAX_PROJECTION_STACK_DEPTH); Превысив глубину стека, вы получаете сообщение об ошибке GL_STACK_overflow; если вы пытаетесь извлечь матрицу из пустого стека, вы получаете сообщение об ошибке GL_STACK_UNDERFLOW. Глубина стека зависит от реализации. В программной реализации Microsoft используются значения 32 для стека матриц наблюдения модели и 2 для стека матриц проекции. Пример ядра Воспользуемся полученными знаниями. В следующем примере мы построим грубую анимированную модель атома. Атом имеет сферу в центре, представляющую ядро, и три электрона на орбите вокруг атома. Как и ранее, используем ортографическую проекцию. В нашей программе АТОМ задействован механизм GLUT обратного вызова тай- мера (обсуждается в главе 2, “Используя OpenGL”), с помощью которого сцена пе- рерисовывается примерно 10 раз в секунду. При каждом вызове функции Render- Scene увеличивается угол поворота элемента относительно ядра. Кроме того, каж- дый электрон находится на отдельной плоскости. В листинге 4.1 показана функция Renders сепе этого примера, а результат выполнения программы АТОМ демонстри- руется на рис. 4.16. Листинг 4.1. Функция Renderscene ИЗ программы АТОМ // Вызывается для рисования сцены void RenderScene(void) { I/ Угол поворота вокруг ядра static GLfloat fElectl = O.Of; // Очищаем окно текущим цветом очистки glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Обновляем матрицу наблюдения модели glMatrixMode(GL_MODELVIEW); glLoadldentity(); // Транслируем всю сцену в поле зрения // Это исходное преобразование наблюдения glTranslatef(O.Of, O.Of, -100.Of); // Красное ядро glColor3ub(255, 0, 0); glutSolidSphere(10.0f, 15, 15); // Желтые электроны glColor3ub(255, 255,0); // Орбита первого электрона // Записываем преобразование наблюдения glPushMatrixf) ; // Поворачиваем на угол поворота
194 Часть I. Классический OpenGL Рис. 4.16. Результат выполнения программы АТОМ glRotatef(fElectl, O.Of, l.Of, O.Of); // Трансляция элемента от начала координат на орбиту glTranslatef(90.Of, O.Of, O.Of); // Рисуем электрон glutSolidSphere(6.Of, 15, 15); // Восстанавливаем преобразование наблюдения glPopMatrix(); // Орбита второго электрона glPushMatrix(); glRotatef(45.Of, O.Of, O.Of, l.Of); glRotatef(fElectl, O.Of, l.Of, O.Of); glTranslatef(-70.Of, O.Of, O.Of); glutSolidSphere(6.Of, 15, 15); glPopMatrix(); // Орбита третьего электрона glPushMatrix() ; glRotatef(360.Of, -45.Of, O.Of, O.Of, l.Of); glRotatef(fElectl, O.Of, l.Of, O.Of); glTranslatef(O.Of, O.Of, 60.Of); glutSolidSphere(6.Of, 15, 15); glPopMatrix() ; // Увеличиваем угол поворота fElectl += 10.Of; if(fElectl > 360.Of) fElectl = O.Of; // Показываем построенное изображение glutSwapBuffers() ; } Разберем код, отвечающий за расположение первого электрона. Итак, в первой строке записывается текущая матрица наблюдения модели (в стек заносится текущее преобразование). // Орбита первого электрона // Записываем преобразование наблюдения glPushMatrix(); Теперь система координат кажется повернутой вокруг оси у на угол fElectl.
Глава 4 Геометрические преобразования'конвейер 195 // Поворот на угол поворота glRotatef(fElectl, O.Of, l.Of, O.Of); Путем трансляции повернутой системы координат рисуется электрон. // Трансляция от начала координат на орбиту glTranslatef(90.Of, O.Of, O.Of); Затем рисуется электрон (сплошная сфера), и восстанавливается (извлекается из стека) матрица наблюдения модели. // Рисуем электрон glutSolidSphere(6. Of, 15, 15); // Восстанавливаем преобразование наблюдения glPopMatrix(); Остальные электроны размещаются аналогично. Использование проекций Во всех приведенных выше примерах для размещения в объеме наблюдения точки наблюдения и объектов мы использовали матрицу наблюдения модели. В действи- тельности размер и форму наблюдаемого объема задает матрица проекции. Пока что мы создавали простой параллельный наблюдаемый объем, используя функцию glOrtho для указания ближней и дальней, левой и правой, верхней и ниж- ней координат отсечения. При загрузке в качестве матрицы проекции единичной матрицы мы указываем, что плоскости отсечения проходят через точки +1 и —1 на каждой оси. Если вы не загрузили матрицу перспективной проекции, сама по себе матрица проекции не корректирует масштаб или проекцию. Следующая пара программ ORTHO и PERSPECT не рассматривается подробно с точки зрения их исходного кода. В примерах применяется освещение и затенение (которые мы еще не рассматривали), подчеркивающие различия между ортографи- ческой и перспективной проекциями. На интерактивных примерах проще понять, как проекция может исказить внешний вид объекта Было бы неплохо, если бы вы запустили эти примеры при чтении следующих двух разделов. Ортографические проекции Ортографическая проекция, использованная в большинстве рассмотренных ранее примеров, представлена правильным кубом, который со всех сторон имеет вид квад- рата. Логическая ширина одинакова на передней, задней, верхней, нижней, левой и правой сторонах. В результате получается параллельная проекция, которая полезна для рисования специфических объектов, при наблюдении которых со стороны ракурс не учитывается. Это удобно, например, в автоматизированном проектировании, пред- ставлении такой двухмерной графики, как текст, или в архитектурных рисунках, где желательно представить точные размеры. На рис. 4.17 показан результат выполнения простой программы ORTHO (см. на компакт-диске папку, соответствующую данной главе). Чтобы получить полый тру- бообразный брусок, мы использовали ортографическую проекцию. На рис. 4.18 тот же брусок показан немного повернутым, чтобы вы видели его реальную длину.
196 Часть I. Классический OpenGL Рис. 4.17. Полая квадратная труба, показанная с помощью ортографической проекции Рис. 4.18. Вид квадратной трубы сбоку, на котором видна ее длина На рис. 4.19 показано, что вы видите, глядя непосредственно в торец тру- бы. Поскольку труба не сходится на расстоянии, изображение не соответствует тому, что можно наблюдать в реальной жизни. Чтобы учесть перспективу, нуж- на перспективная проекция. Перспективная проекция На перспективной проекции объекты, удаленные от наблюдателя, сокращаются и сжи- маются с помощью перспективного деления. Ширина задней части наблюдаемого объема не равна ширина передней после проектирования на экран. Таким образом, объект с одинаковыми физическими размерами кажется больше вблизи передней ча- сти наблюдаемого объема, чем вблизи задней. Геометрическая фигура, использованная в следующем примере, называется усе- ченной пирамидой. Усеченная пирамида — это усеченный фрагмент пирамиды, на- блюдаемый со стороны узкого конца в направлении широкого. Пример усеченной пирамиды с обозначенным наблюдателем показан на рис. 4.20. Усеченную пирамиду можно определить с помощью функции glFrustum. Ее пара- метрами являются координаты и расстояния между передней и задней отсекающими
Глава 4. Геометрические преобразования: конвейер 197 Рис. 4.19. Наблюдение торца трубы Перспективный объем наблюдения Рис. 4.20. Перспективная проекция, определенная усеченной пирамидой плоскостями. Однако функция glFrustum не совсем понятна интуитивно с точки зре- ния задания проекции для получения желаемого эффекта. Иногда легче использовать вспомогательную функцию gluPerspective. void gluPerspective(GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar); Параметрами функции gluPerspective является угол обзора, характеристиче- ское отношение высоты к ширине и расстояния до ближней и дальней плоскостей отсечения (см. рис. 4.21). Чтобы определить характеристическое отношение, высота (w) поля просмотра делится на его ширину (h). В листинге 4.2 показано, как ортографическая проекция из предыдущего примера меняется на перспективную. Учет ракурса повышает реализм использованной ранее ортографической проекции квадратной трубы (см. рис. 4.22, 4.23 и 4.24). Единствен- ным сделанным нами существенным изменением в коде листинга 4.2 является замена gluOrtho2D на gluPerspective. Листинг 4.2. Установка перспективной проекции в программе PERSPECT // Меняется наблюдаемый объем и поле просмотра. // Вызывается при изменении размеров окна void ChangeSize(GLsizei w, GLsizei h)
198 Часть I. Классический OpenGL ьняя плоскость Рис. 4.21. Усеченная пирамида, определенная функцией gluPerspective Рис. 4.22. Квадратная труба в перспективной проекции GLfloat fAspect; // Предотвращает деление на нуль if(h == 0) h = 1; // Устанавливает размеры поля просмотра равными размерам окна glViewport(0, 0, w, h); fAspect = (GLfloat)w/(GLfloat)h; // Обновляет систему координат
Глава 4. Гэометрические преобразования: конвейер 199 Рис. 4.24. Вид сквозь трубу при использовании перспективной проекции glMatrixMode(GL_PROJECTION); glLoadldentity(); // Генерирует перспективную проекцию gluPerspective(60.Of, fAspect, 1.0, 400.0); glMatrixMode(GL_MODELVIEW); glLoadldentity(); } Те же изменения мы внесли в программу АТОМ, назвав новый код с учетом пер- спективы АТОМ2. Запустите обе программы подряд, чтобы увидеть, что электроны кажутся меньше, когда они удаляются за ядро. Пример Чтобы создать завершенный пример, демонстрирующий работу с матрицей наблю- дения модели и перспективной проекцией, мы смоделировали в программе SOLAR вращение системы “Солнце-Земля-Луна”. Это классический пример вложенных пре- образований, когда объекты преобразовываются относительно друг друга с исполь- зованием стека матриц. Чтобы сделать пример более эффектным, мы добавили функ- ции освещения и затенения. Подробнее об этих процессах рассказывается в следую- щих двух главах. В нашей модели Земля движется вокруг Солнца, а Луна — вокруг Земли. Источник света находится в центра Солнца, которое нарисовано без освещения, создавая иллю- зию сияющего источника света. Пример демонстрирует, насколько просто с помощью OpenGL получать сложные эффекты. В листинге 4.3 приводится код, задающий проекцию, и код визуализации, отве- чающий за движение системы. Таймер, расположенный где-то в программе, ини- циирует перерисовывание окна 10 раз в секунду, поддерживая активной функцию RenderScene. Обратите внимание на рис. 4.25 и 4.26: когда Земля расположена пе- ред Солнцем, она кажется больше; Земля, находящаяся с противоположной стороны, выглядит меньше.
200 Часть I Классический OpenGL Листинг 4.3. Код системы “Солнце-Земля-Луна” // Меняется наблюдаемый объем и поле просмотра. // Вызывается при изменении размеров окна void ChangeSize(GLsizei w, GLsizei h) { GLfloat fAspect; // Предотвращаем деление на нуль if(h == 0) h = 1; // Устанавливаем размеры поля просмотра равными размерам окна glViewport(0, 0, w, h); // Рассчитываем характеристическое отношение окна fAspect = (GLfloat)w/(GLfloat)h; // Устанавливаем перспективную систему координат glMatrixMode(GL_PROJECTION); glLoadldentity(); // Поле обзора равно 45 градусов, ближняя и дальняя плоскости // проходят через 1 и 425 gluPerspective(45.Of, fAspect, 1.0, 425.0); // Обновляем матрицу наблюдения модели glMatrixMode(GL_MODELVIEW); glLoadldentity(); } // Вызывается для рисования сцены void RenderScene(void) { // Угол поворота системы Земля/Луна static float fMoonRot = O.Of; static float fEarthRot = O.Of; // Очищаем окно текущим цветом очистки glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Записываем состояние матрицы и выполняет повороты glMatrixMode(GL_MODELVIEW); glPushMatrix(); // Транслируем всю сцену в поле зрения glTranslatef(O.Of, O.Of, -300.0f); // Устанавливаем цвет материала желтым // Солнце glColor3ub(255, 255, 0); glDisable(GL_LIGHTING); glutSolidSphere(15.Of, 15, 15); glEnable(GL_LIGHTING); // После изображения Солнца помещаем источник света! glLightfv(GL_LIGHT0,GL_POSITION,lightPos) ; // Поворачиваем систему координат glRotatef(fEarthRot, O.Of, l.Of, O.Of); // Рисуем Землю glColor3ub(0,0, 255) ; glTranslatef(105.Of,0.Of,O.Of); glutSolidSphere(15.Of, 15, 15); // Поворот в системе координат, связанной с Землей, // и изображение Луны
Глава 4. Гэометрические преобразования: конвейер 201 Рис. 4.25. Система “Солнце- Земля-Луна”: Земля находится ближе к наблюдателю Рис. 4.26. Система “Солнце- Земля-Луна”: Земля удалена от наблюдателя glColor3ub(200,200,200); glRotatef(fMoonRot, O.Of, l.Of, O.Of); glTranslatef(30.Of, O.Of, O.Of); fMoonRot+= 15.Of; if(fMoonRot > 360.Of) fMoonRot = O.Of; glutSolidSphere(6.Of, 15, 15); // Восстанавливается состояние матрицы glPopMatrix(); // Матрица наблюдения модели // Шаг по орбите Земли равен пяти градусам fEarthRot += 5.Of; if(fEarthRot > 360.Of) fEarthRot = O.Of; // Показывается построенное изображение glutSwapBuffers(); } Нетривиальное умножение матриц Описанные высокоуровневые “законсервированные” преобразования (поворота, мас- штабирования и трансляции) прекрасно подходят для решения многих простых задач. Реальные же мощь и гибкость получат только те, кто потрудится понять непосред- ственное использование матриц. Это не так сложно, как кажется, но вначале нужно понять магию этих 16 чисел, составляющих матрицу преобразования размером 4x4.
202 Часть I Классический OpenGL а0 38 3[2 3] 35 З9 3]3 а2 36 310 а14 Зз З7 Зц 315 Рис. 4.27. Развертывание матрицы по столбцам OpenGL представляет матрицу 4 х 4 не как двухмерный массив чисел с плавающей запятой, а как единый массив 16 значений с плавающей запятой. Подход, принятый в OpenGL, отличается от того, что применяется во многих математических библио- теках, “понимающих” двухмерные массивы. Например, из приведенных ниже двух вариантов OpenGL предпочитает первый. GLfloat matrix[16]; // Прекрасная матрица // с точки зрения OpenGL GLfloat matrix[4][4]; // Вариант популярный, но не сильно // эффективный в OpenGL OpenGL может использовать второй вариант, но первый гораздо эффективнее. Причину этого мы сейчас объясним. Данные 16 элементов представляют матрицу 4x4, как показано на рис. 4.27. Когда элементы массива последовательно прохо- дятся по столбцам матрицы, такой порядок называется развертыванием матрицы по столбцам. В памяти компьютера представление двухмерного массива как матрицы 4x4 (второй вариант в приведенном выше коде) — это развертывание матрицы по строкам. Говоря математическими терминами, чтобы перевести одну матрицу в другую, ее нужно транспонировать. Подлинная магия в том, что эти 16 значений представляют определенную точку в пространстве и ориентацию трех осей относительно системы наблюдения (помни- те, что это стационарная, неизменяемая система координат, о которой мы говорили ранее). Интерпретировать эти числа совсем не сложно. Четыре столбца представ- ляют четырехэлементный вектор. Поскольку мы решили не усложнять излагаемый материал, сфокусируем внимание только на трех первых элементах этих векторов. Четвертый вектор-столбец содержит значения х, у и z преобразованной системы ко- ординат. При действии функции glTranslate на единичную матрицу все, что она делает, — это помещает ваши значения х, у и z в двенадцатую, тринадцатую и че- тырнадцатую позиции матрицы Первые три столбца являются просто направленными векторами, которые пред- ставляют ориентацию (здесь векторы указывают направление) осей х, у и z в про- странстве. В большинстве случаев эти три вектора всегда образуют друг с другом угол 90°. Математически (если вы хотите поразить своих друзей новым термином) это называется ортонормальными векторами. На рис. 4.28 показана матрица пре- образования 4 х 4 с обозначенными векторами-столбцами. Обратите внимание на последнюю строку матрицы кроме последней единицы, все элементы равны нулю. Самым впечатляющим моментом является то, что, если имеется матрица 4x4, которая содержит положение и ориентацию другой системы координат, то, умно- жив эту матрицу на вершину (матрицу- или вектор-столбец), вы получите новую вершину, преобразованную в новую систему координат. Это означает, что положе- ние в пространстве и любую желаемую ориентацию можно единственным образом
Глава 4. Гэометрические преобразования, конвейер 203 Рис. 4.28. Как матрица 4x4 представляет положение и ориентацию в трехмерном пространстве определить матрицей 4 х 4, и, умножив все вершины объекта на эту матрицу, вы преобразуете весь объект в данную точку пространства с данной ориентацией! АППАРАТНЫЕ ПРЕОБРАЗОВАНИЯ Многие реализации OpenGL имеют то, что называется аппаратным T&L (“Transform and Lighting” — преобразование и освещение). Это означает, что матрица преобра- зования умножает многие тысячи вершин на специальном графическом аппаратном обеспечении, которое очень, очень быстро выполняет эту операцию. (Процессорам Intel и AMD такая скорость и не снилась!) Однако такие функции, как glRotate и glScale, создающие матрицы преобразований, обычно не ускоряются аппарат- но, поскольку представляют каплю в море матричной математики, требуемой для рисования сцены. И все-таки, почему OpenGL “настаивает” на развертывании по столбцам? Ответ прост. Чтобы получить вектор, направленный по оси, или вычислить по матрице необходимую трансляцию, OpenGL просто извлекает одну копию из памяти, чтобы иметь все данные в одном месте. При развертывании по строкам программное обес- печение должно обращаться к трем (или четырем) различным ячейкам памяти, чтобы выделить из матрицы один вектор. Загрузка матрицы После того как вы разберетесь, каким образом матрица 4x4 представляет положение и ориентацию, сможете создавать и загружать собственные матрицы преобразований. Чтобы загрузить произвольную матрицу с разверткой по столбцам в стек матриц про- екции, наблюдения модели или текстуры, можно использовать одну из приведенных ниже функций: glLoadMatrixf(GLfloat m); или glLoadMatrixd(GLfloat m);
204 Часть I. Классический OpenGL В большинстве реализаций OpenGL данные конвейера хранятся и обрабатываются как величины с плавающей запятой (float — f), а не как величины двойной точности (double — d); следовательно, использование второго варианта может давать меньшую производительность, ведь в этом случае 16 величин двойной точности нужно вначале преобразовать в числа обычной точности с плавающей запятой. В приведенном ниже коде показано, как в массив загружается единичная матрица, после чего сам массив загружается в стек матриц наблюдения модели. Данный пример эквивалентен вызову glLoadldentity с помощью функций высокого уровня. // Загружается единичная матрица glFloat m[] = { l.Of, O.Of, O.Of, O.Of, O.Of, l.Of, O.Of, O.Of, O.Of, O.Of, l.Of, O.Of, O.Of, O.Of, O.Of, l.Of } // Столбец X // Столбец Y // Столбец Z // Трансляция glMatrixMode(GL_MODELVIEW); glLoadMatrixf(m); Хотя внутренне OpenGL предпочитает развертывание по столбцам, OpenGL пред- лагает и функции для загрузки матрицы в построчном порядке. Чтобы транспониро- вать матрицу перед ее загрузкой в стек матриц применяются следующие функции. void glLoadTransposeMatrixf (GLfloat л?); и void glLoadTransposeMatrixd(GLdouble л?); Выполнение собственных преобразований Ну что ж, рассмотрим, как создавать и загружать собственные матрицы преобразо- ваний (трудный путь)! В приведенной на компакт-диске программе TRANSFORM мы рисуем тор (объект в форме бублика) перед точкой наблюдения и заставляем его вращаться на месте. За всю математику, необходимую для генерации геометрии тора, отвечает функция DrawTorus, принимающая в качестве аргумента матрицу преобра- зования 4x4 (которая позже будет действовать на вершины). Чтобы преобразовать тор, мы создаем матрицу и действуем ею на все вершины. Итак, начнем с основной функции визуализации, приведенной в листинге 4.4. Листинг 4.4. Код, задающий при рисовании матрицу преобразования void RenderScene(void) { GLTMatrix transformationMatrix; // Здесь хранится // матрица поворота static GLfloat yRot = O.Of; // Угол поворота, // задействованный в анимации yRot += 0.5f; // Очищаем окно текущим цветом очистки glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Строим матрицу поворота gltRotationMatrix(gltDegToRad(yRot), O.Of, l.Of, O.Of, transformationMatrix); transformationMatrix[12] = O.Of;
Глава 4 Геометрические преобразования: конвейер 205 transformationMatrix[13] = O.Of; transformationMatrix[14] = -2.5f; DrawTorus(transformationMatrix); // Переключаем буферы glutSwapBuffers(); } Вначале мы указываем, где будет храниться матрица GLTMatrix transformationMatrix; // Здесь хранится // матрица поворота Тип данных GLTMatrix относится к собственному творчеству и является просто определением типа, объявленном в gltools .h как массив из 16 элементов с плава- ющей запятой. typedef GLfloat GLTMatrix[16]; // Матрица 4 на 4 величин типа GLfloat, развертываемая по столбцам Анимация в этом примере заключается в последовательном увеличении перемен- ной yRot, которая представляет поворот вокруг оси у. После очистки буфера цвета и глубины составляем матрицу преобразования: gltRotationMatrix(gltDegToRad(yRot), O.Of, l.Of, O.Of, transformationMatrix); transformationMatrix[12] = O.Of; transformationMatrix[13] = O.Of; transformationMatrix[14] = -2.5f; Первая строка содержит вызов другой функции glTools, именуемой gltRota- tionMatrix. Эта функция принимает в качестве аргументов угол поворота в радиа- нах (это способствует более эффективным расчетам) и три аргумента, задающих век- тор, вокруг которого выполняется поворот. Исключая то, что угол задан в радианах, а не в градусах, эта функция не отличается от функции OpenGL glRotate. Послед- ний аргумент — это матрица, в которой вы хотите записать получающуюся матрицу поворота. Функция gltDegToRad выполняет преобразование градусов в радианы. Как показано на рис. 4.28, последние два столбца матрицы представляют трансля- цию преобразования. Вместо того чтобы выполнять полное умножение матриц, мы можем просто ввести трансляцию непосредственно в матрицу. Теперь получающаяся матрица представляет трансляцию в пространстве (точку, в которую помещается тор) и последующий поворот системы координат объекта, выполненный в этой точке. Далее мы передаем эту матрицу преобразования функции DrawTorus. Мы не будем приводить всю функцию, необходимую для рисования тора, а обратим ваше внимание на такие ее строки: objectvertex[0] = х0*г; objectVertex[l] = у0*г; objectvertex[2] = z; gltTransformPoint(objectvertex, mTransform, transformedVertex); glVertex3fv(transformedVertex);
206 Часть I. Классический OpenGL Рис. 4.29. Вращающийся тор, выполняющий заданные нами преобразования Три компоненты вершины загружаются в массив и передаются функции glt- TransformPoint. Эта функция glTools выполняет умножение вершин на матрицу и возвращает преобразованную вершину в массив trans formedVertex. После этого мы используем векторную версию givertex и посылаем OpenGL данные о верши- нах. Результатом является вращающийся тор, показанный на рис. 4.29. Важно, чтобы вы хотя бы раз увидели реальную механику преобразования вершин матрицей с использованием подобного детального примера. По мере роста ваших зна- ний как программиста OpenGL вы обнаружите, что необходимость преобразовывать точки вручную возникает в таких не связанных прямо с визуализацией задачах, как детектирование столкновений (упругие удары об объекты), отбор по усеченной пира- миде (элементы, которые вы не видите, отбрасываются и не рисуются) и в некоторых других алгоритмах специальных эффектов. Стоит сказать, однако, что для обработки геометрии программа TRANSFORM неэффективна. Мы поручаем процессору заботу обо всей матричной математике вме- сто того, чтобы поручить специализированному аппаратному обеспечению OpenGL выполнить для нас всю работу (а это было бы гораздо быстрее!). Кроме того, посколь- ку OpenGL имеет матрицу наблюдения модели, все преобразованные точки еще раз множатся на единичную матрицу. Это не меняет значения преобразованных вершин, и является ненужной операцией. Для полноты обсуждения мы предоставляем улучшенный пример под названи- ем TRANSFORMGL, в котором применяется матрица преобразования, но всю рабо- ту выполняет OpenGL, используя функцию glLoadMatrixf. Мы удалили функцию DrawTorus с сопутствующим кодом и использовали более универсальную функцию рисования тора gltDrawTorus из библиотеки glTools. Измененный код приводится в листинге 4.5.
Глава 4. Геометрические преобразования: конвейер 207 Листинг 4.5. Загрузка матрицы преобразования непосредственно в OpenGL // Построение матрицы поворота gltRotationMatrix(gltDegToRadfyRot), O.Of, l.Of, O.Of, transformationMatrix); transformationMatrix[12] = O.Of; transformationMatrix[13] = O.Of; transformationMatrix[14] = -2.5f; glLoadMatrixf(transformationMatrix); gltDrawTorus(0.35, 0.15, 40, 20); Складывание преобразований В предыдущем примере мы просто построили одну матрицу преобразования и загру- зили ее в матрицу наблюдения модели. При применении данной техники вся геомет- рия, предшествующая матрице, преобразовывается перед визуализацией Как видно из других примеров, мы часто складываем преобразования. Например, мы использу- ем функцию glTranslate перед функцией glRotate, чтобы вначале транслировать, а затем повернуть объект перед изображением на экране. Действия OpenGL “за сце- ной” при вызове нескольких функций преобразования заключаются в умножении существующей матрицы преобразования на матрицу, которую вы прибавляете или присоединяете к ней. Например, в программе TRANSFORMGL мы можем заменить код, приведенный в листинге 4.5, кодом примерного такого вида: glPushMatrix() ; glTranslatef(O.Of, O.Of, -2.5f); glRotatef(yRot, O.Of, l.Of, O.Of); gltDrawTorus(0.35, 0.15, 40, 20); glPopMatrix() ; При использовании этого подхода мы записываем текущую единичную матрицу, умноженную на матрицу трансляции и на матрицу поворота, а затем с помощью ре- зультата рисуем тор. Вы можете выполнить эти умножения самостоятельно, используя функцию gltMultiplyMatrix из glTools, и тогда получится следующий код. GLTMatrix rotationMatrix, translationMatrix, transformationMatrix; gltRotationMatrix(gltDegToRad(yRot), O.Of, l.Of, O.Of, rotationMatrix) ; gltTranslationMatrix(0.Of, O.Of, -2.5f, translationMatrix); gltMultiplyMatrix(translationMatrix, rotationMatrix, transformationMatrix); glLoadMatrixf(transformationMatrix); gltDrawTorus(0.35f, 0.15f, 40, 20); OpenGL также имеет собственную функцию умножения матриц glMultMatrix, которая принимает в качестве аргумента матрицу и умножает ее на текущую загру- женную матрицу, записывая результат вверху стека матриц. В последнем фрагменте кода мы приводим эквивалент предыдущего кода, на этот раз позволяя OpenGL пе- ремножать матрицы.
208 Часть I Классический OpenGL GLTMatrix rotationMatrix, translationMatrix, transformationMatrix; glPushMatrix(); gltRotationMatrix(gltDegToRad(yRot), O.Of, l.Of, O.Of, rotationMatrix); gltTranslationMatrix(0.Of, O.Of, -2.5f, translationMatrix); glMultMatrixf(translationMatrix); glMultMatirxf(rotationMatrix); gltDrawTorus(0.35f, 0.15f, 40, 20); glPopMatrix(); Повторимся, вы должны помнить, что функции glMultMatrix и другие высо- коуровневые функции, умножающий матрицы (glRotate, glScale, glTranslate), выполняются не на аппаратном обеспечении OpenGL, а на процессоре. Создание в OpenGL движения с использованием камер и актеров Чтобы описать положение и ориентацию любого объекта на трехмерной сцене, вы мо- жете использовать одну матрицу 4x4, представляющую его преобразование. Тем не менее работа непосредственно с матрицами может быть несколько неудобной, поэто- му программисты всегда ищут способы более лаконичного представления положения и ориентации в пространстве. Такие фиксированные объекты, как ландшафт, часто не изменяются, и их вершины обычно точно задают, где в пространстве должны ри- соваться геометрические объекты. Объекты, которые движутся по сцене, называются актерами, по аналогии с актерами на подмостках реальной сцены. Актеры имеют собственные преобразования, и часто другие актеры преобразо- вываются не только относительно внешней системы координат (координаты системы наблюдения), но и относительно других актеров. Говорят, что каждый актер со своими преобразованиями имеет собственную систему отсчета или локальную систему ко- ординат объекта. Часто (в геометрических проверках, не связанных с визуализацией) полезно переходить из локальной системы во внешнюю и обратно. Система актеров Простым и гибким способом представления системы отсчета является использование структуры данных (или класса в C++), которая содержит точку в пространстве, вектор, указывающий вперед, и вектор, указывающий вверх. Используя эти величины, можно однозначно определить данную точку и ориентацию в пространстве. Приведенный ниже пример взят из библиотеки glTools и является структурой данных GLFrame, которая может хранить всю эту информацию в одном месте. typedef struct{ GLTVector3f vLocation; GLTVector3f vUp; GLTVector3f vForward; } GLTFrame;
Глава 4. Гэометрические преобразования: конвейер 209 Использование системы отсчета, подобной приведенной, для представления по- ложения и ориентации объекта является очень мощным аппаратом. Для начала вы можете использовать эти данные непосредственно для создания матрицы преобразо- вания 4x4. Обратимся к рис. 4.28. Вектор направления “вверх” становится столбцом у матрицы, тогда как вектор “вперед” превращается в вектор столбца z, а точка- положение становится вектором-столбцом трансляции. В результате остается неиз- вестным только вектор-столбец х, но поскольку мы знаем, что все три оси взаимно перпендикулярны (ортонормальны5), мы можем вычислить вектор-столбец х, найдя векторное произведение векторов х и у. В листинге 4.6 показана функция gltGet- MatrixFromFrame из glTools, которая именно это и делает. Листинг 4.6. Код вычисления матрицы 4 х 4 по системе отсчета /////////////////////////////////////////////////////////////////// // Выводит матрицу 4 на 4 из системы отсчета void gltGetMatrixFromFrame(GLTFrame *pFrame, GLTMatrix mMatrix) { GLTVector3f vXAxis; // Находим ось x // Рассчитываем ось x gltVectorCrossProduct(pFrame->vUp, pFrame->vForward, vXAxis); // Заселяем матрицу // Вектор-столбец х memcpy(mMatrix, vXAxis, sizeof(GLTVector)) ; mMatrix[3] = O.Of; // Вектор-столбец у memcpy(mMatrix+4, pFrame->vUp, sizeof(GLTVector)); mMatrix[7] = O.Of; // Вектор-столбец z memcpy(mMatrix+8, pFrame->vForward, sizeof(GLTVector)); mMatrix[11] = O.Of; // Вектор трансляции/положения memcpy(mMatrix+12, pFrame->vLocation, sizeof(GLTVector)); mMatrix[15] = l.Of; } Применение преобразования актеров так же просто, как вызов glMultMatrixf с аргументом, равным получающейся в результате матрице. Углы Эйлера: “Используй систему, Люк!” Многие книги по программированию графики рекомендуют еще более простой меха- низм хранения информации о положении и ориентации объекта: углы Эйлера. Углы Эйлера требуют меньше места для хранения, поскольку, по сути, вы записываете по- ложение объекта, а затем просто указываете три угла, представляющих повороты во- круг осей х, у и z, которые иногда называются рысканье, тангаж и крен. Структура, подобная приведенной ниже, может представлять положение и ориентацию самолета. 5 Ортогональны. — Примеч. перев
210 Часть I Классический OpenGL struct EULER { GLTVector3f vPosition; GLfloat fRoll; GLfloat fPitch; GLfloat fYaw; 1; Углы Эйлера немного “скользкие”, как их иногда характеризуют. Первой про- блемой является то, что точку и ориентацию можно представить не единственным набором углов Эйлера. Наличие нескольких наборов углов может стать проблемой, если вас интересует, как можно перейти от одной ориентации к другой. Иногда воз- никает и вторая проблема, называемая “карданным сцеплением” (“gimbal lock”); она делает невозможным поворот вокруг одной из осей. Наконец, углы Эйлера делают более трудоемким расчет новых координат для простого движения вперед вдоль ли- нии наблюдения или расчет новых углов Эйлера, если требуется выполнить поворот вокруг одной из локальных осей. В современной литературе проблемы углов Эйлера иногда пытаются решить с по- мощью математического аппарата, именуемого кватернионами. Сложные для по- нимания кватернионы в действительности не решают проблем, связанных с углами Эйлера, которые вы бы не могли решить самостоятельно, используя описанный выше метод системы отсчета. Мы уже обещали, что в книге не будет сложной математики, поэтому не будем обсуждать достоинства указанных систем. Тем не менее должны сказать, что дебатам на тему “кватернионы против линейной алгебры (матриц)” более ста лет, те. они начались задолго до того, как обе этих концепции начали применяться в компьютерной графике! Управление камерой В OpenGL в действительности нет никакого преобразования камеры. Мы используем концепцию камеры как полезную метафору, помогающую управлять точкой зрения в определенной иммерсивной трехмерной среде. Если вообразить камеру как объект, расположенный в некоторой точке пространства и имеющий ориентацию, мы обна- ружим, что в текущей системе отсчета как актеров, так и камеру можно представить в трехмерной среде. Чтобы применить преобразование камеры, берем преобразование актеров камеры и преображаем его так, чтобы движение камеры назад было эквивалентно движению всего мира вперед. Подобным образом, поворот налево эквивалентен вращению всего мира вправо. Чтобы визуализировать данную сцену, мы обычно принимаем подход, схематически представленный на рис. 4.30. Библиотека GLU содержит функцию, которая формирует преобразование камеры на основе данных, записанных в структуре системы отсчета. void gluLookAt(GLdouble eyex, GLdouble eyey, GLdouble eyez, GLdouble centerx, GLdouble centery,GLdouble centerz, GLdouble upx, GLdouble upy, GLdouble upz); В качестве аргумента функция принимает положение глаза, точку, расположен- ную непосредственно перед глазом, и направление вверх. Библиотека glTools также
Глава 4. Гзометрические преобразования: конвейер 211 Рис. 4.30. Типичный цикл визуализации трехмерной среды > Записать единичную матрицу Применить преобразование камеры Нарисовать элементы, которые не двигаются Нарисовать движущиеся объекты (актеров) • Записать преобразование камеры Применить преобразование актеров Нарисовать геометрию актеров Восстановить преобразование камеры — Восстановить единичную матрицу содержит сокращенную функцию, выполняющую эквивалентное действие с исполь- зованием системы отсчета. void gltApplyCameraTransform(GLTFrame *pCamera); Собираем все вместе Разберем теперь последний пример этой главы, связывающий в единое целое все концепции, которые мы здесь обсуждали. В программе SPHEREWORLD мы создаем мир, населенный сферами, которые располагаются в случайных местах. Каждая сфера представляется отдельной структурой GLTFrame, отвечающей за ее положение и ори- ентацию. Также мы используем систему отсчета, чтобы представить камеру, которую можно двигать вокруг мира сфер с помощью клавиш с изображением стрелки. В цен- тра мира сфер мы с помощью простой высокоуровневой процедуры преобразования создадим вращающийся тор со сферой на его орбите. В данном примере собраны все идеи, рассмотренные выше, и показано, как они работают вместе. В дополнение к основному исходному файлу sphereworld, с про- ект также содержит модули torus . с, matrixmath. с и framemath. с из библиотеки glTools, имеющиеся в папке \common. Мы не приводим программу целиком, по- скольку ее “скелет” GLUT такой же, как и в других примерах, но наиболее важные функции и фрагменты представлены в листинге 4.7. Листинг 4.7. Основные функции программы SPHEREWORLD ♦define NUM_SPHERES 50 GLTFrame spheres[NUM_SPHERES]; GLTFrame framecamera; /////////////////////////////////////////////////////////////////// // Функция выполняет всю необходимую инициализацию в контексте // визуализации void SetupRC() { int iSphere; // Голубоватый фон glClearColor(O.Of, O.Of, .50f, l.Of ); // Все рисуется в каркасном виде glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
212 Часть I. Классический OpenGL glt!nitFrame(&frameCamera); // Инициализируется камера // Случайным образом размещаются жители-сферы for(iSphere = 0; iSphere < NUM_SPHERES; iSphere++) { gltlnitFrame(&spheres[iSphere]); // Инициализируется // система отсчета // Выбираются случайные положения между -20 и 20 // с шагом 0,1 spheres[iSphere].vLocation[ 0 ] = (float)((rand() % 400) - 200) * O.lf; spheres[iSphere].vLocation[l] = O.Of; spheres[iSphere].vLocation[2] = (float)((rand() % 400) - 200) * O.lf; } } /////////////////////////////////////////////////////////////////// // Рисуется земля с координатной сеткой void DrawGround(void) { GLfloat fExtent = 20.Of; GLfloat fStep = l.Of; GLfloat у = -0.4f; GLint iLine; glBegin(GL_LINES); for(iLine = -fExtent; iLine <= fExtent; iLine += fStep) { glVertex3f(iLine, y, fExtent); // Рисуются z-дорожки glVertex3f(iLine, y, -fExtent); glVertex3f(fExtent, y, iLine); glVertex3f(-fExtent, y, iLine); } glEnd(); } // Вызывается для рисования сцены void RenderScene(void) { int i; static GLfloat yRot = O.Of; // Используемый в анимации // угол поворота yRot += 0.5f; // Очищаем окно текущим цветом очистки glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glPushMatrix(); gltApplyCameraTransform(&frameCamera); // Рисуем землю DrawGround(); // Рисуем случайным образом расположенные сферы for(i =0; i < NUM_SPHERES; i++) { glPushMatrix(); gltApplyActorTransform(&spheres[i]); glutSolidSphere(O.lf, 13, 26); glPopMatrix();
Глава 4. Гэометрические преобразования: конвейер 213 } glPushMatrix() ; glTranslatef(O.Of, O.Of, -2.5f); glPushMatrix() ; glRotatef(-yRot * 2.Of, O.Of, l.Of, O.Of); glTranslatef(l.Of, O.Of, O.Of); glutSolidSphere(0.If, 13, 26); glPopMatrix() ; glRotatef(yRot, O.Of, l.Of, O.Of); gltDrawTorus(0.35, 0.15, 40, 20); glPopMatrix() ; glPopMatrix() ; // Переключаем буферы glutSwapBuffers() ; } // Реагирует на клавиши co стрелками, двигая // систему отсчета камеры void SpecialKeys(int key, int x, int y) { if(key == GLUT_KEY_UP) gltMoveFrameForward(&frameCamera, 0. If) ; if(key == GLUT_KEY_DOWN) gltMoveFrameForward(&frameCamera, -0.If); if(key == GLUT_KEY_LEFT) gltRotateFrameLocalY(&frameCamera, 0.1); if(key == GLUT_KEY_RIGHT) gltRotateFrameLocalY(&frameCamera, -0.1); // Обновляем окно glutPostRedisplay() ; } Первые несколько строк содержат макрос, определяющий число сферических “жи- телей” равным 50. После этого объявляется массив систем отсчета и еще одна систе- ма, представляющая камеру. #define NUM_SPHERES 50 GLTFrame spheres[NUM_SPHERES]; GLTFrame frameCamera; Функция SetupRC вызывает функцию glTools под названием gltlnitFrame с целью инициализации в начале координат камеры, смотрящей в отрицательном направлении оси z (ориентация OpenGL по умолчанию). gltlnitFrame(&frameCamera); // Инициализируется камера С помощью этой функции можно инициализировать любую каркасную структуру, также вы можете самостоятельно инициализировать структуру, имеющую желаемое положение и ориентацию. После этого в программе идет цикл, инициализирующий массив сферических каркасов и выбирающий случайные координаты х и z точек расположения этих сфер. // Случайным образом размещаются жители-сферы for(iSphere = 0; iSphere < NUM_SPHERES; iSphere++)
214 Часть I. Классический OpenGL glt!nitFrame(&spheres[iSphere]); // Инициализируется // система отсчета // Выбираются случайные положения между -20 и 20 с шагом 0,1 spheres[iSphere].vLocation[0] = (float)((rand() % 400) - 200) * O.lf; spheres[iSphere].vLocation[l] = O.Of; spheres[iSphere].vLocation[2] = (float)((rand() % 400) - 200) * O.lf; } Затем функция DrawGround с помощью отрезков GL_LINE рисует землю как ряд перекрещивающихся линий. /////////////////////////////////////////////////////////////////// // Рисуется земля с координатной сеткой void DrawGround(void) { GLfloat fExtent = 20.Of; GLfloat fStep = l.Of; GLfloat у = -0.4f; GLint iLine; glBegin(GL_LINES); for(iLine = -fExtent; iLine <= fExtent; iLine += fStep) { glVertex3f(iLine, y, fExtent); // Рисуются z-дорожки glVertex3f(iLine, y, -fExtent); glVertex3f(fExtent, y, iLine); glVertex3f(-fExtent, y, iLine); ) glEndO; } Функция RenderScene рисует мир с нашей точки наблюдения. Обратите вни- мание на то, что мы вначале записываем единичную матрицу, а затем с помощью вспомогательной функции glTools gltApplyCameraTransform применяем преоб- разование камеры. Земля статична и преобразовывается камерой только для того, чтобы создать иллюзию движения. glPushMatrix(); gltApplyCameraTransform(&framecamera); I/ Рисуем землю DrawGround() ; После этого мы рисуем все случайным образом расположенные сферы. По си- стеме отсчета функция gltApplyActorTransform создает матрицу преобразования и умножает ее на текущую матрицу (а это матрица камеры). Каждой сфере должно сопоставляться собственное преобразование относительно камеры, поэтому матрица камеры записывается при каждом вызове функции glPushMatrix и снова восста- навливается с помощью glPopMatrix, готовой к обработке новой сферы или новому преобразованию.
Глава 4. Гэометрические преобразования: конвейер 215 Рис. 4.31. Результат выполнения программы SPHEREWORLD // Рисуем случайным образом расположенные сферы for(i =0; i < NUM_SPHERES; i++) { glPushMatrix(); gltApplyActorTransform(&spheres[i]); glutSolidSphere(0.1f, 13, 26); glPopMatrix(); } А вот теперь пришло время активных действий! Вначале мы перемещаем систему координат немного вниз по оси z, чтобы можно было увидеть, что следует рисовать дальше. Записываем эту точку и выполняем поворот, затем трансляцию и рисуем сферу. Из-за применения данного эффекта сфера кажется вращающейся вокруг начала координат прямо перед нами. Затем восстанавливаем матрицу преобразования, но только так, чтобы положение начала координат было в точке z = —2,5. После этого, но перед рисованием тора, выполняется еще один поворот. В результате кажется, что тор вращается. glPushMatrix(); glTranslatef(O.Of, O.Of, -2.5f); Наконец, при нажатии любой клавиши со стрелкой вызывается функция Spe- cialKeys. Клавиши со стрелками вверх и вниз вызывают функцию glTools glt- MoveFrameForward, которая просто перемещает систему отсчета вдоль линии обзо- ра. В ответ на нажатие клавиш со стрелками влево и вправо функция gltRotate- FrameLocalY поворачивает систему отсчета вокруг локальной оси у (независимо от ориентации).
216 Часть I. Классический OpenGL glPushMatrix(); glRotatef(-yRot * 2.Of, O.Of, l.Of, O.Of); glTranslatef(l.Of, O.Of, O.Of); glutSolidSphere(O.lf, 13, 26); glPopMatrix(); glRotatef(yRot, O.Of, l.Of, O.Of); gltDrawTorus(0.35, 0.15, 40, 20); glPopMatrix(); glPopMatrix(); В сумме все рассмотренные функции дают следующий эффект: мы видим сетку на земле со множеством сфер, разбросанным по случайным местам. Перед нами находится вращающийся тор, по орбите которого быстро движется сфера (рис. 4.31). void SpecialKeys(int key, int x, int y) { if(key == GLUT_KEY_UP) gltMoveFrameForward(&frameCamera, 0.If); if(key == GLUT_KEY_DOWN) gltMoveFrameForward(&frameCamera, -O.lf); if(key == GLUT_KEY_LEFT) gltRotateFrameLocalY(&frameCamera, 0.1); if(key == GLUT_KEY_RIGHT) gltRotateFrameLocalY(&frameCamera, -0.1); // Обновляет окно glutPostRedisplay(); } ПРИМЕЧАНИЕ ПО ОПРОСУ КЛАВИАТУРЫ Движение камеры в ответ на клавиатурные сообщения может иногда дать меньше, чем наиболее гладкую из возможных анимацию. Это объясняется тем, что скорость нажатая на клавиши обычно не превышает 20 раз в секунду. Для получения наи- лучших результатов визуализация должна выполняться со скоростью не меньше, чем 30 кадров в секунду (оптимальный вариант — 60 кадров/с), один раз опрашивая клавиатуру для каждого кадра анимации. Делать это с помощью такой переноси- мой библиотеки, как GLUT, немного опасно, но в главах, посвященных различным операционным системам, рассказано, как достичь наиболее гладкой анимации. Там также рассмотрены методы создания временной анимации вместо представленной выше покадровой (перемещения на фиксированную величину при каждом перери- совывании сцены). Резюме В этой главе изучаются концепции применения OpenGL к созданию трехмерных сцен. Даже если вы не научились оперировать матрицами в уме, теперь вы знаете, что такое матрицы, и как они используются для выполнения различных преобразований. Кроме того, вы узнали, как работать со стеками матриц наблюдения модели и проекции, помещая объекты на сцене и определяя их вид на экране.
Глава 4. Геометрические преобразования: конвейер 217 Кроме того, мы представили функции, необходимые для овладения магией матриц. Эти функции позволяют создавать собственные матрицы, загружать их в стек матриц или умножать на текущую матрицу. В главе также вводится мощная концепция си- стемы отсчета, и показывается, как манипулировать системами отсчета и превращать их в преобразования. Наконец, мы остановились на библиотеке glTools, которую можно найти на компакт-диске, прилагаемом к книге. Эта библиотека написана целиком на перено- симом языке ANSI С и является полезным набором инструментов, выполняющих различные математические операции, и вспомогательных процедур, которые можно использовать при работе с OpenGL. Справочная информация gIFrustum Цель: Умножить текущую матрицу на матрицу перспективной проекции Включаемый файл: <gl.h> Синтаксис: void gIFrustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); Описание: Создает матрицу перспективной проекции. Предполагается, что глаз расположен в точке (0,0,0), расстояние до дальней плоскости отсечения равно zFar, a zNear задает расстояние до ближней плоскости отсечения. Оба значения должны быть положительными. Может неблагоприятно повлиять на точность буфера глубины при большом отношении расстояния до дальней плоскости к расстоянию до ближней (far/near) Параметры: left, right (тип GLdouble) bottom, top (тип GLdouble) zNear, zFar (тип GLdouble*) Что возвращает: Координаты левой и правой плоскостей отсечения Координаты нижней и верхней плоскостей отсечения Расстояние до ближней и дальней плоскостей отсечения. Оба значения должны быть положительными Ничего См. также: glOrtho, glMatrixMode, glMultMatrix, glViewport
218 Часть I. Классический OpenGL ТАБЛИЦА 4.2. Приемлемые идентификаторы матричного режима в функции glMatrixMode Режим Стек матриц GL_MODELVIEW Действия с матрицами затрагивают стек матриц наблюдения модели (применяется для перемещения объектов по сцене) GL_PROJECTION Действия с матрицами затрагивают стек матриц проекции (применяется для определения объема отсечения) GL_TEXTURE Действия с матрицами затрагивают стек матриц текстуры (манипуляции с текстурными координатами) glLoadldentity Цель: Включаемый файл: Синтаксис: Описание: Что возвращает: См. также: Установить текущую матрицу равной единичной <gl.h> void glLoadldentity(void); Заменяет текущую матрицу преобразования единичной. По сути, при этом текущая система координат переходит в систему наблюдения Ничего gILoadMatrix, glMatrixMode, glMultMatrix, glPushMatrix gILoadMatrix Цель: Установить текущую матрицу равной данной Включаемый файл: <gl.h> Варианты: void glLoadMatrixd(const GLdouble *m) ; void glLoadMatrixf(const GLfloat *m); Описание: Заменяет текущую матрицу преобразования произвольной предоставленной матрицей. Отметим, что более эффективным может быть использование других функций манипулирования матрицами, например, glLoadldentity, glRotate, glTranslate и glScale Параметры: *т (тип GLdouble Массив представляет матрицу 4x4, которая будет или GLfloat) использоваться в качестве текущей матрицы преобразования. Массив записан по столбцам в виде 16 последовательных значений Что возвращает: Ничего См. также: glLoadldentity, glMatrixMode, glMultMatrix, glPushMatrix
Глава 4 Геометрические преобразования: конвейер 219 gILoadTransposeMatrix Цель: Загрузить транспонированную матрицу 4 х 4 в стек матриц Включаемый файл: <gl.h> Варианты: void LoadTransposeMatrixf(GLfloat *m); void LoadTransposeMatrixd(GLdouble *m); Описание: OpenGL хранит матрицы 4 x 4 в одномерном массиве с разверткой по столбцам. С помощью указанной функции в стек может загружаться массив с разверткой по строкам, т.е. транспонированная матрица. Принимает матрицу, транспонирует ее, а затем загружает необходимым образом отформатированный массив в верх текущего стека матриц. Некоторые библиотеки OpenGL могут не экспортировать эту функцию, даже если реализация ее и поддерживает. В таком случае с помощью механизма расширения OpenGL можно получить указатель на рассматриваемую функцию Параметры: *т (тип GLfloat или GLdouble) Что возвращает: Транспонированная матрица 4x4, которую требуется загрузить в стек Ничего См. также: glLoadMatrix, glMultTransposeMatrix glMatrixMode Цель: Задать текущую матрицу (gl_projection, gl_modelview или GL_TEXTURE) Включаемый файл: <gl.h> Синтаксис: void glMatrixMode(GLenum mode); Описание: Эта функция определяет, какой стек матриц (gl_modelview, GL_ PROJECTION или GL_TEXTURE) используется для работы с матрицами Параметры: mode (тип GLenum) Определяет, какой стек матриц используется для последующих операций с матрицами. Принимаются любые значения, указанные в табл. 4.2 Что возвращает: Ничего См. также: glLoadMatrix, glPushMatrix
220 Часть I. Классический OpenGL glMultMatrix Цель: Умножить текущую матрицу на данную Включаемый файл: <gl.h> Варианты: void glMultMatrixd(const GLdouble *m); void glMultMatrixf(const GLfloat *m) ; Описание: Умножает матрицу из выбранного в данный момент стека на заданную матрицу. После этого результат записывается как текущая матрица в верху стека матриц Параметры: *Л1 (тип GLdouble или GLfloat) Что возвращает: Массив представляет матрицу 4 х 4, на которую будет умножена текущая матрица. Массив записывается по столбцам как 16 последовательных значений Ничего См. также: glMatrixMode, glLoadldentity, glLoadMatrix, glPushMatrix glMultTransposeMatrix Цель: Умножить транспонированную матрицу 4 х 4 на матрицу из текущего стека Включаемый файл: <gl.h> Варианты: void MultTransposeMatrixf(GLfloat *m); void MultTransposeMatrixd(GLdouble *m); Описание: OpenGL хранит матрицы 4 x 4 в одномерном массиве с разверткой по столбцам. С помощью этой функции на матрицу текущего стека может умножаться массив с разверткой по строкам, т.е. транспонированная матрица. Принимает матрицу, транспонирует ее, а затем умножает необходимым образом отформатированный массив на верхнюю матрицу текущего стека матриц. Некоторые библиотеки OpenGL могут не экспортировать эту функцию, даже если реализации ее и поддерживает. В таком случае с помощью механизма расширения OpenGL можно получить указатель на рассматриваемую функцию Параметры: *т (тип GLfloat Транспонированная матрица 4 х 4, на которую будет множится или GLdouble) верхняя матрица текущего стека Что возвращает: Ничего См. также: glMultMatrix, glLoadTransposeMatrix
Глава 4. Гэометрические преобразования: конвейер 221 glPopMatrix Цель: Вытолкнуть текущую матрицу из стека Включаемый файл: <gl.h> Синтаксис: void glPopMatrix(void); Описание: Выталкивает последнюю (самую верхнюю) матрицу из текущего стека матриц. Чаще всего функция применяется для восстановления предыдущего условия, касающегося текущей матрицы преобразования, если оно было записано с помощью glPushMatrix Что возвращает: Ничего См. также: glPushMatrix glPushMatrix Цель: Поместить текущую матрицу в стек матриц Включаемый файл: <gl.h> Синтаксис: void glPushMatrix(void) ; Описание: Помещает текущую матрицу в текущий стек матриц. Чаще всего применяется для записи текущей матрицы преобразования, чтобы позже ее можно было восстановить с помощью вызова glPopMatrix Что возвращает: Ничего См. также: glPopMatrix g I Rotate Цель: Повернуть текущую матрицу согласно матрице поворота Включаемый файл: <gl.h> Варианты: void glRotated(GLdouble angle, GLdouble x, GLdouble y, GLdouble z); void glRotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); Описание: Умножает текущую матрицу на матрицу поворота, выполняющую поворот против часовой стрелки вокруг направленного вектора, проходящего от начала координат до точки (ж, у, z). Результат умножения становится текущей матрицей преобразования Параметры: angle (тип GLdouble или GLfloat) Угол поворота против часовой стрелки в градусах X, у, Z Радиус-вектор, используемый в качестве оси вращения (тип GLdouble или GLfloat) Что возвращает: Ничего См. также: glScale, glTranslate
222 Часть I. Классический OpenGL gIScale Цель: Умножить текущую матрицу на матрицу масштабирования Включаемый файл: <gl.h> Варианты: void glScaled(GLdouble x, GLdouble y, GLdouble z); void glScalef(GLfloat x, GLfloat y, GLfloat z); Описание: Умножает текущую матрицу на матрицу масштабирования. Результат умножения становится текущей матрицей преобразования Параметры: х, у, Z Масштабные коэффициенты по осям х, у и z (тип GLdouble или GLfloat) Что возвращает: Ничего См. также: glRotate, gITranslate gITranslate Цель: Умножить текущую матрицу на матрицу трансляции Включаемый файл: <gl.h> Варианты: void glTranslated(GLdouble x,GLdouble y, GLdouble z); void glTranslatef(GLfloat x,GLfloat y,GLfloat z); Описание: Умножает текущую матрицу на матрицу трансляции. Результат умножения становится текущей матрицей преобразования Параметры: х, у, Z Координаты х, у и z вектора трансляции (тип GLdouble или GLfloat) Что возвращает: Ничего См. также: glRotate, gIScale
Глава 4. Геометрические преобразования: конвейер 223 gluLookAt Цель: Определить преобразование наблюдения Включаемый файл: <glu.h> Синтаксис: void gluLookAt (GLdouble eyex, GLdouble eyey, GLdouble eyez, GLdouble centerx, GLdouble centery, GLdouble centerz, GLdouble upx, GLdouble upy, GLdouble upz); Описание: Определяет преобразование наблюдения на основе положения глаза, центра сцены, вектора, указывающего вверх с точки зрения наблюдателя Параметры: eyex, eyey, eyez Координаты х, у и z положения глаза (тип GLdouble) centerx, centery, Координаты х, у и z центра сцены centerz (тип GLdouble) upx, upy, upz Координаты х, у и z, задающие вектор направления вверх (тип GLdouble) Что возвращает: Ничего См. также: gIFrustum, gluPerspective gluOrtho2D Цель: Включаемый файл: Синтаксис: Описание: Параметры: left, right (тип GLdouble) bottom, top (тип GLdouble) Что возвращает: См. также: Определить двухмерную ортографическую проекцию <glu.h> void gluOrtho2D(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top); Определяет матрицу двухмерной ортографической проекции. Применение матрицы проекции эквивалентно вызову glOrtho с параметрами near и far, установленными, соответственно, равными 0 и 1 Задает левую и правую дальние плоскости отсечения Задает верхнюю и нижнюю плоскости отсечения Ничего glOrtho, gluPerspective
224 Часть I. Классический OpenGL gluPerspective Цель: Определить матрицу наблюдения перспективной проекции Включаемый файл: <glu.h> Синтаксис: void gluPerspective(GLdouble fovy, GLdouble Описание: aspect, GLdouble zNear, GLdouble zFar); Создает матрицу, описывающую наблюдаемый объем Параметры: fovy (усеченная пирамида) в глобальных координатах. Характеристическое отношение должно соответствовать характеристическому отношению поля просмотра (задается с помощью giviewport). Перспективное деление рассчитывается на основе на угла обзора и расстояния до ближней и дальней плоскостей отсечения Угол обзора по направлению у в градусах (тип GLdouble) aspect Характеристическое отношение. Используется для определения (тип GLdouble) угла обзора по направлению х. Характеристическое отношение zNear, zFar равно х/у Расстояние от наблюдателя до ближней и дальней плоскостей (тип GLdouble) отсечения. Данные значения всегда положительны Что возвращает: Ничего См. также: glFrustum, gluOrtho2D
ГЛАВА 5 Цвет, материалы и освещение: основы Ричард С. Райт-мл. ИЗ ЭТОЙ ГЛАВЫ ВЫ УЗНАЕТЕ . . . Действие Функция Задание цвета через RGB-компоненты glColor Установка модели затенения glShadeModel Формирование модели освещения glLightModel Задание параметров освещения glLight Установка отражательных свойств материала glColorMaterial/glMaterial Установка нормалей к поверхности glNormal В данной главе трехмерная графика становится привлекательной (если вы не оце- нили по достоинству приведенные ранее каркасные модели), и с этого момента по- лучаемые изображения будут все лучше. Вы будете изучать OpenGL от основ к вер- шинам, сначала учась писать программы, а затем собирать объекты из примитивов и манипулировать ими в трехмерном пространстве. До этого момента мы создавали фундамент, и пока что вы не могли сказать, на что будет похож дом. Переиначив известную фразу, можно сказать “А где сутьВ 9”. Честно говоря, суть начинается здесь. Практически во всей оставшейся части книги наука отходит на второй план, и начинает править магия. Если верить Артуру Кларку, “Любая достаточно развитая технология неотличима от магии”. Разумеется, в раскрашивании и освещении нет никакой магии, хотя со временем начинает казать- ся, что магия здесь все-таки присутствует. Если вы хотите зарыться в “достаточно раз- витую технологию” (математику), обратитесь к приложению А, “Что еще почитать”. Данная глава могла иметь и другое название: “Добавление реализма к созданным сценам”. Как вы, наверное, заметили, в реальной жизни с цветом связано гораздо больше тонкостей, чем мы можем задать в OpenGL. Объекты могут не только иметь цвет, но и быть блестящими или тусклыми, они могут даже светиться собственным светом. Кажущийся цвет объекта меняется в зависимости от освещения, здесь важен даже цвет света, падающего на объект. На освещенный объект могут падать тени при освещении или наблюдении под определенным углом.
226 Часть I Классический OpenGL •Длина волны Рис. 5.1. Измерение длины волны света Что такое цвет? Поговорим немного о том, чем же является цвет. Как он появляется в природе, и как мы видим цвета? Понимание теории цвета и того, как человеческий глаз ви- дит цветную сцену, позволяет глубже заглянуть в процесс программного задания цвета. (Если вы прекрасно разбираетесь в теории цвета, то можете спокойно пропу- стить этот раздел.) Свет — это волна Цвет — это просто длина волны света, видимого человеческому глазу. Если вы учили физику в школе, то, возможно, помните, что свет — это одновременно волна и частица. Свет представляется как волна, распространяющаяся по пространству, как рябь по воде, и он же представляется как частица, подобная капле дождя, падающей на землю. Если сказанное сбивает вас с толку, вы можете понять, почему большинство людей не изучает квантовую механику! Свет, который вы наблюдаете практически от любого источника, в действительно- сти является смесью множества различных разновидностей света. Эти разновидности света определяются своими длинами волн. Длина волны света измеряется как рас- стояние между между пиками световой волны (рис. 5.1). Длины волн видимого свет меняются от 390 нанометров6 (фиолетовый свет) до 720 нанометров (красный свет); данный диапазон обычно называется видимой частью спектра. Несомненно, вы слышали термины ультрафиолетовый и инфракрасный свет; они относятся к свету, невидимому невооруженным глазом, и располагающемуся по обе стороны видимой части спектра. Вы можете считать, что спектр содержит все цвета радуги (рис. 5.2). Свет как частица Вы можете сказать: “Если цвет — это длина волны света, а в этой ‘радуге’ представлен только видимый свет, то откуда же берется коричневый цвет, который я вижу на дереве, или черный, который я вижу в кофе, или даже белый, как эта страница?” Чтобы ответить на этот вопрос, скажем для начала, что черный (как и белый) — это вообще не цвет. В действительности черный — это отсутствие цвета, а белый — комбинация всех цветов Т.е. белый объект одинаково отражает все “цветные” длины волн, а черный объект одинаково поглощает все длины волн. Одна миллиардная часть метра
Глава 5. Цвет, материалы и освещение: основы 227 390 нм 720 нм Рис. 5.2. Спектр видимого света Рис. 5.3. Объект отражает одни фотоны и поглощает другие Что касается коричневого цвета и многих других цветов, которые вы можете ви- деть в разных местах, они действительно являются цветами. На физическом уровне это составные цвета. Они образованы различными дозами “чистых” цветов, присут- ствующих в спектре. Чтобы понять, как работает эта концепция, представьте свет как частицу. Любой объект, освещаемый источником света, бомбардируется милли- ардами фотонов, или крошечных частиц света. Из уроков физики можно вспомнить, что каждый фотон также является волной, которая характеризуется длиной и цветом, отвечающим ей в спектре. Все физические объекты состоят из атомов. Отражение фотонов от объектов зави- сит от типа атомов, количества фотонов каждого типа и упорядочения атомов (и их электронов) в объекте. Одни фотоны отражаются, а другие поглощаются (поглощен- ные фотоны обычно переходят в тепло), и любой материал или смесь материалов (например, деревянный брусок) какие-то длины волн отражает сильнее, чем другие. Данный принцип иллюстрируется на рис. 5.3.
228 Часть I. Классический OpenGL 6 красных, 4 зеленых и 1 синий фотон Рис. 5.4. Как глаз воспринимает коричневый цвет Детектор фотонов Свет, отраженный от деревянного бруска и попадающий в глаз, интерпретируется как цвет. Миллиарды фотонов поступают в ваш глаз и фокусируются на его задней стен- ке, где сетчатка действует подобно фотографической пластине. Миллионы колбочек сетчатки возбуждаются при падении на них фотонов, а это приводит к тому, что энер- гия нервного импульса доходит до мозга, который и интерпретирует информацию как свет и цвет. Чем больше фотонов ударяется о колбочки, чем более они возбуждаются. Этот уровень возбуждения интерпретируется мозгом как яркость света (чем ярче свет, тем больше фотонов ударяется о колбочки). Глаз имеет три типа колбочек. Все они реагируют на фотоны, но каждый тип имеет максимум реакции на своей длине волны. Один тип колбочек больше возбуждается фотонами, имеющими длины волн красной части спектра; другой реагирует на “зе- леные” фотоны, а третий — на “синие”. Таким образом, свет, образованный преиму- щественно красными длинами волн, больше возбуждает чувствительные к красному колбочки, и мозг получат сигнал, что свет, который вы видите, имеет преимуществен- но красноватый оттенок. Разумеется, комбинация различных длин волн различной интенсивности даст смесь цветов, но необходимые математические расчеты выпол- няете вы сами. Таким образом, если все длины волн представлены равномерно, мозг будет воспринимать белый цвет, а при отсутствии света любой длины волны мозг будет воспринимать черный цвет. Таким образом, любой “цвет”, который воспринимает глаз, в действительности состоит из света всего видимого спектра. “Аппаратное обеспечение” глаза реагирует на то, что оно видит, оперируя относительными концентрациями и интенсивностями красного, зеленого и синего света. На рис. 5.4 показано, как из смеси 60% красных фотонов, 40% зеленых фотонов и 10% синих фотонов составляется коричневый цвет. X Компьютер как генератор фотонов Теперь, когда вы понимаете, как человеческий глаз различает цвета, к генерации цветов с помощью компьютера имеет смысл подойти точно так же: задать отдельно интенсивности красного, зеленого и синего компонентов света. Так получилось, что компьютерные мониторы создаются с возможностью отображения трех цветов (как вы думаете, каких?) и возможностью регулировки их интенсивности. На задней части монитора находится электронная пушка, “стреляющая” электронами на экран, где вы видите изображение. Экран содержит люминофоры, излучающие красный, зеленый
Глава 5. Цвет, материалы и освещение: основы 229 Рис. 5.5. Генерация цветов компьютерным монитором Электронная пушка и синий свет, когда о них ударяются электроны. Интенсивность излучаемого света зависит от интенсивности пучка электронов. (Так как же тогда работают цветные ЖК-дисплеи? Ответить на этот вопрос вам предлагается самостоятельно!) Указан- ные три цветных люминофора группируются очень близко, образуя на экране одну физическую точку (рис. 5.5). Напомним, что в главе 2, “Используя OpenGL”, мы объясняли, как OpenGL точ- но определяет цвет через интенсивности красного, зеленого и синего с помощью команды glColor. Цветовоспроизводящая аппаратура ПК Было время, когда выражение “наиболее мощное графическое аппаратное обеспече- ние для ПК” означало “графическая карта Hercules”. Данная карта могла создавать растровые изображения с разрешением 720 х 348. Недостатком было то, что каж- дый пиксель имел только два состояния: “включено” и “выключено”. Впрочем, на то время даже такая растровая графика на ПК была великим событием, и люди могли создавать удивительную монохромную графику — и даже трехмерную! В действительности до карт Hercules были карты CGA (Color Graphics Adapter — адаптер цветной графики). Появившиеся вместе с первыми IBM PC, эти карты могли поддерживать разрешение 320 X 200 пикселей и одновременно отображать на экране 4 из 16 цветов. При использовании двух цветов было возможно еще большее разреше- ние (640 х 200), но оно было не таким эффективным (или выгодным) как разрешение карты Hercules. (Цветные мониторы тогда стоили немало.) По сегодняшним стан- дартам карты CGA были ничтожными; но в то время они превосходили графические возможности домашних компьютеров Commodore 64 или Atari. Не имея достаточного разрешения для бизнес-графики или ограниченного моделирования, CGA использо- вались в основном в простых играх для ПК или бизнес-приложениях, в которых было
230 Часть I Классический OpenGL выгодно использовать цветной текст. В целом на тот момент не было других прило- жений, которые были бы коммерчески оправданными и требовали более дорогого аппаратного обеспечения. Следующим большим прорывом в графике персональных компьютеров стали представленные IBM карты EGA (Enhanced Graphics Adapter — усовершенствованный графический адаптер). Эти карты могли поддерживать более 25 строк цветного текста в новых текстовых режимах, а при отображении графики карты поддерживали 16- цветные растровые изображения размером 640 х 350 пикселей! Другие технические улучшения устранили некоторые проблемы мерцания CGA-предков и обеспечили бо- лее красивую и гладкую анимацию. Теперь реализация аркадных игр, бизнес-графики и даже простой трехмерной графики на ПК стала не только возможной, но и целесо- образной. Это было огромным шагом от CGA, но в целом компьютерная графика все еще находилась в младенческом возрасте. Последним серийным стандартом для ПК, установленным IBM была карта VGA (сокр. от Vector Graphics Array — матрица векторной графики, а не от Video Graphics Adapter — адаптер видеографики, как часто расшифровывают эту аббревиатуру). Дан- ная карта была значительно быстрее, чем EGA; она могла поддерживать 16 цветов при сравнительно большом разрешении (640 х 480) и 256 цветов при разрешении 320 х 200. Эти 256 цветов выбирались из палитры, вмещавшей более 16 миллионов возмож- ных цветов. Именно тогда были открыты двери для графики на ПК. Стала возможной фактически фотореалистичная графика. На рынке ПК начали появляться программы построения хода лучей, трехмерные игры и пакеты редактирования фотографий. Помимо этого для своих “рабочих станций” IBM разработала мощную графиче- скую карту (8514). Эта карта позволяла работать с разрешением 1 024 х 768 при 256 цветах. В IBM предполагали, что такая карта будет использоваться только в ав- томатизированном проектировании и научных приложениях! Однако о потребителях можно с уверенностью сказать только одно: они всегда хотят большего. Недальновид- ность IBM стоила этой компании места законодателя рынка графики для ПК. Вскоре начали подниматься другие производители, поставлявшие карты “Super-VGA”, ко- торые могли работать со все большим разрешением, используя все больше цветов. Вначале мы увидели разрешение 800 х 600, затем — 1 024 х 768 и даже больше, при 256 цветах, после этого — 32 000 и 65 000. В настоящее время карты с 24-битовым цветом могут отображать 16 миллионов цветов при разрешениях, значительно превы- шающих 1 024 х 768. Даже продаваемые сейчас ПК Windows начального уровня могут поддерживать по крайней мере 16 миллионов цветов при разрешении 1 024 х 768 или даже больше. Все эти мощности делают реальной “крутую” фотореалистичную трехмерную гра- фику. Когда Microsoft перенесла OpenGL на платформу Windows, стало возможным создание на ПК мощных графических ускорителей. Объединяя современные быст- рые процессоры с видеокартами с ускорением трехмерной графики, вы получите производительность, всего несколько лет назад достижимую лишь на графических рабочих станциях за $100 000 (с учетом рождественской скидки!). Типичные совре- менные домашние компьютеры могут использоваться для сложной имитации, игр и многого другого. Термин “виртуальная реальность” уже успел устареть почти так же, как ракетоносители “Buck Rogers”, и мы уже принимаем достоинства трехмерной графики как должное.
Глава 5. Цвет, материалы и освещение: основы 231 Режимы отображения ПК Microsoft Windows и Apple Macintosh сделали революцию в области графики на ПК в двух аспектах. Во-первых, они создали серийные графические операционные сре- ды, которые были приняты бизнес-сообществом, а впоследствии и потребительским рынком. Во-вторых, они существенно облегчили программирование графики на ПК. Графическое аппаратное обеспечение “виртуализировалось” драйверами устройств отображения. Теперь, вместо того чтобы писать инструкции непосредственно для видеоаппаратуры, программисты могут писать программы для одного программно- го интерфейса приложения (например, OpenGL!), а детали общения с аппаратным обеспечением возьмет на себя операционная система. Разрешение экрана Разрешение экранов современных компьютеров может меняться от 640 х 480 пиксе- лей до 1 600 х 1 200 или даже больше. Считается, что самого низкого разрешения, 640 х 480, достаточно для отображения графики, и люди с проблемами зрения часто работают при низком разрешении, но на большом мониторе. Задавая объем отсечения и поле просмотра, вы всегда должны учитывать размер окна (см. главу 2). Масштаби- руя размер рисунка до размера окна, легко учесть комбинации различных разрешений и размеров окна, которые могут встретиться. Качественно написанные графические приложения всегда отображают примерно одинаковое изображение, независимо от разрешения экрана. При увеличении разрешения изображение на экране должно пере- страиваться так, чтобы пользователь мог более четко видеть большее число деталей. Насыщенность цвета Если увеличение разрешения экрана или числа доступных для рисования пикселей увеличивает детализацию и резкость изображения, качество получаемого изображе- ния должно повышаться и при увеличении числа доступных цветов. Изображение, показанное на компьютере, способном отображать миллионы цветов, должно вы- глядеть значительно лучше, чем тот же рисунок, показанный всего с 16 цветами. В программировании вам, очевидно, придется учитывать только три степени насы- щенности цвета: 4 бит, 8 бит и 24 бит. 4-битовый режим цвета На слабых машинах программа должна запускаться при 16 цветах — это называет- ся 4-битовый режим, поскольку информация о цвете каждого пикселя записывается с использованием 4 бит. Эти 4 бит представляют значение от 0 до 15 — индекс в набор из 16 предопределенных цветов. (Когда вы используете ограниченный набор цветов, доступ к которым осуществляется с помощью индекса, этот набор называется палит- рой.) Если в вашем распоряжении имеется всего 16 цветов, простор для улучшения ясности и четкости изображения слишком мал. Обычно предполагается, что серьез- ные графические приложения могут спокойно разрабатываться без учета 16-цветного режима. Пожалуй, то, что большинство новых аппаратных средств, доступных для отображения информации, уже не поддерживает этот режим, — к лучшему
232 Часть I. Классический OpenGL 8-битовый режим цвета 8-битовый режим цвета поддерживает на экране до 256 цветов. Это существенное улучшение по сравнению с 4-битовым цветом, хотя такой насыщенности цвета все же недостаточно для серьезной работы. Большинство аппаратных ускорителей OpenGL не ускоряет 8-битовый цвет, но при программной визуализации, при определенных условиях, можно получить в Windows удовлетворительные результаты. В этом случае самые важные моменты касаются построения правильной палитры цветов. Более подробно эта тема освещается в главе 13, “Wiggle: OpenGL в системе Windows”. 24-битовый режим цвета Наилучшее качество изображений, доступное сейчас на ПК, дает 24-битовый режим цвета. В этом режиме каждому пикселю сопоставляется 24 бит, из которых по 8 бит выделено для хранения информации об одном из составляющих цветов (красный, зеленый и синий). Любой пиксель экрана можно раскрасить одним из 16 миллионов возможных цветов. Наиболее очевидным недостатком этого режима является объем памяти, требуемой для записи информации об экранах с высоким разрешением (более 2 Мбайт при экране 1 024 х 768). Кроме того, перемещение больших участков памяти также происходит гораздо медленнее, например, когда вы применяете анимацию или просто рисуете на экране. К счастью, современные графические адаптеры с ускори- телями оптимизированы под операции такого типа и в них вмонтирована большая па- мять, предназначенная для удовлетворения потребностей в дополнительной памяти. 16- и 32-битовые режимы цвета Чтобы сэкономить память или улучшить производительность, многие графические карты также поддерживают другие режимы цвета В контексте улучшения произ- водительности был разработан 32-битовый режим цвета, иногда называемый реали- стичным цветовоспроизведением (true color). В действительности при 32-битовом режиме отображения возможен показ такого же числа цветов, что и при 24-битовом режиме, но этот режим более выгоден с точки зрения производительности, поскольку каждому пикселю сопоставляется 32-битовый адрес. К сожалению, при этом не ис- пользуется 8 бит (1 байт) на каждый пиксель. На современных 32-битовых ПК Intel кратность памяти 32 обеспечивает более быстрый доступ к памяти. Современные ускорители OpenGL также поддерживают 32-битовый режим, где 24 бит зарезер- вированы для записи RGB-цветов, а 8 бит — для хранения значения альфа. (Более подробно об альфа-канале рассказывается в следующей главе.) Другой популярный режим отображения (16-битовый цвет) иногда применяется для более эффективного использования памяти. Это позволяет выбирать для раскра- шивания пикселя один из 65 536 возможных цветов. На практике указанный режим так же эффективен, как и 24-битовая насыщенность цвета при воспроизведении фото- графических изображений, поскольку на большинстве фотографий трудно заметить разницу между 16- и 24-битовыми режимами цвета. Экономия памяти и увеличение скорости отображения картинок на экране сделало этот режим популярным в первом поколении “игровых” трехмерных ускорителей. Тем не менее повышенная точность воспроизведения цвета 24-битового режима действительно улучшает качество изоб- ражения, особенно в операциях затенения и смешения.
Глава 5 Цвет, материалы и освещение-основы 233 Рис. 5.6. Начало координат пространства цветов RGB Красный Использование цвета в OpenGL Теперь вы знаете, как OpenGL задает точный цвет через интенсивности красного, зе- леного и синего компонентов. Кроме того, вам известно, что современное аппаратное обеспечение персональных компьютеров может отображать все, почти все возмож- ные комбинации или лишь несколько из них. Таким образом, возникает вопрос: как задать цвет через красный, зеленый и синий компоненты? Куб цвета Поскольку цвет задается тремя положительными кодами цвета, доступные цвета мож- но смоделировать объемом, который мы называем пространством цветов RGB. Дан- ное пространство показано на рис. 5.6, где обозначено начало координат, и на осях отмечены красный, синий и зеленый цвета. Координаты в этом пространстве задаются так же, как координаты х, у и z. В начале координат (0,0,0) относительные интенсив- ности всех компонентов равны нулю, поэтому получается черный цвет. Для хранения информации о цветах на ПК допускается использование максимум 24 бит (по 8 бит на каждый компонент), поэтому будем считать, что значение 255 (максимальное число, которое можно записать с помощью 8 бит) представляет максимально насыщенный компонент Таким образом, получаем куб со сторонами 255 единиц. Вершина куба, противоположная началу координат, которое соответствует черному цвету и пред- ставляется концентрациями (0,0,0), соответствует белому цвету и характеризуется относительными концентрациями (255, 255, 255). Точки максимальной насыщенно- сти (255), соответствующие вершинам куба, расположенным на осях, представляют чистые красный, зеленый и синий цвета. Такой “куб цвета” (рис. 5.7) содержит все возможные цвета либо на поверхности, либо внутри куба. Например, возможные оттенки серого (между черным и белым) располагаются на диагонали, соединяющей вершины (0,0,0) и (255,255, 255). На рис. 5.8 показан куб цвета, созданный программой CCUBE (находится на компакт-диске в папке, соответствующей данной главе). На поверхности этого куба представлены различные цвета, плавно связывающие черный цвет одной вершины с белым цветом противоположной. Красный, синий и зеленый цвета располагаются
234 Часть I. Классический OpenGL Рис. 5.7. Пространство цветов RGB Рис. 5.8. Результат выполнения программы CCUBE — куб цветов в соответствующих вершинах, расположенных на расстоянии 255 единиц от начала координат. Кроме того, обозначены желтый, голубой и пурпурный цвета, являющиеся комбинациями двух из трех основных цветов. Нажимая клавиши со стрелками, вы можете вращать куб, изучая его со всех сторон. Задание цвета рисования Кратко напомним основную информацию о функции glColor. Ее прототип можно записать следующим образом. void glColor<x><t>(red, green, blue, alpha); В имени функции <x> представляет число аргументов; это может быть 3 — крас- ный, зеленый и синий или 4 — те же плюс компонент альфа. Компонент альфа задает прозрачность цвета и более подробно рассмотрен в следующей главе. Сейчас же мы будем использовать вариант функции с тремя аргументами. Параметр <t> в имени функции задает тип данных аргумента и может иметь одно из следующих значений: b, d, f, i, s, ub, ui или us от типов данных byte, double, float, integer, short, unsigned byte, unsigned integer и unsigned short соответственно.
Глава 5. Цвет, материалы и освещение: основы 235 В другой версии функции к концу названия добавляется буква v; данная версия принимает в качестве аргумента массив, содержащий аргументы (v — сокращение от “vector”). Все остальные подробности, касающиеся функции glColor, можно найти в справочном разделе в конце этой главы. В большинстве программ OpenGL, с которыми вам придется столкнуться, ис- пользуется функция glColor3f, и интенсивность всех компонентов задается как величина, принадлежащая диапазону от 0. О (нулевая интенсивность) до 1.0 (макси- мальная интенсивность). Если у вас есть опыт программирования в Windows, легче использовать вариант glColor3ub этой функции. В качестве аргументов эта версия принимает три байта без знака, представляющие числа от 0 до 255 и задающие ин- тенсивности красного, зеленого и синего компонентов. Использование этой версии функции подобно применению следующего макроса Windows под названием RGB. glColor3ub(0,255,128) = RGB(0,255,128) Описанный подход может облегчить вам согласование цветов OpenGL с существу- ющими цветами RGB, которые вы привыкли применять в программах, не использу- ющих OpenGL. Тем не менее следует отметить, что внутренне OpenGL представляет коды цвета как величины с плавающей запятой, и из-за необходимости преобра- зования констант в форму внутреннего представления возможна небольшая потеря производительности (если это произойдет во время выполнения программы). Также может случиться, что в будущем возникнут буферы цвета с большим разрешением (фактически, буферы цвета, в которые заносятся величины с плавающей запятой, уже появились), поэтому коды цвета, заданные через величины с плавающей запятой могут оказаться более удобными для аппаратного обеспечения. Затенение Рабочее определение glColor звучало так: эта функция устанавливает текущий цвет рисования, и все объекты, нарисованные впоследствии, имеют последний заданный цвет. После обсуждения примитивов рисования OpenGL в предыдущей главе это определение можно расширить: функция glColor устанавливает текущий цвет, ко- торый используется для рисования всех вершин, заданных после этой команды. До этого момента мы во всех примерах рисовали каркасные объекты или сплошные тела, каждая грань которых изображалась своим цветом. А каким будет цвет объекта, если вершины примитива (точки, отрезка или многоугольника) задать разноцветными? Рассмотрим вначале точки. Точка имеет только одну вершину, поэтому какой бы цвет вы не задали для этой вершины, точка будет изображена одним этим цветом. Все просто. Отрезок имеет две вершины, которые могут иметь разные цвета. Цвет отрезка зависит от модели затенения. Затенение определяется просто как гладкий переход одного цвета в другой. С помощью прямого отреза мы можем соединить любые две точки пространства цветов RGB (см. рис. 5.7). При гладком затенении цвета вдоль линии меняются так же, как при проходе куба цвета от точки, соответствующей одному цвету, к точке, соответствующей другому. На рис. 5.9 показан куб цветов с обозначенными черной и белой вершинами. Под ним изображена линия с двумя вершинами — черной и белой Цвета, выбираемые для то- чек отрезка, соответствуют цветам, расположенным на диагонали куба, соединяющей
236 Часть I Классический OpenGL Оттенки серого, переходящие в белый Черный Вершина 2 (255,255,255) Белый Рис. 5.9. Закрашивание отрезка с черной и белой вершинами черную и белую вершины. В результате получается отрезок, плавно переходящий от черного цвета к белому через различные оттенки серого. Затенение можно описать математически, найдя уравнение линии, соединяющей две точки в трехмерном пространстве цветов RGB. После этого вы можете просто “пройти” от одного конца линии к другому, извлекая по пути координаты, опреде- ляющие цвет всех пикселей на экране. Во многих книгах по компьютерной графике объясняется алгоритм, позволяющий достичь такого эффекта, масштабировать цвет- ной отрезок в физическую линию на экране и т.д. К счастью, OpenGL делает всю эту работу за вас! Для многоугольников затенение становится немного сложнее. Треугольник, на- пример, также можно представить как плоскость, принадлежащую кубу цвета. На рис. 5.10 показан треугольник, вершины которого — максимально насыщенные крас- ный, зеленый и синий цвета. Код отображения этого треугольника на экране приво- дится в листинге 5.1, а всю программу TRIANGLE можно найти на компакт-диске, прилагаемом к этой книге. Листинг 5.1. Плавное затенение треугольника, имеющего красную, синюю и зеленую вершины // Активизирует плавное затенение glShadeModel(GL_SMOOTH); // Рисует треугольник glBegin(GL_TRIANGLES); // Красная вершина glColor3ub((GLubyte)255,(GLubyte)O,(GLubyte)O);
Глава 5. Цвет, материалы и освещение: основы 237 Красный Рис. 5.10. Треугольник в пространстве цветов RGB Синий glVertex3f(O.Of,200.Of, 0.Of); // Зеленая вершина в правом нижнем углу glColor3ub((GLubyte)0,(GLubyte)255,(GLubyte)0); glVertex3f(200.Of,-70.Of, O.Of); // Синяя вершина в левом нижнем углу glColor3ub((GLubyte)0,(GLubyte)0,(GLubyte)255); glVertex3f(-200.Of, -70.Of, O.Of); glEnd(); Выбор модели затенения В первой строке листинга 5.1 в действительности задается модель затенения OpenGL, которая будет использоваться для плавного преобразования одного цвета в другой. Это модель затенения по умолчанию, но чтобы гарантировать, что ваша программа работает так, как предполагалось, указанную функцию стоит всегда вызывать явно. Кроме указанной модели затенения можно задать модель плоского затенения, ис- пользовав GL_FLAT в качестве аргумента функции glShadeModel. Плоское затенение означает, что внутри примитива никаких расчетов промежуточных цветов не выпол- няется. В общем случае при плоском затенении цветом внутренней части примитива является цвет последней заданной вершины. Единственным исключением являет- ся примитив GL_POLYGON, где цвет внутренней части совпадает с цветом первой вершины. Далее в коде, приведенном в листинге 5.1, задается красная верхняя вершина треугольника, зеленая правая нижняя вершина и синяя левая нижняя. Поскольку ука- зано плавное затенение, внутренняя часть треугольника затеняется, и формируются плавные переходы цветов один в другой. Результат выполнения программы TRIANGLE показан на рис. 5.11. Отметим, что данный результат представляет плоскость, показанную на рис. 5.10. Разные цвета вершин могут быть и у многоугольников, более сложных, чем тре- угольники. В таких случаях логика затенения может быть более сложной. К счастью, пока вы работаете с OpenGL, можете об этом не беспокоиться. Не имеет значения,
238 Часть I. Классический OpenGL Рис. 5.11. Результат выполнения программы TRIANGLE Рис. 5.12. Простой самолет, построенный путем присвоения треугольникам различных цветов насколько сложными являются многоугольники, — OpenGL успешно выполнит зате- нение внутренних точек между любыми вершинами. Цвет в реальном мире Реальные объекты не выглядят как раскрашенные сплошными или плавно перехо- дящими друг в друга красками, основанными на четко заданных RGB-кодах. Рас- смотрим, например, рис. 5.12, где показан результат выполнения программы JET, представленной на компакт-диске. Это простой реактивный самолет, нарисованный вручную с помощью треугольников, причем при рисовании использовались только методы, рассмотренные выше. Напомним, что все программы, приведенные в этой главе, позволяют вращать объект с помощью клавиш с изображениями стрелок. Цвета треугольников выбирались так, чтобы подчеркнуть трехмерную структуру самолета. Тем не менее набор треугольников очень мало походит на что-либо, наблю- даемое в реальной жизни. Предположим, что вы построили модель этого самолета и раскрасили все плоские поверхности указанными цветами. Модель должна выглядеть
Глава 5. Цвет, материалы и освещение: основы 239 Рис. 5.13. Объект, освещенный исключительно рассеянным светом глянцевой или матовой в зависимости от типа использованной краски, а цвет всех плоских поверхностей будет меняться в зависимости от угла наблюдения и положения источников света. К счастью, OpenGL выполняет аппроксимацию предметов реального мира с уче- том условий освещения. Если объект сам не излучает света, он освещается светом трех различных категорий: рассеянным (светом окружающей среды), диффузным и отраженным. Рассеянный свет Рассеянный свет не поступает из какого-либо определенного направления (поэто- му его еще называют светом окружающей среды). Он имеет источник, но лучи его света отражаются от комнаты или сцены и становятся ненаправленными. Объекты, освещенные рассеянным светом, равномерно окрашивают со всех сторон и во всех направлениях. О всех примерах, приводившихся до этого момента в книге, можно ска- зать, что представленные объекты освещаются ярким рассеянным светом, поскольку объекты были всегда видимы и равномерно раскрашены (или затенены) вне зависи- мости от угла поворота или наблюдения. Объект, освещенный рассеянным светом, показан на рис. 5.13. Диффузный свет Диффузный свет поступает из определенного направления, но он равномерно отра- жается от поверхности. Даже при том, что свет отражается равномерно, поверх- ность объекта выглядит ярче, если свет расположен прямо на поверхности, чем если он падает на поверхность под углом. Хорошим примером источника диф- фузного света является люминесцентное освещение или сияние солнечного света в полдень через боковое окошко. На рис. 5.14 показан объект, освещенный источни- ком диффузного света. Отраженный свет Подобно диффузному свету, приходящему из определенного направления, но равно- мерно рассеиваемому во всех направлениях, отраженный свет является направлен- ными, но он отражается в ограниченном направлении. Сильный отраженный свет обычно дает яркое пятно на поверхности, именуемое “зайчиком ” (или бликом). При-
240 Часть I. Классический OpenGL Источник диффузного света Рис. 5.14. Объект, освещенный исключительно источником диффузного света Зеркальный источник света Рис. 5.15. Объект, освещенный исключительно источником отражаемого света мерами источников отражаемого света являются прожектор и Солнце. Пример объ- екта, освещенного только источником отражаемого света, приводится на рис. 5.15. Собираем все вместе Ни один реальный источник света не относится только к одной из указанных ка- тегорий — он описывается как смесь нескольких источников разной интенсивности. Например, красный луч лазера в лаборатории образован почти чисто красным отра- жательным компонентом. Однако частицы дыма или пыли рассеивают луч, поэтому можно видеть, как он проходит через комнату. Такое рассеяние представляет диф- фузный компонент света. Если луч достаточно ярок, и нет других источников света, объекты в комнате приобретают красноватый оттенок, и здесь мы имеем дело с незна- чительным рассеянным светом. Таким образом, говорят, что источник света на сцене имеет три компонента: рас- сеянный, диффузный и отражаемый свет. Точно так же компоненты цвета, состав- ляющие освещение, определяются RGBA-кодом, описывающим относительные ин- тенсивности красного, зеленого и синего света, составляющего данный компонент. (При описании цвета света значение альфа игнорируется.) Например, красный свет описанного лазера можно представить через компоненты, указанные в табл. 5.1. Обратите внимание на то, что красный луч лазера не содержит зеленого или си- него света. Также отметим, что отражаемый, диффузный и рассеянный свет могут представляться значениями из диапазона 0-1. Изучая приведенную таблицу, можно
Глава 5. Цвет, материалы и освещение, основы 241 ТАБЛИЦА 5.1. Распределение цвета и света в источнике-лазере Красный Зеленый Синий Альфа Отражаемый свет 0,99 0,0 0,0 1,0 Диффузный свет 0,10 0,0 0,0 1,0 Рассеянный свет 0,05 0,0 0,0 1,0 сказать, что красный луч лазера на некоторой сцене имеет очень большой отража- емый компонент, небольшой диффузный компонент и очень маленький рассеянный компонент. В месте, на которое направлен луч, вы скорее всего увидите красное пят- но. Кроме того, из-за условий в комнате (дым, пыль и т.д.) диффузный компонент позволяет видеть луч, проходящий через комнату. Наконец, рассеянный свет немного рассеивает свет по всей комнате (также благодаря частицам дыма или пыли). Рас- сеянный и диффузный компоненты света часто объединяются, поскольку они схожи по своей природе. Материалы в реальном мире Свет — это лишь один компонент общей картины. В реальном мире объекты имеют собственный цвет. Ранее в этой главе мы описывали цвет объекта через отражаемые этим объектом длины волн. Синий мяч отражает преимущественно синие фотоны и поглощает большинство других. Это предполагает, что свет, которым освещается мяч, имеет синие фотоны, которые, отражаясь, попадут в глаз наблюдателя. В общем случае большинство сцен в реальном мире освещается белым светом, содержащим равномерную смесь всех цветов. Следовательно, при освещении белым светом боль- шинство объектов предстают в “естественных” цветах. Однако это не всегда так; если поместить синий мяч в темную комнату, которая освещается только желтым светом, наблюдателю мяч будет казаться черным, поскольку весь желтый свет поглощается, а синего света, который можно было бы отразить, нет. Свойства материалов Используя освещение, мы не говорим, что многоугольники имеют цвет, скорее мы указываем материалы, которые имеют определенные отражательные свойства. Вме- сто того чтобы сказать, что многоугольник красный, мы говорим, что многоугольник сделан из материала, отражающего преимущественно красный свет. При этом мы по- прежнему утверждаем, что поверхность красная, но теперь должны дополнительно задать отражательные свойства материала для источников рассеянного, диффузного и отраженного света. Материал может быть блестящим и очень хорошо отражать отраженный свет, поглощая большую часть рассеянного или диффузного света. И на- оборот, тусклый освещенный объект может поглощать весь отраженный свет и ни при каких обстоятельствах не выглядеть блестящим. Кроме того, нужно задавать еще и излучающие свойства таких объектов, испускающих собственный свет, как задние габаритные фонари или часы, светящиеся в темноте.
242 Часть I. Классический OpenGL Источник рассеянного света RGB Рассеянные "цвет" материала (0,5; 1,0,5) Рис. 5.16. Расчет компонента “рассеянный свет” цвета объекта Добавление света к материалам Установка освещения и свойств материалов для достижения желаемых эффектов тре- бует некоторой практики. Кубов цвета или эвристических правил, дающих быстрые и легкие ответы, не существует. Здесь анализ уступает место искусству, а наука — магии. При рисовании объекта OpenGL решает, какой цвет использовать для каждо- го пикселя объекта. Объект характеризуется отражательными “цветами”, а источник света — своими “цветами”. Как OpenGL определяет, какие цвета использовать? По- нять соответствующие принципы несложно, но это потребует некоторых усилий на уровне начальной школы. (Помните, учитель говорил, что когда-нибудь эти знания вам пригодятся?) Каждой вершине используемых примитивов присваивается RGB-код, основанный на суммарном эффекте рассеянного, диффузного и отражаемого освещения, умножен- ного на способность материала отражать рассеянный, диффузный и отражательный свет. В результате, поскольку вы используете плавное сопряжение цветов вершин, достигается иллюзия реального освещения. Расчет эффектов рассеянного света Чтобы рассчитать эффекты рассеянного света, вначале потребуется отказаться от понятия цвета, а помнить только об интенсивностях красного, зеленого и синего компонентов. Источнику рассеянного света, дающему свет, состоящий из красно- го, зеленого и синего компонентов половинной интенсивности, соответствует RGB- код (0.5,0.5,0.5). Если этот рассеянный свет освещает объект, отражательная спо- собность рассеянного света которого задается RGB-кодом (0.5,1.0,0.5), суммарный вклад рассеянного света в “цвет” запишется следующим образом (0.5 * 0.5,0.5 * 1.0,0.5 * 0.5) = (0.25,0.5,0.25) Таким образом мы умножаем каждую составляющую света источника на соответ- ствующий компонент свойства материала (рис. 5.16). Следовательно, компоненты цвета материала определяют, какой процент падаю- щего света будет отражен. В нашем примере в рассеянном свете присутствует крас- ный компонент, интенсивность которого равна половине максимальной, а свойство
Глава 5 Цвет, материалы и освещение: основы 243 материала, характеризующее способность отражения отражательного света, равно 0,5, т.е. будет отражена половина половины максимальной интенсивности, или од- на четвертая — 0,25. Диффузные и отражательные эффекты Расчет рассеянного света довольно прост. Диффузный свет описывается интенсивно- стью RGB-компонентов, точно так же взаимодействующими с материалом, который характеризуется определенными свойствами. Однако диффузный свет является на- правленным, а интенсивность на поверхности объекта меняется в зависимости от угла между поверхностью и источником света, расстояния до источника света и ко- эффициентов ослабления (если между источником и поверхностью имеется не полно- стью прозрачная среда, подобная туману) и т.д. То же можно сказать и об источниках и интенсивностях отраженного света. Суммарный эффект, выраженный через RGB- коды, рассчитывается точно так же, как для рассеянного света, но интенсивности источника света (скорректированные с учетом угла падения) умножаются на пара- метры отражательных свойств материала. После этого все три полученных набора RGB-кодов суммируются и дают окончательный цвет объекта. Если какой-то компо- нент цвета превышает 1,0, он устанавливается равным 1. (Вы не можете получить интенсивность больше максимальной!) В общем случае рассеянный и диффузный компоненты источников света и мате- риалов одинаковы и в основном определяют цвета объекта. Отраженный свет и соот- ветствующие свойства материала обычно имеют характеристики, близкие к светло- серому или белому цвету. Отраженный компонент существенно зависит от угла па- дения, а “зайчики” на поверхности объекта обычно имеют белый цвет. Добавление света к сцене Изложенный ниже материал поможет систематизировать все вышесказанное. Рас- смотрим несколько примеров кода OpenGL, необходимого для создания освещения; это также поможет закрепить все, что вы изучили. Мы продемонстрируем несколько дополнительных особенностей и требований к освещению, вводимых OpenGL. Ниже приведено несколько примеров, основанных на программе JET. Исходная версия не содержит кода, генерирующего освещение, — в ней просто рисуются треугольники с активизированным удалением скрытых поверхностей (проверка глубин). Полно- стью завершенный самолет будет иметь металлическую поверхность, сверкающую на солнце при вращении модели с помощью клавиш со стрелками. Активизация освещения Чтобы указать OpenGL рассчитывать освещение, вызывается функция glEnable с па- раметром GL_LIGHTING. glEnable(GL_LIGHTING); Данная команда сообщает OpenGL, что для определения цвета каждой вершины на сцене нужно использовать свойства материалов и параметры освещения. Однако,
244 Часть i. Классический OpenGL Рис. 5.17. Неосвещенный самолет, не отражающий света если свойства материалов или параметры освещения не заданы, объект останется темным и неосвещенным, как показано на рис. 5.17. Посмотрите на код любой про- граммы JET и вы увидите, что мы вызываем функцию SetupRC непосредственно после формирования контекста визуализации. Именно в этот момент мы выполняем инициализацию параметров освещения. Настройка модели освещения После того как вы активизировали расчет освещения, первое, что нужно сделать, — задать модель освещения. На модель освещения влияют три параметра, которые ука- зываются с помощью функции glLightModel. Первый из этих параметров — gl_light_model_ambient — используется в сле- дующем примере (программа AMBIENT на компакт-диске). Он позволяет задавать глобальный рассеянный свет, равномерно со всех сторон освещающий объекты на сцене. В приведенном ниже коде, например, задается яркий белый свет. // Яркий белый свет (RGB-коды максимальной интенсивности) GLfloat ambientLight[] = { l.Of, l.Of, l.Of, l.Of }; // Активизируется освещение glEnable(GL_LIGHTING); // В модели освещения задается использование рассеянного света, // заданного в функции ambientLight[] glLightModelfv(GL_LIGHT_MODEL_AMBIENT,ambientLight); Использованный в данном случае вариант функции glLightModel (glLightMod- elfv) в качестве первого параметра принимает параметр модели освещения, который задается или модифицируется, и массив RGBA-кодов, описывающий свет. Использу- емые по умолчанию значения (0.2, 0.2, 0.2, 1.0) дают довольно тусклое осве- щение. Параметры же нашей модели освещения позволяют легко определять, освеща- . ются ли передние, задние или обе части многоугольников, и видеть, как рассчитыва- ются углы отраженного освещения. Подробнее все параметры описаны в справочном разделе в конце главы.
Глава 5. Цвет, материалы и освещение- основы 245 Установка свойств материалов Теперь, когда у нас есть источник рассеянного света, следует установить часть свойств материала, чтобы многоугольники отражали свет, и мы могли видеть само- летик Свойства материала можно задать двумя способами. Первый — использовать функцию glMaterial перед заданием каждого многоугольника или набора много- угольников. Рассмотрим следующий фрагмент кода. Glfloat gray[] = { 0.75f, 0.75f, 0.75f, l.Of }; glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gray); glBegin(GL_TRIANGLES); glVertex3f(-15.Of, O.Of, 30.Of) ; glVertex3f(O.Of, 15.Of, 30.Of); glVertex3f(O.Of, O.Of, -56.Of); glEnd(); Первый параметр функции glMaterialfv задает, какие стороны поверхности получают заданные свойства — передняя, задняя или обе (GL_front, GL_back или gl_front_and_back). Второй параметр указывает, какие свойства устанавливаются; в приведенном примере задаются одинаковые отражательные способности рассе- янного и диффузного света. Последним параметром является массив, содержащий RGBA-коды, формирующие указанные свойства. Ко всем примитивам, заданным по- сле вызова функции glMaterial, применяются последние установленные значения, пока не будет вызвана следующая функция glMaterial. В большинстве случаев рассеянный и диффузный компоненты одинаковы, и если не требуются эффекты зеркального отражения (искрящиеся, сияющие пятна), опреде- лять отражательные свойства не нужно. Но даже в этом случае требуется кропотливая работа по определению массива для каждого цвета объекта и вызов функции glMa- terial перед каждым многоугольником или группой многоугольников. Теперь мы готовы рассмотреть второй (предпочтительный) вариант задания свойств материала — согласовании цветов (color tracking). При согласовании цветов вы указываете OpenGL задать свойства материалов, вызывая только функцию gl- Color. Чтобы активизировать согласование цветов, вызывается функция glEnable с параметром GL_color_material. glEnable(GL_COLOR_MATERIAL); Затем с помощью функции glColorMaterial задаются параметры материала, со- ответствующие значениям, заданным с помощью glColor Например, чтобы задать рассеивающие и диффузные свойства передних частей многоугольников, соответству- ющие цветам, заданным с помощью glColor, вызывается такая функция glColorMaterial(GL_FRONT,GL_AMBIENT_AND_DIFFUSE); В приведенном ранее фрагменте кода задаются свойства последующих материа- лов. Подход, который рассмотрен ниже, выглядит более громоздким, но в действи- тельности при увеличении числа различных цветных многоугольников он экономит большое число строк кода и выполняется быстрее. // Активизируется согласование цветов glEnable(GL_COLOR_MATERIAL);
246 Часть I. Классический OpenGL Рис. 5.18. Результат выполнения завершенной программы AMBIENT // Рассеянный и диффузный цвета передней стороны объектов // соответствуют тому, что указано в glColor glColorMaterial(GL_FRONT,GL_AMBIENT_AND_DIFFUSE); glcolor3f(0.75f, 0.75f, 0.75f); glBegin(GL_TRIANGLES); glVertex3f(-15.Of,0.Of, 30.Of); givertex3f(O.Of, 15.Of, 30.Of); glVertex3f(O.Of, O.Of, -56.Of); glEndO; Листинг 5.2 содержит код, который мы с помощью функции SetupRC добавляем в пример с моделью самолета и который устанавливает яркий источник рассеянно- го света и так задает свойства материала, чтобы объект мог отражать свет и был видимым. Кроме того, мы изменили цвета самолета, чтобы разные цвета имели не все многоугольники, а все участки поверхности. Конечный результат выполнения программы, показанный на рис. 5.18, не сильно отличается от изображения, полу- чавшегося до применения освещения. Тем не менее, если вполовину уменьшить рас- сеянный свет, мы получим более приемлемое изображение, показанное на рис. 5.19. В этом случае RGBA-коды рассеянного света устанавливались следующим образом. GLfloat ambientLight[] = { 0.5f, 0.5f, 0.5f, l.Of }; Теперь видно, как уменьшить рассеянный свет на сцене, чтобы получить более тусклое изображение. Данная возможность полезна при моделировании сцен, на кото- рые постепенно опускаются сумерки или заслоняется определенный источник света (один объект выходит в тень другого). Листинг 5.2. Настройка условий рассеянного освещения // Эта функция выполняет необходимую инициализацию в контексте // визуализации. В данном случае устанавливается // и инициализируется освещение сцены. void SetupRC()
Глава 5. Цвет, материалы и освещение: основы 247 Рис. 5.19. Результат выполнения программы AMBIENT при уменьшенной вдвое интенсивности источника света // Параметры света // Яркий белый свет GLfloat ambientLight[] = { l.Of, l.Of, l.Of, l.Of }; glEnable(GL_DEPTH__TEST); // Удаление скрытых поверхностей glEnable(GL_CULL_FACE); // Расчеты внутри самолета не выполняются gIFrontFace(GL_CCW); // Многоугольники с обходом против часовой стрелки // направлены наружу // Освещение glEnable(GL_LIGHTING); // Активизируется освещение // Модель освещения использует рассеянный свет, // заданный функцией ambientLight[] glLightModelfv(GL_LIGHT_MODEL_AMBIENT,ambientLight); glEnable(GL_COLOR_MATERIAL); // Активизируем согласование цветов материалов // Рассеянный и диффузный цвета передней стороны объектов // соответствуют тому, что указано в glColor glColorMaterial(GLJFRONT,GL_AMBIENT_AND_DIFFUSE); // Красивый светло-синий фон glClearColor(O.Of, O.Of, 05.f,l.Of); } Использование источников света Манипулирование рассеянным светом используется, но в большинстве приложе- ний, где моделируется мир, приближенный к реальному, следует задавать один или несколько источников света. Помимо интенсивности и цвета эти источники характе- ризуются положением и/или направлением. Отметим, что размещение таких источ- ников света может очень сильно влиять на вид сцены.
248 Часть I. Классический OpenGL Рис. 5.20. Свет отражается от объектов под определенным углом Наблюдатель OpenGL поддерживает использование по крайней мере восьми независимых ис- точников света, расположенных в любых точках сцены или даже вне отображаемого объема. Вы также можете поместить источник света в бесконечности, сделав его лучи параллельными, или заставить близлежащий источник света излучать во все сторо- ны. Кроме того, можно установить точечный источник света, излучающий в пределах заданного конуса света, и приписать ему любые характеристики. “Вверх” — это куда? Задавая источник света, вы сообщаете OpenGL, где он находится и в каком направ- лении светит. Часто источник света излучает во всех направлениях (тогда он назы- вается ненаправленным), но он может быть и направленным. В любом случае, какой бы объект вы ни рисовали, лучи света от любого источника (отличного от источ- ника рассеянного света) под углом падают на поверхность объекта, образованную множеством многоугольников. Разумеется, если речь идет о направленном свете, не обязательно все поверхности всех многоугольников будут освещены. Отметим, что для расчета эффектов затенения на поверхности многоугольников OpenGL требуется информация, на основании которой можно рассчитать угол. На рис. 5.20 показано, как на многоугольник (квадрат) падает луч света от неко- торого источника. Луч образует угол А с плоскостью, на которую он падает. Затем свет отражается под углом В в сторону наблюдателя (в противном случае вы бы его не видели). Эти углы используются вместе с параметрами освещения и материалов (см. выше) для расчета кажущегося цвета обозначенной точки. Так получилось, что положения, используемые OpenGL, являются вершинами многоугольника. Поскольку OpenGL рассчитывает кажущиеся цвета всех вершин, а затем создает между ними плавные переходы, создается иллюзия освещения. Магия! С точки зрения программирования эти расчеты освещения представляют опре- деленную концептуальную сложность. Каждый многоугольник создается как набор вершин, представляющих собой просто точки. После этого на каждую вершину под определенным углом падает луч света. Каким же образом вы (или OpenGL) рас- считываете угол между точкой и линией (лучом света)? Разумеется, вы не можете геометрически найти угол между одной точкой и линией в трехмерном пространстве, поскольку общее число возможных результатов бесконечно. Следовательно, с каждой вершиной нужно соотнести определенную информацию, сообщающую направление “вверх” от вершины и “прочь” от поверхности примитива.
Глава 5. Цвет, материалы и освещение: основы 249 Вектор нормали i к Вектор нормали 90° 90° _______----------------------------Н~_______ Двухмерный вектор нормали Трехмерный вектор нормали Рис. 5.21. Двух- и трехмерные векторы нормали Нормали к поверхности Линия, идущая под прямым углом от вершины в направлении “вверх”, начинается на воображаемой плоскости (плоскости многоугольника). Данная линия называет- ся вектором нормали (или нормалью). Термин “вектор нормали” может показаться чем-то сродни заумным словечкам, которыми оперируют члены экипажа “Звездного пути”, но он означает просто линию, перпендикулярную действительной или мни- мой поверхности. Вектор — это линия, указывающая в определенном направлении, а словом нормаль всякие умники заменяют слово “перпендикулярный” (пересека- ющийся с чем-то под углом 90°). Можно подумать, слово “перпендикуляр” звучит недостаточно сложно! Таким образом, вектор нормали — это линия, указывающая в направлении, образующем угол 90° с поверхностью вашего многоугольника. При- меры двух- и трехмерных векторов нормали представлены на рис. 5.21. Возможно, у вас уже возник вопрос, почему мы должны задавать вектор нор- мали для каждой вершины. Почему мы не можем просто задать нормаль для всего многоугольника и использовать ее для каждой вершины? Мы можем так сделать, более того, мы так сделаем в последующих примерах. Однако иногда невыгодно, чтобы все нормали были перпендикулярны поверхности многоугольника. Возможно, вы заметили, что многие поверхности не являются плоскими! Их можно аппрок- симировать плоскими многоугольными фрагментами, но в результате вы получите угловатую фасеточную поверхность. Позже мы обсудим технику, позволяющую с по- мощью плоских многоугольников создавать иллюзию гладких кривых, “подгоняя” нормали поверхностей (снова магия!). Однако, будем последовательными... Задание нормали Чтобы понять, как мы задаем нормаль для вершины, рассмотрим рис. 5.22, где изобра- жена плоскость, “плавающая” над плоскостью xz в трехмерном пространстве. Дан- ный рисунок служит только для иллюстрации концепции. Обратите внимание на линию, проходящую через вершину (1,1,0) перпендикулярно плоскости. Если вы- брать любую точку на этой линии, скажем, (1,10,0), линия, проходящая от первой точки (1,1,0) до этой второй точки (1,10,0), будет вектором нормали. Вторая за- данная точка в действительности указывает, что направление от вершины совпадает с положительным направлением оси у. Данная договоренность также использует- ся для обозначения передних и задних сторон многоугольников, поскольку векторы
250 Часть I. Классический OpenGL Рис. 5.22. Вектор нормали, перпендикулярный поверхности направлены вверх и от передней поверхности. Видно, что эта вторая точка характеризуется числом единиц по положительным направлениям осей х, у и z до некоторой точки вектора нормали, указывающего от вершины. Вместо того чтобы задавать две точки для каждого вектора нормали, мы можем вычесть вершину из второй точки нормали и получить тройку координат, обозначающую расстояния по осям х, у и z от вершины до точки. В нашем примере получаем такие три величины. (1,10,0) - (1,1,0) - (1 - 1,10 — 1,0) = (0,9,0) На данный пример можно посмотреть и по-другому: если транслировать вершину в начало координат, точка, заданная как разность двух исходных точек, по-прежнему будет задавать направление, образующее угол 90° с поверхностью. Такой транслиро- ванный вектор нормали показан на рис. 5.23. Вектор — это направленная величина, сообщающая OpenGL, в каком направ- лении располагается лицевая сторона вершины (или многоугольника). В следую- щем сегменте кода показан вектор нормали, заданный в программе JET для одного из треугольников. glBegin(GL_TRIANGLES); glNormal3f(O.Of, -l.Of, O.Of); glVertex3f(O.Of, O.Of, 60.Of); glVertex3f(-15.Of, O.Of, 30.0f); glVertex3f(15.Of,0.Of,30.Of); glEnd(); В качестве аргументов функция glNormal3f принимает тройку координат, зада- ющих вектор нормали, который указывает направление, перпендикулярное поверх- ности треугольника. В нашем примере нормали ко всем трем вершинам имеют оди- наковую ориентацию — по отрицательному направлению оси у. Это простой пример, поскольку треугольник целиком лежит на плоскости xz, и представляет в действи- тельности нижний фрагмент самолета. Позже вы увидите, что часто для всех вершин требуется задавать разные нормали.
Глава 5. Цвет, материалы и освещение: основы 251 Рис. 5.23. Новый транслированный вектор нормали Перспектива задания нормали для каждой вершины каждого многоугольника, ко- торый вы рисуете может привести в уныние, особенно, если лишь несколько по- верхностей лежат на одной из основных плоскостей. Не бойтесь! Чуть ниже мы опишем полезную функцию, которую вы можете вызывать снова и снова для расчета нормалей за вас. ОБХОД МНОГОУГОЛЬНИКА Обратите особое внимание на порядок вершин в треугольниках самолета. Если вы наблюдаете рисование этого треугольника с направления, в котором указывает вектор нормали, вершины кажутся расположенными вокруг треугольника против часовой стрелки. Это называется обходом многоугольника. По умолчанию передняя грань многоугольника определяется как сторона, вершины которой обходятся против ча- совой стрелки. Единичные нормали Если дать OpenGL применить свою магию, все нормали к поверхности будут пре- образованы в единичные нормали. Единичным называется вектор нормали, длина которого равна 1. Длина нормали на рис. 5.23 равна 9. Чтобы найти длину нормали, нужно возвести в квадрат все ее компоненты, сложить их и извлечь из суммы квад- ратный корень. Если поделить каждый компонент нормали на ее длину, получится вектор, указывающий в том же направлении, но имеющий единичную длину. В на- шем случае новый вектор нормали будет задаваться как (0,1,0). Описанный процесс получил название нормировки. Итак, при расчете освещения все векторы нормали должны нормироваться. Привыкайте к жаргону!
252 Часть I Классический OpenGL Вы можете указать OpenGL автоматически преобразовать имеющиеся нормали в единичные, активизировав нормировку с помощью вызова glEnable с парамет- ром GL_NORMALIZE. glEnable(GL_NORMALIZE); Однако данный подход не очень выгоден с точки зрения производительности. Гораздо лучше заранее рассчитать единичные нормали, чем поручать OpenGL вы- полнять эту задачу за вас. Следует отметить, что вызовы функции преобразования glScale также масштаби- руют длины нормалей Если в одной программе вы используете и glScale, и функции освещения, последние могут дать нежелательные эффекты. Если вы задали единич- ные нормали для всех геометрических объектов и использовали постоянный коэф- фициент масштабирования в glScale (все геометрические объекты масштабируются одинаково), можно использовать новую альтернативу GL_NORMALIZE (новой она стала после OpenGL 1.2) — GL_RESCALE_NORMALS. Чтобы активизировать эту возможность, вызывается следующая функция: glEnable(GL_RESCALE_NORMALS); Этот вызов сообщает OpenGL, что нормали не имеют единичной длины, но их можно одинаково масштабировать, чтобы получить единичную длину. Чтобы обра- ботать такую ситуацию, OpenGL исследует матрицу наблюдения модели. В резуль- тате получаем меньше математических операций на вершину, чем потребовалось бы в противном случае. Поскольку перед началом работы OpenGL ему лучше предоставить единичные нормали, библиотека glTools содержит функцию, которая принимает любой вектор нормали и нормирует его. void gltNormalizeVector(GLTVector vNormal); Нахождение нормали На рис. 5.24 представлен многоугольник, не лежащий целиком в одной из осевых плоскостей. Угадать вектор нормали, указывающий от этой поверхности, гораздо сложнее, поэтому нужен удобный способ расчета нормали произвольного много- угольника в трехмерных координатах. Вектор нормали любого многоугольника легко рассчитать по трем точкам, на плос- кости этого многоугольника На рис. 5 25 показаны три точки — Ръ Р2 и Р3, — которые можно использовать для определения двух векторов: вектора Ух, направленного от /’ к Р2, и вектора V2, направленного от Pi к Р3. Математически два вектора в трех- мерном пространстве определяют плоскость. (В этой плоскости, в частности, лежит ваш исходный многоугольник) Если найти векторное произведение двух векторов (математически это записывается как Vi х У2), получающийся в результате вектор будет перпендикулярен данной плоскости. На рис. 5.26, например, показан вектор V3, являющийся векторным произведением Vi и V2.
Глава 5. Цвет, материалы и освещение: основы 253 Рис. 5.24. Нетривиальная задача нахождения нормали Рис. 5.26. Вектор нормали, определенный как векторное произведение двух векторов Поскольку данный метод полезен и используется очень часто, библиотека glTools содержит функцию, рассчитывающую вектор нормали по трем данным точкам мно- гоугольника. void gltGetNormalVector(GLTVector vPl, GLTVector vP2, GLTVector vP3, GLTVector vNormal); Чтобы использовать эту функцию, ей нужно передать три вектора (каждый из которых представляет собой массив из трех величин с плавающей запятой) из много- угольника или треугольника (заданных при обходе вершин против часовой стрелки), а на выходе будет получен другой векторный массив, содержащий вектор нормали.
254 Часть I. Классический OpenGL Устанавливаем источник света Теперь, когда вы понимаете требования по “настройке” многоугольников, чтобы они получали свет и взаимодействовали с источником света, пришла пора включить свет! В листинге 5.3 приведена функция SetupRC из программы LITJET (находится на компакт-диске). Часть процесса установки этой простой программы посвящена созда- нию источника света, который помещается слева вверху, немного позади наблюдателя. Источник света GL_LIGHTO имеет рассеянный и диффузный компонент, интенсивно- сти которых заданы в массивах ambientLight [ ] и dif f useLight [ ]. В результате мы получаем умеренно мощный белый источник света. GLfloat ambientLight[] = { 0.3f, 0.3f, 0.3f, l.Of }; GLfloat diffuseLight[] = { 0.7f, 0.7f, 0.7f, l.Of }; // Устанавливается и активизируется источник света О glLightfv(GL_LIGHTO,GL_AMBIENT,ambientLight); glLightfv(GL_LIGHT0,GL_DIFFUSE,diffuseLight); Наконец, включается источник света GL_LIGHTO. glEnable(GL_LIGHT0) ; С помощью приведенного ниже кода (находится в функции ChangeSize) источник света размещается в пространстве. GLfloat lightPos[] = { -50. f, 50.Of, 100.Of, l.Of }; glLightfv(GL_LIGHT0,GL_POSITION,lightPos); Здесь функция lightPos [ ] содержит положение источника света. Последнее зна- чение массива — 1.0; оно сообщает, что указанные координаты являются положением источника света. Если последнее значение массива равно 0.0, значит, источник света расположен в бесконечности в направлении, которое указано вектором, приведенным в этом массиве. Данного вопроса мы еще коснемся позже. Пока же отметим, что ис- точники света подобны геометрическим объектам в том, что их можно перемещать по сцене с помощью матрицы наблюдения модели. Задействовав положение источника света в преобразовании наблюдения, мы гарантируем, что свет приходит с правиль- ного направления вне зависимости от того, как мы преобразовываем геометрию. Листинг 5.3. Установка контекста освещения и визуализации в программе LITJET // Функция выполняет необходимую инициализацию в контексте // визуализации. В данном случае она задает и инициализирует // освещение сцены. void SetupRC() { // Коды и координаты источников света GLfloat ambientLight[] = { 0.3f, 0.3f, 0.3f, l.Of }; GLfloat diffuseLight[] = { 0.7f, 0.7f, 0.7f, l.Of }; glEnable(GL_DEPTH_TEST); // Удаление скрытых поверхностей gIFrontFace(GL_CCW); // Многоугольники с обходом против часовой стрелки
Глава 5 Цвет, материалы и освещение: основы 255 / / направлены вперед glEnable(GL_CULL_FACE); // Внутри самолета расчеты не производятся // Активизируется освещение glEnable(GL_LIGHTING); // Устанавливается и активизируется источник света О glLightfv(GL_LIGHT0,GL_AMBIENT,ambientLight); glLightfv(GL_LIGHT0,GL_DIFFUSE,diffuseLight); glEnable(GL_LIGHT0); // Активизирует согласование цветов glEnable(GL_COLOR_MATERIAL); // Свойства материалов соответствуют кодам glColor glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE) ; // Светло-синий фон glClearColor(0.Of, O.Of, l.Of, l.Of ); } Устанавливаем свойства материала Внимательно изучая листинг 5.3, вы обнаружите, что активизировано согласование цвета, причем согласовываются рассеивающее и диффузное свойства лицевой поверх- ности многоугольника. Именно такие настройки заданы и в программе AMBIENT. // Активизируем согласование цвета glEnable(GL_COLOR_MATERIAL); // Свойства материалов согласуются с кодами glColor glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE); Устанавливаем многоугольники Код визуализации из первых двух примеров JET существенно изменился, чтобы под- держивать новую модель освещения. В листинге 5.4 приведена выборка из функции RenderScene (программа LITJET). Листинг 5.4. Фрагмент кода, в котором устанавливаются цвета и вычисляются нормали и многоугольники GLTVector vNormal; // Здесь хранятся рассчитанные нормали к поверхностям // Устанавливается цвет материала glColor3ub(128, 128, 128); glBegin(GL_TRIANGLES); glNormal3f(O.Of, -l.Of, O.Of); glNormal3f(O.Of, -l.Of, O.Of); glVertex3f(O.Of, O.Of, 60.Of); glVertex3f(-15.Of, O.Of, 30.0f); glVertex3f(15.Of, O.Of, 30.0f); // Вершины панели
256 Часть I. Классический OpenGL { GLTVector vPoints[3] = {{15.Of, O.Of, 30.Of}, { O.Of, 15.Of, 30.Of}, { O.Of, O.Of, 60.Of}}; // Рассчитывается нормаль плоскости gltGetNormalVector(vPoints[0], vPoints[1], vPoints[2], vNormal); glNormal3fv(vNormal); glVertex3fv(vPoints[0]); glVertex3fv(vPoints[l]); glVertex3fv(vPoints[2]); } { GLTVector vPoints[3] = {{ O.Of, O.Of, 60.Of}, { O.Of, 15.Of, 30.Of}, {-15.Of, O.Of, 30.Of}}; gltGetNormalVector(vPoints[0], vPoints[1],vPoints[2],vNormal); glNormal3fv(vNormal); glVertex3fv(vPoints[0]); glVertex3fv(vPoints[1]); glVertex3fv(vPoints[2]); } Обратите внимание на то, что мы рассчитываем вектор нормали, используя функ- цию gltGetNormalVector из glTools. Кроме того, свойства материалов теперь соответствуют цветам, установленным с помощью glColor. Еще один момент, на который стоит обратить внимание: не все треугольники взяты в обложку из функций glBegin/glEnd. Вы можете один раз указать, что рисуете треугольники, и любые три вершины, которые вы будете приводить после этого, будут расцениваться как верши- ны нового треугольника, пока вы не измените это с помощью функции glEnd. При очень большом числе многоугольников данная техника может существенно повысить производительность, устранив множество ненужных вызовов процедур и настроек групп примитивов. На рис. 5.27 показан результат выполнения завершенной программы LITJET. Те- перь весь самолет составлен из треугольников одного оттенка серого, а не из фигур разных цветов. Мы изменили цвет, чтобы на поверхности было легче наблюдать эф- фекты освещения. Несмотря на то что поверхность имеет один сплошной цвет, бла- годаря освещению вы по-прежнему можете видеть форму. Поворачивая самолетик с помощью клавиш со стрелками, вы можете наблюдать различные эффекты затене- ния, меняющиеся по мере того, как модель движется и взаимодействует со светом. ПОДСКАЗКА Наиболее очевидным способом повышения производительности данного кода явля- ется заблаговременный расчет всех векторов нормали и запись из для последующего использования в функции RenderScene. Прежде чем вы попытаетесь реализовать эту идею, прочтите в главе 11 “Все о конвейере: более быстрое прохождение геометрии” материал о таблицах отображения и массивах вершин, позволяющих записывать рассчитанные значение (не только векторов нормали, но и данных о мно- гоугольниках). Помните, что примеры приводятся для того, чтобы продемонстри- ровать концепции. Код реализации не обязательно является самым эффективным.
Глава 5. Цвет, материалы и освещение: основы 257 Рис. 5.27. Результат выполнения программы LITJET Эффекты освещения Рассеянного и диффузного света из программы LITJET достаточно, чтобы создать ил- люзию освещения. Поверхность самолета кажется затененной согласно углу падения света. При вращении модели эти углы меняются, и вы видите эффекты освещения меняющимися так, что легко догадаться, откуда поступает свет. Мы не учитывали отраженный компонент источника света, а также способность материала самолета к отражению. Хотя эффекты освещения достаточно отчетливы, поверхность самолета кажется раскрашенной ровно и уныло. Рассеянного и диффуз- ного освещения и соответствующих свойств материала достаточно, если вы модели- руете глину, дерево, картон, одежду или другой ровно окрашенный объект. Для таких металлических поверхностей, как обшивка самолета, желательно немного блеска. Отраженные блики Необходимый блеск поверхности ваших объектов добавляют зеркальное отражение и соответствующие свойства материала. Требуемое сияние имеет отбеливающие вли- яние на цвет объекта и может давать блики на поверхности при остром угле между лучом падения света и направлением на наблюдателя. Зеркальные блики появляют- ся практически всегда, когда свет падает на поверхность объекта и отражается от нее. Хорошим примером блика является белая искорка на блестящем красном шаре, выставленном на солнечный свет. Отраженный свет Добавить к источнику света компонент отраженного света достаточно легко. В при- веденном ниже коде иллюстрируется установка источника света в программе LITJET, модифицированной добавлением компонента отраженного света. // Коды и координаты источников света GLfloat ambientLight[] = { 0.3f, 0.3f, 0.3f, l.Of }; GLfloat diffuseLight[] = { 0.7f, 0.7f, 0.7f, l.Of }; GLfloat specular[] = { l.Of, l.Of, l.Of, l.Of};
258 Часть I Классический OpenGL // Активизируется освещение glEnable(GL_LIGHTING); // Устанавливается и активизируется источник света О glLightfv(GL_LIGHT0,GL_AMBIENT,ambientLight); glLightfv(GL_LIGHTO,GL_DIFFUSE,diffuseLight); glLightfv(GL_LIGHTO,GL_SPECULAR,specular); glEnable(GL_LIGHT0); Массив specular [ ] задает в качестве компонента отраженного света очень яркий источник белого света. В данном случае нашей целью является моделирование ярко- го солнечного света. Следующая строка просто прибавляет компонент отраженного света к источнику света GL_LIGHTO. glLightfv(GL_LIGHT0,GL_SPECULAR,specular); Если бы это было единственным изменением в программе LITJET, вы не заметили бы никакой разницы во внешнем виде самолета — мы не определили соответствую- щих отражательных свойств материала. Зеркальное отражение Добавление к свойствам материала коэффициента зеркального отражения выполняет- ся так же просто, как добавление зеркального компонента к источнику света. В следу- ющем фрагменте кода приводятся команды программы LITJET, на этот раз модифици- рованной так, чтобы материал имел ненулевой коэффициент зеркального отражения. // Коды и координаты источников света GLfloat specref[] = { l.Of, l.Of, l.Of, l.Of }; // Активизируем согласование цветов glEnable(GL_COLOR_MATERIAL); // Свойства материалов согласуются с кодами glColor glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE); // С этого момента все материалы имеют максимальный коэффициент // зеркального отражения glMaterialfv(GL_FRONT, GL_SPECULAR, specref); glMateriali(GL_FRONT, GL_SHININESS, 128); Как и ранее, мы так активизировали согласованием цветов, чтобы коэффициенты рассеянного и диффузного отражения материалов соответствовали текущему цвету, заданному функциями glColor. (Разумеется, мы не хотим, чтобы коэффициент зер- кального отражения согласовывался со значениями функции glColor, поскольку мы задаем его отдельно и он не меняется.) Теперь мы добавили массив specref [ ], который содержит RGBA-коды коэффи- циента зеркального отражения. Этот массив, состоящий целиком из единиц, характе- ризует поверхность, которая отражает практически весь отражаемый свет. В приве- денной ниже строке указывается, что материал всех последующих многоугольников будут иметь такой же коэффициент зеркального отражения. glMaterialfv(GL_FRONT, GL_SPECULAR,specref);
Глава 5. Цвет, материалы и освещение: основы 259 Поскольку мы не вызываем функцию glMaterial еще раз с параметром GL_SPECULAR, указанное свойство имеют все материалы. Мы специально поступи- ли так, поскольку требуется, чтобы весь самолет казался сделанным из металла или блестящих сплавов. Что еще важного мы сделали в процедуре настройки? Мы задали, что отражатель- ные свойства (диффузный и рассеянный свет) материала всех последующих мно- гоугольников (если мы не изменим это поведение с помощью вызова glMaterial или glColorMaterial) меняется так же, как текущий цвет, но свойства зеркального отражения при этом остаются прежними. Коэффициент зеркального отражения Как отмечалось ранее, сильный отраженный свет и высокая отражательная способ- ность делают цвета объекта светлее. В нашем примере сильный (максимальной интен- сивности) зеркальный свет и очень большая (максимальная) отражательная способ- ность привели к тому, что самолет кажется белым или серым за исключением точек поверхности, уделенных от источника света (эти точки кажутся черными и неосве- щенными). Чтобы смягчить этот эффект, после задания компонента зеркального от- ражения мы введем в код следующую строку: glMateriali(GL_FRONT,GL_SHININESS,128); Свойство GL_SHININESS задает коэффициент зеркального отражения материала — насколько маленьким и сфокусированным является блик. Значение 0 соответствует несфокусированному “зайчику”, и именно так получается равномерное осветление цветов всего многоугольника. Задав это значение, вы уменьшаете размер и силь- нее фокусируете яркое пятно света. Чем больше значение, чем ярче и блестящее поверхность. Во всех реализациях OpenGL диапазон значений этого параметра со- ставляет 1-128. В листинге 5.5 показан новый код процедуры SetupRC в контексте программы SHINYJET. Приведенный код является единственным отличием данной программы от LITJET (ну, еще изменился заголовок окна) и дает очень блестящий самолетик. Результат выполнения данной программы показан на рис. 5.28, но чтобы прочув- ствовать отличия, нужно запустить программу и, нажав одну из клавиш со стрелкой, полюбоваться тем, как модель вращается под лучами источника света. Листинг 5.5. Код настройки из программы SHINYJET, дающий “зайчики” на поверхности самолета // Эта функция выполняет необходимую инициализацию в контексте // визуализации. В данном случае она устанавливает и инициализирует // освещение на сцене void SetupRC() { // Коды и координаты источников света GLfloat ambientLight[] = { 0.3f, 0.3f, 0.3f, l.Of }; GLfloat diffuseLight[] = { 0.7f, 0.7f, 0.7f, l.Of }; GLfloat specular[] = { l.Of, l.Of, l.Of, l.Of}; GLfloat specref[] = { l.Of, l.Of, l.Of, l.Of }; glEnable(GL_DEPTH_TEST); // Удаление скрытых поверхностей
260 Часть I. Классический OpenGL Рис. 5.28. Результат выполнения программы SHINYJET gIFrontFace(GL_CCW); // Многоугольники с обходом против часовой стрелки // направлены вперед glEnable(GL_CULL_FACE); // Внутри самолета расчеты не производятся // Активизируется освещение glEnable(GL_LIGHTING); // Устанавливается и активизируется источник света 0 glLightfv(GL_LIGHTO,GL_AMBIENT,ambientLight); glLightfv(GL_LIGHTO,GL_DIFFUSE,diffuseLight); glLightfv(GL_LIGHT0,GL_SPECULAR,specular); glEnable(GLJLIGHTO) ; // Активизируем согласование цвета glEnable(GL_COLOR_MATERIAL); // Свойства материалов соответствуют кодам glColor glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE); // С этого момента все материала получают способность // отражать блики glMaterialfv(GL_FRONT, GL_SPECULAR,specref); glMateriali(GL_FRONT, GL_SHININESS,128); // Светло-синий фон glClearColor(O.Of, O.Of, l.Of, l.Of ); } Усреднение нормалей Ранее мы упоминали, что “настраивая” нормали, можно получать поверхности, со стоящие из плоских многоугольников, но кажущиеся гладкими. Данная техника, на званная усреднением нормалей, дает несколько интересных оптических иллюзий. На пример, фигура на рис. 5.29, собранная из треугольников и четырехугольников, ка жется сферой.
Глава 5. Цвет, материалы и освещение: основы 261 Рис. 5.29. Сфера, собранная из треугольников и четырехугольников Если для каждой грани сферы задана единственная нормаль, “сфера” будет вы- глядеть как большой многогранный драгоценный камень. Если же задать для каж- дой вершины “истинную нормаль”, расчеты освещения в каждой вершине дадут значения, которые OpenGL гладко интерполирует по поверхности многоугольника. Таким образом плоские многоугольника затеняются так, как если бы они были глад- кой поверхностью. Что мы подразумеваем под “истинной” нормалью? Многоугольное представле- ние является лишь аппроксимацией истинной поверхности. Теоретически, исполь- зуя достаточное число многоугольников, можно получить поверхность, кажущуюся гладкой. По сути, это похоже на идею, которую в главе 3 “Рисование в простран- стве: геометрические примитивы и буферы” мы использовали для рисования гладкой кривой с помощью набора коротких отрезков. Если рассматривать каждую верши- ну как точку истинной поверхности, реальная нормаль к этой поверхности и будет истинной нормалью. В нашем примере со сферой нормаль будет выходить из центра сферы и проходить через вершину. На рис. 5.30 и 5.31 это показано для простого двухмерного случая. На рис. 5.30 каждому плоскому сегменту соответствует нормаль, направленная пер- пендикулярно его поверхности. Здесь мы все делаем так же, как в приведенном выше примере LITJET. Однако на рис. 5.31 видно, что ни одна нормаль не перпендику- лярна отрезку, из которого выходит, — она перпендикулярна поверхности сферы (или касательной сферы). Касательная дотрагивается до кривой в одной точке, не пересекая ее. Трехмер- ный ее эквивалент — касательная плоскость. На рис. 5.31 показан контур реальной поверхности, и видно, что нормаль в действительности перпендикулярна линии, ка- сательной к поверхности. Расчет нормали сферы прост. (Нормаль описывается такими же величинами, как и положение вершины относительно центра.) Для других нетривиальных поверхно- стей расчеты могут быть не такими простыми. В подобных случаях рассчитываются нормали для всех многоугольников, имеющих общую вершину. А в качестве “истин- ной” нормали вершины берется среднее найденных нормалей. Визуально получается
262 Часть I Классический OpenGL Рис. 5.30. Аппроксимация с нормалями, перпендикулярными граням Рис. 5.31. Все нормали перпендикулярны исходной поверхности красивая, гладкая, правильная поверхность, хотя в действительности она составлена из множества маленьких плоских сегментов Собираем все вместе Итак, пришло время написать более сложную программу Мы собираемся проде- монстрировать использование нормалей для создания иллюзии гладкой поверхности, перемещение источника света по сцене, создание точечного источника света и, нако- нец, определим один из недостатков схемы освещения OpenGL Все сказанное реализовано в программе-примере SPOT С помощью функции glutSolidSphere мы создали сплошную сферу в центре отображаемого объема На этой сфере мы зажгли источник света, который можно перемещать Кроме того, мы изменили “гладкость” нормалей и продемонстрировали некоторые ограничения модели освещения OpenGL До этого момента мы задавали положение источника света (с помощью функ- ции glLight)так
Глава 5 Цвет, материалы и освещение основы 263 // Массив, задающий положение GLfloat lightPos[] = { O.Of, 150.Of, 150.Of, l.Of }; 11 Устанавливается положение источника света glLightfv(GL_LIGHT0,GL_POSITION,lightPos); Массив lightPos [ ] содержит координаты x, у и z реального положения источ- ника света на сцене или направления, с которого поступает свет. Последнее значение (в данном случае — 1.0) указывает, что в этой точке действительно расположен ис- точник света. По умолчанию свет равномерно излучается по всем направлениям, но это можно изменить, задав эффект прожекторного освещения Чтобы создать источник света в бесконечности, указав направление от него с по- мощью вектора, вместо последнего элемента массива lightPos [ ] ставится 0.0 Та- кой направленный источник света равномерно освещает поверхность объектов (те все лучи света параллельны). С другой стороны, лучи локального источника света расходятся во все стороны от самого источника. Создание прожектора Создание прожектора не отличается от создания любого другого локального ис- точника света В коде, приведенном в листинге 5 6, показана функция SetupRC из программы SPOT. Здесь в центре окна помещается синяя сфера и создастся про- жектор, который можно перемещать вертикально с помощью клавиш со стрелками вверх и вниз и горизонтально — с помощью стрелок вправо и влево. При удалении прожектора от поверхности сферы за ним по поверхности следует яркий блик Листинг 5.6. Настройка освещения в программе SPOT // Коды и координаты источников света GLfloat lightPos[] = { O.Of, O.Of, 75.Of, l.Of }; GLfloat specular[] = { l.Of, l.Of, l.Of, l.Of); GLfloat specref)] = { l.Of, l.Of, l.Of, l.Of }; GLfloat ambientLight[] = { 0.5f, 0.5f, 0.5f, l.Of); GLfloat spotDir[] = { O.Of, O.Of, -l.Of ); 11 Эта функция выполняет необходимую инициализацию в контексте // визуализации. В данном случае она устанавливает и инициализирует // освещение на сцене void SetupRC() glEnable(GL_DEPTH_TEST); // Удаление скрытых поверхностей glFrontFace(GL_CCW); // Многоугольники с обходом против часовой стрелки // направлены вперед glEnable(GL_CULL_FACE); // Задние поверхности не отображаются // Активизируется освещение
264 Часть I Классический OpenGL glEnable(GL_LIGHTING); // Устанавливается и активизируется источник света О // Создается слабое рассеянное освещение, чтобы можно было // видеть объекты glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambientLight); // Источник света имеет только диффузный и отражательный // компоненты glLightfv(GL_LIGHT0,GL_DIFFUSE,ambientLight); glLightfv(GL_LIGHT0,GL_SPECULAR,specular); glLightfv(GL_LIGHT0,GL_POSITION,lightPos); // Прожекторные эффекты // Угол конуса освещения составляет 60 градусов glLightf(GL_LIGHTO,GL_SPOT_CUTOFF,60.Of); // Активизируется источник света glEnable(GL_LIGHT0); // Активизируем согласование цветов glEnable(GL_COLOR_MATERIAL); // Свойства материалов соответствуют кодам glColor glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE); //С этого момента все материала получают способность // отражать блики glMaterialfv(GL_FRONT, GL_SPECULAR, specref); glMateriali(GL_FRONT, GL_SHININESS,128); // Черный фон glClearColor(0.Of, O.Of, O.Of, l.Of ); ) Прожектором источник света делает следующая строка кода // Прожекторные эффекты // Угол конуса освещения составляет 60 градусов glLightf(GL_LIGHT0,GL_SPOT_CUTOFF,60.Of); Значение GL_spot_cutoff задаст угол конуса света, излучаемого прожектором (угол между осью и краем конуса) Для нормального локального источника света это значение равно 180°, так что свет нс образует конуса Фактически при создании прожекторных эффектов допускаются только углы от 0 до 90°. Прожектор излучает конус света, а объекты вне этого конуса нс освещаются На рис. 5 32 показано, как угол связан с шириной основания конуса Рисование прожектора Если вы помещаете на сцене луч прожектора, нужна точка, из которой исходит этот луч То, что в некоторой точке пространства имеется источник света, не означает, что вы увидите в этой точке яркое пятно В нашей программе SPOT в месте, где располагается источник-прожектор, мы поместили красный конус, обозначив поло- жение этого источника света Внутрь конуса мы поместили яркую желтую сферу, имитирующую электрическую лампочку В данном примере имеется всплывающее меню, которое мы использовали для де- монстрации нескольких моментов Меню содержит позиции выбора плоского и глад- кого затенения и создания сферы с низкой, средней и высокой степенью “мозаич-
Глава 5 Цвет, материалы и освещение основы 265 Рис. 5.32. Угол конуса света (прожектор) Рис. 5.33. Слева показана сфера, собранная из мелких фрагментов, справа — сфера, полученная всего из нескольких многоугольников ности” (малое, среднее или большое число многоугольников, составляющих поверх- ность) Мозаичное представление заключается в разбиении сетки многоугольников на более мелкую сетку (больше вершин). На рис 5.33 показано каркасное пред- ставление сферы с очень мелкой мозаикой рядом со сферой, составленной всего из нескольких многоугольников На рис. 5.34 пример показан в исходном состоянии с прожектором, немного сме- щенным в одну сторону. (Для перемещения источника света можно использовать кла- виши со стрелками.) Сфера состоит из нескольких многоугольников с плоским зате- нением. В Windows для открывания всплывающего меню используйте правую кнопку мыши (в Мас — щелчок при нажатой клавише Ctrl), а с помощью этого меню можно переключаться между плоским затенением и низким, средним и высоким мозаичным представлением сферы. Полный код визуализации сцены приводится в листинге 5 7 Листинг 5.7. Функция визуализации программы SPOT, отвечающая за движение прожектора // Вызывается для рисования сцены void RenderScene(void) { if(iShade == MODE_FLAT) glShadeModel(GL_FLAT); else // iShade = MODE_SMOOTH; glShadeModel(GL_SMOOTH); // Очищает окно текущим цветом очистки glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
266 Часть I. Классический OpenGL Рис. 5.34. Программа SPOT — малое число вершин, плоское затенение // Вначале помещается источник света // Записывается координатное преобразование glPushMatrix(); // Поворачивается система координат glRotatef(yRot, O.Of, l.Of, O.Of); glRotatef(xRot, l.Of, O.Of, O.Of); // Задается новое положение и направление в повернутых // координатах glLightfv(GL_LIGHTO,GL_POSITION,lightPos); glLightfv(GL_LIGHT0,GL_SPOT_DIRECTION,spotDir); // Рисуется красный конус, окружающий источник света glColor3ub(255,0,0); // Транслируется начало координат, чтобы переместить конус // в место расположения источника света glTranslatef(lightPos[0],lightPos[1],lightPos[2]); glutSolidCone(4.Of,6.Of,15,15); // Рисуется немного смещенная сфера, обозначающая // электрическую лампочку // Сохраняются переменные состояния освещения glPushAttrib(GL_LIGHTING_BIT); // Выключается свет, и задается яркая желтая сфера gIDisable(GL_LIGHTING); glColor3ub(255,255,0); glutSolidSphere(3.Of, 15, 15); // Восстанавливаются переменные состояния освещения glPopAttribf); // Восстанавливаются координатные преобразования glPopMatrix(); // Устанавливается цвет материала, и рисуется сфера glColor3ub(0, 0, 255); iffiTess == MODE_VERYLOW) glutSolidSphere(30.Of, 7, 7); else if(iTess == MODE_MEDIUM) glutSolidSphere(30.Of, 15, 15); else // iTess = MODE_MEDIUM;
Глава 5. Цвет, материалы и освещение: основы 267 Рис. 5.35. Гладкое затенение, но недостаточное число вершин glutSolidSphere(30.Of, 50, 50); // Отображаются результаты glutSwapBuffers(); } Переменные Hess и iMode устанавливаются обработчиком меню GLUT и кон- тролируют, на сколько участков разбивается сфера и какое затенение применяется — плоское или гладкое. Обратите внимание на то, что источник света размещается до визуализации геометрических объектов. Как отмечалось в главе 2, OpenGL — это программный интерфейс непосредственного типа действия: желая осветить объект, вы помещаете источник света в удобном для вас месте до рисования объекта. На рис. 5.34 можно видеть, что сфера раскрашена грубо и явно заметна каж- дая плоская грань. Как следует из рис. 5.35, переключение на гладкое затенение не очень помогает. Как видно из рис. 5.36, увеличение числа вершин дает лучшие результаты, однако при перемещении источника света вокруг сцены все еще наблюдаются раздражаю- щие артефакты. Эти артефакты являются одним из недостатков схемы освещения OpenGL. Лучше всего охарактеризовать эту ситуацию можно, сказав, что данные ар- тефакты являются недостатком схемы освещения вершин (не обязательно OpenGL!). Освещая вершины, а затем интерполируя полученные значения, мы получаем грубую аппроксимацию освещения. В большинстве случаев этого подхода достаточно, но, как видно из нашего примера, в определенных ситуациях его явно мало. Если пе- реключиться на очень мелкое мозаичное представление и подвигать источник света, можно видеть, что освещение портит все, кроме тех мест, где оно отсутствует.
268 Часть I. Классический OpenGL Рис. 5.36. Выбор более мелкой сетки многоугольников дает лучшее осве- щение вершин Отметим, что по мере того, как аппаратные ускорители OpenGL начинали ускорять преобразования и эффекты освещения, а процессоры становились мощнее, стало реальным более мелкое мозаичное представление геометрических объектов и лучшие эффекты освещения OpenGL. Осталось сделать последнее заключение относительно примера SPOT, касающее- ся мозаичного представления со средней степенью детализации и плоского затенения. Как показано на рис. 5.37, каждая грань сферы окрашивается плоско равномерно. Все вершины имеют одинаковый цвет, который, впрочем, корректируется с учетом нор- мали и света. При плоском затенении каждый многоугольник имеет цвет последней заданной вершины, и гладкая интерполяция значений вершин не производится. Тени Глава по цвету и освещению была бы неполной без обсуждения теней. Добавление тени к сценам может существенно повысить их реализм и визуальную эффективность. На рис 5.38 и 5.39 показаны два освещенных куба. Хотя оба варианта окрашены, куб с тенью более убедителен, чем куб без нее. Что такое тень? Концептуально рисование тени является достаточно простым. Тень получается тогда, когда объект закрывает свет от источника света и не дает ему попасть на другой объект или поверхность, расположенную за объектом, создающим тень. Область по- верхности затененного объекта, на которую наложился контур затеняющего объекта,
Глава 5. Цвет, материалы и освещение: основы 269 Рис. 5.37. Многогранная сфера кажется темной. Тень можно создать программными средствами, спроектировав ис- ходный объект на плоскость поверхности, содержащей объект. Затем объект рисуется черным (или другим темным цветом), возможно, частично прозрачным. Существует множество методов и алгоритмов рисования теней (в том числе достаточно сложные). Поскольку книга посвящена преимущественно программному интерфейсу OpenGL, мы надеемся, что, овладев программным средством, вы найдете время изучить ли- тературу, предлагаемую в приложении А, в том числе касающуюся математического аппарата, который является основой запрограммированного подхода. В главе 18, “Тек- стуры глубины и тени”, рассмотрено несколько средств прямой поддержки создания теней в OpenGL; в данной главе достаточно продемонстрировать один из простейших методов, хорошо работающий при наложении теней на плоскую поверхность (такую, как земля). Процесс подобного проектирования иллюстрируется на рис. 5.40.
270 Часть I. Классический OpenGL Рис. 5.40. Проектирование объекта с цепью создания тени Для наложения проекции объекта на другую поверхность используются нетриви- альные действия с матрицами, которых мы коснулись в предыдущей главе. Сейчас мы попытаемся свести процесс к максимально простым концепциям и действиям. Код наложения проекции Итак, нужно так “сплющить” спроектированную матрицу наблюдения модели, чтобы все представленные в ней объекты рисовались в двухмерном виде. Вне зависимости от ориентации объекта он сплющивается на плоскость, в которой лежит его тень. Еще следует учесть два параметра: расстояние до источника света и направление его излучения. Направление лучей источника света определяет форму тени и влияет на ее размер. Если вы когда-нибудь видели свою тень ранним или поздним утром, то знаете, насколько длинной и искривленной она может быть в зависимости от положения Солнца. Приведенная в листинге 5.8 функция gltMakeShadowMatrix из библиотеки glTools принимает в качестве аргументов три точки плоскости, на которой вы же- лаете видеть тень (эти три точки не должны лежать на одной прямой), положение источника света и указатель на матрицу преобразования, которую строит эта функ- ция. Мы не будем углубляться в дебри линейной алгебры, но вам следует знать, что эта функция выводит коэффициенты уравнения плоскости, на которой будет распола- гаться тень, и с их помощью, учитывая положение источника света, строит матрицу преобразования. Если эту матрицу умножить на текущую матрицу наблюдения моде- ли, все нарисованные впоследствии объекты будут спроектированы на эту плоскость.
Глава 5 Цвет, материалы и освещение основы 271 Листинг 5.8. Функции, дающие матрицу “теневого преобразования" //По коэффициентам уравнения плоскости и положению источника света // создается матрица отбрасывания тени. Полученное значение // сохраняется в destMat void gltMakeShadowMatrix(GLTVector3 vPoints[3], GLTVector4 vLightPos, GLTMatrix destMat) { GLTVector4 vPlaneEquation; GLfloat dot; gltGetPlaneEquation(vPoints[0], vPoints[l], vPoints[2], vPlaneEquation); // Скалярное произведение направляющего вектора плоскости // и положения источника света dot = vPlaneEquation[0]*vLightPos[0] + vPlaneEquation[1]*vLightPos[1] + vPlaneEquation[2]*vLightPos[2] + vPlaneEquation[3]*vLightPos[3]; // Выполняется проектирование // Первый столбец destMat[0] = dot - vLightPos[0] * vPlaneEquation[0]; destMat[4] = O.Of - vLightPos[0] * vPlaneEquation[1]; destMat[8] = O.Of - vLightPos[0] * vPlaneEquation[2]; destMat[12] = O.Of - vLightPos[0] * vPlaneEquation[3] ; // Второй столбец destMat[l] = O.Of - vLightPosfl] * vPlaneEquation[0]; destMat[5] = dot - vLightPos[1] * vPlaneEquation[1]; destMat[9] = O.Of - vLightPos[1] * vPlaneEquation[2]; destMat[13] = O.Of - vLightPos[1] * vPlaneEquation[3] ; // Третий столбец destMat[2] = O.Of - vLightPos[2] * vPlaneEquation[0]; destMat[6] = O.Of - vLightPos[2] * vPlaneEquation[1] ; destMat[10] = dot - vLightPos[2] * vPlaneEquation[2] ; destMat[14] = O.Of - vLightPos[2] * vPlaneEquation[3]; // Четвертый столбец destMat[3] = O.Of - vLightPos[3] * vPlaneEquation(0]; destMat[7] = O.Of - vLightPos[3] * vPlaneEquation[1] ; destMat[11] = O.Of - vLightPos[3] * vPlaneEquation[2]; destMat[15] = dot - vLightPos[3] * vPlaneEquation[3] ; ) Пример тени Чтобы продемонстрировать использование функции, приведенной в листинге 5 8, подвесим самолет в воздухе над землей Источник света будет помещен выше и немного левее самолета С помощью клавиш со стрелками самолет можно вра- щать, при этом на земле будет соответствующим образом меняться его тень Результат выполнения соответствующей программы SHADOW показан на рис 5 41 В коде, приведенном в листинге 5 9, показано, как была создана матрица проекции тени Обратите внимание, что мы создали матрицу один раз в функции SetupRC и записали ее в глобальной переменной
272 Часть I. Классический OpenGL Рис. 5.41. Результат выполнения ---------------------------1 программы SHADOW Листинг 5.9. Установка матрицы проекции тени GLfloat lightPos[] = { -75.Of, 150.Of, -50.Of, O.Of }; // Матрица преобразования, дающая проекцию тени GLTMatrix shadowMat; // Эта функция выполняет необходимую инициализацию в контексте // визуализации. В данном случае она устанавливает и инициализирует // освещение на сцене void SetupRC{) { // Любые три точки на поверхности (заданы в порядке против // часовой стрелки) GLTVector3 points[3] = {{-30.Of, -149.Of, -20.Of }, {-30.Of, -149.Of, { 40.Of, -149.Of, 20.Of }, 20.Of }}; glEnable(GL_DEPTH_TEST); // Удаление скрытых поверхностей glFrontFace(GL_CCW); // Многоугольники с обходом против часовой стрелки // направлены вперед glEnable(GL_CULL_FACE); // Внутри самолета расчеты не производятся // Активизируется освещение glEnable(GL_LIGHTING); // Код настройки освещения и т.п. // Светло-синий фон glClearColor(0.Of, O.Of, l.Of, l.Of ); // Расчет матрицы проекции для рисования тени на земле gltMakeShadowMatrix(points, lightPos, shadowMat); }
Глава 5 Цвет, материалы и освещение основы 273 В листинге 5 10 приводится код визуализации системы “самолетик с тенью” Вна- чале мы рисуем землю Затем изображаем самолетик (как обычно), восстанавливаем матрицу наблюдения модели и умножаем ее на матрицу тени Так мы получаем искомую матрицу отбрасывания тени. После этого снова рисуем самолет. (Мы мо- дифицировали код, и теперь он принимает метку, сообщающую функции DrawJet проводить цветную или черно-белую визуализацию) Восстановив еще раз матрицу наблюдения модели, рисуем небольшую желтую сферу, аппроксимирующую положе- ние источника света Обратите внимание на то, что перед рисованием плоскости под самолетиком мы отключили проверку глубины Данный прямоугольник лежит в той же плоскости, в которой нарисованатснь, и мы должны гарантировать, что тень рисуется. Мы еще не обсуждали, что произойдет, ес- ли рисовать два объекта или плоскости в одном месте. Впрочем, мы рассматривали проверку глубины как средство, позволяющее определить порядок рисования объек- тов Если два объекта занимают одно и то же место, обычно показывается только последняя нарисованная фигура Иногда, впрочем, из-за слабого разрешения значе- ний по глубине возникает полная неразбериха — на экране вперемешку отображаются пиксели, принадлежащие разным объектам (z-fighting)' Листинг 5.10. Визуализация самолета и его тени // Вызывается для рисования сцены void RenderScene(void) { // Очищает окно текущим цветом очистки glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Рисуется земля; чтобы создать иллюзию глубины, мы вручную // создаем темно-зеленую тень на заднем плане glBegin(GL_QUADS); glColor3ub(0,32,0); glVertex3f(400.Of, -150.Of, -200 Of); glVertex3f(-400 Of, -150.Of, -200.Of); glColor3ub(0,255,0); glVertex3f(-400.Of, -150.Of, 200.Of); glVertex3f(400.Of, -150 Of, 200.Of); glEnd(); // Записывается состояние матрицы и выполняются повороты glPushMatrix(); // Рисуется самолет с новой ориентацией; перед его вращением //в нужном месте помещается источник света glEnable(GL_LIGHTING); glLightfv(GL_LIGHT0,GL_POSITION,lightPos); glRotatef(xRot, l.Of, O.Of, 0 Of); glRotatef(yRot, O.Of, l.Of, O.Of); DrawJet(FALSE); // Восстанавливается исходное состояние матрицы glPopMatrix(); //Мы готовы рисовать тень и землю // Вначале деактивизируется освещение и записывается состояние // проекции gIDisable(GL_DEPTH_TEST); gIDisable(GL_LIGHTING); glPushMatrix();
274 Часть I Классический OpenGL // Текущая матрица множится на матрицу проекции тени glMultMatrixf((GLfloat *)shadowMat); // Самолетик поворачивается в новом плоском пространстве glRotatef(xRot, l.Of, O.Of, O.Of); glRotatef(yRot, O.Of, l.Of, O.Of); II Чтобы обозначить рисовании тени, функции передается // значение TRUE DrawJet(TRUE); // Восстанавливается нормальная проекция glPopMatrix(); // Рисуется источник света glPushMatrix(); glTranslatef(lightPos[0],lightPos[1], lightPos[ 2 ]) ; glColor3ub(255,255,0); glutSolidSphere(5.Of,10,10); glPopMatrix() ; II Восстанавливаются переменные состояния освещения glEnable(GL_DEPTH_TEST); // Отображаются результаты glutSwapBuffers(); Возвращаясь к миру сфер Последний пример слишком большой, чтобы полностью приводить его исходный код В предыдущей главе мы рассматривали программу SPHEREWORLD, создающую трехмерный населенный мир с анимацией и движением камеры. В данной главе мы снова возвращаемся к миру сфер и добавляем к “жителям” (тору и сферам) источники света и свойства материалов. Кроме того, с помощью технологии наложения теней на плоскость мы создаем тени на земле! Как уже отмечалось, время от времени мы будем возвращаться к этому примеру, добавляя к нему изученные функции OpenGL На рис. 5.42, например, показан результат выполнения программы SPHERE WORLD с учетом материала, изученного в данной главе. Резюме В этой главе вводится несколько мощных и в каком-то смысле магических возмож- ностей OpenGL Вначале мы рассказали, как добавлять к трехмерным сценам цвет и плавное затенение Затем мы показали, как задавать один или несколько источников света и определять характеристики их освещения через рассеянный, диффузный и от- ражательный компоненты Мы объяснили, как соответствующие свойства материалов взаимодействуют с этими источниками света, и продемонстрировали несколько та- ких специальных эффектов, как добавление зеркальных бликов и сглаживание резких краев между граничащими треугольниками. Кроме того мы подробнее коснулись вопроса размещения источников света, созда- ния точечных источников света и манипулирования ими Представленная в данной главе высокоуровневая функция действий с матрицами делает генерацию плоских теней максимально простой
Глава 5. Цвет, материалы и освещение: основы 275 Рис. 5.42. Полностью раскрашенный и затененный мир сфер Справочная информация glColor Цель: Установить текущий цвет в режиме RGBA Включаемый файл: <gl. h> Варианты: void glColor3b (GLbyte red, GLbyte green, GLbyte blue); void glColor3d(GLdouble red, GLdouble green, GLdouble blue); void glColor3f(GLfloat red, GLfloat green, GLfloat blue); void glColor3i(GLint red, GLint green, GLint blue); void glColor3s(GLshort red, GLshort green, GLshort blue); void glColor3ub(GLubyte red, GLubyte green, GLubyte blue); void glColor3ui(GLuint red, GLuint green, GLuint blue); void glColor3us(GLushort red, GLushort green, GLushort blue); void glColor4b(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha); void glColor4d(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha); void glColor4f(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);
276 Часть I Классический OpenGL Описание: Параметры: red green blue alpha Что возвращает: См. также: void glColor4i(GLint red, GLint green, GLint blue, GLint alpha); void glColor4s(GLshort red, GLshort green, GLshort blue, GLshort alpha); void glColor4ub(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); void glColor4ui(GLuint red, GLuint green, GLuint blue, GLuint alpha); void glColor4us(GLushort red, GLushort green, GLushort blue, GLushort alpha); void glColor3bv(const GLbyte *v); void glColor3dv(const GLdouble *v); void glColor3fv(const GLfloat *v); void glColor3iv(const GLint *v); void glColor3sv(const GLshort *v); void glColor3ubv(const GLubyte *v); void glColor3uiv(const GLuint *v); void glColor3usv(const GLushort *v); void glColor4bv(const GLbyte *v); void glColor4dv(const GLdouble *v); void glColor4fv(const GLfloat *v); void glColor4iv(const GLint *v); void glColor4sv(const GLshort *v); void glColor4ubv(const GLubyte *v); void glColor4uiv(const GLuint *v); void glColor4usv(const GLushort *v); Устанавливает текущий цвет, задавая по отдельности красный, зеленый и синий компоненты цвета Некоторые функции также принимают компонент альфа Каждый компонент представляет диапазон интенсивности от нуля (0.0) до максимальной (1.0) Функции с суффиксом v принимают указатель на массив, задающий компоненты, причем все элементы массивы должны быть одного типа Если компонент альфа нс задан, он неявно считается равным 1.0 Если заданы не типы с плавающей запятой (float), диапазон от нуля до максимального значения, представляемого данным типом, отображается в диапазон величин с плавающей запятой от 0.0 до 1.0 Задает красный компонент цвета Задает зеленый компонент цвета Задает синий компонент цвета Задает альфа-компонент цвета Используется только в вариантах, принимающих четыре аргумента Указатель на массив красных, зеленых, синий значений и, возможно, альфа-значений Ничего glColorMaterial, glMaterial
Глава 5 Цвет, материалы и освещение основы 277 glColorMask Цель: Разрешить или запретить модификацию кодов цвета в буфере цветов Включаемый файл: <gl h> Синтаксис: void glColorMask(GLboolean bRed, GLboolean bGreen, GLboolean bBlue, GLboolean bAlpha); Описание: Позволяет разрешать или запрещать изменения отдельных компонентов цвета в буфере цветов (По умолчанию все разрешено ) Например, присваивая аргументу bAlpha значение GL_FALSE, мы запрещаем изменения компонента альфа Параметры: bRed (тип GLboolean) Задает, можно ли модифицировать красный компонент bGreen (тип GLboolean) Задает, можно ли модифицировать зеленый компонент bBlue (тип GLboolean) Задает, можно ли модифицировать синий компонент bAlpha (тип GLboolean) Задает, можно ли модифицировать альфа-компонент Что возвращает: Ничего См. также: glColor glColorMaterial Цель: Включаемый файл: Синтаксис: Описание: Параметры: face (тип GLenum) mode (тип GLenum) Что возвращает: См. также: Задать цвет материала согласно текущему цвету, установленному с помощью glColor <gl. h> void glColorMaterial(GLenum face, GLenum mode); Позволяет задавать свойства материалов, непосредственно не вызывая функцию glMaterial С помощью данной функции устанавливается, что свойства материала должны быть такими, чтобы цвет соответствовал заданному в функции glColor По умолчанию согласование цветов деактивизировано, чтобы активизировать его, нужно вызвать glEnable (GL_COLOR_MATERIAL) Чтобы позже отключить согласование цветов, вызывается функция glDisable(GL_COLOR_MATERIAL) Задает, должна ли передняя (GL_FRONT), задняя (gl_back) или обе (gl_front_and_back) стороны соответствовать текущему цвету Задасц какое свойство материала должно соответствовать текущему цвету Возможны такие значения GL_EMISSION, GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR ИЛИ GL_AMBIENT_AND_DIFFUSE Ничего glColor, glMaterial, glLight, glLightModel
278 Часть I. Классический OpenGL gIGetLight Цель: Получить информацию о текущих настройках источника света Включаемый файл: <gl.h> Варианты: void glGetLightfv(GLenum light, GLenum pname, GLfloat *params'); void glGetLightiv(GLenum light, GLenum pname, GLint *params); Описание: Используется, чтобы запросить текущие установки одного из восьми поддерживаемых источников света. Возвращаемые значения записываются по адресам, на которые указывает params В большинстве случаев это массив из четырех значений, содержащий RGBA-коды заданных свойств Параметры: light (тип GLenum) Задает источник света, о котором затребована информация. Допустимые значения — от 0 до gl_max_lights (минимум 8, как требуется в спецификации). Константы могут принимать значения ОТ GL_LIGHT0 ДО GL_LIGHT7 pname (тип GLenum) Задает, какое свойство источника света затребовано. Допустимы следующие значения: gl_ambient, gl_diffuse, gl_specular, GLJPOSITION, GL_SPOT_DIRECTION, gl_spot_exponent, GL_SPOT_CUTOFF, GL_CONSTANT_ATTENUATION, GL_LINEAR_ATTENUATION И GL_QUADRATIC_ATTENUATION params (тип GLfloat* или GLint*) Задает массив типа integer или float, представляющий возвращаемые значения. Эти значения имеют форму массива из четырех, трех или одного элемента. Значение возвращаемых констант расшифровывается в табл. 5.2 Что возвращает: Ничего См. также: glLight gIGetMaterial Цель: Получить текущие установки свойств материала Включаемый файл: <gl.h> Варианты: void glGetMaterialfv(GLenum face, GLenum pname, GLfloat *params); void glGetMaterialiv(GLenum face, GLenum pname, GLint *params); Описание: Используется для запроса текущих свойств материала передней или задней стороны объекта. Возвращаемые значения записываются по адресам, на которые указывают params. В большинстве случаев это массив из четырех значений, содержащий RGBA-коды заданных свойств
Глава 5 Цвет, материалы и освещение' основы 279 ТАБЛИЦА 5.2. Допустимые параметры освещения в функции glGetLight Свойство Значение возвращаемых значений GL_AMBIENT GL_DIFFUSE GL_SPECULAR GL_POSITION Четыре RGBA-компонента Четыре RGBA-компонента Четыре RGBA-компонента Четыре элемента, задающих положение источника света Первые три элемента задают положение источника света Если четвертый равен 1.0, в этой точке находится источник света В противном случае источник света является направленным, и все его лучи параллельны GL_SPOT_DIRECTION Три элемента, задающих направление света Вектор не нормирован и записан в координатах системы наблюдения GL_SPOT_EXPONENT Одно значение, представляющее коэффициент зеркального отражения GL_SPOT_CUTOFF Одно значение, представляющее угол отсечения источника пятна света GL_CONSTANT_ATTENUATION Одно значение, представляющее постоянное затухание света GL_LINEAR_ATTENUATION Одно значение, представляющее линейное затухание света GL_QUADRATIC_ATTENUATION Одно значение, представляющее квадратичное затухание света Параметры: face (тип GLenum) рпате (тип GLenum) Задает, свойства каких материалов касается запрос- передних (gl_front) иди задних (gl_back) Задает, какого свойства материала касается запрос. Возможны следующие значения: GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR, GL_EMISSION, GL_SHININESS И GL_COLOR_INDEXES params (тип GLint* или GLfloat*) Задает массив целых чисел или величин с плавающей запятой, представляющих возвращаемые значения. В случае свойств GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR И GL_EMISSION ЭТО четырехэлементный массив, содержащий RGBA-коды заданных свойств. Для свойства GL_SHININESS возвращается одно значение, представляющее коэффициент зеркального отражения. GL_COLOR_INDEXES возвращает массив из трех элементов, содержащий рассеянный, диффузный и зеркальный компоненты в форме индексов цвета. GL_COLOR_INDEXES применяется только при освещении с помощью индексов цвета Что возвращает: Ничего См. также: glMaterial
280 Часть I Классический OpenGL gILight Цель: Установить параметры одного из доступных источников света Включаемый файл: <gl.h> Варианты: void glLightf(GLenum light, GLenum pname, GLfloat param); void glLighti(GLenum light, GLenum pname, GLint param); void glLightfv(GLenum light, GLenum pname, const GLfloat*params); void glLightiv(GLenum light, GLenum pname, const GLint *params); Описание: Вызывается, чтобы установить параметры освещения для одного из восьми поддерживаемых источников света Первые два варианта функции требуют установки единственного параметра, задающего одно из следующих свойств. GL_SPOT_EXPONENT, GL_SPOT_CUTOFF, GL_CONSTANT_ATTENUATION, GL_LINEAR_ATTENUATION и GL_QUADRATIC_ATTENUATION Следующие два варианта применяются для задания параметров освещения и требуют массива из нескольких значений В их число входят GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR, GL_POSITION и GL_SPOT_DIRECTION Кроме того, в вариантах можно использовать параметры с одним значением, задавая вместо ♦params одноэлементный массив Параметры: light (тип GLenum) Задает, какой источник света модифицируется Возможны значения от 0 до GL_MAX_LIGHTS (минимум 8) Константы источников света нумеруются от GL_lighto до GL_LIGHT7 pname (тип GLenum) Задает, какой параметр освещения устанавливается с помощью данной функции. Полный список возможных параметров и их значений приведен в табл 5.2 param (тип GLfloat или GLint) Задает значение параметров, определяемых одной величиной В число этих параметров входят GL_SPOT_EXPONENT, GL_SPOT_CUTOFF, GL_CONSTANT_ATTENUATION, GL_LINEAR_ATTENUATION И GL_QUADRATIC_ATTENUATION. Эти параметры имеют смысл только для прожекторов params (тип GLfloat* ИЛИ GLint*) Что возвращает: Задает массив значений, полностью описывающих устанавливаемые параметры. Перечень параметров и их значений см. в табл. 5 2 Ничего См. также: glGetLight
Глава 5 Цвет, материалы и освещение- основы 281 gILightModel Цель: Установить параметры модели освещения, используемой OpenGL Включаемый файл: <gl.h> Варианты: void gILightModelf(GLenum pname, GLfloat param) void gILightModeli(GLenum pname, GLint param); void gILightModelfv(GLenum pname, const GLfloat ★params); void glLightModeliv(GLenum pname, const GLint ★params); Описание: Функцию можно использовать для задания параметров используемой OpenGL модели освещения. Установить можно один или все три параметра модели освещения GL_LIGHT_MODEL_AMBIENT применяется для установки по умолчанию рассеянного (окружающего) освещения. Изначально этот свет имеет значение (0 2, 0.2, 0 2, 1 0) в RGBA-кодах. Чтобы установить эту модель освещения можно использовать только последние два варианта функции, поскольку они принимают указатели на массив, который может содержать RGBA-коды Задавая параметр GL_LIGHT_MODEL_TWO_SIDE, мы указываем, освещаются ли обе стороны многоугольников По умолчанию освещается только передняя часть (определяется обходом) многоугольника, а в качестве свойств материала передней стороны используются величины, заданные функцией glMaterial Устанавливая параметр модели освещения равным GL_LIGHT_MODEL_LOCAL_VIEWER, МЫ меняем расчеты углов зеркального отражения при наблюдении по отрицательному направлению оси z или от начала координат системы, привязанной к глазу Наконец, с помощью GL_LIGHT_MODEL_COLOR можно задать, будет ли зеркальное освещение давать другой цвет (например, получат ли текстуры зеркальный свет) или все три компонента освещения комбинируются С GL_SINGLE_COLOR Параметры: pname (тип GLenum) Задает параметр модели освещения. Допустимы значения GL_LIGHT_MODEL_AMBIENT, GL_LIGHT_MODEL_LOCAL_VIEWER, GL_LIGHT_MODEL_TWO_SIDE И GL_LIGHT_MODEL_COLOR_CONTROL
282 Часть I Классический OpenGL param (тип GLfloat или GLint) Значение 0.0 параметра GL_LIGHT_MODEL_LOCAL_VIEWER указывает, что при расчете углов зеркального освещения считается, что направление наблюдения совпадает с отрицательным направлением оси z. Другие значения указывают, что наблюдение ведется от начала системы координат, связанной с глазом Значение 0.0 параметра GL_LIGHT_MODEL_TWO_SIDE указывает, что во всех расчетах освещения будут фигурировать только передние стороны многоугольников. Любые другие значения указывают, что задействованы передние и задние грани Установки данного параметра никак не влияют на точки, линии или растровые изображения. Параметр GL_LIGHT_MODEL_COLOR_CONTROL может иметь значение GL_SEPARATE_SPECULAR_COLOR или GL_SINGLE_COLOR params (тип GLfloat* или GLint*) В режиме GL_LIGHT_MODEL_AMBIENT ИЛИ GL_LIGHT_MODEL_LOCAL_VIEWER данный параметр указывает на массив целых значений или величин с плавающей запятой, только первый элемент которого используется для установки значения параметра. В режиме GL_LIGHT_MODEL_AMBIENT данный массив указывает на четыре значения, указывающие RGBA-компоненты рассеянного света Что возвращает: Ничего См. также: glLight, glMaterial glMaterial Цель: Установить параметры материала, используемые в модели освещения Включаемый файл: <gl.h> Варианты: void glMaterialf(GLenum face, GLenum pname, GLfloat param); void glMateriali(GLenum face, GLenum pname, GLint param); void glMaterialfv(GLenum face, GLenum pname, const GLfloat params) void glMaterialiv(GLenum face, GLenum pname, const GLint params); Описание: Используется для задания отражательных свойств материала, из которого сделаны многоугольники Свойства GL_AMBIENT, GL_DIFFUSE и GL_SPECULAR влияют на то, как отражаются соответствующие компоненты падающего света GL_EMISSION применяется для описания материалов, излучающих собственный света. Значение GL_SHININESS может меняться от 0 до 128, причем более высокие значения дают более яркие зеркальные блики на поверхности Чтобы задавать отражательные свойства материалов в режиме индексирования цвета применяется GL_COLOR_INDEXES
Глава 5 Цвет, материалы и освещение' основы 283 Параметры: face (тип GLenum) Задает, свойства каких сторон многоугольников задаются — передних (gl_front), задних (gl_back) или обеих (GL_FRONT_AND_BACK) pname (тип GLenum) Задает параметр материала (одно значение), устанавливаемый с помощью первых двух вариантов функции. В настоящее время единственным допустимым параметром является GL_SHININESS. Следующие два варианта, принимающие в качестве параметров массивы, могут задавать такие свойства материалов: GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR, GL_EMISSION, GL_SHININESS, GL_AMBIENT_AND_DIFFUSE ИЛИ GL_COLOR_INDEXES param (тип GLfloat или GLint) params (тип GLfloat* или GLint*) Что возвращает: Задает значение, которое присваивается параметру, заданному С помощью pname (GL_SHININESS) Задает массив значений типа float или integer, который содержит компоненты устанавливаемого свойства Ничего См. также: glGetMaterial, glColorMaterial, gILight, glLightModel gINormal Цель: Определить нормаль к поверхности для следующей вершины или набора вершин Включаемый файл: <gl.h> Варианты: void gINormal3b(GLbyte nx, GLbyte ny, GLbyte nz); void glNormal3d(GLdouble nx,GLdouble ny, GLdouble nz) ; void glNormal3f(GLfloat nx,GLfloat ny,GLfloat nz); void glNormal3i(GLint nx, GLint ny, GLint nz); void glNormal3s(GLshort nx,GLshort ny, GLshort nz); void glNormal3bv(const GLbyte *v); void glNormal3dv(const GLdouble *v); void glNormal3fv(const GLfloat *v); void glNormal3iv(const GLint *v); void glNormal3sv(const GLshort *v); Описание: Вектор нормали задает, какое направление считается верхом, и перпендикулярен поверхности многоугольника. Применяется для расчетов освещения и затенения. Если задавать единичный вектор, скорость визуализации увеличивается. OpenGL может автоматически преобразовывать нормали в единичные (нормировать их), если данную возможность активизировать С помощью glEnable (GL_NORMALI ZE);
284 Часть I Классический OpenGL Параметры: nx ny nz V Задаст величину вектора нормали в направлении х Задаст величину вектора нормали в направлении у Задаст величину вектора нормали в направлении z Задает массив из трех элементов, содержащий величины вектора нормали в направлениях х, у и z Что возвращает: Ничего См. также: glTexCoord. glVertex glShadeModel Цель: Установить затенение по умолчанию — плоское или гладкое Включаемый файл: <gl.h> Синтаксис: void glShadeModel(GLenum mode); Описание: Примитивы OpenGL всегда затеняются, но модель затенения может быть плоской (GL_flat) или гладкой (gl_smooth) В простейшем случае перед рисованием примитива, с помощью функции glColor задастся один цвет Данный примитив является сплошным и плоским (нс меняется) вне зависимости от затенения Если задать для всех вершин разные цвета, получаемое изображение будет зависеть от модели затенения При I ладком затенении цвет внутренних точек многоугольника интерполируется по цветам, заданным для вершин Это означает, что при переходе от одной вершины к другой цвет меняется Изменение цвета происходит в соответствии с изменением линии, соединяющей два соответствующих цвета в кубе цветов Если активизировано освещение, OpenGL выполняет другие расчеты, определяя правильные значения всех вершин При плоском затенении вся область, принадлежащая примитиву, закрашивается цветом последней вершины Единственным исключением является GL_POLYGON, когда во всей области используется цвет, заданный для первой вершины Параметры: mode (тип GLenum) Что возвращает: Задаст модель затенения GL_FLAT или GL_SMOOTH По умолчанию значение равно GL_SMOOTH Ничто См. также: glColor,glLight,gILightModel
ГЛАВА 6 Подробнее о цвете и материалах Ричард С Райт-мл ИЗ ЭТОЙ ГЛАВЫ ВЫ УЗНАЕТЕ Действие Функция Как смешивать цвета и объекты glBlendFunc, glBlendFuncSeparate, glBlendEquation, glBlendColor Как использовать альфа-тест для удаления фрагментов Как добавлять подсказки с помощью тумана Как визуализировать анимацию с размытием вследствие движения glAlphaFunc glFog glAccum Из предыдущей главы вы узнали, что для того, чтобы шар казался красным, недо- статочно просто установить красный цвет рисования. Свойства материалов и пара- метры освещения могут дать многое с точки зрения добавления реализма к гра- фическим объектам, но с моделированием реального мира связано еще несколько вопросов, которые мы и рассмотрим в данной главе. Для начала скажем, что многие эффекты достигаются за счет смешения цветов. Такие прозрачные объекты, как вит- ражное стекло или пластиковые бутылки, позволяют видеть объекты, расположенные за ними, но свет таких объектов смешивается с цветом прозрачного материала, через который вы смотрите. В OpenGL подобная прозрачность достигается за счет следу- ющей схемы, вначале рисуются фоновые объекты, а затем цвет объектов переднего плана смешивается с цветами, уже присутствующими в буфере цветов Чтобы овла- деть этой техникой, нужно рассмотреть четвертый компонент цвета, который мы пока не изучили, — величину альфа Смешение Вы уже знаете, что процедуры визуализации OpenGL при нормальных обстоятель- ствах помещают коды цвета в буфер цветов. Кроме того, вы знаете, что в буфер глубины помещаются параметры глубины каждого фрагмента. При отключенной (де- активизированной) проверке глубины новые коды цвета просто записываются поверх значений, уже присутствующих в буфере цветов Если включить (активизировать) проверку глубины, новые цветные фрагменты будут замещать существующие только
286 Часть I Классический OpenGL в том случае, если они располагаются ближе к ближней плоскости отсечения, чем значения, записанные в буфере Разумеется, все сказанное относится к нормальным обстоятельствам, и сформулированные правила теряют силу в тот момент, когда вы включаете смешение цветов OpenGL. glEnable(GL_BLENDING); При активизированном смешении цветов новый цвет объединяется с кодом цвета, уже присутствующим в буфере цветов Используя различные способы объединения цветов, можно получить интересные специальные эффекты. Объединение цветов Вначале мы должны ввести более строгую терминологию поступающих кодов цвета и кодов, уже присутствующих в буфере Код, уже записанный в буфере цвета, назы- вается целевым, и содержит три отдельных компонента (соответствующих красному, зеленому и синему цветам) и (необязательно) записанное значение альфа. Код цвета, поступающий в результате выполнения команд визуализации и взаимодействующий или не взаимодействующий с целевым цветом, называется исходным Исходный цвет также содержит три или четыре компонента (красный, зеленый, синий и, возможно, альфа). Объединение исходного и целевого цветов при смешении происходит соглас- но уравнению смешивания. По умолчанию уравнение смешения выглядит следу- ющим образом. Cf = (Cs * S) + (Cd * P) Здесь Cf — конечный цвет, Cs — исходный цвет, Cd — целевой цвет, a S и D — коэффициенты смешения источника и цели Коэффициенты смешения задаются с по- мощью следующей функции glBlendFunc(GLenum S, GLenum D); Видно, что S и D относятся к типу перечисляемых, а не физических величин, которые вы задаете непосредственно В табл 6.1 перечислены возможные значения смешивающих функций Нижние индексы обозначают “исходный цвет” (source — s), “целевой цвет” (destination — d) и “цвет смеси” (color — С, рассматривается ниже) R, G, В и А обозначают красный, зеленый, синий и альфа-компонент, соответственно Помните, что цвета представляются величинами с плавающей запятой, поэто- му их сложение, вычитание и даже умножение относятся к разрешенным операци- ям Табл 6 1 может показаться слишком запутанной, поэтому рассмотрим типичную функцию смешения. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); Приведенная функция сообщает OpenGL взять исходный (поступающий) цвет и умножить цвет (RGB-значения) на значение альфа. Полученный результат нуж- но прибавить к результату умножения целевого цвета на один минус альфа-фактор источника Предположим, например, что в буфере цвета уже имеется красный цвет (l.Of, O.Of, O.Of, O.Of) Это целевой цвет (Cd) Если поверх рисуется что-
Глава 6 Подробнее о цвете и материалах 287 ТАБЛИЦА 6.1. Функции смешивания OpenGL Коэффициенты Коэффициент Функция смешения (RGB) смешения (альфа) GL_ZERO (0,0, 0) GL_ONE (1-U) GL_SRC_COLOR (R_s, G_s, B3) GL_ONE_MINUS_SRC_COLOR (1,1, 1) - (Rs, Gs, Bs) GL_DST_COLOR (R_d, G_d, Bd) GL_ONE_MINUS_DST_COLOR (1,1,1) - (Rd, Gd, Bd) GL_SRC_ALPHA (A_s, A_s, As) GL_ONE_MINUS_SRC_ALPHA (1,1,1) - (As, A„, As) GL_DST_ALPHA (A_d, A_d, Ad) GL_ONE_MINUS_DST_ALPHA (1,1, 1) - (Ad, Ad, Ad) GL_CONSTANT_COLOR (R_c, G_c, Bc) GL_ONE_MINUS_CONSTANT_COLOR (1,1,1) - (Rc, Gc, Bc) GL_CONSTANT_ALPHA (A_c, A_c, Ac) GL_ONE_MINUS_CONSTANT_ALPHA (1,1, 1) - (Ac, Ac, Ac) GL_SRC_ALPHA_SATURATE (/,/,/)' 0 1 As 1-Aa 1-Ad 1- A, Ad 1 - Ad Ac 1 -Ac Ac 1-Ac 1 f = min(4sz, 1 - Ad) либо синего цвета с альфа-фактором 0.5 (O.Of, O.Of, l.Of, 0.5f), конечный цвет можно рассчитать следующим образом. Cd = целевой цвет = (1.0/, 0.0/, 0.0/, 0.0/) Cs = исходный цвет = (0.0/, 0.0/, 1.0/, 0.5/) S = альфа-фактор источника -- 0.5 D = единица минус альфа-фактор источника = 1.0 — 0.5 = 0.5 Теперь уравнение Cf = (Cs * S) + (Cd * D) превращается в такое: Cf = (синий * 0.5) -I- (красный * 0.5) Конечный цвет является масштабированной комбинацией исходного кода красно- го цвета и поступающего кода синего цвета. Чем выше входной альфа-фактор, тем больше поступающего и меньше старого цвета будет в конечном цвете. Описанная функция смешения часто используется для создания иллюзии прозрач- ного объекта, за которым находится что-то непрозрачное. Тем не менее данная техно- логия требует, чтобы вначале вы нарисовали фоновый объект (или объекты), а затем поверх них изображали прозрачные объекты. Эффект может быть достаточно впе- чатляющим. Например, в программе REFLECTION мы использовали прозрачность для создания иллюзии отражения от зеркальной поверхности Вначале мы создали
288 Часть I. Классический OpenGL Рис. 6.1. Использование смешивания для получения ложного эффекта отражения крутящийся тор, вокруг которого вращается сфера (подобно сцене, полученной в при- мере “мир сфер” из предыдущей главы). Ниже тора и сферы мы поместили пол из отражающих плиток. Результат выполнения данной программы показан на рис. 6.1, а код, отвечающий за рисование, приводится в листинге 6.1. Листинг 6.1. Функция визуализации программы REFLECTION /////////////////////////////////////////////////////////////////// // Вызывается для рисования сцены void RenderScene(void) { // Очищаем окно текущим цветом очистки glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glPushMatrix(); // Источник света помещается под полом и освещает // "отраженный" мир glLightfv(GL_LIGHTO, GL_POSITION, fLightPosMirror); glPushMatrix!); gIFrontFace(GL_CW); // Зеркально отображается геометрия, инвертируется // ориентация glScalef(l.Of, -l.Of, l.Of); DrawWorld(); gIFrontFace(GL_CCW); glPopMatrix();
Глава 6 Подробнее о цвете и материалах 289 ТАБЛИЦА 6.2. Доступные режимы (уравнения) смешивания Режим Функция GL_FUNC_ADD (режим по умолчанию) GL_FUNC_SUBTRACT GL_FUNC_REVERSE_SUBTRACT GL_MIN GL_MAX С/= (Cs * S) + (Cd * £>) Cf = (Cs * S) - (Cd * D) Cf = (Cd * £>) - (Cs * S) Cf = mm(Cs, Cd) Cf = max(Cs, Cd) // Над "отраженными" объектами рисуется прозрачная земля glDisable(GL_LIGHTING); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); DrawGround(); glDisable(GL_BLEND); glEnable(GL_LIGHTING); // Восстанавливается правильное освещение и правильно // рисуется мир glLightfv(GL_LIGHT0, GL_POSITION, fLightPos); DrawWorld(); glPopMatrix(); // Переключает буферы glutSwapBuffers(); ) Суть алгоритма, порождающего искомый эффект, заключается в рисовании сцены наоборот. Чтобы нарисовать сцену, мы используем функцию DrawWorld (), но чтобы изобразить ее наоборот, мы масштабируем все с коэффициентом — 1 (инверсия отно- сительно оси у), меняем обход многоугольников на обратный и помещаем источник света под наблюдателем. После того как перевернутый мир нарисован, мы изобра- жаем землю, однако с помощью смешения пол, расположенный поверх обращенного мира, становится прозрачным. Наконец, отключаем смешение, помещаем источника света над наблюдателем и рисуем мир нормально. Изменение уравнения смешивания Приведенное ранее уравнение смешения С/ = (Cs * S) + (Cd * D) является уравнением no умолчанию. В действительности можно выбирать из пяти различных уравнений смешения, перечисленных в табл. 6 2 и задаваемых с помощью следующей функции: void glBlendEquation(GLenum mode); Если гибкости glBlendFunc недостаточно, можно применять следующую функцию void glBlendFuncSeparate(GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha);
290 Часть I Классический OpenGL Если glBlendFunc задает функции смешивания исходного и целевого RGBA- кодов, то glBlendFuncSeparate позволяет по отдельности задавать функции сме- шивания для компонентов RGB и альфа Наконец, как показано в табл 6.1, значения GL_CONSTANT_COLOR, GL_ONE_MINUS _CONSTANT_COLOR, GL_CONSTANT_ALPHA и GL_ONE_MINUS_CONSTANT_ALPHA ПОЗВО- ЛЯЮТ вводить в уравнение смешивания постоянный цвет-компонент Этот постоянный цвет изначально (по умолчанию) является черным (0. Of, O.Of, O.Of, O.Of), но его можно изменить с помощью приведенной ниже функции void glBlendColor(GLclampf red, GLclampf green, Glclampf blue, GLclampf alpha); Сглаживание Еще одной сферой, где применяются возможности смешения OpenGL, является сгла- живание В большинстве случаев отдельные визуализированные фрагменты отоб- ражаются в отдельные пиксели экрана компьютера Эти пиксели квадратные (или прямоугольные), и обычно можно довольно отчетливо видеть границу раздела двух цветов Эти неровности притягивают внимание и разрушают иллюзию естественного изображения Они являются страшными предателями, сообщающими, что изображе- ния С1енерированы компьютером, а ведь во многих задачах визуализации (в част- ности, в играх, тренажерах или художественных произведениях) желательно полу- чить максимальный реализм1 На рис 6 2 показан результат выполнения программы SMOOTHER, а на рис 6 3 приведено увеличенное изображение участка ломаной и нескольких точек-звезд, где отчетливо видны зазубренные края Чтобы справиться с проблемой зазубренных краев примитивов, в OpenGL ис- пользуется смешение и объединение цвета фрагмента с целевым цветом пикселя и его окружения По сути, цвета пикселей слегка размываются на соседние пиксели вдоль краев примитивов Включить сглаживание достаточно просто Вначале нужно активизировать сме- шение и указать, что функция смешения та же, что использовалась в предыдущем разделе для имитации прозрачности glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); Кроме того, нужно убедиться, что уравнение смешения задано с параметром GL_ADD, но поскольку это значение по умолчанию и оно является наиболее рас- пространенным уравнением смешения, мы не будем приводить соответствующий код (кроме того, изменение уравнения смешения поддерживается не во всех реализациях OpenGL) После активизации смешения и выбора подходящей функции смешения, с помощью вызова glEnable можно выбрать сглаживание точек, линий и/или мно- гоугольников (любых сплошных примитивов) glEnable(GL_POINT_SMOOTH); // Сглаживание точек glEnable(GL_LINE_SMOOTH); // Сглаживание линий glEnable(GL_POLYGON_SMOOTH); // Сглаживание краев многоугольников
Глава 6. Подробнее о цвете и материалах 291 Рис. 6.2. Результат выполнения программы SMOOTHER Рис. 6.3. Увеличенное изображение зубцеобразных краев
292 Часть I. Классический OpenGL Рис. 6.4. Зубцеобразных краев больше нет! Однако следует отметить, что параметр GL_POLYGON_SMOOTH поддерживается не во всех реализациях OpenGL. В листинге 6.2 приведен код из программы SMOOTHER, реагирующей на метку во всплывающем меню, которое позволяет поль- зователю переключаться между режимами визуализации с сглаживанием и без него. При запуске программы с активизированным сглаживанием точки и линии кажутся более гладкими (смазанными). На рис. 6.4 показана та же область, что и на рис. 6.3, но теперь с несколько уменьшенной зазубренностью краев. Листинг 6.2. Переключение между визуализацией с сглаживанием и обычной /////////////////////////////////////////////////////////////////// // В ответ на выбор позиции меню должным // образом устанавливаются флаги void ProcessMenu(int value) { switch(value) { case 1: // Включается сглаживание и дается подсказка обеспечить // наивысшее качество glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); glEnable(GL_POINT_SMOOTH); glHint(GL_POINT_SMOOTH_HINT, GL_NICEST); glEnable(GL_LINE_SMOOTH); glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); glEnable(GL_POLYGON_SMOOTH); glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
Глава 6 Подробнее о цвете и материалах 293 break; case 2: // Выключается смешивание и сглаживание glDisable(GL_BLEND); glDisable(GL_LINE_SMOOTH); glDisable(GL_POINT_SMOOTH); glDisable(GL_POLYGON_SMOOTH); break; default: break; } glutPostRedisplay(); // Инициируется перерисовывание изображения 1 Особое внимание обратите на вызовы функции glHint, обсуждавшейся в главе 2, “Используя OpenGL”. Существует множество алгоритмов и подходов, позволяющих получить сглаженные примитивы. В конкретной реализации OpenGL может выби- раться любой из этих подходов; возможна поддержка даже двух вариантов' OpenGL можно запросить, поддерживается ли несколько алгоритмов сглаживания, и выбрать из них самый быстрый (gl_fastest) или наиболее точный (gl_nicest). Множественная выборка Одно из наибольших преимуществ сглаживания состоит в том, что оно может дать более естественный и реалистичный внешний вид визуализированных объектов Сглаживание точек и линий поддерживается широко, но, к сожалению, сглажива- ние многоугольников доступно не на всех платформах Однако даже если параметр GL_POLYGON_SMOOTH доступен, как средство сглаживания всей сцены он не так удо- бен, как можно подумать Поскольку данная техника основана на операции смешения, нужно вначале упорядочить все примитивы по глубине! Печально! Самым свежим добавлением к OpenGL, призванным решить эту проблему, явля- ется множественная выборка (multisampling). Если эта возможность поддерживается (это особенность OpenGL 1.3), к буферу кадров добавляется дополнительный буфер, включающий коды цвета, глубины и трафарета Все примитивы дискретизуются (вы- бираются) несколько раз на каждый пиксель, и результаты записываются в этом буфе- ре. При каждом обновлении пикселя эти выборки разрешаются в одно значение, по- этому с точки зрения программиста данное значение находится автоматически и вы- числяется “за сценой”. Естественно, необходима дополнительная память, и обработ- ка не проходит без снижения производительности, кроме того, некоторые реализации могут не под держивать множественную выборку во многих контекстах визуализации Чтобы получить множественную дискретизацию, вначале нужно создать контекст визуализации, поддерживающий буфер кадра множественных выборок. Для разных платформ реализация этого принципа отличается, но пока вы не овладеете другими средствами (см главы, посвященные конкретным операционным системам), восполь- зуйтесь битовым полем GLUT (GLUT_MULTISAMPLE), позволяющим запрашивать эту возможность Например, чтобы получить буфер кадров с множественной выборкой, полноцветным представлением, двойной буферизацией и записью кода глубины, мож- но вызвать следующую функцию.
294 Часть I. Классический OpenGL Рис. 6.5. Увеличенные изображения сцены с нормальной визуализацией (сле- ва) и множественной выборкой (справа) glut!nitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH | GLUT_MULTISAMPLE); Чтобы включать и выключать множественную дискретизацию, используется ком- бинация команды glEnable/glDisable с токеном GL_MULTISAMPLE: glEnable(GL_MULTISAMPLE); или gIDisable(GL_MULTISAMPLE); Следующая программа, MULTISAMPLE, является продолжением “мира сфер” из предыдущей главы, но выбрана и активизирована множественная выборка. На рис. 6.5 показаны отличия увеличенных фрагментов двух программ. Можно ви- деть, что множественная выборка действительно помогает сгладить границы гео- метрических объектов на изображении справа, что дает более приятный внешний вид визуализированного объекта. Следует сделать еще одно важно замечание, касающееся множественной выборки: когда эта возможность активизирована, то активизированные возможности сглажива- ния точек, линий и многоугольников игнорируются. Это означает, что сглаживание точек и линий нельзя одновременно использовать с множественной выборкой. В кон- кретной реализации OpenGL точки и линии могут выглядеть лучше при включенном сглаживании, чем при активизированной множественной выборке. Чтобы использо- вать это, можете отключать множественную выборку перед рисованием точек и ли- ний, а затем снова включать ее при изображении других сплошных геометрических объектов. В качестве грубой схемы подобного подхода можно привести следующий код: gIDisable(GL_MULTISAMPLE); glEnable(GL_POINT_SMOOTH); // Рисуются гладкие точки // ... gIDisable(GL_POINT_SMOOTH); glEnable(GL_MULTISAMPLE);
Глава 6 Подробнее о цвете и материалах 295 СОРТИРОВКА ПО СОСТОЯНИЯМ Включение и выключение различных возможностей OpenGL меняет внутреннее со- стояние драйвера. Данные изменения состояния могут быть “дорогими” с точки зрения производительности визуализации Довольно часто программисты, которых заботят вопросы производительности, сортируют все команды визуализации, чю- бы геометрические объекты, требующие одного состояния, рисовались вместе В частности, такая сортировка по состояниям является одной из распространенных технологий улучшения скорости визуализации в играх Буферы множественной выборки используют RGB-коды фрагментов по умолча- нию и не включают альфа-компонент цветов Чтобы изменить это поведение, вызы- вается функция glEnable с одним из следующих аргументов. • GL_SAMPLE_ALPHA_TO_COVERAGE — Используется альфа-компонент • GL_SAMPLE_ALPHA_TO_ONE — Альфа устанавливается равным 1 и используется • GL_SAMPLE_COVERAGE — Используется значение, установленное с помощью функ- ции glSampleCoverage После активизации GL_SAMPLE_COVERAGE функция glSampleCoverage позволя- ет задавать конкретное значение. Затем к этому значению и коду процентного охвата фрагмента (побитово) применяется операция логическою И. void glSampleCoverage(GLclampf value, GLboolean invert); Детали действия операция множественной выборки в спецификации задаются нестрого, и точные результаты ее применения мшут зависоь от реализации Туман Другим легким в использовании специальным эффектом, коюрый поддерживает OpenGL, является туман. После завершения всех расчетов по цвету OpenGL поз- воляет смешивать заданный цвет тумана с цветом геометрических объектов. Доля цвета тумана, смешиваемою с цветом объектов, зависит ог расстояния этих объектов до начала координат (камеры) Таким образом удается создавать трехмерные сце- ны, имитирующие присутствие гумана Туман может быть полезным для медленного затенения объектов, “исчезающих” в фоне, что позволяет создавать мощные и реали- стичные подсказки о глубине (depth cue) На рис. 6 6 показан результат выполнения программы FOGGED, как видите, это все тот же мир сфер с включенным туманом В листинге 6 3 показано несколько строк кода, добавление которых в функцию SetupRC дает изображенный эффект Листинг 6.3. Установка тумана в мире сфер // Сероватый фон glClearColor(fLowLight[0],fLowLight[1],fLowLight[ 2 ] ,fLowLight[3]); // Установка параметров тумана glEnable(GL_FOG); // Включается туман glFogfv(GL_FOG_COLOR, fLowLight); // Цвет тумана соответствует фону
296 Часть I. Классический OpenGL Рис. 6.6. Мир сфер с туманом glFogf(GL_FOG_START, 5.0 f); // Насколько далеко // начинается туман glFogf(GL_FOG_END, 30.Of); // Насколько далеко // заканчивается туман glFogi(GL_FOG_MODE, GL_LINEAR); // Какое уравнение тумана // используется Для включения/выключения тумана применяется следующие функции: glEnable/glDisable(GL_FOG); Для изменения параметров тумана (поведения тумана) используется функция glFog. Существует несколько вариантов этой функции. void glFogi(GLenum pname, GLint param); void glFogf(GLenum pname, GLfloat param); void glFogiv(GLenum pname, GLint* params); void glFogfv(GLenum pname, GLfloat* params); Первым из проиллюстрированных вариантов является следующий: glFogfv(GL_FOG_COLOR, fLowLight); // Цвет тумана соответствует фону При использовании с параметром GL_FOG_COLOR данная функция ожидает указа- тель на массив величин с плавающей запятой, который задает цвет тумана. В данном случае мы использовали туман цвета фона. Если цвет тумана не соответствует цве- ту фона (подобного строгого требования не существует!), то объекты, спрятавшиеся в тумане, становятся силуэтами цвета тумана, проявляющимися на заданном фоне.
Глава 6 Подробнее о цвете и материалах 297 ТАБЛИЦА 6.3. Три уравнения тумана, поддерживаемых OpenGL Режим тумана GL_LINEAR GL_EXP GL_EXP2 Уравнение тумана f = (end - c)/(end - start) J = exp(—d*c) / = exp(-(d * c)2) Следующие две строки позволяют задать, насколько должен удалиться объект, чтобы на него начал действовать туман и объект полностью скрылся в тумане (при- обретает цвет тумана) glFogf(GL_FOG_START, 5.Of); // Насколько далеко начинается туман glFogf(GL_FOG_END, 30.Of); // Насколько далеко заканчивается туман Параметр GL_FOG_START задает, как далеко от глаза начинает проявляться эффект тумана, a GL_FOG_END — это расстояние от глаза, когда цвет тумана полностью по- давляет цвет объекта Переход от начала к концу тумана контролируется уравнением тумана, которое в данном случае задано с параметром GL_LINEAR. glFogi(GL_FOG_MODE,GL_LINEAR);//Какое уравнение тумана используется Согласно данному уравнению, рассчитывается “коэффициент тумана”, который меняется от 0 до 1 на заданном участке (от начала тумана до конца) OpenGL поддерживает три уравнения тумана, которые приведены в табл 6 3 • GL_LINEAR, GL_EXP И GL_EXP2 В этих уравнениях с — расстояние фрагмента от наблюдателя, encl — рас- стояние GL_FOG_END, a start — расстояние GL_FOG_START Значение с/ представ- ляет собой плотность тумана Как правило, плотность тумана задается с помо- щью функции glFogf glFogf(GL_FOG_DENSITY, 0.5f); На рис 6 7 показано, как уравнение тумана и плотность тумана влияют на пере- ход исходного цвета фрагмента в цвет тумана GL_LINEAR задает простую линейную прогрессию, а уравнения GL_EXP и GL_EXP2 представляют нелинейные переходы цветов Изменение плотности при выбранном линейном уравнении (GL_LINEAR) ни- как не влияет на результат, а вот две другие кривые в общем случае притягиваются вниз с увеличением плотности Приведенные графики, например, приблизительно соответствуют плотности 0,5 Расстояние до фрагмента от наблюдателя можно рассчитать двумя способами Пер- вый вариант — задействовать параметр GL_FRAGMENT_DEPTH и извлечь код глубины фрагмента Включать и выключать данную возможность можно с помощью glFog glFogi(GL_FOG_COORD_SRC, GL_FRAGMENT_DEPTH); Второй вариант - - интерполировать глубину тумана между вершинами, этот метод расчета глубины используется по умолчанию Он можег быть немного быстрее, но также способен ухудшить качество изображения Подобно схеме расчоа освещения на основе вершин, для данного метода справедливо правило чем больше геометри- ческих объектов, тем лучшие он даст результаты Как всегда, данный режим можно задать явно, используя функцию glFog glFogi(GL_FOG_COORD_SRC, GL_FOG_COORD);
298 Часть I Классический OpenGL ТАБЛИЦА 6.4. Операции накопления OpenGL Операция Описание GL_ACCUM GL_LOAD GL_RETURN GL_MULT GL_ADD Масштабирует значения буфера цвета с указанным коэффициентом и прибавляет их к текущему содержимому буфера накопления Масштабирует значения буфера цвета с указанным коэффициентом и замещает ими текущее содержимое буфер накопления Масштабирует коды цвета в буфере накопления с указанным коэффициентом, а затем копирует значения в буфер цвета Масштабирует коды цвета в буфере накопления с указанным коэффициентом и записывает результат в буфере накопления Масштабирует коды цвета в буфере накопления с указанным коэффициентом и прибавляет результат к текущему содержимому буфера накопления Буфер накопления Помимо буферов цвета, трафарета и глубины OpenGL поддерживает буфер накопле- ния Этот буфер позволяет визуализировать буфер цвета, чтобы затем нс отображать результаты в окне, а скопировать содержимое буфера цвета в буфер накопления Поддерживается несколько типов операций копирования, позволяющих последова- тельно различными способами смешивать содержимое буфера цвета с накопленным содержимым буфера накопления (отсюда и название') После того как вы получи- те изображение, содержимое буфера накопления можно скопировать в буфер цвета и отобразить результаты, переключив буферы Поведением буфера накопления управляет следующая функция void glAccum(GLenumm op, GLfloat value); Первый параметр задаст, какую операцию накопления следует использовать, а вто- рой — это значение с плавающей запятой, используемое для масштабирования опе- рации Список поддерживаемых операций накопления приводится в табл 6 4
Глава 6. Подробнее о цвете и материалах 299 Рис. 6.8. Летящая сфера с размыванием вследствие движения Из-за больших объемов информации, копируемой и обрабатываемой при действи- ях с буфером накопления, описанная возможность редко используется в приложениях реального времени. При визуализации не в реальном времени OpenGL может давать поразительные эффекты, неожиданные для программных интерфейсов реального вре- мени. Например, сцену можно визуализировать несколько раз, каждый раз перемещая точку зрения на долю пикселя. Накапливая такие множественные визуализации, мы размываем резкие края и можем получить полностью сглаженную сцену, качество которой гораздо выше того, что можно получить с помощью множественной выбор- ки. Эффект размывания можно также использовать, чтобы смазать фон или передний план изображения, а затем визуализировать объекты в фокусе, имитируя эффекты наведения резкости камеры. В приведенной на компакт-диске программе MOTIONBLUR демонстрируется еще один способ использования буфера накопления для создания эффекта размывания вследствие движения (motion blur). Движущаяся сфера последовательно рисуется в нескольких местах, при этом каждый раз она заносится в буфер накопления с посте- пенно уменьшающимися весовыми коэффициентами. В результате получаем яркую красную сферу со стелющимся следом (затухающим образом этой сферы). Результат выполнения данной программы показан на рис. 6.8. В листинге 6.4 приведена функция DrawGeometry, рисующая все геометрические объекты сцены. Затем данную функцию многократно вызывает функция Render- Scene, которая накапливает результаты вызовов в буфере накопления. По заверше- нии накопления с помощью приведенных ниже строк содержимое буфера передается в буфер цвета и выполняется переключение буферов.
300 Часть I Классический OpenGL glAccum(GL_RETURN, l.Of); glutSwapBuffers(); Листинг 6.4. Использование буфера накопления для создания размывания вследствие движения /////////////////////////////////////////////////////////////////// // Рисуется земля и вращающаяся сфера void DrawGeometry(void) { // Очищаем окно текущим цветом очистки glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glPushMatrix(); DrawGround(); // Помещает движущуюся сферу glColor3f(l.Of, O.Of, O.Of); glTranslatef(O.Of, 0.5f, -3.5f); glRotatef(-(yRot * 2.Of), O.Of, l.Of, O.Of); glTranslatef(l.Of, O.Of, O.Of); glutSolidSphere(0.If, 17, 9); glPopMatrix() ; } /////////////////////////////////////////////////////////////////// // Вызывается для рисования сцены. Мир рисуется несколько раз, // при этом каждый получаемый кадр смешивается с предыдущим. // Для создания иллюзии размывания вследствие движения, //на каждом шаге меняется текущий поворот void Renderscene(void) { GLfloat fPass; GLfloat fPasses = 10.Of; // Текущий поворот на несколько градусов назад yRot = 35.Of; for(fPass = O.Of; fPass < fPasses; fPass += l.Of) { yRot += . 75f; //l.Of / (fPass+1.Of); // Рисуется сфера DrawGeometry(); // Результат накапливается в заднем буфере if(fPass == O.Of) glAccum(GL_LOAD, 0.5f); else glAccum(GL_ACCUM, 0.5f * (l.Of / fPasses)); } // Содержимое буфера накопления копируется в буфер цвета // и выполняется переключение буферов glAccum(GL_RETURN, l.Of); glutSwapBuffers();
Глава 6 Подробнее о цвете и материалах 301 Наконец, вы нс должны забыть запросить использование буфера накопления, на- страивая контекст визуализации OpenGL (подробнее о том, как выполнить это на вашей платформе, рассказывается в главе, посвященной выбранной операционной системе) Кроме того, GLUT также поддерживает использование буфера накопления, для чего нужно ввести параметр GLUT_ACCUM в функцию glutlnitDisplayMode glutlnitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH | GLUT_ACCUM); Другие операции с цветом Смешение является мощной возможностью OpenGL, включающей мириады алгорит- мов создания спецэффектов Помимо прямой поддержки смешения, тумана и буфера накопления OpenGL также поддерживает несколько других средств манипуляции ко- дами цвета и фрагментами, записываемыми в буфер цвета Маскировка цвета После того как окончательный цвет рассчитан и готовится его запись в буфер цве- та, OpenGL позволяет замаскировать один или несколько каналов цвета, используя функцию glColorMask void glColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); Параметры этой функции соответствуют красному, зеленому, синему и альфа- каналу Передача функции значения GL_TRUE разрешает, а значения GL_FALSE — запрещает запись в соответствующем канале Логические операции с цветом Многие двухмерные программные интерфейсы приложений позволяют выполнить бинарные логические операции с исходным и целевым цветами OpenGL также поддерживает такие типы двухмерных операций, для этого применяется функ- ция glLogicOp- void glLogicOp(GLenum op); Режимы логических операций перечислены в табл 6 5 Логические опера- ции не активизированы по умолчанию, и для управления ими (как и боль- шинством состояний) используются функции glEnable/glDisable с аргументом GL_COLOR_LOGIG_OP Например, чтобы включить логические операции, применяет- ся следующая команда glEnable(GL_COLOR_LOGIC_OP); Альфа-тест Ачьфа-тест позволяет указать OpenGL, что необходимо отбрасывать фрагменты, альфа которых нс удовлетворяет указанному условию Отброшенные фрагменты не
302 Часть I Классический OpenGL ТАБЛИЦА 6.5. Побитовые логические операции с цветом Значение аргумента Операция GL CLEAR 0 GL AND s & d GL AND REVERSE s & ~d GL_COPY s GL_AND_INVERTED ~s&d NOOP d XOR s xor d OR s | d NOR ~(s | d) GL_EQUIV ~(s xor d) GL_INVERT ~d GL_OR_REVERSE s 1 ~d GL_COPY_INVERTED ~s GL_OR_INVERTED ~s | d GL_NAND ~(s&d) SET все 1 ТАБЛИЦА 6.6. Функции сравнения альфа-теста Константа Функция сравнения GL_NEVER GL_ALWAYS GL_LESS GL_LEQUAL GL_EQUAL GL_GEQUAL GL_GREATER GL_NOTEQUAL Никогда не пропускать Всегда пропускать Пропускать, если фрагмент меньше эталонного значения Пропускать, если фрагмент меньше или равен эталонному значению Пропускать, если фрагмент равен эталонному значению Пропускать, если фрагмент больше или равен эталонному значению Пропускать, если фрагмент больше эталонного значения Пропускать если фрагмент не равен эталонному значению записываются в буфер цвета, глубины, трафарета или накопления Данная возмож- ность позволяет повышать производительность, отбрасывая значения, которые в про- тивном случае могли быть записаны в буферы, и удалять из буфера глубины гео- метрические объекты, которые нельзя увидеть в буфере цвета (из-за очень малых значений альфа) Параметр альфа-теста и функция сравнения задаются с помощью функции glAlphaFunc void glAlphaFunc(GLenum func, GLclampf ref); Эталонное значение oi раничсно диапазоном от 0 0 до 1 0, а функцию сравне- ния можно задавать любой из констант, перечисленных в табл 6 6 Чтобы включать и выключать альфа-тест, применяются функции glEnable/glDisable с аргументом GL_ALPHA_TEST Отмстим, что поведение этой функции подобно поведению функции glDepthFunc, рассмотренной в главе 3
Глава 6 Подробнее о цвете и материалах 303 Сглаживание Сглаживание (dithering) — это простая (в принципе) операция, позволяющая отобра- жать системы с небольшим числом дискретных цветов, имитируя изображение более широкого диапазона цветов Например, серый цвет можно сымитировагь, отображая на экране смесь белых и черных точек Отобразив больше белых точек, чем чер- ных, получим светло-серый цвет, а увеличив относительное число черных точек — темно-серый. Если глаз наблюдателя расположен достаточно далеко от дисплея, вы не сможете рассмотреть отдельные точки, и благодаря эффекту (оптического) смеше- ния создастся иллюзия смеси цветов Описанная техника полезна при отображении систем, поддерживающих только 8 или 16 бит информации о цвете В конкретной ре- ализации OpenGL может быть запрограммирован свой алгоритм сглаживания, однако в любом случае он может существенно улучшить качество изображений маломощных систем По умолчанию сглаживание отключено, и им можно управлять с помощью glEnable/glDisable и константы GL_DITHER glEnable(GL_DITHER); // Активизация сглаживания В мощных системах отображения с большой цветовой разрешающей способно- стью сглаживание нс требуется, и данная технология может вообще нс реализовы- ваться с целью экономии производительности Резюме В данной главе мы рассмотрели цвет не только с позиции простых эффектов зате- нения и освещения Было показано, как с помощью смешения создать прозрачные и отражающие поверхности и создавать сглаженные точки, линии и многоугольники, используя возможности смешения и множественной выборки OpenGL Также был представлен буфер накопления и разобран по меньшей мере один специальный эф- фект, для создания которого применяется этот буфер Наконец, было показано, как OpenGL поддерживает такие функции манипуляции с цветом, как маскировка цветов, побитовые логические операции с цветом и сглаживание и как использовать альфа- тест для отбрасывания ненужных фрагментов В следующей главе от цветов, теней и смешения мы перейдем к операциям, включающим данные реальных изображений В папке, соответствующей данной 1лавс нс компакт-диске, вы найдете обнов- ленный вариант мира сфер из главы 5 Изучая исходный код, вы увидите, как мы реализовали множество юхнологий, описанных в данной ьчаве, в мире с туманом, отбрасыванием частично прозрачных теней на землю и полным сглаживанием всех геометрических объектов композиции
304 Часть I Классический OpenGL Справочная информация glAccum Цель: Рассчитать в буфере накопления значения пикселей Включаемый файл: <GL/gl.h> Синтаксис: void glAccum(GLenum op, GLfloat value); Описание: Работает в буфере накопления. Исключая случай применения с параметром GL_RETURN, все коды цвета масштабируются согласно параметру value и суммируются со значением в буфере накопления или записываются в этот буфер. При применении с параметром GL_RETURN коды цвета в буфере накопления масштабируются согласно параметру value и записывается в текущем буфере цвета Параметры: ор (тип GLenum) Задействованная функция накопления Допустимые значения перечислены в табл 6.4 value (тип GLfloat) Что возвращает: Дробная часть выполняемого накопления Ничего См. также: glClearAccum gIBIendColor Цель: Установить постоянный цвет, с которым выполняется смешение и который (необязательно) используется в уравнении смешения Включаемый файл: <gl/gl.h> Синтаксис: void glBlendcolor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); Описание: Устанавливает один цвет смеси, используемый при применении одного постоянного цвета в уравнении смешения. Эти параметры смешивания равны GL_CONSTANT_COLOR, GL_ONE_MINUS_CONSTANT_COLOR, GL_CONSTANT_ALPHA И GL_ONE_MINUS_CONSTANT_ALPHA Параметры: red (тип GLClampf) green (тип GLClampf) blue (тип GLClampf) alpha (тип GLClampf) Что возвращает: Интенсивность красного компонента в цвете, используемом как постоянный при смешении Интенсивность зеленого компонента в цвете, используемом как постоянный при смешении Интенсивность синего компонента в цвете, используемом как постоянный при смешении Интенсивность альфа-компонента в цвете, используемом как постоянный при смешении Ничего См. также: glBlendEquation, glBlendFunc, glBlendFuncSeparate
Глава 6 Подробнее о цвете и материалах 305 gIBIendEquation Цель: Установить уравнение смешения, используемое в операциях смешения цвета Включаемый файл: <gl/gl.h> Синтаксис: void gIBIendEquation(GLenum mode); Описание: При активизированном смешении цвета источника и цели объединяются Функция glBlendFunc определяет весовые коэффициенты, характеризующее соотношение этих двух цветов в смеси, но данная функция определяет, какое уравнение будет использовано при вычислении нового цвета Допустимые уравнения и их значения приведены в табл 6.2. По умолчанию применяется уравнение GL_FUNC_ADD Параметры: mode (тип GLenum) Одно из значений, заданный в табл 6.2 Что возвращает: Ничего См. также: glBlendColor, glBlendFunc, glBlendFuncSeparate glBlendFunc Цель: Установить параметры источника и цели функции смешения цветов Включаемый файл: <gl/gl.h> Синтаксис: void glBlendFunc(GLenum sfactor, GLenum dfactor); Описание: Устанавливает параметры смешения цветов источника и цели при смешивании цветов Чтобы активизировать смешение цветов, следует вызвать функцию glEnable (GL_BLEND) Значением функции по умолчанию является glBlendFunc(GL_ONE, GL_ZERO) Список других приемлемых параметров приведен в табл. 6.1 Параметры: sfactor (тип GLenum) Функция смешения для цвета источника dfactor Функция смешения для цвета цели (тип GLenum) Что возвращает: Ничего См. также: glBlendColor, gIBIendEquation, glBlendFuncSeparate
306 Часть I Классический OpenGL gIBIendFuncSeparate Цель: Включаемый файл: Синтаксис: Описание: Параметры: srcRGB (тип GLenum) dstRGB (тип GLenum) srcAlpha (тип GLenum) dstAlpha (тип GLenum) Что возвращает: См. также: Применить к RGB-коду и значению альфа различные параметры смешения <gl/gl,h> void gIBIendFuncSeparate(GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); Позволяет применять различные весовые коэффициенты к окраске (RGB) и альфа-компоненту цвета. Функция применима к кодам источника и цели Перечень допустимых параметров смешения представлен в табл 6 1 Параметр смешения для RGB-кода источника Параметр смешения для RGB-кода цели Параметр смешения для альфа-фактора источника Параметр смешения для альфа-фактора цели Ничего glBlendColor, glBlendEquation, glBlendFunc glClearAccum Цель: Задать коды цвета, применяющиеся для очистки буфера накопления Включаемый файл: <gl/gl.h> Синтаксис: void glClearAccum(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); Описание: Задает коды цвета, которые будут использованы при очистке буфера накопления. Буфер накопления очищается посредством передачи функции glClear параметра GL_ACCUM_BUFFER_BIT Параметры: red (тип GLfloat) Значение красного компонента цвета green (тип GLfloat) Значение зеленого компонента цвета blue Значение синего компонента цвета (тип GLf loat) alpha (тип GLfloat) Значение альфа-компонента цвета Что возвращает: Ничего См. также: glAccum, glClear
Глава 6 Подробнее о цвете и материалах 307 glColorMask Цель: Замаскировать или активизировать запись в буфер цвета Включаемый файл: Синтаксис: Описание: Параметры: red (тип GLboolean) green (тип GLboolean) blue (тип GLboolean) alpha (тип GLboolean) Что возвращает: См. также: <gl/gl.h> void glColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha}; Запись в буфер цвета можно замаскировать, установив с помощью данной функции цветную маску Передавая GL_TRUE для любого канала цвета, мы разрешаем запись, а с помощью GL_FALSE запрещаем изменения (запись) Разрешает или запрещает запись красного компонента буфера цвета Разрешает или запрещает запись зеленого компонента буфера цвета Разрешает или запрещает запись синего компонента буфера цвета Разрешает или запрещает запись альфа-компонента буфера цвета Ничего gIDepthMask, glLogicOp, gIStencilMask gIFog Цель: Включаемый файл: Проконтролировать поведение тумана <gl/gl.h> Синтаксис: Описание: Параметры: pname (тип GLenum) void glFogf(GLenum pname, GLfloat param}; void glFogfv(GLenum pname, GLfloat *params}; void glFogi(GLenum pname, GLint param}; void glFogiv(GLenum pname, GLint *params}; Функция gIFog устанавливает различные параметры тумана. Чтобы визуализировать изображения с туманом, данную возможность следует активизировать с помощью glEnable(GL_FOG) Устанавливаемый параметр. Допустимые значения перечислены ниже GL_FOG_COLOR Задает цвет тумана в форме массива четырех значений (RGBA) с плавающей запятой GL_FOG_COORD_SRC Опретсляег, как рассчитываются координаты тумана Может иметь значение GL_FOG_COORD (туман с интерполяцией по вершинам) или GL_FOG_FRAGMENT_DEPTH (использование кода глубины фрагмента)
308 Часть I Классический OpenGL GL_FOG_DENSITY Задает плотность тумана GL_FOG_END Задает максимальное расстояние, на котором применяется туман GL_FOG_MODE Задает режим тумана Может иметь значение GL_LINEAR, GL_EXP ИЛИ GL_EXP2 GL_FOG_START. Задает расстояние, с которого начинает применяться туман param (тип GLfloat, GLint) params (тип GLfloat*, GLint*) Что возвращает: Значение параметра Указатель на массив параметров типа float или integer Ничего См. также: glEnable gILogicOp Цель: Выбрать логическую операцию, которая будет выполняться при записи цвета Включаемый файл: <gl/gl.h> Синтаксис: void gILogicOp(GLenum op); Описание: Эта функция задает битовую логическую операцию, которая будет применена к поступающему в буфер цвета коду цвета (источник) и коду, существующему в буфере (цель). По умолчанию логическая операция деактивизирована и для ее включения следует использовать glEnable(GL_COLOR_LOGIC_OP) Параметры: op (тип GLenum) Что возвращает: Задает выполняемую логическую операцию. Может использоваться любая из указанных в табл 6.5 констант Ничего См. также: glColorMask
Глава 6 Подробнее о цвете и материалах 309 gISampleCoverage Цель: Задать интерпретацию альфа-значений при расчетах с множественной выборкой Включаемый файл: <gl/gl. h> Синтаксис: void gISampleCoverage(GLclampf value, GLboolean invert); Описание: Обычно в операциях с множественной выборкой для расчета границ фрагментов задействованы только RGB-коды. Однако, если активизировать одну из функций GL_SAMPLE_ALPHA_TO_ONE, GL_SAMPLE_ALPHA_TO_COVERAGE или GL_SAMPLE_COVERAGE, OpenGL будет использовать альфа-компонент для расчета границ. При активизированной функции GL_SAMPLE_ALPHA_TO_COVERAGE ИЛИ GL_SAMPLE_COVERAGE данная функция применяется для установки значения, к которому вместе с кодом границы объекта будет применена логическая операция И (или которое будет инвертировано, а затем задействовано в операции И) Параметры: value (тип GLclampf) Временное значение границы, используемое при активизированной функции gl_alpha_to_coverage или GL_SAMPLE_COVERAGE invert (тип GLboolean) Параметру присваивается значение true, чтобы указать, что временное значение границы должно инвертироваться перед использованием Что возвращает: Ничего См. также: glutlnitDisplayMode

ГЛАВА 7 Воспроизведение изображений с помощью OpenGL Ричард С Райт-мп Из ЭТОЙ ГЛАВЫ ВЫ УЗНАЕТЕ Действие Функция Как установить растровое положение glRasterPos, glWindowPos Как нарисовать растровое изображение glBitmap Как считывать и записывать цветные и зображения glReadPixels, glDrawPixels Как увеличивать, сокращать и зеркально отображать изображения glPixelZoom Как задавать операции с цветами glPixellransfer, glPixelMap Как выполнять подстановку цветов glColorTable Как выполнять сложную фильтрацию изображений glConvolutionFilter2D Как собирать статистику о цветах изображения glHistogram, glGetHistogram В предыдущих главах излагались основы возможностей трехмерной графики OpenGL До этого момента выполнение всех программ представляло результат пре- образования и проектирования в двухмерное пространство трехмерных примитивов, которые в конечном счете растеризовались в буфере цвета Однако OpenGL также поддерживает чтение непосредственно из буфера цвета и запись прямо в него Это означает, что данные изображения можно считывать непосредственно из буфера цве- та в буфер памяти, обрабатывая или записывая в файл Это также означает, что вы можете считывать изображение из файла и помещать его непосредственно в буфер цвета OpenGL позволяет не только считывать и записывать двухмерные изображе- ния, но и поддерживает множество операций воспроизведения изображений, коюрыс можно применять автоматически в процессе чтения и записи В данной главе рас- сматриваются все богатые функции OpenGL работы с двухмерными изображениями (о которых, правда, нс всегда знают)
312 Часть I. Классический OpenGL Рис. 7.1. Битовое изображение лошади Растровые изображения Вначале были растровые изображения. И были они ... достаточно хорошими. Первые дисплеи компьютеров были монохромными (один цвет), обычно зелеными или жел- тыми. и все пиксели экрана могли находиться в одном из двух состояний: “включено” или “выключено”. В те дни компьютерная графика была очень простой, и данные об изображении представлялись битовыми образами (bitmap) — наборами нулей и еди- ниц, обозначающими выключенные и включенные пиксели. В битовом образе каждый бит блока памяти соответствовал одному состоянию пикселя на экране. С подобной идеей мы уже сталкивались в разделе “Заполнение многоугольников, или возвращаясь к фактуре” (глава 3, “Рисование в пространстве: геометрические примитивы и буфе- ры”). Битовые образы можно использовать в шрифтах и формах символов, масках (наложение фактуры на многоугольники) и даже двухцветных сглаженных изобра- жениях. На рис. 7.1 показано изображение лошади, представленное в виде битового образа. Несмотря на использование всего двух цветов (черные и белые точки) лошадь угадывается довольно отчетливо. Сравните это изображение с рис. 7.2, где показа- но полутоновое изображение той же лошади. На пиксельном образе (pixelmap или pixmap) каждый пиксель имеет одну из 256 различных интенсивностей серого цвета (пиксельные образы подробно рассмотрены в следующем разделе). Термин битовый образ часто применяется к изображениям, содержащим полутоновые или полноцвет- ные данные. Такое описание распространенно на платформе Windows и, по мнению многих, является неправильным использованием данного термина. В этой книге под битовым образом мы будем понимать действительно битовый (двоичный) образ, со- стоящий из значений “включено” и “выключено”, а пиксельным (или растровым) образом будем называть данные изображения, содержащие коды цвета или интенсив- ности пикселей.
Глава 7. Воспроизведение изображений с помощью OpenGL 313 Рис. 7.2. Растровое изображение лошади Пример битового образа В листинге 7.1 приведена программа BITMAPS, использующая те же битовые дан- ные, что применялись в главе 3 для наложения фактуры на многоугольник (маленький костер, представленный битовым “узором” 32 х 32). Помните, что битовые образы строятся снизу вверх, т.е. первая строка данных представляет нижнюю строку бито- вого образа. Названная программа создает окно 512х512и заполняет его изображе- нием костра из 16 строк и 16 столбцов. Результат выполнения программы показан на рис. 7.3. Обратите внимание на то, что функция Changesize задает ортографическую проекцию, сохраняя высоту и ширину окна в пикселях. Листинг 7.1. Программа BITMAPS ♦include /Common/OpenGLSB.h" // Активизируются необходимые возможности системы и OpenGL ♦include /Common/GLTools.h" // Подключаем набор инструментов OpenGL // Битовый образ костра GLubyte fire[128] = { 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, 0x00, OxcO, 0x00, 0x00, 0x01, OxfO, 0x00, 0x00, 0x07, OxfO, OxOf, 0x00, Oxlf, OxeO, Oxlf, 0x80, Oxlf, OxcO,
314 Часть I Классический OpenGL Рис. 7.3.16 строк и столбцов битового образа костра OxOf, ОхсО, 0x07, ОхеО, 0x03, OxfO, 0x03, 0xf5, 0x07, Oxfd, Oxlf, Oxfc, Oxff, 0xe3, Oxde, 0x80, 0x71, 0x10, 0x03, 0x10, 0x02, 0x88, 0x05, 0x05, 0x02, 0x82, 0x02, 0x40, 0x02, 0x64, 0x00, 0x92, 0x00, OxbO, 0x00, 0xc8, 0x00, 0x85, 0x00, 0x03, 0x00, 0x00, 0x3f, 0x80, 0x7e, 0x00, Oxff, 0x80, Oxff, OxeO, Oxff, 0xf8, Oxff, 0xe8, Oxbf, 0x70, 0xb7, 0x00, 0x4a, 0x80, 0x4e, 0x40, 0x8c, 0x20, 0x04, 0x40, 0x14, 0x40, 0x10, 0x80, Oxla, 0x80, 0x29, 0x00, 0x48, 0x00, 0x90, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00 }
Глава 7 Воспроизведение изображений с помощью OpenGL 315 /////////////////////////////////////////////////////////////////// // Эта функция выполняет необходимую инициализацию в контексте // визуализации. void SetupRC() { // Черный фон glClearColor(0.Of, O.Of, O.Of, O.Of); } /////////////////////////////////////////////////////////////////// // Устанавливается система координат, согласованная // с координатами окна void ChangeSize(int w, int h) // Предотвращает деление на нуль, когда окно слишком маленькое // (нельзя сделать окно нулевой ширины). if(h == 0) h = 1; glViewport(0, 0, w, h); // Система координат обновляется перед модификацией glMatrixMode(GL_PROJECTION); glLoadldentity(); // Псевдокоординаты окна gluOrtho2D(0.0, (GLfloat) w, O.Of, (GLfloat) h); glMatrixMode(GL_MODELVIEW); glLoad!dentity(); ) /////////////////////////////////////////////////////////////////// // Вызывается для рисования сцены void RenderScene(void) { int x, y; // Очищаем окно текущим цветом очистки glClear(GL_COLOR_BUFFER_BIT); // Устанавливается белый цвет glColor3f(l.Of, l.Of, l.Of); // Цикл из 16 строк и столбцов for(у = 0; у < 16; у++) { // Устанавливается растровое положение данного "квадрата" glRasterPos2i(0, у * 32); for(x = 0; х < 16; х++) // Рисуется битовый образ "костра", // меняется растровое положение glBitmap(32, 32, 0.0, 0.0, 32.0, 0.0, fire); } // Переключает буферы glutSwapBuffers(); ) /////////////////////////////////////////////////////////////////// // Точка входа основной программы int main(int argc, char* argv[]) { glutlnit(&argc, argv);
316 Часть I Классический OpenGL glutlnitDisplayMode(GLUT_RGB | GLUT_DOUBLE); glutlnitWindowSize(512, 512); glutCreateWindow("OpenGL Bitmaps”); glutReshapeFunc(ChangeSize) ; glutDisplayFunc(RenderScene); SetupRC(); glutMainLoop(); return 0; ) Установка растрового положения Суть программы BITMAPS заключена в функции RenderScene, где вложенные цик- лы рисуют 16 строк и 16 столбцов растрового образа костра. // Цикл из 16 строк и столбцов for(у =0; у < 16; у++) { // Устанавливается растровое положение "квадрата" glRasterPos2i(0, у * 32); for(x = 0; х < 16; х++) // Рисуется битовый образ "костра", // меняется растровое положение glBitmap(32, 32, 0.0, 0.0, 32.0, 0.0, fire); ) Первый цикл (переменная у) проходит по строкам с 0 по 16. Вызов следующей процедуры устанавливает растровое положение точки, в которой вы хотите изобра- зить битовый образ. glRasterPos2i(0, у * 32); Растровое положение интерпретируется как вызов функции glVertex, т е. с по- мощью текущих матриц наблюдения модели и проекции так же преобразовываются координаты Получающееся в результате положение в координатах окна становится текущим растровым положением. Все операции растеризации (битовые и пиксельные отображения) применяются к текущему растровому положению, задающему левый нижний угол изображения. Если текущее растровое положение не попадает в поле просмотра окна, оно неприемлемо, и любая операция OpenGL, требующая растрового положения, даст сбой В этом примере мы специально задаем, чтобы проекция OpenGL соответство- вала размерам окна, поэтому для размещения битовых образов мы можем исполь- зовать координаты окна Тем не менее данная техника не всегда удобна, так что OpenGL предлагает альтернативную функцию, позволяющую устанавливать растро- вое положение в координатах окна безотносительно к текущей матрице преобразова- ния или проекции void glWindowPos2i(GLint х, GLint у); Функция glWindowPos может иметь два или три аргумента и принимает вели- чины типа integer, float, double и short (как и функция glVertex). Полная информация об этой функции приводится в справочном разделе в конце главы
Глава 7 Воспроизведение изображений с помощью OpenGL 317 Следует сделать еще одно важное замечание, касающееся растрового положения щет растрового образа задается путем вызова функции glRasterPos либо giwin- iowPos Это означает, что текущий цвет, установленный ранее с помощью glColor, ограничивается последующими растровыми операциями Вызов glColor после уста- човки растрового положения никак не повлияет на цвет битового образа Рисование битового образа Наконец, мы добрались до команды, которая действительно рисует битовый образ в буфере цвета glBitmap(32, 32, 0.0, 0.0, 32.0, 0.0, fire); Функция glBitmap копирует указанный битовый образ в буфер цвета в текущем растровом положении и (необязательно) меняет растровое положение — все в одной операции Эта функция имеет следующий синтаксис void glBitmap(GLsize width, GLsize height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, GLubyte *bitmap); Первые два параметра width и height задают ширину и высоту битового образа (в битах). Следующие два параметра xorig и yorig (величины типа float) задают начало битового образа. Чтобы началом считался левый нижний угол образа, обо- им аргументам присваивается значение 0.0. Следующие аргументы, xmove и ymove, задают смещение в пикселях растрового положения в направлениях х и у после визуализации растрового образа. Обратите внимание на то, что эти четыре парамет- ра представлены величинами с плавающей запятой. Последний аргумент bitmap — просто указатель на данные битового образа Отметим, что при рисовании битового образа фрагменты в буфере цвета создают только единицы изображения; нули никак на влияют на уже записанное содержимое Упаковка пикселей Битовые и пиксельные образы очень редко плотно пакуются в память Из соображе- ний производительности на многих аппаратных платформах каждая строка битового или пиксельного образа должна начинаться с выровненного по байтам адреса Ком- пиляторы автоматически помещают переменные и буферы по адресам, выровненным оптимальным для данной архитектуры образом По умолчанию OpenGL предпола- гает 4-байтовое выравнивание, которое подходит для многих используемых систем. Битовый образ костра, использованный в предыдущем примере, был упакован плот- но, но в данном случае это не представляет проблем, поскольку сам битовый образ имел 4-байтовое выравнивание Напомним, что битовый образ имел ширину 32 бит (ровно 4 байт) Если бы мы использовали 34-битовый образ (всего на два бита боль- ше), пришлось бы дополнять каждую строку 30 лишними битами неиспользуемого пространства и задействовать 64 бит Хотя это может показаться ненужной тратой па- мяти, такое упорядочение позволяет процессору более эффективно захватывать блоки данных (например, такие, как строка битов битового образа) Используя приведенные ниже функции, можно изменить схему хранения и извле- чения пикселей битовых или растровых образов
318 Часть I Классический OpenGL ТАБЛИЦА 7.1. Параметры функции glPixelstore Имя параметра Тип Исходное значение GL PACK SWAP BYTES GLboolean GL_FALSE GL UNPACK SWAP BYTES GLboolean GL_FALSE GL PACK LSB FIRST GLboolean GL_FALSE GL_UNPACK_LSB_FIRST GLboolean GL_FALSE GL_PACK_ROW_LENGTH GLint 0 GL_UNPACK_ROW_LENGTH GLint 0 GL_PACK_SKIP_ROWS GLint 0 GL_UNPACK_SKIP_ROWS GLint 0 GL_PACK_SKIP_PIXELS GLint 0 GL_UNPACK_SKIP_PIXELS GLint 0 GL_PACK_ALIGNMENT GLint 4 GL_UNPACK_ALIGNMENT GLint 4 GL_PACK_IMAGE_HEIGHT GLint 0 GL_UNPACK_IMAGE_HEIGHT GLint 0 GL_PACK_SKIP_IMAGES GLint 0 GL_UNPACK_SKIP_IMAGES Glint 0 void glPixelStorei(GLenum pname, GLint param); void glPixelStoref(GLenum pname, GLfloat param); Например, если требуется переключиться на плотную упаковку данных, вызыва- ется следующая функция glPixelStorei(GL_UNPACK_ALIGNMENT, 1); Параметр GL_UNPACK_ALIGNMENT задает, как данные изображения будут распако- вываться из буфера данных Подобным образом вы можете использовать константу GL_PACK_ALIGNMENT, сообщая OpcnGL, как паковать данные, считываемые из буфе- ра цвета и помещаемые в заданный пользователем буфер памяти Полный перечень доступных режимов хранения пикселей, которые можно активизировать с помощью указанной функции, приводится в табл 7 1 (более подробно детали использования данной функции освещаются в справочном разделе) Пиксельные образы Гораздо больший интерес и больше сфер применения в современных полноцветных компьютерных системах представляют пиксельные образы По смехе распределе- ния памяти пиксельный образ похож на битовый, однако каждый пиксель может представляться несколькими битами памяти Дополнительные биты позволяют за- писывать либо интенсивность (иногда называемую сигналом яркости), либо коды цветов-компонентов Пиксельные образы рисуются в текущем растровом положении гак же, как и битовые, но используется при этом следующая функция void glDrawPixels(GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels);
Глава 7 Воспроизведение изображений с помощью OpenGL 319 ТАБЛИЦА 7.2. Пиксельные форматы OpenGL Константа Описание GL_RGB GL_RGBA GL_BGR/GL_BGR_EXT GL_BGRA/GL_BGRA_EXT GL_RED GL_GREEN GL_BLUE GL_ALPHA GL_LUMINANCE GL_LUMINANCE_ALPHA GL_STENCIL_INDEX GL_DEPTH_COMPONENT Цвета в порядке “красный, зеленый, синий” Цвета в порядке “красный, зеленый, синий, альфа” Цвета в порядке “синий, зеленый, красный” Цвета в порядке "синий, зеленый, красный, альфа” Пиксели содержат только красный компонент Пиксели содержат только зеленый компонент Пиксели содержат только синий компонент Пиксели содержат только альфа-компонент Пиксели содержат только компонент яркости (интенсивности) Пиксели содержат яркости и альфа-компонент Пиксели содержат только код трафарета Пиксели содержат только код глубины Первые два аргумента задают ширину и высоту изображения в пикселях Третий аргумент задает формат данных изображения, затем следует тип данных и указа- тель на собственно данные В отличие от glBitmap данная функция не обновляет растровое положение, кроме того она существенно гибче с точки зрения задания данных изображения Каждый пиксель представляется одним или несколькими элементами данных, со- держащихся в указателе *pixel. Цветовая схема этих элементов данных задается параметром format с помощью одной из констант, перечисленных в табл. 7.2. Два из указанных форматов (GL_STENCIL_INDEX и GL_DEPTH_COMPONENT) при- меняются для чтения и записи непосредственно в буферы трафарета и глубины. Параметр type интерпретирует данные, на которые указывает параметр *pix- els Он сообщает OpenGL, какой тип данных используется в буфере для хране- ния компонентов цвета Возможные значения аргументов рассматриваемой функ- ции перечислены в табл. 7 3. Пиксельные форматы с упаковкой Пиксельные форматы с упаковкой, перечисленные в табл 7 3, впервые появились в OpenGL 1 2 как средство, позволяющее хранить данные об изображении в более сжатой форме, согласующейся с возможностями большого диапазона цветовоспроиз- водящего аппаратного обеспечения Структура аппаратуры отображения информации позволяла экономить память или работать быстрее с небольшими наборами упакован- ных пиксельных данных Эти пиксельные форматы все еще используются некоторым аппаратным обеспечением и могут пригодиться будущим аппаратным платформам. Названные пиксельные форматы сжимают данные о цвете до минимального числа битов, причем число битов на цветовой канал указывается в соответствую- щей константе. Например, формат GL_UNSIGNED_BYTE_3_3_2 записывает три би- та первого компонента, три бита второго и два бита третьего компонента. Помни- те, что конкретные компоненты (красный, зеленый, синий и альфа) упорядочены согласно параметру format функции glDrawPixels Компоненты упорядочивают- ся от старших битов (most significant bit — MSB) до младших (least significant
320 Часть I Классический OpenGL ТАБЛИЦА 7.3. Типы пиксельных данных Константа Описание GL_UNSIGNED_BYTE GL_BYTE GL_BITMAP GL_UNSIGNED_SHORT GL_SHORT GL_UNSIGNED_INT GL_INT GL_FLOAT GL_UNSIGNED_BYTE_3_2_2 GL_UNSIGNED_BYTE_2_3_3_REV GL_UNSIGNED_SHORT_5_6_5 GL_UNSIGNED_SHORT_5_6_5_REV GL_UNSIGNED_SHORT_4_4_4_4 GL_UNSIGNED_SHORT_4_4_4_4_REV GL_UNSIGNED_SHORT_5_5_5_1 GL_UNSIGNED_SHORT_1_5_5_5_REV GL_UNSIGNED_INT_8_8_8_8 GL_UNSIGNE D_INT_8_8_8_8_REV GL_UNSIGNED_INT_10_l0_l0_2 GL UNSIGNED INT 2 10 l0 l0 REV Все компоненты цвета является 8-битовыми целыми числами без знака 8-битовые числа со знаком Отдельные биты без данных о цвете, то же, что glBitmap 16-битовые целые числа без знака 16-битовые целые числа со знаком 32-битовые целые числа без знака 32-битовые целые числа со знаком Величины с плавающей запятой обычной точности Упакованные RGB-коды Упакованные RGB-коды Упакованные RGB-коды Упакованные RGB-коды Упакованные RGBA-коды Упакованные RGBA-коды Упакованные RGBA-коды Упакованные RGBA-коды Упакованные RGBA-коды Упакованные RGBA-коды Упакованные RGBA-коды Упакованные RGBA-коды UNSIGNED.BYTE.3_ 3.2 7 6 5 4 3 2 1 0 Первый I Второй I Третий I компонент I компонент [компонент! UNSIGNED_BYTE.2_3_3.REV 7 6 5 4 3 2 1 0 I Третий | Второй Первый компонент! компонент | компонент | Рис. 7.4. Схема двух упакованных пиксельных форматов bit — LSB) GL_UNSIGNED_BYTE_2_3_3_REV обращает этот порядок и помещает последний компонент в два старших бита и тд На рис 7 4 графически показа- на побитовая схема данных двух упорядочений. Все остальные форматы с упаков- кой интерпретируются аналогично. Более яркий пример Пришло время использовать полученные знания о пикселях для создания более яр- кого и реалистичного изображения костра На рис 7 5 показан результат выполнения программы IMAGELOAD, которая загружает изображение fire.tga и использует функцию glDrawPixels для помещения этого рисунка непосредственно в буфер
Глава 7. Воспроизведение изображений с помощью OpenGL 321 Рис. 7.5. Изображение костра, загруженное из файла цвета. Программа почти идентичная BITMAPS, только данные об изображении вна- чале считываются из файла targa (обратите внимание на расширение .tga) с помо- щью функции glTools gltLoadTGA, а затем рисуются с помощью glDrawPixels, а не glBitmap, как было раньше. Функция, загружающая и отображающая файл на экране, приведена в листинге 7.2. Листинг 7.2. Функция Renderscene, загружающая и отображающая файл изображения // Вызывается для рисования сцены void RenderScene(void) { GLubyte *plmage = NULL; GLint iwidth, iHeight, iComponents; GLenum eFormat; // Очищаем окно текущим цветом очистки glClear(GL_COLOR_BUFFER_BIT); // Информация в файле targa выравнена по одному байту glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // Загружает файл TGA, дает информацию по ширине, высоте //и компонентам/формату plmage = gltLoadTGA("fire.tga", &iwidth, &iHeight, &iComponents, &eFormat); // Для задания растрового положения применяются координаты окна glRasterPos2i(0, 0); // Рисуется пиксельный образ if(plmage != NULL) glDrawPixels(iwidth, iHeight, eFormat, GL_UNSIGNED_BYTE, plmage); // Данные изображения уже не нужны free(plmage);
322 Часть I Классический OpenGL // Переключает буферы glutSwapBuffers(); } Рассмотрим вызов функции, считывающей файл targa // Загружает файл TGA, дает информацию по ширине, высоте // и компонентам/формату plmage = gltLoadTGA("fire.tga", &iWidth, &iHeight, &iComponents, &eFormat); Данную функцию мы будем часто использовать и в других программах, когда по- требуется загрузить данные изображения из файла Первым аргументом этой функции является имя загружаемого файла targa (если необходимо — с путем) Формат targa яв- ляется широко поддерживаемым и распространенным форматом изображений В от- личие от файлов JPEG файлы targa (обычно) хранят изображение в несжатой форме Функция gltLoadTGA открывает файл, а затем считывает и разбирает его заголо- вок, определяя ширину, высоту и формат данных файла Число компонентов может быть равно одному, трем или четырем и представлять яркостное, RGB- или RGBA- изображение, соответственно Последний параметр — это указатель на величину типа GLenum, получающую формат изображения OpcnGL, соответствующий файлу При успешном вызове функции он возвращает только что присвоенный указатель на дан- ные изображения, считанные непосредственно из файла Если файл не найден или произошла другая ошибка, функция возвращает NULL Полное описание функции gltLoadTGA приведено в листинге 7 3 Листинг 7.3. Функция gltLoadTGA, загружающая файлы Targa для использования OpenGL /////////////////////////////////////////////////////////////////// // Распределяет память и загружает биты файла targa. Возвращает // указатель на новые буфер, высоту и ширину текстуры, формат // данных OpenGL. Вызывает free() для освобождения буфера // после завершения. Работает только с простыми унифицированными // файлами targas с 8-, 24- или 32-битовым цветом, без палитр, // без группового кодирования GLbyte *gltLoadTGA(const char *szFileName, GLint *iWidth, GLint *iHeight, GLint *iComponents, GLenum ‘eFormat) { FILE *pFile; // Указатель файла TGAHEADER tgaHeader; // Заголовок файла TGA unsigned long IXmageSize; // Размер изображения в байтах short sDepth; // Размер пикселя GLbyte *pBits = NULL; // Указатель на биты // Значения по умолчанию/значения при сбое * iWidth = 0; * iHeight = 0; * eFormat = GL_BGR_EXT; * iComponents = GL_RGB8; / / Пытаемся открыть файл
Глава 7 Воспроизведение изображений с помощью OpenGL 323 pFile = fopen(szFileName, ”rb"); if(pFile == NULL) return NULL; // Считываем заголовок (двоичный) fread(&tgaHeader, 18/* sizeof(TGAHEADER)*/, 1, pFile); // Обращение байтов при переходе между обратным и прямым // порядком битов fdef ___APPLE__ BYTE_SWAP(tgaHeader.colorMapStart); BYTE_SWAP(tgaHeader.colorMapLength); BYTE_SWAP(tgaHeader.xstart) ; BYTE_SWAP(tgaHeader.ystart) ; BYTE_SWAP(tgaHeader.width); BYTE_SWAP(tgaHeader.height); idif // Получаем ширину, высоту и глубину текстуры *iWidth = tgaHeader.width; *iHeight = tgaHeader.height; sDepth = tgaHeader.bits / 8; // Проверки приемлемости. Очень просто: я понимаю только // 8-, 24- или 32-битовые файлы targa if(tgaHeader.bits != 8 && tgaHeader.bits != 24 && tgaHeader.bits != 32) return NULL; // Расчет размера буфера изображения HmageSize - tgaHeader.width * tgaHeader.height * sDepth; // Распределение памяти и проверка успешности pBits = malloc(HmageSize * sizeof(GLbyte)); if(pBits == NULL) return NULL; // Считывание битов // Проверка на наличие ошибок чтения. Здесь должны // отлавливаться групповое кодирование или другие // форматы, которые не нужно распознавать if (f read(pBits, HmageSize, 1, pFile) '= 1) { free(pBits); return NULL; } // Устанавливается формат, ожидаемый OpenGL switch(sDepth) case 3: // Наиболее вероятный случай *eFormat = GL_BGR_EXT; *iComponents = GL_RGB8; break; case 4 : * eFormat = GL_BGRA_EXT; * iComponents = GL_RGBA8; break; case 1• ‘eFormat = GL_LUMINANCE; * iComponents = GL_LUMINANCE8;
324 Часть I Классический OpenGL break; }; // Работа с файлом закончена fclose(pFile); // Возвращает указатель на данные изображения return pBits; } Возможно, вы отметили, что число компонентов устанавливается равным не це- лым числам 1, 3 или 4, a GL_LUMINANCE8, GL_RGB8 и GL_RGBA8. Когда OpenGL манипулирует данными изображения, он распознает специальные константы как за- прос на внутреннее поддержание полной точности воспроизведения изображения. Например, из соображений производительности некоторые реализации OpenGL мо- гут внутренне перевыбирать изображение с 24-битовым цветом до 16-битового цвета. Особенно такой подход распространен при загрузке текстуры (см главу 8, “Наложе- ние текстуры основы”) во многих реализациях, где цветовая разрешающая способ- ность вывода на дисплей равна всего 16 бит, а загружается изображение с большей насыщенностью цвета Константы представляют собой запросы к OpenGL, выдава- емые с целью хранения и использования данных изображения, предоставленных с полной насыщенностью — 8 бит на канал. Перемещение пикселей Запись данных о пикселях в буфер цвета может оказаться очень полезной сама по себе, но можно считывать данные из буфера цвета и даже копировать их из одной части буфера цвета в другую. Функция, считывающая данные о пикселях, действует подобно glDrawPixels (только наоборот) void glReadPixels(GLint х, GLint у, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); В координатах окна вы задаете х и у — координаты левого нижнего угла прямо- угольника, который следует считать, после чего указываете ширину и высоту пря- моугольника в пикселях Параметры format и type представляют формат и тип данных Если буфер цвета хранит данные не так, как вы запросили, OpenGL вы- полнит все необходимые преобразования Данная возможность может быть очень полезной, особенно когда вы узнаете пару магических трюков, которые можно при- менять с функций glPixelTransfer (подробнее см раздел “Передача пикселей”). Указатель на данные изображения *pixels должен быть действительным и преду- сматривать достаточно места для хранения данных об изображении после преобразо- вания, поскольку в противном случае во время выполнения вы скорее всего получите исключительную ситуацию, связанную с памятью Копирование пикселей из одной части буфера цвета в другую также является довольно простой операцией, при выполнении которой не требуется выделять вре- менной памяти для хранения информации Вначале с помощью glRasterPos или glWindowPos задается растровое положение — целевой угол (помните, — левый ниж- ний угол), куда вы хотите скопировать данные изображения Затем для выполнения операции копирования используется следующая функция
Глава 7 Воспроизведение изображений с помощью OpenGL 325 void glCopyPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type); Параметры x и у задают левый нижний угол копируемого прямоугольника, после чего указывается его ширина и высота в пикселях Парамор type должен иметь значение GL_COLOR, соответствующее копированию данных о цвете Здесь вы также можете использовать константы GL_DEPTH и GL_STENCIL, тогда копирование будо выполняться в буфер глубины или зрафарсга (перемещение кодов i дубины и тра- фарета также может пригодиться в некоторых алгоритмах визуализации и создания специальных эффектов) По умолчанию все описанные операции с пикселями действуют в заднем буфере в контексте визуализации с двойной буферизацией и в переднем буфере в контексте визуализации с простой буферизацией Источник или цель данных операций можно изменить, используя следующие функции void glDrawBuffer(GLenum mode); void glReadBuffer(GLenum mode); Функция glDrawBuffer определяет, какие пиксели будут рисоваться при выпол- нении операции glDrawPixels или glCopyPixels Здесь можно использовать лю- бую из допустимых констант буфера, рассмотренных в главе 3 GL_NONE, GL_FRONT, GL_BACK, GL_FRONT_AND_BACK, GL_FRONT_LEFT, GL_FRONT_RIGHT и ТД Функция glReadBuf fer принимает тс же константы и задаст целевой буфер цвета для операции чтения, выполняемой функцией glReadPixels или glCopyPixels Запись пикселей Теперь вы знаете достаточно о том, как перемещать пиксели, чтобы записать еще одну полезную функцию из библиотеки glTools Дополнением к функции загрузки файлов targa gltLoadTGA являсзся gltWnteTGA Эта функция считывает данные о цвете из переднего буфера цвета и записывает их в файл изображения в формате targa В следующем разделе данная функция используется при работе с интересными пиксельными операциями OpenGL Полная информация о функции gltWnteTGA приводится в листинге 7 4 Листинг 7.4. Функция gltWnteTGA, записывающая изображение на экране в файл Targa /////////////////////////////////////////////////////////////////// // Записывается текущее поле просмотра как файл targa // Перед вызовом этой функции не забудьте вызвать SwapBuffers // для использование двойной буферизации или glFinish для простой // буферизации. Возвращает 0, если происходит ошибка, и 1, // если все хорошо GLint gltWriteTGA(const char *szFileName) FILE *pFile; TGAHEADER tgaHeader; unsigned long llmageSize; GLbyte *pBits = NULL; GLint iViewport[4]; // Указатель файла // Заголовок файла TGA // размер изображения в байтах // Указатель на биты // Размер поля просмотра в пикселях
326 Часть I Классический OpenGL GLenum lastBuffer; // Память для хранения текущих настроек буфера чтения // Получает размеры поля просмотра glGet!ntegerv(GL_VIEWPORT, iViewport); // Насколько большим будет изображение // (файлы targa плотно упакованы) HmageSize = iViewport[2] * 3 * iViewport[3]; // Распределяет блок. Если это не работает, возвращаемся домой pBits = (GLbyte * Jmalloc ( HmageSize); if(pBits == NULL) return 0; // Считывает биты из буфера цвета glPixelStorei(GL_PACK_ALIGNMENT, 1); glPixelStorei(GL_PACK_ROW_LENGTH, 0); glPixelStorei(GL_PACK_SKIP_ROWS, 0); glPixelStorei(GL_PACK_SKIP_PIXELS, 0) ; // Получает текущие установки буфера чтения и записывает их. // Переключается на передний буфер и выполняет операцию чтения. //В конце концов восстанавливает состояние буфера чтения glGet!ntegerv(GL_READ_BUFFER, &lastBuffer); glReadBuffer(GL_FRONT); glReadPixels(0, 0, iViewport[2], iViewport[3], GL_BGR, GL_UNSIGNED_BYTE, pBits); glReadBuffer(lastBuffer); // Инициализирует заголовок файла Targa tgaHeader.identsize = 0; tgaHeader.colorMapType = 0; tgaHeader.imageType = 2; tgaHeader.colorMapStart = 0; tgaHeader.colorMapLength = 0; tgaHeader.colorMapBits = 0; tgaHeader.xstart = 0; tgaHeader.ystart = 0; tgaHeader.width = iViewport[2]; tgaHeader.height = iViewport[3]; tgaHeader.bits = 24; tgaHeader.descriptor = 0; // Обращение байтов при переходе между обратным и прямым // порядком битов #ifdef ___APPLE__ BYTE_SWAP(tgaHeader.colorMapStart); BYTE_SWAP(tgaHeader.colorMapLength); BYTE_SWAP(tgaHeader.xstart); BYTE_SWAP(tgaHeader.ystart); BYTE_SWAP(tgaHeader.width); BYTE_SWAP(tgaHeader.height); #endif // Пытается открыть файл pFile = fopen(szFileName, "wb"); iftpFile == NULL) { free(pBits); // Освобождает буфер и возвращает ошибку return 0;
Глава 7 Воспроизведение изображений с помощью OpenGL 327 // Записывает заголовок fwrite(&tgaHeader, sizeof(TGAHEADER), 1, pFile); I/ Записывает данные об изображении fwrite(pBits, HmageSize, 1, pFile); // Освобождает временный буфер и закрывает файл free(pBits); fclose(pFile); // Успех! return 1; Развлекаемся с помощью пикселей В данном разделе мы обсудим поддержку OpcnGL увеличения и уменьшения изоб- ражения, переворота изображений и выполнения нескольких специальных операций в процессе передачи данных о пикселях в буфер цвета и из него. Вместо того чтобы создавать отдельную программу для демонстрации каждого обсуждаемого специаль- ного эффекта, мы написали одну — OPERATIONS Эта программа отображает простое цветное изображение, загруженное из файла targa Щелчок правой кнопкой мыши со- отнесен с системой меню GLUT, позволяющей выбирать один из восьми режимов рисования или сохранять модифицированное изображение на диске в файле screen- shot, tga В листинге 7 5 программа приведена целиком В последующих разделах она разбирается по отдельным фрагментам и подробно описывается Листинг 7.5. Исходный код программы OPERATIONS // Operations.с // OpenGL. Суперкнига // Демонстрирует операции воспроизведения изображений // Программа написана Ричардом С. Райтом--мл. #include /Common/OpenGLSB.h" // Активизируются необходимые возможности системы и OpenGL #include /Common/GLTools h" // Подключаем набор инструментов OpenGL #include <math.h> /////////////////////////////////////////////////////////////////// // Глобальные переменные модуля для записи исходных // данных изображения static GLubyte *plmage = NULL; static GLint iWidth, iHeight, iComponents; static GLenum eFormat; // Глобальные переменные для хранения режима рисования static GLint iRenderMode = 1; /////////////////////////////////////////////////////////////////// // Эта функция выполняет необходимую инициализацию в контексте // визуализации.
328 Часть I Классический OpenGL void SetupRC(void) { // Черный фон glClearColor(O.Of, O.Of, O.Of, O.Of); // Загружаем изображение лошади glPixelStorei(GL_UNPACK_ALIGNMENT, 1); plmage = gltLoadTGA("horse.tga", SiWidth, SiHeight, SiComponents, &eFormat); ) void ShutdownRC(void) { // Освобождаем исходные данные изображения free(plmage); } ////////////////////////////////////////////////////////////////// // Должным образом обновляем флаги в ответ на выбор позиции из меь void ProcessMenufint value) { if(value == 0) // Записываем изображение gltWnteTGA( "Screenshot. tga") ; else 11 Меняем индекс режима визуализации на индекс, // соответствующий позиции меню iRenderMode = value; // Активизируем перерисовывание изображения glutPostRedisplay(); } ////////////////////////////////////////////////////////////////// // Вызывается для рисования сцены void RenderScene(void) { GLint iViewport[4]; GLbyte *pModifiedBytes = NULL; GLfloat invertMap[256]; GLint i; // Очищаем окно текущим цветом очистки glClear(GL_COLOR_BUFFER_BIT); // Текущее растровое положение всегда соответствует левому // нижнему углу окна glRasterPos2i(0, 0); //В зависимости от индекса режима визуализации выполняются // необходимые операции с изображением switch(iRenderMode) { case 2: // Переворачиваем пиксели glPixelZoom(-l.Of, -l.Of); glRasterPos2i(iWidth, iHeight); break; case 3: // Увеличиваем пиксели для заполнения окна glGet!ntegerv(GL_VIEWPORT, iViewport); glPixelZoom((GLfloat) iViewport[2] / (GLfloat)iWidth, (GLfloat) iViewport[3] / (GLfloat)iHeight);
Глава 7 Воспроизведение изображений с помощью OpenGL 329 break; case 4: // Только красный glPixelTransferf(GL_RED_SCALE, l.Of) ; glPixelTransferf(GL_GREEN_SCALE, O.Of) ; glPixelTransferf(GL_BLUE_SCALE, O.Of); break; case 5: // Только зеленый glPixelTransferf(GL_RED_SCALE, O.Of); glPixelTransferf(GL_GREEN_SCALE, 1. Of) ; glPixelTransferf(GL_BLUE_SCALE, O.Of); break; case 6: // Только синий glPixelTransferf(GL_RED_SCALE, O.Of); glPixelTransferf(GL_GREEN_SCALE, 0.Of); glPixelTransferf(GL_BLUE_SCALE, 1.Of) ; break; case 7: // Черно-белый, более сложный режим 11 Вначале рисуем изображение в буфере цвета glDrawPixels(iWidth, iHeight, eFormat, GL_UNSIGNED_BYTE, plmage); // Распределяем память для карты яркости pModifiedBytes = (GLbyte *)malloc(iWidth * iHeight); // Масштабируем цвета согласно стандарту NSTC glPixelTransferf(GL_RED_SCALE, 0.3f); glPixelTransferf(GL_GREEN_SCALE, 0.59f) ; glPixelTransferf(GL_BLUE_SCALE, 0 . Ilf) ; 11 Считываем пиксели в буфер // (будем применено увеличение) glReadPixels(O,0,iWidth, iHeight, GL_LUMINANCE, GL_UNSIGNED_BYTE, pModifiedBytes); 11 Масштабирование цвета возвращается в норму glPixelTransferf(GL_RED_SCALE, 1.Of); glPixelTransferf(GL_GREEN_SCALE, 1.Of); glPixelTransferf(GL_BLUE_SCALE, 1.Of) ; break; case 8: // Инверсия цветов invertMap[0] = l.Of; for(i = 1; i < 256; i++) invertMapti] = l.Of - (l.Of / 255.Of * (GLfloat)i); glPixelMapfv(GL_PIXEL_MAP_R_TO_R, 255, invertMap); glPixelMapfv(GL_PIXEL_MAP_G_TO_G, 255, invertMap); glPixelMapfv(GL_PIXEL_MAP_B_TO_B, 255, invertMap); glPixelTransferi(GL_MAP_COLOR, GL_TRUE); break; case 1: // Просто копия старого изображения default: // Данная строка специально оставлена пустой break; ) // Рисуются пиксели if(pModifiedBytes == NULL) glDrawPixels(iWidth, iHeight, eFormat, GL_UNSIGNED_BYTE, plmage);
330 Часть I Классический OpenGL else glDrawPixels(iWidth, iHeight, GL_LUMINANCE, GL_UNSIGNED_BYTE, pModifledBytes); free(pModifiedBytes); } // Обновление всего до настроек по умолчанию glPixelTransferi(GL_MAP_COLOR, GL_FALSE); glPixelTransferf(GL_RED_SCALE, l.Of); glPixelTransferf(GL_GREEN_SCALE, 1.Of); glPixelTransferf(GL_BLUE_SCALE, l.Of); glPixelZoom(1.Of, l.Of); // Без увеличения пикселе! // Переключает буферы glutSwapBuffers(); } void ChangeSize(int w, int h) { // Предотвращает деление на нуль, когда окно слишком мале£ // (нельзя сделать окно нулевой ширины) if (h == 0) h = 1; glViewport(0, 0, w, h); 11 Система координат обновляется перед модификацией glMatrixMode(GL_PROJECTION); glLoadldentity(); // Устанавливается объем отсечения gluOrtho2D(0.Of, (GLfloat) w, 0.0, (GLfloat) h); glMatrixMode(GL_MODELVIEW); glLoadldentity(); } /////////////////////////////////////////////////////////////; // Точка входа основной программы int main(int argc, char* argv[]) glutlnit(&argc, argv); glutlnitDisplayMode(GLUT_RGB I GL_DOUBLE); glutlnitWindowSize(800 ,600); glutCreateWindow("OpenGL Image Operations"); glutReshapeFunc(ChangeSize); glutDisplayFunc(RenderScene); // Создается меню и добавляются опции выбора glutCreateMenu(ProcessMenu); glutAddMenuEntry("Save Image",0); glutAddMenuEntry("DrawPixels",1); glutAddMenuEntry("FlipPixels",2); glutAddMenuEntry("ZoomPixels",3); glutAddMenuEntry("Just Red Channel",4); glutAddMenuEntry("Just Green Channel",5); glutAddMenuEntry("Just Blue Channel",6); glutAddMenuEntry("Black and White", 7); glutAddMenuEntry("Invert Colors", 8); glutAttachMenu(GLUT_RIGHT_BUTTON);
Глава 7 Воспроизведение изображений с помощью OpenGL 331 SetupRC(); glutMainLoop(); ShutdownRC(); return 0; } // Настройка // Основной программный цикл // Выключение Основной каркас программы достаточно прост. В отличие от предыдущего приме- ра IMAGELOAD сейчас изображение загружается и содержится в памяти все время работы программы, поэтому при каждом изменении экрана не требуется повторная загрузка изображения. Как показано ниже, информация об изображении и указатель на требуемые байты хранятся как глобальные переменные модуля. static GLubyte *plmage = NULL; static GLint iWidth, iHeight, iComponents; static GLenum eFormat; Таким образом, функция SetupRC всего лишь загружает изображение и инициали- зирует глобальные переменные, содержащие формат, ширину и высоту изображения // Загружаем изображение лошади glPixelStorei(GL_UNPACK_ALIGNMENT, 1); plmage = gltLoadTGA!"horse.tga", &iWidth, &iHeight, &iComponents, &eFormat); При завершении программы память, которая ей была выделена с помощью функ- ции gltLoadTGA В ShutdownRC, освобождается free(plmage); В главной функции вы создаете меню и добавляете позиции и значения для раз- личных требуемых операций // Создается меню и добавляются опции выбора glutCreateMenu(ProcessMenu); glutAddMenuEntry!"Save Image",0); glutAddMenuEntry("Draw Pixels",1); glutAddMenuEntry!"Flip Pixels",2); glutAddMenuEntry!"Zoom Pixels",3); glutAddMenuEntry("Just Red Channel",4); glutAddMenuEntry("Just Green Channel",5); glutAddMenuEntry("Just Blue Channel",6); glutAddMenuEntry("Black and White", 7); glutAddMenuEntry("Invert Colors", 8); glutAttachMenu(GLUT_RIGHT_BUTTON); Эти позиции меню устанавливают желаемое значение переменной iRenderMode или, если значение равно 0, записывают изображение так, как оно отображено в данный момент void ProcessMenu(int value) if(value == 0) // Записываем изображение gltWriteTGA!"Screenshot.tga"); else // Меняем индекс режима визуализации на индекс,
332 Часть I. Классический OpenGL Рис. 7.6. Результат выполнения по умолчанию программы OPERATIONS // соответствующий позиции меню iRenderMode = value; // Активизируем перерисовывание изображения glutPostRedisplay(); } Наконец, изображение рисуется в буфере цвета, и за это отвечает функция Ren- derScene. Данная функция содержит оператор выбора, использующий переменную iRenderMode для выбора одного из восьми различных режимов рисования. По умол- чанию просто вызывается неизмененная функция glDrawPixels, помещающая изоб- ражение в левом нижнем углу окна, как показано на рис. 7.6. Другие случаи рассмот- рены ниже. Изменение масштаба пикселей Другой простой, но достаточно распространенной операцией, которую часто требует- ся производить с данными пикселей, является сжатие или растягивание изображения. В контексте OpenGL это называется масштабированием пиксечей (pixel zoom); вы- полняется оно с помощью следующей функции: void glPixelZoom(GLfloat xfactor, GLfloat уfactor); Аргументы xfactor и yfactor задают коэффициенты масштабирования по осям .г и у. С помощью указанной операции можно сжимать, растягивать и даже перево- рачивать изображение. Например, при коэффициенте 2 изображение будет записано с двукратным размером вдоль заданной оси, а при коэффициенте 0.5 оно будет сжато
Глава 7. Воспроизведение изображений с помощью OpenGL 333 Рис. 7.7. Масштабирования пикселей для растягивания изображения на все окно вдвое. Для примера выбор в программе OPERATIONS позиции Zoom Pixels из меню соответствует третьему режиму визуализации. В этом случае перед вызовом функции glDrawPixels выполняются приведенные ниже строки кода, задающие коэффици- енты масштабирования в направлениях х и у такими, чтобы растянутое изображение заняло все окно. case 3: // Увеличиваем пиксели для заполнения окна glGet!ntegerv(GL_VIEWPORT, iViewport); glPixelZoom((GLfloat) iViewport[2] / (GLfloat)iWidth, (GLfloat) iViewport[3] / (GLfloat)iHeight); break; Результат выполнения программы для этого случая показан на рис. 7.7. С другой стороны, отрицательный коэффициент масштабирования переворачивает изображение вдоль выбранной оси. Использование такого коэффициента не только обращает порядок пикселей на изображении, но и меняет на обратное направление рисования пикселей относительно растрового положения. Например, при обычном рисовании изображения левый нижний угол помещен в текущее растровое положе- ние. Если оба коэффициента масштабирования отрицательные, растровое положение становится правым верхним углом полученного изображения. В программе OPERATIONS выбор из меню позиции Flip Pixels переворачивает изображение горизонтально и вертикально. Как видно из приведенного фрагмен- та кода, оба масштабных коэффициента пикселей устанавливаются равными -1.0, и растровое положение меняется с левого нижнего угла окна на правый верхний угол (задается шириной и высотой изображения).
334 Часть I. Классический OpenGL Рис. 7.8. Изображение, полученное с помощью инверсии по осям х и у case 2: // Переворачиваем пиксели glPixelZoom(-l.Of, -l.Of); glRasterPos2i(iwidth, iHeight); break; Инвертированное изображение, получающееся при выборе этой опции из меню, показано на рис. 7.8. Передача пикселей Помимо масштабирования пикселей OpenGL поддерживает ряд простых математи- ческих операций, которые можно выполнить с данными, передаваемыми из буфера цвета или в него. Эти режимы передачи пикселей устанавливаются с помощью одной из следующих функций, а параметры передачи пикселей перечислены в табл. 7.4. void glPixelTransferi(GLenum pname, GLint param); void glPixelTransferf(GLenum pname, GLfloat param); Параметры масштабирования и смещения позволяют масштабировать и смещать отдельные цветовые каналы. Масштабный коэффициент умножается на код компо- нента, а код смещения прибавляется к коду компонента. Определенные таким образом операции масштабирования и смещения широко распространены в компьютерной графике, связанной с настройкой значений цветовых каналов. Уравнение, согласно которому производится расчет, записывается достаточно просто: новое значение = (старое значение * масштабный коэффициент) + смешение (7.1)
Глава 7 Воспроизведение изображений с помощью OpenGL 335 ТАБЛИЦА 7.4. Параметры передачи пикселей Константа Тип Значение по умолчанию GL_MAP_COLOR GLboolean GL_FALSE GL_MAP_STENCIL GLboolean GL_FALSE GL_RED_SCALE GLfloat 1.0 GL_GREEN_SCALE GLfloat 1.0 GL_BLUE_SCALE GLfloat 1.0 GL_ALPHA_SCALE GLfloat 1.0 GL_DEPTH_SCALE GLfloat 1.0 GL_RED_BIAS GLfloat 0.0 GL_GREEN_BIAS GLfloat 0.0 GL_BLUE_BIAS GLfloat 0.0 GL_ALPHA_BIAS GLfloat 0.0 GL_DEPTH_BIAS GLfloat 0.0 GL_POS T_CONVOLUTION_RE D_S CALE GLfloat 1.0 GL_POST_CONVOLUTION_GREEN_SCALE GLfloat 1.0 GL_POST_CONVOLUTION_BLUE_SCALE GLfloat 1.0 GL_POST_CONVOLUTION_ALPHA_SCALE GLfloat 1.0 GL_POST_CONVOLUTION_RED_BIAS GLfloat 0.0 GL_POST_CONVOLUTION_GREEN_BIAS GLfloat 0.0 GL_POST_CONVOLUTION_BLUE_BIAS GLfloat 0.0 GL_POST_CONVOLUTION_ALPHA_BIAS GLfloat 0.0 GL_POST_COLOR_MATRIXJRED_SCALE GLfloat 1.0 GL_POST_COLOR_MATRIX_GREEN_SCALE GLfloat 1.0 GL_POST_COLOR_MATRIX_BLUE_SCALE GLfloat 1.0 GL_POST_COLOR_MATRIX_ALPHA_SCALE GLfloat 1.0 GL_POST_COLOR_MATRIX_RED_BIAS GLfloat 0.0 GL_POST_COLOR_MATRIX_GREEN_BIAS GLfloat 0.0 GL_POST_COLOR_MATRIX_BLUE_BIAS GLfloat 0.0 GL_POST_COLOR_MATRIX_ALPHA_BIAS GLfloat 0.0 По умолчанию масштабные коэффициенты равны 1.0, а коды смещения — 0.0. По сути, такие величины нс влияют на коды компонентов В качестве примера пред- положим, что требуется отобразить только красную составляющую изображения Для этого масштабные коэффициенты синего и зеленого каналов устанавливаются рав- ными 0.0, а после изображения рисунка возвращаются к исходному значению 1.0 glPixelTransferf(GL_GREEN_SCALE, O.Of); glPixelTransfer(GL_BLUE_SCALE, O.Of); Для иллюстрации данной концепции в программу OPERATIONS включено меню с позициями Just Red, Just Green и Just Blue Выбор любой из этих позиций отключает все каналы, кроме одного, в результате на изображении присутствуют только коды красного, зеленого или синего компонентов case 4: // Только красный glPixelTransferf(GL_RED_SCALE, l.Of); glPixelTransferf(GL_GREEN_SCALE, O.Of); glPixelTransferf(GL_BLUE_SCALE, O.Of); break; case 5: // Только зеленый glPixelTransferf(GL_RED_SCALE, O.Of);
336 Часть I Классический OpenGL glPixelTransferf(GL_GREEN_SCALE, 1 Of); glPixelTransferf(GL_BLUE_SCALE, O.Of); break; case 6: // Только синий glPixelTransferf(GL_RED_SCALE, O.Of); glPixelTransferf(GL_GREEN_SCALE, 0.Of); glPixelTransferf(GL_BLUE_SCALE, l.Of); break; После рисования с помощью переноса пикселей масштабные коэффициенты воз- вращаются к исходному значению — 1.0 glPixelTransferf(GL_RED_SCALE, l.Of); glPixelTransferf(GL_GREEN_SCALE, 1 Of); glPixelTransferf(GL_BLUE_SCALE, l.Of); Такого же результата можно добиться с помощью параметров масштабирования и смещения, но тогда придется ожидать завершения операций свертки или действий с матрицей цветов Эти операции доступны в подмножестве построения изображений, которое мы рассмотрим ниже Болес интересным примером передачи пикселей является черно-белое отображе- ние цветного изображения Увидеть такое изображение в программе OPERATIONS можно, выбрав из меню опцию Black and White Итак, вначале в буфере цвета рису- ется ПОЛНОЦВС1НОС изображение glDrawPixels(iWidth, iHeight, eFormat, GL_UNSIGNED_BYTE, plmage); Затем выделяс1ся буфер, настолько большой, чтобы вместить колы яркости всех пикселей pModifledBytes = (GLbyte *Jmalloc(iWidth * iHeight); Помните, что яркостному изображению соответствует всего один цветовой канал, поэтому для его хранения выделяется 1 байт (8 бш) на пиксель При вызове glRead- Pixels OpenGL авюматичсски преобразовывает изображение в буфере цвета в коды яркости, но требует, чтобы данные имели формат GL_LUMINANCE glReadPixels(0,0,iWidth, iHeight, GL_LUMINANCE, GL_UNSIGNED_BYTE, pModifledBytes); 3aiCM яркостное изображение снова можно записать в буфер цвета и увидеть преобразованное черно-белое изображение glDrawPixels(iWidth, iHeight, GL_LUMINANCE, GL_UNSIGNED_BYTE, pModifledBytes); Использование описанного подхода кажется привлекательным, и он почти работа- ет Проблема заключается в том, что, когда OpenGL преобразовывает цветное изоб- ражение в яркостное, компоненты цветовых каналов просто суммируются Если три цветовых канала при суммировании дадут значение больше 1, оно будет просто при- равнено к 1 Таким обраюм создастся эффект перенасыщенности многих областей изображения (рис 7 9) Чтобы решить возникшую проблему, режим передачи пикселей нужно установить так, чтобы коды цвета соответствующим образом масштабировались при переходе из пространства цветов к яркости Согласно стандарт Национального комитета по телевизионным шандартам (National Television Standards Committee - NTSC), прсоб-
Глава 7. Воспроизведение изображений с помощью OpenGL 337 Рис. 7.9. Перенасыщенность, вызванная принятым по умолчанию преоб- разованием цвета в яркость разование из пространства цветов RGB к черно-белому (полутоновому) изображению происходит следующим образом: яркость = (0.3 * красный) + (0.59 * зеленый) + (0.11 * синий) Такое преобразование можно легко задать в OpenGL, вызвав необходимые функ- ции непосредственно перед выполнением glReadPixels. // Масштабируем цвета согласно стандарту NSTC glPixelTransferf(GL_RED_SCALE, 0.3f); glPixelTransferf(GL_GREEN_SCALE, 0.59f); glPixelTransferf(GL_BLUE_SCALE, 0.Ilf); После считывания пикселей режим передачи пикселей возвращается к нормальному. // Масштабирование цвета возвращается в норму glPixelTransferf(GL_RED_SCALE, 1.Of); glPixelTransferf(GL_GREEN_SCALE, l.Of); glPixelTransferf(GL_BLUE_SCALE, 1.Of); Теперь результатом выполнения программы является приятное полутоновое изоб- ражение. Поскольку рисунки в книге не цветные, результат выполнения программы, получаемый на экране, выглядит точно так же, как рисунок на рис. 7.6. Отображение пикселей Помимо операций масштабирования и смещения в число операций переноса пикселей входит отображение цвета. Картой цветов называется справочная таблица, исполь-
338 Часть I Классический OpenGL ТАБЛИЦА 7.5. Параметры карты пикселей Имя карты GL_PIXEL_MAP_R_TO_R GL_PIXEL_MAP_G_TO_G GL_PIXEL_MAP_B_TO_B GL_PIXEL_MAP_A_TO_A зуемая для преобразования одного кода цвета (индекса-указателя в таблицу) в другой код цвета (действительный код цвета, записанный в позиции, указываемой индексом) Отображение цвета применяется во многих областях, например, цветокоррекции, гамма-коррекции или преобразованиях между различными представлениями цветов Исследуя программу OPERATIONS и выбрав опцию Invert Colors, вы обнаружите интересный пример. В этом случае устанавливается карта цветов, в процессе перено- са пикселей обращающая все коды цвета. Это означает, что все три канала отобража- ются из диапазона 0.0-1.0 в диапазон 1 0-0 0. В результате получается изображение, выглядящее, как негатив фотографии Чтобы активизировать отображение пикселей, вызывается функция glPixel- Transfer с параметром GL_MAP_COLOR, имеющим значение GL_TRUE. glPixelTransferi(GL_MAP_COLOR, GL_TRUE); Чтобы установить карту пикселей, нужно вызвать другую функцию — glPixelMap и предоставить карту в одном из трех форматов. glPixelMapuiv(GLenum тар, GLint mapsize, GLuint ★values'); glPixelMapusv(GLenum map, GLint mapsize, GLushort *values); glPixelMapfv(GLenum map, GLint mapsize, GLfloat * values'); Допустимы значения функции перечислены в табл. 7 5 В этом примере мы устанавливаем карту из 256 величин типа float и заполняем карту промежуточными значениями от 1 0 до 0.0 GLfloat invertMap[256]; invertMap[0] = l.Of; for (i = 1; i < 256; 1++) invertMapfi] = l.Of - (1.0f/255.0f * (GLfloat)i); Затем мы устанавливаем красную, зеленую и синюю карты этой обращенной карты и включаем отображение цвета glPixelMapfv(GL_PIXEL_MAP_R_TO_R, 255, invertMap); glPixelMapfv(GL_PIXEL_MAP_G_TO_G, 255, invertMap); glPixelMapfv(GL_PIXEL_MAP_B_TO_B, 255, invertMap); glPixelTransferi(GL_MAP_COLOR, GL_TRUE); При вызове glDrawPixels цвета-компоненты повторно отображаются с помощью таблицы обращения, по сути, создавая цветной негатив, показанный на рис 7 10
Глава 7. Воспроизведение изображений с помощью OpenGL 339 Рис. 7.10. Использование карты цветов для создания цветного негатива “Подмножество” воспроизведения изображений Все функции манипуляции изображениями, которые мы рассматривали до этого мо- мента, являются частью основного программного интерфейса OpenGL, начиная с вер- сии 1.0. Единственным исключением является функция glwindowPos, которая была добавлена в версию 1.4, чтобы облегчить установку растрового положения. Суще- ствующие возможности предоставляют OpenGL достаточную поддержку большин- ства действий, необходимых при обработке изображений. Для выполнения нетриви- альных операций с изображениями OpenGL также может включать (как в версии 1.2) подмножество воспроизведения изображений (imaging subset). Данное подмножество является необязательным, т.е. многие поставщики могут не включать эти функции в свою реализацию. Тем не менее, если подмножество воспроизведения изображений поддерживается, обязательной является поддержка всех функциональных возможно- стей данного элемента. Чтобы во время выполнения определить, поддерживается ли приложением под- множество воспроизведения изображений, можно поискать в строке расширений то- кен GL_ARB_imaging. Например, если вы используете библиотеку glTools, код может выглядеть примерно так: if(gltIsExtSupported("GL_ARB_imaging") == 0) { // Ошибка, воспроизведение изображений не поддерживается
340 Часть I Классический OpenGL else { // Какая-то работа с изображениями } Доступ к подмножеству воспроизведения изображений организован посредством механизма расширений OpenGL, а это означает, что потребуется использовать файл заголовка glext.h и получить функциональные указатели, указывающие на функ- ции, которые следует использовать. Некоторые реализации OpenGL (в зависимости от средств разработки платформы) уже могут иметь эти функции и константы, вклю- ченные в файл заголовка gl. h (например, в заголовках Apple XCode они уже опреде- лены) Для компиляции на Macintosh мы используем встроенную поддержку подмно- жества воспроизведения изображений; на ПК мы применяем механизм расширений, с помощью которого получаем функциональные указатели на функции воспроизве- дения изображений. Программа IMAGING смоделирована очень похоже на предыдущий пример OPERATIONS — она также является программой, с помощью контекстного ме- ню демонстрирующей различные операции При запуске программа проверяет до- ступность подмножества построения изображения и прерывает выполнение, если оно не найдено // Проверяется наличие подмножества воспроизведения изображений // (это нужно сделать после создания окна, поскольку в противном // случае не будет контекста OpenGL, которому можно было бы // направить запрос) if(gltIsExtSupported("GL_ARB_imaging") == 0) ( printf("Imaging subset not supported\r\n"); return 0; ) Целиком функция RenderScene представлена в листинге 7.6. Далее в этом разделе мы обсудим различные ее фрагменты Листинг 7.6. Функция RenderScene ИЗ программы IMAGING /////////////////////////////////////////////////////////////////// // Вызывается для рисования сцены void RenderScene(void) { GLint i; // Переменная цикла GLint iViewport[4]; // Поле просмотра GLint iLargest; // Наибольшее значение гистограммы static GLubyte invertTable[256][3]; // Инвертированная // таблица цветов // Черно-белое масштабирование static GLfloat lumMat[16] = { 0.30f, 0.30f, 0.30f, O.Of, 0.59f, 0.59f, 0.59f, O.Of, O.llf, O.llf, O.llf, O.Of,
Глава 7 Воспроизведение изображений с помощью OpenGL 341 О Of, 0 Of, O.Of, l.Of }; static GLfloat mSharpen[3][3] = { // Усиленное ядро свертки { 0 Of, -l.Of, O.Of), {-l.Of, 5.Of, -l.Of }, { 0 Of, -l.Of, O.Of }}; static GLfloat mEmboss[3][3] = { // Рельефное ядро свертки { 2.Of, O.Of, O.Of }, { 0 Of, -l.Of, O.Of }, { 0 Of, O.Of, -l.Of )); static GLint histoGram[256]; // Память для хранения статистики гистограммы // Очищаем окно текущим цветом очистки glClear(GL_COLOR_BUFFER_BIT); // Текущее растровое положение всегда находится II в левом верхнем углу окна glRasterPos2i(0, 0); glGetlntegerv(GL_VIEWPORT, iViewport), glPixelZoom((GLfloat) iViewport[2] / (GLfloat)iWidth, (GLfloat) iViewport[3] I (GLfloat)iHeight) ; if(bHistogram == GL_TRUE) // Собираются данные гистограммы // Собираем данные по яркости, используя собственную // формулу обращения вместо той, что предлагает OpenGL // (простая сумма всех цветовых компонентов) glMatrixMode(GL_COLOR) ; glLoadMatrixf(lumMat); glMatrixMode(GL_MODELVIEW); // Начинаем собирать данные гистограммы, 256 кодов яркости glHistogram(GL_HISTOGRAM, 256, GL_LUMINANCE, GL_FALSE); glEnable(GL_HISTOGRAM); ) // В зависимости от индекса режима визуализации выполняются // операции с изображением switch(iRenderMode) { case 5: // Наведение резкости изображения glConvolutionFilter2D(GL_CONVOLUTION_2D, GL_RGB, 3, 3, GL_LUMINANCE,GL_FLOAT,mSharpen); glEnable(GL_CONVOLUTION_2D), break; case 4: // Тиснение изображения glConvolutionFilter2D(GL_CONVOLUTION_2D, GL_RGB, 3, 3, GL_LUMINANCE, GL_FLOAT, mEmboss); glEnable(GL_CONVOLUTION_2D); glMatrixMode(GL_COLOR); glLoadMatrixf(lumMat); glMatrixMode(GL_MODELVIEW); break; case 3: // Инверсия изображения for(i = 0; i < 255; i++) invertTable[i][0] = (GLubyte)(255 - i); invertTable[i][1] = (GLubyte)(255 - i);
342 Часть I Классический OpenGL invertTable[i][2] = (GLubyte)(255 - i); } glColorTable(GL_COLOR_TABLE, GL_RGB, 256, GL_RGB, GL_UNSIGNED_BYTE, invertTable); glEnable(GL_COLOR_TABLE); break; case 2: // Увеличение яркости изображения glMatrixMode(GL_COLOR); glScalef(1.25f, 1.25f, 1.25f); glMatrixMode(GL_MODELVIEW); break; case 1: // Просто копия старого изображения default: // Данная строка специально оставлена пуст< break; } // Рисуются пиксели glDrawPixels(iWidth, iHeight, eFormat, GL_UNSIGNED_BYTE, plmage); // Извлечь и нарисовать гистограмму? if(bHistogram == GL_TRUE) { // Данные гистограммы считываются в буфер glGetHistogram(GL_HISTOGRAM, GL_TRUE, GL_LUMINANCE, GL_IN' histoGram); // Находится наибольшее значение // для масштабирования графика iLargest = 0; ford = 0; 1 < 255; 1++) if(iLargest < histoGram[i]) iLargest = histoGram(i]; I/ Белые линии glColor3f(l.Of, l.Of, l.Of); glBegin(GL_LINE_STRIP); for(i = 0; i < 255; i++) glVertex2f((GLfloat)i, (GLfloat)histoGramfi] / (GLfloat) iLargest * 128.Of); glEnd(); bHistogram = GL_FALSE; gIDisable(GL_HISTOGRAM); // Обновление всего до настроек по умолчанию glMatrixMode(GL_COLOR); glLoadldentity(); glMatrixMode(GL_MODELVIEW); gIDisable(GL_CONVOLUTION_2D); gIDisable(GL_COLOR_TABLE); // Демонстрируем наш тяжкий труд ... glutSwapBuffers();
Глава 7 Воспроизведение изображений с помощью OpenGL 343 Рис. 7.11. Конвейер воспроизведения изображений OpenGL Подмножество воспроизведения изображений можно разбить на три основных области новых функциональных возможностей, матрица цветов и таблица цветов, свертки и гистограммы Помните, что обработка изображений является широкой и сложной темой, и ей вполне можно посвятить отдельную книгу. Ниже мы при- водим краткий обзор функциональных возможностей, иллюстрируя их на простых примерах Более глубокое обсуждение обработки изображений можно найти в одной из книг, предложенных в приложении А, “Что еще почитать”. Конвейер воспроизведения изображений Операции воспроизведения изображений OpenGL обрабатываются в конкретном порядке, который называется конвейером воспроизведения изображений (imaging pipeline) Как геометрические объекты обрабатываются в конвейере преобразований, данные изображений последовательно проходят через процесс воспроизведения изоб- ражения. На рис 7 11 конвейер воспроизведения изображений разбит на отдельные операции, которые подробно рассмотрены в данном разделе Матрица цветов Простейшей составляющей новых функциональных возможностей, добавленных подмножеством воспроизведения изображения, является матрица цветов Как вы уже знаете, коды цвета можно описывать как координаты в пространстве цветов RGB, которое сродни XYZ и представляется кубом цветов (см. главу 5, “Цвет, матери- алы и освещение: основы”). Компонент альфа цвета можно представить себе как компонент W вектора, тогда четыре координаты цвета будут соответствующим об- разом преобразовываться с помощью матрицы цвета 4x4 Матрица цветов — это стек матриц, действующий так же, как другие стеки матриц OpenGL (GL_MODELVIEW, GL_PROJECTION, GL_TEXTURE) Чтобы сделать стек матриц цвета текущим, вызыва- ется функция glMatrixMode с аргументом GL_COLOR glMatrixMode(GL_COLOR);
344 Часть I Классический OpenGL К матрице цветов можно применять все процедуры работы с матрицами (glLoad- ldentity, glLoadMatrix и тд). В стек матриц цвета можно помещать элементы и извлекать их из него, но реализация должна поддерживать стек глубиной всего два элемента Выбрав в программе IMAGING из меню элемент Increase Contrast, мы установим второй режим визуализации, те функция RenderScene будет использовать матрицу цветов для применения к кодам цвета положительного масштабного коэффициента, что увеличит контрастность изображения case 2: // Увеличение яркости изображения glMatrixMode(GL_COLOR); glScalef(1.25f, 1.25f, 1.25f); glMatrixMode(GL_MODELVIEW); break; Когда изменения происходят на экране, эффект получается мягким, но достаточно очевидным. После визуализации матрица цветов снова становится единичной. // Обновление всего до настроек по умолчанию glMatrixMode(GL_COLOR); glLoadldentity(); glMatrixMode(GL_MODELVIEW); Кодирование цвета С помощью таблиц цвета задается таблица кодов цвета, применяющаяся для заме- щения текущего цвета пикселя. Данная функциональная возможность подобна отоб- ражению пикселей, но она более гибкая с точки зрения построения и применения таблицы цветов Для задания этой таблицы применяется следующая функция- void glColorTable(GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const GLvoid *table}; Параметр target задает, в каком месте конвейера воспроизведения изображений должна применяться таблица цветов. Этим параметром может быть один из кодов, перечисленных в табл 7 6 Если указание места начинается с gl_proxy, выполняется проверка того, может ли загружаться предоставленная таблица (вместится ли она в память). Параметр internalFormat задает внутреннее представление OpenGL таб- лицы цветов, на которую указывает параметр table. Он может иметь одно ИЗ следующих значений- GL_ALPHA, GL_ALPHA4, GL_ALPHA8, GL_ALPHA12, GL_ ALPHA16, GL_LUMINANCE, GL_LUMINANCE4, GL_LUMINANCE8, GL_LUMINANCE12, GL_LUMINANCE16, GL_LUMINANCE_ALPHA, GL_LUMINANCE4_ALPHA4, GL_LUMINANCE6 _ALPHA2, GL_LUMINANCE8_ALPHA8, GL_LUMINANCE12_ALPHA4, GL_LUMINANCE12_ ALPHA12, GL_LUMINANCE16_ALPHA16, GL_INTENSITY, GL_INTENSITY4, GL_ INTENSITY8, GL_INTENSTIY12, GL_INTENSITY16, GL_RGB, GL_R3_G3_B2, GL_RGB4, GL_RGB5, GL_RGB8, GL_RGB10, GL_RGB12, GL_RGB16, GL_RGBA, GL_RGBA2, GL_RGBA4, GL_RGB5_A1, GL_RGBA8, GL_RGB10_A2, GL_RGBA12, GL_RGBA16 О значении цвето- вых компонентов этого списка вы уже должны догадываться по названиям, а числовой суффикс просто оказывает число битов представления данного компонента
Глава 7 Воспроизведение изображений с помощью OpenGL 345 ТАБЛИЦА 7.6. Место применения таблицы кодировки цветов Цель Положение GL_COLOR_TABLE Применяется в начале конвейера воспроизведения изображений GL_POST_CONVOLUTION_COLOR_TABLE Применяется после операции свертки GL_POST_COLOR_MATRIX_COLOR_TABLE Применяется после операций с матрицей цветов GL_PROXY_COLOR_TABLE Проверка допустимости загрузки таблицы цветов GL_PROXY_POST_CONVOLUTION_COLOR_TABLE Проверка допустимости загрузки таблицы цветов GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE Проверка допустимости загрузки таблицы цветов Параметры format и type описывают формат таблицы цветов, предоставленной в указателе table. Все значения этих параметров соответствуют аргументам, исполь- зуемым в функции glDrawPixels, и перечислены в табл 7 2 и 7.3. В приведенном примере таблица цветов демонстрируется, так сказать, “в дей- ствии”. Она дублирует эффект инверсии цветов из программы OPERATIONS, но вместо отображения пикселей в ней применяется таблица цветов. При выборе из меню опции Invert Color устанавливается режим визуализации 3, и выполняется сле- дующий сегмент функции RenderScene. case 3: // Инверсия изображения for(i = 0; i < 255; i++) { invertTable[i][0] = 255 - i; invertTable[i][1] = 255 - i; invertTable[i][2] = 255 - i; 1 glColorTable(GL_COLOR_TABLE, GL_RGB, 256, GL_RGB, GL_UNSIGNED_BYTE, invertTable); glEnable(GL_COLOR_TABLE); Чтобы использовать загруженную таблицу цветов, нужно активизировать табли- цы цветов вообще, вызвав функцию glEnable с параметром GL_COLOR_TABLE. По завершении рисования всех пикселей таблица цветов деактивизируется. glDisable(GL_COLOR_TABLE); Результат выполнения данного кода точно повторяет изображение, приведен- ное на рис. 7.10. “Представитель” Поддержка реализацией OpenGL таблиц цвета может огра- ничиваться ресурсами системы. Большие таблицы цветов, например, невозможно за- грузить, если они требуют слишком много памяти. Чтобы определить, поместится ли данная таблица цветов в память и можно ли будет ее использовать, можно применять представители (proxy) таблиц цветов, перечисленные в табл. 7 6. С помощью данных указателей места и функции glGetColorTableParameter можно узнать, поместит- ся ли таблица цветов в память. Функция glGetColorTableParameter позволяет
346 Часть I Классический OpenGL запросить OpenGL относительно различных установок таблиц цветов, более подроб- но она описана в справочном разделе. В данном случае эта функция используется для того, чтобы узнать, согласуется ли ширина таблицы цветов с шириной, запрошенной с помощью вызова таблицы цветов-“представителя” GLint width; glColorTable(GL_PROXY_COLOR_TABLE, GL_RGB, 256, GL_RGB, GL_UNSIGNED_BYTE, NULL); glGetColorTableParameteriv(GL_PROXY_COLOR_TABLE, GL_COLOR_TABLE_WIDTH, &width); if(width == 0) { // Ошибка ... Обратите внимание на то, что для “представителя” не нужно задавать указатель на реальную таблицу цветов Другие операции С отображением пикселей таблицу цветов роднит еще и то, что ее можно использовать для масштабирования и смещения составляющих кодов цветов Для этого нужно всего лишь вызвать такую функцию: void glColorTableParameteriv(GLenum target, GLenum pname, GLint *param); void glColorTableParameterfv(GLenum target, GLenum pname, GLfloat *param); Параметром target функции glColorTableParameter может быть GL_COLOR _TABLE, GL_POST_CONVOLUTION_COLOR_TABLE ИЛИ GL_POST_COLOR_MATRIX_COLOR _TABLE. Параметр pname устанавливает масштаб или смещение, имея значение GL_COLOR_TABLE_SCALE или GL_COLOR_TABLE_BIAS, соответственно. Последним параметром является указатель на массив четырех элементов, в котором записаны ко- ды масштабирования или смещения красного, зеленого, синего и альфа-компонентов. Кроме того, таблицу цветов можно визуализировать, используя как источник данных содержимое буфера цвета (после визуализации или рисования) Функция glCopyColorTable в качестве входа принимает данные из текущего буфера чтения (текущий GL_READ_BUFFER) void glCopyColorTable(GLenum target, GLenum internalFormat, GLint x, GLint y, GLsizei width); Параметры target и internalFormat идентичны одноименным параметрам функции glColorTable. После выполнения приведенной функции из буфера цве- та извлекается массив таблицы цветов, начиная с положения х,у и захватывая width пикселей. Используя функцию glColorSubTable, можно заменить всю таблицу цветов или ее часть void glColorSubTable(GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const void *data); Большинство параметров соответствует аналогам в функции glColorTable, ис- ключением являются лишь start и count. Параметр start — это позиция в таблице цветов, с которой начинается замещение, a count — число кодов цвета, которые нужно заменить
Глава 7 Воспроизведение изображений с помощью OpenGL 347 Рис. 7.12. Ядро наведения резкости Наконец, используя функцию glCopyColorSubTable, можно заменить всю или часть таблицы цветов из буфера цвета (подобно glCopyColorTable). void glCopyColorSubTable(GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); Как и выше, источником таблицы цветов является буфер цвета, х и у задают положение, с которого начинают считываться коды цвета, подлежащие замещению, a width — число замещаемых кодов цвета. Свертки В сфере области обработки изображений свертки являются мощной техникой, позво- ляющей создавать множество специальных эффектов: размывание, увеличение резко- сти и другие Свертка — это фильтр, обрабатывающий пиксели изображения согласно схеме весовых коэффициентов, именуемой ядром. Свертка замещает каждый пиксель взвешенным средним значением этого пикселя и его соседей, причем код цвета каж- дого пикселя масштабируется согласно весовым коэффициентам, записанным в ядре. Обычно ядра свертки — это прямоугольные массивы величин типа float (зна- чения с плавающей запятой), которые представляют весовые коэффициенты соот- ветствующей структуры пикселей изображения. Например, приведенное ниже ядро увеличивает в программе IMAGING резкость изображения. static GLfloat mSharpen[3][3] = { // Ядро, увеличивающее резкость { O.Of, -l.Of, O.Of), {-l.Of, 5.Of, -l.Of), { O.Of, -l.Of, O.Of}); Центральный пиксель учитывается с весовым коэффициентом 5.0, что очень силь- но выделяет пиксель, находящийся в данном месте. Пикселям, расположенным слева, справа, снизу и сверху от центрального, присвоены отрицательные весовые коэффи- циенты, а угловые пиксели вообще не учитываются. На рис. 7.12 для примера показан блок данных изображения с наложенным ядром свертки. Цифра 5 в центре ядра пред- ставляет замещаемый пиксель, также обозначены значения ядра, применение которых к окружающим пикселям дает новое значение центрального пикселя (обозначен кру- жочком). Ядро свертки применяется к каждому пикселю изображения, в результате чего получается более резкое изображение. Увидеть данный процесс в действии мож- но, выбрав опцию Sharpen Image в меню программы IMAGING. Чтобы применить сверточный фильтр, в программе IMAGING перед операцией glDrawPixels просто вызываются две указанных функции.
348 Часть I Классический OpenGL glConvolutionFilter2D(GL_CONVOLUTION_2D, GL_RGB, 3, 3, GL_LUMINANCE, GL_FLOAT, mSharpen); glEnable(GL_C0NV0LUTI0N_2D); Функция glConvolutionFilter2D имеет следующий синтаксис void glConvolutionFilter2D(GLenum target, GLenum internalFormat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid * image); Первый параметр, target, должен быть равен GL_CONVOLUTION_2D Второй па- раметр, internalFormat, принимает тс же значения, что и glColorTable, и задаст, к каким компонентам пикселей применяется свертка Параметры width и height являются шириной и высотой ядра свертки Наконец, format и type задают формат и тип пикселей, записанных для данного изображения В случае фильтра наведения резкости данные пикселей имеют формат GL_RGB, и ядром является GL_LUMINANCE, поскольку оно содержит всею одно значение для каждого пикселя (в противополож- ность случаю, когда каждый цветовой канал имеет собственный весовой коэффици- ент) Ядра свертки включаются и выключаются с помощью вызова glEnable или glDisable с параметром GL_CONVOLUTION_2D Свертка является частью конвейера воспроизведения изображения, и се можно объединять с другими операциями с изображением Например, чтобы заполнить изображением все окно, вместе с масштабированием пикселей использовался уже продемонстрированный фильтр наведения резкости Чтобы получить более интерес- ный пример, объединим масштабирование пикселей с матрицей цветов и сверточным фильтром В следующем фрагменте кода определяется матрица цветов, которая пре- образует изображение в черно-белое (полутоновое), и сверточный фильтр, создающий эффект чеканки // Черно-белое масштабирование static GLfloat lumMat[16] = {0.30f, 0 30f, 0 30f, O.Of, 0.59f, 0.59f, 0.59f, O.Of, O.llf, O.llf, O.llf, 0 Of, O.Of, O.Of, O.Of, l.Of }; static GLfloat mSharpen[3][3] = { // Сверточный фильтр наведения резкости { O.Of, -1 Of, 0 Of}, {-l.Of, 5 Of, -l.Of }, { 0 Of, -1 Of, 0 Of }}; static GLfloat mEmboss[3][3] = { // Сверточный фильтр чеканки {2.Of, O.Of, O.Of ), (0 Of, -l.Of, O.Of }, {0 Of, 0 Of, -l.Of }}; Выбирая из всплывающего меню опцию Emboss Image, вы устанавливаете режим визуализации 4, и перед функцией glDrawPixels выполняется следующий фрагмент функции RenderScene case 4: // Чеканка glConvolutionFilter2D(GL_CONVOLUTION_2D, GL_RGB, 3, 3, GL_LUMINANCE, GL_FLOAT, mEmboss); glEnable(GL_CONVOLUTION_2D); glMatrixMode(GL_COLOR); glLoadMatrixf(lumMat);
Глава 7. Воспроизведение изображений с помощью OpenGL 349 Рис. 7.13. Использование свертки и матрицы цветов для имитации чеканки Рис. 7.14. Внешнее произведение двух одномерных фильтров glMatrixMode(GL_MODELVIEW); break; Изображение-чеканка представлено на рис. 7.13. Из буфера цвета Ядра свертки также можно загружать из буфера цвета. Для этого используется следующая функция, ведущая себя аналогично функции загрузки таблицы цветов из буфера цвета. void glCopyConvolutionFilter2D(GLenum target, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height); Значение target всегда должно быть равным GL_CONVOLUTION_2D, a internal- Format обозначает формат данных о цвете, как в функции glConvolutionFilter2D. Ядро загружается по данным пикселей, записанных в буфере цвета в ячейке (х,у) до указанных ширины width и высоты height. Сепарабельные фильтры Сепарабельным сверточным фильтром называется фильтр, ядро которого можно представить произведением матриц двух одномерных фильтров. Например, на рис. 7.14 одномерная матрица-строка и матрица-столбец перемножаются, давая в результате матрицу 3x3 (ядро нового фильтра). Для задания двух одномерных фильтров применяется следующая функция:
350 Часть I Классический OpenGL void glSeparableFilter2D(GLenum target, GLenum internalFormat, GLsizei width, GLsizei height, GLenum format, GLenum type, void *row, const GLvoid *column); Все параметры имеют то же значение, что и в функции glConvolutionFilter2D, только в адрес фильтров передается два параметра- row и col Параметр target в данном случае должен иметь только значение GL_SEPARABLE_2D Одномерные ядра OpenGL также поддерживает одномерные сверточные филь- тры, но они применяются только к одномерным текстурам Поведение этих сверток принципиально ничем не отличается от поведения двухмерных сверток, единствен- ное отличие в том, что они применяются только к строкам пикселей (тексел ей, если речь идет об одномерных текстурах). Эти одномерные свертки имеют одномерные ядра, и для загрузки и копирования фильтров применяются такие функции. glConvolutionFilterlDCGLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const GLvoid *image); glCopyConvolutionFilterlD(GLenum target, GLenum internalFormat, GLint x, GLint y, GLsizei width); Разумеется, при использовании этих функции параметр target должен иметь значение GL_CONVOLUTION_1D. Другие уловки свертки При применении ядра сверточного фильтра к изобра- жению, на его краях ядро будет перекрываться с границами изображения и выходить за них. Поступки OpenGL в данной ситуации определяются режимом границы сверт- ки. Данный режим устанавливается с помощью функции glConvolutionParameter, имеющей четыре разновидности glConvolutionParameteri(GLenum target, GLenum pname, GLint param); glConvolutionParameterf(GLenum target, GLenum pname, GLfloat param); glConvolutionParameteriv(GLenum target, GLenum pname, GLint *params); glConvolutionParameterfv(GLenum target, GLenum pname, GLfloat *params); Параметр target может принимать значения GL_CONVOLUTION_1D, GL_CONVOLUTION _2D или GL_SEPARABLE_2D Чтобы задать режим границы, GL_CONVOLUTION_BORDER _MODE используется как параметр pname, а вместо param подставляется требуемая константа граничного режима Если присвоить param значение GL_CONSTANT_BORDER, пиксели вне границы изображения приравниваются к постоянному коду пикселя. Для задания этого зна- чения вызывается функция glConvolutionParameterfv с параметром GL_ CON- STANT_BORDER и массив величин типа float, содержащий RGBA-коды, которые задают пиксели постоянного цвета Если режим границы установить равным GL_REDUCE, ядро свертки не будет приме- няться к граничным пикселям Таким образом, ядро никогда не будет перекрываться с краем изображения В этом случае следует помнить, что вы, по сути, уменьшаете изображение на ширину и высоту сверточного фильтра
Глава 7. Воспроизведение изображений с помощью OpenGL 351 Рис. 7.15. Гистограмма в Photoshop Последний режим границы — GL_REPLICATE_BORDER. В этом случае свертка при- меняется так, если изображение повторялось по горизонтали и вертикали столько раз, сколько требуется, чтобы избежать наложения. Кроме ТОГО, используя GL_CONVOLUTION_FILTER_BIAS и/или GL_CONVOLUTION_ FILTER_SCALE в качестве имени параметра (pname) и подставляя значения смещения и масштабирования в param или params, вы также можете применить к значениям ядра смещение и масштабирование. Гистограмма Гистограмма — это графическое представление распределения частот на изображе- нии. Говоря нормальным языком, на гистограмме подсчитано, сколько раз встречается на изображении каждый код цвета, и эти счетчики представлены в виде диаграммы, состоящей из столбиков. Гистограммы можно представлять для кодов интенсивности или отдельно для каждого цветового канала. Гистограммы часто используются в об- работке изображений, и многие цифровые камеры могут отображать гистограммы для полученных изображений. С помощью этой информации фотографы определяют, охватывает ли камера весь динамический диапазон снимаемого изображения, либо это изображение недодержано или передержано. Как показано на рис. 7.15, такие популярные пакеты обработки изображений, как Adobe Photoshop, также позволяют рассчитывать и отображать гистограммы. Если сбор гистограмм активизирован, OpenGL собирает статистику по всем изоб- ражениям, записываемым в буфер цвета. Чтобы подготовиться к сбору данных ги- стограмм, нужно сообщить OpenGL, сколько данных требуется собирать и в каком формате. Для этого вызывается функция glHistogram. void glHistogram(GLenum target, GLsizei width, GLenum internalFormat, GLboolean sink);
352 Часть I Классический OpenGL Параметр target должен иметь значение GL_HISTOGRAM или GL_PROXY_HISTOGRAM (последнее используется для того, чтобы определить, достаточно ли ресурсов для хранения гистограммы) Параметр width сообщает OpenGL, сколько позиций де- лать в таблице гистограммы Это значение должно быть степенью 2 (1, 2, 4, 8, 16 и тд ) Параметр internalFormat задает формат данных, в котором будет хранить- ся гистограмма, соответствующий допустимым параметрам формата таблиц цвета с единственным исключением — нет параметра GL_INTENSITY. Наконец, можно от- бросить пиксели и ничего не рисовать, задав значением параметра sink равным GL_TRUE Включать и выключать сбор данных для гистограмм можно с помощью вызовов glEnable или glDisable с параметром GL_HISTOGRAM. glEnable(GL_HISTOGRAM); После передачи данных изображения данные гистограммы собираются с помощью следующей функции void glGetHistogramfGLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values}; Единственным приемлемым значением параметра target является GL_HISTOGRAM При установке reset равным GL_TRUE, данные гистограммы удаляются В противном случае гистограмма становится кумулятивной, и каждая операция передачи пикселей добавляет статистические данные к гистограмме Параметр format задает формат данных собранной информации, a type и values представляют тип данных, который будет использоваться, и адрес ячейки памяти, где размещается гистограмма Вернемся теперь к нашему примеру и применим гистограмму Выбирая в про- грамме IMAGING из меню позицию Histogram, получим полутоновую версию изоб- ражения и график в левом нижнем углу, представляющий статистические частоты всех кодов яркости Результат выполнения программы показан на рис 7 16 Первое, что делает функция RenderScene, — выделяет память для хранения ги- стограммы В приведенной ниже строке создастся массив целых чисел длиной 256 элементов Каждый элемент массива содержит счетчик, регистрирующий, сколько раз использовалось соответствующее значение яркости при рисовании изображе- ния на экране static GLint histoGram[256]; // Память для хранения статистики гистограммы Установив флаг гисюграммы (выбрав соответствующую опцию меню), вы ука- зываете OpenGL начать сбор данных гистограммы Вызов функции glHistogram сообщает OpenGL собрать статстику о 256 отдельных значениях яркости, которые могут присутствовать на изображении Параметр sink получает значение false, поэтому изображение также рисуется на экране if(bHistogram == GL_TRUE) // Собираются данные гистограммы { // Собираем данные по яркости, используем собственную // формулу обращения вместо той, что предлагает OpenGL // (простая сумма всех цветовых компонентов) glMatrixMode(GL_COLOR); glLoadMatrixf(lumMat); glMatrixMode(GL_MODELVIEW); // Начинаем собирать данные гистограммы, 256 кодов яркости
Глава 7. Воспроизведение изображений с помощью OpenGL 353 Рис. 7.16. Гистограмма кодов яркости изображения glHistogram(GL_HISTOGRAM, 256, GL_LUMINANCE, GL_FALSE); glEnable(GL_HISTOGRAM); } Обратите внимание на то, что в этом случае нужно задать матрицу цветов, чтобы обеспечить преобразования к полутонам. По умолчанию OpenGL использует преобра- зование GL_LUMINANCE, суммируя красный, зеленый и синий компоненты цвета. При использовании данной формулы преобразования вы получите такие же гистограмму и изображение, как и приведенные на рис. 7.15. После того как пиксели отображены, с помощью указанного ниже кода собираем данные гистограммы. // Извлечь и нарисовать гистограмму? if(bHistogram == GL_TRUE) { // Данные гистограммы считываются в буфер glGetHistogram(GL_HISTOGRAM, GL_TRUE, GL_LUMINANCE, GL_INT, histoGram); Теперь проходим данные гистограммы, находя в них самое большое значение. Это делается потому, что далее это значение будет использовано как масштабный коэф- фициент, с помощью которого график будет помещен в левый нижний угол экрана. // Находится наибольшее значение для масштабирования графика GLint iLargest = 0; for(i =0; i < 255; i++) if(iLargest < histoGram[i]) iLargest = histoGramfi];
354 Часть I Классический OpenGL Наконец, пришло время нарисовать график с собранной статистикой В следую- щем фрагменте кода цвет рисования устанавливается белым, а затем мы последова- тельно проходим по всем данным гистограммы, создавая сплошной набор полосок. Данные масштабируются на максимальное значение, так что весь график имеет ши- рину 256 пикселей и высоту 128 пикселей. После завершения метка гистограммы устанавливается равной false, и с помощью вызова функции gIDisable сбор дан- ных для гистограммы деактивизируется. // Белые линии glColor3f(l.Of, l.Of, l.Of); glBegin(GL_LINE_STRIP); for(i =0; i < 255; i++) glVertex2f((GLfloat)i, (GLfloat)histoGram[i] /(GLfloat) iLargest * 128.Of); glEnd(); bHistogram = GL_FALSE; gIDisable(GL_HISTOGRAM); } Операции с экстремальными значениями В предыдущем примере для визу- ализированного изображения мы проходили данные гистограммы в поиске наиболь- шего компонента яркости. Если вас интересуют только наибольшие или наименьшие из полученных значений, данные по всей гистограмме можно не собирать, собрав только наибольшие и наименьшие значения Обращаться с данным набором экстре- мальных значений нужно так же, как с гистограммой Вначале с помощью указанной ниже функции задается формат данных, среди которых нужно собрать статистику. void glMinmax(GLenum target, GLenum internalFormat, GLboolean sink); Здесь значение target равно GL_MINMAX, a internalFormat и sink задаются точно так же, как в функции glHistogram Кроме того, нужно активизировать сбор экстремальных данных glEnable(GL_MINMAX); Экстремальные данные собираются с помощью функции glGetMinmax, аналогич- ной glGetHistogram void glGetMinmax(GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); Как и ранее, параметр target имеет значение GL_MINMAX, а другие параметры задаются так же, как в функции glGetHistogram Резюме В данной главе мы показали, что OpenGL предоставляет первоклассную поддержку действий с цветными изображения — от чтения и записи битовых образов и цвет- ных изображений непосредственно в буфер цвета, до обработки цвета и операций с кодовыми картами цветов. Многие (но не все) реализации OpenGL предлагают да- же больше, поддерживая подмножество воспроизведения изображений OpenGL Оно позволяет добавлять сложные фильтры обработки изображений и средства анализа в мощные программы обработки графики
Глава 7 Воспроизведение изображений с помощью OpenGL 355 Кроме того, в данной главе мы заложили фундамент, позволяющий в следую- щей главе вернуться к трехмерной геометрии в контексте описания возможностей наложения текстуры OpenGL Рассмотренные выше функции, выполняющие загруз- ку и обработку данных изображения, используются при переносе манипуляций с информацией об изображениях на трехмерные примитивы. Справочная информация gIBitmap Цель: Нарисовать растровый образ в текущем растровом положении Включаемый файл: <gl.h> Синтаксис: void gIBitmap(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap); Описание: В OpenGL растровыми образами (bitmap) называют бинарные шаблоны изображения без данных о цвете. Эта функция рисует растровый шаблон в текущем растровом положении Помимо ширины и высоты растрового образа нужно задавать все смещения в бинарной карте изображения Текущее растровое положение обновляется после переноса растрового образа на величину, заданную параметрами xmove и ymove. Растровые образы принимают цвет, который был текущим на момент задания растрового положения с помощью glRasterPos или glWindowPos Параметры: width, height (тип GLsizei) Ширина и высота растрового образа xorig, yorig Положение начала растрового образа, отсчитываемое от левого (тип GLsizei) верхнего угла xmove, ymove Смещение в направлении хну, прибавляемое к текущему (тип GLsizei) растровому положению после рисования bitmap (тип Указатель на растровое изображение const GLubyte*) Что возвращает: Ничего См. также: glRasterPos, glWindowPos, glDrawPixels, glPixelStore glColorSubTable Цель: Заменить всю существующую кодовую таблицу цветов или ее часть Включаемый файл: <gl. h>
356 Часть I Классический OpenGL Синтаксис: void glColorSubTable(GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const void *data); Описание: Эта функция позволяет заменять всю заданную таблицу цветов или ее часть. Кодовые таблицы цветов расходуют ресурсы системы, поэтому, если вместо задания новой таблицы заменить всю уже существующую таблицу цветов (или ее фрагмент нужного размера), можно выиграть в производительности Параметры: target (тип GLenum) start (тип GLsizei) count (тип GLsizei) format (тип GLenum) Целевая таблица цветов. Параметр имеет определенный смысл и может иметь любое из указанных в табл. 7 6 значений Начальная точка таблицы цветов, с которой начинается замещение Число элементов таблицы цветов, подлежащих замещению Формат данных в таблице цветов Здесь может использоваться любое значение, приемлемое при задании таблицы с помощью glColorTable Что возвращает: Ничего См. также: glColorTable, glCopyColorSubTable glColorTable Цель: Задать таблицу кодов, заменяющих цвета Включаемый файл: <gl.h> Синтаксис: void glColorTable(Glenum target, GLenum internalFormat, GLsizei width, sGLenum format, GLenum type, const GLvoid *table); Описание: Таблицы цветов используются для замещения кодов цветов пикселей. Коды компонентов цвета пикселя используются как коды позиций таблицы, на которую указывает table. Код цвета, записанный в этой позиции, заменяется компонентом цвета, который первоначального был соотнесен с обрабатываемым пикселем Действия в таблицей цветов нужно активизировать, вызвав функцию glEnable (GL_COLOR_TABLE) Параметры: target (тип GLenum) Загружаемая таблица цветов Параметр может иметь любое значение, указанное в табл 7.6
Глава 7 Воспроизведение изображений с помощью OpenGL 357 internal Format (тип GLenum) Внутреннее представление таблицы Может использоваться любая из следующих символьных констант GL_ALPHA, GL_ALPHA4, GL_ALPHA8, GL_ALPHA12, GL_ALPHA16, GL_LUMINANCE, GL_LUMINANCE4, GL_LUMINANCE8, GL_LUMINANCE12, GL_LUMINANCE16, GL_LUMINANCE_ALPHA, GL_LUMINANCE4_ALPHA4, GL_LUMINANCE6_ALPHA2, GL_LUMINANCE8_ALPHA8, GL_LUMINANCE12_ALPHA4, GL_LUMINANCE12_ALPHA12, GL_LUMINANCE16_ALPHA16, GL_INTENSITY, GL_INTENSITY4, GL_INTENSITY8, GL_INTENSTIY12, GL_INTENSITY16, GL_RGB, GL_R3_G3_B2, GL_RGB4, GL_RGB5, GL_RGB8, GL_RGB10, GL_RGB12, GL_RGB16, GL_RGBA, GL_RGBA2, GL_RGBA4, GL_RGB5_A1, GL_RGBA8, GL_RGB10_A2, GL_RGBA12, GL_RGBA16 width (тип GLsizei) format (тип GLenum) type (тип GLenum) table (тип void*) Что возвращает: Ширина в пикселях таблицы цветов Должна быть степенью двойки 2 (1, 2, 4, 8, 16, 32, 64 и тд) Формат пиксельных данных Может иметь любое значение, указанное в табл 7 2 Тип данных компонентов цвета пикселя. Приемлемы любые типы данных из табл 7 3 Указатель на массив, составляющий таблицу цветов Ничего См. также: glColorSubTable, glColorTableParameter, glCopyColorSubTable, glCopyColorTable glColorTableParameter Цель: Задать значения масштаба и смещения для таблицы цветов Включаемый файл: <gl.h> Варианты: void glColorTableParameteriv(GLenum target, GLenum pname, GLint *param); void glColorTableParameterfv(GLenum target, GLenum pname, GLfloat *param); Описание: Эта функция позволяет применять к значениям таблицы цветов масштабирование и смещение Коэффициенты масштабирования умножаются на коды компонентов цвета, а коэффициенты смещения прибавляются к ним Коды компонентов цвета ограничиваются согласно допустимому диапазону [0,1]
358 Часть I Классический OpenGL Параметры: target (тип GLenum) Таблица цветов, к которой будет применен параметр Может иметь значение GL_COLOR_TABLE, GL_POST_CONVOLUTION_COLOR_TABLE ИЛИ GL_POST_COLOR_MATRIX_COLOR_TABLE pname (тип GLenum) Либо GL_COLOR_TABLE_SCALE (устанавливает параметр масштабирования), либо GL_COLOR_TABLE_BIAS (устанавливает параметр смещения) param (тип GLint* или GLfloat*) Что возвращает: Указатель на массив значений (параметры масштабирования или смещения). Число элементов массива должно равняться числу компонентов в таблице цветов Ничего См. также: glColorTable glConvolutionFilterl D Цель: Задать одномерный сверточный фильтр Включаемый файл: <gl.h> Синтаксис: void glConvolutionFilterlD(GLenum target, GLenum internal format, GLsizei width, GLenum format, GLenum type, const GLvoid * image); Описание: Эта функция устанавливает одномерный сверточный фильтр, который будет использовать с функцией glTexImagelD Одномерные сверточные фильтры не влияют на двухмерные операции формирования изображения Данную операцию нужно активизировать, вызвав функцию glEnable(GL_CONVOLUTION_1D) Параметры: target (тип GLenum) Цель фильтра. Должна быть GL_CONVOLUTION_1D internal format Компоненты пикселя, к которым будет применяться фильтр (тип GLenum) Можно указывать любой формат из перечисленных в табл 7 3 width (тип GLsizei) Ширина фильтра format Цветовой формат данных фильтра. Можно использовать любой (тип GLenum) формат из указанных в табл 7 2 type Тип данных значений, формирующих изображение фильтра (тип GLenum) Допустим любой тип данных из приведенных в табл 7 3 image (тип GLvoid*) Указатель на данные изображения фильтра Что возвращает: Ничего См. также: glConvolutionFilter2D
Глава 7. Воспроизведение изображений с помощью OpenGL 359 glConvolutionFilter2D Цель: Задать двухмерный сверточный фильтр Включаемый файл: <gl.h> Синтаксис: void glConvolutionFilter2D(GLenum target, GLenum internal format, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *image); Описание: Эта функция устанавливает двухмерный сверточный фильтр, используемый с функциями glCopyPixels, glDrawPixels, glReadPixels и glTex!mage2D Данную операцию нужно активизировать, вызвав glEnable (GL_CONVOLUTION_2D) Параметры: target (тип GLenum) Цель фильтра. Должна быть GL_CONVOLUTION_2D internal format Компоненты пикселя, к которым будет применяться фильтр. (тип GLenum) Можно указывать любой формат из перечисленных в табл. 7 2 width Ширина фильтра (тип GLsizei) height (тип GLsizei) Высота фильтра format Цветовой формат данных фильтра Можно использовать любой (тип GLenum) формат из указанных в табл 7.3 type Тип данных значений, формирующих изображение фильтра (тип GLenum) Допустим любой тип данных из приведенных в табл 7 3 image Указатель на данные изображения фильтра (тип GLvoid*) Что возвращает: Ничего См. также: glConvolutionFilterlD glConvolutionParameter Цель: Задать параметры операции свертки Включаемый файл: <gl.h> Синтаксис: glConvolutionParameteri(GLenum target, GLenum pname, GLint param); glConvolutionParameterf(GLenum target, GLenum pname, GLfloat param); glConvolutionParameteriv(GLenum target, GLenum pname, GLint *params); glConvolutionParameterfv(GLenum target, GLenum pname, GLfloat *pa rams);
360 Часть I Классический OpenGL Описание: Эта функция устанавливает параметры, влияющие на операцию с целевым сверточным фильтром Параметры: target (тип GLenum) Сверточный фильтр, параметр которого устанавливается Должен иметь значение GL_CONVOLUTION_1D, GL_CONVOLUTION_2D ИЛИ GL_SEPARABLE_2D pname (тип GLenum) Извлекаемый параметр свертки Должен иметь значение GL_CONVOLUTION_BORDER_COLOR, GL_CONVOLUTION_BORDER_MODE, GL_CONVOLUTION_FILTER_SCALE ИЛИ GL_CONVOLUTION_FILTERJ3IAS param (тип GLint или GLfloat) params (тип GLint* или GLfloat*) Что возвращает: Значение устанавливаемого параметра Указатель на ячейку памяти, содержащую устанавливаемые значения параметров Ничего См. также: glGetConvolutionParameter glCopyColorSubTable Цель: Заменить фрагмент таблицы цветов данными из буфера цветов Включаемый файл: <gl.h> Синтаксис: void glCopyColorSubTable(GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); Описание: Эта функция замещает позиции таблицы цветов, используя значения, считанные из буфера цветов Параметры: target (тип GLenum) Обрабатываемая таблица цветов Возможны следующие значения: GL_COLOR_TABLE, GL_POST_CONVOLUTION_COLOR_TABLE ИЛИ GL_POST_COLOR_MATRIX_COLOR_TABLE start (тип GLsizei) X, у (тип GLsizei) width (тип GLsizei) Что возвращает: Смещение от начала таблицы цветов до позиции, с которой следует начинать замещение Координаты х и у позиции в буфере цветов, с которой начинается извлечение данных Число замещаемых позиций таблицы цветов Ничего См. также: glColorTable, glColorSubTable, glCopyColorTable
Глава 7 Воспроизведение изображений с помощью OpenGL 361 glCopyColorTable Цель: Создать новую таблицу цветов, используя данные из буфера цветов Включаемый файл: <gl,h> Синтаксис: void glCopyColorTable(GLenum target, GLenum internalFormat, GLint x, GLint y, GLsizei width); Описание: Подобно glColorTable, эта функция создает новую таблицу цветов. При этом она считывает значения таблицы цветов из буфера цветов, а не из заданного пользователем буфера данных Параметры: target (тип GLenum) Обрабатываемая таблица цветов. Возможны следующие значения: GL_COLOR_TABLE, GL_POST_CONVOLUTION_COLOR_TABLE ИЛИ GL_POST_COLOR_MATRIX_COLOR_TABLE internalFormat (тип GLenum) Внутренний формат OpenGL информации о цвете Может использоваться тот же набор значений, что применяется в функции glColorTable (см. табл. 7 2) х, У (тип GLint) width (тип GLsizei) Что возвращает: Позиция буфера цветов, с которой начинается считывание значений таблицы цветов Число загружаемых кодов цвета Ничего См. также: glColorTable, glCopyColorSubTable glCopyConvolutionFilterl D Цель: Согласно информации из буфера цветов определить одномерный сверточный фильтр Включаемый файл: <gl.h> Синтаксис: void glCopyConvolutionFilterlD(GLenum target, GLenum internalFormat, GLint x, GLint y, Glsizei width); Описание: Используя данные, считанные из буфера цветов, эта функция загружает одномерный сверточный фильтр Параметры: target (тип GLenum) Цель фильтра Параметр должен иметь значение GL_CONVOLUTION_1D
362 Часть I Классический OpenGL ТАБЛИЦА 7.7. Типы значений пикселей Тип Описание GL_COLOR Значения буфера цветов GL_STENCIL Значения буфера трафарета GL_DEPTH Значения буфера глубин internal format (тип GLenum) (тип GLint) width (тип GLsizei) Что возвращает: См. также: Компоненты-пиксели, к которым будет применяться фильтр. Могут использоваться любые из перечисленных в табл. 7 2 форматов Координаты х и у позиции в буфере цветов, задающей начало данных, касающихся сверточного фильтра Ширина сверточного фильтра в пикселях Ничего glConvolutionFilterlD, glCopyConvolutionFilter2D glCopyConvolutionFilter2D Цель: Согласно информации из буфера цветов определить двухмерный сверточный фильтр Включаемый файл: <gl.h> Синтаксис: void glCopyConvolutionFilter2D(GLenum target, Описание: GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height); Используя данные, считанные из буфера цветов, эта функция Параметры: target загружает двухмерный сверточный фильтр Цель фильтра. Параметр должен иметь значение (тип GLenum) GL_CONVOLUTION_2 D internal format Компоненты-пиксели, к которым будет применяться фильтр (тип GLenum) Могут использоваться любые из перечисленных в табл. 7 2 х, у форматов Координаты х и у позиции в буфере цветов, задающей начало (тип GLint) данных, касающихся сверточного фильтра width Ширина сверточного фильтра в пикселях (тип GLsizei) height Высота сверточного фильтра в пикселях (тип GLsizei) Что возвращает: Ничего См. также: glConvolutionFilter2D, glCopyConvolutionFilterlD
Глава 7 Воспроизведение изображений с помощью OpenGL 363 glCopyPixels Цель: Скопировать блок пикселей в буфер кадров Включаемый файл: <gl.h> Синтаксис: void glCopyPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type); Описание: Эта функция копирует информацию о пикселях из указанной области в буфере кадров в текущее растровое положение. Для установки текущего растрового положения применяется функция glRasterPos или glWindowPos Если текущее растровое положение неприемлемое, информация о пикселях не копируется На действие glCopyPixels влияют вызовы функций glDrawBuffer, glPixelMap, glPixelTransfer, glPixelZoom, glReadBuffer и подмножество построения изображений Параметры: х (тип GLint) у (ТИП GLint) width (тип GLsizei) Горизонтальная координата левого нижнего угла окна Вертикальная координата левого нижнего угла окна Ширина изображения в пикселях Если значение отрицательное, изображение рисуется справа налево. По умолчанию изображение рисуется слева направо height (тип GLsizei) Высота изображения в пикселях Если значение отрицательное, изображение рисуется сверху вниз По умолчанию изображение рисуется снизу вверх type (тип GLenum) Что возвращает: Источник копируемых значений пикселей Могут использоваться любые типы пикселей из табл 7 7 Ничего См. также: glDrawBuffer, glPixelMap, glPixelStore, glPixelTransfer, glPixelZoom, glReadBuffer glDrawPixels Цель: Нарисовать блок пикселей в буфере кадров Включаемый файл: <gl.h> Синтаксис: void glDrawPixels(GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); Описание: Эта функция копирует пиксели из памяти в текущее растровое положение Для установки текущего растрового положения применяется функция glRasterPos или glWindowPos Если текущее растровое положение неприемлемое, информация о пикселях нс копируется Помимо аргументов формата и типа существует несколько параметров, определяющих кодировку информации о пикселях в памяти и контролирующих обработку данных перед их помещением в буфер цветов
364 Часть I Классический OpenGL Параметры: width Ширина изображения в пикселях (тип GLsizei) height (тип GLsizei) Высота изображения в пикселях Если значение отрицательное, изображение рисуется сверху вниз. По умолчанию изображение рисуется снизу вверх format (тип GLenum) type (тип GLenum) pixels (тип void*) Что возвращает: Пространство цветов изображаемых пикселей Могут использоваться любые значения из табл. 7 2 Тип данных компонентов цвета Допустимые типы данных перечислены в табл 7 3 Указатель на информацию о пикселях изображения Ничего См. также: gIDrawBuffer, glPixelMap, glPixelStore, glPixelTransfer, glPixelZoom gIGetConvolutionFilter Цель: Извлечь текущий сверточный фильтр Включаемый файл: <gl.h> Синтаксис: void gIGetConvolutionFilter(GLenum target, GLenum format, GLenum type, void *data); Описание: Эта функция позволяет запрашивать и считывать текущий сверточный фильтр. Для хранения ядра сверточного фильтра, в *data вы должны выделить достаточно памяти Параметры: target (тип GLenum) format (тип GLenum) Извлекаемый сверточный фильтр Параметр должен иметь значение GL_CONVOLUTION_1D либо GL_CONVOLUTION_2D Желательный пиксельный формат сверточных данных Могут использоваться любые из перечисленных в табл 7 2 форматов, исключая GL_STENCIL_INDEX И GL_DEPTH_COMPONENT type (тип GLenum) data (тип void*) Что возвращает: Тип данных памяти для хранения данных Могут использоваться любые типы данных из табл 7 3 Указатель на буфер, получающий данные сверточного фильтра Ничего См. также: glConvolutionFilterlD, glConvolutionFilter2D, glGetConvolutionParameter
Глава 7 Воспроизведение изображений с помощью OpenGL 365 gIGetConvolutionParameter Цель: Запросить различные параметры свертки Включаемый файл: <gl.h> Варианты: void glGetConvolutionParameteriv(GLenum target, GLenum pname, GLint* params); void glGetConvolutionParameterfv(GLenum target, GLenum pname, GLfloat* params); Описание: Эта функция позволяет запрашивать у OpenGL текущее состояние всех действующих параметров свертки Параметры: target (тип GLenum) Извлекаемый сверточный фильтр. Должен иметь значение GL_CONVOLUTION_1D, GL_CONVOLUTION_2D ИЛИ GL_SEPARABLE_2D pname (тип GLenum) Извлекаемый параметр свертки Должен иметь одно из следующих значений- GL_CONVOLUTION_BORDER_COLOR, GL_CONVOLUTION_BORDER_MODE, GL_CONVOLUTION_FILTER_SCALE, GL_CONVOLUTION_FILTER_BIAS, GL_CONVOLUTION_FORMAT, GL_CONVOLUTION_WIDTH, GL_CONVOLUTION_HEIGHT, GL_MAX_CONVOLUTION_WIDTH ИЛИ GL_MAX_CONVOLUTION_HEIGHT params (тип GLint* или GLfloat*) Что возвращает: Указатель на память, получающую результаты запроса, заданные параметром pname Ничего См. также: glConvolutionFilterlD, glConvolutionFilter2D, glConvolutionParameter gIGetColorTable Цель: Извлечь содержимое текущей таблицы цветов Включаемый файл: <gl. h> Синтаксис: void gIGetColorTable(GLenum target, GLenum format, GLenum type, void *table); Описание: Эта функция позволяет извлекать содержимое любой текущей таблицы цветов Если требуется, данные таблицы цветов преобразовываются в нужный формат Параметры: target (тип GLenum) format (тип GLenum) Загружаемая таблица цветов Допустимые значения перечислены в табл 7.6 Формат информации о пикселях. Допустимые форматы перечислены в табл 7.2
366 Часть I Классический OpenGL type (тип GLenum) table (тип void*) Что возвращает: Тип данных памяти. Допустимые типы данных перечислены в табл 7.3 Указатель на буфер, в который копируется таблица цветов Ничего См. также: glColorTable glGetColorTableParameter Цель: Извлечь значения настроечных параметров таблицы цветов Включаемый файл: <gl.h> Варианты: void glGetColorTableParameteriv(GLenum target, GLenum pname, GLint* params); void glGetColorTableParameterfv(GLenum target, GLenum pname, GLfloat* params); Описание: Эта функция позволяет позволяет запрашивать значения любых настроечных параметров таблицы цветов Результаты могут считываться в форме целых чисел или значений с плавающей запятой Параметры: target (тип GLenum) pname (тип GLenum) Одно из имен таблицы цветов, перечисленных в табл. 7.6 Один из перечисленных ниже параметров таблицы цветов: GL_COLOR_TABLE_SCALE, GL_COLOR_TABLE_B I AS, GL_COLOR_TABLE_FORMAT, GL_COLOR_TABLE_WIDTH, GL_COLOR_TABLE_RED_SIZE, GL_COLOR_TABLE_GREEN_SIZE, GL_COLOR_TABLE_BLUE_SIZE, GL_COLOR_TABLE_ALPHA_SIZE, GL_COLOR_TABLE_LUMINANCE_SIZE, GL_COLOR_TABLE_INTENSITY_SIZE params (тип GLint* или GLfloat*) Что возвращает: Адрес переменной, получающей извлеченное значение параметра Ничего См. также: glColorTableParameter gIGetHistogram Цель: Получить собранную статистику гистограммы Включаемый файл: <gl.h> Синтаксис: void gIGetHistogram(GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid ★values);
Глава 7 Воспроизведение изображений с помощью OpenGL 367 Описание: Эта функция извлекает информацию гистограммы, собранную после предшествовавшего вызова функции glHistogram Кроме того, предварительно требуется активизировать сбор гистограммы (параметр активизации GL_HISTOGRAM) Параметры: target (тип GLenum) reset (тип GLboolean) format (тип GLenum) type (тип GLenum) values (тип void*) Целевой буфер Параметр должен иметь значение GL_HISTOGRAM Метка, указывающая, должны ли статистические данные гистограммы очищаться после копирования в значения Формат хранения счетчиков цветов Допустимы любые значения из перечисленных в табл. 7 2 Тип данных памяти для хранения счетчиков цветов Можно использовать любое значение из табл 7 3 Указатель на буфер, получающий статистические данные гистограммы. Для хранения данных гистограммы должно выделяться достаточное место Что возвращает: Ничего См. также: glHistogram, glResetHistogram gIGetHistogramParameter Цель: Извлечь информацию о текущих параметрах гистограммы Включаемый файл: <gl.h> Варианты: void glGetHistogramParameteriv(GLenum target, GLenum pname, GLint* params); void glGetHistogramParameterfv(GLenum target, GLenum pname, GLfloat* params); Описание: Данные функции извлекают информацию о настройках текущей гистограммы Параметр GL_HISTOGRAM_SINK не может использоваться вместе с GL_PROXY_HISTOGRAM. Чтобы определить, может ли конкретная гистограмма использовать с текущими системными ресурсами, применяются представители (proxies) гистограммы Параметры: target (тип GLenum) Целевой буфер гистограммы, о которой собирается информация Параметр должен иметь значение GL_HISTOGRAM ИЛИ GL_PROXY_HISTOGRAM pname (тип GLenum) Извлекаемый параметр Возможны следующие значения- GL_HISTOGRAM_FORMAT, GL_HISTOGRAM_WIDTH, GL_HISTOGRAM_RED_SIZE, GL_HISTOGRAM_GREEN_SIZE, GL_HISTOGRAM_BLUE_SIZE, GL_HISTOGRAM_ALPHA_SIZE, GL_HISTOGRAM_LUMINANCE_SIZE, GL_HISTOGRAM_SINK
368 Часть I Классический OpenGL params (тип GLint* или GLfloat*) Что возвращает: Адрес переменной, получающей запрошенный параметр Ничего См. также: glHistogram, glGetHistogram glGetMinmax Цель: Получить минимальный и максимальный код цвета Включаемый файл: <gl,h> Синтаксис: void glGetMinmax(GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); Описание: Эта функция получает минимальный и максимальный используемый код цвета Параметры: target (тип GLenum) reset (тип GLboolean) format (тип GLenum) type (тип GLenum) values (тип GLvoid*) Что возвращает: Целевой буфер для записи минимума/максимума Параметр должен иметь значение GL_MINMAX Значение GL_TRUE обновляет минимальное и максимальное значения в буфере Формат массива значений (табл 7 2) Тип значений в массиве (табл 7.3) Указатель на массив значений Ничего См. также: glMinMax, glResetMinMax gIGetSeparableFilter Цель: Извлечь содержимое текущего сепарабельного фильтра Включаемый файл: <gl.h> Синтаксис: void gIGetSeparableFilter(GLenum target, GLenum format, GLenum type, void *row, void *column, const GLvoid *span); Описание: Эта функция позволяет извлекать размер и содержимое текущего сепарабельного сверточного фильтра Параметры: target (тип GLenum) format (тип GLenum) Извлекаемый сепарабельный фильтр. Параметр должен иметь значение GL_SEPARABLE_2D Желательный формат значений Допустимые значения перечислены в табл 7 2
Глава 7 Воспроизведение изображений с помощью OpenGL 369 type (тип GLenum) row, column (тип void*) span (тип void*) Что возвращает: Тип данных объекта-хранилища информации. Допустимые значения перечислены в табл 7 3 Целевые буферы для записи сверточных фильтров по строкам и столбцам Параметр зарезервирован для будущего использования Ничего См. также: glSeparableFilter2D glHistogram Цель: Определить, как следует хранить статистические данные гистограммы Включаемый файл: <gl.h> Синтаксис: void glHistogram(GLenum target, GLsizei width, GLenum internalFormat, GLboolean sink}; Описание: Эта функция определяет, как OpenGL должен собирать и хранить информацию гистограммы Если значение target равно GL_PROXY_HISTOGRAM, чтобы посмотреть, достаточно ли системных ресурсов для удовлетворения запроса, можно использовать функцию glGetHistogramParameter Параметры: target (тип GLenum) width (тип GLsizei) internalFormat (тип GLenum) Целевой буфер гистограммы Параметр должен иметь значение GL_HISTOGRAM либо GL_PROXY_HISTOGRAM Число позиций в таблице гистограммы Значение должно быть степенью 2 Как должна храниться гистограмма Допускаются следующие значения GL_ALPHA, GL_ALPHA4, GL_ALPHA8, GL_ALPHA12, GL_ALPHA16, GL_LUMINANCE, GL_LUMINANCE4, GL_LUMINANCE8, GL_LUMINANCE12, GL_LUMINANCE16, GL_LUMINANCE_ALPHA, GL_LUMINANCE4_ALPHA4, GL_LUMINANCE6_ALPHA2, GL_LUMINANCE8_ALPHA8, GL_LUMINANCE12_ALPHA4, GL_LUMINANCE12_ALPHA12, GL_LUMINANCE16_ALPHA16, GL_RGB, GL_R3_G3_B2, GL_RGB4, GL_RGB5, GL_RGB8, GL_RGB10, GL_RGB12, GL_RGB16, GL_RGBA, GL_RGBA2, GL_RGBA4, GL_RGB5_A1, GL_RGBA8, GL_RGB10_A2, GL_RGBA12, GL_RGBA16 sink (тип GLboolean) Что возвращает: Метка, определяющая, продолжают ли использоваться пиксели в конвейере построения изображений Ничего См. также: gIGetHistogram, glResetHistogram
370 Часть ( Классический OpenGL gIMinmax Цель: Инициализировать буфер минимума/максимума Включаемый файл: <gl,h> Синтаксис: void gIMinmax(GLenum target, GLenum Описание: internalFormat, GLboolean sink}; Эта функция инициализирует буфер минимума/максимума Параметры: target Значение должно быть равно GL_MINMAX (тип GLenum) internalFormat Внутренний формат массива значений Допустимые значения (тип GLenum) совпадают с форматами функции glHistogram sink Метка, определяющая, передаются ли пиксели дальше по (тип GLboolean) конвейеру построения изображений Что возвращает: Ничего См. также: glGetMinMax, glResetMinMax gIPixelMap Цель: Определить таблицу соответствий для переноса пикселей Включаемый файл: <gl.h> Варианты: void glPixelMapfv(GLenum map, GLint mapsize, const GLfloat *values}; void glPixelMapuiv(GLenum map, GLint mapsize, const GLuint *values}; void glPixelMapusv(GLenum map, GLint mapsize, const GLushort *values}; Описание: Эта функция устанавливает таблицы соответствий для функций glCopyPixels, glDrawPixel, glReadPixels, glTexImagelD и glTex!mage2D. Эти таблицы соответствий (или карты) используются только в том случае, если с помощью glPixelTransfer активизирована соответствующая опция GL_MAP_COLOR или GL_MAP_STENCIL Карты применяются перед рисованием и после считывания значений из буфера кадров Параметры: тар (тип GLenum) Тип определяемой карты Допустимые значения перечислены в табл. 7 5 mapsize (тип GLint) values (тип GLfloat* ИЛИ GLuint*, или GLushort*) Что возвращает: Размер таблицы соответствий Таблица соответствий Ничего
Глава 7 Воспроизведение изображений с помощью OpenGL 371 См. также: glCopyPixels, glDrawPixels, glPixelStore, glPixelTransfer, glReadPixels, glTexImagelD, glTex!mage2D glPixelStore Цель: Проконтролировать, как пиксели хранятся и считываются из памяти Включаемый файл: <gl.h> Варианты: void glPixelStorei(GLenum pname, GLint param); Описание: void glPixelStoref(GLenum pname, GLfloat param); Эта функция управляет хранением пикселей с помощью Параметры: pname glReadPixels и считыванием с помощью glDrawPixels, glTexImagelD и glTex!mage2D. Не влияет на работу функции glCopyPixels Устанавливаемый параметр. Должен иметь значение, указанное (тип GLenum) в табл. 7.8 param (тип GLint Значение параметра. Допустимые значения различных или GLfloat) параметров приведены в табл. 7.8 Что возвращает: Ничего См. также: glDrawPixels, glReadPixels, glTexImagelD, glTexImage2D glPixelTransfer Цель: Установить режимы переноса пикселей Включаемый файл: <gl.h> Синтаксис: void glPixelTransfer!(GLenum pname, GLint param); void glPixelTransferf(GLenum pname, GLfloat param); Описание: Эта функция устанавливает режимы переноса пикселей для функций glCopyPixels, glDrawPixels, glReadPixels, glTexImagelD и glTexImage2D Параметры: pname Устанавливаемый параметр переноса. Возможны любые (тип GLenum) значения из табл. 7.4 param The parameter value (тип GLint или GLfloat) Что возвращает: Ничего См. также: glCopyPixels, glDrawPixels, glPixelMap, glReadPixels, glTexImagelD, glTexImage2D
372 Часть I Классический OpenGL ТАБЛИЦА 7.8. Типы объектов-хранилищ пикселей Значение по Имя умолчанию Описание GL PACK SWAP BYTES GL_TRUE Если значение — true, все несколькобайтовые значения при записи в память меняют положение байтов GL_PACK_LSB_FIRST GL_FALSE Если значение — true, крайний левый бит растрового образа записывается в бите 0, а нв 7 GL_PACK_ROW_LENGTH 0 Данный тип хранилища устанавливает ширину пикселя изображения Если значение равно 0, вместо него используется аргумент width GL_PACK_SKIP_PIXELS 0 Данный тип хранилища устанавливает число пропускаемых горизонтально пикселей изображения GL_PACK_SKIP_ROWS 0 Данный тип хранилища устанавливает число пропускаемых вертикально пикселей изображения GL_PACK_ALIGNMENT 4 Данный тип хранилища устанавливает выравнивание всех строк развертки изображения GL_UNPACK_SWAP_BYTES GL_TRUE Если значение — true, всего несколькобайтовые значения при считывания из памяти меняют положение байтов GL_UNPACK_LSB_FIRST GL_FALSE Если значение — true, крайний левый бит растрового образа считывается из бита 0, а не 7 GL_UNPACK_ROW_LENGTH 0 Данный тип хранилища устанавливает ширину изображения в пикселях Если значение равно 0, вместо него используется аргумент width GL_UNPACK_SKIP_PIXELS 0 Данный тип хранилища устанавливает число пропускаемых горизонтально пикселей изображения GL_UNPACK_SKIP_ROWS 0 Данный тип хранилища устанавливает число пропускаемых вертикально пикселей изображения GL_UNPACK_ALIGNMENT 4 Данный тип хранилища устанавливает выравнивание всех строк развертки изображения gIPixelZoom Цель: Включаемый файл: Синтаксис: Описание: Установить масштабирование при переносе пикселей <gl.h> void glPixelZoom(GLfloat xfactor, GLfloat yfactor) Эта функция устанавливает масштабирование пикселей для функций glCopyPixels, glDrawPixels, glReadPixels, glTexImagelD и glTex!mage2D Для масштабирования пикселей при их чтении из памяти или буфера кадров используется алгоритм ближайшего соседа Для функций glCopyPixels и glDrawPixels масштабированные пиксели рисуются в буфере кадров в текущем растровом положении
Глава 7 Воспроизведение изображений с помощью OpenGL 373 Параметры: xfactor (тип GLfloat) yfactor (тип GLfloat) Что возвращает: Коэффициент горизонтального масштабирования (10 — без изменения масштаба) Коэффициент вертикального масштабирования (1.0 — без изменения масштаба) Ничего См. также: glCopyPixels, glDrawPixels, glReadPixels, glTexImagelD, glTexImage2D gIRasterPos Цель: Установить текущее растровое положение Включаемый файл: <gl.h> Варианты: void glRasterPos2d(GLdouble x, GLdouble y); void glRasterPos2dv(GLdouble *v); void glRasterPos2f(GLfloat x, GLfloat y); void glRasterPos2fv(Glfloat *v); void glRasterPos2i(GLint x, GLint y); void glRasterPos2iv(Glint *v); void glRasterPos2s(GLshort x, GLshort y); void glRasterPos2sv(GLshort *v); void glRasterPos3d(GLdouble x, GLdouble y, GLdouble z); void glRasterPos3dv(GLdouble *v); void glRasterPos3f(GLfloat x, GLfloat y, GLfloat z); void glRasterPos3fv(GLfloat *v); void glRasterPos3i(GLint x, GLint y, GLint z); void glRasterPos3iv(GLint *v); void glRasterPos3s(GLshort x, GLshort y, GLshort z); void glRasterPos3sv(GLshort *v); void glRasterPos4d(GLdouble x, GLdouble y, GLdouble z, GLdouble w); void glRasterPos4dv(GLdouble *v); void glRasterPos4f(GLfloat x, GLfloat y, GLfloat z, GLfloat w); void glRasterPos4fv(GLfloat *v); void glRasterPos4i(GLint x, GLint y, GLint z, GLint w); void glRasterPos4iv(Glint *v); void glRasterPos4s(GLshort x, GLshort y, GLshort z, GLshort w); void glRasterPos4sv(GLshort *v);
374 Часть I Классический OpenGL Описание: Эта функция устанавливает текущее растровое положение. Поставляемые координаты преобразовываются и проектируются с участием текущей матрицы наблюдения модели и проекции, в результате превращаясь в точку в двухмерном окне. Если растровое положение лежит вне границ окна, оно неприемлемо, и растровые операции не выполняются. При установке растрового положения текущий цвет растровых изображений (glBitmap) устанавливается равным текущему цвету Параметры: X, у, Z, W (тип GLdouble или GLfloat, или GLint, или GLshort) v (тип GLdouble* ИЛИ GLfloat*, или GLint*, или GLshort*) Что возвращает: Координаты х, у, z и w растрового положения Указатель на массив, содержащий координаты растрового положения Ничего См. также: glWindowPos g I Read Pixels Цель: Считать блок пикселей из буфера кадров Включаемый файл: <gl.h> Синтаксис: void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); Описание: Эта функция копирует информацию о пикселях из буфера кадров в память Помимо аргументов формата и типа используется еще несколько параметров, определяющих кодировку данных в памяти и контролирующих обработку данных перед помещением в буфер памяти Значения пикселей модифицируются согласно настройкам текущей карты пикселей, режиму хранения пикселей, операции переноса пикселей и текущему буферу чтения Параметры: x (тип GLint) у (тип GLint) Горизонтальная координата левого нижнего угла окна Вертикальная координата левого нижнего угла окна
Глава 7 Воспроизведение изображений с помощью OpenGL 375 width (тип GLsizei) height (тип GLsizei) format (тип GLenum) type (тип GLenum) pixels (тип void*) Что возвращает: Ширина изображения в пикселях Высота изображения в пикселях Пространство цветов считываемых пикселей. Возможные значения выбираются из констант, определенных в табл. 7 2 Желательный тип данных возвращаемых пикселей. Допустимые значения перечислены в табл. 7.3 Указатель на информацию о пикселях изображения Ничего См. также: glPixelMap, glPixelStore, glPixelTransfer, glReadBuffer g I ResetH istogram Цель: Очистить буфер гистограммы Включаемый файл: <gl.h> Синтаксис: void glResetHistogram(GLenum target); Описание: Параметры: Эта функция очищает буфер гистограммы target (тип GLenum) Что возвращает: Целевой буфер гистограммы. Параметр должен иметь значение GL_HISTOGRAM Ничего См. также: gIGetHistogram, glHistogram gIResetMinmax Цель: Обновить минимальный/максимальный коды цвета Включаемый файл: <gl.h> Синтаксис: void glResetMinMax(GLenum target); Описание: Эта функция обновляет минимальный/максимальный коды цвета Параметры: target (тип GLenum) Что возвращает: Целевой буфер минимального/максимального значений. Параметр должен иметь значение GL_MINMAX Ничего См. также: glGetMinMax, glMinMax
376 Часть I. Классический OpenGL glSeparableFilter2D Цель: Определить двухмерный сепарабельный сверточный фильтр Включаемый файл: <gl.h> Синтаксис: void glSeparableFilter2D(GLenum target, GLenum internalFormat, GLsizei width, GLsizei height, GLenum format, GLenum type, ) void *row, const GLvoid *column); Описание: Эта функция задает двухмерный сепарабельный сверточный фильтр. Этот фильтр задается двумя одномерными массивами Конечный двухмерный сверточный фильтр находится как внешнее произведение двух данных массивов Параметры: target (тип GLenum) internalformat (тип GLenum) width (тип GLsizei) height (тип GLsizei) format (тип GLenum) type (тип GLenum) row (тип void*) column (тип void*) Что возвращает: Целевой сепарабельный фильтр Параметр должен иметь значение GL_separable_2D Компоненты-пиксели, к которым применяется фильтр Допустимые значения перечислены в табл. 7 2 Ширина фильтра Высота фильтра Формат цвета данных фильтра Допускается любой формат, указанный в табл 7 3 Тип данных значения, образующих изображение фильтра Можно использовать любые типы данных, указанные в табл 7 3 Массив-строка Массив-столбец Ничего См. также: glGetSeparableFilter
Гпава 7 Воспроизведение изображений с помощью OpenGL 377 glWindowPos Цель: Установить текущее растровое положение в координатах окна Включаемый файл: <gl.h> Варианты: void glWindowPos2d(GLdouble x, GLdouble y); void glWindowPos2dv(GLdouble *v); void glWinodwPos2f(GLfloat x, GLfloat y); void glWindowPos2fv(Glfloat *v); void glWindowPos2i(GLint x, GLint y); void glWindowPos2iv(Glint *v); void glWindowPos2s(GLshort x, GLshort y); void glWindowPos2sv(GLshort *v); void glWindowPos3d(GLdouble x, GLdouble y, GLdouble z); void glWindowPos3dv(GLdouble *v); void glWindowPos3f(GLfloat x, GLfloat y, GLfloat z); void glWindowPos3fv(GLfloat *v); void glWindowPos3i(GLint x, GLint y, GLint z); void glWindowPos3iv(GLint *v); void glWindowPos3s(GLshort x, GLshort y, GLshort z); void glWindowPos3sv(GLshort *v); Описание: Эта функция устанавливает текущее растровое положение в координатах окна Если заданное положение не попадает в окно, оно неприемлемо, и растровые операции не выполняются В отличие от glRasterPos данная функция не преобразовывает координаты путем действия матрицами преобразования, в позволяет задавать растровое положение непосредственно в координатах окна Параметры: х, у, Z (тип GLdouble или GLfloat, или GLint, или GLshort) v (тип GLdouble* или GLfloat*, или GLint*, или GLshort*) Что возвращает: Координаты х, у и z растрового положения в координатах окна Указатель на массив, содержащий координаты растрового положения Ничего См. также: glRasterPos

ГЛАВА 8 Наложение текстуры: основы Ричард С. Райт-мл. Из ЭТОЙ ГЛАВЫ ВЫ УЗНАЕТЕ ... Действие Загрузка текстурных изображений Наложение текстуры на геометрические объекты Изменение текстурной среды Установка параметров наложения текстуры Генерация множественных отображений Управление несколькими текстурами Функция glTexImage, glTexSublmage glTexCoord glTexEnv glTexParameter gluBuildMipmaps glBindTexture В предыдущей главе мы подробно рассмотрели основы загрузки данных изобра- жения в OpenGL. Данные изображения, не модифицированные масштабированием пикселей, обычно характеризуются взаимно однозначным соответствием между пик- селями изображения и пикселями экрана. Именно так возник сам термин пиксель (от “picture element” — элемент изображения). В данной главе мы изучим наложение изображений на трехмерные примитивы. Когда изображения применяются к геомет- рическому примитиву, мы называем их текстурой или картой текстуры. Из рис. 8.1 видно, насколько разными могут быть изображения, отличающиеся текстурной гео- метрией. Куб слева закрашен и затенен однородной поверхностью, тогда как куб справа демонстрирует богатство деталей, которые можно получить только с помо- щью наложения текстуры. Загружаемое текстурное изображение имеет определенную структуру и упорядо- чение, как и пиксельные образы, но в этом случае редко существует взаимно одно- Рис. 8.1. Сильный контраст между текстурной и нетекстурной геометрией
380 Часть I Классический OpenGL значное соответствие между /лекселями (отдельные элементы изображения в тексту- ре, от “texture element” — элемент текстуры) и пикселями экрана В данной (лаве рассмотрены основы загрузки карты текстуры в память и вес способы, которыми сс можно отобразить и применить к геометрическим примитивам Загрузка текстур Первым необходимым шагом при наложении карты текстуры на геометрический объ- ект является загрузка текстуры в память. Загруженная текстура становится частью текущего состояния текстуры (подробнее об этом ниже) Для загрузки данных тек- стуры из буфера памяти (который, например, считывает информацию из файла на диске) чаще всего используются такие функции OpenGL void glTex!magelD(GLenum target, GLint level, GLint internal format, GLsizei width, GLint border, GLenum format, GLenum type, void *data); void glTex!mage2D(GLenum target, GLsizei width, GLenum format, GLint level, GLint internal format, GLsizei height, GLint border, GLenum type, void *data); void glTex!mage3D(GLenum target, GLint level, GLint internal format, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, void *data); Эти три сравнительно длинных функции сообщают OpenGL всю информа- цию, требуемую для интерпретации данных текстуры, на которые указывает параметр data Первое, что следует знать об этих функциях, — все они, по сути, являются тремя разновидностями одной функции glTexlmage OpenGL поддерживает одно-, двух- и трехмерные карты текстуры и использует соответствующие функции для загрузки текстуры и присвоения ей статуса текущей Также следует знать, что при вызове одной из указанных функций OpenGL копирует информацию о текстуре из data Эти операции копирования данных могут быть довольно дорогими, и несколько способов решения этой проблемы предлагаются в разделе “Текстурные объекты” Аргумент target данных функций должен иметь значение GL_texture_1 D, GL_TEXTURE_2D или GL_TEXTURE_3D соответственно Кроме того, можно за- дать “представитель” proxy текстуры так же, как в примерах предыдущей главы, задействовав параметры gl_proxy_texture_1D, gl_proxy_texture_2d GL_PROXY_TEXTURE_3D и используя функцию glGetTexParameter для извлечения результатов запроса “представителя”. Параметр level задаст загруженный уровень сокращенной текстуры (mipmap) Сокращенные текстуры рассмотрены в следующем разделе, озаглавленном “Нало- жение сокращенных текстур”, поэтому для несокращенных текстур (старое доб- рое обычное наложение текстуры) значение этого параметра всегда устанавливает- ся равным О
Глава 8 Наложение текстуры основы 381 ТАБЛИЦА 8.1. Наиболее распространенные внутренние форматы текстуры Константа Значение GL_ALPHA GL_LUMINANCE GL_LUMINANCE_ALPHA GL_RGB GL_RGBA Записывать тексели как параметры альфа Записывать тексели как коды яркости Записывать тексели с кодом яркости и альфа Записывать тексели как красный, зеленый и синий компоненты Записывать тексели как красный, зеленый, синий и альфа-компонент ТАБЛИЦА 8.2. Форматы текселей в функции glTeximage Константа Описание GL_RGB GL_RGBA GL_BGR/GL_BGR_EXT GL_BGRA/GL_BGRA_EXT GL_RED GL_GREEN GL_BLUE GL_ALPHA GL_LUMINANCE Цвета в порядке “красный, зеленый, синий” Цвета в порядка “красный, зеленый, синий, альфа” Цвета в порядке “синий, зеленый, красный" Цвета в порядке “синий, зеленый, красный, альфа" Все пиксели содержат только красный компонент Все пиксели содержат только зеленый компонент Все пиксели содержат только синий компонент Все пиксели содержат только альфа-компонент Все пиксели содержат только компонент яркости (интенсивности) GL_LUMINANCE_ALPHA GL_STENCIL_INDEX GL_DEPTH_COMPONENT Все пиксели содержат яркости и альфа-компонент Все пиксели содержат только код трафарета Все пиксели содержат только код глубины Далее нужно задать параметр internal format текстурных данных. Так мы со- общаем OpenGL, сколько компонентов цвета на тексель нужно записывать, объем памяти компонентов и/или информацию о том, следует ли сжимать текстуру (см. обсуждение сжатия текстуры в следующей главе). В табл. 8.1 перечислены наи- более распространенные значения этой функции. Полный их список приводится в справочном разделе Параметры width, height и depth (где они нужны) задают размеры загружаемой текстуры Важно отметить, что эти размеры должны быть целыми степенями двой- ки — 1, 2, 4, 8, 16, 32, 64 и тд Не существует жесткого требования, чтобы карты тек- стуры были квадратными (все размеры равны), но текстуры, загруженные с размера- ми, не являющимися степенями двойки, приведут к неявной деактивизации текстуры Параметр border позволяет задавать ширину границы для карт текстуры. Грани- цы текстуры разрешают расширять ширину, высоту или глубину карты текстуры на дополнительный набор текселей, располагающихся вдоль границы Границы тексту- ры играют важную роль в обсуждении текстурной фильтрации (см. ниже). Пока же просто устанавливайте это значение равным 0. Последние три параметра (format, type и data) идентичны соответствующим аргументам функции glDrawPixels, используемой при помещении данных изобра- жения в буфер цвета Приемлемые значения констант format и type перечислены в табл 8 2 и 8 3
382 Часть I Классический OpenGL ТАБЛИЦА 8.3. Типы тексельных данных Константа Описание GL_UNSIGNED_BYTE Все компоненты цвета является 8-битовыми целыми числами без знака GL_BYTE 8-битовые числа со знаком GL_BITMAP Отдельные биты без данных о цвете; то же, что glBitmap GL_UNSIGNED_SHORT 16-битовые целые числа без знака GL_SHORT 16-битовые целые числа со знаком GL_UNSIGNED_INT 32-битовые целые числа без знака GL_INT 32-битовые целые числа со знаком GL_FLOAT Величины с плавающей запятой обычной точности GL.UNS IGNED_BYTE_3_2_2 Упакованные RGB-коды GL_UNS IGNED_BYTE_2_3_3_REV Упакованные RGB-коды GL_UNSIGNED_SHORT_5_6_5 Упакованные RGB-коды GL_UNSIGNED_SHORT_5_6_5_REV Упакованные RGB-коды GL_UNS IGNED_SHORT_4_4_4_4 Упакованные RGBA-коды GL_UNSIGNED_SHORT_4_4_4_4_REV Упакованные RGBA-коды GL_UNS IGNED_SHORT_5_5_5_1 Упакованные RGBA-коды GL_UNSIGNED_SHORT_1_5_5_5_REV Упакованные RGBA-коды GL_UNSIGNED_INT_8_8_8_8 Упакованные RGBA-коды GL_UNS IGNED_INT_8_8_8_8_REV Упакованные RGBA-коды GL_UNS IGNED_INT_10_l 0_l 0_2 Упакованные RGBA-коды GL_UNSIGNED_INT_2_10_l0_l0_REV Упакованные RGBA-коды Загруженные текстуры не применяются к геометрическим объектам, если не акти- визировано соответствующее состояние текстуры. Чтобы включить или выключить данное состояние текстуры, вызывается функция glEnable или glDisable с пара- метром GL_TEXTURE_1D, GL_TEXTURE_2D ИЛИ GL_TEXTURE_3D. В каждый момент времени может быть активизировано только одно из этих состояний (см обсуждение множественного наложения текстуры в следующей главе). Тем не менее стоит при- учиться отключать неиспользуемые состояния текстуры. Например, чтобы переклю- чаться между одно- и двухмерным текстурированием, следует вызывать следующие функции: glDisable(GL_TEXTURE_1D); glEnable(GL_TEXTURE_2D); И еще одно замечание относительно загрузки текстур: данные текстуры, загружен- ные функцией glTexImage, проходят через тот же конвейер преобразований пикселей и воспроизведения изображений, что описывался в предыдущей главе. Это означает, что к загруженным данным текстуры применяются упаковка пикселей, масштабиро- вание пикселей, таблицы цветов, свертки и т.д. Использование буфера цвета Одно- и двухмерные текстуры также можно загрузить, используя данные из буфера цвета. С помощью указанных ниже двух функций изображение можно считать из буфера цвета и использовать его как новую текстуру.
Глава 8 Наложение текстуры основы 383 void glCopyTex!magelD(GLenum target, GLint level, GLenum internal format, GLint x, GLint y, GLsizei width, GLint border); void glCopyTex!mage2D(GLenum target, GLint level, GLenum internal format, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); Данные функции похожи на glTexImage, но здесь х и у задают положение в бу- фере цвета, с которого начинается чтение данных текстуры Исходный буфер задастся с использованием glReadBuffer и ведет себя так же, как glReadPixels Обновление текстуры Многократная загрузка новых текстур может стать критическим параметром произ- водительности в таких приложениях реального времени, как игры или имитаторы. Если загруженная карта текстуры уже не требуется, ее можно заменить целиком или частично Отметим, что замещение карты текстуры часто можно выполнить гораздо быстрее, чем загрузку новой текстуры с помощью glTexImage В данной ситуации используется функция glTexSublmage, которая также имеет три варианта. void glTexSub!magelD(GLenum target, GLint level, GLint xOffset, GLsizei width, GLenum format, GLenum type, const GLvoid *data); void glTexSub!mage2D(GLenum target, GLint level, GLint xOffset, GLint yOffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid Mata); void glTexSubImage3D(GLenum target, GLint level, GLint xOffset, GLint yOffset, GLint zOffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *data); Обычно аргументы точно соответствуют параметрам, используемым в функции glTexImage Параметры xOffset, yOffset и zOffset задают смещения в существу- ющей карте текстуры, с которого начинается замещение данных текстуры Значения width, height и depth задают размеры текстуры, которая “вставляется” в существу- ющую текстуру Последний набор функций (разновидности glCopyTexSublmage) позволяет ком- бинировать чтение из буфера цвета с вводом или замещением части текстуры void glCopyTexSubImagelD(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); void glCopyTexSub!mage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); void glCopyTexSub!mage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, Glint zoffset, GLint x, GLint y, GLsizei width, GLsizei height);
384 Часть I. Классический OpenGL (0^0).........(0 5) । । । । । (10) •----------------------------> Текстурные координаты s Одномерные текстурные координаты Рис. 8.2. Адресация текселей с помощью текстурных координат Возможно, вы отметили, что в приведенном списке нет функции glCopyTexIm- age3D. Это объясняется тем, что буфер цвета является двухмерным, поэтому не су- ществует соответствующего способа использовать двухмерное цветное изображение как источник трехмерной текстуры Тем не менее с помощью glCopyTexSubImage3D данные буфера можно использовать для задания плоскости текселей в трехмерной текстуре Отображение текстур на геометрические объекты Загрузка текстуры и активизация текстурирования указывают OpenGL применять текстуру ко всем примитивам OpenGL. При этом следует предоставить OpenGL ин- формацию о том, как наложить текстуру на геометрический объект. Для этого зада- ются текстурные координаты всех вершин Обращение к текселям карты текстуры выполняется не как к ячейкам памяти (как было для пиксельных образов), а как к аб- страктным текстурным координатам (обычно — значения с плавающей запятой). Как правило, текстурные координаты задаются в виде значений с плавающей запятой, принадлежащих диапазону 0-1 Текстурные координаты обозначаются s, t, i и q (по- добно координатам вершины х, у, z и ш), поддерживаются одно-, двух- и трехмерные координаты, также предусмотрена возможность масштабирования координат На рис 8 2 показаны одно-, двух- и трехмерные текстуры, а также их упорядочение относительно текселей Поскольку четырехмерных текстур не существует, вы можете поинтересоваться, для чего нужна координата q. Координата q соответствует геометрической координате w Это масштабный множитель, применяющийся к остальным текстурным коорди- натам; те реальные значения, используемые в качестве текстурных координат, равны s/q, t/q и r/q По умолчанию q равно 1 0
Глава 8 Наложение текстуры основы 385 Рис. 8.3. Наложение двухмерной текстуры на квадрат Текстурные координаты задаются с помощью функции glTexCoord. Весьма по- хожая на функции координат вершин, нормалей поверхностей и кодов цвета, gl- TexCoord имеет множество разновидностей, перечисленных в справочном разделе Ниже приведены три простейших варианта, используемых в наших демонстрацион- ных программах. void glTexCoordlf(GLfloat s); void glTexCoord2f(Glfloat s, GLfloat t); void glTexCoord3f(GLfloat s, GLfloat t, GLfloat r); При использовании данных функций со всеми вершинам соотносится одна тек- стурная координата Затем OpenGL при необходимости растягивает или сжимает тек- стуру, чтобы наложить ее на геометрический объект (Растяжение или сжатие при- меняется с использованием текущего фильтра текстуры, данный вопрос рассмотрен немного ниже). На рис 8.3 приведен пример двухмерной текстуры, отображаемой на примитив GL_QUAD Обратите внимание на то, что углы текстуры соответствуют уг- лам квадрата Помните, что точно так же, как другие свойства вершины (материалы, нормали и т.д.), текстурные координаты вы должны задать до собственно вершины' К сожалению, вам редко встретится такое прекрасное соответствие, чтобы квадрат- ная текстура накладывалась на квадратный объект. Чтобы помочь вам лучше понять текстурные координаты, на рис 8 4 предоставлен другой пример На данном рисунке также изображена квадратная карта текстуры, однако геометрический объект на этот раз является треугольником На карту текстуры наложены текстурные координаты, соответствующие точкам текстуры, переходящим в вершины треугольника Матрица текстуры Текстурные координаты также можно преобразовывать с помощью.матрицы тексту- ры Стек матриц текстуры действует точно так же, как стек любых других матриц, обсуждавшихся ранее (наблюдения модели, проекции и цвета). Чтобы сделать матри-
386 Часть I Классический OpenGL (0 5 l) Рис. 8.4. Применение фрагмента карты текстуры к треугольнику цу текстуры целью вызова матричной функции, вызывается функция glMatrixMode с аргументом GL_TEXTURE glMatrixMode(GL_TEXTURE); В спецификациях OpenGL требуется, чтобы стек матрицы текстуры имел глубину всего два элемента (для использования glPushMatrix и glPopMatrix) Текстурные координаты можно транслировазь, масштабировать и даже поворачивать Пример наложения двухмерной текстуры Загрузка текстуры и предоставление текстурных координат являются необходимыми требованиями наложения текстуры Кроме этого существует еще несколько вопросов, которые следует рассмотреть — намотка координат, текстурные фильтры и текстурная среда Что эти термины означают, и как ими пользоваться'’ Сделаем пока небольшую паузу и разберем простой пример применения двухмерной текстуры В приведенном ниже коде используются функции, которые мы уже рассматривали, и несколько но- вых На основе данного примера мы и опишем указанные дополнительные вопросы наложения текстуры В листинге 8 1 приведен весь код программы PYRAMID, рисующей простую освещенную четырехгранную пирамиду, составленную из треугольников На каждую грань пирамиды и ее основание наложена текстура камня Пирамиду можно вращать с помощью клавиш со стрелками так же, как во всех примерах из предыдущих глав Результат выполнения программы PYRAMID показан на рис 8 5 Листинг 8.1. Исходный код программы PYRAMID // Pyramid.с // Демонстрирует простое наложение текстуры // OpenGL SuperBible
Глава 8. Наложение текстуры: основы 38 Рис. 8.5. Результат выполнения программы PYRAMID / Richard S. Wright Jr. include /Common/OpenGLSB.h" / Активизируются необходимые возможности системы и OpenGL include /Common/GLTools.h" // GLTools / Величины поворота tatic GLfloat xRot = O.Of; tatic GLfloat yRot = O.Of; / Меняется наблюдаемый объем и поле просмотра. / Вызывается при изменении размеров окна oid Changesize(int w, int h) { GLfloat fAspect; // Предотвращает деление на нуль if(h == 0) h = 1; I/ Размер поля просмотра устанавливается равным размеру окна glViewport(0, 0, w, h); fAspect = (GLfloat)w/(GLfloat)h; // Обновляется система координат glMatrixMode(GL_PROJECTION); glLoadldentity(); // Генерируется перспективная проекция gluPerspective(35.Of, fAspect, 1.0, 40.0); glMatrixMode(GL_MODELVIEW); glLoadldentity();
388 Часть I Классический OpenGL 11 Эта функция выполняет необходимую инициализацию в контексте // визуализации. В данном случае она устанавливает и инициализирует // освещение на сцене void SetupRC() { GLubyte *pBytes; GLint iWidth, iHeight, iComponents; GLenum eFormat; // Коды и координаты источников света GLfloat whiteLight[] = { 0.05f, 0.05f, 0.05f, l.Of }; GLfloat sourceLight[] = { 0.25f, 0.25f, 0.25f, l.Of ); GLfloat lightPos[] = { -10.f, 5.Of, 5.Of, l.Of }; glEnable(GL_DEPTH_TEST); // Удаление скрытых поверхностей glFrontFace(GL_CCW); // Многоугольники с обходом против часовой стрелки // направлены наружу glEnable(GL_CULL_FACE); // Внутри пирамиды расчеты не производятся // Активизируется освещение glEnable(GL_LIGHTING); // Устанавливается и активизируется источник света 0 glLightModelfv(GL_LIGHT_MODEL_AMBIENT,whiteLight); glLightfv(GL_LIGHTO,GL_AMBIENT,sourceLight); glLightfv(GL_LIGHTO,GL_DIFFUSE,sourceLight); glLightfv(GL_LIGHTO,GL_POSITION, lightPos); glEnable(GL_LIGHT0); // Активизирует согласование цветов glEnable(GL_COLOR_MATERIAL); // Свойства материалов соответствуют кодам glColor glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE); 11 Темно-синий фон glClearColor(0.Of, O.Of, O.Of, l.Of ); // Загружается текстура glPixelStorei(GL_UNPACK_ALIGNMENT, 1); pBytes = gltLoadTGA("Stone.tga", &iWidth, &iHeight, &iComponents, &eFormat); glTex!mage2D(GL_TEXTURE_2D, 0, iComponents, iWidth, iHeight, 0, eFormat, GL_UNSIGNED_BYTE, pBytes); free(pBytes); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) ; glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glEnable(GL_TEXTURE_2D); } // Реакция на нажатие клавиш со стрелками void SpecialKeys(int key, int x, int y)
Глава 8 Наложение текстуры основы if(key == GLUT_KEY_UP) xRot-= 5. Of; if(key == GLUT_KEY_DOWN) xRot += 5.Of; if(key == GLUT_KEY_LEFT) yRot -= 5. Of; if(key == GLUT_KEY_RIGHT) yRot += 5. Of; xRot = (GLfloat)((const int)xRot % 360); yRot = (GLfloat)((const int)yRot % 360); // Обновляет окно glutPostRedisplay(); ) Вызывается для рисования сцены id RenderScene(void) { GLTVector3 vNormal; GLTVector3 vCorners{5] = {{ O.Of, ,80f, O.Of), // Верхняя вершина 0 { -0.5f, O.Of, -,50f}, // Левая задняя вершина 1 { 0.5f, O.Of, -0.50f}, // Правая задняя вершина 2 { 0.5f, O.Of, 0.5f), // Правая передняя вершина 3 { -0.5f, O.Of, 0.5f}); // Левая передняя вершина 4 // Очищаем окно текущим цветом очистки glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Записывается состояние матрицы и выполняются повороты glPushMatrix(); // Объекты перемещаются назад, и выполняется // поворот на месте glTranslatef(0.Of, -0.25f, -4.Of); glRotatef(xRot, l.Of, O.Of, O.Of); glRotatef(yRot, O.Of, l.Of, O.Of); // Рисуется пирамида glColor3f(l Of, l.Of, l.Of); glBegin(GL_TRIANGLES); // Нижний участок (два треугольника) glNormal3f(O.Of, -l.Of, O.Of); glTexCoord2f(l.Of, l.Of); glVertex3fv(vCorners[2] ); glTexCoord2f(O.Of, O.Of); glVertex3fv(vCorners[4]); glTexCoord2f(0.Of, l.Of); glVertex3fv(vCorners[1]); glTexCoord2f(1.Of, l.Of); glVertex3fv(vCorners[2]); glTexCoord2f(1.Of, O.Of); glVertex3fv(vCorners[3]); glTexCoord2f(0.Of, O.Of); glVertex3fv(vCorners[4]);
390 Часть I Классический OpenGL // Передняя грань gltGetNormalVector(vCorners[0], vCorners[4], vCorners[3], vNormal); glNormal3fv(vNormal); glTexCoord2f(0.5f, l.Of); glVertex3fv(vCorners[0]); glTexCoord2f(0.Of, O.Of); glVertex3fv(vCorners[4]); glTexCoord2f(1.Of, O.Of); glVertex3fv(vCorners[3]); // Левая грань gltGetNormalVector(vCorners[0], vCorners[l], vCorners[4], vNormal); glNormal3fv(vNormal); glTexCoord2f(0.5f, l.Of); glVertex3fv(vCorners[0]); glTexCoord2f(0.Of, O.Of); glVertex3fv(vCorners[1]); glTexCoord2f(l.Of, O.Of); glVertex3fv(vCorners[4]) ; // Задняя грань gltGetNormalVector(vCorners[0], vCorners[2], vCorners[l], vNormal); gINormal3fv(vNormal); glTexCoord2f(0.5f, l.Of); glVertex3fv(vCorners[0]); glTexCoord2f(O.Of, 0 Of); glVertex3fv(vCorners[2] ) ; glTexCoord2f(1.Of, O.Of); glVertex3fv(vCorners[l]) ; // Правая грань gltGetNormalVector(vCorners[0], vCorners[3], vCorners[2], vNormal); glNormal3fv(vNormal); glTexCoord2f(0.5f, l.Of); glVertex3fv(vCorners[0]); glTexCoord2f(O.Of, O.Of); glVertex3fv(vCorners[3]); glTexCoord2f(1.Of, O.Of); glVertex3fv(vCorners[2]); glEnd(); // Восстанавливается состояние матрицы glPopMatrix(); // Переключение буферов glutSwapBuffers(); } int mainfint argc, char *argv[]) { glutlnit(&argc, argv); glutlnitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); glutlnitWindowSize(800, 600); glutCreateWindow("Textured Pyramid"); glutReshapeFunc(ChangeSize);
Глава 8 Наложение текстуры основы 391 glutSpecialFunc(SpecialKeys); glutDisplayFunc(RenderScene); SetupRC(); glutMainLoop(); return 0; ) Вся необходимая инициализация выполняется функцией SetupRC, в том числе загрузка текстуры с использованием функции gltLoadTGA, представленной в преды- дущей главе, и предоставление битов функции glTexlmage2D // Загружается текстура pBytes = gltLoadTGA("Stone.tga", SiWidth, &iHeight, &iComponents, &eFormat); glTex!mage2D(GL_TEXTURE_2D, 0, iComponents, iWidth, iHeight, 0, eFormat, GL_UNSIGNED_BYTE, pBytes); free(pBytes); Разумеется, также следует включить наложение текстуры glEnable(GL_TEXTURE_2D), Функция RenderScene рисует пирамиду в виде ряда текстурных треугольников В приведенном ниже фра; меше кода показано построение одной грани с помощью нормали (рассчитана с использованием угловых вершин), после чего следуют три текстурных координаты и координаты вершин // Передняя грань gltGetNormalVector(vCorners[0], vCorners[4], vCorners[3], vNormal); glNormal3fv(vNormal); glTexCoord2f(0.5f, l.Of); glVertex3fv(vCorners[0]), glTexCoord2f(0 Of, 0 Of); glVertex3fv(vCorners[4]), glTexCoord2f(l.Of, 0 Of); glVertex3fv(vCorners[3]); Текстурная среда В программе PYRAMID пирамида рисуется с белыми гранями (свойство материала), а текстура накладывайзся так, что се цвета масштабируются согласно окраске осве- щенной I сомстричсскои фщуры На рис S 6 отдельно показана нстскстурированная пирамида, исходная гекстура и текстурированная, но затененная пирамида То, как OpenGL объединяе! цвета текселей с цветом геометрического объекта, на который накладывается текстура, зависит от режима текстурной среды Данный режим устанавливается с помощью функции glTexEnv void glTexEnvi(GLenum target, GLenum pname, GLint param); void glTexEnvf(GLenum target, GLenum pname, GLfloat param); void glTexEnviv(GLenum target, GLenum pname, GLint *param); void glTexEnvfv(GLenum target, GLenum pname, GLfloat ‘param);
392 Часть I. Классический OpenGL Рис. 8.6. Освещенная геометрия + текстура = затененная текстура Функция имеет множество разновидностей и с ее помощью можно управлять более сложными деталями текстурирования, рассмотренными в следующей главе. В программе PYRAMID эта функция задает режим среды GL_MODULATE до примене- ния какой-либо текстуры. glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); В режиме MODULATE цвет текселя умножается на цвет геометрического объекта (после расчета освещения). Именно поэтому проявляется затененность пирамиды, из-за чего текстура кажется затененной. Используя данный режим, можно менять цветовой тон текстуры, накладывая цветные геометрические объекты. Например, черно-белая текстура кирпичной кладки, наложенная на красные, желтые и коричне- вые геометрические объекты, даст красные, желтые и коричневые кирпичи, хотя при этом будет использована единственная текстура. Если требуется просто заменить цвет геометрии, на которую накладывается тек- стура, в качестве режима среды задается GL_REPLACE. Таким образом, цвета фраг- ментов геометрического объекта непосредственно замещаются цветами текселей. По- ступив так, вы сведете на нет все влияние объекта на текстуру. Если текстура имеет альфа-канал, можно активизировать смешение; иногда с помощью данного режима создаются прозрачные геометрические объекты, согласно альфа-каналу украшенные узором карты текстуры. Если текстура не имеет компонента альфа, можно активизировать режим GL_DECAL, который ведет себя точно так же, как GL_REPLACE. В этом режиме тек- стура просто “переводится” (decal) поверх геометрии и кодов цвета, рассчитанных для фрагментов. Однако, если текстура имеет компонент альфа, переводной рису- нок можно применить так, чтобы там, где коды альфа смешиваются с фрагментами объекта, через него просматривался объект. Текстуры также можно смешивать с постоянным цветом, используя текстурную среду GL_BLEND. Устанавливая данный текстурный режим, также следует указывать цвет текстурной среды. GLfloat fColor[4] = { l.Of, O.Of, O.Of, O.Of }; glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_BLEND); glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, fColor); Наконец, коды цвета текселей можно просто добавить к фрагменту, на который на- кладываются текстуры, установив режим среды равным GL_ADD. Любым кодам цвета, превышающим 1.0, присваивается значение 1.0, и в результате могут образовываться насыщенные коды цвета (по сути, белый или более близкие к белому цвета, чем вы могли ожидать).
Глава 8 Наложение текстуры основы 393 Полный список констант текстурной среды приводится в справочном разделе В следующей главе описывается несколько режимов и текстурных эффектов, акти- визируемых и контролируемых с помощью представленной функции. Кроме того, в следующих разделах и программах-примерах мы вернемся к этой функции и рас- смотрим несколько дополнительных способов ее применения. Параметры текстуры Наложение текстуры — это не просто нашлепывание изображения на треугольную грань На правила визуализации и поведение применяющихся карт текстуры влияет множество параметров Все они задаются с помощью различных вариантов функции glTexParameter void glTexParameterf(GLenum target, GLenum pname, GLfloat param); void glTexParameteri(GLenum target, GLenum pname, GLint param); void glTexParameterfv(GLenum target, GLenum pname, GLfloat *params); void glTexParameteriv(GLenum target, GLenum pname, GLint *params); Первый аргумент target задает, к какому текстурному режиму должен приме- няться параметр, и его значениями могут быть GL_TEXTURE_1D, GL_TEXTURE_2D или GL_TEXTURE_3D Второй аргумент pname задает, какой параметр текстуры устанавли- вается, и наконец, аргументы param или params устанавливают значение конкретного параметра текстуры Основная фильтрация В отличие от растровых образов, рисуемых в буфере цвета при применении текстуры к геометрическому объекту, между текселями карты текстуры и пикселями экрана практически никогда нс существует взаимно-однозначного соответствия Конечно, аккуратный программист может добиться такого результата, но только с помощью текстурной геометрии, тщательно спланированной так, чтобы на экране создавать иллюзии того, что тскссли и пиксели выровнены В реальной же жизни текстурные изображения всегда должны растягиваться или сжиматься, если их применяют к гео- метрическим поверхностям Из-за ориентации геометрических объектов данная тек- стура может даже одновременно растягиваться и сжиматься на поверхности объекта. Процесс расчета цветных фрагментов растянутой или сжатой карты текстуры на- зывается фильтрацией текст)ры С помощью функции параметров текстуры OpcnGL позволяет устанавливать фильтры увеличения и уменьшения Данные фильтры соотне- сены с именами параметров GL_TEXTURE_MAG_FILTER И GL_TEXTURE_MIN_FILTER Меняя значения функции фильтрации, можно выбрать два базовых текстурных филь- тра GL_NEAREST и GL_LINEAR, которые соответствуют фильтрации по ближайшему соседу и линейной фильтрации Фильтрация по ближайшему соседу является простейшим (и самым быстрым) ме- тодом фильтрации, который можно выбрать в OpcnGL Вначале вычисляются текстур- ные координаты, а затем строится их зависимость от тексслсй текстуры — на какой тскссль припадет координата, такой цвет и будет использован в качестве цвета фраг-
394 Часть I. Классический OpenGL Рис. 8.7. Увеличенное изображение фильтрации по ближайшему соседу мента текстуры. Фильтрация по ближайшему “соседу” характеризуется “большими пикселями”, поскольку в этом режиме текстура чрезмерно растягивается. Соответ- ствующий пример демонстрируется на рис. 8.7. Для выбора в качестве текстурного фильтра двухмерного (GL_TEXTURE_2D) фильтра уменьшения или увеличения при- меняются такие две функции: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); Линейная фильтрация требует больше работы, чем фильтрация по ближайшему “соседу”, но часто эти издержки себя оправдывают. На современном потребительском аппаратном обеспечении дополнительная цена линейной фильтрации пренебрежимо мала. При линейной фильтрации учитывается не тексель, ближайший к текстурной координате, а взвешенное среднее текселей, окружающих текстурную координату, т.е. выполняется линейная интерполяция. Чтобы интерполированный фрагмент точно со- ответствовал цвету текселя, текстурная координата должна попасть в центр текселя. Линейная фильтрация характеризуется “пористой” (нечеткой) графикой при растяги- вании текстуры. Однако размытость часто дает более реалистичный и менее искус- ственный внешний вид, чем зазубренные блоки режима фильтрации по ближайшему “соседу”. На рис. 8.8 демонстрируется пример-противопоставление иллюстрации на рис. 8.7. Задать линейную фильтрацию (в режиме GL_TEXTURE_2D) довольно просто, и для этого используются приведенные ниже команды (также включены в функцию SetupRC программы PYRAMID). glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
Глава 8. Наложение текстуры: основы 395 Рис. 8.8. Увеличенное изображение линейной фильтрации Намотка текстуры Обычно текстурные координаты для отображения текселей карты текстуры при- надлежат диапазону от 0.0 до 1.0. Если текстурные координаты выходят из этого диапазона, OpenGL обрабатывает их согласно текущему режиму намотки текстуры. Режим намотки можно установить для каждой координаты отдельно, вызвав функ- цию glTexParameteri с параметром GL_TEXTURE_WRAP_S, GL_TEXTURE_WRAP_T или GL_TEXTURE_WRAP_R. Собственно режим намотки можно установить равным одному из следующих значений: gl_REPEAT, GL_CLAMP, GL_CLAMP_to_EDGE или GL_CLAMP_TO_BORDER. Режим GL_REPEAT просто вынуждает текстуру повторяться в том направлении, где текстурная координата превысила 1.0, причем текстура повторяется для всех целых текстурных координат. Этот режим очень полезен при наложении небольшой моза- ичной плитки на большие геометрические поверхности. Аккуратно разработанные бесшовные текстуры позволяют создать иллюзию гораздо большей текстуры за счет гораздо меньшего текстурного изображения. Другие режимы не повторяют, а “вжи- мают” текстуру в требуемый диапазон (отсюда и слово “clamp” в их названиях). Если бы единственным следствием выбора режима намотки было повторение или не повторение текстуры, требовалось бы всего два режима: повтор и ограничение согласно разрешенному диапазону. Однако кроме этого режим намотки текстуры сильно влияет на выполнение текстурной фильтрации на краях карты текстуры. При фильтрации с параметром GL_NEAREST режим намотки не имеет значения, посколь- ку текстурные координаты всегда относятся к конкретному текселю карты текстуры. В то же время фильтр gl_linear принимает среднее пикселей, окружающих рассчи-
396 Часть I Классический OpenGL тываемую координату, но при этом возникают проблемы с текселями, лежащими на краях карты текстуры. Данная проблема решается достаточно аккуратно в режиме GL_REPEAT Выбор- ки текселей просто берутся из следующей строки или столбца, которые в режиме повторения повторяют первые. Данный режим идеально подходит для текстур, обо- рачивающих объект и встречающихся на другой стороне (рассмотрите, например, наложение текстуры на сферу) Режимы намотки текстуры с ограничением согласно допустимому диапазону пред- лагают несколько вариантов обработки краев текстуры. В режиме GL_CLAMP требуе- мые тексели извлекают из границ текстуры или TEXTURE_BORDER_COLOR (устанавли- вается с помощью glTexParameterfv) Режим намотки GL_CLAMP_TO_EDGE игно- рирует выборки текселей, выходящие за край, и не включает их в среднее Наконец, GL_CLAMP_TO_BORDER использует тексели границы везде, где текстурные координаты выходят из диапазона 0.0-1 0 Типичным приложением режимов ограничения согласно допустимому диапазону является наложение текстуры на большую область, которая требует текстуры, слиш- ком большой, чтобы поместиться в память В этом случае область разбивается на маленькие “плитки”, которые затем располагаются рядом Если в подобном случае не использовать такой режим, как GL_CLAMP_TO_EDGE, на стыках между плитками мы получим визуальные артефакты фильтрации Например, в программе PYRAMID текстурные координаты вдоль основания пира- миды могут породить темный стык, если не указать, что режим намотки ограничивает билинейную фильтрацию пределами карты текстуры glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE); Мультфильмы с текстурами В первом примере главы использовались двухмерные текстуры, поскольку обыч- но они простые и их легче понять Большинство людей могут быстро интуитивно принять помещение двухмерной картинки на боковую поверхность двухмерного гео- метрического объекта (например, треугольника) Рассмотрим теперь наложение од- номерной текстуры, широко используемое в компьютерных играх для визуализации геометрии, которая при отображении на экране создает иллюзию мультипликации В мультипликационном, или келевом, затенении в качестве таблицы соответствий применяется одномерная карта текстуры, на основе которой геометрические объекты заполняются сплошным цветом (с помощью GL_nearest) В основе подхода лежит идея использования нормали поверхности из геометри- ческого описания объекта и нормали источника света для нахождения интенсивности света, падающего на поверхность модели Скалярное произведение данных векто- ров дает значение между 0 0 и 10, которое используется как одномерная текстурная координата В программе TOON, представленной в листинге 8 2, с помощью дан- ной техники рисуется зеленый тор Результат выполнения программы TOON пока- зан на рис. 8 9
Глава 8. Наложение текстуры: основы 397 Рис. 8.9. Тор с келевым затенением Листинг 8.2. Исходный код программы TOON // Toon.с // OpenGL. Суперкнига // Демонстрирует келевое/мультипликационное затенение с помощью // одномерной текстуры // Программа написана Ричардом С. Райтом—мл. #include /Common/OpenGLSB.h" // Активизируются необходимые возможности системы и OpenGL #include /Common/GLTools.h" // Подключаем набор инструментов OpenGL #include <math.h> // Рисуется тор (баранка); для затенения используется текущая // одномерная текстура void toonDrawTorus(GLfloat majorRadius, GLfloat minorRadius, int numMajor, int numMinor, GLTVector3 vLightDir) { GLTMatrix mModelViewMatrix; GLTVector3 vNormal, vTransformedNormal; double majorstep = 2.0f*GLT_PI / numMajor; double minorstep = 2.0f*GLT_PI / numMinor; int i, j; // Получается матрица наблюдения модели glGetFloatv(GL_MODELVIEW_MATRIX, mModelViewMatrix); // Нормируется вектор источника света gltNormalizeVector(vLightDir);
398 Часть I Классический OpenGL //С помощью лент треугольников рисуется тор for (1=0; iCnumMajor; ++i) { double a0 = i * majorstep; double al = aO + majorstep; GLfloat xO = (GLfloat) cos(aO); GLfloat yO = (GLfloat) sin(aO); GLfloat xl = (GLfloat) cos(al); GLfloat yl = (GLfloat) sin(al); glBegin(GL_TRIANGLE_STRIP); for (j=0; j<=numMinor; ++j) { double b = j * minorstep; GLfloat c = (GLfloat) cos(b); GLfloat r = minorRadius * c + majorRadius; GLfloat z = minorRadius * (GLfloat) sin(b); // Первая точка vNormal[0] = x0*c; vNormal[l] = y0*c; vNormal[2] = z/minorRadius; gltNormalizeVector(vNormal); gltRotateVector(vNormal, mModelViewMatrix, vTransformedNormal); // Текстурные координаты устанавливаются согласно // интенсивности света glTexCoordlf(gltVectorDotProduct(vLightDir, vTransformedNormal)); glVertex3f(x0‘r, y0*r, z); // Вторая точка vNormal[0] = xl*c; vNormal[1] = yl*c; vNormal[2] = z/minorRadius; gltNormalizeVector(vNormal); gltRotateVector(vNormal, mModelViewMatrix, vTransformedNormal); // Текстурные координаты устанавливаются согласно // интенсивности света glTexCoordlf(gltVectorDotProduct(vLightDir, vTransformedNormal)); glVertex3f(xl*r, yl*r, z); glEndt); ) ) // Вызывается для рисования сцены void RenderScene(void) { // Угол поворота static GLfloat yRot = 0 Of, // Откуда приходит свет GLTVector3 vLaghtDir = { -l.Of, 1 Of, 1 Of I, // Очищаем окно текущим цветом счистки
Глава 8 Наложение текстуры основы 399 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) ; glPushMatrix(); glTranslatef(O.Of, O.Of, -2.5f); glRotatef(yRot, O.Of, l.Of, O.Of); toonDrawTorus(0.35f, 0 15f, 50, 25, vLightDir); glPopMatrix() ; // Переключает буферы glutSwapBuffers(); // Каждый кадр поворачивается на 1/2 градуса yRot += 0.5f; } // Эта функция выполняет необходимую инициализацию в контексте // визуализации. void SetupRC() { // Загружается одномерная текстура с кодом // мультипликационного затенения // Зеленый, более зеленый ... GLbyte toonTable[4][3] = { { 0, 32, 0 }, { 0, 64, 0 }, { 0, 128, 0 }, { 0, 192, 0 }}; // Синеватый фон glClearColor(0.Of, 0 Of, .50f, l.Of ); glEnable(GL_DEPTH_TEST) ; glEnable(GL_CULL_FACE); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) ; glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glTex!magelD(GL_TEXTURE_lD, 0, GL_RGB, 4, 0, GL_RGB, GL_UNSIGNED_BYTE, toonTable); glEnable(GL_TEXTURE_1D); ) /////////////////////////////////////////////////////////////////// // Вызывается библиотекой GLUT в холостом режиме (окно не двигается // и не меняет размеров) void TimerFunction(int value) { // Сцена перерисовывается с новыми координатами glutPostRedisplay(); glutTimerFunc(33,TimerFunction, 1); } void ChangeSize(int w, int h) GLfloat fAspect; // Предотвращает деление на нуль, когда окно слишком маленькое // (нельзя сделать окно нулевой ширины). if(h == 0)
400 Часть I Классический OpenGL glViewport(0, 0, w, h); fAspect = (GLfloat)w / (GLfloat)h; // Система координат обновляется перед модификацией glMatrixMode(GL_PROJECTION); glLoadldentity(); // Устанавливается объем отсечения gluPerspective(35.Of, fAspect, l.Of, 50.0f); glMatrixMode(GL_MODELVIEW); glLoadldentity(); ) /////////////////////////////////////////////////////////////////// // Точка входа программы int main(int argc, char* argv[]) { glutlnit(&argc, argv); glutlnitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); glutlnitWindowSize(800,600); glutCreateWindow("Toon/Cell Shading Demo"); glutReshapeFunc(ChangeSize); glutDisplayFunc(RenderScene); glutTimerFunc(33, TimerFunction, 1); SetupRC(); glutMainLoop(); return 0; ) Сокращенные текстуры Применение сокращенной (или множественной) текстуры (mipmapping) является мощной техникой наложения текстуры, позволяющей повысить и производитель- ность визуализации, и визуальное качество сцены Эта техника решает две распро- страненных проблемы стандартного метода наложения текстуры Первой является эффект сцинтилляции (наложения артефактов), проявляющийся на поверхности объ- ектов, визуализированных на очень маленькой площади экрана по сравнению с от- носительным размером наложенной текстуры Сцинтилляцию можно рассматривать как разновидность мерцания, которое появляется, когда область дискретизации кар- ты текстуры движется непропорционально ее размерам на экране Отрицательные эффекты сцинтилляции наиболее заметны при движении камеры или объектов Вторая проблема больше связана с производительностью, но вызвана теми же причинами, что и сцинтилляция Чтобы отобразить небольшое число фрагментов на экране, нужно загрузить в память и обработать (отфильтровать) большой фраг- мент текстуры. Из-за этого при увеличении размера текстуры существенно снижает- ся производительность Решение обеих проблем заключается в простом использовании меньших карт тек- стуры Однако это решение создает новую проблему при приближении камеры к тому же объекту его нужно будет визуализировать с большими размерами, и небольшую карту текстуры придется растягивать до состояния объекта с безнадежно расплыв- шейся или блочной текстурой.
Глава 8. Наложение текстуры: основы 401 Рис. 8.10. Ряд изображений с последовательно сокращающейся текстурой 5 Выходом из создавшейся ситуации является множественное отображение (mipmapping). Данная техника получила название от латинского выражения “mul- tum in parvo” (MIP), означающего “многое в малом”. По сути, вы загружаете не одно изображение в одно состояние текстуры, а целый ряд изображений (от наибольшего до наименьшего) в единое “всеобъемлющее” (mipmapped) состояние текстуры. За- тем OpenGL с помощью нового набора режимов фильтрации выбирает текстуру или текстуры, наилучшим образом подходящие для данной геометрии. За счет немного увеличившихся требований к памяти (и, возможно, существенно увеличившихся тре- бований к обработке) можно одновременно избавиться от сцинтилляции и издержек, связанных с обработкой далеких объектов, при этом поддерживая версии текстуры с большими разрешениями, которые при необходимости можно будет использовать. “Всеобъемлющая” текстура состоит из ряда текстурных изображений, каждое по- следующее из которых вдвое меньше предыдущего. Данный сценарий иллюстриру- ется на рис. 8.10. Уровни множественных отображений не обязательно должны быть квадратными, но сокращение размеров вдвое продолжается до тех пор, пока послед- нее изображение не будет текселем 1x1. После того как в ходе сокращения один из размеров становится равным 1, далее сокращается только второй размер. Кстати, использование квадратного набора “всеобъемлющих” текстур требует примерно на треть больше памяти, чем применение обычной текстуры. Уровни множественной текстуры загружаются с помощью glTexImage. Теперь в этой команде для нас важен параметр level, поскольку он задает, какому уров- ню текстуры соответствуют предоставленные данные изображения. Первым идет уровень 0, затем следуют уровни 1, 2 и т.д. Если технология множественной тек- стуры не используется, загружается только уровень 0. Чтобы использовать множе- ственную текстуру по умолчанию, следует заселить все ее уровни. Тем не менее можно задать только базовый и максимальный уровни, указав параметры текстуры GL_TEXTURE_BASE_LEVEL И GL_TEXTURE_MAX_LEVEL. Например, если нужно загру- жать только уровни с 0 по 4, функция glTexParameteri вызывается дважды. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 4); ХОТЯ GL_TEXTURE_BASE_LEVEL и GL_TEXTURE_MAX_LEVEL контролируют, ка- кие уровни текстуры загружаются, вы можете явно ограничить диапазон за- гружаемых уровней, применив вместо данных параметров GL_TEXTURE_MIN_LOD И GL_TEXTURE_MAX_LOD.
402 Часть I Классический OpenGL ТАБЛИЦА 8.4. Фильтры множественной текстуры Константа Описание GL_NEAREST Фильтрация по ближайшему “соседу” на основном уровне текстуры GL_LINEAR GL_NEAREST_MIPMAP_NEAREST Линейная фильтрация на основном уровне текстуры Выбор ближайшего уровня текстуры и выполнение фильтрации по ближайшему “соседу” GL_NEAREST_MIPMAP_LINEAR Выполнение линейной интерполяции между уровнями текстуры и выполнение фильтрации по ближайшему “соседу” GL_LINEAR_MIPMAP_NEARE ST Выбор ближайшего уровня текстуры и выполнение линейной фильтрации GL_LINEAR_MIPMAP_LINEAR Выполнение линейной интерполяции между уровнями текстуры и выполнение линейной фильтрации, также называется трилинейной фильтрацией или трилинейным множественным отображением Фильтрация множественной текстуры Множественная текстура добавляет новый прием к двум базовым режимам текстур- ной фильтрации GL_NEAREST и GL_LINEAR, предоставляя четыре перестановки ва- рианта фильтрации множественной текстуры, перечисленных в табл 8 4 Простая загрузка уровней текстуры с помощью функции glTexImage сама по себе не активизирует множественную текстуру Если фильтр текстуры установлен равным GL_LINEAR или GL_NEAREST, используется только основной уровень тексту- ры, а все уровни множественной текстуры игнорируются Чтобы эти загруженные уровни использовались, нужно задать один из фильтров множественной текстуры Соответствующие константы имеют вид GL_FILTER_MIPMAP_SELECTOR, где FILTER задает фильтр текстуры, который будет использоваться на выбранном уровне мно- жественной текстуры SELECTOR указывает, как выбирается уровень множественной текстуры, например, GL_NEAREST соответствует выбору ближайшего подходящего уровня Использование в качестве SELECTOR константы GL_LINEAR даст линейную интерполяцию между двумя ближайшими уровнями множественной текстуры, ре- зультат которой будет отфильтрован с применением выбранного фильтра текстуры Указание одного из режимов фильтрации множественной текстуры без загрузки уров- ней множественной текстуры равнозначно дсактивизации наложения текстуры Выбор фильтра меняется в зависимости от приложения и существующих требо- ваний к производительности GL_NEAREST_MIPMAP_NEAREST, например, даст очень высокую производительность и небольшие артефакты наложения (сцинтилляции), но фильтрация по ближайшим соседям часто неприятна зрительно В играх для ускоре- ния обработки часто применяется GL_LINEAR_MIPMAP_NEAREST, поскольку это соот- ветствует высококачественной линейной фильтрации, но выбор между доступными уровнями текстуры разных размеров делается быстро Применяя для выбора уровня принцип ближайших “соседей” (как в примерах в предыдущем абзаце), также можно получить нежелательные визуальные арте- факты. При наблюдении поверхности под углом на ней часто заметно место не-
Глава 8 Наложение текстуры основы 403 рехода от одного уровня текстуры к другому. Данный переход выглядит как ис- кажение или резкий переход от одного уровня детализации к другому. Фильтры GL_LINEAR_MIPMAP_LINEAR И GL_NEAREST_MIPMAP_LINEAR ВЫПОЛНЯЮТ дополни- тельную интерполяцию между уровнями множественной текстуры, что позволяет устранить зону перехода, хотя и за счет существенно увеличивающейся обработки Фильтр GL_LINEAR_MIPMAP_LINEAR называется трилинейной фильтрацией, и до недавнего времени он был стандартом фильтрации текстуры, дающим наивысшую точность Совсем недавно на аппаратном обеспечении OpenGL стала доступна ани- зотропная фильтрация (рассмотрена в следующей главе), дающая лучшее качество, но за счет еще больших издержек. Генерация уровней множественной текстуры Как отмечалось ранее, множественная текстура требует приблизительно на треть больше памяти, чем просто загрузка основного текстурного изображения. Кроме то- го, нужно, чтобы для загрузки были доступны все меньшие и меньшие версии основ- ного текстурного изображения. Иногда это бывает неудобно, поскольку изображения с меньшим разрешением не обязательно будут доступны или программисту, или ко- нечному пользователю вашего программного обеспечения. В связи с этим в библиоте- ку GLU включена функция gluScalelmage (см. справочный раздел), которую можно применить для последовательного масштабирования и загрузки изображения, пока не будут загружены все необходимые уровни множественной текстуры. Часто доступна еще более удобная функция, автоматически создающая масштабированные изображе- ния и загружающая их с помощью glTexImage Эта функция — gluBuildMipmaps — имеет три варианта и поддерживает одно-, двух- и трехмерные карты текстуры. int gluBuildlDMipmaps(GLenum target, GLint internalFormat, GLint width, GLenum format, GLenum type, const void *data}; int gluBuild2DMipmaps(GLenum target, GLint internalFormat, GLint width, GLint height, GLenum format, GLenum type, const void *data); int gluBuild3DMipmaps(GLenum target, GLint internalFormat, GLint width, GLint height, GLint depth, GLenum format, GLenum type, const void *data); Можно провести параллель между использованием данных функций и glTexlm- age, однако приведенные функции не имеют параметра level для задания уровня множественной текстуры и не поддерживают обработку границы текстуры Кроме то- го, следует знать, что использование данных функций не дает изображений уровней множественной текстуры такого же качества, какое можно получить с помощью дру- гих средств (например, Photoshop). Для сокращения изображений библиотека GLU использует прямоугольный фильтр (box filter), а это может привести к нежелательной потере мелких деталей при уменьшении изображения С помощью новых версий библиотеки GLU можно на более низком уровне по- лучить контроль над тем, какие уровни множественной текстуры загружаются. Для эюго были введены следующие функции.
404 Часть I Классический OpenGL int gluBuildlDMipmapLevels(GLenum target, GLint internalFormat, GLint width, GLenum format, GLenum type, GLint level, GLint base, GLint max, const void *data); int gluBuild2DMipmapLevels(GLenum target, GLint internalFormat, GLint width, GLint height, GLenum format, GLenum type, GLint wlevel, GLint base, GLint max, const void *data); int gluBuild3DMipmapLevels(GLenum target, Glint internalFormat, GLint width, GLint height, GLint depth, GLenum format, GLenum type, GLint level, GLint base, GLint max, const void *data); В этих функциях level обозначает уровень множественной текстуры, задавае- мый параметром data. С помощью предоставленных данных строятся уровни мно- жественной текстуры от base до max Аппаратная генерация множественных отображений Если вы заранее знаете, что требуется загрузка всех уровней множественной текстуры, можете использовать аппаратное ускорение OpenGL для быстрой генерации всех необходимых уровней множественного отображения Для этого параметр текстуры GL_generate_mipmap устанавливается равным GL_TRUE glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE); После установки данного параметра все вызовы функций glTexImage или gl- TexSublmage, обновляющие основную карту текстуры (уровень 0 множественной текстуры), автоматически обновляют все нижние уровни множественной текстуры Если задействовать графическое аппаратное обеспечение, данная возможность будет реализована гораздо быстрее, чем при использовании gluBuildMipmaps Тем не ме- нее следует знать, что изначально данная возможность относилась к расширениям и в основной программный интерфейс OpenGL была включена только в версии 1.4 Смещение уровня детализации Чтобы определить, какой уровень множественного отображения выбрать, OpenGL ис- пользует формулу, в которую входят размеры уровней множественного отображения и области экрана, занимаемой геометрическим объектом. OpenGL старается добить- ся близкого соответствия между выбранным уровнем множественного отображения и представлением текстуры на экране Вы можете указать OpenGL сместить критерий выбора назад (к большим уровням) или вперед (к меньшим уровням множественного отображения). Это может привести к повышению производительности (использова- ние меньших уровней) или увеличению резкости объектов с наложенной текстурой (использование больших уровней множественного отображения) Как показано ниже, смещение задается с помощью параметра текстурной среды GL_TEXTURE_LOD_BIAS glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, -1.5); В приведенном примере степень детализации текстуры немного смещена к большим уровням детализации (меньшего параметра уровня), что дает более резкие текстуры за счет усложнения обработки
Глава 8 Наложение текстуры основы 405 Текстурные объекты До этого момента мы показывали, как загружать параметры текстуры, влияющие на наложение карт текстуры на геометрические объекты. Рисунок и параметры тексту- ры задаются с помощью функции glTexParameter, содержащей состояние тек- стуры Загрузка и поддержание состояния текстуры находится на одном из пер- вых мест во многих приложениях OpenGL с интенсивным использованием тексту- ры (в частности, игр). Особенно трудоемкими являются вызовы таких функций, как glTexImage, gl- TexSublmage и gluBuildMipmaps. Эти функции перемещают большие участ- ки памяти и, возможно, требуют изменения формата данных, согласовывающе- го эти данные с внутренним представлением. Переключение между текстурами или загрузка другого текстурного изображения обычно может оказаться доволь- но дорогой операцией. Текстурные объекты позволяют загружать более одного состояния текстуры за раз (включая рисунок текстуры) и быстро переключаться между ними. Состояние тек- стуры поддерживается связанным в настоящее время текстурным объектом, который идентифицируется целым числом без знака. Для выделения нескольких текстурных объектов применяется следующая функция void glGenTextures(GLsizei n, GLuint *textures); Вызывая эту функцию, вы задаете число текстурных объектов и указатель на мас- сив целых чисел без знака, который будет заселен идентификаторами текстурных объектов Идентификаторы вы можете представить себе как обработчики доступных состояний текстуры Чтобы “привязаться” к одному из этих состояний, вызывает- ся такая функция: void glBindTexture(GLenum target, GLuint texture); Параметр target должен иметь значение GL_TEXTURE_1D, GL_TEXTURE_2D либо GL_TEXTURE_3D, a texture — это конкретный текстурный объект, с которым мы связываемся. С этого момента вся загрузка текстур и настройки параметров текстуры влияют только на текущий связанный текстурный объект Для удаления текстурных объектов применяется следующая функция void glDeleteTextures (GLsizei n, GLuint ★textures'); Аргументы данной функции имеют то же значение, что и в функции glGen- Textures Отметим, что не нужно генерировать и удалять все текстурные объекты одновременно Многократный вызов функции glGenTextures влечет за собой очень малые издержки Многократный вызов glDeleteTextures может ввести небольшие задержки, но это связано только с тем, что вы освобождаете большие объемы памяти текстуры Чтобы проверить, насколько справедливы имена объектов (или обработчиков), тс узнать, соответствуют ли они текстурным объектам, используется следую- щая функция GLboolean gllsTexture(GLuint texture); Эта функция возвращает GL_TRUE, если целое число представляет распределенное ранее имя текстурного объекта, и GL_FALSE в противном случае
406 Часть I. Классический OpenGL Рис. 8.11. Туннель, визуализированный стремя различными текстурами Управление несколькими текстурами В общем случае текстурные объекты применяются для загрузки нескольких текстур в ходе инициализации программы и быстрого переключения между ними в процессе визуализации. Когда программа завершается, текстурные объекты удаляются. При запуске приводимой ниже программе TUNNEL загружаются три текстуры, которые затем переключаются при визуализации туннеля. Туннель имеет кирпичные стены и пол и потолок из разных материалов. Результат выполнения программы TUNNEL показан на рис. 8.11. Программа TUNNEL также иллюстрирует множественное отображение и различ- ные режимы фильтрации текстуры множественного отображения. Нажимая клави- ши со стрелками вверх и вниз, вы перемещаете точку наблюдения назад-вперед по туннелю, а контекстное меню (вызывается щелчком правой кнопки мыши) позво- ляет переключаться между шестью различными режимами фильтрации и сравни- вать их влияние на визуализацию изображения. Исходный код программы приводит- ся в листинге 8.3. Листинг 8.3. Исходный код программы TUNNEL // Tunnel.с // Демонстрирует множественное отображение и использование // текстурных объектов // OpenGL. Суперкнига // Ричард С. Райт—мл. ♦include /Common/OpenGLSB.h" // Активизируются необходимые возможности системы и OpenGL
Глава 8 Наложение текстуры, основы 407 ♦include /Common/GLTools.h" // GLTools /I Величины поворота static GLfloat zPos = -60.Of; // Текстурные объекты ♦ define TEXTURE_BRICK 0 ♦ define TEXTURE_FLOOR 1 ♦ define TEXTURE_CEILING 2 ♦ define TEXTURE_COUNT 3 GLuint textures[TEXTURE_COUNT]; const char *szTextureFiles[TEXTURE_COUNT] = { "brick.tga", "floor.tga", "ceiling.tga" ); /////////////////////////////////////////////////////////////////// // Меняется текстурный фильтр для каждого текстурного объекта void ProcessMenu(int value) GLint iLoop; for(iLoop = 0; iLoop < TEXTURE_COUNT; iLoop++) { glBindTexture(GL_TEXTURE_2D, textures[iLoop]); switch(value) { case 0: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GLJNEAREST); break; case 1: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) ; break; case 2 : glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST); break; case 3: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR); break; case 4: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); break; case 5: default: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR) ; break; } } // Инициируется перерисовывание
408 Часть I Классический OpenGL glutPostRedisplay(); } /////////////////////////////////////////////////////////////////// 11 Эта функция выполняет необходимую инициализацию в контексте // визуализации. Здесь задаются и инициализируются текстурные // объекты void SetupRC() { GLubyte *pBytes; GLint iwidth, iHeight, iComponents; GLenum eFormat; GLint iLoop; // Черный фон glClearColor(O.Of, O.Of, O.Of,l.Of); // Текстуры применяются как переводные рисунки, без эффектов // освещения или окрашивания glEnable(GL_TEXTURE_2D); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); // Загружаются текстуры glGenTextures(TEXTURE_COUNT, textures); for(iLoop = 0; iLoop < TEXTURE_COUNT; iLoop++) { // Связывание co следующим текстурным объектом glBindTexture(GL_TEXTURE_2D, textures[iLoop]); // Загружается текстура, устанавливаются режимы фильтрации // и намотки pBytes = gltLoadTGA(szTextureFiles[iLoop],&iWidth, &iHeight, &iComponents, &eFormat); gluBuild2DMipmaps(GL_TEXTURE_2D, iComponents, iWidth, iHeight, eFormat, GL_UNSIGNED_BYTE, pBytes); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) ; glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // Первоначальные данные текстуры уже не нужны free(pBytes); /////////////////////////////////////////////////////////////////// // Выключается контекст визуализации. Просто удаляются // текстурные объекты void ShutdownRC(void) ( glDeleteTextures(TEXTURE_COUNT, textures); } /////////////////////////////////////////////////////////////////// // В ответ на нажатия клавиш со стрелками точка наблюдения
Глава 8 Наложение текстуры основы 409 // перемещается взад-вперед void SpecialKeys(int key, int x, int y) { if(key == GLUT_KEY_UP) zPos += l.Of; if(key == GLUT_KEY_DOWN) zPos -= l.Of; // Обновляет окно glutPostRedisplay(); ) /////////////////////////////////////////////////////////////////// 11 Меняет наблюдаемый объем и поле просмотра. Вызывается // при изменении размеров окна void ChangeSize(int w, int h) { GLfloat fAspect; // Предотвращает деление на нуль if(h == 0) h = 1; I/ Размер поля просмотра устанавливается равным размеру окна glViewport(0, 0, w, h); fAspect = (GLfloat)w/(GLfloat)h; // Обновляется система координат glMatrixMode(GL_PROJECTION); glLoadldentity(); // Генерируется перспективная проекция gluPerspective(90.Of,fAspect,1,120); glMatrixMode(GL_MODELVIEW); glLoadldentity(); } /////////////////////////////////////////////////////////////////// 11 Вызывается для рисования сцены void RenderScene(void) GLfloat z; I/ Очищаем окно текущим цветом очистки glClear(GL_COLOR_BUFFER_BIT); I/ Записывается состояние матрицы и выполняются повороты glPushMatrix(); // Объекты перемещаются назад и выполняется поворот на месте glTranslatef(0.Of, O.Of, zPos); 11 Пол f or (z = 60.Of; z >= O.Of; z -= 10) { glBindTexture(GL_TEXTURE_2D, textures[TEXTURE_FLOOR]); glBegin(GL_QUADS); glTexCoord2f(O.Of, O.Of); glvertex3f(-10.Of, -10.Of, z); glTexCoord2f(l.Of, O.Of); glVertex3f(10.Of, -10.Of, z); glTexCoord2f(1 Of, l.Of);
410 Часть I Классический OpenGL glVertex3f(10.Of, -10.Of, z - lO.Of); glTexCoord2f(O.Of, l.Of); glVertex3f(-10.Of, -10.Of, z - 10.Of); glEnd(); // Потолок glBindTexture(GL_TEXTURE_2D, textures[TEXTURE_CEILING]); glBegin(GL_QUADS) ; glTexCoord2f(O.Of, l.Of); glVertex3f(-10.Of, 10.Of, z - lO.Of); glTexCoord2f(1.Of, l.Of); glVertex3f(lO.Of, lO.Of, z - lO.Of); glTexCoord2f(1.Of, O.Of); glVertex3f(lO.Of, lO.Of, z); glTexCoord2f(O.Of, O.Of); glVertex3f(-lO.Of, lO.Of, z); glEnd(); // Левая стена glBindTexture(GL_TEXTURE_2D, textures[TEXTURE_BRICK]); glBegin(GL_QUADS); glTexCoord2f(O.Of, O.Of); glVertex3f(-lO.Of, -lO.Of, z); glTexCoord2f(1.Of, O.Of); glVertex3f(-lO.Of, -lO.Of, z - lO.Of); glTexCoord2f(l.Of, l.Of); glVertex3f(-lO.Of, lO.Of, z - lO.Of); glTexCoord2f(O.Of, l.Of); glVertex3f(-lO.Of, lO.Of, z); glEnd(); 11 Правая стена glBegin(GL_QUADS); glTexCoord2f(O.Of, l.Of); glVertex3f(lO.Of, lO.Of, z); glTexCoord2f(1.Of, l.Of); glVertex3f(lO.Of, lO.Of, z - lO.Of); glTexCoord2f(1.Of, O.Of); glVertex3f(lO.Of, -lO.Of, z - lO.Of); glTexCoord2f(0.Of, O.Of); glVertex3f(lO.Of, -lO.Of, z); glEnd(); } 11 Восстанавливается состояние матрицы glPopMatrix(); // Переключение буферов glutSwapBuffers(); ) /////////////////////////////////////////////////////////////////// // Точка входа программы int main(int argc, char *argv[]) { // Стандартный набор инициализации glutlnit(&argc, argv); glutlnitDisplayMode(GLUT_DOUBLE | GLUT_RGB); glut!nitWindowSize(800, 600);
Глава 8 Наложение текстуры основы 411 glutCreateWindow("Tunnel"); glutReshapeFunc(ChangeSize); glutSpecialFunc(SpecialKeys); glutDisplayFunc(RenderScene); // Добавляются позиции меню для изменения фильтра glutCreateMenu(ProcessMenu); glutAddMenuEntry("GL_NEAREST",0); glutAddMenuEntry("GL_LINEAR",1); glutAddMenuEntry("GL_NEAREST_MIPMAP_NEAREST", 2) ; glutAddMenuEntry("GL_NEAREST_MIPMAP_LINEAR”, 3); glutAddMenuEntry("GL_LINEAR_MIPMAP_NEAREST", 4); glutAddMenuEntry("GL_LINEAR_MIPMAP_LINEAR", 5); glutAttachMenu(GLUT_RIGHT_BUTTON); // Запуск, основной цикл, выключение SetupRC(); glutMainLoop(); ShutdownRC(); return 0; } В данном примере вначале создаются идентификаторы трех текстурных объектов Массив textures содержит три целых числа, которые представляют макросы ТЕХ- TURE_BRICK, TEXTURE_FLOOR и TEXTURE_CEILING С целью увеличения гибкости также создается макрос, определяющий максимальное число текстур, которые будут загружены, и массив строк символов, содержащий имена файлов карт текстуры. // Текстурные объекты ♦ define TEXTURE_BRICK 0 ♦ define TEXTURE_FLOOR 1 ♦ define TEXTURE_CEILING 2 ♦ define TEXTURE_COUNT 3 GLuint textures[TEXTURE_COUNT]; const char *szTextureFiles[TEXTURE_COUNT] = { "brick.tga", "floor.tga", "ceiling.tga" ); Текстурные объекты распределяется в функции SetupRC glGenTextures(TEXTURE_COUNT, textures); Далее с каждым текстурным объектом связывается простой цикл и в состояние текстуры этого объекта загружается изображение текстуры и параметры ее нанесения for(iLoop = 0; iLoop < TEXTURE_COUNT; iLoop++) // Связывание co следующим текстурным объектом glBindTexture(GL_TEXTURE_2D, textures[iLoop]); // Загружается текстура, устанавливаются режимы фильтрации // и намотки pBytes = gltLoadTGA(szTextureFiles[iLoop],&iWidth, &iHeight, &iComponents, &eFormat); gluBuild2DMipmaps(GL_TEXTURE_2D, iComponents, iWidth, iHeight, eFormat, GL_UNSIGNED_BYTE, pBytes); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
412 Часть I Классический OpenGL GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) ; glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) ; 11 Первоначальные данные текстуры уже не нужны free(pBytes); } Завершив инициализацию всех трех текстурных объектов, можно переключаться между ними в процессе визуализации, меняя текстуры. glBindTexture(GL_TEXTURE_2D, textures[TEXTURE_FLOOR]); glBegin(GL_QUADS); glTexCoord2f(O.Of, O.Of); glVertex3f(-10.Of, -10.Of, z); glTexCoord2f(l.Of, O.Of); glVertex3f(10.Of, -10.Of, z); Наконец, программа завершается, для полной очистки осталось только удалить текстурные объекты. /////////////////////////////////////////////////////////////////// // Выключается контекст визуализации. Просто удаляются // текстурные объекты void ShutdownRC(void) { glDeleteTextures(TEXTURE_COUNT, textures); } Обратите также внимание на то, что при установке фильтра множественной тек- стуры в программе TUNNEL он выбирается только для уменьшающего фильтра glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR) ; Данная ситуация является характерной, поскольку после того как OpenGL выбе- рет наибольший доступный уровень множественного отображения, более высоких уровней, из которых можно было бы выбирать текстуру, уже нет Можно сказать, что после прохождения определенного порога применяется наибольшее доступное текстурное изображение, и нет дополнительных уровней множественной текстуры, из которых можно было бы выбирать больший рисунок. Резидентные текстуры Многие реализации OpenGL поддерживают ограниченную высокопроизводительную памя1ь текстуры Доступ к текстурам, располагающимся в этой памяти, можно по- лучить очень быстро, и общая производительность довольно высока Изначально любая загруженная текстура хранится в этой памяти, однако обычно доступная быст- рая память ограничена, и в определенный момент придется записывать текстуру
Глава 8 Наложение текстуры основы 413 в медленной памяти. Как часто и бывает, медленная память может располагаться вне аппаратного обеспечения OpenGL (например, в системной памяти ПК, в противопо- ложность памяти графической карты или памяти AGP) Чтобы оптимизировать производительность визуализации, OpenGL автоматически перемещает наиболее часто используемые текстуры в быструю память. Текстуры, рас- положенные в данной памяти, называются резидентными. Чтобы определить, являет- ся ли связанная текстура резидентной, можно вызвать glGetTexParameter и найти значение, соотнесенное с GL_TEXTURE_RESIDENT. Более полезной может быть про- верка группы текстур на предмет принадлежности к резидентным. Она выполняется с помощью следующей функции GLboolean glAreTexturesResident(GLsizei п, const GLuint ★textures, GLboolean *residences); Эта функция принимает число текстурных объектов, которые нужно проверить, массив имен текстурных объектов и массив булевых меток со значением GL_TRUE или GL_FALSE, указывающих состояние каждого текстурного объекта Если все текстуры являются резидентными, массив остается без изменений, и функция возвращает зна- чение GL_TRUE. Описанная возможность призвана сэкономить время проверки мас- сива в тех случаях, когда требуется узнать, являются ли все текстуры резидентными Приоритеты текстуры По умолчанию в большинстве реализаций OpenGL для определения того, какие тек- стуры могут быть резидентными, используется алгоритм наиболее используемого элемента (Most Frequently Used — MFU) Однако если есть несколько небольших тек- стур, которые используются лишь немного реже, чем, скажем, пара больших текстур, в результате описанных действий можно очень сильно потерять в производитель- ности Чтобы “подсказать” механизму, использованному в реализации для выбора резидентных текстур, каким текстурам отдавать предпочтение, с помощью приведен- ной ниже функции устанавливается приоритет каждой текстуры void glPrioritizeTextures(GLsizei п, const GLuint *textures, const GLclampf ★priorities); Данная функция принимает массив имен текстурных объектов и соответствующий массив приоритетов текстурных объектов, принадлежащих диапазону 0-1 Низкий приоритет сообщает, что соответствующий текстурный объект следует выносить из резидентной памяти при нехватке места Высокий приоритет (например, 1.0) сообща- ет, что вам хотелось бы по возможности оставить текстурный объект в резидентной памяти, даже если текстура используется редко Резюме В данной главе мы расширили понятия о простых методах загрузки и отображения изображений на наложение изображений (карт текстуры) на реальные трехмерные геометрические объекты Вы узнали, как загрузить карту текстуры и использовать текстурные координаты для отображения изображения в вершины геометрическо- го объекта Кроме того, вы научились различным способам фильтрации текстурных
414 Часть I Классический OpenGL изображений и смешения их с кодами цвета геометрических объектов. Было расска- зано, как использовать множественные отображения (miprnap) для улучшения про- изводительности и визуальной точности. В заключение мы обсудили, как управлять несколькими текстурами, переключаясь между ними легко и быстро, а также как сообщить OpenGL, какие текстуры должны иметь приоритет, если доступна высоко- производительная (или локальная) память текстуры. Справочная информация glAreTexturesResident Цель: Определить, являете я ли набор текстурных объектов резидентным Включаемый файл: <gl,h> Синтаксис: GLboolean glAreTexturesResident(GLsizei n, const GLuint *textures, GLboolean * residences'); Описание: Многие реализации OpenGL поддерживают высокопроизводительный набор текстур, именуемых резидентными Резидентные текстуры хранятся локально или в быстрой памяти, прямой доступ к которой имеет аппаратное обеспечение OpenGL. Наложение резидентной текстуры выполняется гораздо быстрее, чем нерезидентной. Данная функция позволяет проверить массив обработчиков текстурных объектов на предмет принадлежности к резидентным Если все запрошенные текстурные объекты являются резидентными, функция возвращает значение GL_TRUE Параметры: п (тип GLsizei) Размер массива текстурных объектов textures Массив, содержащий идентификаторы запрашиваемых (тип GLuint*) текстурных объектов residences Массив, для каждого текстурного объекта заселяемый (тип GLboolean*) соответствующими метками (GL_TRUE или GL_FALSE) Если все заданные текстурные объекты относятся к резидентным, массив не модифицируется Что возвращает: Ничего См. также: glGenTextures, glBindTexture, glDeleteTextures, glPrioritizeTextures
Глава 8 Наложение текстуры основы 415 glBindTexture Цель: Связать текущее состояние текстуры с именованной целью Включаемый файл: <gl. h> Синтаксис: void glBindTexture(GLenum target, GLuint texture); Описание: Эта функция позволяет создавать именованное состояние текстуры или переключаться на него При первом вызове функция создает новое текстурное состояние, идентифицируемое именем текстуры — целым числом без знака При последующих вызовах с тем же идентификатором текстуры данное текстурное состояние становится текущим Параметры: target (тип GLenum) texture (тип GLuint) Что возвращает: Связываемая текстура. Параметр должен иметь значение GL_TEXTURE_1D, GL_TEXTURE_2D ИЛИ GL_TEXTURE_3D Имя или обработчик текстурного объекта Ничего См. также: glGenTextures, glDeleteTextures, glAreTexturesResidient, glPrioritizeTextures, gllsTexture glCopyTexImage Цель: Скопировать пиксели из буфера цветов в текстуру Включаемый файл: <gl.h> Варианты: void glCopyTexImagelD(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border); void glCopyTex!mage2D(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); Описание: Используя данные, считываемые непосредственно из буфера цветов, эти функции определяют одно- или двухмерное текстурное изображение Если источник карты текс гуры является результатом операции визуализации, нс забудьте вызвать glFinish, чтобы гарантировать, что OpenGL завершил операцию визуализации до вызова указанной функции Данные считываются из буфера цветов, заданного функцией glReadBuffer
416 Часть I Классический OpenGL Параметры: target (тип GLenum) Целевая текстура Параметр должен иметь значение GL_TEXTURE_1D при glCopyTexImagelD и GL_TEXTURE_2D при glCopyTex!mage2D level (тип GLint) internal (тип GLenum) Загружаемый уровень множественной текстуры Внутренний формат и разрешение текстурных данных Это должна быть одна из констант форматов текстуры, принимаемых функцией glTexImage, однако вы не можете использовать значения 1,2,3 или 4 для представления количества компонентов цвета к, у (тип GLint) Позиция в буфере цвета, с которой начинается чтение информации о цвете width, height (тип GLsizei) Ширина и высота (только для функции glCopyTexImage2D) прямоугольника с данными о цвете, считываемого из буфера цветов border (тип GLint) Ширина границы текстуры. Допускаются только значения 1 или О Что возвращает: Ничего См. также: glTexImage, glCopyTexSublmage glCopyTexSublmage Цель: Заместить часть карты текстуры данными из буфера кадров Включаемый файл: <gl.h> Варианты: void glCopyTexSublmageID(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); void glCopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); void glCopyTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, Glint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); Описание: Эта функция замещает часть существующей карты текстуры данными, считываемыми непосредственно из буфера цветов Если источник карты текстуры является результатом операции визуализации, не забудьте вызвать gIFinish, чтобы гарантировать, что OpenGL завершил операцию визуализации до вызова указанной функции. Данные считываются из буфера цветов, заданного функцией glReadBuffer
Глава 8 Наложение текстуры основы 417 Параметры: target (тип GLenum) Цель текстурного запроса. Параметр должен иметь значение GL_TEXTURE_1D для glCopyTexSublmagelD, GL_TEXTURE_2D для glCopyTexSub!mage2D и GL_TEXTURE_3D ДЛЯ glcopyTexSub!mage3D level (тип GLint) xoffset (тип GLint) yoffset (тип GLint) zoffset (тип GLint) x, у (тип GLint) Обновляемый уровень множественной текстуры Смещение х от начала карты текстуры, с которого начинается замещение данных Смещение у от начала двух- или трехмерной карты текстуры, с которого начинается замещение данных Смещение z от начала трехмерной карты текстуры, с которого начинается замещение данных Координаты х, у позиции в буфере цветов, с которой начинается считывание данных в текстуру width, height (тип GLsizei) Что возвращает: Ширина и высота (только для двух- и трехмерных текстур) данных, считываемых из буфера цветов Ничего См. также: glTexImage, glCopyTexImage, glTexSublmage gIDeleteTextures Цель: Удалить набор текстурных объектов Включаемый файл: <gl,h> Синтаксис: void gIDeleteTextures(GLsizei n, const GLuint ★textures}; Описание: Эта функция удаляет набор текстурных объектов. После удаления текстурного объекта его можно переопределить, вызвав функцию glBindTexture. Вся память, используемая существующими текстурными объектами освобождается и становится доступной для других текстур. Все недопустимые имена текстурных объектов в массиве игнорируются Параметры: п (тип GLsizei) textures (тип GLuint*) Что возвращает: Число удаляемых текстурных объектов Массив, содержащий список удаляемых текстурных объектов Ничего См. также: glGenTextures, glBindTexture
418 Часть I Классический OpenGL ТАБЛИЦА 8.5. Константы запроса параметров уровня текстуры Константа Описание GL_TEXTURE_WIDTH GL_TEXTURE_HEIGHT GL_TEXTURE_DEPTH GL_TEXTURE_INTERNAL_FORMAT GL_TEXTURE_BORDER GL_TEXTURE_RED_SIZE GL_TEXTURE_GREEN_SIZE GL_TEXTURE_BLUE_SIZE GL_TEXTURE_ALPHA_SIZE GL_TEXTURE_LUMINANCE_SIZE GL_TEXTURE_INTENSITY_SIZE GL_TEXTURE_COMPONENTS GL_TEXTURE_COMPRESSED_IMAGE_SIZE Ширина текстурного изображения включая границу текстуры (если она определена) Высота текстурного изображения включая границу текстуры (если она определена) Глубина текстурного изображения включая границу текстуры (если она определена) Внутренний формат текстурного изображения Ширина границы текстуры Разрешение в битах красного компонента текселя Разрешение в битах зеленого компонента текселя Разрешение в битах синего компонента текселя Разрешение в битах компонента альфа текселя Разрешение в битах яркости текселя Разрешение в битах интенсивности текселя Число компонентов цвета данного текстурного изображения Размер в байтах сжатого текстурного изображения (нужен сжатый внутренний формат) glGenTextures Цель: Сгенерировать список имен текстурных объектов Включаемый файл: <gl.h> Синтаксис: void glGenTextures(GLsizei n, GLuint *textures); Описание: Эта функция заполняет массив затребованным количеством имен текстурных объектов Имена текстурных объектов представляют собой целые числа без знака, но при этом нс гарантируется, что возвращаемый массив будет содержать непрерывную последовательность целочисленных имен Имена текстурных объектов, возвращаемые этой функций всегда уникальны, если они не были ранее удалены с помощью glDeleteTextures Параметры: п (тип GLsizei) Число генерируемых текстурных объектов textures Массив, содержащий список сгенерированных имен (тип GLuint*) текстурных объектов Что возвращает: Ничего См. также: glDeleteTextures, glBindTexture, gllsTexture
Глава 8 Наложение текстуры основы 419 gIGetTexLevelParameter Цель: Вернуть параметры текстуры для заданного уровня множественной текстуры Включаемый файл: <gl.h> Варианты: void gIGetTexLevelParameterfv(GLenum target, GLint level, GLenum pname, GLfloat *params); void glGetTexLevelParameteriv(GLenum target, GLint level, GLenum pname, GLint *params); Описание: Эта функция позволяет запрашивать значения различных параметров, которые могут быть справедливыми для конкретного уровня множественной текстуры. Все параметры уровня текстуры, которые можно запрашивать, перечислены в табл. 8 5 Возвращаемые результаты могут содержаться в одном или нескольких значениях с плавающей запятой или целочисленных значениях Параметры: target (тип GLenum) Цель текстурного запроса. Параметр должен иметь значение GL_TEXTURE_1D, GL_TEXTURE_2D, GL_TEXTURE_3D, GL_PROXY_TEXTURE_1D, GL_PROXY_TEXTURE_2 D, GL_PROXY_TEXTURE_3D, GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, GL_TEXTURE_CUBE_MAP_POSITIVE_Z ИЛИ GL_TEXTURE_CUBE_MAP_NEGATIVE_Z level (тип GLint) pname (тип GLenum) params (тип GLfloat* или GLint*) Что возвращает: Запрашиваемый уровень множественной текстуры Константа из табл 8 5, задающая запрашиваемый параметр текстуры В этих переменных записывается возвращаемые значения параметров Ничего См. также: gIGetTexParameter, glTexParameter gIGetTexParameter Цель: Запросить значения параметров текстуры Включаемый файл: <gl. h> Варианты: void glGetTexParameterfv(GLenum target, GLenum pname, GLfloat *params); void glGetTexParameteriv(GLenum target, GLenum pname, GLint *params);
420 Часть I Классический OpenGL ТАБЛИЦА 8.6. Константы запроса параметров текстуры Константа Описание GL_TEXTURE_MAG_FILTER GL_TEXTURE_MIN_FILTER GL_TEXTURE_MIN_LOD GL_TEXTURE_MAX_LOD GL_TEXTURE_BASE_LEVEL GL_TEXTURE_MAX_LEVEL GL_TEXTURE_LOD_BIAS GL_TEXTURE_WRAP_S GL_TEXTURE_WRAP_T GL_TEXTURE_WRAP_R GL_TEXTURE_BORDER_COLOR GL_TEXTURE_PRIORITY GL_TEXTURE_RE SIDENT GL_DEPTH_TEXTURE_MODE GL_TEXTURE_COMPARE_MODE GL_TEXTURE_COMPARE_FUNC GL_TEXTURE_GENERATE_MIPMAP Возвращает значение фильтра увеличения текстуры Возвращает значение фильтра уменьшения текстуры Возвращает значение минимального уровня детализации Возвращает значение максимального уровня детализации Возвращает уровень базовый уровень множественной текстуры Возвращает максимальный уровень массива множественной текстуры Смещение уровня детализации текстуры Возвращает режим обертки в направлении координаты 5 Возвращает режим обертки в направлении координаты t Возвращает режим обертки в направлении координаты г Возвращает цвет границы текстуры Возвращает текущие настройки приоритета текстуры Возвращает GL_TRUE, если текстура резидентная, GL_FALSE — в противном случае Возвращает режим текстуры глубины Возвращает режим сравнения текстуры Возвращает функцию сравнения текстуры Возвращает GL_TRUE, если активизирована автоматическая генерация множественной текстуры Описание: Параметры: target (тип GLenum) pname (тип GLenum) params (тип GLfloat* или GLint*) Что возвращает: См. также: Эта функция позволяет запрашивать значение различных параметров текстуры. Она часто используется с заменителем, что позволяет выяснить, можно ли загрузить текстуру Вес параметры текстуры, которые можно запрашивать, перечислены в табл 8 5. Возвращаемые результаты могут содержаться в одном или нескольких значениях с плавающей запятой или целочисленных значениях Цель текстурного запроса. Параметр должен иметь значение GL_TEXTURE_1D, GL_TEXTURE_2 D, GL_TEXTURE_3D, GL_PROXY_TEXTURE_1 D, GL_PROXY_TEXTURE_2 D, GL_PROXY_TEXTURE_3D ИЛИ GL_TEXTURE_CUBE_MAP Запрашиваемое значение параметра Возможные значения перечислены в табл. 8 6 Адрес переменной (переменных), получающей значение (значения) параметров Ничего glGetTexLevelParameter, glTexParameter
Глава 8 Наложение текстуры' основы 421 gIGetTexImage Цель: Вернуть текстурное изображение Включаемый файл: <gl.h> Синтаксис: void gIGetTexImage(GLenum target, GLint level, GLenum format, GLenum type, void *pixels); Описание: Параметры: Эта функция позволяет заполнить буфер данных информацией, формирующей текущую текстуру Действие этой функции обратно к действию glTexImage, загружающей текстуру в указанный буфер данных target Цель операции копирования текстуры. Параметр должен иметь (тип GLenum) значение GL_TEXTURE_1D, GL_TEXTURE_2D, GL_TEXTURE_3D, GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, GL_TEXTURE_CUBE_MAP_POSITIVE_Z ИЛИ GL_TEXTURE_CUBE_MAP_NEGATIVE_Z level (тип GLint) Считываемый уровень множественной текстуры format Желательный пиксельный формат возвращаемых данных. (тип GLenum) Возможны следующие значения: GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_RGB, GL_RGBA, GL_LUMINANCE, GL_BGR, GL_BGRA ИЛИ GL_LUMINANCE_ALPHA type Тип пикселей возвращаемых данных Возможные значения (тип GLenum) перечислены в табл 8.3 pixels Указатель на буфер памяти, принимающий текстурное (тип void*) изображение Что возвращает: Ничего См. также: glTexImage gllsTexture Цель: Определить, приемлемо ли имя текстурного объекта Включаемый файл: <gl.h> Синтаксис: GLboolean gllsTexture(GLuint texture); Описание: Эта функция позволяет определить, приемлемо ли данное целочисленное значение в качестве имени текстурного объекта. Приемлемым является целочисленное имя, используемое существующим текстурным объектом. Это означает, что для связывания этого имени текстурного по крайней мере раз использовалась функция glBindTexture, причем после этого имя не удалялось с помощью glDeleteTextures
422 Часть I Классический OpenGL Параметры: texture (тип GLuint) Что возвращает: Искомый текстурный объект GL_TRUE, если целое число является приемлемым (используемым) именем текстурного объекта, GL_FALSE — в противном случае См. также: glGenTextures, gIDeleteTextures, glBindTexture, glAreTexturesResidient, gIPrioritizeTextures gIPrioritizeTextures Цель: Установить приоритет текстурного объекта Включаемый файл: <gl.h> Синтаксис: void gIPrioritizeTextures(GLsizei n, Gluint ★textures, const GLclampf *priorities); Описание: Эта функция присваивает n приоритетов текстурным объектам, содержащимся в массиве текстур Массив приоритетов задается в переменной priorities. Приоритет текстуры ограничивается согласно допустимому диапазону от 0 0 до 10, таким образом реализация получает подсказку, что данная текстура должна оставаться резидентной Резидентными называются текстуры, хранящиеся в высокопроизводительной или локальной памяти Приоритет текстуры 1 0 подсказывает, что текстуру желательно хранить как резидентную, тогда как 0 0 рекомендует переключать текстуру при необходимости Параметры: п (тип GLsizei) Количество имен текстурных объектов, содержащихся в массиве текстур textures (тип GLuint*) Массив имен текстурных объектов Каждый элемент массива соответствует приоритету, устанавливаемому в массиве приоритетов priorities (тип const GLclampf*) Массив ограниченных согласно допустимому диапазону значений с плавающей запятой, задающих приоритет текстурного объекта Элемент массива соответствует конкретному имени текстурного объекта, расположенному на аналогичной позиции массива текстур Что возвращает: Ничего См. также: glAreTexturesResident, glGenTextures, glBindTexture, glDeleteTexture, gllsTexture
Глава 8 Наложение текстуры основы 423 gITexCoord Цель: Задать координату текущего текстурного изображения, используемую для визуализации текстурированных многоугольников Включаемый <gl.h> файл: Варианты: void glTexCoordlf(GLfloat s); void glTexCoordlfv(GLfloat *v); void glTexCoordld(GLdouble s); void glTexCoordldv(GLdouble *v); void glTexCoordli(GLint s); void glTexCoordliv(GLint *v); void glTexCoordls(GLshort s); void glTexCoordlsv(GLfloat *v); void glTexCoord2f(GLfloat s, GLfloat t); void glTexCoord2fv(GLfloat *v); void glTexCoord2d(GLdouble s, GLdouble t); void glTexCoord2dv(GLdouble *v); void glTexCoord2i(GLint s, GLint t); void glTexCoord2iv(GLint *v); void glTexCoord2s(GLshort s, GLshort t); void glTexCoord2sv(GLfloat *v); void glTexCoord3f(GLfloat s,GLfloat t,GLfloat r); void glTexCoord3fv(GLfloat *v); void glTexCoord3d(GLdouble s,GLdouble t,GLdouble r); void glTexCoord3dv(GLdouble *v) ; void glTexCoord3i(GLint s, GLint t, GLint r); void glTexCoord3iv(GLint *v); void glTexCoord3s(GLshort s, GLshort t, GLshort r); void glTexCoord3sv(GLfloat *v); void glTexCoord4f(GLfloat s, GLfloat t, GLfloat r, GLfloat g); void glTexCoord4fv(GLfloat *v); void glTexCoord4d(GLdouble s, GLdouble t, GLdouble r, GLdouble g); void glTexCoord4dv(GLdouble *v); void glTexCoord4i(GLint s, GLint t, GLint r, GLint g) ; void glTexCoord4iv(GLint *v); void glTexCoord4s(GLshort s, GLshort t, GLshort r, GLshort g) ; void glTexCoord4sv(GLfloat *v);
424 Часть I Классический OpenGL Приведенные функции устанавливают координату текущего текстурного изображения в одном или нескольких измерениях (до 4) Текстурные координаты можно обновлять где-угодно между glBegin и glEnd, и они соответствуют последующему вызову glVertex Координата д текстуры используется для масштабирования значений координат s, t и г, и по умолчанию ее значение равно 1.0. Использовав в качестве цели функции glMatrixMode параметр GL_TEXTURE, вы сможете применять к текстурным координатам любые допустимые Описание: См. также: матричные операции Горизонтальная координата текстурного изображения Вертикальная координата текстурного изображения Глубинная координата текстурного изображения Масштабная координата текстурного изображения Массив значений, содержащий 1, 2, 3 или 4 значения, требуемых для задания текстурной координаты Ничего Параметры: s (тип GLdouble или GLfloat, или GLint, или GLshort) t (тип GLdouble или GLfloat, или GLint, или GLshort) г (тип GLdouble или GLfloat, или GLint, или GLshort) q (тип GLdouble или GLfloat, или GLint, или GLshort) v (тип GLdouble* или GLfloat*, или GLint*, или GLshort*) Что возвращает: glTexGen, glTexImage, glTexParameter gITexEnv Цель: Установить параметры текстурной среды Включаемый <gl.h> файл: Варианты: void glTexEnvf(GLenum target,GLenum pname,GLfloat param); void glTexEnvfv(GLenum target,GLenum pname,GLfloat ★param); void glTexEnvi(GLenum target,GLenum pname,GLint param); void glTexEnviv(GLenum target, GLenum pname,GLint *param); Описание: Данные функции устанавливают параметры среды отображения текстуры Текстурная среда устанавливается для текстурной единицы и существует вне состояния, ограниченного glBindTexture Следовательно, текстурная среда влияет на все текстурные объекты, которые можно связать с активной текстурной единицей
Глава 8 Наложение текстуры, основы 425 ТАБЛИЦА 8.7. Режимы текстурной среды Константа Описание GL_DECAL Значения текселей применяются к кодам геометрических фрагментов Если активизировано смешивание и текстура содержит альфа-канал, геометрические объекты смешиваются с текстурой согласно текущей функции смешивания GL_REPLACE Значения текселей замещают коды геометрических фрагментов Если активизировано смешивание и текстура содержит альфа-канал, коды альфа текстуры используются для замещения цветов геометрических фрагментов в буфере цветов GL_MODULATE Коды цветов текселей умножаются на коды цветов геометрических фрагментов GL_ADD Коды цветов текселей прибавляются к кодам цветов геометрических фрагментов GLJBLEND GL_COMBINE Коды цветов текселей умножаются на цвет текстурной среды Коды цветов текселей объединяются со второй текстурной единицей согласно функции объединения текстуры (см следующую главу) Параметры: target (тип GLenum) рлате (тип GLenum) Определяемая текстурная среда Параметр должен иметь значение GL_TEXTURE_ENV ИЛИ GL_TEXTURE_FILTER_CONTROL Определяемое имя параметра Когда целью является GL_TEXTURE_FILTER CONTROL, имя параметра должно быть равно GL_TEXTURE_LOD_BIAS, а параметром является значение, смещающее выбор уровня детализации множественной текстуры Когда целью является gl_texture_env, имя параметра должно быть равно gl_texture_env_mode, gl_texture_env_color, GL_COMBINE_RGB ИЛИ GL_COMBINE_ALPHA Когда имя параметра равно gl_texture_env_color, параметр указывает на массив, содержащий коды цвета раскраски текстурной среды Когда имя параметра равно GL_texture_env_mode, допустимы следующие параметры- GL_REPLACE, GL_DECAL, GL_MODULATE, gl_blend, GL_ADD или GL_COMBINE Данные режимы среды описаны в табл 8 7 param Значение параметра Допускается одно из указанных выше значений(GL_REPLACE, GL_DECAL, GL_MODULATE, GLJBLEND, GL_ADD ИЛИ GL_COMBINE) ИЛИ, для GL_TEXTURE_ENV_COLOR, — массив, содержащий RGBA-компоненты цвета текстурной среды Что возвращает: Ничего См. также: glTexParameter
glTexImage Цель: Определить одно-, двух- или трехмерное текстурное изображение Включаемый файл: <gl. h> Варианты: void glTex!magelD(GLenum target, GLint level, GLint internal format, GLsizei width, GLint border, GLenum format, GLenum type, void *data); void glTex!mage2D(GLenum target, GLint level, GLint internal format, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, void *data); void glTex!mage3D(GLenum target, GLint level, GLint internal format, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, void *data}; Описание: Эта функция определяет одно-, двух- или трехмерное текстурное изображение. Данные изображения подчиняются режимам, определенным с помощью функций glPixelMap, glPixelStore и glPixelTransfer Параметры: target (тип GLenum) Цель задаваемого действия. Параметр должен иметь одно из следующих значений GL_TEXTURE_1D или GL_PROXY_TEXTURE_1D для glTexImagelD, GL_TEXTURE_2D ИЛИ GL_PROXY_TEXTURE_2D для glTex!mage3D, GL_TEXTURE_3D ИЛИ GL_TEXTURE_PROXY_3D ДЛЯ glTex!mage3D Только для двухмерных кубических карт он также может иметь значение GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_CUBE_MAP_POSITIVE_Y, GL_CUBE_MAP_NEGATIVE_Y, GL_CUBE_MAP_POSITIVE_Z ИЛИ GL_CUBE_MAP_NEGATIVE_Z level (тип GLint) internalFormat (тип GLint) Уровень детализации Обычно — 0, если множественное отображение не используется Внутренний формат данных изображения Может содержать цифры 1^4, указывающие число компонентов цвета, или одну ИЗ следующих констант. GL_ALPHA, GL_ALPHA4, GL_ALPHA8, GL_ALPHA12, GL_ALPHA16, GL_LUMINANCE, GL_LUMINANCE4, GL_LUMINANCE8, GL_LUMINANCE12, GL_LUMINANCE16, GL_LUMINANCE_ALPHA, GL_LUMINANCE4_ALPHA4, GL_LUMINANCE6_ALPHA2, GL_LUMINANCE8_ALPHA8, GL_LUMINANCE 12_ALPHA4, GL_LUMINANCE 12_ALPHA12, GL_LUMINANCE16_ALPHA16, GL_INTENSITY, GL_INTENSITY4, GL_INTENSITY8, GL_INTENSTIY12, GL_INTENSITY16, GL_RGB, GL_R3_G3_B2,GL_RGB4, GL_RGB5, GL_RGB8, GL_RGB10, GL_RGB12, GL_RGB16, GL_RGBA, GL_RGBA2, GL_RGBA4, GL_RGB5_A1, GL_RGBA8, GL_RGB10_A2, GL_RGBA12, GL_RGBA16
Глава 8 Наложение текстуры основы 427 width (тип GLsizei) Ширина одно-, двух или трехмерного текстурного изображения. Параметр должен быть степенью 2, но может включать границу текстуры height (тип GLsizei) Высота двух или трехмерного текстурного изображения Параметр должен быть степенью 2, но может включать границу текстуры depth (тип GLsizei) border (тип GLint) format (тип GLenum) type (тип GLenum) Глубина трехмерного текстурного изображения. Параметр должен быть степенью 2, но может включать границу текстуры Ширина границы Все реализации должны поддерживать границы текселей 0, 1 и 2 Формат информации о пикселях. Допускается использование любого из указанных в табл 8 2 типов формата Тип данных каждого значения текселя Допустимые типы перечислены в табл. 8 3 pixels (тип GLvoid*) Что возвращает: Информация о пикселях Ничего См. также: glTexSublmage, glCopyTexImage, glCopyTexSublmage glTexParameter Цель: Установить параметры наложения текстуры Включаемый файл: <gl h> Варианты: void glTexParameterf(GLenum target, GLenum qewpname, GLfloat param); void glTexParameterfv(GLenum target, GLenum pname, GLfloat *pa ram); void glTexParameteri(GLenum target, GLenum pname, GLint param); void glTexParameteriv(GLenum target, GLenum pname, GLint *param); Описание: Эта функция устанавливает несколько параметров наложения текстуры Эти параметры связаны с текущим состоянием текстуры (чтобы сделать состояние текущим, применяется функция glBindTexture) Параметры: target (тип GLenum) Цель (текстура), к которой применяется указанный параметр Возможные значения gl_texture_id, gl_texture_2d, GL_TEXTURE_3D ИЛИ GL_TEXTURE_CUBE_MAP pname (тип GLenum) Устанавливаемый параметр текстурирования Допустимы следующие имена GL_TEXTURE_MIN_FILTER- Задает метод или фильтр уменьшения текстурного изображения. Можно использовать текстурные фильтры из табл 8 4
428 Часть I. Классический OpenGL GL_TEXTURE_MAX_FILTER: Задает метод или фильтр увеличения текстурного изображения. Можно использовать текстурные фильтры из табл. 8 4 GL_TEXTURE_MAX_LOD: Задает максимальный уровень детализации текстуры, используемый во множественной текстуре GL_TEXTURE_MIN_LOD: Задает минимальный уровень детализации текстуры, используемый во множественной текстуре GL_TEXTURE_BASE_LEVEL. Задает минимальный загружаемый уровень детализации текстуры GL_TEXTURE_MAX_LEVEL: Задает максимальный загружаемый уровень детализации текстуры GL_TEXTURE_WRAP_S: Задает способ обработки координат s текстуры, не попадающих в диапазон от 0 0 до 1 О (GL_CLAMP, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_BORDER, GL_REPEAT, GL_MIRRORE D_RE PEAT) GL_TEXTURE_WRAP_T: Задает способ обработки координат t текстуры, не попадающих в диапазон от 0.0 до 1.0 (GL_CLAMP, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_BORDER, GL_REPEAT, GL_MIRRORE D_RE PEAT) GL_TEXTURE_WRAP_R. Задает способ обработки координат т текстуры, не попадающих в диапазон от 0.0 до 1 0 (GL_CLAMP, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_BORDER, GL_REPEAT) GL_BORDER_COLOR. Задает цвет границы для текстуры без границы GL_GENERATE_MIPMAP Задает, должны ли уровни детализации множественной текстуры генерироваться автоматически (GL_TRUE = да) GL_TEXTURE_PRIORITY- Устанавливает приоритет данной текстуры. Значение должно ограничиваться согласно допустимому диапазону от 0 0 до 10 GL_DEPTH_TEXTURE_MODE: Задает режим текстуры глубины (см главу 18) GL_TEXTURE_COMPARE_MODE Устанавливает режим сравнения текстуры (см главу 18) GL_TEXTURE_COMPARE_FUNC’ Устанавливает функцию сравнения текстуры (см главу 18) param (тип GLfloat Значение параметра, заданного именем pname или GLfloat*, или GLint или GLint*) Что возвращает: Ничего См. также: glTexEnv, glTexGen, glBindTexture
I лава а наложение текстуры основы 429 glTexSublmage Цель: Включаемый файл: Варианты: Описание: Параметры: target (тип GLenum) level (тип GLint) xOffset (тип GLint) yOffset (тип GLint) zOffset (тип GLint) width (тип GLsizei) height (тип GLsizei) depth (тип GLsizei) forma t (тип GLenum) type (тип GLenum) data (тип const void*) Что возвращает: См. также: Заместить фрагмент существующей карты текстуры <gl.h> void glTexSub!magelD(GLenum target, GLint level, GLint xOffset, GLsizei width, GLenum format, GLenum type, const GLvoid *data); void glTexSub!mage2D(GLenum target, GLint level, GLint xOffset, GLint yOffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *data); void glTexSub!mage3D(GLenum target, GLint level, GLint xOffset, GLint yOffset, GLint zOffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *data); Эта функция замещает фрагмент существующей одно-двух-, или трехмерной карты текстуры. Обновление всей или части существующей карты текстуры может выполняться существенно быстрее, чем загрузка нового текстурного изображения с помощью glTexImage С помощью данной функции вы не можете выполнить первоначальную загрузку текстуры; она применяется только для обновления существующей текстуры Цель текстурного запроса Параметр должен иметь значение GL_TEXTURE_1D, GL_TEXTURE_2D ИЛИ GL_TEXTURE_3D Обновляемый уровень множественной текстуры Смещение в направлении х до позиции существующей одно-, двух- или трехмерной текстуры, с которой начинается обновление Смещение в направлении у до позиции существующей двух- или трехмерной текстуры, с которой начинается обновление Смещение в направлении z до позиции существующей трехмерной текстуры, с которой начинается обновление Ширина обновляемых данных одно-, двух- или трехмерной текстуры Высота обновляемого двух- или трехмерного текстурного изображения Глубина обновляемого трехмерного текстурного изображения Любой приемлемый формат текстуры (см. табл 8 2) Любой приемлемый тип данных пикселя (см. табл. 8.3) Указатель на данные, используемые для обновления цели (текстуры) Ничего glTexImage, glCopyTexSublmage
430 Часть I Классический OpenGL gluBuildMipmapLevels Цель: Включаемый файл: Варианты: Описание: Параметры: target (тип GLenum) internalFormat (тип GLint) width (тип GLint) height тип GLint) depth (тип GLint) format (тип GLenum) type (тип GLenum) level (тип GLint) base (тип GLint) max (тип GLint) data (тип void*) Что возвращает: См. также: Автоматически сгенерировать и обновить диапазон уровней множественной текстуры <gl.h> int gluBuildlD MipmapLevels(GLenum target, GLint internalFormat, GLint width, GLenum format, GLenum type, GLint level, GLint base, GLint max, const void *data); int gluBuild2D MipmapLevels(GLenum target, GLint internalFormat, GLint width, GLint height, GLenum format, GLenum type, GLint level, GLint base, GLint max, const void *data); int gluBuild3D MipmapLevels(GLenum target, Glint internalFormat, GLint width, GLint height, GLint depth, GLenum format, GLenum type, GLint level, GLint base, GLint max, const void *data); С помощью gluScalelmage эта функция автоматически масштабирует и загружает ряд уровней множественной текстуры, опираясь на предоставленный основной уровень. Преимущество использования этой функции заключается в том, что она позволяет обновлять только выбранный диапазон уровней множественной текстуры Цель текстурного запроса. Параметр должен иметь значение GL_TEXTURE_1D, GL_TEXTURE_2D ИЛИ GL_TEXTURE_3D Любой приемлемый внутренний формат текстуры, распознаваемый функцией glTexImage Ширина одно-, двух- или трехмерного источника текстуры Высота двух- или трехмерного источника текстуры Глубина трехмерного источника текстуры Любой приемлемый формат текселей (см. табл 8 2) Любой приемлемый тип данных текселя (см. табл 8 3) Основной уровень множественной текстуры, заданный данными, предоставленными в data Уровень, с которого начинается генерация множественной текстуры Самый высокий генерируемый уровень множественной текстуры (самое маленькое изображение) Предоставленные данные текстурного изображения Ничего gluBuildMipmaps
Глава 8 Наложение текстуры основы 431 gluBuildMipmaps Цель: Включаемый файл: Варианты: Описание: Параметры: target (тип GLenum) internalFormat (тип GLint) width (тип GLint) height (тип GLint) depth (тип GLint) format (тип GLenum) type (тип GLenum) data (тип void*) Что возвращает: См. также: Автоматически создать и загрузить набор готовых изображений множественной текстуры <gl.h> int gluBuildlD Mipmaps(GLenum target, GLint internalFormat, GLint width, GLenum format, GLenum type, const void *data); int gluBuild2D Mipmaps(GLenum target, GLint internalFormat, GLint width, GLint height, GLenum format, GLenum type, const void *data); int gluBuild3D Mipmaps(GLenum target, GLint internalFormat, GLint width, GLint height, GLint depth, GLenum format, GLenum type, const void ★da ta) ; Эта функция принимает предоставленные текстурные данные (основной уровень множественной текстуры) и автоматически последовательно масштабирует изображение, загружая все уровни множественной текстуры. Эта работа выполняется процессором клиента, а не реализацией OpenGL, поэтому операция может быть довольно длительной Цель текстурного запроса. Параметр должен иметь значение GL_TEXTURE_1D, GL_TEXTURE_2D ИЛИ GL_TEXTURE_3D Любой приемлемый внутренний формат текстуры, распознаваемый функцией glTexImage Ширина одно-, двух- или трехмерного источника текстуры Высота двух- или трехмерного источника текстуры Глубина трехмерного источника текстуры Любой приемлемый формат текселей (см табл 8 2) Любой приемлемый тип данных текселей (см табл 8 3) Данные основного уровня множественной текстуры Ничего gluBuildMipmapLevels

ГЛАВА 9 Наложение текстуры: следующий шаг Ричард С. Райт-мл Из ЭТОЙ ГЛАВЫ ВЫ УЗНАЕТЕ . . . Действие Функция Добавление зеркальных бликов к текстурным объектам Использование анизотропной текстурной фильтрации Загрузка и использование сжатых текстур gILightModel, glSecondaryColor glTexParameterf glCompressedTexImage, glCompressedTexSublmage Наложение текстуры является, пожалуй, одной из самых впечатляющих особен- ностей OpenGL (ну ладно, чуть-чуть позади схем затенения1), и оно жизненно важно в индустрии игр и имитаторов В главе 8, “Наложение текстуры- основы,” излагались основы загрузки и наложения карт текстуры на геометрические объекты. В данной главе мы расширим полученные знания и рассмотрим несколько тонких моментов наложения текстуры в OpenGL. Дополнительный цвет Наложение текстуры на геометрический объект дает после применения освещения скрытый и часто нежелательный побочный эффект. В общем случае текстурная среда устанавливается равной GL_MODLULATE, что приводит к такому объединению осве- щенной геометрии с картой текстуры, что текстурная геометрия также кажется осве- щенной. Обычно OpenGL рассчитывает освещение и цвета отдельных фрагментов согласно стандартной модели освещения. Затем цвета фрагментов объединяются с от- фильтрованными текселями, которые налагаются на геометрические объекты. Однако данный процесс обладает побочным эффектом- существенно уменьшается видимость зеркальных “зайчиков” на поверхности. На рис. 9.1 показано исходное изображения мира сфер из главы 5, “Цвет, мате- риалы и освещение: основы”. На этом рисунке отчетливо видны блики на поверхно- сти тора. На рис. 9.2 показан результат выполнения программы SPHEREWORLD из предыдущей главы. На этом рисунке можно наблюдать эффекты наложения текстуры после добавления освещения
434 Часть I. Классический OpenGL Рис. 9.1. Исходный тор SPHEREWORLD с зеркальными зайчиками Рис. 9.2. Текстурный тор с приглушенными бликами
Глава 9. Наложение текстуры: следующий шаг 435 Рис. 9.3. Блики, восстановленные на текстурном торе Решением данной проблемы является применение зеркальных бликов после на- ложения текстуры. Данную модель, получившую название вторичного зеркального цвета, можно применять вручную или автоматически (в рамках модели освещения). Обычно при этом выбирается стандартная модель освещения OpenGL, просто вклю- чаемая с помощью gILightModel. glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR); Чтобы позже вернуться к нормальной модели освещения, можно указать GL_SINGLE_COLOR в качестве параметра модели света. glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_COLOR_SINGLE); На рис. 9.3 показан результат выполнения программы SPHEREWORLD (очеред- ная версия) с восстановленными зеркальными бликами на торе. Мы не приводим полный листинг программы, поскольку в нем всего лишь добавлена указанная выше строчка кода. Кроме того, вызвав функцию glSecondaryColor, можно непосредственно за- дать вторичный цвет после наложения текстуры, если вы не используете освещение (освещение деактивизировано). Эта функция, как и glColor, имеет множество раз- новидностей, перечисленных в справочном разделе. Следует также отметить, что, задав вторичный цвет, вы также должны явно активизировать его использование. glEnable(GL_COLOR_SUM);
436 Часть I. Классический OpenGL Изотропная выборка Анизотропная выборка Рис. 9.4. Нормальная и анизотропная дискретизация текстуры Анизотропная фильтрация Анизотропная фильтрация текстуры не является частью основной спецификации OpenGL, но относится к широко поддерживаемым расширениям, которые могут су- щественно улучшить качество фильтрации текстуры. Фильтрация текстуры рассмат- ривалась в предыдущей главе, из которой вы узнали о двух базовых фильтрах тек- стуры: фильтре ближайших “соседей” (GL_nearest) и линейном (gl_linear). При фильтрации карты текстуры OpenGL с помощью текстурных координат определяет, куда в карте текстуры попадает конкретный фрагмент геометрического объекта Тек- сели, непосредственно окружающие данную точку, дискретизуются с использованием одной из двух операций фильтрации: GL_nearest или GL_linear Описанный процесс работает идеально, когда текстура, наложенная на геометрию, изучается перпендикулярно точке наблюдения, как показано на рис 9 4 (слева) Одна- ко при изучении геометрии под углом к точке наблюдения, регулярная дискретизация окружающих текселей приводит к потере в текстуре части информации (в результате текстура выглядит размытой!). Более реалистичными и точными были бы выборки, вытянутые вдоль направления плоскости, содержащей текстуру. Результат этого пока- зан на рис. 9.4 (справа). Учет угла наблюдения при фильтрации текстуры называется анизотропной фильтрацией. Анизотропную фильтрацию можно применить к любому из основных ре- жимов фильтрации текстуры или режимов множественной текстуры, ее при- менение требует трех этапов. Вначале нужно определить, поддерживается ли расширение. Это означает, что нужен запрос относительно строки расширения GL_EXT_texture_filter_anisotropic. Для выполнения этой задачи можно за- действовать функцию glTools gltlsExtensionSupported. if(glt!sExtSupported("GL_EXT_texture_filter_anisotropic”)) // Устанавливает метку, что расширение поддерживается Определив, что расширение поддерживается, можно найти максимальную вели- чину поддерживаемой анизотропии. Для этого можно применить функцию glGet- Floatv и параметр gl_max_texture_max_anisotropy_ext. GLfloat fLargest; glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &fLargest); Чем больше величина применяемой анизотропии, тем больше текселей дискре- тизуется вдоль направления наибольших изменений. Значение 1 0 представляет нор- мальную фильтрацию текстуры (именуемую изотропной фильтрацией) Следует все-
Глава 9. Наложение текстуры: следующий шаг 437 Рис. 9.5. Туннель из программы ANISOTROPIC с трилинейной фильтрацией гда помнить, что анизотропная фильтрация не является бесплатной. Дополнительный объем работы (в том числе, с другими текселями) может иногда привести к суще- ственному снижению производительности. На современном аппаратном обеспечении данная возможность реализуется достаточно быстро, благодаря чему она стала стан- дартной в популярных играх, анимации и имитаторах. В заключение с помощью функции glTexParameter и константы GL_TEXTURE_ MAX_ANISOTROPY_EXT устанавливается величина анизотропии, которую вы хотите применить. Например, если, используя предыдущий код, вы хотите получить макси- мально возможную анизотропию, следует так вызывать функцию glTexParameter: glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, fLargest); Данный модификатор применяется к текстурному объекту точно так же, как стан- дартные параметры фильтрации. В приведенной на компакт-диске программе ANISOTROPIC предлагается заме- чательный пример анизотропной фильтрации текстуры. Эта программа отображает туннель со стенами, полом и потолком. Клавиши со стрелками перемещают точку на- блюдения (или туннель) взад-вперед. Щелчок правой кнопкой мыши вызывает меню, позволяющее выбирать различные фильтры текстуры, а также включать и выключать анизотропную фильтрацию. На рис. 9.5 показан туннель с применением множествен- ного отображения с трилинейной фильтрацией. Обратите внимание на то, каким рас- плывчатым становится узор с расстоянием, что особенно заметно на кирпичах стен.
438 Часть I. Классический OpenGL Рис. 9.6. Пример туннеля ANISOTROPIC с анизотропной фильтрацией Сравним теперь рис. 9.5 с рис. 9.6, на котором была активизирована анизотроп- ная фильтрация. Теперь раствор между кирпичами виден отчетливо вплоть до кон- ца туннеля. Анизотропная фильтрация может также существенно сократить види- мые “узоры” в местах перехода между уровнями множественной текстуры при ис- пользовании фильтров множественного отображения gl_linear_mipmap_nearest И GL_NEAREST_MIPMAP_NEAREST. Сжатие текстуры Наложение текстуры может повысить реалистичность любой визуализированной трехмерной сцены при минимальной цене с точки зрения обработки вершин. Од- нако недостатком текстур является то, что они требуют много памяти для хранения и обработки. Ранние попытки сжатия текстуры заключались в простой записи тек- стур в форме файлов JPG и восстановлении текстуры при загрузке перед вызовом функции glTexImage. Это позволяло экономить дисковое пространство и сокращать время передачи изображения через сеть (подобную Internet), но требования к па- мяти для хранения текстурных изображений, загруженных в память графического аппаратного обеспечения, оставались. “Родная” поддержка сжатия текстуры была добавлена к OpenGL в версии 1.3. Бо- лее ранние версии OpenGL могут также под держивать сжатие текстуры посредством одноименной функции расширения. Проверить, допустимо ли расширение, можно С помощью параметра GL_ARB_texture_compression.
Глава 9 Наложение текстуры следующий шаг 439 ТАБЛИЦА 9.1. Форматы сжатых текстур Сжатый формат Базовый внутренний формат GL_COMPRESSED_ALPHA GL_COMPRESSED_LUMINANCE GL_COMPRESSED_LUMINANCE_ALPHA GL_COMPRESSED_INTENSITY GL_COMPRESSED_RGB GL_COMPRESSED_RGBA GL_ALPHA GL_LUMINANCE GL_LUMINANCE_ALPHA GL_INTENSITY GL_RGB GL_RGBA Поддержка сжатия текстуры в аппаратном обеспечении OpenGL позволяет не толь- ко загружать сжатую текстуру; в большинстве реализаций текстуры остаются сжаты- ми даже в памяти графической аппаратуры Это позволяет загружать больше текстур в память меньшего размера и значительно повышать производительность при работе с текстурой из-за более редкого переключения текстур (перемещения текстур) и более редких обращений к памяти в процессе фильтрации текстуры. Техника сжатия текстуры Чтобы воспользоваться преимуществами поддержки OpenGL сжатых текстур, тек- стуры не нужно сжимать изначально. Чтобы попросить OpenGL сжимать текстурное изображение при загрузке, применяется одно из перечисленных в табл 9 1 значений параметра internalFormat любой функции glTexImage Такое сжатие изображение немного увеличивает издержки при загрузке тексту- ры, но может повысить производительность операций с текстурой вследствие более эффективного использования памяти текстуры. Если по какой-то причине текстуру нельзя сжать, OpenGL использует вместо нее указанный базовый внутренний формат и загружает текстуру несжатой Если вы пытаетесь загрузить и сжать текстуру описанным способом, проверить, была ли текстура успешно сжата, можно, вызвав функцию glGetTexLevelParame- teriv С параметром GL_TEXTURE_COMPRESSED. GLint compFlag; glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED, &compFlag); Функция glGetTexLevelParameteriv принимает несколько новых имен па- раметров, относящихся к сжатым текстурам Все эти параметры перечислены в табл 9 2 При сжатии текстур с применением значений, перечисленных в табл. 9 1, OpenGL выбирает наиболее подходящий формат сжатия текстуры С помощью glHint вы можете указать, должен ли OpenGL делать выбор в пользу наиболее быстрого или наиболее качественного алгоритма glHint(GL_TEXTURE_COMPRESSION_HINT, GL_FASTEST); glHint(GL_TEXTURE_COMPRESSION_HINT, GL_NICEST); glHint(GL_TEXTURE_COMPRESSION_HINT, GL_DONT_CARE);
440 Часть I Классический OpenGL ТАБЛИЦА 9.2. Параметры сжатой текстуры, извлекаемые с помощью glGetTexLevelParameter Параметр Что возвращает GL_TEXTURE_COMPRESSED Значение 1, если текстура сжатая, 0 — в противном случае GL_TEXTURE_COMPRESSED_IMAGE_SIZE GL_TEXTURE_INTERNAL_FORMAT GL_NUM_COMPRESSED_TEXTURE_FORMATS Размер в байтах сжатой текстуры Использованный формат сжатия Число поддерживаемых форматов сжатых текстур GL_COMPRESSED_TEXTURE_FORMATS Массив констант, соответствующих всем поддерживаемым форматам сжатых текстур GL_TEXTURE_COMPRESSION_HINT Код подсказки сжатия текстуры (GL_NICEST/GL_FASTEST) ТАБЛИЦА 9.3. Форматы сжатия ДЛЯ GL_EXT_texture_compression_s3tc Формат Описание GL_COMPRESSED_RGB_S3TC_DXT1 RGB-данные сжаты, значение альфа всегда равно 1 0 GL_COMPRESSED_RGBA_S3TC_DXT1 RGB-данные сжаты, значение альфа равно 1 0 или 0 0 GL_COMPRESSED_RGBA_S3TC_DXT3 RGB-данные сжаты, значение альфа записано в 4 бит GL_COMPRESSED_RGBA_S3TC_DXT5 RGB-данные сжаты, значение альфа является взвешенным средним 8-битовых значений Точный формат сжатия зависит от реализации. Чтобы узнать количество фор- матов сжатия и список соответствующих кодов, применяются константы GL_NUM_ COMPRESSED_TEXTURE_FORMATS И GL_COMPRESSED_TEXTURE_FORMATS. Чтобы про- верить поддержку конкретного набора форматов сжатых текстур, нужно прове- рить наличие расширения, поддерживающего эти форматы Например, одним из наиболее популярных форматов сжатия текстуры (на ПК и Macintosh) является GL_EXT_texture_compression_s3tc Если расширение поддерживается, поддер- живаются и все форматы сжатых текстур, перечисленные в табл. 9 3 (все эти кон- станты определены в файле glext. h на компакт-диске), но только для двухмерных текстур. Загрузка сжатых текстур С помощью функций, описанных в предыдущем разделе, можно указать OpenGL сжать текстуры в “родном” поддерживаемом формате, извлечь данные с помо- щью функции glGetCompressedTexImage (идентична функции gIGetTexImage из предыдущей главы) и записать их на диск При последующих загрузках необрабо- танные сжатые данные можно использовать, что приведет к гораздо более быстрой загрузке текстуры
Глава 9 Наложение текстуры, следующий шаг 441 Чтобы загрузить предварительно сжатые данные текстуры, применяется одна из указанных ниже функций void glCompressedTexImagelD(GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLint border, GLsizei imageSize, void *data); void glCompressedTexImage2D(GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, void *data); void glCompressedTexImage3D(GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border, Glsizei imageSize, GLvoid *data); Все эти функции практически идентичны функции glTexImage, рассмотренной в предыдущей главе Единственным отличием является то, что параметр internal- Format должен задавать поддерживаемое сжатое текстурное изображение. Если реа- лизация поддерживает расширение GL_EXT_texture_compression_s3tc, это будет одно из значений, перечисленных в табл 9.3. Существует также соответствующий набор функций glCompressedTexSublmage, предназначенных для обновления ча- сти или всех уже загруженных текстур, копирующий функциональные возможности набора glTexSublmage (см. предыдущую главу). Генерация текстурных координат В главе 8 рассказывалось, что текстуры отображаются на геометрический объект с ис- пользованием текстурных координат Часто при загрузке моделей (см главу 11, “Все о конвейере более быстрое прохождение геометрии”) текстурные координаты уже предоставляются вам. Если требуется, вы легко можете вручную отобразить текстур- ные координаты на любую поверхность (сферу или плоскость) Однако для сложной плоскости не так-то легко вручную вывести координаты. В подобных случаях мож- но обратиться за помощью к OpenGL, который (с определенными ограничениями) автоматически генерирует текстурные координаты. Генерация текстурных координат S, Т, R и Q активизируется с помощью функ- ции glEnable glEnable(GL_TEXTURE_GEN_S); glEnable(GL_TEXTURE_GEN_T); glEnable(GL_TEXTURE_GEN_R); glEnable(GL_TEXTURE_GEN_Q); При активизированной генерации текстурных координат все вызовы функции gl- TexCoord игнорируются, и OpenGL для каждой вершины рассчитывает текстурные координаты. Разумеется, генерацию текстурных координат можно не только вклю- чить, но и выключить, применив для этого функцию glDisable. glDisable(GL_TEXTURE_GEN_S); glDisable(GL_TEXTURE_GEN_T);
442 Часть I Классический OpenGL glDisable(GL_TEXTURE_GEN_R); glDisable(GL_TEXTURE_GEN_Q); Функция или метод, используемые для генерации текстурных координат, задаются с помощью следующих функций: void glTexGenf(GLenum coord, GLenum pname, GLfloat param); void glTexGenfv(GLenum coord, GLenum pname, GLfloat *param); Параметр coord задает, какую текстурную координату устанавливает функция Он может иметь такие значения: GL_S, GL_T, GLJR или GL_Q. Параметр pname должен быть равен gl_texture_gen_mode, gl_object_plane или gl_eye_plane. Послед- ний параметр задает значения функции или режима генерации текстуры Обратите внимание на то, что существуют также версии этой функции, принимающие целые величины (GLint) и значения двойной точности (GLdouble). В листинге 9 1 представлена программа TEXGEN, отображающая тор, которым можно манипулировать (вращать) с помощью клавиш со стрелками. Щелчком правой кнопки мыши вызывается контекстное меню, позволяющее выбирать три режима ге- нерации текстуры, которые мы обсудим ниже- линейный по объектам (Object Linear), линейный по наблюдению (Eye Linear) и отображение сферы (Sphere Mapping). Листинг 9.1. Исходный код программы TEXGEN ♦include /Common/OpenGLSB.h" // Активизируются необходимые возможности системы и OpenGL ♦include /Common/gltools.h" // Библиотека gltools // Величины поворотов static GLfloat xRot = O.Of; static GLfloat yRot = O.Of; GLuint toTextures[2]; // Два текстурных объекта int iRenderMode = 3; //По умолчанию установлено отображение сферы /////////////////////////////////////////////////////////////////// // В ответ на выбор позиции меню устанавливаются // соответствующие метки void ProcessMenu(int value) { // Плоскость проекции GLfloat zPlane[] = O.Of, O.Of, l.Of, O.Of ; // Записывается режим визуализации iRenderMode = value; // Согласно выбору позиции меню, устанавливается // генератор текстуры switch(value) { case 1: // Линейный по объектам glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); glTexGenfv(GL_S, GL_OBJECT_PLANE, zPlane); glTexGenfv(GL_T, GL_OBJECT_PLANE, zPlane); break; case 2: // Линейный по наблюдению
Глава 9 Наложение текстуры следующий шаг 443 glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR); glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR); glTexGenfv(GL_S, GL_EYE_PLANE, zPlane); glTexGenfv(GL_T, GL_EYE_PLANE, zPlane); break; case 3: default: // Отображение сферы glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); break; glutPostRedisplay(); // Восстановление изображения ) //////////////////////////////////////////////////////////////// Вызывается для рисования сцены d RenderScene(void) { // Очищаем окно текущим цветом очистки glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Переключение на ортографическую проекцию // для фонового рисования glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadldentity(); gluOrtho2D(0 Of, l.Of, 0 Of, l.Of); glMatrixMode(GL_MODELVIEW); glBindTexture(GL_TEXTURE_2D, toTextures[1]); // Фоновая текстура // Задаем текстурные координаты glDisable(GL_TEXTURE_GEN_S); glDisable(GL_TEXTURE_GEN_T); // Для фона запись в буфер глубины не производится glDepthMask(GL_FALSE); // Фоновое изображение glBegin(GL_QUADS); glTexCoord2f(0 Of, O.Of); glVertex2f(0.Of, O.Of); glTexCoord2f(l.Of, O.Of); glVertex2f(l Of, O.Of); glTexCoord2f(1 Of, l.Of); glVertex2f(1.Of, 1 Of); glTexCoord2f(O.Of, l.Of); glVertex2f(0.Of, l.Of); glEnd(); // Возвращаемся к трехмерному основанию glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); // Включаем генерацию текстур и запись в буфер глубины glEnable(GL_TEXTURE_GEN_S); glEnable(GL_TEXTURE_GEN_T);
444 Часть I Классический OpenGL glDepthMask(GL_TRUE); // Возможно, потребуется окаймить текстуру i f(iRende rMode ! = 3) glBindTexture(GL_TEXTURE_2D, toTextures[0]); // Записывается состояние матрицы и выполняются повороты glPushMatrix(); glTranslatef(0.Of, O.Of, -2.Of); glRotatef(xRot, l.Of, O.Of, O.Of); glRotatef(yRot, O.Of, l.Of, O.Of); // Рисуется тор gltDrawTorus(0.35, 0.15, 61, 37); // Восстанавливается состояние матрицы glPopMatrix(); // Отображаются результаты glutSwapBuffers(); /////////////////////////НИИ II III IIII НИИ / / / И / И И И И /////// // Эта функция выполняет необходимую инициализацию в контексте // визуализации. void SetupRC() { GLbyte *pBytes; // Байты текстуры GLint iComponents, iWidth, iHeight; // Размеры текстуры GLenum eFormat; // Формат текстуры glEnable(GL_DEPTH_TEST); // Удаление скрытых // поверхностей gIFrontFace(GL_CCW); // Многоугольники с обходом против часовой стрелки // смотрят вперед glEnable(GL_CULL_FACE); // Внутри самолета расчеты не производятся // Белый фон glClearColor(l.Of, l.Of, l.Of, l.Of ); // Маркировка текстурной среды glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); // Две текстуры glGenTextures(2, toTextures); lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll И Загружается основная текстура glBindTexture(GL_TEXTURE_2D, toTextures[0]); pBytes = gltLoadTGA("stripes.tga", &iWidth, &iHeight, &iComponents, &eFormat); glTex!mage2D(GL_TEXTURE_2D, 0, iComponents, iWidth, iHeight, 0, eFormat, GL_UNSIGNED_BYTE, (void *)pBytes); free(pBytes); g] TexParaireterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glEnable(GL_TEXTURE_2D);
Глава 9 Наложение текстуры следующий шаг 445 /////////////////////////////////////////////////////////////// // Загружается карта среды glBindTexture(GL_TEXTURE_2D, toTextures[1]); pBytes = gltLoadTGA("Environment.tga", &iWidth, &iHeight, &iComponents, &eFormat); glTex!mage2D(GL_TEXTURE_2D, 0, iComponents, iWidth, iHeight, 0, eFormat, GL_UNSIGNED_BYTE, (void *)pBytes); free(pBytes); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) ; glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glEnable(GL_TEXTURE_2D); // Включается генерация текстурных координат glEnable(GL_TEXTURE_GEN_S); glEnable(GL_TEXTURE_GEN_T); // По умолчанию применяется отображение сферы glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); } /////////////////////////////////////////////////////////////////// // Обработка действий с клавишами со стрелками void SpecialKeys(int key, int x, int y) { if(key == GLUT_KEY_UP) xRot-= 5. Of; if(key == GLUT_KEY_DOWN) xRot += 5.0 f; if(key == GLUT_KEY_LEFT) yRot -= 5. Of; if(key == GLUT_KEY_RIGHT) yRot += 5.0f; if(key > 356.Of) xRot = O.Of; if(key < -1.Of) xRot = 355.0f; if(key > 356.Of) yRot = O.Of; if(key < -l.Of) yRot = 355.Of; // Обновляется окно glutPostRedisplay() ; ) /////////////////////////////////////////////////////////////////// // Обновляется проекция и положение источника света void ChangeSize(int w, int h) { GLfloat fAspect; // Предотвращает деление на нуль if(h == 0)
446 Часть I Классический OpenGL // Размер поля просмотра устанавливается равным размеру окна glViewport(0, 0, w, h); // Обновляется система координат glMatrixMode(GL_PROJECTION); glLoadldentity!); fAspect = (GLfloat) w / (GLfloat) h; gluPerspective(45.0f, fAspect, l.Of, 225.Of); glMatrixMode(GL_MODELVIEW); glLoadldentity(); } /////////////////////////////////////////////////////////////////// // Точка входа программы int main(int argc, char* argv[]) { glutlnit(&argc, argv); glutlnitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); glutlnitWindowSize(800,600); glutCreateWindow!"Texture Coordinate Generation"); glutReshapeFunc(ChangeSize); glutSpecialFunc(SpecialKeys); glutDisplayFunc(RenderScene); SetupRC(); // Создается меню glutCreateMenu(ProcessMenu); glutAddMenuEntry("Object Linear",1); glutAddMenuEntry("Eye Linear",2); glutAddMenuEntry("Sphere Map",3); glutAttachMenu(GLUT_RIGHT_BUTTON); glutMainLoop(); // He забываем про текстурные объекты glDeleteTextures(2, toTextures); return 0; Отображение, линейное по объектам Если режим генерации текстуры установлен равным GL_OBJECT_LINEAR, текстурные координаты генерируются с использованием такой функции coord = Р1*Х + P2*Y + P3*Z + P4*W Значения х, Y, Z и W являются координатами вершин объекта, на который наклады- вается текстура, а Р1-Р4 — это коэффициенты уравнения плоскости Далее текстурные координаты проектируются на геометрический объект с перспективы данной плоско- сти Например, чтобы спроектировать текстурные координаты для S и Т с плоскости Z = 0, в программе TEXGEN применялся следующий код // Плоскость проекции GLfloat zPlane[] = O.Of, O.Of, l.Of, O.Of ;
Глава 9. Наложение текстуры: следующий шаг 447 Рис. 9.7. Тор с текстурой, отображенной линейно относительно объектов // Отображение, линейное по объектам glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); glTexGenfv(GL_S, GL_OBJECT_PLANE, zPlane); glTexGenfv(GL_T, GL_OBJECT_PLANE, zPlane); Обратите внимание на то, что функции генерации текстурных координат могут от- личаться для разных координат. В данном случае для координат S и Т использовалась одинаковая функция. Описанная техника отображает текстуру на объект в координатах объекта неза- висимо от задействованных преобразований модели. На рис. 9.7 показан результат выполнения программы TEXGEN при выбранном режиме Object Linear. Не име- ет значения, как вы переориентируете тор, отображение останется фиксированным относительно других геометрических объектов. Отображение, линейное относительно точки наблюдения Если режим генерации текстуры установлен равным gl_eye_linear, текстурные координаты генерируются почти так же, как в режиме GL_OBJECT_LINEAR, только теперь координаты X, Y, Z и W указывают положение точки наблюдения (где распо- лагается камера или глаз). Кроме того, перед применением уравнения коэффициенты уравнения плоскости инвертируются.
448 Часть I. Классический OpenGL Рис. 9.8. Пример наложения текстуры, линейного относительно точки наблюдения Таким образом, текстура, по сути, проектируется с плоскости на геометрический объект. Если геометрия меняется согласно матрице наблюдения модели, текстура кажется скользящей по поверхности. В программе TEXGEN данная возможность активизируется с помощью следующего кода: // Плоскость проекции GLfloat zPlane[] = { O.Of, O.Of, l.Of, O.Of }; // Отображение, линейное относительно наблюдения glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR); glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR); glTexGenfv(GL_S, GL_EYE_PLANE, zPlane); glTexGenfv(GL_T, GL_EYE_PLANE, zPlane); Результат выполнения программы TEXGEN при выборе из меню варианта Eye Linear показан на рис. 9.8. Вращая тор с помощью клавиш со стрелками, обратите внимание на то, как спроектированная текстура скользит по объекту. Сферическое отображение Если режим генерации текстуры установлен равным GL_SPHERE_MAP, OpenGL рас- считывает текстурные координаты так, что объект кажется отраженным в текущей карте текстуры. Данный режим проще всего настроить, и в программе TEXGEN это сделано с помощью следующих строк:
Глава 9. Наложение текстуры: следующий шаг 449 Рис. 9.9. Карта среды, полученная с помощью сферического отображения glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); Обычно хорошую текстуру можно получить, сфотографировав что-то через объек- тив типа “рыбий глаз”. Наложение такой текстуры на геометрический объект выгля- дит очень убедительно. Для получения еще более реалистичных результатов сфери- ческое отображение обычно заменяют кубическим (рассмотрено ниже). Тем не менее сферическое отображение все же находит применение в определенных сферах. В частности, сферическое отображение требует всего одной текстуры, а не ше- сти, и если строгое отражение не требуется, сферическое отображение дает вполне приемлемые результаты. Даже если у вас нет качественной текстуры, полученной с помощью “рыбьего глаза”, сферическое отображение можно применить для ап- проксимации карты среды. Блестящие поверхности отражают свет от окружающей обстановки, но качество отражения несравнимо с зеркальным. В программе TEXGEN мы использовали подходящую карту текстуры для создания фона (он демонстрирует- ся при любом режиме), а также в качестве источника сферической карты. На рис. 9.9 демонстрируется текстурный тор на фоне с похожей окраской. Перемещая тор с по- мощью клавиш со стрелками, наблюдаем аппроксимацию отражающей поверхности.
450 Часть I. Классический OpenGL Рис. 9.10. Кубическая карта мира сфер в программе CUBEMAP Кубическое отображение Последние два режима генерации текстуры, GL_REFLECTION_MAP и GL_NORMAL_MAP, требуют использования нового типа текстурной среды — кубической карты. Кубиче- ская карта — это не одна текстура, а скорее набор шести текстур, образующих шесть граней куба. На рис. 9.10 для примера показана схема шести квадратных текстур, образующих кубическую карту программы CUBEMAP. Данные шесть плиток представляют внешний вид мира сфер в шести направле- ниях (спереди, сзади, слева, справа, сверху и снизу). Применяя режим генерации текстуры GL_REFLECTION_MAP, можно создать точную отражающую поверхность. Загрузка кубических карт Кубические карты добавляют шесть новых значений, которые можно передать функ- ции glTex!mage2D: GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP _NEGATIVE_X, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGA- TIVE_Y, GL_TEXTURE_CUBE_MAP_POSITIVE_Z И GL_TEXTURE_CUBE_MAP_NEGATIVE _z. Константы представляют направления во внешних координатах граней описанно- го вокруг объекта куба, на который налагается текстура. Например, чтобы загрузить карту для положительного направления оси X, можно применить такую функцию: glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGBA, iWidth, iHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, plmage);
Глава 9 Наложение текстуры следующий шаг 451 Для развития этого примера рассмотрим следующий фрагмент кода из программы CUBEMAP. Здесь мы записали в массивы имена и идентификаторы шести кубиче- ских карт, а затем использовали цикл для загрузки всех шести изображений в один текстурный объект const char *szCubeFaces[6] = { "right.tga", "left.tga", "up.tga", "down.tga", "backward.tga”, "forward.tga" }; GLenum cube[6] { GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z }; glBindTexture(GL_TEXTURE_CUBE_MAP, textureOboects[CUBE_MAP]); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_REPEAT) ; // Загружаются изображения кубической карты for(i = 0; i < 6; 1++) { GLubyte *pBytes; GLint iWidth, iHeight, iComponents; GLenum eFormat; // Загружается карта текстуры // glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_GENERATE_MIPMAP, GL_TRUE); pBytes = gltLoadTGA(szCubeFaces[i], &iWidth, &iHeight, &iComponents, &eFormat); glTexImage2D(cube[i], 0, iComponents, iWidth, iHeight, 0, eFormat, GL_UNSIGNED_BYTE, pBytes); free(pBytes); Обратите внимание на то, что первый параметр функции glBindTexture теперь имеет значение GL_texture_CUBE_map, а нс gl_texture_2D То же значение долж- но передаваться функции glEnable, чтобы активизировать кубическое отображение glEnable(GL_TEXTURE_CUBE_MAP); Если одновременно активизированы GL_TEXTURE_CUBE_MAP и GL_TEXTURE_2D, приоритет имеет GL_TEXTURE_CUBE_MAP Обратите также внимание на то, что зна-
452 Часть I Классический OpenGL чения параметров текстуры (устанавливаются с помощью glTexParameter) влияют на все шесть изображений единой кубической текстуры. Во всех случаях кубические карты рассматриваются как единая трехмерная карта текстуры, и для интерполяции значений текстурных координат применяются координаты S, Т и R Использование кубических карт Наиболее распространенной сферой применения кубических карт является создание объектов, отражающих окружающую обстановку. Шесть изображений, использован- ных в программе CUBEMAP, созданы по одному из примеров мира сфер с удаленны- ми тором и вращающимися сферами. Точка наблюдения менялась шесть раз, и полу- ченные изображения записывались (использовалось 90-градусное поле зрения) Затем данные изображения были собраны в одну кубическую карту с применением куба из предыдущего раздела, причем результат выглядел подобно тому, что изображено на рис 9 9 В программе CUBEMAP режим генерации текстуры для всех трех координат уста- навливался равным GL_REFLECTION_MAP glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP); glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP); glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP); Затем при рисовании тора двухмерное текстурирование деактивизировалось, и ак- тивизировалось кубическое отображение. Активизировалась генерация текстурных координат, и рисовался тор После этого восстанавливалось нормальное состоя- ние текстуры. gIDisable(GL_TEXTURE_2D); glEnable(GL_TEXTURE_CUBE_MAP); glBindTexture(GL_TEXTURE_CUBE_MAP, textureObjects[CUBE_MAP]); glEnable(GL_TEXTURE_GEN_S); glEnable(GL_TEXTURE_GEN_T); glEnable(GL_TEXTURE_GEN_R); gltDrawTorus(0.35, 0.15, 61, 37); gIDisable(GL_TEXTURE_GEN_S); gIDisable(GL_TEXTURE_GEN_T); gIDisable(GL_TEXTURE_GEN_R); gIDisable(GL_TEXTURE_CUBE_MAP); glEnable(GL_TEXTURE_2D); На рис 9.11 показан результат выполнения программы CUBEMAP Обратите вни- мание на то, как земля правильно отражается от нижних поверхностей тора, а серое небо — от верхних поверхностей Точно так же выглядят сферы, разбросанные по миру сфер и отражающиеся в боках тора Множественная текстура Современные аппаратные реализации OpenGL поддерживают возможность одновре- менного применения к геометрическим объектам нескольких текстур Все реали- зации OpenGL поддерживают по крайней мере наложение одной текстуры Узнать количество доступных текстурных единиц можно с помощью запроса с параметром GL_MAX_TEXTURE_UNITS
Глава 9. Наложение текстуры: следующий шаг 453 Рис. 9.11. Результат выполнения программы CUBEMAP GLint iUnits; glGet!ntegerv(GL_MAX_TEXTURE_UNITS, &iUnits); Текстуры применяются начиная с базовой текстурной единицы (gl_textureo) до текстурной единицы с максимальным номером (GL_TEXTUREn, где п — число применяемых текстурных единиц). Каждая текстурная единица имеет собственную текстурную среду, которая определяет, как объединяются фрагменты с предыдущей текстурной единицей. На рис. 9.12 показаны три текстуры, применяющиеся к геомет- рическим объектам, каждая из которых имеет собственную текстурную среду. По умолчанию первая текстурная единица является активной. Все команды тексту- ры за исключением glTexCoord влияют на текущую активную текстурную единицу. Изменить текущую текстурную единицу можно с помощью функции glActiveTex- ture с идентификатором текстурной единицы в качестве аргумента. Например, чтобы переключиться на вторую текстурную единицу и активизировать двухмерное тексту- рирование для этой единицы, вызываются следующие команды: glActiveTexture(GL_TEXTURE1); glEnable(GL_TEXTURE_2D); Чтобы деактивизировать текстурирование на второй текстурной единице и сно- ва переключиться на первую (базовую) текстурную единицу, следует вызывать та- кие процедуры: glDisable(GL_TEXTURE_2D); glActiveTexture(GL_TEXTUREO);
454 Часть I Классический OpenGL Рис. 9.12. Порядок обработки нескольких текстур Все вызовы таких функций текстуры, как glTexParameter, glTexEnv, glTex- Gen, glTexImage и glBindTexture, ограничиваются только текущей текстурной единицей. При визуализации геометрических объектов применяется текстура из всех активизированных единиц текстуры, и используются ранее заданные среды и пара- метры Единственным исключением являются текстурные координаты Множественные текстурные координаты Иногда (правда, редко) можно применить активные текстуры для всех текстур, ис- пользуя одни и те же текстурные координаты. При использовании нескольких текстур вы по-прежнему можете задавать текстурные координаты с помощью функции gl- TexCoord, однако данные текстурные координаты используется только для первой текстурной единицы (GL_TEXTUREO). Чтобы задать текстурные координаты отдельно для каждой текстурной единицы, нужна новая функция glMultiTexCoord2f(GLenum texUnit, GLfloat s, GLfloat t); Параметр texUnit равен GL_TEXTUREO, GL_TEXTURE1, и так далее до макси- мального числа поддерживаемых текстурных единиц В используемой нами версии glMultiTexCoord задаются координаты s и t двухмерной текстуры Как и в случае glTexCoord, множество вариаций функции glMultiTexCoord позволяет задавать одно-, двух-, трех и четырехмерные текстурные координаты в множестве различных форматов данных Кроме того, с одной или несколькими текстурными единицами можно использовать генерацию текстурных координат Пример использования нескольких текстур В листинге 9 2 представлен код программы MULTITEXTURE. Она похожа на CUBE- MAP, и в листинге приводятся только аспекты, которыми эта программа отличается В CUBEMAP мы удалили с тора текстуру дерева и заменили ее кубической картой,
Глава 9. Наложение текстуры: следующий шаг 455 Рис. 9.13. Результат выполнения программы MULTITEXTURE сымитировав идеальную отражающую поверхность. В MULTITEXTURE мы вернули текстуру дерева на тор, но переместили кубическую текстуру на вторую текстурную единицу. Для смешения этих двух текстур применялась среда GL_MODULATE. Кроме того, мы осветлили текстуры кубической карты, чтобы поверхность сделать ярче, а текстуру дерева более заметной. Результат выполнения программы MULTITEX- TURE показан на рис. 9.13. Листинг 9.2. Исходный код программы MULTITEXTURE ♦include /Common/OpenGLSB.h" // Активизируются необходимые возможности системы и OpenGL ♦include /Common/GLTools.h" // Подключаем набор инструментов OpenGL ♦include <math.h> ♦define NUM_SPHERES 30 GLTFrame spheres[NUM_SPHERES]; GLTFrame framecamera; // Данные о свете и материале GLfloat fLightPos[4] = { -100.Of, 100.Of, 50.Of, l.Of }; // Точечный источник света GLfloat fNoLight[] = { O.Of, O.Of, O.Of, O.Of }; GLfloat fLowLight[] = { 0.25f, 0.25f, 0.25f, l.Of }; GLfloat fBrightLight[] = { l.Of, l.Of, l.Of, l.Of }; ♦define GROUND_TEXTURE 0
456 Часть I Классический OpenGL # define SPHERE_TEXTURE 1 # define WOOD_TEXTURE 2 # define CUBE_MAP 3 # define NUM_TEXTURES 4 GLuint textureObjects[NUM_TEXTURES]; const char *szTextureFiles[] = {"grass.tga", "orb.tga", "wood.tga"); const char *szCubeFaces[6] = { "right.tga", "left.tga", "up.tga", "down.tga", "backward.tga", "forward.tga"}; GLenum cube[6] = { GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z }; /////////////////////////////////////////////////////////////////// // Эта функция выполняет необходимую инициализацию в контексте // визуализации. void SetupRC() { int iSphere; int i; // Сероватый фон glClearColor(fLowLight[0], fLowLight[1], fLowLight[2], fLowLight[3]); 11 Задние части многоугольников отбрасываются glCullFace(GLJBACK); glFrontFace(GL_CCW); glEnable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); 11 Устанавливаются параметры источника света: glLightModelfv(GL_LIGHT_MODEL_AMBIENT, fNoLight); glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR); glLightfv(GL_LIGHTO, GL_AMBIENT, fLowLight); glLightfv(GL_LIGHTO, GL_DIFFUSE, fBrightLight); glLightfv(GL_LIGHT0, GL_SPECULAR, fBrightLight); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0) ; // Преимущественно используется согласование свойств материала glEnable(GL_COLOR_MATERIAL); glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE); glMateriali(GL_FRONT, GL_SHININESS, 128); gltlnitFrame(&frameCamera); // Инициализируется камера // Случайным образом размещаются объекты мира сфер for(iSphere = 0; iSphere < NUM_SPHERES; iSphere++) { gltlnitFrame(&spheres[iSphere]); // Инициализируется система отсчета IIZ шагом 0.1 выбирается случайная точка между -20 и 20 spheres[iSphere].vLocation[0] = (float)((rand() % 400) - 200) * O.lf; spheres[iSphere].vLocation[1] = O.Of;
Глава 9 Наложение текстуры следующий шаг 457 spheres[iSphere].vLocation[2] = (float)((rand() % 400) - 200) * O.lf; ) // Устанавливаются карты текстуры glEnable(GL_TEXTURE_2D); glGenTextures(NUM_TEXTURES, textureObjects); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); // Загружаются правильные текстуры for (1 = 0; 1 < CUBE_MAP; 1++) { GLubyte *pBytes; GLint iWidth, iHeight, iComponents; GLenum eFormat; glBindTexture(GL_TEXTURE_2D, textureObjects[i]); 11 Загружается эта карта текстуры glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE); pBytes = gltLoadTGA(szTextureFiles[i], &iWidth, &iHeight, &iComponents, &eFormat); glTex!mage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB, iWidth, iHeight, 0, eFormat, GL_UNSIGNED_BYTE, pBytes); free(pBytes); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) ; } glActiveTexture(GL_TEXTURE1); gIDisable(GL_TEXTURE_2D); glEnable(GL_TEXTURE_CUBE_MAP); glBindTexture(GL_TEXTURE_CUBE_MAP, textureObjects[CUBE_MAP]); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP); glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP); glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP); glEnable(GL_TEXTURE_GEN_S); glEnable(GL_TEXTURE_GEN_T); glEnable(GL_TEXTURE_GEN_R); glBindTexture(GL_TEXTURE_CUBE_MAP, textureObjects[CUBE_MAP]); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R,
458 Часть I Классический OpenGL GL_REPEAT); // Загружаются изображения кубической карты for(i =0; 1 < 6; 1++) { GLubyte *pBytes; GLint iWidth, iHeight, iComponents; GLenum eFormat; // Загружается данная карта текстуры // glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_GENERATE_MIPMAP, GL_TRUE); pBytes = gltLoadTGA(szCubeFaces[i], &iWidth, &iHeight, &iComponents, &eFormat); glTexImage2D(cube[i], 0, iComponents, iWidth, iHeight, 0, eFormat, GL_UNSIGNED_BYTE, pBytes); free(pBytes); /////////////////////////////////////////////////////////////////// // Рисуются случайные объекты и дуэт вращающегося тора и сферы void Drawinhabitants(void) static GLfloat yRot = O.Of; // Угол поворота в анимации GLint i; yRot += 0.5f; glColor4f(l Of, l.Of, l.Of, 1 Of); // Рисуются случайным образом расположенные сферы glBindTexture(GL_TEXTURE_2D, textureObjects[SPHERE_TEXTURE]); for(i =0; i < NUM_SPHERES; i++) { glPushMatrix(); gltApplyActorTransform(&spheres[i]); gltDrawSphere(0.3f, 21, 11); glPopMatrix(); } glPushMatrix(); glTranslatef(O.Of, O.lf, -2.5f); // Top будет зеркальным glMaterialfv(GL_FRONT, GL_SPECULAR, fBrightLight); glRotatef(yRot, O.Of, l.Of, O.Of); // Привязываемся к первой текстуре (дереву) glBindTexture(GL_TEXTURE_2D, textureOb^ects[WOOD_TEXTURE]); glActiveTexture(GL_TEXTURE1); glEnable(GL_TEXTURE_CUBE_MAP); glActiveTexture(GL_TEXTURE0); gltDrawTorus(0.35, 0.15, 41, 17); glActiveTexture(GL_TEXTURE1); glDisable(GL_TEXTURE_CUBE_MAP); glActiveTexture(GL_TEXTURE0); glMaterialfv(GL_FRONT, GL_SPECULAR, fNoLight); glPopMatrix();
Глава 9 Наложение текстуры следующий шаг 459 ТАБЛИЦА 9.4. Функции объединения текстур Константа GL_REPLACE GL_MODULATE GL_ADD GL_ADD_SIGNED GL_INTERPOLATE GL_SUBTRACT GL_DOT3_RGB/GL_DOT3_RGBA Функция ArgO ArgO * Arg1 ArgO + Arg1 ArgO + Arg1 - 0 5 (ArgO * Arg2) + (Arg1 * (1 - Arg2)) ArgO - Arg1 4*((ArgOr - 0 5) * (Arglr - 0 5) + (ArgOg - 0 5) (Arglg - 0 5) + (ArgOb -05)* (Arglb - 0 5)) Объединение текстур В главе 6, “Подробнее о цветах и материалах”, рассказывалось, как для контроля над объединением (смешением) цветных фрагментов использовать уравнение смешения, когда в буфере цвета рисуется несколько уровней геометрии (обычно в порядке от заднего к переднему плану) Схемы объединения текстур (texture combiners) OpcnGL предлагают контроль такого же типа (только лучше) над объединением нескольких текстурных фрагментов По умолчанию для каждой текстурной единицы можно вы- брать ОДИН ИЗ режимов текстурной среды (GL_DECAL, GL_REPLACE, GL_MODULATE или GL_ADD), а результат наложения каждой текстуры дает вклад в следующую тек- стурную единицу Подробно текстурные среды рассматривались в главе 8. Схемы объединения текстуры добавляют новую текстурную среду GL_COMBINE, позволяющую явно задавать способ сложения фрагментов отдельных текстур Чтобы использовать эти схемы, вызывается функция glTexEnv glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); Схемы объединения текстур полностью контролируются функцией glTexEnv Да- лее нужно выбрать, какую функцию объединения текстуры вы хотите использовать Вторым аргументом функции glTexEnv является селектор функции объединения, который может иметь значение GL_COMBINE_RGB или GL_COMBINE_ALPHA Третий аргумент — функция текстурной среды, которую вы хотите назначить (для обработки RGB- или альфа-кодов) Все эти функции перечислены в табл 9.4. Например, чтобы выбрать схему объединения GL_REPLACE для RGB-кодов, вызывается такая функция glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_REPLACE); По большому счету такая схема всего лишь дублирует нормальную текстурную среду GL_RE PLACE Значения аргументов Arg0-Arg2 выбираются из значений источников и операндов функции glTexEnv Параметры GL_SOURCEx_RGB и GL_SOURCEx_ALPHA используют- ся для задания аргументов функции объединения RGB- или альфа-кодов, где х может равняться 0, 1 и 2 Возможные значения этих источников приводятся в табл 9 5 Например, чтобы в качестве ArgO выбрать текстуру единицы 0, вызывает- ся такая функция glTexEnvi(GL_TEXTURE_ENV, GL_SOURCEO_RGB, GL_TEXTURE0);
460 Часть I Классический OpenGL ТАБЛИЦА 9.5. Источники функций объединения текстур Константа Описание GL_TEXTURE Текстура ограничивается текущей активной текстурной единицей GL_TEXTUREx GL_CONSTANT Текстура ограничивается текстурной единицей х Код цвета (или альфа) устанавливается переменной текстурной среды GL_TEXTURE_ENV_COLOR GL_PRIMARY_COLOR Код цвета (или альфа) переносится от первоначального геометрического фрагмента GL_PREVIOUS Код цвета (или альфа) приходит от текстурной среды предыдущей текстурной единицы ТАБЛИЦА 9.6. Операнды функций объединения текстур Константа Значение GL_SRC_COLOR Код цвета от источника Константу нельзя использовать вместе с GL_OPERANDx_ALPHA GL_ONE_MINUS_SRC_COLOR Дополнение до единицы кодов цвета источника Константу нельзя использовать вместе С GL_OPERANDx_ALPHA GL_SRC_ALPHA GL_ONE_MINUS_SRC_ALPHA Значение альфа источника Дополнение до единицы значения альфа источника Кроме того, вы можете управлять значениями, которые при данном источнике используются для каждого аргумента. Для установки этих операндов применяются константы GL_OPERANDx_RGB или GL_OPERANDx_ALPHA, где х — 0, 1 или 2 Прием- лемые операнды и их значения представлены в табл. 9.6. Например, если в первые две текстурные единицы загружены текстуры, а в про- цессе наложения текстуры требуется перемножать коды цветов обеих текстур, можно ввести в программу такой код' // Сообщаем OpenGL, что требуются схемы объединения текстур glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); // Сообщаем OpenGL, какую схему мы желаем использовать // (GL_MODULATE для RGB-кодов) glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE); // Указываем OpenGL использовать в качестве ArgO коды цвета // текстурной единицы 0 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCEO_RGB, GL_TEXTURE0); glTexEnvi(GL_TEXTURE_ENV, GL_OPERANDO_RGB, GL_SRC_COLOR) ; // Указываем OpenGL использовать в качестве Argl коды цвета // текстурной единицы 1 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCEO_RGB, GL_TEXTURE1); glTexenvi(GL_TEXTURE_ENV, GL_OPERANDO_RGB, GL_SRC_COLOR);
Глава 9 Наложение текстуры следующий шаг 461 Наконец, с помощью схем объединения текстур можно задавать постоянный ко- эффициент масштабирования RGB- или альфа-кодов По умолчанию установлены такие параметры- glTexEnvf (GL_TEXTURE_ENV, GL_RGB_SCALE, l.Of); glTexEnvf(GL_TEXTURE_ENV, GL_ALPHA_SCALE, l.Of); Резюме В этой главе мы углубились в рассмотрение наложения текстуры на геометрические объекты Было показано, как с помощью сжатия текстуры улучшить фильтрацию, повысить производительность и эффективно использовать память и как автомати- чески сгенерировать текстурные координаты для геометрических объектов Было рассказано, как добавлять правдоподобные карты среды с помощью сферических отображений и более реалистичное и правильное отражение с помощью кубиче- ских отображений В заключение мы обсудили множественную текстуру и объединение текстур Возможность одновременного наложения нескольких текстур является основой мно- жества спецэффектов, включая аппаратную поддержку отображения шероховатости Применяя схемы объединения текстур, вы получаете большую гибкость в плане кон- троля над смешением нескольких (до трех) текстур Хотя новые программы, ставшие реальными с выходом нового языка затенения OpenGL, предлагают совершенный контроль над применением текстуры, вы легко и быстро можете добиваться требуе- мых эффектов, используя только возможности, описанные в данной главе. Справочная информация glActiveTexture Цель: Включаемый файл: Синтаксис: Описание: Параметры: texUnit (тип GLenum) Установить активную текстурную единицу <gl.h> void glActiveTexture(GLenum texUnit); Эта функция устанавливает активную текстурную единицу, которая является целью всех текстурных функций, исключая glTexCoord и glTexCoordPointer Что возвращает: См. также: Текстурная единица, которую нужно сделать текущей Возможны следующие значения от GL_TEXTUREO до GL_TEXTUREn, где п — число поддерживаемых текстурных единиц Ничего glClientActiveTexture
462 Часть I Классический OpenGL glClientActiveTexture Цель: Установить активную текстурную единицу для массива вершин Включаемый файл: <gl.h> Синтаксис: void glClientActiveTexture(GLenum texUnit); Описание: Эта функция устанавливает активную текстурную единицу для Параметры: texUnit спецификации текстурных координат массива вершин Функция массива вершин glTexCoordPointer (см главу 11) по умолчанию задает указатель на первую текстурную единицу (GLJTEXTUREO) Эта функция используется для изменения текстурной единицы, являющейся целью glTexCoordPointer Многократный вызов этой функции позволяет задавать несколько массивов текстурных координат для операций е множественной текстурой Текстурная единица, которую нужно сделать текущей. (тип GLenumm) Возможны следующие значения- от GLJTEXTUREO до Что возвращает: GL_TEXTUREn, где п — число поддерживаемых текстурных единиц Ничего См. также: glActiveTexture glCompressedTexImage Цепь: Загрузить сжатое текстурное изображение Включаемый файл: <gl h> Варианты: void glCompressedTexImagelD(GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLint border, GLsizei imageSize, void *data) ; void glCompressedTexImage2D(GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, void *data); void glCompressedTexImage3D(GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border, Glsizei imageSize, GLvoid *data); Описание: Эта функция позволяет загружать сжатое текстурное изображение Она практически идентична семейству функций glTexImage, только параметр internalFormat должен задавать поддерживаемый формат сжатой текстуры
Глава 9 Наложение текстуры следующий шаг 463 Параметры: target (тип GLenum) Параметр должен иметь значение GL_TEXTURE_1D для glCompressedTexImagelD, GL_TEXTURE_2D для glCompressedTexImage2D или GL_TEXTURE_3D для glCompressedTexImage3D Только для двухмерных кубических карт он также может иметь значение GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_CUBE_MAP_POSITIVE_Y, GL_CUBE_MAP_NEGATIVE_Y, GL_CUBE_MAP_POSITIVE_Z ИЛИ GL_CUBE_MAP_NEGATIVE_Z level (тип GLint) internalFormat (тип GLint) Уровень детализации. Обычно равен 0, если не используется множественное отображение Внутренний формат сжатых данных Конкретные значения форматов сжатия текстуры зависят от реализации В табл 9 3 перечислены форматы одного широко поддерживаемого формата сжатия для ПК и Macintosh width (тип GLsizei) Ширина одно-, двух- или трехмерного текстурного изображения Параметр должен представляться степенью 2, но может также включать границу height (тип GLsizei) Высота двух- или трехмерного текстурного изображения Параметр должен представляться степенью 2, но может также включать границу depth (тип GLsizei) Глубина трехмерного текстурного изображения Параметр должен представляться степень 2, но может также включать границу border (тип GLint) imageSize (тип GLsizei) Ширина границы Границы поддерживаются нс всеми форматами сжатия текстуры Размер сжатых данных в байтах data (тип GLvoid*) Сжатые данные текстуры Что возвращает: Ничего См. также: glCompressedTexSublmage, glTexImage glCompressedTexSublmage Цель: Заменить фрагмент существующей карты текстуры Включаемый файл: <gl.h> Варианты: void glCompressedTexSubImagelD(GLenum target, GLint level, GLint xOffset, GLsizei width, GLsizei imageSize, const void *data); void glCompressedTexSubImage2D(GLenum target, GLint level, GLint xOffset, GLint yOffset, GLsizei width, GLsizei height, GLsizei imageSize, const void ★data);
464 Часть I. Классический OpenGL Параметры: target (тип GLenum) level (тип GLint) xOffset (тип GLint) yOffset (тип GLint) zOffset (тип GLint) width (тип GLsizei) height (тип GLsizei) depth (тип GLsizei) imageSize (тип GLsizei) data (тип const void*) Что возвращает: См. также: void glCompressedTexSub!mage3D(GLenum target, GLint level, GLint xOffset, GLint yOffset, GLint zOffset, GLsizei width, GLsizei height, GLsizei depth, GLsizei imageSize, const void *data); Эта функция замещает фрагмент существующей одно-, двух- или трехмерной сжатой карты текстуры. Обновление всей или части существующей карты текстуры может выполняться значительно быстрее, чем загрузка изображения с помощью функции glCompressedTexImage. С помощью данной функции вы не можете выполнить первоначальную загрузку текстуры; она применяется только для обновления существующей текстуры Параметр должен иметь значение GL_texture_1d, GL_TEXTURE_2D ИЛИ GL_TEXTURE_3D Обновляемый уровень множественной текстуры Смещение в направлении х до позиции существующей одно-, двух- или трехмерной текстуры, с которой начинается обновление Смещение в направлении у до позиции существующей двух- или трехмерной текстуры, с которой начинается обновление Смещение в направлении z до позиции существующей трехмерной текстуры, с которой начинается обновление Ширина одно-, двух- или трехмерного обновляемого текстурного изображения Высота обновляемого двух- или трехмерного текстурного изображения Глубина обновляемого трехмерного текстурного изображения Размер сжатых текстурных данных в байтах Указатель на данные, используемые для обновления цели (текстуры) Ничего glCompressedTexImage, glTexSublmage gIGetCompressedTexImage Цель: Вернуть сжатое текстурное изображение Включаемый файл: <gl.h> Синтаксис: void gIGetCompressedTexImage(GLenum target, GLint level, void *pixels);
Глава 9 Наложение текстуры следующий шаг 465 Описание: Эта функция позволяет заполнить буфер данных информацией, формирующей текущую текстуру Действие этой функции обратно к действию glCompressedTexImage, загружающей текстуру в указанный буфер данных. Функцию нельзя использовать для извлечения сжатой версии несжатой текстуры Параметры: target (тип GLenum) Цель операции копирования текстуры Параметр должен иметь значение GL_TEXTURE_1D, GL_TEXTURE_2D, GL_TEXTURE_3D, GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, G L_TEXTURE_CUBE_MAP_NEGATIVE_Y, GL_TEXTURE_CUBE_MAP_POSITIVE_Z ИЛИ GL_TEXTURE_CUBE_MAP_NEGATIVE_Z level (тип GLint) pixels (тип void*) Что возвращает: Считываемый уровень множественной текстуры Указатель на буфер памяти, принимающий сжатое текстурное изображение Ничего См. также: glCompressedTexImage, glCompressedTexSublmage, gIGetTexImage glMultiTexCoord Цель: Задать координаты текущего текстурного изображения для заданной текстурной единицы Включаемый файл: <gl.h> Варианты: void glMultiTexCoordldfGLenum texUnit, GLdouble s); void glMultiTexCoordldv(GLenum texUnit, GLdouble *v); void glMultiTexCoordli(GLenum texUnit, GLint s); void glMultiTexCoordliv(GLenum texUnit, GLint *v); void glMultiTexCoordls(GLenum texUnit, GLshort s); void glMultiTexCoordlsv(GLenum texUnit, GLshort *v); void glMultiTexCoordlf(GLenum texUnit, GLfloat s); void glMultiTexCoordlfv(GLenum texUnit, GLfloat *v); void glMultiTexCoord2i(GLenum texUnit, GLint s, GLint t); void glMultiTexCoord2iv(GLenum texUnit, GLint *v); void glMultiTexCoord2s(GLenum texUnit, GLshort s, GLshort t); void glMultiTexCoord2sv(GLenum texUnit, GLshort *v);
466 Часть I Классический OpenGL void glMultiTexCoord2f(GLenum texUnit, GLfloat s, GLfloat t); void glMultiTexCoord2fv(GLenum texUnit, GLfloat *v); void glMultiTexCoord2d(GLenum texUnit, GLdouble s, GLdouble t); void glMultiTexCoord2dv(GLenum texUnit, GLdouble *v); void glMultiTexCoord3s(GLenum texUnit, GLshort s, GLshort t, GLshort r); void glMultiTexCoord3sv(GLenum texUnit,GLshort *v); void glMultiTexCoord3i(GLenum texUnit, GLint s, GLint t, GLint r); void glMultiTexCoord3iv(GLenum texUnit, GLint *v}; void glMultiTexCoord3f(GLenum texUnit, GLfloat s, GLfloat t, GLfloat r); void glMultiTexCoord3fv(GLenum texUnit, GLfloat *v); void glMultiTexCoord3d(GLenum texUnit, GLdouble s, GLdouble t, GLdouble r); void glMultiTexCoord3dv(GLenum texUnit, GLdouble *v); void glMultiTexCoord4f(GLenum texUnit, GLfloat s, GLfloat t, GLfloat r, GLfloat g); void glMultiTexCoord4fv(GLenum texUnit, GLfloat *v); void glMultiTexCoord4d(GLenum texUnit, GLdouble s, GLdouble t, GLdouble r, GLdouble g); void glMultiTexCoord4dv(GLenum texUnit, GLdouble *v) ; void glMultiTexCoord4i(GLenum texUnit, GLint s, GLint t, GLint r, Glint g); void glMultiTexCoord4iv(GLenum texUnit, GLint *v); void glMultiTexCoord4s(GLenum texUnit, GLshort s, GLshort t, GLshort r, GLshort g); void glMultiTexCoord4sv(GLenum texUnit, GLshort *v); Описание: Приведенные функции устанавливают координату текущего текстурного изображения для заданной текстурной единицы в одне или нескольких измерениях (до 4) Текстурные координаты можно обновлять где-либо между glBegin и glEnd, и они соответствуют последующему вызову glVertex Координата q текстуры используется для масштабирования значений координат s, f и г, и умолчанию ее значение равно 1 0 Использовав в качестве цели функции glMatrixMode параметр GL_TEXTURE, вы сможете применять к текстурным координатам любые допустимые матричн операции
Глава 9 Наложение текстуры следующий шаг 467 Параметры: texUnit (тип GLenum) s (тип GLdouble или GLfloat, или GLint, или GLshort) t (тип GLdouble или GLfloat, или GLint, или GLshort) г (тип GLdouble или GLfloat, или GLint, или GLshort) q (тип GLdouble или GLfloat, или GLint, или GLshort) v (тип GLdouble* или GLfloat*, или GLint*, или GLshort*) Что возвращает: См. также: Текстурная единица, к которой применяется данная координата Возможны любые значения от GL_TEXTUREO до GL_TEXTUREn, где п — число поддерживаемых текстурных единиц Горизонтальная координата текстурного изображения Вертикальная координата текстурного изображения Глубинная координата текстурного изображения Масштабная координата текстурного изображения Массив значений, содержащий 1, 2, 3 или 4 значения, требуемых для задания текстурной координаты Ничего glTexGen, glTexImage, glTexParameter, glTexCoord gISecondaryColor Цель: Задаст вторичный цвет Включаемый файл: <gl.h> Варианты: void glSecondaryColor3b(GLbyte red, GLbyte green, GLbyte blue); void glSecondaryColor3s(GLshort red, GLshort green, GLshort blue); void glSecondaryColor3i(GLint red, GLint green, GLint blue); void glSecondaryColor3f(GLclampf red, GLclampf green, GLclampf blue); void glSecondaryColor3d(GLclampd red, GLclampd green, GLclampd blue); void glSecondaryColor3ub(GLubyte red, GLubyte green, GLubyte blue); void glSecondaryColor3us(GLushort red, GLushort green, GLushort blue); void glSeconaryColor3ui(GLuint red, GLuint green, GLuint blue);
468 Часть I Классический OpenGL Описание: Параметры: red green blue values Что возвращает: См. также: void glSeconaryColor3bv(GLbyte* values); void glSecondaryColor3sv(GLshort* values); void glSecondaryColor3iv(GLint* values); void glSecondaryColor3fv(GLclampf* values); void glSecondaryColor3dv(GLclampd* values); void glSecondaryColor3ubv(GLubyte* values); void glSecondaryColor3usv(GLushort* values); void glSecondaryColor3uiv(GLuint* values); При наложении текстуры на объект зеркальные блики обычно приглушаются применением текстуры после освещения Этот эффект можно компенсировать, установив параметр модели освещения GL_LIGHT_MODEL_COLOR_CONTROL. Данная модель использует зеркальное освещение и свойства материала для задания вторичного цвета, добавляемого после текстуры Эта функция позволяет задавать отдельный код вторичного цвета, добавляемый к фрагментам, когда освещение не активизировано, — например, если вы выполняете расчет освещения по собственной схеме После активизации освещения вызовы функции glSecondaryColor никак не проявляются. Кроме того, с помощью glEnable (GL_COLOR_SUM) нужно активизировать операцию суммирования цветов Задает красный компонент цвета Задает зеленый компонент цвета Задает синий компонент цвета Задает указатель на коды цветных компонентов Ничего glColor glTexGen Цель: Определить параметры для генерации текстурных координат Включаемый файл: <gl. h> Синтаксис: void glTexGend(GLenum coord, GLenum pname, GLdouble param); void glTexGenf(GLenum coord, GLenum pname, GLfloat param); void glTexGeni(GLenum coord, GLenum pname, GLint param); void glTexGendv(GLenum coord, GLenum pname, GLdouble *param); void glTexGenfv(GLenum coord, GLenum pname, GLfloat *param); void glTexGeniv(GLenum coord, GLenum pname, GLint ★param);
Глава 9 Наложение текстуры следующий шаг 469 Описание: Параметры: coord (тип GLenum) pname (тип GLenum) param Что возвращает: См. также: Эта функция устанавливает параметры для генерации текстурных координат, если с помощью glEnable активизирована одна или несколько из следующих возможностей GL_TEXTURE_GEN_S, GL_TEXTURE_GEN_T, GL_TEXTURE_GEN_R ИЛИ GL_TEXTURE_GEN_Q Когда значение GL_TEXTURE_GEN_MODE равно GL_OBJECT_LINEAR, текстурные координаты генерируются путем умножения координат текущего объекта (вершины) на постоянный вектор, заданный функций GL_OBJECT_PLANE. coordinate = v[0] * р[0] + v[l] * р[1] + v[2] * р[2] + v[3] * р[3] В режиме GL_EYE_LINEAR используются координаты наблюдения (координаты объекта, умноженные на матрицу GL_MODELVIEW) Если GL_TEXTURE_GEN_MODE имеет значение GL_SPHERE_MAP, координаты генерируются в сфере вокруг текущего положения наблюдателя или начала координат. При заданном режиме GL_TEXTURE_REFLECTION_MAP текстурные координаты рассчитываются как отражения текущей кубической карты Наконец, в режиме GL_TEXTURE_NORMAL_MAP также фигурируют кубические карты, но в координаты наблюдения преобразуется нормаль объекта (а не вершина), которая затем используется в текстурных координатах Отображаемая текстурная координата Парамс гр должен иметь одно из следующих значений. GL_S, GL_T, GL_R или GL_Q Устанавливаемый параметр, который должен иметь одно из следующих значений GL_TEXTURE_GEN_MODE, GL_OBJECT_PLANE ИЛИ GL_EYE_PLANE Значение параметра В режиме GL_TEXTURE_GEN_MODE параметр param может иметь указанные ниже значения GL_OBJECT_LINEAR" Текстурные координаты рассчитываются по координатам в системе объекта (вершинам) GL_EYE_LINEAR Текстурные координаты рассчитается по координатам в системе наблюдения (координаты в системе объекта, умноженные на матрицу GL_MODELVIEW) GL_SPHERE_MAP Текстурные координаты генерируются в сфере вокруг точки наблюдения GL_REFLECTION_MAP Текстурные координаты юнерируются с использованием кубической карты и отражаются от вершин GL_NORMAL_MAP Текстурные координаты генерирую!ся с использованием кубической карты и основаны на нормалях объектов, преобразованных в систему координат наблюдения Для режимов GL_OBJECT_PLANE И GL_EYE_PLANE param представляет собой четырехэлементный массив, используемый для умножения на координаты в системе объекта или наблюдения Ничего glTexCoord, glTexEnv, glTexImagelD, glTex!mage2D, glTexParameter

ГЛАВА 10 Кривые и поверхности Ричард С Райт-мл Из ЭТОЙ ГЛАВЫ ВЫ УЗНАЕТЕ . . Действие Функция Изображение сфер, цилиндров и дисков Использование карт для визуализации кривых и поверхностей Безье Использование схем оценки для упрощения отображения поверхности Создание поверхностей NURBS Создание линий разреза Мозаичное представление выпуклых и вогнутых многоугольников gluSphere/gluCylinder/gluDisk glMap/glEvalCoord glMapGrid/glEvalMesh gluNewNurbsRenderer/gluBeginSurface/ gluNurbsSurface/gluEndSurface/ gluDeleteNurbsRendererf10 gluBeginTrim/gluPwlCurve/gluEndTrim gluTessBeginPolygon/ gluTessEndPolygon Логика трехмерной графики представляет немногим более, чем просто компьюте- ризированную версию принципа соединения точек Вершины располагаются в трех- мерном пространстве и соединяются плоскими примитивами Гладкие кривые и по- верхности аппроксимируются с помощью плоских многоугольников и различных трюков, связанных с затенением Как правило, чем больше задействовано много- угольников, тем более гладкой и искривленной кажется поверхность Разумеется, OpenGL неявно поддерживает гладкие кривые и поверхности, поэтому вы можете задать сколько угодно вершин и установить любые желаемые или рассчитанные зна- чения для нормалей и кодов цвета Тем не менее OpenGL предлагает дополнительную поддержку, немного облег- чающую задачу построения более сложных поверхностей Проще всего для этою использовать функции GLU, визуализирующие сферы, цилиндры, конусы (частный случай цилиндров, в чем вы скоро убедитесь) и плоские круглые диски, возможно, с дырками посредине OpenGL также предлагает первоклассную поддержку сложных поверхностей, которые трудно смоделировать с помощью простого математического уравнения кривые и поверхности Безье и NURBS Наконец, OpenGL может прини- мать большие, неправильные и вогнутые многоугольники и разбивать их на меньшие фрагменты, управлять которыми гораздо проще
472 Часть I. Классический OpenGL С fl О0 Сфера Цилиндр Конус Плоский Диск рис -jq -j Возможные диск с отверстием квадратичные формы Встроенные поверхности Дополняющая OpenGL библиотека OpenGL Utility Library (GLU) содержит несколько функций, визуализирующих поверхности второго порядка. Эти квадратичные функ- ции отвечают за рисование сфер, цилиндров и дисков. При этом можно задавать радиус обоих концов цилиндра. Установив радиус одного конца равным 0, получим конус. Диски также описываются достаточно гибко и позволяют задавать фигуры с отверстиями в центре (шайбы). Все эти формы показаны на рис. 10.1. Названные квадратичные объекты можно комбинировать, создавая более сложные модели. Например, использовав только сферы и цилиндры, можно написать про- грамму трехмерного молекулярного моделирования. На рис. 10.2 показаны трехмер- ные единичные оси, нарисованные с помощью сферы, трех цилиндров, трех конусов и трех дисков. Данную модель можно включить в любую из программ с помощью следующей функции glTools: void gltDrawUnitAxes(void) Настройка квадратичных состояний Квадратичные поверхности можно рисовать с определенной долей гибкости, задавая нормали, указывая текстурные координаты и т.д. Если переводить все это в параметры функции рисования сфер, например, можно получить функцию, содержащую неверо- ятно длинный список параметров, значения которых придется постоянно указывать. Вместо этого квадратичные функции используют объектно-ориентированную модель. По сути, вы с помощью одной или нескольких функций установки состояния создаете квадратичный объект и устанавливаете его состояние визуализации. После этого вы указываете этот объект при рисовании одной из его поверхностей, и то, как будет визу- ализирована поверхность, определяется согласно состоянию объекта. В приведенном ниже коде показано, как создать пустой квадратичный объект, а затем удалить его. GLUquadricObj *pObj; // . . . pObj = gluNewQuadric(); // Создается и инициализируется // квадратичная поверхность // Задаются параметры визуализации квадратичных поверхностей // Рисуются поверхности // - - . gluDeleteQuadric(pObj); // Освобождается память, которую занимала квадратичная поверхность
Глава 10. Кривые и поверхности 473 Рис. 10.2. Оси х, у и z, нарисованные с помощью квадратичных поверхностей ТАБЛИЦА 10.1. Стили рисования объектов второго порядка Константа Описание GLU_FILL GLU_LINE GLU_POINT GLU_SILHOUETTE Квадратичные объекты рисуются как сплошные Квадратичные объекты рисуются как каркасные Квадратичные объекты рисуются как набор точек-вершин Похоже на каркасное изображение, только смежные грани многоугольников не рисуются Обратите внимание на то, что создается указатель на тип данных GLUQuadricObj, а не на экземпляр самой структуры данных. Это объясняется тем, что функция gluNewQuadric не только выделяет память для структуры, но и инициализирует элементы структуры со значениями по умолчанию. Для модификации состояния рисования объекта GLUQuadricObj и, соответствен- но, поверхностей, рисуемых с его помощью, предназначены четыре функции. Первая функция устанавливает стиль рисования квадратичной поверхности. void gluQuadricDrawStylelGLUquadricObj *obj, GLenum drawstyle); Первый параметр является указателем на объект второго порядка, а параметр drawstyle имеет одно из значений, перечисленных в табл. 10.1. Следующая функция задает, будет ли геометрия квадратичной поверхности гене- рироваться с помощью нормалей. void gluQuadricNormals(GLUquadricObj *pbj, GLenum normals);
474 Часть I Классический OpenGL Квадратичные поверхности могут рисоваться без нормалей (GLU_NONE), с глад- кими нормалями (GLU_SMOOTH) или с плоскими нормалями (GLU_FLAT) Основное отличие гладких нормалей от плоских заключается в том, что в первом случае зада- ются нормали для каждой вершины поверхности, что позволяет получить сглаженный внешний вид При использовании плоских нормалей задается одна нормаль для всех вершин ячейки (треугольника) поверхности. Кроме того, можно задать, указывает нормаль наружу или внутрь от поверхности Например, при рисовании окрашенной сферы естественно положить, что нормали на- правлены наружу Если же вы рисуете внутреннюю часть сферы (например, часть ку- полообразного потолка), удобно, чтобы освещение и нормали относились к внутрен- ней части сферы Данный параметр устанавливается с помощью следующей функции. void gluQuadricOrientation(GLUquadricObj *obj, GLenum orientation); Значением параметра orientation может быть GLU_OUTSIDE либо GLU_INSIDE. По умолчанию квадратичные поверхности обходятся против часовой стрелки, и пе- редние грани направлены наружу от поверхности. Значение термина “наружная по- верхность” для сфер и цилиндров понятно, у дисков так называется сторона, направ- ленная по положительному направлению оси z. Вызвав указанную ниже функцию, можно также затребовать для поверхностей второго порядка генерацию текстурных координат void gluQuadricTexture(GLUquadricObj *obj, GLenum textureCoords); Параметр textureCoords может иметь значение GL_TRUE либо GL_FALSE При генерации текстурных координат для квадратичных поверхностей текстуры равно- мерно оборачиваются вокруг сфер и цилиндров, при наложении текстуры на диск центр текстуры совмещается с центром диска, а края текстуры выравниваются по краям диска Рисование поверхностей второго порядка Задав состояние объекта второго порядка, поверхность можно нарисовать, вы- звав единственную функцию Например, чтобы нарисовать сферу, вызывается та- кая функция void gluSphere(GLUQuadricObj *obj, GLdouble radius, GLint slices, GLint stacks); Первый параметр obj является просто указателем на квадратичный объект, ранее установленный для желаемого состояния визуализации Параметр radius является радиусом сферы, после него указывается число секторов и слоев Сферы рисуют- ся с помощью кольцеобразных полос треугольников (или квадратов, это зависит от используемой библиотеки GLU), уложенных штабелем снизу вверх, как показано на рис 10 3 Число ломтиков задает, сколько наборов треугольников (или квадратов) при- меняется для совершения полного оборота вокруг сферы Описанное представление сферы похоже на условное разделение глобуса линиями широты и долготы Квадратичные сферы рисуются так, что положительное направление оси z указы- вает на вершину сферы На рис 10 4 для примера показана каркасная квадратичная сфера, нарисованная возле единичных осей
Глава 10 Кривые и поверхности 475 Рис. 10.3. Секторы и слои квадратичной сферы Цилиндры также рисуются вдоль положительного направления оси z и состо- ят из набора полосок, уложенных штабелем Ниже приведена функция изображе- ния цилиндра. void gluCylinder(GLUquadricObj *obj, GLdouble baseRadius, GLdouble topRadius, GLdouble height, GLint slices, GLint stacks); С помощью этой функции можно задавать радиус основания (возле начала коорди- нат) и радиус вершины (по положительному направлению оси z). Параметр height просто определяет длину цилиндра. Ориентация цилиндра показана на рис. 10.5, а на рис 10.6 изображен тот же цилиндр, у которого параметр topRadius задан равным нулю, в результате чего получается конус. Последней поверхностью второго порядка является диск. Диски рисуются с помо- щью колец полос треугольников или квадратов, разделенных на несколько ломтиков. Для визуализации диска применяется такая функция: void gluDisk(GLUquadricObj ★obj, GLdouble innerRadius, GLdouble outerRadius, GLint slices, GLint loops); Чтобы нарисовать диск, задается внутренний и внешний радиус. Если внутренний радиус равен 0, получается сплошной диск, подобный изображенному на рис. 10.7. Ненулевой радиус определяет отверстие в шайбе, как показано на рис. 10.8. В обоих случаях диск рисуется в плоскости ху.
476 Часть I. Классический OpenGL Рис. 10.4. Ориентация квадратичной поверхности Рис. 10.5. Ориентация квадратичного цилиндра
Глава 10. Кривые и поверхности 477 Рис. 10.6. Квадратичный конус, сделанный из цилиндра Рис. 10.7. Квадратичный диск с показанными кольцами и ломтями
478 Часть I. Классический OpenGL Рис. 10.8. Квадратичный диск с отверстием в центре (шайба) Моделирование с помощью квадратичных поверхностей В представленной на компакт-диске программе SNOWMAN с помощью объектов второго порядка рисуется грубая модель снеговика. Его тело образуют три белых сферы. Глаза сделаны из двух маленьких черных сфер, а нос-морковка моделируется оранжевым конусом. В качестве тела черной шляпы-цилиндра взят цилиндр, а верх и поля цилиндра сделаны из дисков — сплошного и с отверстием. Результат выполне- ния программы SNOWMAN показан на рис. 10.9, а в листинге 10.1 приводится код визуализации, с помощью которого рисуется снеговик (преобразование различных квадратичных поверхностей, располагающихся в нужных местах). Листинг 10.1. Код визуализации примера SNOWMAN void RenderScene(void) { GLUquadricObj *pObj; // Квадратичный объект // Очищает окно текущим цветом очистки glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) ; // Записывается состояние матрицы и выполняются повороты glPushMatrix(); // Объект отодвигается назад, // и выполняется поворот на месте glTranslatef(O.Of, -l.Of, -5.Of); glRotatef(xRot, l.Of, O.Of, O.Of); glRotatef(yRot, O.Of, l.Of, O.Of); // Что-то рисуем
Глава 10. Кривые и поверхности 479 Рис. 10.9. Снеговик, визуализированный с помощью квадратичных объектов рОЬj = gluNewQuadric(); gluQuadricNormals(pObj, GLU_SMOOTH); // Основное тело glPushMatrix(); glColor3f(l.Of, l.Of, l.Of); gluSphere(pObj, .40f, 26, 13); // Низ glTranslatef(O.Of, .550f, O.Of); // Средний участок gluSphere(pObj, .3f, 26, 13); glTranslatef(0.Of, 0.45f, O.Of); // Голова gluSphere(pObj, 0.24f, 26, 13); // Глаза glColor3f(O.Of, O.Of, O.Of); glTranslatef(O.lf, O.lf, 0.21f); gluSphere(pObj, 0.02f, 26, 13); glTranslatef(-0.2f, O.Of, O.Of); gluSphere(pObj, 0.02f, 26, 13); // Hoc glColor3f(l.Of, 0.3f, 0.3f); glTranslatef(O.lf, -0.12f, O.Of); gluCylinder(pObj, 0.04f, O.Of, 0.3f, 26, 13); glPopMatrix(); // Шляпа glPushMatrix(); glColor3f(O.Of, O.Of, O.Of); glTranslatef(O.Of, 1.17f, O.Of);
480 Часть I Классический OpenGL glRotatef(-90.Of, l.Of, O.Of, O.Of); gluCylinder(pObj, 0.17f, 0.17f, 0.4f, 26, 13); // Поля шляпы gIDisable(GL_CULL_FACE); gluDisk(pObj, 0.17f, 0.28f, 26, 13); glEnable(GL_CULL_FACE); glTranslatef(O.Of, O.Of, 0.40f); gluDisk(pObj, O.Of, 0.17f, 26, 13); glPopMatrix(); // Восстанавливается состояние матрицы glPopMatrix(); // Переключение буферов glutSwapBuffers(); } Кривые и поверхности Безье Поверхности второго порядка предоставляют встроенную поддержку простых по- верхностей, которые легко смоделировать с помощью алгебраических уравнений Однако, предположим, что нужно создать кривую или поверхность, а у вас нет ал- гебраического уравнения, которое ее описывает. Данная нетривиальная задача за- ключается в том, чтобы в обратном порядке пройти процесс вывода поверхности на экран, начав с се визуализированного вида и найдя описывающие его полиномы второго или третье) о порядка Принятие строгого математического подхода является решением долгим и подверженным ошибкам (даже если вы привлечете на помощь компьютер), а от мысли сделать все вручную лучше отказаться сразу же Поняв эту фундаментальную особенность искусства компьютерной графики, ди- зайнер автомобилей “Рено” Пьер Безье (Pierre Bezier) в 1970-х создал набор математи- ческих моделей, которые могли представлять кривые и поверхности, требуя задания лишь небольшого набора контрольных точек Помимо упрощения представления кри- волинейных поверхностей эти модели облегчили интерактивную настройку формы кривой или поверхности Вскоре возникли новые типы кривых и поверхностей (даже появился целый сло- варь для именования поверхностей, генерируемых компьютером) Математика этого трюка не сложнее манипуляций с матрицами, которые мы рассматривали в главе 4, “Геометрические преобразования- конвейер”, и понять се интуитивно довольно про- сто Поэтому точно так же, как мы поступили в главе 4, мы не будем нацеливаться на строгую математику, а остановимся на работе с нужными функциями Параметрическое представление Кривая имеет начальную точку, длину и конечную точку В действительности кри- вая — это просто линия, которая проходит по трехмерному пространству С другой сIороны, поверхность имеет ширину и высоту, следовательно, является областью в пространстве Вначале мы научимся рисовать гладкие кривые в трехмерном про-
Глава 10 Кривые и поверхности 481 Рис. 10.10. Параметрическое представление кривых и поверхностей странстве, а затем расширим эту концепцию на поверхности Итак, рассмотрим спер- ва принятый словарь и математические основы При упоминании о прямых линиях вы, возможно, вспоминаете следующее уравнение У = 771Х + 6 (10.1) Здесь т равно тангенсу угла наклона линии, а Ь — это точка пересечения линии с осью у Сказанное может напомнить вам школьные уроки алгебры, где изучались уравнения парабол, гипербол, экспонент и т.д. Во всех этих уравнениях у (или х) выражается через какую-то функцию от х (или у) Другой способ выражения уравнения кривой или прямой — через параметриче- ское уравнение В параметрическом уравнении и х, и у выражаются через другую переменную, которая меняется в заданном диапазоне значений, не являющимся явно частью геометрии кривой Иногда (например, в физике) координаты х, у и z частицы могут представляться функцией времени, где время выражается в секундах В при- веденном ниже примере /(), и /;() являются уникальными функциями, которые меняются со временем (?) 7 = f(t) у = <М Z = h(t) (102) При определении кривой в OpenGL мы 1акже задаем се как параметрическое уравнение Параметр кривой, который мы будем обозначать и, и его область зна- чений формируют область определения данной кривой Поверхности описываются с помощью двух параметров и и v На рис 10 10 приведены кривая и поверхность, определенные через и и г Здесь важно понимать, что параметры (н и и) характеризу- Ю1 уравнения, описывающие кривую, реальные значения координат они нс отражают
482 Часть I Классический OpenGL Рис. 10.11. Влияние контрольных точек на форму кривой Контрольные точки Кривая представляется несколькими контрольными точками, влияющими на форму этой кривой Для кривой Безье первая и последняя контрольные точки являются частью кривой Другие контрольные точки действуют как магниты, притягивая к се- бе кривую. Иллюстрация данной концепции для разного числа контрольных точек приведена на рис 10 11 Порядок кривой представляется числом контрольных точек, используемых для описания ее формы Степень кривой на единицу меньшее ее порядка. Математиче- ское значение данных терминов связано с параметрическими уравнениями, которые точно описывают кривую, порядком называется число коэффициентов в уравнении, а степенью — наибольший показатель степени параметра в уравнениях. Если вас инте- ресуют математические основы кривых Безье, рекомендуем обратиться к литературе, указанной в приложении А, “Что еще почитать”. Кривая на рис. 10.11, б называется квадратичной (степени 2), а кривая на рис. 10 11, в — кубической (степени 3) Кубические кривые наиболее распространены. Теоретически можно определить кривую любого порядка, но чем выше порядок кри- вой, тем менее контролируемы ее осцилляции, кроме того, кривые высоких порядков сильно меняются при незначительном изменении контрольных точек Непрерывность Если поместить рядом две кривых, создав одну общую точку (именуемую точкой пре- рывания), они сформируют кусочно-гладкую кривую Непрерывность данных кривых в точке прерывания описывает, насколько гладким является переход между фрагмен- тами. Существует четыре категории непрерывности, разрыв, позиционная непрерыв- ность (СО), непрерывность касательной (С1) и кривизны (С2). Как видно из рис 10 12, если кривые вообще не встречаются, непрерывность от- сутствует Позиционная непрерывность достигается в том случае, когда кривые по крайней мере встречаются и имеют общую конечную точку. Непрерывность касатель- ной присутствует, если обе кривых имеют одинаковую касательную в точке прерыва- ния Наконец, непрерывность кривизны означает, что обе кривых также имеют оди- наковую скорость изменения в точке прерывания (отсюда и более плавный переход) При сборке сложных поверхностей или кривых из множества кусочков обычно стараются достичь непрерывности касательной или кривизны. Позже мы покажем, что непрерывность легко получить, правильно выбрав значения некоторых парамет- ров процесса генерации кривых и поверхностей
Глава 10 Кривые и поверхности 483 Отсутствует СО (позиционная) ; Касательная С1 (касательные) Рис. 10.12. Непрерывность кусочно-гладких кривых С2 (кривизна) Функции оценки OpenGL содержит несколько функций, облегчающих изображение кривых и поверх- ностей Безье. Чтобы нарисовать их, задаются контрольные точки и области изме- нения параметров и и v Затем, вызывая подходящую функцию оценки (evaluator), OpenGL генерирует точки, образующие кривую или поверхность. Рассмотрим внача- ле двухмерный пример кривой Безье, а затем расширим его на три измерения, создав поверхность Безье. Двухмерная кривая Начать обучение лучше всего с примера, разбирая его по строчкам Для этого в ли- стинге 10 2 приведен код из программы BEZIER, полностью представленной на компакт-диске. В данной программе задается четыре контрольных точки кривой Безье, а затем с помощью функции оценки визуализируется сама кривая Результат выполнения программы показан на рис 10 13. Листинг 10.2. Код программы BEZIER, отвечающий за рисование кривой Безье с четырьмя контрольными точками // Число контрольных точек этой кривой GLint nNumPoints = 4; GLfloat ctrlPoints[4][3] = {{-4.Of, O.Of, O.Of}, // Конечная точка {-6.Of, 4.Of, O.Of), // Контрольная точка { 6.Of,-4.Of, O.Of}, // Контрольная точка { 4.Of, O.Of, O.Of}};// Конечная точка // Эта функция используется для наложения контрольных точек //на кривую void DrawPoints(void) { int i; // Переменная-счетчик // Размер точки устанавливает больше, чтобы сделать ее заметнее glPointSize(5.Of);
484 Часть I. Классический OpenGL Рис. 10.13. Результат выполнения программы BEZIER // Последовательно проходятся все контрольные точки // данного примера glBegin(GL_POINTS); for(i =0; i < nNumPoints; i++) glVertex2fv(ctrlPoints[i]); glEnd(); } // Вызывается для рисования сцены void RenderScene(void) { int i; // Очищает окно текущим цветом очистки glClear(GL_COLOR_BUFFER_BIT); // Настройка кривых Безье //В действительности эти команды нужно вызвать один раз, // и их можно перенести в функции установки glMaplf(GL_MAP1_VERTEX_3, // Тип генерируемых данных O.Of, // Нижняя граница и 100.Of, // Верхняя граница и 3, // Расстояние между точками данных nNumPoints, // Число контрольных точек &ctrlPoints[0][0]); // Массив контрольных точек // Активизируется функция оценки glEnable(GL_MAP1_VERTEX_3); // Точки соединяются ломаной линией
Глава 10 Кривые и поверхности 485 glBegin(GL_LINE_STRIP); f or (i = 0; i <= 100; 1++) { // Оценивается кривая в этой точке glEvalCoordlf((GLfloat) i); glEnd(); // Рисуются контрольные точки DrawPoints(); // Выводит команды рисования из стека glutSwapBuffers(), } // Эта функция выполняет необходимую инициализацию в контексте // визуализации. void SetupRC() { // Окно очищается до белого цвета glClearColor(l.Of, l.Of, l.Of, l.Of ); // Рисуем синим цветом glColor3f(O.Of, O.Of, l.Of); } III III III 11U III I//III I / и/III и/III III III IU и//и III III III III III / II Устанавливается двухмерная проекция void ChangeSize(int w, int h) { // Предотвращает деление на нуль if(h == 0) // Размер поля просмотра устанавливается равным размеру окна glViewport(0, 0, w, h), glMatrixMode(GL_PROJECTION); glLoadldentity(); gluOrtho2D(-10.Of, 10.Of, -10.Of, 10.Of); // Обновления матрицы наблюдения модели glMatrixMode(GL_MODELVIEW); glLoadldentity(); Первое, что мы делаем в листинге 10 2, — определяем контрольные точки кривой // Число контрольных точек этой кривой GLint nNumPoints = 4; GLfloat ctrlPoints[4][3]={{-4.Of, O.Of, O.Of), // Конечная точка {-6.Of, 4 Of, 0 Of), // Контрольная точка { 6 Of,-4 Of, O.Of}, // Контрольная точка { 4.Of, O.Of, O.Of}};// Конечная точка Мы определили глобальные переменные для нескольких контрольных точек и мас- сив контрольных точек Экспериментируя, вы можете менять приведенный код, до- бавляя новые точки или просто модифицируя положения предложенных
486 Часть I Классический OpenGL Действие функции DrawPoints понятно Мы вызываем ее из кода визуализа- ции, чтобы отобразить контрольные точки вдоль кривой Эта возможность также полезна при экспериментировании с размещением контрольных точек. Стандартная функция ChangeSize устанавливает двухмерную ортографическую проекцию, охва- тывающую диапазон от -10 до 10 по направлениям х и у Наконец, мы подходим к коду визуализации Вначале функция RenderScene вы- зывает glMaplf (после очистки экрана) для отображения кривой // Вызывается для рисования сцены void RenderScene(void) { int i; // Очищает окно текущим цветом очистки glClear(GL_COLOR_BUFFER_BIT); // Настройка кривых Безье //В действительности эти // и их можно перенести в glMaplf(GL_MAP1_VERTEX_3, O.Of, 100.Of, 3, nNumPoints, &ctrlPoints[0][0] >; команды нужно вызвать один раз, функции установки // Тип генерируемых данных // Нижняя граница и // Верхняя граница и // Расстояние между точками данных // Число контрольных точек // Массив контрольных точек Первый параметр функции glMaplf (GL_MAP1_VERTEX_3) устанавливает функ- цию оценки, генерирующую тройки координат вершин (х, у и z) Кроме того, мож- но указать функции оценки сгенерировать другие значения — текстурные коорди- наты и коды цвета Подробнее эти возможности освещены в справочном разделе в конце главы Следующие два параметра задают нижнюю и верхнюю границы параметра и дан- ной кривой. Нижнее значение определяет первую точку кривой, а верхнее — послед- нюю. Все промежуточные значения соответствуют другим точкам кривой В данном случае мы задали диапазон 0-100 Четвертый параметр функции glMaplf задает число значений типа float между вершинами в массиве контрольных точек. Поскольку каждая вершина задается тремя значениями (х, у и z), эта величина равна 3. Подобная гибкость позволяет помещать контрольные точки в произвольную структуру данных при условии их размещения с правильным интервалом Последний параметр является указателем на буфер, который содержит контроль- ные точки, определяющие кривую. В данном случае мы передали указатель на первый элемент массива. Создав отображение кривой, мы активизировали схему оценки, поз- воляющую использовать данное отображение Эта возможность поддерживается с по- мощью переменной состояния, а чтобы активизировать функцию оценки, дающую контрольные точки вдоль кривой, требуется всего лишь вызывать такую функцию. // Активизируется функция оценки glEnable(GL_MAP1_VERTEX_3);
Глава 10 Кривые и поверхности 487 Функция glEvalCoordlf принимает один аргумент: параметрическое значение, представляющее точку на кривой. Затем эта функция вычисляет кривую при указан- ном значении и вызывает glVertex с параметрами найденной точки. Последователь- но проходя область определения кривой и вызывая glEvalCoord для нахождения вершин, рисуем кривую, соединяя отрезками найденные точки. // Точки соединяются ломаной линией glBegin(GL_LINE_STRIP); ford = 0; i <= 100; i++) { // Оценивается кривая в этой точке glEvalCoordlf((GLfloat) i); } glEnd(); Наконец, мы рисуем собственно контрольные точки. // Рисуются контрольные точки DrawPoints() ; Оценка кривой OpenGL позволяет существенно облегчить даже описанную выше удобную схему. Создадим с помощью функции glMapGrid сетку, равномерную в области опреде- ления и (параметрического аргумента кривой). Затем вызовем glEvalMesh, чтобы “соединить точки” с помощью заданного примитива (GL_LINE или GL_POINTS) Та- ким образом, вызываются такие две функции: // Для отображения в сетку применяется функции высокого уровня, // затем вычисляется все сразу // Отображается сетка 100 точек от 0 до 100 glMapGridld(100,0.0,100.0); //С помощью линий вычисляется сетка glEvalMeshl(GL_LINE,0,100); Эти вызовы полностью заменяют следующий код: // Точки соединяются ломаной линией glBegin(GL_LINE_STRIP) ; ford = 0; i <= 100; i++) { // Вычисляется кривая в данной точке glEvalCoordlf((GLfloat) i); ) glEnd(); Видно, что данный подход компактнее и эффективнее, но его реальная ценность проявляется при оценке поверхностей, а не кривых.
488 Часть I Классический OpenGL Трехмерная поверхность Создание трехмерной поверхности Безье весьма похоже на создание ее двухмерного аналога Помимо вычисления точек вдоль области определения и мы должны вы- числить их в области определения v В листинге 10 3 приводится код следующей программы BEZ3D, которая отображает каркасную сетку трехмерной поверхности Безье Первым изменением по сравнению с предыдущим примером является то, что мы определили еще три набора контрольных точек для поверхности в области опреде- ления и. Чтобы поверхность была простой, в контрольных точках мы меняли только координату z. Таким образом была создана гладкая поверхность, представляющая собой как бы двухмерную поверхность Безье, растянутую вдоль оси z. Листинг 10.3. Код программы BEZ3D, отвечающий за создание поверхности Безье // Число контрольных точек этой кривой GLint nNumPoints = 3; GLfloat ctrlPoints[3][3][3]= {{{-4.Of, O.Of, 4.Of}, {-2.Of, 4.Of, 4.Of}, { 4.Of, O.Of, 4.Of}}, {{-4.Of, O.Of, O.Of}, {-2.Of, 4.Of, O.Of}, { 4.Of, O.Of, O.Of}}, {{-4.Of, O.Of, -4.Of}, {-2.Of, 4.Of, -4.Of}, { 4.Of, O.Of, -4.Of}}}; // Эта функция применяется для наложение контрольных точек // на кривую void DrawPoints(void) ( int i,j; // Переменные-счетчики // Размер точки устанавливается больше, чтобы сделать // ее заметнее glPointSize(5.0f); // Последовательно проходятся все контрольные точки // данного примера glBegin(GL_POINTS); for(i =0; i < nNumPoints; i++) for(j = 0; j < 3; j++) glVertex3fv(ctrlPoints[i][j]) ; glEndO; } // Вызывается для рисования сцены void RenderScene(void) // Очищает окно текущим цветом очистки glClear(GL_COLOR_BUFFER_BIT); // Записывается стек матриц проекции модели glMatrixMode(GL_MODELVIEW); glPushMatrix(); // Поворачиваем сетку, чтобы сделать ее виднее glRotatef(45.Of, O.Of, l.Of, O.Of);
Глава 10 Кривые и поверхности 489 glRotatef(60.Of, l.Of, O.Of, O.Of); // Настройка поверхности Безье // Эти команды нужно вызвать один раз, и их можно // перенести в функции установки glMap2f(GL_MAP2_VERTEX_3, // Тип генерируемых данных O.Of, // Нижняя граница и 10.Of, // Верхняя граница и 3, // Расстояние между точками в данных 3, // Размерность в направлении //и (порядок) O.Of, // Нижняя граница v 10.Of, // Верхняя граница v 9, // Расстояние между точками данных 3, // Размерность в направлении // v (порядок) &ctrlPoints[0][0](0]); // Массив контрольных точек // Активизируется функция оценки glEnable(GL_MAP2_VERTEX_3); // Для отображения в сетку применяется функции высокого уровня, // затем вычисляется все сразу // Отображается сетка 10 точек от 0 до 10 glMapGrid2f(10,O.Of,10.Of,10,0.Of,10.Of); // С помощью линий оценивается сетка glEvalMesh2(GL_LINE,0,10,0,10); 11 Рисуются контрольные точки DrawPoints(); // Восстанавливается матрица наблюдения модели glPopMatrix(); // Отображается изображение glutSwapBuffers(); Приведенный сейчас код визуализации также отличается от того, что был ранее Помимо поворота изображения для получения лучшего визуального эффекта, мы вызываем функцию glMap2f вместо glMaplf. Так мы задаем контрольные точки в двух областях определения (и и и), а не в одной (?z), как было ранее. // Настройка поверхности Безье // Эти команды нужно вызвать один раз, и их можно // перенести в функции установки glMap2f(GL_MAP2_VERTEX_3, // Тип генерируемых данных O.Of, // Нижняя граница и 10.Of, // Верхняя граница и 3, // Расстояние между точками в данных 3, // Размерность в направлении и (порядок) O.Of, // Нижняя граница v 10.Of, // Верхняя граница v 9, // Расстояние между точками данных 3, // Размерность в направлении v (порядок) &ctrlPoints[0][0][0]); // Массив контрольных точек
490 Часть I Классический OpenGL Мы по-прежнему должны задавать верхнюю и нижнюю границы и, а также рас- стояние между точками в области определения и (как и ранее, оно равно 3). Однако теперь требуется задать еще верхнюю и нижнюю границы области определения v. Расстояние между точками в области определения v равно теперь девяти значениям, поскольку у нас трехмерный массив контрольных точек, каждой из которых соответ- ствует в области определения и три точки по три значения (3x3 = 9). Затем мы сообщаем glMap2 f, сколько точек в направлении v задается для каждого шага по и, после чего предоставляем указатель на собственно контрольные точки. Двухмерная функция оценки активизируется аналогично рассмотренной выше од- номерной, затем вызывается функция glMapGrid2f, в качестве аргумента которой дается число делений области в направлениях и и v // Активизируется функция оценки glEnable(GL_MAP2_VERTEX_3); // Для отображения в сетку применяется функции высокого уровня, // затем вычисляется все сразу // Отображается сетка 10 точек от 0 до 10 glMapGrid2f(10,O.Of,10.Of, 10, 0.Of, 10. Of) ; После настройки функции оценки можно вызывать двухмерную (и и v) версию функции glEvalMesh, с помощью которой вычисляется поверхностная сетка. В дан- ном случае применяются прямые отрезки, и задаются значения областей определения и и v от 0 до 10 //С помощью линий оценивается сетка glEvalMesh2(GL_LINE,0,10,0,10); Результат запуска программы показан на рис. 10.14. Освещение и векторы нормали Другой важной особенностью функций оценки является автоматическая генерация нормалей к поверхности Если в коде инициализации заменить // С помощью линий оценивается сетка glEvalMesh2(GL_LINE,0,10,0,10); на // С помощью линий оценивается сетка glEvalMesh2(GL_FILL,0,10,0,10); а затем вызвать функцию glEnable(GL_AUTO_NORMAL); будет активизировано освещение поверхностей, генерируемых функциями оценки. На рис. 10.15 показана та же поверхность, что и на рис 10 14, но с активизирован- ным освещением и включенным автоматическим построением нормалей. Код этой программы приведен в примере BEZLIT на компакт-диске (папка, соответствую- щая данной главе) Отметим, что код программы незначительно отличается от ко- да программы BEZ3D.
Глава 10. Кривые и поверхности 491 Рис. 10.14. Результат выполнения программы BEZ3D Рис. 10.15. Результат выполнения программы BEZLIT
492 Часть I Классический OpenGL Третьего порядка Четвертою порядка Пятого порядка Рис. 10.16. Непрерывность кривой Безье при увеличении ее порядка NURBS Описанные функции можно использовать для оценки поверхностей Безье любого порядка, но для более сложных кривых элементы (кривые или поверхности) Безье нужно гладко собирать из кусочков При увеличении числа контрольных точек создать кривую с хорошей непрерывностью становится сложно Контроль самого высокого уровня над построением необходимых кусочно-гладких объектов предлагают функ- ции NURBS библиотеки GLU NURBS — это сокращение от non-umform icilional B-sphne (неравномерные рациональные би-сплаины) Зная математику, можно ска- зать, что это всего лишь обобщенная форма кривых и поверхностей, позволяющая получать кривые и поверхности Безье и несколько других классов объектов Дан- ные функции позволяют настраивать влияние контрольных точек, задаваемых для функций оценки, чтобы в результате получались плавные кривые и поверхности, соединяющие большое число контрольных точек От кривых Безье к би-сплайнам Кривая Безье определяется двумя точками, выполняющими роль конечных, и любым числом других контрольных точек, влияющих на форму кривой Три кривых Безье, показанных на рис IO I6, задаются тремя, четырьмя и пятью контрольными ючками, соответственно Кривая касатсльна линии, соединяющей конечные точки с соседни- ми контрольными точками Квадратичные (три точки) и кубические (четыре точки) кривые Безье достаточно гладкие и обычно относя 1ся к классу функций, непрерыв- ных в С2 (непрерывная кривизна) При большем числе контрольных точек гладкость нарушается, поскольку дополниюльныс контрольные точки растя! иваюг кривую С другой стороны, би-сплайны (бикубические сплайны) очень похожи на кри- вые Безье, но кривая при построении разбиваося на сегменты На форму любого сегмента влияют только ближайшие четыре контрольных точки, в сумме дающие кусочно-гладкую кривую, каждый сегмент которой похож по характеристикам на кривую Безье четвертого порядка Длинная кривая с большим числом контрольных точек является гладкой внутри, причем места соединения сегментов характеризуются непрерывностью в С2 Сказанное также означает, что кривая нс обязательно должна проходить через все контрольные точки
Глава 10 Кривые и поверхности 493 Узлы Реальная мощь NURBS заключается в возможности настройки влияния четырех кон- трольных точек для любого данного сегмента с целью получения требуемой гладко- сти Данный контроль дает последовательность значений, именуемых узлами (knots) Для каждой контрольной точки определяется два значения узла. Диапазон значений узлов соответствует параметрической области определения значений и и и и должен быть неубывающим. Значения узлов определяют влияние контрольных точек, попа- дающих в этот диапазон пространства uv. На рис. 10.17 показана характеристика влияния контрольных точек на кривую, захватывающая четыре единицы в диапазоне изменения параметра и. Точки в середине области определения и сильнее притягива- ют кривую, причем на нее воздействуют только точки, принадлежащие диапазону 0-3. Основным здесь является то, что в каждой контрольной точке в области опреде- ления uv существует только одна из кривых воздействия. Сила воздействия точек внутри области определения обусловлена последовательностью узлов. Если значе- ние узла повторяется, точки вблизи этого параметрического значения имеют боль- шее влияние. Повторение значений узлов называется кратностью узла (knot mul- tiplicity). Большая кратность узла уменьшает кривизну кривой или поверхности в соответствующей области. Создание поверхности NURBS Функции NURBS библиотеки GLU предлагают полезное высокоуровневое средство визуализации поверхностей. Явно вызывать функции оценки или устанавливать отоб- ражения либо сетки не нужно. Чтобы визуализировать кривую NURBS, вначале со- здается объект NURBS, к которому вы обращаетесь при вызове родственных функций с целью модификации внешнего вида поверхности или кривой. Функция gluNewNurbsRenderer создает функцию визуализации для кривой NURBS, a gluDeleteNurbsRenderer уничтожает ее. Использование этих функций демонстрируется в следующем коде. // Указатель на объект NURBS GLUnurbsObj *pNurb = NULL;
494 Часть I Классический OpenGL II Настройка объекта NURBS pNurb = gluNewNurbsRenderer(); // Операции c NURBS ... // Удаляется объект NURBS, если он был создан if(pNurb) gluDeleteNurbsRenderer(pNurb); Свойства NURBS Создав функцию визуализации NURBS, можно устанавливать различные высокоуров- невые свойства NURBS. // Устанавливается допуск дискретизации gluNurbsProperty(pNurb, GLU_SAMPLING_TOLERANCE, 25.Of); // С помощью закрашивания формируется сплошная поверхность // (для создания сетки многоугольников // применяйте GLU_OUTLINE_POLYGON) gluNurbsProperty(pNurb, GLU_DISPLAY_MODE, (GLfloat)GLU_FILL); Обычно эти функции вызываются в процедуре настройки, а не в коде визуализа- ции. В приведенном примере glu_sampling_tolerance определяет “степень зерни- стости” сетки, формирующей поверхность, a glu_fill указывает OpenGL заполнить ячейки сетки (не генерировать каркасное изображение). Определение поверхности Определение поверхности передается функции gluNurbsSurface в форме массивов контрольных точек и последовательностей узлов. Как показано ниже, эта функция также окружается парой gluBeginSurface/gluEndSurface. // Визуализация NURBS // Начинается определение NURBS gluBeginSurface(pNurb); // Оценивается поверхность gluNurbsSurface(pNurb, // 8, Knots, // // 8, Knots, // // 4*3, // // 3, // // &ctrlPoints[0][0][0], // Указатель на функцию визуализации NURBS Число узлов и массив узлов в направлении и Число узлов и массив узлов в направлении v Расстояние между контрольными точками в направлении и Расстояние между контрольными точками в направлении v Контрольные точки порядок поверхности по и и v
Глава 10 Кривые и поверхности 495 GL_MAP2_VERTEX_3); // Тип поверхности // Закончили работу с поверхностью gluEndSurface(pNurb); Для создания нескольких поверхностей NURBS можно многократно вызывать функцию gluNurbsSurface, при этом свойства, заданные для функции визуализации NURBS, по-прежнему действуют. Часто такое поведение желательно, очень редко требуется, чтобы две поверхности (возможно, соединенные) имели различные стили заполнения (одна сплошная, а другая — в форме каркасной сетки) С помощью контрольных точек и значений узлов, представленных в следующем фрагменте кода, создается поверхность NURBS, показанная на рис. 10 18 Целиком данную программу можно найти на компакт-диске, прилагаемом к книге // Сетка сформирована точками, расположенными // в четыре единицы от -б до +б по осям х и у // u v (х, у, z) GLfloat ctrlPoints[4][4][3]= {{{-6.Of, -б.Of, с интервалом {-б.Of, {-6.Of, {-6.Of, {-2.Of, -2.Of, 2.Of, 6.Of, -6.Of, {-2.Of, -2.Of, {-2.Of, 2.Of, {-2.Of, 6.Of, {{ 2.Of, -6.Of, { 2.Of, -2.Of, { 2.Of, 2.Of, { 2.Of, 6.Of, {{ 6.Of, -6.Of, { 6.Of, -2.Of, { 6.Of, 2.Of, { 6.Of, 6.Of, // Последовательность узлов для NURBS O.Of}, O.Of}, O.Of}, O.Of}}, O.Of}, 8.Of}, 8.Of}, O.Of}}, O.Of}, 8.Of}, 8.Of), O.Of}}, O.Of}, O.Of}, O.Of}, O.Of)}}; 11 u // v // v H v // u // v // v // v // u 11 v 11 v // v // v // u H v 11 v // v = 0, = 0 GLfloat Knots[8]={0.Of, O.Of, O.Of, O.Of, l.Of, l.Of, l.Of, l.Of); Подрезка Подрезка (trimming) означает создание вида поверхности NURBS в разрезе Данная возможность часто используется для обрезания острых краев поверхности NURBS. Кроме того, она позволяет создавать отверстия в поверхности. На рис. 10.19 показан результат выполнения программы NURBT (приведена на компакт-диске), где строится та же поверхность NURBS, что и в предыдущем примере (контрольные точки не показаны), но с удаленной треугольной областью. В листинге 10.4 приводится код, добавленный к программе NURBS для создания эффекта вырезания. Внутри пары команд gluBeginSurface/gluEndSurface вызы- вается gluBeginTrim, с помощью gluPwlCurve задается линия разреза, а с помощью gluEndTnm завершается блок вырезания
496 Часть I. Классический OpenGL Рис. 10.18. Результат выполнения программы NURBS Листинг 10.4. Модификации программы NURBS для иллюстрации вырезани // Задается внешняя сторона контура разреза, включающая // всю поверхность GLfloat outsidePts[5][2] = /* против часовой стрелки */ {{O.Of, O.Of), {l.Of, O.Of}, {l.Of, l.Of}, {O.Of, l.Of), {O.Of, O.Of}}; // Задается внутренняя сторона контура разреза, создающая // треугольную дырку в поверхности GLfloat insidePts[4][2] = /* по часовой стреле */ {{0.25f, 0.25f}, {0.5f, 0.5f}, {0.75f, 0.25f}, (0.25f, 0.25f}} // Визуализируется NURBS // Начало определения NURBS gluBeginSurface(pNurb); // Оценивается поверхность gluNurbsSurface(pNurb, // // 8, Knots, // // 8, Knots, // Указатель на визуализации Число узлов и в направлении Число узлов и в направлении функцию NURBS массив узлов и массив узлов
Глава 10. Кривые и поверхности 497 Рис. 10.19. Результат выполнения программы NURBT 4*3, // Расстояние между контрольными // точками в направлении и 3, // Расстояние между контрольными // точками в направлении v &ctrlPoints[0][0][0], // Контрольные точки 4, 4, // Порядок поверхности по и и v GL_MAP2_VERTEX_3); // Тип поверхности // Внешняя область включая всю кривую gluBeginTrim (pNurb); gluPwlCurve (pNurb, 5, &outsidePts[0][0], 2, GLU_MAP1_TRIM_2); gluEndTrim (pNurb); // Внутренняя треугольная область gluBeginTrim (pNurb); gluPwlCurve (pNurb, 4, &insidePts[0][0], 2, GLU_MAP1_TRIM_2); gluEndTrim (pNurb); // Закончили работу с поверхностью gluEndSurfасе(pNurb); Внутри пары команд gluBeginTrim/gluEndTrim можно задавать любое количе- ство кривых, если они формируют замкнутый кусочно-гладкий цикл. Кроме того, с помощью gluNurbsCurve можно определить вырезаемый участок или часть выре- заемого участка. Следует, однако, помнить, что кривые вырезания должны задаваться через единичное параметрическое пространство uv. Это означает, что вся область определения uv масштабируется в область от 0.0 до 1.0.
498 Часть I. Классический OpenGL Рис. 10.20. Область внутри кривых с обходом по часовой стрелке вырезается Функция gluPwlCurve определяет кусочно-гладкую линейную кривую, представ- ляющую собой набор точек, соединяющих два конца. В приведенном сценарии внут- ренняя кривая формирует треугольник, однако с помощью большего числа точек можно аппроксимировать любую кривую. Следуя по линии разреза, мы отрезаем область поверхности справа от кривой. Таким образом, кривая с обходом по часовой стрелке задает вырезание фрагмен- та, который находится внутри нее. Обычно поступают следующим образом: задается внешняя линия разреза, которая замыкает все пространство параметров NURBS, а по- сле этого внутри данной области указываются меньшие области вырезания с обходом по часовой стрелке. Данный принцип иллюстрируется на рис. 10.20. Кривые NURBS Точно так же, как существуют поверхности и кривые Безье, имеются кривые и по- верхности NURBS. С помошью функции gluNurbsCurve можно даже вырезать поверхности NURBS. Надеемся, что сказанного выше достаточно для самостоя- тельных экспериментов с вырезанием поверхностей. Впрочем, если вам требует- ся что-то, с чего можно было бы начать, изучите представленную на компакт- диске программу NURBC. Мозаика Чтобы OpenGL работал максимально быстро, все геометрические примитивы долж- ны быть выпуклыми. Мы уже отмечали этот момент в главе 3, “Рисование в про- странстве: геометрические примитивы и буферы”. Тем не менее часто приходится обрабатывать вершины, соответствующие вогнутым или сложным формам, которые требуется визуализировать с помощью OpenGL. Как показано на рис. 10.21, дан- ные формы относятся к одной из двух базовых категорий. Слева показан простой вогнутый многоугольник, а справа — более сложный многоугольник с отверсти- ем. Для описании формы слева можно применить примитив типа GL_POLYGON, но визуализация будет неудачной, поскольку алгоритмы OpenGL оптимизированы для выпуклых многоугольников. Что же касается фигуры справа ... в общем, надежда сделать что-либо мала.
Глава 10. Кривые и поверхности 499 Рис. 10.21. Примеры невыпуклых многоугольников Рис. 10.22. Сложные формы, разбитые на треугольники Вогнутый многоугольник Сложный многоугольник Интуитивное решение обеих названных проблем состоит в разбиении формы на меньшие выпуклые многоугольники или треугольники, которые вместе образуют ко- нечную общую форму. На рис. 10.22 показано возможное разбиение форм, приведен- ных на рис. 10.21, на несколько более удобных треугольников. Разбиение форм вручную является в лучшем случае очень кропотливой работой, потенциально подверженной ошибкам. К счастью, библиотека OpenGL Utility Library содержит функции, помогающие разбивать вогнутые и сложные многоугольники на меньшие приемлемые примитивы OpenGL. Процесс разбиения этих многоугольников называется составлением мозаики (tessellation). Функция мозаики При составлении мозаики очень важным понятием является объект мозаики (tessella- tor object), который нужно создавать и удалять почти так же, как объекты состояния квадратичной поверхности: GLUteaaelator *pTess; pTess = gluNewTes(); // Работа с мозаикой gluDeleteDess(pTess);
500 Часть I. Классический OpenGL Все функции мозаики используют в качестве первого параметра объект мозаики. Это позволяет одновременно активизировать несколько таких объектов или взаимо- действовать с библиотеками или другим кодом, также использующим мозаику Функ- ции мозаики меняют состояние и поведение объекта мозаики, а это гарантирует, что изменения повлияют только на тот объект, с которым вы сейчас работаете Да, еще один момент: в названии функции GLUtesselator только одна буква /, те слово “tessellator” написано с ошибкой' Разбиение и визуализация многоугольника с помощью функции мозаики происхо- дит следующим образом 1 Создастся объект мозаики 2 Устанавливаются состояние и обратные вызовы функции мозаики 3 Начинается рисование многоугольника 4 . Начинается рисование контура. 5 Функции мозаики передаются вершины, задающие контур. 6 . Завершается контур. 7 . Если остались необработанные контуры — переход на этап 4 8 Завершается многоугольник Каждый многоугольник состоит из одного или нескольких контуров. Многоуголь- ник на рис 10 21 (слева) содержит один контур, описанный вокруг внешней части многоугольника Многоугольник справа содержит два контура внешнюю границу и границу внутреннего отверстия Многоугольники могут содержать любое число контуров (иметь несколько отверстий). Действия по составлению из многоугольника мозаики не происходят до этапа 8. Отметим, что данная задача может быть весь- ма трудоемкой, и если геометрия статическая, иногда лучше сохранить вызовы этих процедур в таблице отображения (рассматривается в следующей главе) Обратные вызовы функции составления мозаики В процессе составления мозаики соответствующая функция вызывает несколько функций обратного вызова, которые вы должны указать заранее Эти обратные вызо- вы используются для задания информации о вершинах и началах/концах примитивов. Функции обратного вызова регистрируются с помощью следующей функции void gluTessCallback(GLUTesselator *tobj, GLenum which, void qw(*fn)()); Первым параметром является объект мозаики Второй — задаст тип регистрируе- мого обратного вызова, а последним является указатель на саму функцию обратного вызова. В табл 10 3 (справочный раздел) перечислены функции обратного вызова, которые можно использовать Сейчас же рассмотрим в качестве примера такой код. // В начале группы треугольников вызывается glBegin gluTessCallback(pTess, GLU_TESS_BEGIN, glBegin);
Глава 10 Кривые и поверхности 501 //В конце группы треугольников вызывается glEnd gluTessCallback(pTess, GLU_TESS_END, glEnd); // Для каждой вершины вызывается glVertex3dv gluTessCallback(pTess, GLU_TESS_VERTEX, glVertex3dv); Обратный вызов GLU_TESS_BEGIN задает функцию, которую нужно вызывать в начале каждого нового примитива Задавая glBegin, мы просто указываем схе- ме построения мозаики вызывать glBegin, чтобы начать группу примитивов Это можс! показаться бессмысленным, но вы также можете задавать здесь собствен- ную функцию, вводящую дополнительную обработку при начале нового примитива (Предположите, например, что требуется узнать, сколько треугольников задействова- но в конечном мозаичном многоугольнике ) Обратный вызов GLU_TESS_END просто сообщает функции составления мозаи- ки, что нужно вызвать glEnd и что нет другого кода, который вы бы хотели ввести в процесс визуализации Наконец, вызов GLU_TESS_VERTEX включен в вызов glver- tex3dv для задания данных о вершинах. Мозаичное представление требует, чтобы данные вершин задавались с двойной точностью, причем всегда используются вер- шины с тремя компонентами Как и выше, вы можете задать собственную функцию, вводящую дополнительную обработку (например, добавление информации о цвете, нормали или текстурных координатах) В качестве примера задания собственной функции обратного вызова (а нс суще- ствующих функции OpenGL) рассмотрим следующий код, в котором регистрируется функция, сообщающая о любых ошибках, которые могут произойти в процессе со- ставления мозаики /////////////////////////////////////////////////////////////////// // Обратный вызов ошибки составления мозаики void tessError(GLenum error) // Получение строки сообщения об ошибке const char *szError = gluErrorString(error); // Сообщение об ошибке устанавливается как заголовок окна glutSetWindowTitle(szError); } // Регистрируется обратный вызов ошибки gluTessCallback(pTess, GLU_TESS_ERROR, tessError); Задание информации о вершинах Чтобы начать рисование многоугольника (это соответствует этапу 3 приведенной ранее последовательности действий), вызывается такая функция void gluTessBeginPolygon(GLUTesselator *tobj, void *dataj ; Вначале вы передаете объект мозаики, а затем указатель на любые определен- ные пользователем данные, которые нужно соотнести с этой мозаикой Эти данные могут извлекаться в процессе составления мозаики с помощью функций обратно- го вызова, перечисленных в табл 10 3 (часто это просто значение NULL) Чтобы
502 Часть I Классический OpenGL завершить рисование многоугольника (этап 8) и начать построение мозаики, вызыва- ется такая функция" void gluTessEndPolygon(GLUTesselator *tobj); Между началом и концом рисования многоугольника задаются вложенные мно- гоугольники (один или несколько), для чего применяется следующая пара функ- ций (этапы 4 и 6): void gluTessBeginContour(GLUTesselator *tobj); void gluTessEndContour(GLUTesselator *tobj); Наконец, внутри кода, характеризующего контур, нужно указать все вершины, образующие контур (этап 5). С помощью следующей функции вершины по одной передаются схеме формирования мозаики: void gluTessVertex(GLUTesselator *tobj, GLdouble v[3], void *data); Параметр v содержит реальные данные о вершинах, применяющиеся в расче- тах мозаики. Параметр data является указателем на данные вершин, передаваемые функции обратного вызова, заданной с помощью GLU_VERTEX. Почему два различных аргумента задают одно и то же9 Потому что указатель на данные вершин может также указывать на дополнительную информацию о вершинах (цвет, нормали и тд.). Ес- ли вместо GLU_VERTEX задать собственную функцию, в процедуре обратного вызова будут доступны дополнительные данные о вершинах Собираем все вместе Рассмотрим теперь пример программы, принимающей сложный многоугольник и со- здающей мозаику с целью визуализации сплошной формы Приведенная на компакт- диске программа FLORIDA содержит информацию о вершинах, необходимую для рисования грубой, но узнаваемой формы штата Флорида Программа имеет три ре- жима визуализации, доступных из контекстного меню. Line Loops, Concave Polygon и Complex Polygon Базовая форма с активизированным режимом Line Loops показа- на на рис. 10 23. В листинге 10 5 приведены данные вершин и код визуализации, изображающие контуры штата и озера Окичоби Листинг 10.5. Данные о вершинах и код рисования контура штата // Данные по береговой линии #define COAST_POINTS 24 GLdouble vCoast[COAST_POINTS][3] = {{-70.0, 30.0, 0.0), {-50.0, 30.0, 0.0), {-50.0, 27.0, 0.0), { -5.0, 27.0, 0.0), { 0.0, 20.0, 0.0), { 8.0, 10.0, 0.0), { 12.0, 5.0, 0.0}, 5.0, 0.0}, 0.0, 0.0), { Ю.0, { 15.0,-10.0, 0.0), { 20.0,-20.0, 0.0}, { 20.0,-35.0, 0.0}, { 10.0,-40.0, 0.0},
Главе 10. Кривые и поверхности 503 Рис. 10.23. Базовый контур штата Флорида { 0.0,-30.0, 0.0}, { -5.0,-20.0, 0.0}, {-12.0,-10.0, 0.0), {-13.0, -5.0, 0.0), {-12.0, 5.0, 0.0}, {-20.0, 10.0, 0.0}, {-30.0, 20.0, 0.0}, {-40.0, 15.0, 0.0}, {-50.0, 15.0, 0.0}, {-55.0, 20.0, 0.0}, {-60.0, 25.0, 0.0}, {-70.0, 25.0, 0.0}} // Озеро Окичоби ♦define LAKE_POINTS 4 GLdouble vLake[LAKE_POINTS][3] = {{10.0, -20.0, 0.0}, {15.0, -25.0, 0.0}, {10.0, -30.0, 0.0}, { 5.0, -25.0, 0.0}} case DRAW_LOOPS: // Рисуются циклы линий { glColor3f(O.Of, O.Of, O.Of); // Просто черный контур // Цикл линий с формой береговой линии glBegin(GL_LINE_LOOP); for(i =0; i < COAST_POINTS; i++) glVertex3dv(vCoast[i]); glEnd(); // Цикл линий с формой внутреннего озера glBegin(GL_LINE_LOOP); for(i =0; i < LAKE_POINTS; i++)
504 Часть I. Классический OpenGL Рис. 10.24. Сплошной вогнутый многоугольник glVertex3dv(vLake[i]); glEnd(); } break; В режиме визуализации Concave Polygon (“вогнутый многоугольник”) рисуется только внешний контур. В результате, несмотря на то, что многоугольник очевид- но вогнутый, получается сплошная закрашенная форма, показанная на рис. 10.24 (соответствующий код построения мозаики приведен в листинге 10.6). Листинг 10.6. Рисование вогнутого многоугольника case DRAW_CONCAVE: // Мозаичное представление вогнутого // многоугольника { // Объект схемы построения GLUtesselator *pTess; // Зеленый многоугольник glColor3f(O.Of, l.Of, O.Of); // Создается объект мозаичного представления pTess = gluNewTess(); // Устанавливаются функции обратного вызова //В начале группы многоугольников просто вызывается glBegin gluTessCallback(pTess, GLU_TESS_BEGIN, glBegin); //В конце группы многоугольников просто вызывается glEnd gluTessCallback(pTess, GLU_TESS_END, glEnd); // Для каждой вершины просто вызывается glVertex3dv gluTessCallback(pTess, GLU_TESS_VERTEX, glVertex3dv); // Регистрируются обратный вызов ошибки gluTessCallback(pTess, GLU_TESS_ERROR, tessError); // Начинается многоугольник gluTessBeginPolygon(pTess, NULL);
Глава 10. Кривые и поверхности 505 Рис. 10.25. Сплошной многоугольник с отверстием // Начинается единственный контур gluTessBeginContour(pTess); // Поставляется список вершин for(i = 0; i < COAST_POINTS; i++) gluTessVertex(pTess, vCoast[i], vCoast[i]); // He может иметь значение NULL // Замыкается контур и многоугольник gluTessEndContour(pTess); gluTessEndPolygon(pTess); // Работа с объектом мозаичного представления завершена gluDeleteTess(pTess); ) break; В заключение мы представим более сложный многоугольник с одним отверстием. В режиме рисования Complex Polygon изображается сплошная фигура с отверстием, представляющим Окичоби (большое озеро в южной Флориде, обычно показываемое на картах). Результат выполнения программы с активизированным данным режимом показан на рис. 10.25, а соответствующий код представлен в листинге 10.7. Листинг 10.7. Мозаичное представление сложного многоугольника с несколькими контурами case DRAW_COMPLEX: // Объект представляем мозаикой, // но вырезаем отверстие 1 // Объект схемы мозаичного представления GLUtesselator *pTess; // Зеленый многоугольник glColor3f(O.Of, l.Of, O.Of); // Создается объект мозаичного предоставления pTess = gluNewTess();
506 Часть I Классический OpenGL II Устанавливаются функции обратного вызова //В начале группы треугольников просто вызывается glBegin gluTessCallback(pTess, GLU_TESS_BEGIN, glBegin); //В конце группы треугольников просто вызывается glEnd gluTessCallback(pTess, GLU_TESS_END, glEnd); // Для каждой вершины просто вызывается glVertex3dv gluTessCallback(pTess, GLU_TESS_VERTEX, glVertex3dv); // Регистрируется обратный вызов ошибки gluTessCallback(pTess, GLU_TESS_ERROR, tessError); // Как подсчитать заполненные и открытые области gluTessProperty(pTess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_ODD); 11 Начинается многоугольник gluTessBeginPolygon(pTess, NULL); // Без пользовательских данных // Первый контур (очертание штата) gluTessBeginContour(pTess); for(i = 0; i < COAST_POINTS; i++) gluTessVertex(pTess, vCoast[i], vCoast[i]); gluTessEndContour(pTess); // Второй конутр (очертание озера) gluTessBeginContour(pTess); for(i =0; i < LAKE_POINTS; i++) gluTessVertex(pTess, vLake[i], vLake[i]); gluTessEndContour(pTess); // Закончили с многоугольником gluTessEndPolygon(pTess); 11 Объект мозаичного представления больше не нужен gluDeleteTess(pTess); } break; Приведенный код содержит вызов новой функции // Как подсчитать заполненные и открытые области gluTessProperty(pTess,GLU_TESS_WINDING_RULE,GLU_TESS_WINDING_ODD); Данный вызов сообщает схеме построения мозаики, какие области запол- нять, а какие оставить пустыми при наличии нескольких контуров Значение GLU_TESS_WINDING_ODD в действительности является значением по умолчанию, и мы могли бы просто опустить эту функцию Однако вы должны понимать, как схема построения мозаики обрабатывает вложенные контуры Задав значение ODD, мы сообщили, что любая точка внутри многоугольника закрашивается в том случае, если она замкнута в нечетном числе контуров Область внутри озера (внутренний контур) окружена двумя (четное число) контурами и поэтому не закрашивается. Точ- ки вне озера, но внутри границы штата замкнуты всего одним контуром (нечетное число), поэтому при рисовании они закрашиваются.
Глава 10 Кривые и поверхности 507 Резюме Библиотека квадратичных поверхностей сводит создание нескольких простых по- верхностей (сфер, цилиндров, дисков и конусов) до уровня детской игры Приме- нение этой концепции к более сложным кривым и поверхностям могло сделать эту главу самой сложной в книге. Тем нс менее, как вы видели, концепции, формирую- щие основу рисования кривых и поверхностей, понять совсем не трудно. Если вы желаете глубже разобраться в математике или получить больше советов по созданию моделей на основе NURBS, обратитесь к книгам, рекомендованным в приложении А. Другие примеры, приведенные в данной главе, послужат хорошей начальной точ- кой для экспериментов с NURBS. Настраивайте контрольные точки и последователь- ности узлов, создавая искривленные или смятые поверхности Кроме того, порабо- тайте с поверхностями второго и больших порядков Будьте осторожны одним из подводных камней, которых стоит избегать, является создание сложной поверхности с помощью единственного сплайна NURBS Большие возможности и гибкость вы получите, составляя сложные поверхности из нескольких меньших и более удобных в работе поверхностей NURBS или Безье. В заключение мы проиллюстрировали мощную поддержку OpenGL автомати- ческого мозаичного представления многоугольников Было показано, как рисовать сложные поверхности и узоры с помощью всего лишь нескольких точек, задающих границы. Также вы узнали, что вогнутые области и даже области с отверстиями можно разбить на более простые выпуклые примитивы, используя объект мозаики библиотеки GLU. Справочная информация glEvalCoord Цель: Рассчитать активизированные ранее одно- и двухмерные карты Включаемый файл: <gl.h> Варианты: void glEvalCoordldfGLdouble и); void glEvalCoordlf(GLfloat u); void glEvalCoord2d(GLdouble u, GLdouble v); void glEvalCoord2f(GLfloat u, GLfloat v); void glEvalCoordldv(const GLdouble *u); void glEvalCoordlfv(const GLfloat *u); void glEvalCoord2dv(const GLdouble *u); void glEvalCoord2fv(const GLfloat *u); Описание: Использует ранее активизированную функцию оценки (заданную посредством glMap) для получения вершин, цветов, нормалей или текстурных значений на основе параметрических величин u/v Типы моделируемых данных и вызовов функций задаются функциями glMapl и д1Мар2 Параметры: u, V Задают параметрические величины и и v, которые будут вычисляться вдоль кривой или поверхности Что возвращает: Ничего См. также: glEvalMesh, glEvalPoint, glMapl, glMap2, glMapGrid
508 Часть I Классический OpenGL glEvalMesh Цель: Включаемый файл: Варианты: Описание: Параметры: mode (тип GLdouble) il, 12 (тип GLint) Jl, J2 (тип GLint) Что возвращает: См. также: Вычислить одно- или двухмерную сетку из точек или линии <gl.h> void glEvalMeshl(GLenum mode, GLint il, GLint 12); void glEvalMesh2(GLenum mode, GLint 11, GLint 12, GLint jl, GLint j2); Используется вместе c glMapGrid для эффективного создания равномерной сетки в области определения и и v В действительности glEvalMesh вычисляет сетку и даст точки, отрезки или закрашенные многоугольники Задаст, должна ли сетка вычисляться в виде точек (GL_POINT), линий (GL_LINE) или закрашенных ячеек поверхности (GL_FILL) Задаст первое и последнее целочисленные значения области определения и Задаст первое и последнее целочисленные значения области определения v Ничего glBegin, glEvalCoord, glEvalPoint, glMapl, glMap2, glMapGrid glEvalPoint Цель: Сгенерировать и вычислить одну точку сетки Включаемый файл: <gl.h> Варианты: void glEvalPointl(GLint i); void glEvalPoint2(GLint 1, GLint j); Описание: Функцию можно использовать вместо glEvalMesh для вычисления области определения в одной точке В результате вычислений находится один примитив (GL_points) Первый вариант данной функции (glEvalPointl) используется для кривых, а второй (glEvalPoint2) — для поверхностей Параметры: 1, 3 (тип GLint) Задает параметрические значения области определения и и v Что возвращает: Ничего См. также: glEvalCoord, glEvalMesh, glMapl, glMap2, glMapGrid
। лава i и кривые и поверхности &иэ gIGetMap Цель: Извлечь параметры функции оценки Включаемый файл: <gl.h> Варианты: void glGetMapdv(GLenum target, GLenum query, GLdouble *v); void glGetMapfv(GLenum target, GLenum query, GLfloat *v); void glGetMapiv(GLenum target, GLenum query, GLint Описание: Извлекает настройки отображения, установленные функцией glMap. Объяснение типов отображений см. в разделах, посвященных функциям glMapl и д1Мар2 Параметры: target (тип GLenum) Имя отображения, определены такие отображения' GL_MAP1_COLOR_4, GL_MAP1_INDEX, GL_MAP1_NORMAL, GL_MAP1_TEXTURE_COORD_1, GL_MAP1_TEXTURE_COORD_2, GL_MAP1_TEXTURE_COORD_3, GL_MAP1_TEXTURE_COORD_4, GL_MAP1_VERTEX_3, GL_MAP1_VERTEX_4, GL_MAP2_COLOR_4, GL_MAP2_INDEX, GL_MAP2_NORMAL, GL_MAP2_TEXTURE_COORD_1, GL_MAP2_TEXTURE_COORD_2, GL_MAP2_TEXTURE_COORD_3, GL_MAP2_TEXTURE_COORD_4, GL_MAP2_VERTEX_3 и GL_MAP2_VERTEX_4 Объяснение типов отображений см в разделе, посвященном функции glMap query (тип GLenum) Задает, какой параметр отображения нужно возвращать в *v Возможны следующие значения. GL_COEFF возвращает массив, содержащий контрольные точки отображения Координаты записаны по строкам Одномерные отображения возвращают число контрольных точек, равное порядку, а двухмерные карты — число контрольных точек, равных (порядок и) х (порядок v) GL_ORDER возвращает порядок функции оценки Для одномерных карт это одно значение Для двухмерных — возвращается два значения (массив, в котором вначале записан порядок и, а затем порядок v) GL_DOMAIN возвращает параметры линейного параметрического отображения Для одномерных схем оценки это наименьшее и наибольшее значение и Для двухмерных карт это минимальное и максимальное значение и, а также минимальное и максимальное значение v *v Указатель на память, которая будет содержать затребованный параметр Тип данных этого хранилища данных должен соответствовать тому, что использует функция (double, float или integer) Что возвращает: Ничего См. также: glEvalCoord, glMapl, glMap2
510 Часть I Классический OpenGL gIMap Цель: Включаемый файл: Варианты: Описание: Параметры: target (тип GLenum) Определить одно- или двухмерную схему оценки <gl.h> void glMapld(GLenum target, GLdouble ul, GLdouble u2, GLint ustride, GLint uorder, const GLdouble ★points); void glMaplf(GLenum target, GLfloat ul, GLfloat u2, GLint ustride, GLint uorder, const GLfloat ★points); void glMap2d(GLenum target, GLdouble ul, GLdouble u2, GLint ustride, GLint uorder, GLdouble vl, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); void glMap2f(GLenum target, GLfloat ul, GLfloat u2, GLint ustride, GLint uorder, GLfloat vl, GLfloat v2, GLint vstride, GLint vorder, const GLfloat ★points); Определяют одно- или двухмерные схемы оценки Функции glMaplx используются для одномерных схем оценки (кривых), а функции д1Мар2х — для двухмерных схем оценки (поверхности) Схемы оценки предоставляют информацию о вершинах или другие данные (см параметр target), вычисленные для одного или двух диапазонов изменения параметров (и и v) Задает, какие типы значений порождаются схемами оценки Допустимы следующие значения одно- и двухмерных схем оценки. GL_MAP1_VERTEX_3 (ИЛИ GL_MAP2_VERTEX_3): контрольные точки — три величины с плавающей запятой, представляющие координаты х, у и z Команды glVertex3 функции генерируются внутренне при расчете значения карты GL_MAP1_VERTEX_4 (или GL_MAP2_VERTEX_4): контрольные точки — четыре величины с плавающей запятой, представляющие координаты х, у, z и w. Команды функции glVertex4 генерируются внутренне при расчете значения карты GL_MAP1_INDEX (или GL_MAP2_INDEX): сгенерированные контрольные точки — отдельные величины с плавающей запятой, представляющие индексированный цвет Команды функции glIndex генерируются внутренне при расчете значения карты Примечание В отличие от непосредственного вызова функции gllndex данная функция не меняет текущий индекс цвета
Глава 10 Кривые и поверхности 511 GL_MAP1_COLOR_4 (или GL_MAP2_COLOR_4): сгенерированные контрольные точки — четыре величины с плавающей запятой, представляющие красный, зеленый, синий и альфа-компонент цвета. Команды glColor4 генерируются внутренне при расчете значения карты Примечание В отличие от непосредственного вызова функции glColor4f данная функция не меняет текущий цвет GL_MAP1_NORMAL (или GL_MAP2_NORMAL)’ сгенерированные контрольные точки — три величины с плавающей запятой, представляющие компоненты х, у и z вектора нормали. Команды функции glNormal генерируются внутренне при расчете значения карты. Примечание В отличие от непосредственного вызова функции glNormal данная функция нс меняет текущую нормаль GL_MAP1_TEXTURE_COORD_1 (или GL_MAP2_TEXTURE_COORD_1). сгенерированные контрольные точки — отдельные величины с плавающей запятой, представляющие текстурную координату s. Команды функции glTexCoordl генерируются внутренне при расчете значения карты. Примечание- В отличие от непосредственного вызова функции glTexCoord данная функция не меняет текущую текстурную координату GL_MAP1_TEXTURE_COORD_2 (или GL_MAP2_TEXTURE_COORD_2)’ сгенерированные контрольные точки — две величины с плавающей запятой, представляющие текстурные координаты s и t. Команды функции glTexCoord2 генерируются внутренне при расчете значения карты. Примечание: В отличие от непосредственного вызова функции glTexCoord данная функция не меняет текущие текстурные координаты GL_MAP1_TEXTURE_COORD_3 (или GL_MAP2_TEXTURE_COORD_3)’ сгенерированные контрольные точки — три величины с плавающей запятой, представляющие текстурные координаты s,t и г. Команды функции д1ТехСоо^Згенсрируются внутренне при расчете значения карты Примечание- В отличие от непосредственного вызова функции glTexCoord данная функция не меняет текущие текстурные координаты GL_MAP1_TEXTURE_COORD_4 (или GL_MAP2_TEXTURE_COORD_4). сгенерированные контрольные точки — четыре величины с плавающей запятой, представляющие текстурные координаты s,t, г и q Команды функции glTexCoord4 генерируются внутренне при расчете значения карты. Примечание- В отличие от непосредственного вызова функции glTexCoord данная функция не меняет текущие текстурные координаты
512 Часть I Классический OpenGL ul, u2 Задает линейное отображения параметрической величины и vl, v2 Задает линейное отображения параметрической величины v Данный параметр используется только в двухмерных картах ustride, vstnde Задает число величин типа float или double между контрольными точками в структуре данных *points Координаты всех точек занимают последовательные ячейки памяти, но данный параметр позволяет размещать точки нужным образом, чтобы данные можно было получать из произвольной структуры данных uorder, vorder Задает число контрольных точек в направлениях и и v ★points Указатель на контрольные точки в памяти Может представлять собой двух- или трехмерный массив, а также произвольную структуру данных Что возвращает: Ничего См. также: glBegin, glColor, glEnable, glEvalCoord, glEvalMesh, glEvalPoint, gIMapGrid, gINormal, glTexCoord, glVertex gIMapGrid Цель: Определить одно- или двухмерную сетку отображения Включаемый файл: <gl.h> Варианты: void glMapGridld(GLint un, GLdouble ul, GLdouble u2); void glMapGridlf(GLint un,GLfloat ul,GLfloat u2); void glMapGrid2d(GLint un, GLdouble ul, GLdouble u2, GLint vn, GLdouble vl, GLdouble v2); void glMapGrid2f(GLint un, GLfloat ul, GLfloat u2, GLint vn, GLfloat vl, GLfloat v2); Описание: Устанавливает одно- или двухмерную сетку отображения Создает координатную сетку и используется вместе с функциями glMap и glEvalMesh для эффективного расчета отображения Параметры: un, vn (тип GLint) Задает число делений сетки в направлениях и или v ul, и2 Задает нижнее и верхнее значения области сетки в направлении и vl, v2 Задаст нижнее и верхнее значения области сетки в направлении v Что возвращает: Ничего См. также: glEvalCoord, glEvalMesh, glEvalPoint, glMapl, glMap2
Глава 10 Кривые и поверхности 513 gluBeginCurve Цель: Начать определение кривой NURBS Включаемый файл: <glu.h> Синтаксис: void gluBeginCurve(GLUnurbsObj *nObj); Описание: Используется вместе c gluEndCurve для разграничения определения кривой NURBS Параметры: nObj (тип GLUnurbsObj*) Что возвращает: Задает объект NURBS Ничего См. также: gluEndCurve gluBeginSurface Цель: Начать определение поверхности NURBS Включаемый файл: <glu. h> Синтаксис: void gluBeginSurface(GLUnurbsObj *nObj); Описание: Используется вместе c gluEndSurface для разграничения определения поверхности NURBS Параметры: nObj (тип GLUnurbsObj *) Что возвращает: Задает объект NURBS Ничего См. также: gluEndSurface gluBeginTrim Цель: Начать определение линии разреза NURBS Включаемый файл: <glu.h> Синтаксис: void gluBeginTrim(GLUnurbsObj *nObj); Описание: Используется вместе c gluEndTrim для разграничения определения линии разреза Линией разреза является кривая или набор соединенных кривых, определенных с помощью gluNurbsCurve или gluPwlCurve Функции gluBeginTrim и gluEndTrim должны располагаться внутри пары gluBeginSurface/gluEndSurface При использовании отсечения направление кривой задает, какие участки кривой будут отрезаны Остаются области поверхности, расположенные слева от направления обхода линии разреза Следовательно, линии разреза с обходом по часовой стрелке удаляют внутренние для них области, а линии с обходом против часовой стрелки — внешние Параметры: nObj (тип GLUnurbsObj *) Что возвращает: Задает объект NURBS Ничего См. также: gluEndTrim
514 Часть I Классический OpenGL gluCylinder Цель: Нарисовать цилиндр вращения Включаемый файл: <glu.h> Синтаксис: void gluCylinder(GLUquadricObj *obj, GLdouble baseRadius, GLdouble topRadius, GLdouble height, GLint slices, GLint stacks); Описание: Рисует полый бесконечный цилиндр вдоль оси z. Если topRadius или bottomRadius равно 0, вместо него рисуется конус. Высота цилиндра вдоль положительного направления оси z равна height. Аргумент slices контролирует число боковых поверхностей цилиндра, а аргумент stacks задает число сегментов, генерируемых вдоль оси z цилиндра Параметры: obj (тип Информация о состоянии поверхности второго порядка, GLUquadricObj *) используемая при визуализации baseRadius Радиус основания (z = 0) цилиндра (тип GLdouble*) topRadius (тип GLdouble*) Радиус вершины (z = высота) цилиндра height (тип GLdouble*) Высота или длина цилиндра вдоль оси z slices Число сторон цилиндра (тип GLint) stacks Число сегментов цилиндра вдоль оси z (тип GLint) Что возвращает: Ничего См. также: gluDeleteQuadric, gluNewQuadric, gluQuadricCallback, gluQuadricDrawStyle, gluQuadricNormals, gluQuadricOrientation, gluQuadricTexture gluDeleteNurbsRenderer Цель: Уничтожить объект NURBS Включаемый файл: <glu. h> Синтаксис: void gluDeleteNurbsRenderer(GLUnurbsObj *nobj); Описание: Удаляет заданный объект NURBS и освобождает выделенную Параметры: nObj (тип GLUnurbsObj *) Что возвращает: См. также: ему память Задает удаляемый объект NURBS Ничего gluNewNurbsRenderer
Глава 10. Кривые и поверхности 515 gluDeleteQuadric Цель: Удалить объект состояния поверхности второго порядка Включаемый файл: <glu. h> Синтаксис: void gluDeleteQuadric(GLUquadricObj *obj); Описание: Удаляет объект состояния поверхности второго порядка. Удаленный объект нельзя больше использовать в операциях рисования Параметры: obj (тип Удаляемый объект состояния поверхности второго порядка GLUquadricObj *) Что возвращает: Ничего См. также: gluNewQuadric, gluQuadricCallback, gluQuadricDrawStyle, gluQuadricNormals, gluQuadricOrientation, gluQuadricTexture gluDeleteTess Цель: Удалить объект мозаичного представления Включаемый файл: <glu. h> Синтаксис: void gluDeleteTess(GLUtesselator *tobj); Описание: Освобождает память, соотнесенную с объектом мозаичного представления Параметры: tobj (тип Удаляемый объект мозаичного представления GLUtesselator*) Что возвращает: Ничего См. также: gluNewTess gluDisk Цель: Нарисовать квадратичный диск Включаемый файл: <glu. h> Синтаксис: void gluDisk(GLUquadricObj *obj, GLdouble innerRadius, GLdouble outerRadius, GLint slices, GLint loops); Описание: Рисует диск, перпендикулярный оси z. Если innerRadius равно 0, рисуется сплошной диск Аргумент slices контролирует число боковых граней диска, а аргумент loops задает число генерируемых колец с общим центром на оси z
516 Часть I Классический OpenGL Параметры: obj (тип GLUquadricObj*) innerRadius (тип GLdouble*) outerRadius (тип GLdouble*) Информация о состоянии поверхности второго порядка, используемая при визуализации Внутренний радиус диска Внешний радиус диска slices (тип GLint) Число боковых поверхностей цилиндра loops (тип GLint) Что возвращает: Число концентрических окружностей с центром на оси z Ничего См. также: gluDeleteQuadric, gluNewQuadric, gluQuadricCallback, gluQuadricDrawStyle, gluQuadricNormals, gluQuadricOrientation, gluQuadricTexture gluEndCurve Цель: Завершить определение кривой NURBS Включаемый файл: <glu.h> Синтаксис: void gluEndCurve(GLUnurbsObj *nobj); Олисание: Используется вместе c gluBeginCurve для разграничения определения кривой NURBS Параметры: nObj (тип GLUnurbsObj *) Что возвращает: Задает объект NURBS Ничего См. также: gluBeginCurve gluEndSurface Цель: Завершить определение поверхности NURBS Включаемый файл: <glu.h> Синтаксис: void gluEndSurface(GLUnurbsObj *nObj); Описание: Используется вместе c gluBeginSurface для разграничения определения поверхности NURBS Параметры: nObj (тип GLUnurbsObj *) Что возвращает: Задаст объект NURBS Ничего См. также: gluBeginSurface
Глава 10 Кривые и поверхности 517 gluEndTrim Цель: Завершить определение контура обрезания NURBS Включаемый файл: <glu.h> Синтаксис: void gluEndTrim(GLUnurbsObj *nObj); Описание: Используется вместе c gluBeginTrim, чтобы отметить конец линии разреза обрезания NURBS. Подробнее о линиях разреза см.gluBeginTrim Параметры: nObj (тип GLUnurbsObj*) Что возвращает: Задает объект NURBS Ничего См. также: gluBeginTrim gluGetNurbsProperty Цель: Извлечь свойство NURBS Включаемый файл: <gl.h> Синтаксис: void gluGetNurbsProperty(GLUnurbsOb} *nObj, GLenum property, GLfloat *value) ; Описание: Извлекает свойство NURBS, заданное для объекта NURBS Объяснение свойств см в описании функции gluNurbsProperty Параметры: nObj (тип GLUnurbsOb]*) Задает объект NURBS property (тип GLenum*) Извлекаемое свойство NURBS. Допустимыми являются следующие свойства. GLU_SAMPLING_TOLERANCE, GLU_DISPLAY_MODE, GLU_CULLING, GLU_AUTO_LOAD_MATRIX, GLU_PARAMETRIC_TOLERANCE, GLU_SAMPLING_METHOD, GLU_U_STEP и GLU_V_STEP. Подробнее об этих свойствах см в описании функции gluNurbsProperty value (тип GLfloat*) Что возвращает: Указатель на положении в памяти, куда скопировать значение указанного свойства Ничего См. также: gluNewNurbsRenderer, gluNurbsProperty
518 Часть I Классический OpenGL gluLoadSamplingMatrices Цель: Загрузить матрицы дискретизации и отбора NURBS Включаемый файл: Синтаксис: Описание: Параметры: nObj (тип GLUnurbsObj*) modelMatrix (тип GLfloat[16]) projMatrix (тип GLfloat[16]) viewport (тип GLint[4]) Что возвращает: См. также: <gl.h> void gluLoadSamplingMatrices(GLUnurbsObj *nObj, const GLfloat modelMatrix[lf>], const GLfloat projMatrix[lf>], const GLint viewport [ 4 ]) ; Используется для повторного расчета матриц дискретизации и отбора для поверхностей NURBS. Матрица дискретизации позволяет определить, насколько мелкой мозаикой нужно представлять поверхность, чтобы удовлетворить допуску дискретизации. Матрица отбора позволяет определить, нужно ли отбирать поверхность перед визуализацией. Обычно вызывать эту функцию не требуется, если не отключено свойство GLU_AUTO_LOAD_MATRIX. Иногда, правда, она нужна при использовании режимов выбора и обратной связи Задает объект NURBS Задает матрицу наблюдения модели Задает матрицу проекции Задает поле просмотра Ничего gluNewNurbsRenderer, gluNurbsProperty gluNewNurbsRenderer Цель: Включаемый файл: Синтаксис: Описание: Что возвращает: См. также: Создать объект NURBS <glu.h> GLUnurbsObj* gluNewNurbsRenderer(void); Создает объект визуализации NURBS. Этот объект используется для контроля над поведением и характеристиками кривых и поверхностей NURBS Данный указатель требуется всем функциям, позволяющим устанавливать свойства NURBS. После завершения визуализации объекта NURBS этот объект следует удалить с помощью gluDeleteNurbsRenderer Указатель на новый объект NURBS. Этот объект используется при вызове функций визуализации и управления gluDeleteNurbsRenderer
Глава 10. Кривые и поверхности 519 gluNewQuadric Цель: Создать новый объект состояния поверхности второго порядка Включаемый файл: <glu.h> Синтаксис: GLUquadricObj *gluNewQuadric(void); Описание: Создает новый непрозрачный объект состояния квадратичной поверхности, который будет использоваться при рисовании. Объект состояния квадратичной поверхности содержит спецификации, определяющие, как будут нарисованы последующие изображения Параметры: Нет Что возвращает: NULL, если память недоступна; в противном случае — указатель на допустимый объект состояния поверхности второго порядка См. также: gluDeleteQuadric, gluQuadricCallback, gluQuadricDrawStyle, gluQuadricNormals, gluQuadricOrientation, gluQuadricTexture gluNewTess Цель: Создать объект мозаичного представления Включаемый файл: <glu.h> Синтаксис: GLUtriangulatorObj *gluNewTess(void); Описание: Параметры: Создает объект мозаичного представления Нет Что возвращает: Новый объект мозаичного представления (тип GLUtriangulatorObj *) См. также: gluDeleteTess gluNurbsCallback Цель: Определить обратный вызов для функции NURBS Включаемый файл: <glu.h> Синтаксис: void gluNurbsCallback(GLUnurbsObj *nObj, GLenum which, void(*fn)( )); Описание: Устанавливает функцию обратного вызова NURBS. Единственным поддерживаемым обратным вызовом до версии GLU 1.3 был GL_ERROR В случае ошибки указанная функция вызывается с аргументом типа GLenum. С помощью констант от GLU_NURBS_ERROR1 ДО GLU_NURBS_ERROR37 МОЖНО задать одну из 37 ошибок NURBS С помощью функции gluErrorString вы можете извлечь определение ошибки в форме строки символов. Данные коды ошибок перечислены в табл. 10 2 Для GLU версий 1.3 и более поздних значение GLU_ERROR было вытеснено значениями GLU_NURBS_ERROR и рядом других обратных вызовов, указанных в графе “Параметры”
520 Часть I Классический OpenGL Параметры: nObj (тип GLUnurbsObj*) which (тип GLenum) fn (тип void * ()) Что возвращает: См. также: Задаст объект NURBS Задаст определяемый обратный вызов. До GLU версии 1 3 единственным допустимым значением было GLU_error В GLU версии 1.3 и более поздних на место GLU_ERROR пришли значения GLU_NURBS_ERROR и следующие обратные вызовы GLU_NURBS_BEGIN, GLU_NURBS_VERTEX, GLU_NURBS_NORMAL, GLU_NURBS_COLOR, GLU_NURBS_TEXTURE_COORD, GLU_NURBS_END, GLU_NURBS_BEGIN_DATA, GLU_NURBS_VERTEX_DATA, GLU_NURBS_NORMAL_DATA, GLU_NURBS_COLOR_DATA, GLU_NURBS_TEXTURE_COORD_DATA И GLU_NURBS_END_DATA Задает функцию, которую нужно использовать в обратом вызове. В различных обратных вызовах применяются следующие прототипы GLU_NURBS_BEGIN• void *(GLenum type); GLU_NURBS_BEGIN_DATA: void *(GLenum type, void ‘userData) GLU_NURBS_VERTEX: void ‘(GLfloat ‘vertex); GLU_NURBS_VERTEX_DATA: void (GLfloat ‘vertex, void ‘userData) GLU_NURBS_NORMAL: void *(GLfloat ‘normal); GLU_NURBS_NORMAL_DATA: void ‘(GLfloat ‘normal, void ‘userData); GLU_NURBS_COLOR: void ‘(GLfloat ‘color); GLU_NURBS_COLOR_DATA: void ‘(GLfloat ‘color, void ‘userData); GLU_NURBS_TEXTURE_COORD: void *(GLfloat ‘texCoord); GLU_NURBS_TEXTURE_COORD_DATA: void *(GLfloat ‘texCoord, void ‘userData); GLU_NURBS_END: void ‘(void); GLU_NURBS_END_DATA• void ‘(void userData); GLU_NURBS_ERROR: void ‘(GLenum error); Ничего gluErrorString
I лава 1 и кривые и поверхности 5Z1 ТАБЛИЦА 10.2. Коды ошибок NURBS Код ошибки Определение GLU_NURBS_ERROR1 GLU_NURBS_ERROR2 GLU_NURBS_ERROR3 GLU_NURBS_ERROR4 GLU_NURBS_ERROR5 GLU_NURBS_ERROR6 GLU_NURBS_ERROR7 GLU_NURBS_ERROR8 GLU_NURBS_ERROR9 GLU_NURBS_ERROR10 GLU_NURBS_ERROR11 GLU_NURBS_ERROR12 GLU_NURBS_ERROR13 GLU_NURBS_ERROR14 GLU_NURBS_ERROR15 GLU_NURBS_ERROR16 GLU_NURBS_ERROR17 GLU_NURBS_ERROR18 GLU_NURBS_ERROR19 GLU_NURBS_ERROR20 GLU_NURBS_ERROR21 GLU_NURBS_ERROR2 2 GLU_NURBS_ERROR2 3 GLU_NURBS_ERROR24 GLU_NURBS_ERROR2 5 GLU_NURBS_ERROR2 6 GLU_NURBS_ERROR2 7 GLU_NURBS_ERROR2 8 GLU_NURBS_ERROR2 9 GLU_NURBS_ERROR3 0 GLU_NURBS_ERROR31 GLU_NURBS_ERROR32 GLU_NURBS_ERROR33 GLU_NURBS_ERROR34 GLU_NURBS_ERROR3 5 GLU_NURBS_ERROR3 6 GLU_NURBS_ERROR37 Порядок сплайна не поддерживается Слишком мало узлов Допустимый диапазон узлов пуст Узел, уменьшающий последовательность узлов Кратность узла больше порядка сплайна endcurve должно следовать за bgncurve bgncurve должно предшествовать endcurve Пропущенные или лишние геометрические данные Невозможно нарисовать pwlcurves Пропущенные или лишние данные области Пропущенные или лишние данные области endtrim должно предшествовать endsurface bgnsurface должно предшествовать endsurface В качестве обрезающей кривой передана кривая неподходящего типа bgnsurface должно предшествовать bgntrim endtrim должно следовать за bgntrim bgntrim должно предшествовать endtrim Неверная или пропущенная обрезающая кривая bgntrim должно предшествовать pwlcurve pwlcurve определено дважды Смешаны pwlcurve и nurbscurve Неверное использование типа данных обрезания nurbscurve определено дважды Смешаны nurbscurve и pwlcurve nurbssurface определено дважды Неправильно заданное свойство endsurface должно следовать за bgnsurface Пересекающиеся или неверно ориентированные кривые обрезания Пересекающиеся кривые обрезания Не используется Несвязанные кривые обрезания Неизвестная ошибка узла Встретилось отрицательное значение счетчика вершин Встретился отрицательный шаг Неизвестный дескриптор типа Контрольные точки не определены Дублирующаяся точка или pwlcurve gluNurbsCurve Цель: Определить форму кривой NURBS Включаемый файл: <glu.h> Синтаксис: void gluNurbsCurve (GLUnurbsObj * *nObj, GLint nknots, GLfloat *knot, GLint stride, GLfloat *ctlArray, GLint order, GLenum type); Описание: Определяет форму кривой NURBS Определение данной кривой должно разграничиваться функциями gluBeginCurve и gluEndCurve
522 Часть I Классический OpenGL Параметры: nObj (тип GLUnurbsObj*) nknots (тип GLint) knot (тип GLfloat*) stride (тип GLint) ctlArray (тип GLfloat*) order (тип GLint) type (тип GLenum) Указатель на объект NURBS (созданный с помощью gluNewNurbsRenderer) Число узлов в *knots. Равно числу контрольных точек плюс order Массив значений узлов в неубывающем порядке Смещение (число значений обычной точности с плавающей запятой) между контрольными точками Указатель на массив или структуру данных, содержащую контрольные точки для поверхности NURBS Порядок поверхности NURBS (порядок на 1 больше, чем степень) Тип поверхности. Возможны следующие значения: GL_MAP2_VERTEX_3, GL_MAP2_VERTEX_4, GL_MAP2_INDEX, GL_MAP2_COLOR_4, GL_MAP2_NORMAL, GL_MAP2_TEXTURE_COORD_1, GL_MAP2_TEXTURE_COORD_2, GL_MAP2_TEXTURE_COORD_3 И GL_MAP2_TEXTURE_COORD_4 Что возвращает: Ничего См. также: gluBeginCurve, gluEndCurve, gluNurbsSurface gluNurbsProperty Цель: Установить свойство NURBS Включаемый файл: <glu.h> Синтаксис: void gluNurbsProperty(GLUnurbsObj *nObj, GLenum property, GLfloat value); Описание: Устанавливает свойства объекта NURBS. Допустимы следующие свойства: GLU_SAMPLING_TOLERANCE: Устанавливает максимальную длину, в пикселях, используемую в методе дискретизации GLU_PATH_LENGTH Значение по умолчанию равно 50 0 GLU_DISPLAY_MODE: Определяет, как визуализируется поверхность NURBS. Параметр value может иметь значение GLU_FILL (использовать закрашенные и затененные многоугольники), GLU_OUTLINE_POLYGON (рисовать только контуры многоугольников после мозаичного представления) или glu_OUTLINE_patch (рисовать только контуры определенных пользователем участков и обрезающих кривых). Значение по умолчанию равно GLU_FILL GLU_CULLING’ Интерпретирует параметр value как булево значение, указывающее, следует ли отбрасывать кривую NURBS, если ее контрольные точки находятся вне поля просмотра
Глава 10 Кривые и поверхности 523 GLU_PARAMETRIC_TOLERANCE: Устанавливает максимальное расстояние в пикселях, используемое при выборе метода дискретизации GLU_PARAMETRIC_ERROR. Значение по умолчанию равно 0.5. Данное свойство было введено в GLU версии 1.1 GLU_SAMPLING_METHOD: Задает, как поверхность NURBS будет представляться мозаикой. Данное свойство было введено в GLU версии 1.1. Допустимы следующие методы: GLU_PATH_LENGTH — поверхность визуализируется с максимальной длиной (в пикселях) сторон многоугольников, образующих мозаику, не превышающей значение, заданное GLU_SAMPLING_TOLERANCE; GLU_PARAMETRIC_ERROR — поверхность визуализируется со значением GLU_PARAMETRIC_TOLERANCE, указывающим максимальное расстояние (в пикселях) между многоугольниками мозаики и поверхностями, которые они аппроксимируют, GLU_DOMAIN_DISTANCE — в параметрических координатах указывается, сколько точек выборки на единицу длины следует брать в направлениях и и и. Значение по умолчанию равно GLU_PATH_LENGTH GLU_U_STEP: в параметрических координатах устанавливает число точек выборки на единицу длины по направлению и. Это значение используется, когда метод GLU_SAMPLING_METHOD выбран равным GLU_DOMAIN_DISTANCE. Значение по умолчанию равно 100. Свойство введено в GLU версии 1.1 GLU_V_STEP: в параметрических координатах устанавливает число точек выборки на единицу длины по направлению v. Это значение используется, когда метод GLU_SAMPLING_METHOD выбран равным GLU_DOMAIN_DISTANCE. Значение по умолчанию равно 100. Свойство введено в GLU версии 1.1 GLU_AUTO_LOAD_MATRIX: интерпретирует параметр value как булево значение. Если оно установлено равным GL_TRUE, код NURBS загружает матрицу проекции, матрицу наблюдения модели и поле просмотра с сервера OpenGL, вычисляя матрицы дискретизации и отбора для каждой кривой NURBS. Матрицы дискретизации и отбора нужны для того, чтобы определить мозаичное разбиение поверхности NURBS на отрезки или многоугольники и забраковать поверхность NURBS, если она лежит вне поля просмотра. Если данный режим установлен как GL_FALSE, пользователь должен предоставить указанные матрицы и поля просмотра для схемы визуализации NURBS, которая использует полученную информацию для построения матриц дискретизации и отбора. Указанное можно сделать с помощью функции gluLoadSamplingMatrices. По умолчанию данный режим — GL_TRUE. Меняя его, вы не повлияете на матрицы дискретизации и отбора (если не вызывать функцию gluLoadSamplingMatrices)
524 Часть I Классический OpenGL Параметры: nObj (тип GLUnurbsObj*) property (тип GLenum) Объект NURBS, имеющий модифицируемое свойство (Создается путем вызова функции glNewNurbsRenderer ) Устанавливаемое или модифицируемое свойство Возможны следующие значения- GLU_SAMPLING_TOLERANCE, GLU_DISPLAY_MODE, GLU_CULLING, GLU_AUTO_LOAD_MATRIX, GLU_PARAMETRIC_TOLERANCE, GLU_SAMPLING_METHOD, GLU_U_STEPИ GLU_V_STEP value (тип GLfloat) Что возвращает: Значение, которое присваивается указанному свойству Ничего См. также: gluGetNurbsProperty, gluGetString, gluLoadSamplingMatrices, gluNewNurbsRenderer, gluNewNurbsRenderer, gluNurbsCurve, gluPwlCurve gluNurbsSurface Цель: Определить форму поверхности NURBS Включаемый файл: <glu.h> Синтаксис: void gluNurbsSurface(GLUnurbsObj *nObj, GLint uknotCount, GLfloat *uknot, GLint vknotCount, GLfloat *vknot, GLint uStride, GLint vStride, GLfloat *ctlArray, GLint uorder, GLint vorder, GLenum type); Описание: Определяет форму поверхности NURBS Параметр нужно обособить gluBeginSurface и gluEndSurface Форма поверхности определяется перед обрезанием Для обрезания поверхности NURBS можно применять функции gluBeginTrim/gluEndTrim и gluNurbsCurve или gluPwlCurve Параметры: nObj (тип GLUnurbsObj*) uknotCount (тип GLint) и knot (тип GLfloat*) Указатель на объект NURBS (Создается с помощью функции gluNewNurbsRenderer) Число узлов в параметрическом направлении и Массив значений узлов, представляющий узлы в направлении и Значения должны идти в ненисходящем порядке Длина массива задается в uknotCount vknotCount (тип GLint) vknot (тип GLfloat*) Число узлов в параметрическом направлении v Массив значений узлов, представляющий узлы в направлении v Значения должны идти в ненисходящем порядке Длина массива задается в vknotCount
Глава 10 Кривые и поверхности 525 uStride (тип GLint) Смещение, выраженное в количестве значений обычной точности с плавающей запятой, между последовательными контрольными точками в параметрическом направлении и в ctlArray vStride (тип GLint) Смещение, выраженное в количестве значений обычной точности с плавающей запятой, между последовательными контрольными точками в параметрическом направлении v в ctlArray ctlArray (тип GLfloat*) Указатель на массив, содержащий контрольные точки поверхности NURBS Смещения между последовательными точками в параметрических направлениях и и и представлены в uStride и vStride uorder (тип GLint) Порядок поверхности NURBS в параметрическом направлении и Порядок на единицу меньше, чем степень, следовательно, поверхность, кубическая по и, имеет порядок 4 по и vorder (тип GLint) Порядок поверхности NURBS в параметрическом направлении v Порядок на единицу меньше, чем степень, следовательно, поверхность, кубическая по v, имеет порядок 4 по и type (тип GLenum) Тип поверхности Возможные значения GL_MAP2_vertex_3, GL_MAP2_VERTEX_4, GL_MAP2_INDEX, GL_MAP2_COLOR_4, GL_MAP2_NORMAL, GL_MAP2_TEXTURE_COORD_1, GL_MAP2_TEXTURE_COORD_2, GL_MAP2_TEXTURE_COORD_3 И GL_MAP2_TEXTURE_COORD_4 Что возвращает: Ничего См. также: gluBeginSurface, gluBeginTrim gluPartialDisk Цель: Нарисовать сектор квадратичного диска Включаемый файл: <glu.h> Синтаксис: void gluPartialDisk.( GLUquadricObj *obj, GLdouble innerRadius, GLdouble outerRadius, GLint slices, GLint loops, GLdouble startAngle, GLdouble sweepAngle); Описание: Рисует сектор диска, перпендикулярный оси z Если значение innerRadius равно 0, изображается сплошная окружность Аргумент slices задает число боковых сторон диска, а аргумент loops — число колец, генерируемых вокруг оси z Аргумент startAngle задает начальный угол диска (0е соответствует вершине, а 90° — правому краю диска) Аргумент sweepAngle определяет фрагмент диска в градусах
526 Часть I Классический OpenGL Параметры: obj (тип GLUquadricObj *) innerRadius (тип GLdouble*) outerRadius (тип GLdouble*) slices (тип GLint) loops (тип GLint) startAngle (тип GLdouble*) sweepAngle (тип GLdouble*) Что возвращает: Информация о состоянии объектов второго порядка, используемая при визуализации Внутренний радиус диска Внешний радиус диска Число боковых сторон цилиндра Число колец с центром на оси z Начальный угол сектора диска Угловой размер сектора диска Ничего См. также: gluDeleteQuadric, gluNewQuadric, gluQuadricCallback, gluQuadricDrawStyle, gluQuadricNormals, gluQuadricOrientation, gluQuadricTexture gluPwICurve Цель: Задать кусочно-гладкую обрезающую кривую NURBS Включаемый файл: <glu.h> Синтаксис: void gluPwICurve(GLUnurbsObj *nObj, GLint count, GLfloat *array, GLint stride, .GLenum type); Описание: Определяет кусочно-гладкую линейную обрезающую кривую для поверхности NURBS. Массив точек выражается в параметрическом пространстве с координатами и и v. Это пространство представляет собой единичный квадрат (по обеим осям длина стороны равна 1). Обрезающие кривые с обходом по часовой стрелке удаляют очерченную область; при обходе против часовой стрелки отбрасывается внешняя область. Как правило, вначале задается область обрезания вокруг поверхности, которая обозначает все точки, не принадлежащие поверхности После этого определяется меньшая кривая с обходом по часовой стрелке, вырезающая участки внутри обозначенной поверхности. Обрезающие кривые могут быть кусочно-гладкими. Это означает, что для определения обрезаемой области можно один или несколько раз вызывать gluPwICurve или gluNurbsCurve, если эти функции определяют кривые, имеющие общие конечные точки и формирующие замкнутую область в пространстве u/v
Глава 10 Кривые и поверхности 527 Параметры: nObj (тип GLUnurbsObj*) count (тип GLint) array (тип GLfloat*) stride (тип GLint) type (тип GLenum) Задает обрезаемый объект NURBS Задает число точек кривой, перечисленных в * array Задает массив граничных точек данной кривой Задает смещение между точками кривой Задает тип кривой: значение GLU_MAP1_TRIM_2 применяется, когда обрезающая кривая задана через координаты и и и, значение glu_mapi_trim_3 — когда кроме этого применяется координата w (масштабирование) Что возвращает: Ничего gluQuadricCallback Цель: Определить функцию обратного вызова объекта второго порядка Включаемый файл: <glu.h> Синтаксис: void gluQuadricCallback(GLUquadricObj *obj, GLenum which, void (*fn)()) ; Описание: Определяет функции обратного вызова, которые будут использованы при рисовании квадратичных форм. В настоящее время определена единственная функция обратного вызова — GLU_ERROR, вызываемая при наличии ошибки OpenGL или GLU Параметры: obj (тип GLUquadricObj *) which (тип GLenum) fn (тип void(*) О) Что возвращает: Информация о состоянии объектов второго порядка, используемая при визуализации Определяемая функция обратного вызова. Параметр должен иметь значение GLU_ERROR Функция обратного вызова (получает одну переменную типа GLenum, содержащую ошибку) Ничего См. также: gluDeleteQuadric, gluNewQuadric, gluQuadricDrawStyle, gluQuadricNormals, gluQuadricOrientation, gluQuadricTexture
528 Часть I Классический OpenGL gluQuadricDrawStyle Цель: Установить стиль рисования квадратичного объекта состояния Включаемый файл: <glu.h> Синтаксис: void gluQuadricDrawStyle(GLUquadricObj *obj, GLenum drawStyle); Описание: Выбирает стиль рисования для всех квадратичных форм Параметры: obj (тип GLUquadricObj*) drawstyle (тип GLenum) Информация о состоянии объектов второго порядка, используемая при визуализации Стиль рисования Допустимы следующие состояния GLU_FILL. объекты второго порядка рисуются закрашенными (применяются примитивы многоугольников и лент) GLU_LINE объекты второго порядка рисуются “каркасными” (применяются примитивы линий) GLU_SILHOUETTE объекты второго порядка рисуются с использованием примитивов линий, изображаются только внешние края GLU_POINT‘ объекты второго порядка рисуются с применением примитивов точек Что возвращает: Ничего См. также: gluDeleteQuadric, gluNewQuadric, gluQuadricCallback, gluQuadricNormals, gluQuadricOrientation, gluQuadricTexture gluQuadricNormals Цель: Установить тип нормалей освещения, используемых при рисовании квадратичных объектов Включаемый файл: <glu.h> Синтаксис: void gluQuadricNormals(GLUquadricObj *obj, GLenum normals); Описание: Эта функция задает тип нормалей освещения, генерируемых при рисовании форм с использованием заданных квадратичных объектов состояния
Глава 10 Кривые и поверхности 529 Параметры: obj (тип GLUquadricObj *) normals (тип GLenum) Информация о состоянии объектов второго порядка, используемая при визуализации Тип генерируемой нормали Допустимы следующие типы GLU_NONE Нормали освещения не генерируются GLU_FLAT Нормали освещения генерируются для каждого многоугольника (в результате получается фасеточная структура) GLU_SMOOTH Нормали освещения генерируются для каждой вершины (в результате получается гладкое изображение) Что возвращает: Ничего См. также: gluDeleteQuadric, gluNewQuadric, gluQuadricCallback, gluQuadricDrawStyle, gluQuadricOrientation, gluQuadricTexture gluQuadricOrientation Цель: Установить ориентацию нормалей освещения для квадратичных объектов Включаемый файл: <glu.h> Синтаксис: void gluQuadricOrientation(GLUquadricObj *obj, GLenum orientation); Описание: Задает направление нормалей освещения для полых объектов Параметр ориентации (orientation) может иметь значение GLU_OUTSIDE (нормали освещения указывают наружу) или GLU_INSIDE (нормали освещения указывают внутрь) Параметры: obj (тип Информация о состоянии объектов второго порядка, GLUquadricObj *) используемая при визуализации orientation Ориентация нормалей освещения — GLU_OUTSIDE или (тип GLenum) GLU_INSIDE Значение по умолчанию равно GLU_OUTSIDE Что возвращает: Ничего См. также: gluDeleteQuadric, gluNewQuadric, gluQuadricCallback, gluQuadricDrawStyle, gluQuadricNormals, gluQuadricTexture
530 Часть I. Классический OpenGL gluQuadricTexture Цель: Активизировать или деакгивизировать генерацию текстурных координат для изображений-текстур, отображаемых на объекты второго порядка Включаемый файл: <glu.h> Синтаксис: void gluQuadricTexture(GLUquadricObj *obj, GLboolean textureCoords); Описание: Контролирует, генерируются ли текстурные координаты для квадратичных форм Параметры: obj (тип Информация о состоянии объектов второго порядка, GLUquadricObj *) используемая при визуализации textureCoords GL_TRUE — если нужно генерировать текстурные координаты; (тип GLboolean) GL_FALSE — в противном случае Что возвращает: Ничего См. также: gluDeleteQuadric, gluNewQuadric, gluQuadricCallback, gluQuadricDrawStyle, gluQuadricNormals, gluQuadricOrientation gluSphere Цель: Нарисовать квадратичную сферу Включаемый файл: <glu.h> Синтаксис: void gluSphere(GLUquadricObj *obj, GLdouble radius, GLint slices, GLint stacks); Описание: Рисует полую сферу с центром в начале координат Аргумент slices контролирует число линий долготы на сфере, а аргумент stacks — число линий широты Параметры: obj (тип Информация о состоянии объектов второго порядка, GLUquadricObj *) используемая при визуализации radius (тип GLdouble*) Радиус сферы slices Число линий долготы на сфере (тип GLint) stacks Число линий широты на сфере (тип GLint) Что возвращает: Ничего См. также: gluDeleteQuadric, gluNewQuadric, gluQuadricCallback, gluQuadricDrawStyle, gluQuadricNormals, gluQuadricOrientation, gluQuadricTexture
Глава 10. Кривые и поверхности 531 gluTessBeginContour Цель: Задать новый контур или полость в сложном многоугольнике Включаемый файл: <glu.h> Синтаксис: void gluTessBeginContour(GLUtesselator *tobj); Описание: Параметры: Задает новый контур или полость в сложном многоугольнике tobj (тип GLUtesselator*) Что возвращает: Объект мозаичного представления, используемый для данного многоугольника Ничего См. также: gluTessBeginPolygon, gluTessEndPolygon, gluTessEndContour, gluTessVertex gluTessBeginPolygon Цель: Начать мозаичное представление сложного многоугольника Включаемый файл: <glu.h> Синтаксис: void gluTessBeginPolygon(GLUtesselator *tobj, GLvoid *data); Описание: Начинает представление сложного многоугольника в форме мозаики Параметры: tobj (тип GLUtesselator*) data (тип GLvoid*) Что возвращает: См. также: Объект мозаичного представление, используемый для данного многоугольника Данные, которые передаются обратным вызовам GLU_TESS_*_DATA Ничего gluTessEndPolygon, gluTessBeginContour, gluTessEndContour, gluTessVertex gluTessCallback Цель: Задать функцию обратного вызова для мозаичного представления Включаемый файл: <glu.h> Синтаксис: void gluTessCallback(GLUtesselator *tobj, GLenum which, void (*fn)()); Описание: Задает функцию обратного вызова для процедур мозаичного представления. Функции обратного вызова не замещают и не меняют операции схемы мозаичного представления. Вместо этого они предлагают средства добавления информации к мозаичному выходу (например, цвет или текстурные координаты)
532 Часть I Классический OpenGL Параметры: tobj (тип Объект мозаичного представления, используемый для данного GLUtesselator*) многоугольника which (тип GLenum) Определяемая функция обратного вызова. Приемлемые значения перечислены в табл. 10 3 fn (тип void(*) ()) Вызываемая функция Что возвращает: Ничего gluTessEndContour Цель: Включаемый файл: Синтаксис: Описание: Параметры: tobj (тип GLUtesselator*) Что возвращает: См. также: Завершить контур сложного многоугольника <glu.h> void gluTessEndContour(GLUtesselator *tobj); Завершает контур текущего многоугольника Объект мозаичного представления, используемый при обработке данного многоугольника Ничего gluTessBeginPolygon, gluTessBeginContour, gluTessEndPolygon, gluTessVertex gluTessEndPolygon Цель: Завершить мозаичное представление сложного многоугольника Включаемый файл: Синтаксис: Описание: Параметры: tobj (тип GLUtesselator*) Что возвращает: См. также: и визуализировать его <glu.h> void gluTessEndPolygon(GLUtesselator *tobj); Завершает представление сложного многоугольника в виде мозаики и визуализирует окончательный результат Объект мозаичного представления, используемый при обработке данного многоугольника Ничего gluTessBeginPolygon, gluTessBeginContour, gluTessEndContour, gluTessVertex
Глава 10 Кривые и поверхности 533 ТАБЛИЦА 10.3. Идентификаторы обратного вызова схемы мозаичного представления Константа Описание GLU_TESS_BEGIN Задает функцию, вызовом которой начинается примитив GL_TRIANGLES, GL_TRIANGLE_STRIP ИЛИ GL_TRIANGLE_FAN Эта функция должна принимать один параметр типа GLenum, задающий визуализируемый примитив, и обычно она равна glBegin GLU_TESS_BEGIN_DATA 11одобно GLU_TESS_BEGIN задает функцию [GLU 1 2], вызовом которой начинаются примитивы GL_TRIANGLES, GL_TRIANGLE_STRIP ИЛИ GL_TRIANGLE_FAN Функция должна принимать параметр типа Glenum, задающий визуализируемый примитив, и указатель типа GLvoid вызова функции gluTessBeginPolygon GLU_TESS_COMBINE Задает функцию, вызываемую [GLU 1 2] при совпадении вершин многоугольника GLU_TESS_COMBINE_DATA Подобно GLU_TESS_COMBINE, задает функцию [GLU 1 2], вызываемую при совпадении вершин многоугольника Функция также получает указатель на пользовательские данные в функции gluTessBeginPolygon GLU_TE S S_E DGE_FLAG Задает функцию, отмечающую, относятся ли последующие обратные вызовы GLU_TESS_VERTEX к исходным или к сгенерированным вершинам Функция должна принимать один аргумент типа GLboolean GL_TRUE для исходных И GL_FALSE — для сгенерированных вершин GLU_TESS_EDGE_FLAG_DATA Задает функцию, подобную GLU_TESS_EDGE_FLAG, только допускающую наличие указателя на пользовательские данные неизвестного типа (void pointer) GLU_TESS_END Задает функцию, отмечающую конец изображаемого примитива, — обычно glEnd Аргумент не требуется GLU_TESS_END_DATA Задает функцию, подобную GLU_TESS_END, только допускающую наличие указателя на пользовательские данные неизвестного типа (void pointer) GLU_TESS_ERROR Задает функцию, вызываемую при наличии ошибки Должна принимать один аргумент типа GLenum GLU_TESS_VERTEX Задает функцию, вызываемую перед передачей вершины (обычно с помощью glVertex3dv) Эта функция принимает копию третьего аргумента функции gluTessVertex GLU_TE S S_VERTEX_DATA Подобно GLU_TESS_VERTEX задает функцию (GLU 1 2], вызываемую перед передачей любой вершины Эта функция также принимает копию второго аргумента функции gluTessBeginPolygon
534 Часть I. Классический OpenGL gluTessProperty Цель: Установить значение свойства мозаичного представления Включаемый файл: Синтаксис: Описание: Параметры: tobj (тип GLUtesselator*) which (тип GLenum) value (тип GLdouble*) Что возвращает: См. также: <glu.h> void gluTessProperty(GLUtesselator *tobj, GLenum which, GLdouble value); Устанавливает значение свойства мозаичного представления Меняемый объект мозаичного представления Меняемое свойство: GLU_TESS_BOUNDARY_ONLY, GLU_TESS_TOLERANCE ИЛИ GLU_TESS_WINDING_RULE Значение свойства. Для свойства GLU_TESS_BOUNDARY_ONLY значением может быть GL_TRUE или GL_FALSE. При выборе GL_TRUE отображаются только границы многоугольника (без отверстий). Для свойства GLU_TESS_TOLERANCE значение равно координатному допуску для вершин многоугольника. Для свойства GLU_TESS_WINDING_RULE возможны следующие значения: GLU_TESS_WINDING_NONZERO, GLU_TESS_WINDING_POSITIVE, GLU_TESS_WINDING_NEGATIVE, GLU_TESS_WINDING_ABS_GEQ_TWO ИЛИ GLU_TESS_WINDING_ODD Ничего gluTessBeginPolygon, gluTessEndPolygon, gluTessBeginContour, gluTessEndContour, gluTessCallback, gluTessVertex, gluNewTess, gluDeleteTess gluTessVertex Цель: Включаемый файл: Синтаксис: Описание: Параметры: tobj (тип GLUtesselator*) v (тип GLdouble[3]) data (тип void*) Что возвращает: См. также: Добавить вершину к текущему периметру многоугольника <glu.h> void gluTessVertex(GLUtesselator *tobj, GLdouble v[3], void *data); Добавляет вершину к текущей траектории мозаичного представления. Аргумент data передается функции обратного вызова GL_VERTEX Объект мозаичного представления, используемый при обработке многоугольника Трехмерная вершина Указатель на данные, который передается функции обратного вызова GL_VERTEX Ничего gluTessBeginPolygon, gluTessEndPolygon, gluTessBeginContour, gluTessEndContour
ГЛАВА 11 Все о конвейере: быстрое прохождение геометрии Ричард С. Райт-мл. Из ЭТОЙ ГЛАВЫ ВЫ УЗНАЕТЕ . . . Действие Функция Собрать трехмерный объект из многоугольников Оптимизировать отображение объекта с помощью таблиц отображения Увеличить эффективность хранения и передачи геометрии Уменьшить полосу пропускания данных glBegin/glEnd/glVertex glNewList/glEndList/glCallList glEnableClientState/glDisableClientState/ glVertexPointer/glNormalPointer/ glTexCoordPointer/glColorPointer/ glEdgeFlagPointer/glFogCoordPointer/ glSecondaryColorPointer/glArrayElement/ glDrawArrays/gllnterleavedArrays glDrawElements/glDrawRangeElements/ glMultiDrawElements В предыдущих главах мы рассмотрели основные методы и технологии визуали- зации OpenGL. Используя только эти знания, вы не сможете реализовать лишь неко- торые трехмерные сцены. Теперь же мы обратим внимание на технику и практику визуализации сложных геометрических моделей с использованием свежеприобретен- ных знаний о возможностях OpenGL. Начнем с обзора множества сложных моделей, собираемых из меньших кусочков. Затем введем новые функциональные возможности OpenGL, помогающие быстро перемещать геометрические объекты, и другие команды OpenGL для аппаратного устройства визуализации (видеокарты). В заключение мы введем высокоуровневые возможности, позволяющие убрать из конвейера обработки “дорогие” команды рисо- вания и геометрические объекты, не попадающие в поле зрения. Сборка модели Вообще говоря, все трехмерные объекты, визуализированные с помощью OpenGL, составлены из набора 10 основных примитивов OpenGL: GL_POINTS, GL_LINES, GL_LINE_STRIP, GL_LINE_LOOP, GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_
536 Часть I Классический OpenGL Рис. 11.1. Шестигранный болт, который мы будем моделировать в этой главе TRIANGLE_FAN, GL_QUADS, GL_QUAD_STRIPS ИЛИ GL_POLYGON. При увеличении раз- мера или в случае сложной геометрической структуры управление всеми отдельны- ми вершинами и группами примитивов может быть достаточно непростым, поэтому обычно подход к обработке сложнейших объектов выражается принципом “разделяй и властвуй” — структура разбивается на множество маленьких простых кусочков. Например, снеговик в программе SNOWMAN в главе 10, “Кривые и поверхно- сти”, был просто составлен из сфер, цилиндров, конусов и дисков, упорядоченных с помощью нескольких геометрических преобразований Данную главу мы начнем с другой простой модели, составленной из нескольких частей: металлического бол- та. Хотя такой болт вы, возможно, не найдете ни в одном магазине, с точки зрения учебной цели свою задачу он выполняет. Болт будет иметь шестигранную головку (как типичные стальные болты) По- скольку рассматривается учебный пример, мы упростили нарезку до спиральной по- лосы, проходящей по поверхности болта (в действительности она вырезается в по- верхности). Грубо наша цель изображена на рис. 11 1 Мы намереваемся создать по-отдельности три основных компонента болта — головку, тело и нарезку, — а затем соединить их в конечный объект. Кусочки и части Любую поставленную задачу по программированию можно разбить на меньшие, бо- лее управляемые задачи. Разбивая задачи, мы облегчаем обработку и кодирование меньших частей и получаем возможность повторного использования фрагментов ко- да. Трехмерное моделирование не является исключением; большие, сложные системы обычно создаются из множества меньших, более управляемых кусочков. Как отмечалось ранее, мы решили разбить болт на три части, головку, тело и на- резку. Очевидно, что разбиение задачи упрощает графическое рассмотрение каждого фрагмента, но в то же время мы получаем три объекта, которые можем использовать повторно. В более сложных приложениях моделирования возможность повторно- го использования крайне важна В приложениях автоматизированного проектирова- ния, например, может потребоваться смоделировать множество различных болтов с различными длинами, толщинами и плотностями резьбы. Вместо того чтобы, ска- жем, создавать функцию RenderHead, рисующую головку болта, можно написать функцию, принимающую параметры, которые задают число граней, толщину и диа- метр головки болта
Главе 11 Все о конвейере быстрое прохождение геометрии 537 Рис. 11.2. Примитивный контур головки болта Кроме того, каждый фрагмент болта мы смоделируем в координатах, наиболее удобных для описания объекта. Довольно часто отдельные объекты или сетки моде- лируются относительно начала координат, а затем с помощью трансляции и поворо- та переводятся в нужное положение. Позже, собирая конечный объект, мы сможем транслировать компоненты, поворачивать их и даже масштабировать, если это потре- буется для сборки сложного объекта. Мы поступаем так по двум причинам. Первая из них заключается в том, что можно выполнять повороты вокруг геометрическо- го центра объекта, а не вокруг произвольной точки, смещенной к одной стороне (в зависимости от прихоти автора модели) Вторая причина станет очевидной позже. Пока же достаточно сказать, что полезно знать расстояние от любой точки до центра объекта, поскольку это это облегчает расчет трехмерных границ объекта. Головка Головка болта имеет шесть плоских граней и плоский верх и низ. Данный объ- ект можно построить с помощью двух шестиугольников, представляющих верхнюю и нижнюю стороны головки, и набора четырехугольников по краям, образующим стороны. Для рисования головки с минимальным числом вершин можно задейство- вать функцию GL_QUAD_STRIP, однако, как отмечалось в главе 6, “Подробнее о цвете и материалах”, такой подход потребует, чтобы все ребра имели общую нормаль к поверхности Используя отдельные четырехугольники (GL_QUADS), мы можем со- кратить объем работы, передавая OpenGL дополнительную вершину на сторону (в противоположность передаче двух треугольников) Для небольших моделей, подоб- ных этой, разница пренебрежимо мала. Для больших моделей такой шаг означает существенную экономию Рассмотрим рис 11.2, где показано построение головки болта с помощью веера треугольников и прямоугольников. В качестве верхнего и нижнего участков головки болта использовался веер треугольников с шестью элементами. Затем с помощью четырехугольников мы создали боковые грани головки. Всего для рисования головки болта мы использовали 18 примитивов: по 6 тре- угольников (или один веер) сверху и снизу плюс шесть прямоугольников, образующих бока головки. В листинге 11.1 приведены функции, визуализирующие головку болта. На рис. 11.3 показано, на что похожа эта головка при визуализации ее как самостоя- тельного объекта (см. программу BOLT в папке данной главы на компакт-диске). Код содержит только функции, которые мы уже рассматривали, но он гораздо важнее, чем любой из примеров, разобранных в предыдущих главах. Обратите внимание, что начало системы координат является центром головки болта.
538 Часть I. Классический OpenGL Рис. 11.3. Результат выполнения программы рисования головки Листинг 11.1. Код визуализации головки болта /////////////////////////////////////////////////////////////////// // Создается головка болта void RenderHead(void) { float х,у,angle; // Рассчитываются // положения float height - 25.Of; // Толщина головки float diameter = 30.Of; // Диаметр головки GLTVector3 vNormal,vCorners[4]; // Память для хранения // вершин и нормалей float step = (3.1415f/3.0f); // шаг = 1/6 часть // окружности = // шестиугольник // Устанавливается цвет материала для головки болта glColor3f(O.Of, O.Of, 0.7f); // Начинается новый веер треугольников для охвата // верхней поверхности glFrontFace(GL_CCW); glBegin(GL_TRIANGLE_FAN); // Все нормали для верхней части болта направлены // прямо вверх оси z
Глава 11 Все о конвейере' быстрое прохождение геометрии 539 glNormal3f(O.Of, O.Of, l.Of); // Центр веера находится в начале координат glVertex3f(O.Of, O.Of, height/2.Of); // Делим окружность на шесть частей и начинаем расставлять // точки, определяющие веер. Похоже, нам придется обходить // этот веер в обратном порядке. Из-за этого обращается // обход того, что в противном случае было при примитивом // с обходом по часовой стрелке. Не меняйте состояние // с помощью функции gIFrontFace()! Первая и последняя // вершины замыкают веер glVertex3f(O.Of, diameter, height/2.Of); for(angle = (2.0f*3.1415f)-step; angle >= 0; angle — step) { // Рассчитываются координаты x и у следующей вершины х = diameter*(float)sin(angle); у = diameter*(float)cos(angle); // Задается следующая вершина для веера треугольников glVertex3f(х, у, height/2.Of); } // Последняя вершина замыкает веер glVertex3f(0.Of, diameter, height/2.Of); // Завершили рисование веера, покрывающего верхнюю часть glEnd(); // Начинаем новый веер треугольников, покрывающий нижнюю часть glBegin(GL_TRIANGLE_FAN); // Нормали нижних точек указывают вниз по отрицательному // направлению оси z glNormal3f(O.Of, O.Of, -l.Of); // Центр веера находится в начале координат glVertex3f(O.Of, O.Of, -height/2.Of); // Делим окружность на шесть частей и начинаем расставлять // точки, определяющие веер for(angle = O.Of; angle < (2.Of*3.1415f); angle += step) { // Рассчитываются координаты x и у следующей вершины х = diameter*(float)sin(angle) ; у = diameter*(float)cos(angle); // Задается следующая вершина для веера треугольников glVertex3f(х, у, -height/2.Of); ) // Последняя вершина замыкает веер glVertex3f(O.Of, diameter, -height/2.Of); // Завершили рисование веера, покрывающего нижнюю часть glEnd(); // Строим стороны из треугольников (каждая сторона - это два // треугольника, упорядоченных так, чтобы формировать // четырехугольник) glBegin(GL_QUADS); // Идем по кругу и рисуем стороны for(angle = O.Of; angle < (2.0f*3.1415f); angle += step) { // Рассчитываем координаты x и у следующей точки // шестиугольника
Часть I Классический OpenGL х = diameter*(float)sin(angle); у = diameter*(float)cos(angle); // Начинаем снизу головки vCorners[0][0] = x; vCorners[0][1] = y; vCorners[0][2] = -height/2.Of; // Заходит на верх головки vCorners[1][0] = х; vCorners[1][1] = у; vCorners[1][2] = height/2.Of; // Рассчитываем следующую точку шестиугольника х = diameter*(float)sin(angle+step); у = diameter*(float)cos(angle+step); // Перед тем как продолжать, убеждаемся, что мы // еще не закончили if(angle+step < 3.1415*2.0) // Если мы закончили, просто замыкаем веер // в известной точке vCorners[2][0] = х; vCorners[2][1] = у; vCorners[2][2] = height/2.0f; vCorners[3][0] = х; vCorners[3][1] = у; vCorners[3][2] = -height/2.Of; } else { // Мы не закончили, ставим точки вверху // и внизу головки vCorners[2][0] = O.Of; vCorners[2][1] = diameter; vCorners[2][2] = height/2.0f; vCorners[3][0] = O.Of; vCorners[3][1] = diameter; vCorners[3][2] = -height/2.Of; // Все векторы нормали всей грани указывают // в одном направлении gltGetNormalVector(vCorners[0], vCorners[1], vCorners[2], vNormal); gINormal3fv(vNormal) ; // Задаем по-отдельности прямоугольники, // лежащие рядом друг с другом glVertex3fv(vCorners[0]); glVertex3fv(vCorners[1]); glVertex3fv(vCorners[2]); glVertex3fv(vCorners[3]); ) glEnd();
Глава 11. Все о конвейере: быстрое прохождение геометрии 541 Рис. 11.4. Тело болта, визуализированное в форме ленты четырехугольников, обернутой вокруг основы Тело Тело болта представляет собой просто цилиндр с дном. Мы составляем цилиндр, рисуя точки с координатами (х, z) по кругу, а затем указывая для этих точек две ко- ординаты у, и получаем многоугольники, аппроксимирующие тело цилиндра. На этот раз тело целиком формируется лентой четырехугольников, поскольку каждая смеж- ная пара четырехугольников может иметь общую нормаль, необходимую для гладкого затенения (см. главу 5, “Цвет, материалы и освещение: основы”). Визуализированный цилиндр представлен на рис. 11.4. С помощью ленты треугольников создается также дно тела (как выше мы рисо- вали дно головки болта). Однако теперь из-за меньшего шага по кругу мы получаем меньшие плоские грани, благодаря которым тело цилиндра лучше аппроксимирует гладкую кривую. Величина шага также соответствует той, что применяется в образу- ющей тела, поэтому они стыкуются гладко. Код, генерирующий описанный цилиндр, приводится в листинге 11.2. Обрати- те внимание на то, что нормали четырехугольников не рассчитываются с помощью вершин четырехугольников. Обычно указывается, что нормаль одинакова для всех вершин, но в данном случае мы нарушаем эту традицию и задаем новую нормаль для каждой вершины. Поскольку мы моделируем криволинейную поверхность, нор- маль, заданная для любой вершины, является нормалью к истинной кривой. (Если вы не понимаете сказанного, вернитесь к разделу главы 5, посвященному нормалям и эффектам освещения.)
542 Часть I Классический OpenGL Листинг 11.2. Код визуализации тела болта /////////////////////////////////////////////////////////////////// // Создаем тело болта как цилиндр с одним закрытым основанием void RenderShaft(void) { float x,z,angle; // Применяется для расчета // стенки цилиндра float height = 75.Of; // Высота цилиндра float diameter = 20.Of; // Диаметр цилиндра GLTVector3 vNormal,vCorners[2]; // Память для расчетов вершин float step = (3.1415f/50.0f); 11 Стенка цилиндра // аппроксимируется 100 // плоскими сегментами // Устанавливается цвет материала головки болта glColor3f(0.Of, O.Of, 0.7f); // Вначале собираем стенку из 100 четырехугольников, // сформированную стыковкой соседних четырехугольников glFrontFace(GL_CCW); glBegin(GL_QUAD_STRIP) ; // Обходим по кругу и рисуем стороны for(angle = (2.0f*3.1415f); angle > O.Of; angle -= step) { // Рассчитываем координаты x и у первой вершины х = diameter*(float)sin(angle); z = diameter*(float)cos(angle); // Получаем координаты этой точки и извлекаем // длину цилиндра vCorners[0][0] = х; vCorners[0][1] = -height/2.Of; vCorners[0][2] = z; vCorners[1][0] = x; vCorners[1][1] = height/2.Of; vCorners[1][2] = z; // Вместо использования фактической нормали к реальному // плоскому участку применяем нормаль, какой она бы // была, если бы поверхность в действительности была // криволинейной. Поскольку цилиндр идет вдоль оси Y, // нормаль направлена от оси Y непосредственно через // вершину. Следовательно вершину можно использовать // как нормаль при условии, что мы сперва сократим ее //до единичной длины и будем предполагать, что // компонент у равен нулю. vNormal[0] = vCorners[1][0] ; vNormal[1] = O.Of; vNormal[2] = vCorners[1][2]; // Сокращаем нормаль до единичной длины и задаем ее // для этой точки gltNormalizeVector(vNormal); glNormal3fv(vNormal); glVertex3fv(vCorners[0]); glVertex3fv(vCorners[1]);
Гпава 11 Все о конвейере, быстрое прохождение геометрии 543 ) // Убеждаемся, что в фигуре нет щелей, дотягивая последний // четырехугольник до первой точки glVertex3f(diameter*(float)sin(2.0f*3.1415f), -height/2.Of, diameter*(float)cos(2.0f*3.1415f)) ; glVertex3f(diameter*(float)sin(2.Of*3.1415f), height/2.Of, diameter*(float)cos(2.0f*3.1415f)); glEnd(); // Закончили с боками цилиндра // Начинаем новый веер треугольников, охватывающий нижнюю // часть цилиндра glBegin(GL_TRIANGLE_FAN); // Нормаль указывает по отрицательному направлению оси Y glNormal3f(O.Of, -l.Of, O.Of); // Центр веера находится в начале координат glVertex3f(0.Of, -height/2.Of, O.Of); // Проходим по круг с таким же шагом, как и при проходе // стенки цилиндра for(angle = (2.0f*3.1415f); angle > O.Of; angle -= step) { // Рассчитываем координаты x и у следующей вершины х = diameter*(float)sin(angle); z = diameter*(float)cos(angle); // Задаем следующую вершину для веера треугольников glVertex3f(х, -height/2.Of, z); ) // Проверяем, чтобы цикл замкнулся, задавая начальную // вершину в той же дуге, что и последняя glVertex3f(diameter*(float)sin(2.0f*3.1415f), -height/2.Of, diameter*(float)cos(2.0f*3.1415f)); glEnd(); 1 Резьба Резьба — это наиболее сложная часть болта. Она состоит из двух плоскостей V- образной формы, проходящих по спирали по телу болта. На рис. 11.5 показана ви- зуализированная резьба, а в листинге 11.3 приводится код OpenGL, использованный для создания этой формы. Листинг 11.3. Код визуализации резьбы /////////////////////////////////////////////////////////////////// // Спиральная резьба void RenderThread(void) { float х,у,z,angle; float height = 75.Of; float diameter = 20.Of; // Рассчитываются координаты 11 -л шаг по углу // Высота резьбы // Диаметр резьбы
544 Часть I. Классический OpenGL Рис. 11.5. Резьба, накручивающаяся на тело болта (само тело не показано) GLTVector3 vNormal, vCorners[4]; // Память для хранения // нормалей и углов float step = (3.1415f/32.Of); // Один поворот float revolutions = 7.Of; // Сколько оборотов вокруг // тела float threadwidth = 2.Of; // Насколько широкая резьба float threadThick = 3.0f; // Насколько тонкая резьба float zstep = .125f; // Насколько далеко // перемещается резьба вверх //по оси Z при рисовании // следующего сегмента // Устанавливается цвет материала головки болта glColor3f(0.Of, O.Of, 0.4f); z = -height/2.Of+2.Of; // Начальная точка расположен // практически на конце // Идем по кругу и рисуем стороны, пока не завершим оборот for(angle = O.Of; angle < GLT_PI * 2.Of *revolutions; angle += step) { // Рассчитываем координаты x и у следующей вершины х = diameter*(float)sin(angle); у = diameter*(float)cos(angle); // Записываем следующую по телу вершину vCorners[0][0] = х; vCorners[0][1] = у;
Глава 11 Все о конвейере быстрое прохождение геометрии 545 vCorners[0][2] = z; // Рассчитываем точку относительно тела х = (diameter+threadWidth)*(float)sin(angle); у = (diameter+threadWidth)*(float)cos(angle); vCorners[1][0] = x; vCorners[1][1] = y; vCorners[1][2] = z; // Рассчитываем следующую точку относительно тела х = (diameter+threadWidth)*(float)sin(angle+step); у = (diameter+threadWidth)*(float)cos(angle+step); vCorners[2][0] = x; vCorners[2][1] = y; vCorners[2][2] = z + zstep; // Рассчитываем следующую точку относительно тела х = (diameter)*(float)sin(angle+step); у = (diameter)*(float)cos(angle+step); vCorners[3][0] = x; vCorners[3][1] = y; vCorners[3][2] = z+ zstep; // Мы будем использовать треугольники, поэтому // многоугольники с обходом против часовой стрелки // направлены наружу glFrontFace(GL_CCW); glBegin(GL_TRIANGLES); // Начало верхнего участка резьбы // Рассчитываем для этого сегмента нормаль gltGetNormalVector(vCorners[0], vCorners[1], vCorners[2], vNormal); glNormal3fv(vNormal); // Рисуем два треугольника, охватывающих данную область glVertex3fv(vCorners[0]); glVertex3fv(vCorners[1]); glVertex3fv(vCorners[2]); glVertex3fv(vCorners[2]); glVertex3fv(vCorners[3]); glVertex3fv(vCorners[0]); glEnd(); // Перемещаем край вдоль тела немного вверх по оси z, // представляя нижнюю часть резьбы vCorners[0][2] += threadThick; vCorners[3][2] += threadThick; // Пересчитываем нормаль, поскольку точки изменились, // на этот раз она указывает в противоположном направлении, // поэтому обращаем ее gltGetNormalVector(vCorners[0], vCorners[1], vCorners[2], vNormal); vNormal[0] = -vNormal[0]; vNormal[1] = -vNormal[1]; vNormal[2] = -vNormal[2]; // Для обратной стороны резьбы переключаемся на режим, // когда элементы с обходом по часовой стрелке // смотрят наружу glFrontFace(GL_CW);
546 Часть I. Классический OpenGL Рис. 11.6. Результат выполнения программы построения болта // Рисуем два треугольника glBegin(GL_TRIANGLES); glNormal3fv(vNormal); glVertex3fv(vCorners[0]); glVertex3fv(vCorners[1]); glVertex3fv(vCorners[2]); glVertex3fv(vCorners[2]); glVertex3fv(vCorners[3]); glVertex3fv(vCorners[0]); glEnd(); // Постепенно увеличиваем координату Z z += zstep; } } Собираем все вместе Чтобы собрать болт, мы рисуем все три фрагмента в нужных местах. Все составные части должным образом транслируются и поворачиваются. Тело не модифицируется вообще, а резьбу нужно повернуть так, чтобы согласовать с телом. Наконец, чтобы поместить в требуемое место головку болта, ее нужно повернуть и транслировать. Код визуализации, представляющий манипуляцию с тремя компонентами болта и их визуализацию, приведен в листинге 11.4, а на рис. 11.6 показан конечный результат выполнения программы построения болта.
Глава 11 Все о конвейере быстрое прохождение геометрии 547 Листинг 11.4. Код визуализации всех частей болта // Вызывается для рисования сцены void RenderScene(void) { // Очищает окно текущим цветом очистки glClear(GL_COLOR_BUFFER_BIT I GL_DEPTH_BUFFER_BIT) ; // Записывается состояние матрицы glMatrixMode(GL_MODELVIEW); glPushMatrix(); // Поворот относительно осей х и у glRotatef(xRot, l.Of, O.Of, O.Of); glRotatef(yRot, O.Of, O.Of, l.Of); // Визуализируем только резьбу болта Rendershaft(); glPushMatrix(); glRotatef(-90.Of, l.Of, O.Of, O.Of); RenderThread(); glTranslatef(O.Of,O.Of,45.Of); RenderHead(); glPopMatrix(); glPopMatrix(); // Переключаем буферы glutSwapBuffers(); Итак, зачем же нужна вся эта “гимнастика” с выстраиванием всех элементов? Мы легко могли бы изменить геометрию так, чтобы все фрагменты рисовались в их текущем положении. Смысл демонстрации заключается в том, что множество объектов, смоделированных относительно собственных локальных начал координат, достаточно легко упорядочить на сцене в единой целое, создавая более сложную мо- дель или среду. Этот основной принцип и описанная техника сформируют основу для создания собственных графов сцены (см главу 1, “Введение в трехмерную графику и OpenGL”) или виртуальных сред В различных главах мы реализовывали этот прин- цип на практике, используя различные варианты мира сфер В сложной и постоянной (записанной на диск) трехмерной среде положение и ориентацию каждого фрагмента можно записать отдельно, используя структуру GLTFrame из библиотеки glTools. Таблицы отображений Программа BOLT дает разумное представление металлического болта Этот болт, со- стоящий из более чем 1 700 треугольников, с точки зрения геометрии является на данный момент самым сложным вручную сгенерированным объектом Такое чис- ло треугольников несравнимо с наибольшим числом многоугольников, которые вам встретятся при составлении больших сцен и сложных объектов Дешевые новейшие карты с ускорителями трехмерной графики генерируют миллионы треугольников в се- кунду Поэтому одной из задач данной главы является введение более эффективных способов хранения и визуализации геометрии, одним из которых таких являются таблицы отображений OpenGL
548 Часть I Классический OpenGL ОПТИМИЗАЦИЯ ПРОЦЕССА ВИЗУАЛИЗАЦИИ МОДЕЛИ В примере BOLT мы построили модель, с помощью математики представив кри- вые и поверхности в форме уравнений Используя в этих уравнениях точки, мы построили ряд треугольников, формирующих искомую форму Хотя головка и тело болта были простыми, код для создания резьбы мог бы быть устрашающим. Из-за множества факторов создание моделей или геометрических объектов бывает очень длительным процессом Возможно (как в примере выше), вам придется вычислить все вершины и нормали Возможно, понадобится считать эти значения с диска или извлечь по сети Исходя из этого, можно сказать, что производительность визуализа- ции снижается процессами, связанными с созданием геометрии OpenGL предлагает два решения этих проблем, каждое со своими преимуществами Эти решения — таб- лицы отображений и массивы вершин — и рассматриваются далее в этой главе Пакетная обработка данных OpenGL описывается как программный интерфейс к графическому аппаратному обес- печению. Поэтому можно подумать, что команды OpenGL каким-то образом преоб- разуются драйвером в специфические аппаратные команды или операторы, а затем передаются графической карте для немедленного выполнения Если вы действитель- но так подумали, то почти угадали Большинство команд визуализации OpenGL по сути преобразовываются в аппаратно-зависимые команды, однако эти команды не передаются немедленно аппаратному обеспечению Они накапливаются в локальном буфере, пока не будет превышен некоторый порог, и в этот момент они сбрасываются аппаратному обеспечению Основная причина такого поведения заключается в том, что “путешествия” к гра- фическому аппаратному обеспечению занимают много времени, по крайней мере в компьютерном масштабе Человеку данный процесс может казаться очень быстрым, но для процессора, работающего с частотой несколько миллиардов циклов в секунду, это подобно ожиданию огромного круизного лайнера с целью путешествия из Север- ной Америки в Европу и обратно. Очевидно, вы не будете загружать одного пассажира на лайнер и ждать возвращения лайнера, чтобы отправить второго Если вам нужно послать в Европу большое число людей, вы вместите столько пассажиров, сколько сможете' Данная аналогия весьма точна’ гораздо быстрее оптом послать большой объем данных (с определенными ограничениями) через системную шину к аппарат- ному обеспечению, чем разбивать их на множество потоков небольших фрагментов Развивая аналогию, можно сказать, что неыобязательно ждать возвращения пер- вого лайнера, чтобы начать заполнять пассажирами следующий Передача данных в буфер графического аппаратного обеспечения (процесс, именуемый сбрасывани- ем — flushing) является асинхронной операцией Это означает, что процессор может переключаться на другие задачи, не ожидая завершения передачи пакета послан- ных команд визуализации По сути, аппаратное обеспечение может визуализировать предоставленный набор команд в то время, как процессор занят вызовом нового на- бора команд для следующего графического изображения (которое обычно называется кадром, если вы занимаетесь созданием анимации) Подобная парапелъная рабо- та графического аппаратного обеспечения и процессора крайне эффективна и часто пользуется спросом у программистов, озабоченных вопросами производительности.
Глава 11 Все о конвейере быстрое прохождение геометрии 549 Сброс текущего пакета команд визуализации инициируется тремя событиями Первое происходит при заполнении буфера команд драйвера. Вы не имеете доступа к этому буферу и не можете контролировать его размер Настройку размера и дру- гих характеристик этого буфера проделывают поставщики аппаратного обеспечения, ориентируясь на то, чтобы он хорошо работал е их устройствами. Сброс также про- исходит при переключении буферов Переключение буферов не может произойти до завершения выполнения всех команд, ожидающих обработки, поэтому вначале ини- циируется сброс, после чего выполняется команда переключения буферов Переклю- чение буферов является для драйвера очевидным показателем завершения работы с текущей сценой и сигналом к визуализации всех команд Однако если вы выполняете визуализацию с одним буфером, OpenGL никак не может определить, закончили ли вы передавать команды, тс когда аппаратному обеспечению нужно посылать пакет команд для выполнения Чтобы облегчить этот процесс, можно вызвать следующую функцию, вручную инициирующую сброс void glFlush(void); Однако некоторые команды OpenGL не заносятся в буфер для последующего вы- полнения. Например, glReadPixels и glDrawPixels непосредственно обращаются к буферу кадров и считывают или записывают данные Следовательно, необходимо иметь возможность не только сбрасывать буфер, но и ожидать, пока не будут вы- полнены все команды перед вызовом одной из этих функций. Поэтому указанные команды не следует помещать в таблицу отображения Например, если вы визуали- зируете изображение, которое хотите считывать с помощью функции glReadPixels, вы можете считать буфер кадров даже до сброса пакета команд Чтобы одновременно инициировать сброс и подождать завершения всех предыдущих команд визуализации, вызывается следующая функция void glFinish(void); Предварительно обработанные пакеты При вызове команд OpenGL выполняется вполне логичная последовательность дей- ствий. Команды компилируются, или преобразовываются, с высокоуровневого языка команд OpenGL в низкоуровневые аппаратные команды, которые понимает аппарат- ное обеспечение Для сложной геометрии или просто при больших объемах данных о вершинах этот процесс выполняется тысячи раз только для того, чтобы нарисовать на экране единственное изображение. Часто геометрия или другие данные OpenGL остаются без изменений при смене кадров Чтобы устранить ненужные повторяющи- еся служебные издержки, порцию данных из буфера команд, которая выполняет неко- торую повторяющуюся задачу визуализации, принято записывать Позже эту порцию данных можно целиком скопировать в буфер команд, сэкономив множество вызовов функций и работу по компиляции, выполненную для создания данных OpenGL предлагает средство создания предварительно обработанного набора ко- манд OpenGL (порции данных), который можно затем скопировать в буфер команд для быстрого выполнения Такой предварительно обработанный список команд на- зывается табпщей отображения (display list), а создание одного или нескольких таблиц является простым и понятным процессом Как примитивы OpenGL обособля-
550 Часть I Классический OpenGL лись с помощью пары команд glBegin/glEnd, таблицы отображений обособляются с помощью пары glNewList/glEndList Таблица отображения именуется согласно за- панному целочисленному значению Типичная схема создания таблицы отображения иллюстрируется в приведенном ниже коде glNewList(Consigned integer name>,GL_COMPILF); // Какой-то код OpenGL glEndList(), Теперь именованная таблица отображения содержи! все команды визуализации OpcnGL, расположенные между вызовами функции gLNewList и glEndList Пара- метр GL_COMPILE сообщает OpcnGL скомпилировать таблицу, но пока нс выполнять ее Также можно задать параметр GL_COMPIL£_AND_EXECOTE, чюбы одновременно построить и визуализировать таблицу отображения Обычно, впрочем, ыблицы отоб- ражения создаются в процессе инициализации программы (параметр GL_COMPILE) и выполняются позже в ходе визуализации Именем таблицы отображения может быть любое целое число без знака Если вы используете то же самое значение дважды, вторая габ ища отображения заменил предыдущую Поэтому удобно иметь определенный механизм, нс дающии повторно использовать одну и ту же таблицу отображения бо юс одного раза Особенно эго полезно при внедрении библиотек кола, написанных кем-то, кто moi использовать таблицы отображений и выбрать для них те же имена, что и вы OpcnGL предлагает встроенную поддержку вы юления уникальных имен таб- лиц отображения. Приведенная ниже функция возвращает первое уникальное целое число-идентификатор таблицы отображения GLuint glGenLists(GLsizei range); Имена таблиц отображения резервируются последовательно, указанная выше функция возвращает первое из них Эту функцию можно вызывт ь столько ра з, сколь- ко требуется, и для такого количества таблиц отображений за раз, сколько 1рсбустся Существует и другая функция, освобождающая имена таблиц отображения и очища- ющая память, выделенную для хранения этих таблиц void glDeleteLists(GLuint list, GLsizei range), Таблица отображения, содержащая побос число предварительно скомпилирован- ных операций OpcnGL. выполняется с помощью такой команды void glCallList(GLuint list), Кроме того, можно выполнить целый массив габ шц отображения, вызвав следу- ющую команду' void glCallLists(GLsizei п, GLenum type, const GLvoid *lists), Первый параметр задаст число таблиц отображения, содержащихся в мас- сиве lists Второй параметр содержит тип данных массива, обычно это GL_ UNSIGNED_BYTE
Глава 11 Все о конвейере быстрое прохождение геометрии 551 Разъяснения по поводу таблиц отображения Стоит отмстить несколько важных моментов, касающихся таблиц отображений Хотя в большинстве реализаций таблица отображения должна улучшать производитель- ность, степень ее полезности может меняться в зависимости от усилий, вложенных поставщиком в оптимизацию создания и выполнения таблиц отображений Впрочем, таблицы отображения очень редко не дают значительного улучшения производитель- ности, и на них обычно полагаются во всех приложениях, использующих OpenGL Как правило, таблицы отображения удобны при создании предварительно ском- пилированных списков команд OpenGL, особенно если в них фигурирует изменение состояния (например, включение и выключение освещения) Если вы вначале не создали имя таблицы отображения с помощью функции glGenLists, может полу- читься так, что таблица отображения будет работать в одних реализациях и не рабо- тать в других В частности, некоторые команды просто не имеют смысла в таблице отображения Например, в таблице отображения бессмысленно считывание буфера кадров в указатель с помощью glReadPixels, при вызове функции glTex!mage2D в таблице отображения записываются данные об исходном изображении, после чего следует команда, загружающая данные изображения в качестве текстуры (текстуры, записанные таким образом, могут занимать вдвое больше памяти) Тем не менее таблицы отображений великолепно проявляют себя в предварительно скомпилиро- ванных списках геометрических объектов, когда объекты текстуры связаны внутри или снаружи таблиц отображения Наконец, таблицы отображений не могут содер- жать вызовы функций, создающих таблицы отображении Можно сделать так, чтобы одна таблица отображений вызывала другую, но нельзя поместить вызовы функций glNewLists/glEndList в таблицу отображения Преобразование кода в таблицы отображения Чтобы преобразовать программу BOLT так, чтобы в ней использовались таблицы отображений, потребуется всего лишь несколько дополнительных строк кода Во- первых, добавляются три переменные, содержащие идентификаторы таблиц отобра- жения для трех фрагментов болта // Идентификаторы таблицы отображения GLuint headList, shaftList, threadList; Далее в функции SetupRC мы затребовали три имени таблиц отображения и при- своили их переменным таблиц отображения // Получаем имена таблиц отображения headList = glGenLists(3); shaftList = headList + 1; threadList = headList + 2; После этого добавляем три строчки кода, генерирующие три таблицы отображе- ния Каждая таблица просто вызывает функцию, изображающую соответствующий геометрический объект. // Предварительно строим таблицы отображения glNewList(headList, GL_COMPILE); RenderHead();
552 Часть I Классический OpenGL glEndList(); glNewList(shaftList, GL_COMPILE); RenderShaft(); glEndList(); glNewList(threadList, GL_COMPILE); RenderThread(); glEndList(); Наконец, в функции Render мы просто заменяем вызовы функций элементов болта вызовами соответствующих таблиц отображения // Визуализируем только резьбу болта //RenderShaft(); glCallList(shaftList); glPushMatrix(); glRotatef(-90.Of, l.Of, O.Of, O.Of); //RenderThread(); glCallList(threadList); glTranslatef(O.Of,O.Of,45.Of); //RenderHead(); glCallList(headList); В данном примере созданы три таблицы отображения, по одной для каждого компонента болта Кроме того, можно поместить весь болт в одну таблицу отобра- жения или даже создать четвертую таблицу, содержащую вызовы остальных трех Полная версия кода с таблицами отображения приведена в программе BOLTDL на компакт-диске Измерение производительности Такого простого примера, как BOLT, недостаточно, чтобы продемонстрировать по- вышение производительности за счет использования таблиц отображения Чтобы по- казать преимущества использования таблиц отображения (или массивов вершин), нужны две вещи Во-первых, требуется программа с более сложными юометри- чсскими объектами Во-вторых, нужен способ измерения производительности, от- личающийся от субъективного восприятия на уровне “насколько быстрым кажет- ся отображение анимации” В большинство глав данной книги включен пример “мир сфер”, демонстрирующий трехмерную среду, улучшенную с использованием техник, представленных в данной главе С позиции примеров, приводимых в книге, мир сфер содержит много гео- метрических элементов Земля и тор имеют сильно мозаичную структуру, плоскость “населяет” множество сфер с большим разрешением Кроме того, использованный алгоритм плоских теней требует, чтобы практически вся геометрия обрабатывалась дважды (один раз объекты, второй раз лени) Очевидно, на данной программе пре- имущества применения таблиц отображения должны быль заметными
Глава 11 Все о конвейере быстрое прохождение геометрии 553 Показателем производительност визуализации является мера того, сколько кад- ров (отдельных изображений) можно визуализировать за секунду Во многих играх и тестовых программах сеть возможность отображения частоты смены кадров в фор- ме показателя fps (frames per second — кадров в секунду) В очередной версии про- граммы SPHEREWORLD (папка данной главы на компакт-диске) на жран выводится частота смены кадров и добавлена возможность использования таблиц отображения Разница, выраженная в кадрах в секунду, даст оценку повышения производительно- сти, получаемого при введении в приложение таблиц отображения Частота смены кадров представляет собозт число кадров, визуализированных за некоторый промежуток времени, деленное на прошедшее время Подсчет операций переключения буферов достаточно прост, а вот подсчет секунд сложнее, чем кажет- ся К сожалению, точный подсчет времени является нс очень переносимым свой- ством операционной системы и аппаратной платформы Кроме того, хронометриче- ские функции обычно плохо задокументированы, и на их основе могут выводиться неправильные характеристики производительности Например, разрешение большин- ства стандартных функций времени выполнения языка С, которые могуз возвращать время с точностью до ближайшей! миллисекунды, часто измеряется очень большим числом миллисекунд Отнимая одно значение времени от друюго, вы должны полу- чить промежуток времени между событиями, но иногда минимальное время, которое вы можете измерить, составляет 1/20 с При таком плохом разрешении пршрамма иногда успевает визуализировать несколько кадров и несколько раз переключить бу- феры, нс наблюдая изменения прошедшего времени1 Библиотека glTools содержит структуру данных и две работающих со временем функции, которые изолируюI зависимости от операционной системы и дают доста- точно хорошее разрешение по времени На ПК это разрешение обычно составляет порядка миллионных долей секунды, а на Macintosh вы получите разрешение по крайней мерс 10 мс Названные функции дсйсизуют подобно секундомеру Струк- тура данных, содержащая последнее дискретизованное время, определяется следую- щим образом GLTStopwatch frameTimer; Приведенные ниже функции сбрасывают секундомер и считывают число секунд (в виде значения с плавающей запятой), прошедших с момента последнего сбро- са секундомера void gltStopwatchReset(GLTStopwatch ★pTimer); float gltStopwatchRead(GLTStopwatch *pTimer); Чтобы считываемые значения времени имели смысл, требуется хотя бы раз сбро- ешь секундомер Лучший пример Поскольку SPHEREWORLD является довольно длинной программой, которая уже приводилась в предыдущих павах, в листинге 11 5 показана только новая функция RenderScene, содержащая нсско 1ько заслуживающих внимания изменений
554 Часть I Классический OpenGL Листинг 11,5. Основная функция визуализации программы SPHEREWORLD void RenderScene(void) { static int iFrames =0; // Счетчик кадров static GLTStopwatch frameTimer; // Время визуализации /I Первый сброс секундомера if(iFrames == 0) { gltStopwatchReset(&frameTimer); iFrames++; ) // Очищает окно текущим цветом очистки glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glPushMatrix(); gltApplyCameraTransformf &frameCamera); // Перед какими-либо преобразованиями помещается // источник света glLightfv(GL_LIGHT0, GL_POSITION, fLightPos); // Рисуем землю glColor3f(l.Of, l.Of, l.Of); if(iMethod == 0) DrawGround(); else glCallList(groundList); // Вначале рисуем тени glDisable(GL_DEPTH_TEST); glDisable(GL_LIGHTING); glDisable(GL_TEXTURE_2D); glEnable(GLJBLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_STENCIL_TEST); glPushMatrix(); glMultMatrixf(mShadowMatrix); Drawinhabitants(1) ; glPopMatrix)); glDisable(GL_STENCIL_TEST); glDisable(GLJ3LEND) ; glEnable(GL_LIGHTING); glEnable(GL_TEXTURE_2D); glEnable(GL_DEPTH_TEST); // Рисуем жителей Drawinhabitants(0); glPopMatrix(); // Переключает буферы glutSwapBuffers(); // Увеличиваем счетчик кадров iFrames++; 11 Периодический расчет частоты смены кадров if(iFrames == 101) { float fps; char cBuffer[64];
Глава 11 Все о конвейере быстрое прохождение геометрии 555 fps = 100.Of / gltStopwatcnRead(&frameTimer); iffiMetnod == 0) sprintf(eBuffer, "OpenGL SphereWorld without Display Lists % If fps", fps); else sprintf(cBuffer, "OpenGL SphereWorld with Display Lists %.If fps", fps); glutSetWindowTitle(cBuffer); gltStopwatchReset(&frameTimer); iFrames = 1; } // Повторяем все glutPostRedisplay(), Итак, есть две статические переменные, которые будут хранить свое значения между вызовами двух функции Тин переменных — счетчик кадров и таймер кадров static int iFrames = 0; // Счетчик кадров static GLTStopwatch frameTimer; // Время визуализации Поскольку перед использованием таймер нужно инициализировать, счетчик кад- ров пепольз}С1СЯ как сита п.ная метка При первой визуализации сцены переменная iFrames содержи! io и.ко значение 0, поэтому в это время мы выполняем первона- чальный сброс сскун юмора // Первый сброс секундомера if(iFrames == 0) { gltStopwatchReset(&frameTimer), iFrames++; Далее мы визуализируем сцену почти так ж'с, как обычно Обратите внимание на изменение программы в фрагменте, рисующем землю if(iMethod == 0) DrawGround(); else glCallList(groundList); Переменная iMethod усщнавливасгся равной 0 при выборе из всплывающего ме- ню опции Without Display Lists и 1 — при выборе With Display Lists Таб- ища оюбражения i оперируется в функции SetupRC для земли, тора и сферы Подоб- ным образом, функция Drawinhabitants отвечает за переключение между таблицей оюбражения и стаидаршым вызовом функции и управляется значением переменной iMethod После переключения буферов просто увеличивается счетчик кадров // Переключает буферы glutSwapBuffers( ) ; // Увеличивается счетчик кадров 1F rames + +;
556 Часть I Классический OpenGL Частота смены кадров не рассчитывается для каждого отдельного кадра Вместо этого мы подсчитываем некоторое количество кадров, а затем делим общее время на это число При таком подходе убивают двух зайцев Во-первых, разрешение по време- ни не очень большое, и увеличение промежутка времени помогает это замаскировать Во-вторых, процесс расчета и отображения частоты смены кадров также требует вре- мени и замедляет визуализацию, те. неблагоприятно влияет на точность измерения. Когда счетчик кадров достиг 101, визуализировано 100 кадров (напомним, что мы начинаем с первого, а нс нулевого кадра). Поэтому мы создаем строковый буфер, заполняем его рассчитанной частотой смены кадров, а затем просто выталкиваем его содержимое в строке заголовка окна Наконец, нужно обновить таймер и вернуть счетчику значение 1 // Периодический расчет частоты смены кадров if(iFrames == 101) { float fps; char cBuffer[64]; fps = 100.Of I gltStopwatchRead(&frameTimer); if(iMethod == 0) sprintf(cBuffer, "OpenGL SphereWorld without Display Lists %.If fps", fps); else sprintf(cBuffer, "OpenGL SphereWorld with Display Lists %.If fps", fps); glutSetWindowTitle(cBuffer); gltStopwatchReset(&frameTimer); iFrames = 1; 1 Переход на таблицы отображения может существенно повлиять на производитель- ность Некоторые реализации OpenGL даже пытаются, если это возможно, записы- вать таблицы отображения в памяти графической карты, еще больше уменьшая объем работы, необходимой для поставки данных графическому процессору. На рис 11 7 показан результат выполнения программы SPHEREWORLD, запущенной без таблиц отображения На современной графической карте частота смены кадров уже доста- точно велика. Однако на рис. 11 8 видно, что с использованием таблиц отображения частота становится существенно выше Почему вообще нас должна волновать производительность визуализации9 Чем быстрее и эффективнее код визуализации, тем более сложной можно сделать сцену, нс опускаясь до слишком низкой частоты смены кадров Высокая частота дает более гладкую и более красивую анимацию Кроме того, часть времени процессора можно использовать для других задач — физических расчетов или длительных операций ввода-вывода, организованных отдельным потоком.
Глава 11. Все о конвейере: быстрое прохождение геометрии 557 Рис. 11.7. Программа SPHEREWORLD без таблиц отображения Рис. 11.8. Программа SPHEREWORLD с таблицами отображения
558 Часть I Классический OpenGL Массивы вершин Таблицы отображения часто применяются для предварительной компиляции набо- ров команд OpenGL В примере BOLT таблицы отображения были задействованы недостаточно, поскольку все, что мы в них заносили, — это создание геометрии. То же можно сказать и о множестве сфер программы SphereWorld, которые требовали значительных тригонометрических расчетов, записанных путем помещения геомет- рических объектов в таблицы отображения. Вы можете подумать, что так же просто создать несколько массивов, в которых хранятся данные о вершинах для моделей, и сэкономить время расчетов. Отчасти вы правы. Одни реализации записывают таблицы отображения эффек- тивнее, чем другие, и если вы действительно записываете данные о вершинах, то можете просто поместить данные модели в один или несколько массивов и визуа- лизировать изображение по этим массивам предварительно рассчитанной геометрии Единственным недостатком такого подхода является то, что в процессе передачи дан- ных OpenGL по одной вершине вам, возможно, придется последовательно просмат- ривать весь массив информации В зависимости от того, насколько много геометриче- ских объектов задействовано, принятие этого подхода может существенно ухудшить производительность. С другой стороны, преимуществом является то, что в отличие от таблиц отображения геометрия не должна быть статической. Каждый раз, когда вы готовитесь визуализировать геометрию, какие-то функции можно применить ко всем геометрическим данным и можно сместить их или каким-то образом моди- фицировать Например, сетка, используемая для визуализации поверхности океана, может иметь волны (рябь). Плывущего кита или медузу также можно смоделировать с помощью подобных деформируемых сеток. С помощью OpenGL можно взять лучшее из обоих сценариев; для этого при- меняются массивы вершин С помощью массивов вершин можно предварительно рассчитать или модифицировать геометрию “на лету”, но в то же время выполнить пакетную передачу всех геометрических данных за раз. Использование стандартных массивов вершин может быть почти таким же быстрым, как и применение таблиц отображения, но при этом не требуется, чтобы геометрия была статической. Кроме того, по другим причинам иногда удобнее хранить данные в массивах, а следова- тельно, визуализировать непосредственно из этих массивов (потенциально данный подход позволяет более эффективно расходовать память) Использование массивов вершин в OpenGL включает четыре основных этапа. Во- первых, нужно собрать геометрические данные в одном или нескольких массивах Это можно сделать, задав алгоритм или загрузив данные из файла на диске Во- вторых, требуется сообщить OpenGL, где находятся данные. В ходе визуализации OpenGL извлекает данные о вершинах непосредственно из указанным массивов. В - третьих, необходимо явно сообщить OpenGL, какие массивы используются. Можно иметь отдельные массивы для вершин, нормалей, цветов и так далее, кроме того, OpenGL должен знать, какие из этих наборов данных использовать. Наконец, нуж- но выполнить команды OpenGL, визуализирующие изображение с использованием предоставленных данных о вершинах Чтобы продемонстрировать описанные четыре этапа, вернемся к примеру, рас- смотренному в одной из предыдущих глав Для создания программы STARFIELD мы
Глава 11. Все о конвейере: быстрое прохождение геометрии 559 Рис. 11.9. Результат выполнения программы STARFIELD переписали программу SMOOTHER из главы 6. Теперь создается три массива, со- держащие случайным образом сгенерированные положения звезд на небе. Затем мы используем массивы вершин для визуализации непосредственно из этих массивов в обход механизма glBegin/glEnd. Результат выполнения программы STARFIELD показан на рис. 11.9. а в листинге 11.6 приводятся важные фрагменты исходного кода. Листинг 11.6. Код настройки и визуализации программы STARFIELD // Массив маленьких звезд ♦define SMALL_STARS 150 GLTVector2 vSmallStars[SMALL_STARS]; ♦define MEDIUM_STARS 40 GLTVector2 vMediumStars[MEDIUM-STARS]; ♦define LARGE_STARS 15 GLTVector2 vLargeStars[LARGE_STARS]; ♦define SCREEN_X 800 ♦define SCREEN—Y 600 // Эта функция выполняет необходимую инициализацию в контексте // визуализации. void SetupRC() { int i ; // Заселяем список звезд for(i = 0; 1 < SMALL_STARS; i.-г)
560 Часть I Классический OpenGL { vSmallStars[i][0] = (GLfloat)(rand() % SCREEN_X); vSmallStars[i][1] = (GLfloat)(rand() % (SCREEN_Y - 100))+100.Of; ) // Заселяем список звезд for(i = 0; 1 < MEDIUM_STARS; i++) { vMediumStars[i][0] = (GLfloat)(rand() % SCREEN_X * 10)/10.0f; vMediumStars[i][1] = (GLfloat)(rand() % (SCREEN_Y - 100))+100.Of; } // Заселяем список звезд for(i =0; i < LARGE_STARS; i++) { vLargeStars[i][0] = (GLfloat)(rand() % SCREEN_X*10)/10.Of; vLargeStars[i][1] = (GLfloat)(rand() % (SCREEN_Y-100)* 10.Of)/10.0f+100.Of; } // Черный фон glClearColor(O.Of, O.Of, O.Of, l.Of ); // Устанавливаем белый цвет рисования glColor3f(O.Of, O.Of, O.Of); ) /////////////////////////////////////////////////////////////////// // Вызывается для рисования сцены void RenderScene(void) GLfloat x = 700.Of; // Положение и радиус луны GLfloat у = 500.Of; GLfloat г = 50.Of; GLfloat angle = O.Of; // Еще одна переменная цикла // Очищает окно glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Все в белом цвете glColor3f(l.Of, l.Of, l.Of); // Используем массивы вершин glEnableCllentState(GL_VERTEX_ARRAY); // Рисуем маленькие звезды glPointsize(1.Of); // Данный код больше не нужен // glBegin(GL_POINTS); // for(i = 0; i < SMALL_STARS; i++) // glVertex2fv(vSmallStars[i]); // glEndO; // Новые функциональные возможности массива вершин glVertexPointer(2, GL_FLOAT, 0, vSmallStars); glDrawArrays(GL_POINTS, 0, SMALL_STARS); // Рисуем звезды среднего размера glPointsize(3.05f); glVertexPointer(2, GL_FLOAT, 0, vMediumStars);
Глава 11 Все о конвейере быстрое прохождение геометрии 561 glDrawArrays(GL_POINTS, О, MEDIUM_STARS); // Рисуем наибольшие звезды glPointSize(5.5f); glVertexPointer(2, GL_FLOAT, 0, vLargeStars); glDrawArrays(GL_POINTS, 0, LARGE_STARS); // Рисуем "Луну" glBegin(GL_TRIANGLE_FAN); glVertex2f(x, y); forfangle = 0; angle < 2.Of * 3.141592f; angle += O.lf) glVertex2f(x + (float)cos(angle) * r, у + (float)sin(angle) * r); glVertex2f(x + r, y); glEnd() ; // Рисуем удаленный горизонт gILineWidth(3.5); glBegin(GL_LINE_STRIP); glVertex2f(O.Of, 25.0f); glVertex2f(50.Of, 100.Of); glVertex2f(100.Of, 25.0f); glVertex2f(225.Of, 125. Of); glVertex2f(300.Of, 50.0f); glVertex2f(375.Of, 100.Of); glVertex2f(460.Of, 25.0f); glVertex2f(525.Of, lOO.Of); glVertex2f(600.Of, 20.Of); glVertex2f(675.Of, 70.Of); glVertex2f(750.Of, 25.0f); glVertex2f(800.Of, 90.Of); glEnd(); // Переключаем буферы glutSwapBuffers(); } Загрузка геометрии Первым предварительным условием использования массивов вершин является за- пись геометрии в массивах В листинге 11 6 фигурировало три глобально доступных массива двухмерных векторов, которые содержали координаты х и у звезд трех кате- горий. // Массив маленьких звезд #define SMALL_STARS 150 GLTVector2 vSmallStars[SMALL_STARS]; #define MEDIUM_STARS 40 GLTVector2 vMediumStars[MEDIUM_STARS]; #define LARGE_STARS 15 GLTVector2 vLargeStars[LARGE_STARS];
562 Часть I Классический OpenGL Напомним, что в данной программе используется ортографическая проекция, а звезды рисуются как точки в случайных местах экрана Вее массивы заселяются в функции SetupRC посредством простого цикла, в котором выбирается случайные ко- ординаты х и у в том участке окне, где мы хотим расположить звезды В следующих строках кода представлено заселение списка маленьких звезд // Заселение списка звезд for(i =0; 1 < SMALL_STARS; 1++) { vSmallStars[i][0]=(GLfloat)(rand() %SCREEN_X); vSmallStars[i][1]=(GLfloat)(rand() %(SCREEN_Y - 100))+100 Of; } Активизация массивов В функции RenderScene мы активизируем использование массива вершин с помо- щью приведенного ниже кода // Использование массивов вершин glEnableClientState(GL_VERTEX_ARRAY); Это первая из новых функций, связанных с массивами вершин, и ей соответствует такая деактивизирующая функция void glEnableClientState(GLenum array); void glDisableClientState(GLenum array); Указанные функции принимают такие константы, включающие и выключающие использование соответствующих массивов gl_vertex_array, GL_COLOR_ARRAY, GL_SECONDARY_COLOR_ARRAY, GL_NORMAL_ARRAY, GL_FOG_COORDINATE_ARRAY, GL_TEXURE_COORD_ARRAY И GL_EDGE_FLAG_ARRAY В примере STARFIELD мы пе- редавали OpenGL только список вершин Как видите, можно передавать и соответ- ствующие массивы нормалей, текстурных координат, цветов и тл Обычно при представлении данной функции возникает вопрос почему разра- ботчики OpenGL добавили новую функцию glEnableClientState вместо простой активизации с помощью glEnable Хороший вопрос Ответ кроется в задуманном способе работы OpenGL OpenGL разрабатывался как модель “клиентАервер’’ В ка- честве сервера выступало графическое аппаратное обеспечение, а в качестве клиен- та — процессор и память компьютера На ПК, например, сервером бутст видеокарта, а клиентом — процессор и оперативная память Носко и>ку описываемое состояние активизированной/дсактивизированной возможности применяемся к к шснтскотт сто- роне, потребовался новый набор функции Где данные? Прежде чем мы действительно сможем использовать тайные о вершинах, нежно сообщить OpenGL, откуда извлечь эти данные Для >тою в протрамму STARl 1LLD введена следующая строка glVertexPointer(2, GL_FLOAT, 0, vSmallStars);
Глава 11 Все о конвейере' быстрое прохождение геометрии 563 ТАБЛИЦА 11.1. Приемлемые размеры и типы данных массива вершин Команда Элементы Приемлемые типы данных glColorPointer 3.4 GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT, GL_FLOAT, GL_DOUBLE glEdgeFlagPointer 1 He задаются (всегда GLboolean) glFogCoordPointer 1 GL-FLOAT, GL_DOUBLE gINormalPointer 3 GL_BYTE, GL_SHORT, GL_INT, GL_FLOAT, GL__DOUBLE glSecondaryColorPointer 3 GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_INT, GL_UNSIGNED_INT, GL_FLOAT, GL_DOUBLE glTexCoordPointer 1,2, 3,4 GL_SHORT, GL_INT, GL_FLOAT, GL_DOUBLE glVertexPointer 2,3,4 GL_SHORT, GL_INT, GL_FLOAT, GL_DOUBLE И снова новая функция — glVertexPointer. Она сообщает OpenGL, откуда из- влечь данные о вершинах. Существуют также соответствующие функции для других типов данных массивов вершин. void glVertexPointer(GLint size, GLenum type, GLsizei stride, const void "pointer); void glColorPointer(GLint size, GLenum type, GLsizei stride, const void "pointer); void glTexCoordPointer(GLint size, GLenum type, GLsizei stride, const void "pointer); void glSecondaryColorPointer(GLint size, GLenum type, GLsizei stride, const void "pointer); void glNormalPointer(GLenum type, GLsizei stride, const void *pData); void glFogCoordPointer(GLenum type, GLsizei stride, const void "pointer); void glEdgeFlagPointer(GLenum type, GLsizei stride, const void "pointer); Все эти функции тесно связаны и принимают практически идентичные аргумен- ты. Все команды, кроме функций нормали, координат тумана и меток краев, пер- вым принимают аргумент size. Этот аргумент сообщает OpenGL число элементов, составляющих тип координат. Например, вершина может задаваться двумя (х,у), тремя (x,y,z) или четырьмя (г, у, z,w) компонентами. При этом нормали всегда имеют три компонента, координаты тумана и метки краев — один компонент, следо- вательно, задавать для этих массивов аргумент size — это предоставлять избыточ- ную информацию Параметр type задает тип данных OpenGL указанного массива. Не все типы дан- ных приемлемы для всех спецификаций массива вершин. В табл. 11 1 перечислены семь функций массивов вершин (указатели индекса применяются в режиме индек- сирования цвета, поэтому здесь не указаны) и приемлемые типы данных, которые можно задавать для элементов данных
564 Часть I Классический OpenGL Параметр stride задает размер в байтах между элементами массива Обычно это значение равно 0, и элементы массива записываются без промежутков между значения. Наконец, параметр pointer является указателем на массив данных Для массивов это просто имя массива Рисуем! Наконец, мы готовы к визуализации с использованием массивов вершин Массивы вершин можно использовать двумя способами Для иллюстрации рассмотрим метод без массива вершин, в котором массив просто проходится циклически, и функции glVertex передается указатель на каждый элемент массива glBegin(GL_POINTS); for(i =0; i < SMALL_STARS; 1++) glVertex2fv(vSmallStars[i]); glEnd(); Поскольку OpenGL теперь известны данные о вершинах, можно потребовать найти значения вершин glBegin(GL_POINTS); for(i =0; i < SMALL_STARS; 1++) glArrayElenient (i) ; glEnd(); Функция glArrayElement ищет соответствующие данные массива в любых мас- сивах, активизированных с помощью glEnableClientState Если массив активи- зирован, а соответствующий массив не задан (glVertexPointer, glColorPointer, и тд), обращение к памяти скорее всего вызовет сбой программы Преимуществом использования glArrayElement является то, что теперь, когда требуется задать все данные для вершины, один вызов функции заменяет несколько (glNormal, glColor, glVertex и тп ). Кроме того, иногда требуется перескакивать по массиву в непосле- довательном порядке Впрочем, большую часть времени вы просто передаете блок данных о верши- нах, который нужно пройти от начала до конца В таких случаях (как в программе STARFIELD) OpenGL может передать один блок активизированных массивов с по- мощью вызова одной функции void glDrawArrays(GLenum mode, GLint first, GLint count) ; В этой функции mode задает примитив, который будет визуализирован (один па- кет примитивов за вызов функции) Параметр first задает, откуда активизированные массивы начинают извлекать данные, а параметр count сообщает, сколько элементов массива нужно извлечь В примере STARFIELD массив маленьких звезд визуализи- рован следующим образом glDrawArrays(GL_POINTS, 0, SMALL_STARS); Реализации OpenGL могут оптимизировать эти переносы блоков, что значительно увеличивает производительность при большом числе вызовов отдельных функций обработки вершин (glVertex, glNormal и тд.).
Глава 11 Все о конвейере быстрое прохождение геометрии 565 Общие вершины Рис. 11.10. Две ленты треугольников с вершинами, лежащими на общей стороне Индексирование массивов вершин Индексированными называются массивы вершин, которые проходятся не от начала до конца, а в порядке, который задается отдельным массивом индексов. Это может пока- заться неудобным, но в действительности индексированные массивы вершин могут сэкономить память и уменьшить служебные издержки преобразований. В идеальном случае они даже быстрее таблиц отображения! Причина такой эффективности заключается в том, что массив вершин может быть меньше массива индексов Иногда такие соседние примитивы, как треугольники, име- ют общие вершины, которые невозможно сформировать при простом использовании лент или вееров треугольников. Например, применяя обычные методы визуализа- ции или массивы вершин, никаким другим образом нельзя указать набор вершин, общий для двух смежных лент треугольников На рис 11.10 показаны две ленты треугольников, имеющих общую сторону. Хотя ленты треугольников позволяют ис- пользовать общие вершины треугольников ленты, избежать служебных издержек, связанных с преобразованием вершин, общих для двух лент, невозможно, поскольку все ленты нужно задавать отдельно. Рассмотрим теперь простой пример, позже мы обратимся к более сложной модели и разберем потенциальные выгоды использования индексированных массивов Простой куб В примере с резьбой мы повторяли множество нормалей и вершин Можно суще- ственно сэкономить память, если повторно использовать нормаль или вершину в мас- сиве вершин, не записывая ее более одного раза. При этом мы не только экономим память — удачная реализация OpenGL оптимизирована под преобразование данных вершин только раз, что позволяет экономить драгоценное время Вместо того чтобы создавать массив вершин, содержащий все вершины геомет- рического объекта, можно создать массив, содержащий только уникальные вершины объекта. Затем для задания геометрии можно использовать другой массив индексов, указывающий на вершины первого массива Эта связь показана на рис 1111 Каждая вершина состоит из трех значений с плавающей запятой, а индекс — это целочисленное значение На большинстве машин величины типа float и integer имеют длину 4 байт, а это означает, что вы экономите 8 байт на каждой повторно используемой вершине за счет 4 дополнительных байт для каждой вершины При небольшом числе вершин экономия может быть нс очень большой, при использова- нии индексированного массива вы можете израсходовать даже больше памяти, чем при указании информации о повторяющихся вершинах Тем нс менее для больших моделей экономия может быть существенной
566 Часть I Классический OpenGL Рис. 11.11. Массив индексов, указывающих на массив уникальных вершин Рис. 11.12. Куб, содержащий восемь вершин с уникальными номерами На рис II 12 показан куб с пронумерованными вершинами В программе CUBEDX этот куб создается с помощью индексированных массивов вершин В листинге 1! 7 приведен код программы CUBEDX, визуализирующей куб с ис- пользованием индексированных массивов вершин Восемь уникальных вершин за- писаны в массиве corners, а в массиве indexes записаны индексы В процедуре RenderScene устанавливается режим многоугольников GL_LINE, поэтому куб изоб- ражается в виде каркаса Листинг 11.7. Код программы CUBEDX, визуализирующей куб с использованием индексированных массивов вершин // Массив, содержащий восемь вершин куба static GLfloat corners!] = { // Передняя грань куба -25.Of, 25 Of, 25 Of, // О 25.Of, 25.Of, 25 Of, // 1 25 Of, -25.Of, 25.Of, // 2 -25.Of, -25.Of, 25.Of, // 3 // Задняя грань куба
Глава 11 Все о конвейере, быстрое прохождение геометрии 567 -25.Of, 25.Of, -25.Of, // 4 25.Of, 25.Of, -25.Of, // 5 25.Of, -25.Of, -25.Of, // 6 -25.Of, -25.Of, -25.Of}; // 7 // Массив индексов, предназначенный для создания куба static GLubyte indexes}] = { 0, 1, 2, 3, // Передняя грань 4, 5, 1, 0, // Верхняя грань 3, 2, 6, 7, // Нижняя грань 5, 4, 7, 6, // Задняя грань 1, 5, б, 2, // Правая грань 4, 0, 3, 7}; // Левая грань // Величина поворота static GLfloat xRot = O.Of; static GLfloat yRot = O.Of; // Вызывается для рисования сцены void RenderScene(void) { // Очищает окно текущим цветом очистки glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Куб изображается в форме каркаса glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); // Записывается состояние матрицы glMatrixMode(GL_MODELVIEW); glPushMatrix(); glTranslatef(O.Of, O.Of, -200.Of); // Повороты вокруг осей x и у glRotatef(xRot, l.Of, O.Of, O.Of); glRotatef(yRot, O.Of, O.Of, l.Of); // Активизируется и задается массив вершин glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer(3, GL_FLOAT, 0, corners); // Используем массивы рисования glDrawElements(GL_QUADS, 24, GL_UNSIGNED_BYTE, indexes); glPopMatrix(); // Переключаем буферы glutSwapBuffers(); ) Как видно из функции glDrawElements, OpenGL имеет встроенную поддерж- ку индексированных массивов индексов Ключевой строкой листинга 11 7 являет- ся следующая: glDrawElements(GL_QUADS, 24, GL_UNSIGNED_BYTE, indexes); Данная строка очень похожа на вызов упоминавшейся ранее функции glDrawAr- rays, но теперь задается массив индексов, определяющий порядок, в котором обхо- дятся активизированные вершины. Результат выполнения программы CUBEDX по- казан на рис 1113. Разновидностью glDrawElement является функция glDrawRangeElements (по- дробно рассмотрена в справочном разделе), добавляющая два новых параметра, ко- торые задают диапазон индексов. Такая подсказка позволяет некоторым реализациям
568 Часть I. Классический OpenGL Рис. 11.13. Каркасный куб, нарисованный с помощью индексированного массива вершин OpenGL выбрать данные вершин с упреждением, что в принципе является оптимиза- цией процесса. Другим улучшением является функция glMultiDrawArrays, которая позволяет посылать несколько массивов индексов с помощью вызова одной функции. Последней функций массива вершин, которая описана в справочном разделе, явля- ется gllnterleavedArrays. Эта функция позволяет комбинировать несколько мас- сивов вершин в один. Это никак не сказывается на доступе к массивам или на их обходе, но в некоторых аппаратных реализациях позволяет повысить производитель- ность за счет организации памяти. А теперь серьезно Мы уже разобрали несколько простых примеров, теперь пришло время заняться более сложной моделью, характеризующейся большим объемом данных о вершинах. Для этого примера мы выбрали модель, созданную перспективным студентом Full Sail Стефаном Картером (Stephen Carter) и любезную предоставленную факультетом игр этой школы. Кроме того, мы использовали продукт Deep Exploration от Right Hemi- sphere, имеющий полезную возможность экспорта моделей в виде кода OpenGL! Де- моверсия продукта имеется на компакт-диске, прилагающемся к книге. На рис. 11.14 показана запущенная программа Deep Exploration с отображенной моделью, с которой мы будем работать. Мы модифицировали код, сгенерированный Deep Exploration, так, чтобы он рабо- тал с нашей средой GLUT и запускался на платформах Macintosh и ПК. Код, визуа- лизирующий модель, можно найти в программе MODELTEST (папка данной главы
Глава 11. Все о конвейере: быстрое прохождение геометрии 569 Рис. 11.14. Простая модель, которую мы будем визуализировать с помощью OpenGL на компакт-диске). Мы не включили в книгу полный листинг программы, поскольку он довольно длинный и практически бесполезный для человека. Программа состоит из нескольких массивов, представляющих 2 248 отдельных треугольников (слишком много, чтобы внимательно их изучить!). Принятый в указанном инструменте подход заключается в создании наименьшего возможного кода, представляющего данную модель. Deep Exploration великолепно справляется с компактизацией данных. Всего существует 2 248 отдельных треуголь- ников, но за счет использования умной схемы индексирования Deep Exploration коди- рует их всего лишь как 1 254 отдельных вершин, 1 227 нормалей и 2 141 текстурных координат. В приведенном ниже коде показана функция DrawModel, которая цик- лически проходит по множеству индексов и посылает OpenGL текстуру, нормаль и координаты вершин каждого отдельного треугольника. void DrawModel(void) { int iFace, iPoint; glBegin(GL_TRIANGLES); for(iFace = 0; iFace < 2248; iFace++) // Здесь начинаются новые треугольники for(iPoint = 0; iPoint < 3; iPoint++) // Здесь задаются все вершины { // Поиск значения текстуры glTexCoord2fv(textures[face_indices[iFace][iPoint+6]]); // Поиск значения нормали glNormal3fv(normals[face_indices[iFace][iPoint+3]]);
570 Часть I Классический OpenGL // Поиск значения вершины glVertex3fv(vertices[face_indices[iFace][iPoint]]); 1 glEnd(); } Описанный подход идеален, когда требуется оптимизировать объем памяти для хранения данных о модели (например, чтобы сэкономить память в внедренном при- ложении, уменьшить память для хранения данных или полосу пропуская, если модель требуется передавать через сеть). Тем не менее в приложениях реального времени, где производительность иногда важнее экономии памяти, данный код будет неудач- ным, поскольку всякий раз, когда нужно будет вернуться к первому квадрату, вам придется посылать данные OpenGL по одной вершине. Простейшим и, пожалуй, наиболее очевидным подходом к ускорению кода явля- ется помещение функции DrawModel в таблицу отображения. Действительно, такой подход использован в программе MODELTEST, визуализирующей данную модель Рассмотрим цену этого подхода и сравним его с визуализацией той же модели с по- мощью индексированных массивов вершин Измеряя цену Вначале рассчитаем объем памяти, требуемый для хранения исходных компактных данных о вершинах. Для этого можно просто рассмотреть объявления массивов данных и выяснить, насколько большим является стандартный тип данных (сколько места в памяти требуют данные этого типа) static short face_indices[2248][9] = { ... } static GLfloat vertices [1254][3] = { ... } static GLfloat normals [1227][3] = { ... } static GLfloat textures [2141][2] = { ... ] Память, требуемая для хранения face_indices, будет равна sizeof(short) х 2 248 х 9, что составляет 40 464 байт Подобным образом мы рассчитываем объемы памяти для хранения вершин, нормалей и текстур. 15 048, 14 724 и 17 128 байт, соответственно. В результате получаем, что требуется 87 364 байт, или поряд- ка 85 Кбайт, памяти Но постойте! При рисовании модели в таблицу отображения мы снова копируем все эти данные в таблицу отображения, на этот раз восстанавливая сжатые данные, так что теперь многие вершины соседних треугольников дублируются По сути, мы отменяем всю работу по оптимизации памяти для хранения геометрии, чтобы нарисо- вать ее Мы не можем точно рассчитать, сколько места занимает таблица отображения, но можем получить оценку, подсчитав только размер геометрии Существует 2 248 треугольников Треугольники имеют по три вершины, каждая из которых представ- лена тремя величинами типа float (собственно вершина), тремя величинами типа float (нормаль) и двумя величинами типа float (текстурные координаты). Пред- полагая, что для хранения величин с плаваюшей запятой применяется четыре байта (sizeof (float)), получаем следующую оценку:
Глава 11 Все о конвейере быстрое прохождение геометрии 571 2 248 (треугольников) х 3 (вершины) = 6 744 вершин Каждая вершина имеет три компонента (х, </. с) () 7 14 х 3 20 232 значений с плавающей запятой для записи геометрии Каждая вершина имеет нормаль, тс сшс три компонента (> 7} 1 х 3 = 20 232 значений с плавающей запятой для записи нормалей Каждая вершина имеет текстуру, т с еще два компонента 6 744 х 2 = 13 488 значений с плавающей запятой для записи текстурных координат Всего получаем 53 952 величин с плавающей запятой, по 4 байт каждая = 2 Г) 808 байт Общая память, требуемая для данных, записанных в таблицах отображения, и ис- ходных данных равна 311 736 байт, тс чуть больше 300 Кбайт Однако не забывайте о стоимости преобразования 6 744 (2 248 х 3) вершин нужно преобразовать в гео- метрическом конвейере OpenGL, а это очень много операций умножения матриц' Создание подходящего индексированного массива То, что данные в програм- ме MODELTLST храня 1ся в массивах, совсем нс означает, что данные можно ис- пользовать как массив вершин OpenGL В OpenGL массив вершин, массив норма- лей и другие массивы, которые мы хотим использовать, должны иметь одинаковой размер Это объясняется тем, что все элементы массива совместно используются разными массивами. Если применять обычные массивы вершин, то для прохода по набору массивов нулевой элемент массива вершин должен выравниваться с нулевым элементом массива нормалей и тд То же ограничение имеем для индексированных массивов Каждый индекс должен во всех активизированных массивах отмечать со- ответствующие элементы массива Для программы MODELIVA мы написали функцию (листинг 11 8), проходящую по существующему массиву вершин и выполняющую такое повторное индексирова- ние треугольников, чтобы все три массива имели одинаковый размер и все элементы массива точно соответствовали друг другу Листинг 11.8. Код для создания нового индексированного массива вершин /////////////////////////////////////////////////////////////////// //В данном примере приведенные ниже функции закодированы жестко GLushort uilndexes[2248*3]; // Максимальное число индексов GLfloat vVerts[2248*3][3]; // (Наихудший сценарий) GLfloat vText[2248*3][2]; GLfloat vNorms[2248*3][3]; int iLastlndex = 0, // Число действительно // использованных индексов ///////////////////////////////////////////////////////////////////
572 Часть I Классический OpenGL // Сравниваем два значения с плавающей точкой и возвращаем true, // если они достаточно близки, чтобы считаться одинаковыми inline bool IsSame(float x, float y, float epsilon) { if(fabs(x-y) < epsilon) return true; return false; } /////////////////////////////////////////////////////////////////// // Проходим через массивы и находим дублирующиеся вершины, // которые можно использовать совместно. Таким образом немного // расширяется исходный массив и возвращается число действительно // уникальных вершин, которые теперь заселяют массив vVerts int IndexTriangles(void) ( int iFace, iPoint, iMatch; float e = 0.000001; // Насколько мала разность, // чтобы приравнять значения // Циклически проходим по всем граням int iIndexCount = 0; for(iFace = 0; iFace < 2248; iFace++) { for(iPoint = 0; iPoint < 3; iPoint++) { // Поиск соответствий for(iMatch = 0; iMatch < iLastlndex; iMatch++) { // Если вершина такая же ... if(IsSame(vertices[face_indices[iFace][iPoint]][0], vVerts[iMatch][0], e) && IsSame(vertices[face_indices[iFace][iPoint]][1], vVerts[iMatch][1], e) && IsSame(vertices[face_indices[iFace][iPoint]][2], vVerts[iMatch][2], e) && // и нормаль такая же ... IsSame(normals[face_indices[iFace][iPoint+3]][0], vNorms[iMatch][0], e) && IsSame(normals[face_indices[iFace][iPoint+3]][1], vNorms[iMatch][1], e) && IsSame(normals[face_indices[iFace][iPoint+3]][2], vNorms[iMatch][2], e) && // и текстура такая же ... IsSame(textures[face_indices[iFace][iPoint+6]][0], vText[iMatch][0], e) && IsSame(textures[face_indices[iFace][iPoint+6]][1], vText[iMatch][1], e)) { // тогда только добавляем индекс uiIndexes[iIndexCount] = iMatch; iIndexCount++; break;
Глава 11 Все о конвейере' быстрое прохождение геометрии 573 // Соответствие не обнаружено, добавляем эту вершину // в конец списка и обновляем массив индексов if(iMatch == iLastlndex) { // Добавляем данные и новый индекс memcpy(vVerts[iMatch], vertices[face_indices[iFace][iPoint]], sizeof(float) * 3); memcpy(vNorms[iMatch], normals[face_indices[iFace][iPoint+3]], sizeof(float) * 3); memcpy(vText[iMatch], textures[face_indices[iFace][iPoint+6]], sizeof(float) * 2); uilndexes[ilndexCount] = iLastlndex; iIndexCount++; iLastlndex++; ) ) } return ilndexCount; /////////////////////////////////////////////////////////////////// // Функция для сшивания треугольников // и рисования машины void DrawModel(void) { static int ilndexes = 0; char cBuffer[32]; // При первом вызове данного кода выполняется повторное // индексирование треугольников. // Результаты сообщаются в заголовке окна if(ilndexes == 0) { ilndexes = IndexTriangles(); sprintf(cBuffer,"Verts = %d Indexes = %d", iLastlndex, ilndexes); glutSetWindowTitle(cBuffer) ; ) // Используем вершины, нормали и текстурные координаты glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); // Вот здесь сейчас находятся данные glVertexPointer(3, GL_FLOAT,0, vVerts); glNormalPointer(GL_FLOAT, 0, vNorms); glTexCoordPointer(2, GL_FLOAT, 0, vText); // Рисуем их glDrawElements(GL_TRIANGLES, ilndexes, GL_UNSIGNED_SHORT, uilndexes);
574 Часть I Классический OpenGL Итак, вначале нужно объявить память для нового индексированного массива вер- шин. Поскольку мы не знаем заранее, сколько сэкономим и даже будет ли вообще эта экономия, мы выделяем блок массивов, предполагая наихудший вариант развития со- бытий. Если все вершины уникальны, каждой вершине соответствует три величины с плавающей запятой (всего 2 248 граней по 3 вершины в каждой): GLushort uilndexes[2248*3]; // Максимальное число индексов GLfloat vVerts[2248*3][3]; // (Наихудший сценарий) GLfloat vText[2248*3][2]; GLfloat vNorms[2248*3][3]; int iLastlndex = 0; // Число индексов, действительно использованных Поиск дубликатов требует, чтобы мы проверили множество значений с плава- ющей запятой на равенство. Обычно на такие операции наложено табу, поскольку величины с плавающей запятой общеизвестны своими помехами; их значения могут “плавать”, слегка меняясь (просим прощения за каламбур!). Часто эту проблему мож- но решить, написав специальную функцию, которая просто находит разность двух величин и определяет, достаточно ли мала эта разность, чтобы величины считать равными inline bool IsSame(float x, float y, float epsilon) { if(fabs(x-y) < epsilon) return true; return false; } Функция IndexTriangles вызывается только один раз; она проходит по суще- ствующему массиву в поисках дублирующихся вершин. Чтобы отнести вершину к об- щей, нужно, чтобы координаты вершины, нормали и текстуры были одинаковыми. Если соответствие найдено, на эту вершину создается ссылка в новом массиве ин- дексов. Если — нет, вершина добавляется в конец списка уникальных вершин, а затем на нее создается ссылка в массиве индексов. В DrawModel функция IndexTriangles вызывается только раз, и в заголовке окна сообщается, сколько идентифицировано уникальных вершин, и сколько индексов требуется для обхода списка треугольников. // При первом вызове кода выполняется повторное индексирование // треугольников. // Результаты сообщаются в заголовке окна if(ilndexes == 0) { ilndexes = IndexTrianglesО; sprintf(cBuffer,"Verts = %d Indexes = %d", iLastlndex, ilndexes); glutSetWindowTitle(cBuffer); } С этого момента визуализация прямолинейна. Активизируется три набора массивов // Используем вершины, нормали и текстурные координаты glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); Далее мы сообщаем OpenGL, где находятся данные
Глава 11. Все о конвейере: быстрое прохождение геометрии 575 Рис. 11.15. Модель, визуализированная с помощью индексированных массивов вершин // Вот здесь сейчас находятся данные glVertexPointer(3, GL_FLOAT,0, vVerts); glNormalPointer(GL__FLOAT, 0, vNorms); glTexCoordPointer(2, GL_FLOAT, 0, vText); После этого изображаются все треугольники. // Рисуем их glDrawElements(GL_TRIANGLES, ilndexes, GL_UNSIGNED_SHORT, uilndexes); Отказ от треугольников при визуализации может показаться странным, поскольку в таком случае мы лишаемся преимущества общих вершин, существующего для лент и вееров. Тем не менее с помощью индексированных массивов вершин мы можем вернуться к большим пакетам треугольников, при этом сохранив преимущество мно- жества общих вершин, - возможно, даже превысив эффективность, которую предла- гают ленты и вееры. Окончательный результат выполнения программы MODELIVA показан на рис. 11.15. Сравнивая цену Сравним теперь цену двух описанных методов визуализации изображенной модели. Из результата выполнения программы MODELIVA видим, что было найдено 3 193 уникальных вершин; при этом у них могли быть общие нормали и текстурные координаты. Тем не менее визуализация всей модели требует 6 744 индексов (и не удивительно!).
576 Часть I Классический OpenGL ТАБЛИЦА 11.2. Служебные издержки (память и выполнение преобразований) трех методов визуализации Метод визуализации Память Вершины Непосредственный режим 95 Кбайт 6 744 Таблицы отображения 300 Кбайт 6 744 Индексированные массивы вершин 112 Кбайт 3 193 Каждая вершина имеет три компонента (г, у, г). 3 193 вершин х 3 = 9 579 величин с плавающей запятой Каждая нормаль также имеет три компонента. 3 193 нормали х 3 = 9 579 величин с плавающей запятой Каждая текстурная координата имеет два компонента. 3 193 текстурных координаты х 2 = 6 386 величин с плавающей запятой Умножая каждую величину с плавающей запятой на 4 байт, получаем требуемую память: 102 176 байт Но еще требуется добавить массив индексов (тип short). Т е 6 744 элементов по 2 байт каждый = 13488. В результате получаем, что требуемая память равна 115 664 байт Полученные значения сведены в табл. 11 2 Видно, что режим непосредственной визуализации, принятый в Deep Exploration, требует меньше всего памяти Однако в таком случае геометрия передается OpenGL очень медленно. Если поместить код непосредственного режима в таблицу отображе- ния, передача геометрии будет выполняться гораздо быстрее, но требования к памяти возрастают в три раза Индексированный массив вершин кажется хорошим компро- миссом, более чем в два раза уменьшающим требуемую память за счет увеличения наполовину цены преобразований. Разумеется, в данном примере мы выделяли гораздо больший буфер, чтобы вме- стить максимальное число вершин, которые могли потребоваться В реальной про- грамме могут предоставляться инструменты, принимающие рассчитанный индекси- рованный массив и записывающие его на диск с заголовком, в котором указаны требу- емые размеры массива Последующее считывание этой модели в программу является реализацией стандартного средства загрузки модели В результате таких манипуляций загруженная модель представлена в том формате, который требуется для OpenGL. Модели с резкими краями и углами часто имеют меньше вершин — потенциальных кандидатов на общие В то же время модели с большими гладкими поверхностями могут давать значительную экономию с точки зрения памяти и служебных издержек преобразований Добавив к этому экономию от передачи в память меньшего числа геометрических объектов и экономию от математических операций, получаем, что индексированные массивы могут быть существенно выгоднее таблиц отображения даже для статической геометрии Во многих приложениях реального времени ин- дексированные таблицы вершин являются предпочтительным методом визуализации геометрии
Глава 11 Все о конвейере, быстрое прохождение геометрии 577 Резюме В данной главе мы немного сбавили темп введения новых концепций и объяснили, как строить трехмерные объекты, начав с использования примитивов OpenGL, рассмот- рев создание простых трехмерных фрагментов и завершив сборкой этих фрагментов в большие и сложные объекты. Изучение программного интерфейса приложений нс вызывает трудностей, только уровень опыта сборки трехмерных объектов и сцен бу- дет отличать вас от опытных пользователей. После разбиения объекта или сцены на маленькие компоненты (которые могут использоваться повторно) вы можете сэконо- мить время построения, используя таблицы отображения. Множество функций, кото- рые помогут управлять таблицами отображения, вы найдете в справочном разделе. Во второй половине главы рассматривались не способы организации ваших объек- тов, а то, как организовать геометрические данные, использованные для построения этих объектов. Собирая все данные о вершинах в одну структуру данных (массив), вы позволяете реализации OpenGL оптимизировать производительность. Кроме того, данные можно потоком передавать на диск и обратно, записывая геометрию в фор- мате, готовом для использования OpenGL Хотя OpenGL не имеет “формата модели”, как некоторые программные интерфейсы высокого уровня, но если вы хотите создать такой формат самостоятельно, начать следует с построения массива вершин. В общем случае отображение статической геометрии можно существенно уско- рить, применяя таблицы отображения. Кроме того, можно использовать массивы вершин при любой динамической геометрии. С другой стороны, индексированные массивы вершин могут потенциально (но всегда) предложить лучшее обоих подхо- дов — гибкие геометрические данные и эффективный перенос памяти и обработку геометрии. Во многих приложениях используются исключительно массивы вершин Тем не менее старая добрая конструкция glBegin/glEnd также находит множество сфер применения, причем не только в области создания таблиц отображения; она, например, очень полезна, когда количество геометрических объектов динамически меняется при переходах между кадрами В таких ситуациях бессмысленно постоянно перестраивать массив вершин, лучше позволить драйверу работать непосредственно с glBegin/glEnd Справочная информация glArrayElement Цель: Задать элемент массива, используемый для визуализации вершины Включаемый файл: <gl.h> Синтаксис: void glArrayElement(GLint index); Описание: Вместе с парой glBegin/glEnd используется для задания информации о вершинах. Как часть определения примитива OpenGL передается индексированный элемент любого активизированного массива вершин Параметры: index (тип GLint) Индекс элемента массива Что возвращает: Ничего См. также: glDrawArrays, glDrawElements, glDrawRangeElements, gllnterleavedArrays
578 Часть I Классический OpenGL glCallList Цель: Выполнить таблицу отображения Включаемый файл: Синтаксис: Описание: Параметры: list (тип GLuint) Что возвращает: См. также: <gl.h> void glCallList(GLuint list); Выполняет таблицу отображения, определенную параметром list. После вызова этой функции состояние конечного автомата OpenGL не восстанавливается, поэтому функции glPushMatrix стоит вызывать до этой команды, а функцию glPopMatrix — после Вызовы glCallList могут быть вложенными. Функция glGet с аргументом GL_MAX_LIST_NESTING возвращает максимальное число допустимых вложений. Для Microsoft Windows это значение равно 64 Идентифицирует выполняемую таблицу отображения Ничего glCallLists, glDeleteLists, glGenLists, glNewList glCallLists Цель: Включаемый файл: Синтаксис: Описание: Параметры: п (тип GLsizei) type (тип GLenum) *lists (тип GLvoid) Что возвращает: См. также: Выполнить ряд таблиц отображения <gl.h> void glCallLists(GLsizei n, GLenum type, const GLvoid *lists); Последовательно вызывает таблицы отображения, перечисленные в массиве * lists. Массив может быть образован данными практически любого типа. Результат преобразовывается или ограничивается согласно допустимому диапазону до ближайшего целого значения и определяет действительный индекс таблицы отображения. Значения списка можно (необязательно) смещать на значение, заданное функцией glListBase Число элементов в массиве таблиц отображения Тип данных массива, записанного в *lists Может иметь любое из следующих значений gl_byte, gl_unsigned_byte, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT, GL_FLOAT, GL_2_BYTES, GL_3_BYTES И GL_4_BYTES Массив элементов, тип которых задан в type. Указывая тип данных void, вы разрешаете использовать любой из указанных выше типов данных Ничего glCallList, glDeleteLists, glGenLists, glListBase, glNewList
Глава 11 Все о конвейере быстрое прохождение геометрии 579 glColorPointer Цель: Определить массив информации о цвете для функций OpcnGL массива вершин Включаемый файл: <gl,h> Синтаксис: void glColorPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); Описание: Определяет положение, организацию и тип данных, которые будут соотнесены с информацией о цвете вершин при применении функций массива вершин Буфер, на который указывает функция, может содержать динамические данные, которые, однако, должны быть приемлемыми При любом вычислении OpcnGL значений массива вершин данные снова считываются из указанного буфера массива вершин Параметры: size (тип GLint) Число компонентов, задающих цвет Возможные значения- 3 и 4 type Тип данных информации в массиве. Допустимы любые типы (тип GLenum) данных OpenGL, приемлемые для компонентов цвета gl_byte, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT, GL_FLOAT И GL_DOUBLE stride Смещение в байтах между цветами в массиве Значение 0 (тип GLsizei) указывает, что данные упакованы плотно pointer Указатель, задающий положение начала данных массива (тип GLvoid*) вершин Что возвращает: Ничего См. также: glVertexPointer, glNormalPointer, glTexCoordPointer, glEdgeFlagPointer, glFogCoordPointer, gllnterleavedArrays gIDeleteLists Цель: Удалить непрерывный диапазон таблиц отображения Включаемый файл: <gl. h> Синтаксис: void gIDeleteLists(GLuint list, GLsizei range); Описание: Удаляет диапазон таблиц отображения Диапазон начинается с исходного значения и продолжается до набора такого количества таблиц, как указано в переменной range Удаление неиспользуемых таблиц отображения позволяет существенно сэкономить память Неиспользуемые таблицы отображения, попавшие в диапазон подлежащих удалению, игнорируются (это не считается ошибкой)
580 Часть I Классический OpenGL Параметры: list (тип GLuint) range (тип GLsizei) Что возвращает: См. также: Целочисленное имя первой из подлежащих удалению таблиц отображения Число удаляемых таблиц отображения после заданной первой Ничего glCallList, glCallLists, glGenLists, gllsList, glNewList glDrawArrays Цель: Включаемый файл: Синтаксис: Описание: Параметры: mode (тип GLenum) first (тип GLint) count (тип GLsizei) Что возвращает: См. также: Создать последовательность примитивов из активизированного массива вершин <gl.h> void glDrawArrays(GLenum mode, GLint first, GLsizei count); Позволяет визуализировать ряд примитивов, используя данные активизированных в настоящее время массивов вершин. Функция принимает тип примитива и обрабатывает все вершины из заданного диапазона Тип визуализируемого примитива. Допустимы следующие разрешенные типы примитивов OpenGL- GL_POINTS, GL_LINES, GL_LINE_LOOP, GL_LINE_STRIP, GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_QUADS, GL_QUAD_STRIP И GL_POLYGON Первый индекс используемого активизированного массива Число используемых индексов Ничего gIDrawElements, glDrawRangeElements, gllnterleavedArrays gIDrawElements Цель: Визуализировать примитивы по данным массива, используя индекс массива Включаемый файл: <gl,h> Синтаксис: void gIDrawElements(GLenum mode, GLsizei count, GLenum type, GLvoid *pointer); Описание: Вместо последовательного обхода данных массива функция последовательно обходит массив индексов Обычно этот массив соответствует непоследовательному порядку вершин (часто с повторяющимися элементами) и позволяет многократно использовать информацию о вершине
Глава 11 Все о конвейере быстрое прохождение геометрии 581 Параметры: mode (тип GLenum) Тип визуализируемого примитива Возможны следующие значения GL_POINTS, GL_LINES, GL_LINE_LOOP, GL_LINE_STRIP, GL_TRIANGLES, GL_TRIANGLE_FAN, GL_TRIANGLE_STRIP, GL_QUAD, GL_QUAD_STRIP ИЛИ GL_POLYGON count (тип GLsizei) type (тип GLenum) Смещение в байтах между координатами в массиве Значение 0 указывает, что данные упакованы плотно Тип данных, используемых в массиве индексов Допустимы следующие значения. GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT ИЛИ GL_UNSIGNED_INT pointer (тип GLvoid*) Что возвращает: Указатель, задающий положение массива индексов Ничего См. также: glArrayElement, glDrawArrays, gIDrawRangeElements, glDrawMultiRangeElements gIDrawRangeElements Цель: Визуализировать примитивы по данным массива, используя индекс массива и заданный диапазон допустимых значений индекса Включаемый файл: <gl.h> Синтаксис: void gIDrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, GLvoid ★pointer'); Описание: Вместо последовательного обхода данных массива функция последовательно обходит массив индексов Обычно этот массив соответствует непоследовательному порядку вершин (часто с повторяющимися элементами) и позволяет многократно использовать информацию о вершине Помимо этого функция принимает диапазон допустимых значений индекса В некоторых реализациях OpenGL эта информация может использоваться для предварительной выборки данных о вершинах, что позволяет повысить производительность Параметры: mode (тип GLenum) Тип визуализируемого примитива Возможные значения: GL_POINTS, GL_LINES, GL_LINE_LOOP, GL_LINE_STRIP, GL_TRIANGLES, GL_TRIANGLE_FAN, GL_TRIANGLE_STRIP, GL_QUAD, GL_QUAD_STRIP ИЛИ GL_POLYGON start (тип GLint) end (тип GLint) count (тип GLsizei) Первый индекс используемого диапазона Последний индекс используемого диапазона Смещение в байтах между координатами в массиве Значение 0 указывает, что данные упакованы плотно
582 Часть I Классический OpenGL type (тип GLenum) pointer (тип GLvoid*) Что возвращает: См. также: Тип данных, используемых в массиве индексов Допустимы значения GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT ИЛИ GL_UNSIGNED_INT Указатель, задающий положение массива индексов Ничего glArrayElement, glDrawArrays, glDrawElements, glDrawMultiRangeElements glEdgeFlagPointer Цель: Определить массив меток краев для функций массивов вершин Включаемый файл: <gl,h> Синтаксис: void glEdgeFlagPointer(GLsizei stride, const GLvoid ^pointer); Описание: Параметры: Определяет положение данных, применяющихся в массиве меток краев при использовании функций массива вершин. Буфер, на который указывает функция, может содержать динамические данные, которые, однако, должны быть приемлемыми При любом вычислении OpenGL значений массива вершин данные снова считываются из указанного буфера массива вершин Обратите внимание на отсутствие аргумента type, имеюшегося в других функциях с указателями на массив вершин В данном случае тип данных меток краев обязательно должен быть равен GLboolean stride Смещение в байтах между метками краев в массиве Значение 0 (тип GLsizei) указывает, что данные упакованы плотно pointer Указатель, задающий положение начала данных массива (тип GLvoid*) вершин Что возвращает: Ничего См. также: glColorPointer, glNormalPointer, glTexCoordPointer, glVertexPointer, glFogCoordPointer, glEdgeFlagPointer, glSecondaryColorPointer glEnableClientState/gIDisableClientState Цель: Задать тип массива, активизировав или деактивизировав его для использования с массивами вершин OpenGL Включаемый файл: <gl.h> Синтаксис: void glEnableClientState(GLenum array); void glDisableClientState(GLenum array);
Глава 11 Все о конвейере быстрое прохождение геометрии 583 Описание: Приведенные функции сообщают OpenGL, что вы будете или не будете задавать массивы вершин для геометрических Параметры: array (тип GLenum) Что возвращает: См. также: определений Каждый тип массива можно активизировать или деактивизировать отдельно. Использование массивов вершин не исключает использования обычного семейства функций glVertex Спецификация массивов вершин не может храниться в таблице отображения Имя активизируемого или деактивизируемого массива. Допустимы следующие значения GL_VERTEX_ARRAY, GL_COLOR_ARRAY, GL_SECONDARY_COLOR_ARRAY, GL_NORMAL_ARRAY, GL_FOG_COORDINATE_ARRAY, GL_TEXTURE_COORD_ARRAY И GL_EDGE_FLAG_ARRAY Ничего glVertexPointer, glNormalPointer, glTexCoordPointer, glColorPointer, glEdgeFlagPointer, glSecondaryColorPointer, gIFogCoordPointer glEndList Цель: Включаемый файл: Синтаксис: Описание: Что возвращает: См. также: Обозначить конец таблицы отображения <gl.h> void glEndList( void); Для создания таблицы отображения вначале вызывается функция glNewList Впоследствии все команды OpenGL компилируются и помещаются в таблицу отображения Функция glEndList прекращает создание таблицы отображения Ничего glCallList, glCallLists, glDeleteLists, glGenLists, gllsList gIFogCoordPointer Цель: Определить массив координат тумана для функций массива вершин Включаемый файл: <gl. h> Синтаксис: void gIFogCoordPointer(GLenum type, GLsizei stride, const GLvoid ’•'pointer); Описание: Определяет положение, организацию и тип данных, используемых для формирования координат тумана при обработке функций массива вершин Буфер, на который указывает функция, может содержать динамические данные, которые, однако, должны быть приемлемыми При любом вычислении OpenGL значений массива вершин данные снова считываются из указанного буфера массива вершин
584 Часть I Классический OpenGL Параметры: type Тип данных массива Допускается любой тип данных OpenGL, (тип GLenum) приемлемый для компонентов цвета: GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT, GL_FLOAT И GL_DOUBLE stride Смещение в байтах между цветами в массиве. Значение 0 (тип GLsizei) указывает, что данные упакованы плотно pointer Указатель, задающий положение начала данных массива (тип GLvoid*) вершин Что возвращает: Ничего См. также: glColorPointer, glSecondaryColorPointer, glNormalPointer, glTexCoordpointer, glEdgeFlagPointer glGenLists Цель: Сгенерировать непрерывный диапазон пустых таблиц отображения Включаемый файл: <gl.h> Синтаксис: GLuint glGenLists(GLsizei range); Описание: Создает диапазон пустых таблиц отображения. Число генерируемых таблиц зависит от значения, заданного в range Возвращаемое значение представляет первую таблицу отображения в списке пустых таблиц Целью этой функции является резервирование диапазона значений таблиц отображения для будущего использования Параметры: range Число запрашиваемых пустых таблиц отображения (тип GLsizei) Что возвращает: Первую таблицу отображения из затребованного диапазона Значения таблиц отображения, следующих за возвращенным значение до range-1, пусты См. также: glCallList, glCallLists, glDeleteLists, glNewList gl I nterleaved Arrays Цель: Одновременно активизировать и дсактивизировать несколько массивов вершин, а также задать адрес, указывающий на всю информацию о вершинах, которая содержится в сложном массиве Включаемый файл: <gl. h> Синтаксис: void gllnterleavedArrays(GLenum format, GLsizei stride, GLvoid *pointer);
Глава 11 Все о конвейере быстрое прохождение геометрии 585 Описание: Подобно glXXXPointer эта функция одновременно Параметры: format (тип GLenum) stride (тип GLsizei) pointer (тип GLvoid*) Что возвращает: См. также: активизирует и деактивизирует несколько массивов вершин. Данные всех активизированных массивов чередуются в одном множественном массиве Подобные функциональные возможности можно реализовать, аккуратно используя параметр stride в других функциях массива вершин, но приведенная функция позволяет сэкономить несколько промежуточных действий, кроме того, OpenGL может ее оптимизировать Формат упаковки информации о вершинах в чередующемся массиве Допустимые значения перечислены в табл. 11.3 Смещение в байтах между координатами в массиве. Значение 0 указывает, что данные упакованы плотно Указатель, задающий положение чередующегося массива Ничего glColorPointer, glEdgeFlagPointer, glSecondaryColorPointer, glFogCoordPointer, glNormalPointer, glTexCoordPointer, glVertexPointer gllsList Цель: Включаемый файл: Синтаксис: Описание: Параметры: list (тип GLuint) Что возвращает: См. также: Проверить существование таблицы отображения <gl.h> GLboolean gllsList(GLuint list); Позволяет выяснить, существует ли таблица отображения для данного идентификатора. Ее можно использовать для проверки значений таблиц отображения перед их использованием Значение возможно существующей таблицы отображения Эта функция проверяет данное значение, выясняя, определена ли для него таблица отображения GL_TRUE, если таблица отображения существует, GL_FALSE — в противном случае glCallList, glCallLists, gIDeleteLists, glGenLists, glNewList gIListBase Цель: Включаемый файл: Синтаксис: Задать смещение, добавляемое к значениям таблицы, заданным в вызове функции glCallLists <gl.h> void gIListBase(GLuint base);
586 Часть I Классический OpenGL ТАБЛИЦА 11.3. Поддерживаемые форматы чередующихся массивов вершин Формат Описание GL_V2F Два значения типа GL_FLOAT, несущие информацию о вершинах GL_V3F Три значения типа GL_FLOAT, несущие информацию о вершинах GL_C4UB_V2F Четыре значения типа GL_UNSIGNED_BYTE, несущие информацию о цвете, и два значения типа GL_FLOAT, несущие информацию о вершинах GL_C4UB_V3F Четыре значения типа GL_UNSIGNED_BYTE, несущие информацию о цвете, и три значения типа GL_FLOAT, несущие информацию о вершинах GL_C3F_V3F Три значения типа GL_FLOAT, несущие информацию о цвете, и три значения типа GL_FLOAT, несущие информацию о вершинах GL_N3F_V3F Три значения типа GL_FLOAT, несущие информацию о нормалях, и три значения типа GL_FLOAT, несущие информацию о вершинах GL_C4F_N3F_V3F Четыре значения типа gl_float, несущие информацию о цвете, три значения типа GL_FLOAT, несущие информацию о нормалях, и три значения типа GL_FLOAT, несущие информацию о вершинах GL_T2F_V3F Два значения типа GL_FLOAT, несущие информацию о текстурных координатах, три значения типа GL_FLOAT, несущие информацию о вершинах GL_T4F_V4F Четыре значения типа GL_FLOAT, несущие информацию о текстурных координатах, и четыре значения типа GL_FLOAT, несущие информацию о вершинах GL_T2F_C4UB_V3F Два значения типа GL_FLOAT, несущие информацию о текстурных координатах, четыре значения типа GL_UNSIGNED_BYTE, несущие информацию о цвете, и три значения типа GL_FLOAT, несущие информацию о вершинах GL_T2F_C3F_V3F Два значения типа GL_FLOAT, несущие информацию о текстуре, три значения типа GL_FLOAT, несущие информацию о цвете, и три значения типа GL_FLOAT, несущие информацию о вершинах GL_T2F_N3F_V3F Два значения типа GL_FLOAT, несущие информацию о текстурных координатах, три значения типа GL_FLOAT values for normals, и три значения типа GL_FLOAT, несущие информацию о вершинах GL_T 2 F_C 4 F_N 3 F_V 3 F Два значения типа GL_FLOAT, несущие информацию о текстурных координатах, четыре значения типа GL_FLOAT, несущие информацию о цвете, три значения типа GL_FLOAT, несущие информацию о нормалях, и три значения типа GL_FLOAT, несущие информацию о вершинах GL_T 4 F_C 4 F_N 3 F_V4 F Четыре значения типа GL_FLOAT, несущие информацию о текстурных координатах, четыре значения типа GL_FLOAT, несущие информацию о цвете, три значения типа GL_FLOAT, несущие информацию о нормалях, и четыре значения типа GL_FLOAT, несущие информацию о вершинах
Глава 11 Все о конвейере быстрое прохождение геометрии 587 Описание: Функция glCallLists вызывает ряд таблиц отображения, перечисленных в массиве. Эта функция устанавливает смещение, добавляемое для каждой таблицы, имя которой указано в вызове функции По умолчанию это значение равно 0. Чтобы узнать текущее значение, вызывается функция glGet(GL_LIST_BASE) Параметры: base (тип GLuint) Устанавливает целочисленное смещение, которое будет добавляться к таблицам, указанным в вызовах функции glCallLists По умолчанию значение равно 0 Что возвращает: Ничего См. также: glCallLists gIMultiDrawElements Цель: Визуализировать примитивы из нескольких массивов данных, используя массив индексов массивов Включаемый файл: <gl.h> Синтаксис: void gIMultiDrawElements(GLenum mode, GLsizei ★count, GLenum type, GLvoid “indices, GLsizei primcount); Описание: Равнозначна нескольким вызовам glDrawElements Для каждого набора примитивов в параметре count передается массив, задающий число элементов массива для каждого набора примитивов. Массив индексов содержит массив массивов, каждый массив представляет собой соответствующий массив-элемент набора примитивов Параметры: mode (тип GLenum) Тип визуализируемого примитива Возможны следующие значения GL_PO1NTS, GL_LINES, GL_LINE_LOOP, GL_LINE_STRIP, GL_TRIANGLES, GL_TRIANGLE_FAN, GL_TRIANGLE_STRIP, GL_QUAD, GL_QUAD_STRIP ИЛИ GL_POLYGON count (тип GLsizei*) type (тип GLenum) Массив количеств вершин, содержащихся в массивах элементов Тип данных, используемых в массиве индексов. Может быть равен GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT ИЛИ GL_UNSIGNED_INT indices (тип GLvoid**) primcount (тип GLsizei) Что возвращает: Массив указателей на списки элементов массив Число массивов элементов, содержащихся в массивах count и indices Ничею См. также: glDrawElements, gIDrawRangeElements
588 Часть I Классический OpenGL glNewList Цель: Начать создание или замещение таблицы отображения Включаемый файл: <gl.h> Синтаксис: void glNewList(GLuint list, GLenum mode); Описание: Таблица отображения — это группа команд OpenGL, хранящихся для выполнения по сигналу Таблицы отображения можно использовать для ускорения операций рисования, требующих громоздких вычислений, или операций, требующих считывания данных с диска Функция glNewList начинает таблицу отображения с идентификатором, заданным целочисленным параметром list Идентификатор таблицы отображения используется функциями glCallList и glCallLists для обращения к таблице отображения. Если он не уникален, предыдущая таблица отображения может затираться новой Для резервирования диапазона имен таблиц отображения можно использовать функцию glGenLists, а для проверки идентификатора перед использованием — функцию gllsList Таблицы отображения могут только компилироваться или компилироваться и выполняться После вызова функции glNewList и до вызова функции glEndList все команды OpenGL записываются в таблицу отображения в порядке их задания. Приведенные ниже команды выполняются при вызове и никогда не заносятся в таблицу отображения- gllsList, glGenLists, glDeleteLists, glFeedbackBuffer, glSelectBuffer, glRenderMode, glReadPixels, glPixelStore, giFiush, glFinish, gllsEnabled и glGet Параметры: list (тип GLuint) mode (тип GLenum) Числовое имя таблицы отображения Если таблица отображения уже существует, она замещается новой Таблицы отображения могут компилироваться и выполняться позже или компилироваться и выполняться одновременно. Задавая GL_COMPILE, вы указываете только компилировать таблицу отображения, а задавая gl_compile_and_execute — выполнять таблицу отображения в процессе се компиляции Что возвращает: Ничего См. также: glCallList, glCallLists, glDeleteLists, glGenLists, gllsList
Глава 11 Все о конвейере быстрое прохождение геометрии 589 gINormalPointer Цель: Определить массив нормалей для функций массива вершин OpenGL Включаемый файл: <gl.h> Синтаксис: void gINormalPointer(GLenum type, GLsizei stride, const GLvoid *pointer); Описание: Определяет положение, организацию и тип данных, используемых функциями массива вершин для определения нормалей в вершинах. Буфер, на который указывает функция, может содержать динамические данные, которые, однако, должны быть приемлемыми. При любом вычислении OpenGL значений массива вершин данные снова считываются из указанного буфера массива вершин Параметры: type (тип GLenum) Тип данных массива. Допустим любой тип данных OpenGL, приемлемый для нормалей в вершинах: GLJ3YTE, GL_SHORT, GL_INT, GL_FLOAT И GL_DOUBLE stride (тип GLsizei) pointer (тип GLvoid*) Что возвращает: Смещение в байтах между нормалями в массиве Значение 0 указывает, что данные упакованы плотно Указатель, задающий положение начала данных массива нормалей вершин Ничего См. также: glColorPointer, glVertexPointer, glTexCoordPointer, glEdgeFlagPointer, gllnterleavedArrays, glSecondaryColorPointer glSecondaryColorPointer Цель: Определить массив информации о вторичном цвете для функций массива вершин Включаемый файл: <gl.h> Синтаксис: void glSecondaryColorPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); Описание: Определяет положение, организацию и тип данных, используемых функциями массива вершин для определения вторичного цвета вершин Буфер, на который указывает функция, может содержать динамические данные, которые, однако, должны быть приемлемыми При любом вычислении OpenGL значений массива вершин данные снова считываются из указанного буфера массива вершин
590 Часть I Классический OpenGL Параметры: size (тип GLint) type (тип GLenum) Число компонентов цвета Допустимо только значение 3 Тип данных массива. Допустим любой тип данных OpcnGL, приемлемый для компонентов цвета- GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT, GL_FLOAT И GL_DOUBLE stride (тип GLsizei) pointer (тип GLvoid*) Что возвращает: Смещение в байтах между цветами в массиве Значение 0 указывает, что данные упакованы плотно Указатель, задающий положение начала данных массива вершин Ничего См. также: glColorPointer, glFogCoordPointer, glNormalPointer, glTexCoordPointer, glEdgeFlagPointer glTexCoordPointer Цель: Определить массив текстурных координат для функция массива вершин Включаемый файл: <gl.h> Синтаксис: void glTexCoordPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); Описание: Определяет положение, организацию и тип данных, используемых функциями массива вершин для определения текстурных координат Буфер, на который указывает функция, может содержать динамические данные, которые, однако, должны быть приемлемыми При любом вычислении OpenGL значений массива вершин данные снова считываются из указанного буфера массива вершин Параметры: size Число координат на элемент массива Допустимые значения 1, (тип GLint) 2, 3 и 4 type Тип данных массива Допускается любой тип данных OpenGL, (тип GLenum) приемлемый для текстурных координат GL_SHORT, gl_int, GL_FLOAT И GL_DOUBLE stn de Смещение в байтах между координатами в массиве Значение 0 (тип GLsizei) указывает, что данные упакованы плотно pointer Указатель, задающий положение начала данных массива (тип GLvoid*) вершин Что возвращает: Ничего См. также: glColorPointer, glNormalPointer, glSecondaryColorPointer, glVertexPointer, glEdgeFlagPointer, gllnterleavedArrays
Глава 11 Все о конвейере быстрое прохождение геометрии 591 glVertexPointer Цель: Определить массив данных о вершинах для функций массива вершин Включаемый файл: <gl h> Синтаксис: void glVertexPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); Описание: Определяет положение, организацию и тип данных, используемых функциями массива вершин для определения данных о вершинах Буфер, на который указывает функция, может содержать динамические данные, которые, однако, должны быть приемлемыми При любом вычислении OpenGL значений массива вершин данные снова считываются из указанного буфера массива вершин Параметры: size (тип GLint) type (тип GLenum) Число вершин на координату Допустимые значения 2, 3 и 4 Тип данных массива Допускается любой тип данных OpenGL, приемлемый для вершин GL_SHORT, GL_INT, GL_FLOAT И GL_DOUBLE stride (тип GLsizei) pointer (тип GLvoid*) Что возвращает: Смещение в байтах между вершинами в массиве. Значение 0 указывает, что данные упакованы плотно Указатель, задающий положение начала данных массива вершин Ничего См. также: glColorPointer, glNormalPointer, glSecondaryColorPointer, glTexCoordPointer, glEdgeFlagPointer, glInterleavedArrays

ГЛАВА 12 Интерактивная графика Ричард С Райт-мл ИЗ ЭТОЙ ГЛАВЫ ВЫ УЗНАЕТЕ . Действие Функция Присвоение имен OpenGL примитивам gllnitNames/glPushName/glPopName или группам примитивов Выбор как средство выделения объектов, glSelectBuffer/glRenderMode находящихся под указателем мыши Получение информации о месте рисования glFeedbackBuffer/gluPickMatrix объекта с помощью обратной связи До этого момента рассматривалось создание с помощью OpenGL сложной трех- мерной графики, а работа многих приложений заключается всего лишь в ее генера- ции Однако существует множество графических приложений (в частности, игры, АП, трехмерное моделирование и тд ), которые требуют больше взаимодействия с самой сценой Помимо меню и диалоговых окон часто необходимо предоставить средство, с помощью которого пользователь мог бы взаимодействовать с графической сценой Обычно взаимодействие осуществляется с помощью мыши Используя мощное средство OpenGL, именуемое выбором, можно щелкнуть мыш- кой в некоторой точке окна и определить, какие из объектов находятся под этой точкой Акт выбора конкретного объекта на экране называется отбором (picking). С помощью средства выбора OpenGL можно задавать наблюдаемый объем и опре- делять, какие объекты попадают в этот объем Мощная вспомогательная функция gluPickMatrix, основываясь только на экранных координатах и заданных размерах пикселей, создает матрицу, которую можно использовать для формирования меньшего наблюдаемого объема, расположенного под курсором мыши После этого с помощью выбора объем проверяют и определяют, какие объекты в нем содержатся Обратная связь позволяет получать от OpenGL информацию о том, как преоб- разовываются и освещаются вершины, когда они рисуются в буфере кадров Эту информацию можно использовать, передавая результаты визуализации по сети, от- правляя их на плоттер или добавляя в сцену OpenGL другие графические объекты, взаимодействующие с объектами OpenGL Цели обратной связи и выбора отличают- ся, но режимы работы похожи, и технологии могут продуктивно применяться вместе Такую “командную работу” мы проиллюстрируем позже в программе SELECT
594 Часть I Классический OpenGL Выбор Выбор в действительности является режимом визуализации, но в режиме выбора пиксели не копируются в буфер кадров Вместо этого примитивы, которые рисуются в объеме наблюдения (а следовательно, при нормальных условиях появляются в бу- фере кадра), порождают записи совпадения в буфере выбора Этот буфер, в отличие от других буферов OpenGL, представляет собой просто массив целых значений Буфер выбора нужно настраивать заранее, кроме того следует назвать примитивы или группы примитивов (объекты или модели), чтобы их можно было идентифици- ровать в буфере выбора Затем буфер выбора анализируют, определяя, какие объекты пересекли наблюдаемый объем Одной из потенциальных сфер применения данной возможности является определение видимых элементов. Именованные объекты, ко- торые не появляются в буфере выбора, лежат снаружи наблюдаемого объема и нс будут изображены в режиме визуализации Хотя режим выбора является достаточно быстрым для отбора объектов, применение его для универсального отбора элемен- тов, расположенных внутри усеченной пирамиды наблюдения, существенно медлен- нее всех технологий, обсуждавшихся в главе 11, “Все о конвейере более быстрое прохождение геометрии” Обычно вы модифицируете наблюдаемый объем до пере- хода в режим выбора и вызываете код рисования, чтобы определить, какие объекты находятся в некоторой области сцены При отборе задается наблюдаемый объем, соот- ветствующий указателю мыши, после чего проверяется, какие именованные объекты визуализируются под мышью. Называем примитивы Любому примитиву, используемому для визуализации сцены объектов, можно дать имя, но эта возможность редко бывает полезной. Гораздо чаще называются группы примитивов, поскольку таким образом создаются имена конкретных объектов или фрагментов объектов сцены. Имена объектов, подобно именам таблиц отображения, представляют собой обычные целые числа без знака Список имен поддерживается в стеке имен Инициализировав стек имен, можно помещать имена в стек или просто замещать имя, в настоящий момент находящееся на вершине стека При определении в процессе выбора соответствия все имена, в насто- ящий момент находящиеся в стеке имен, добавляются в конец буфера выбора Таким образом, одно соответствие может при необходимости возвращать несколько имен В первом примере ограничимся простыми вещами Создадим упрощенную (и без соблюдения масштаба) модель внутренних планет Солнечной системы При щелчке левой кнопкой мыши будет отображаться окно сообщения, информирующее, на какой планете щелкнул пользователь Кроме того, мы создали текстовые описания Солнца, Меркурия, Венеры, Земли и Марса Часть кода визуализации программы PLANETS приводится в листинге 12 1 Листинг 12.1. Присвоение имен Солнцу и планетам в программе PLANETS /////////////////////////////////////////////////////////////////// // Определяем имена объектов #define SUN 1
Глава 12 Интерактивная графика 59! # define MERCURY 2 # define VENUS 3 # define EARTH 4 # define MARS 5 //////////////////////////////////////////////////////////////////, // Вызывается для рисования сцены void RenderScene(void) { // Очищаем окно текущим цветом очистки glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Записывается состояние матрицы и выполняются повороты glMatrixMode(GL_MODELVIEW); glPushMatrix(); // Транслирует всю сцену в поле зрения glTranslatef(0.Of, O.Of, -300.Of); // Инициализируется стек имен gllnitNames(); glPushName(0); // Называем и рисуем Солнце glColor3f(1.Of, l.Of, O.Of); glLoadName(SUN); DrawSphere(15.Of); // Рисуем Меркурий glColor3f(0.5f, O.Of, O.Of); glPushMatrix(); glTranslatef(24.Of, O.Of, O.Of); glLoadName(MERCURY); DrawSphere(2.Of); glPopMatrix(); // Рисуем Венеру glColor3f(0.5f, 0.5f, l.Of); glPushMatrix(); glTranslatef(60.Of, O.Of, O.Of); glLoadName(VENUS); DrawSphere(4.Of); glPopMatrix(); // Рисуем Землю glColor3f(O.Of, O.Of, l.Of); glPushMatrix(); glTranslatef(100.Of,O.Of,O.Of); glLoadName(EARTH); DrawSphere(8.Of); glPopMatrix(); // Рисуем Марс glColor3f(l.Of, O.Of, O.Of); glPushMatrix(); glTranslatef(150.Of, O.Of, O.Of); glLoadName(MARS); DrawSphere(4.Of); glPopMatrix();
596 Часть I Классический OpenGL // Восстанавливается состояние матрицы glPopMatrix(); // Матрица наблюдения модели glutSwapBuffers(), В программе PLANETS функция инициализирует и очищает стек имен, a gl- PushName помещает в стек значение 0 (чтобы в нем была хотя бы одна позиция) Для Солнца и каждой планеты вызывается функция glLoadName, с помощью которой да- ется имя объекту или объектам, подлежащим изображению Это имя (в форме целого числа без знака) не помещается в стек имен, а заменяет текущее имя на вершине стека Процесс поддержания стека имен мы обсудим позже Сейчас же мы просто заменяем верхнее имя стека имен при рисовании очередного объекта (Солнца или планеты) Работа с режимом выбора Как отмечалось ранее, OpenGL может работать в трех режимах визуализации По умолчанию установлен режим GL_RENDER, в котором фактически происходит рисо- вание не экране. Чтобы использовать средства выбора, необходимо изменить режим визуализации, вызвав следующую функцию OpenGL. glRenderMode(GL_SELECTION); Когда нужно вернуться в режим визуализации и что-то нарисовать, вызывается такая функция OpenGL: glRenderMode(GL_RENDER); Третьим режимом визуализации является GL_FEEDBACK, он обсуждается позже в данной главе Выполнение кода, приведенного в листинге 12 1, не даст никакого эффекта, ес- ли режим визуализации нс переключить вначале на режим выбора Довольно часто (как в приведенном примере) в режимах GL_RENDER и GL_SELECTION используются одинаковые функции В листинге 12 2 приводится код обратного вызова GLUT, инициируемый щелчком левой кнопки мыши При выполнении этого кода программа ожидает щелчка левой кнопки мыши, а затем передает координаты курсора мыши функции ProcessSelec- tion, которая в данном примере обрабатывает щелчки мыши Листинг 12.2. Код, реагирующий на щелчки левой кнопкой мыши /////////////////////////////////////////////////////////////////// // Обрабатывает щелчок мыши void MouseCallbacMint button, int state, int x, int y) if(button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) ProcessSelection(x, y);
Глава 12 Интерактивная графика 597 Буфер выбора |0| —Число имен в стеке имен на момент совпадения = пй |1| —Минимальное значение г [2| —Максимальное значение г [л0+2|-*- Низ стека имен Следующая запись [л0+3]—*- Число имен в стеке имен для этой записи = я, |«o+‘1|-k“ Минимальное значение г Рис. 12.1. Запись соответствия в буфере выбора Буфер выбора Буфер выбора заполняется записями соответствий в процессе визуализации. Запись соответствия генерируется при визуализации примитива или набора примитивов, которые могли попасть в наблюдаемый объем. При нормальных условиях это все, что могло бы появиться на экране. Буфер выбора представляет собой массив целых чисел без знака, и каждая запись соответствия занимает минимум четыре элемента массива Первый индекс массива содержит число имен, находящихся в стеке имен при обнаружении соответствия. В примере PLANETS (листинг 12 1) это число всегда равно 1 Следующие две по- зиции содержат минимальную и максимальную координату окна z для всех вершин, находящихся в объеме наблюдения с момента последней записи соответствия Это значение, принадлежащее диапазону [0,1], масштабируется согласно максимальному размеру целого числа без знака, которое можно записать в буфере выбора Четвертой позицией является дно стека имен Если в стеке имен присутствует более одного имени (определяется первым индексным элементом), они следуют после четвертого элемента Данная схема, проиллюстрированная на рис. 12 1, справедлива для всех записей соответствия, содержащихся в буфере выбора Почему эта схема может ока- заться полезной, мы расскажем при обсуждении отбора Формат буфера выбора не позволяет узнать, сколько записей соответствия нуж- но проанализировать. В действительности буфер выбора не заполняется, пока вы нс переключите режим визуализации обратно на gl_render Если для этого исполь- зуется функция glRenderMode, возвращаемое значение равно числу скопированных записей соответствия В листинге 12 3 приведена функция обработки, вызываемая в программе PLAN- ETS при щелчке мышью Здесь показано, как с помощью функции glSelectBuffer выделяется и задастся буфер выбора Функция принимает два аргумента длину бу- фера и указатель на сам буфер
598 Часть I Классический OpenGL Листинг 12.3. Функция, обрабатывающая щелчки мышью /////////////////////////////////////////////////////////////////// // Обработка выбора; инициируется щелчком правой кнопки мыши // при положении курсора (xPos, yPos). #define BUFFER_LENGTH 64 void ProcessSelection(int xPos, int yPos) { GLfloat fAspect; 11 Память для буфера выбора static GLuint selectBuff[BUFFER_LENGTH]; // Счетчик соответствий и память поля просмотра GLint hits, viewport[4]; // Настройка буфера выбора glSelectBuffer(BUFFER_LENGTH, selectBuff); // Получаем поле просмотра glGet!ntegerv(GL_VIEWPORT, viewport); // Переключаемся на проектирование и записываем матрицу glMatrixMode(GL_PROJECTION); glPushMatrix(); // Меняем режим визуализации glRenderMode(GL_SELECT); // Устанавливаем в качестве нового наблюдаемого объема // единичный куб описанный вокруг положения курсора мыши // (xPos, yPos) и простирающийся на два пикселя по вертикали // и горизонтали glLoadldentity(); gluPickMatrix(xPos, viewport[3] - yPos, 2,2, viewport); // Применяем матрицу перспективной проекции fAspect = (float)viewport[2] / (float)viewport[3] ; gluPerspective(45.0f, fAspect, 1.0, 425.0); // Рисуем сцену RenderScene(); // Собираем соответствия hits = glRenderMode(GL_RENDER); // Если произошло одно совпадение, отображаем информацию if(hits == 1) ProcessPlanet(selectBuff[3 ]); // Восстанавливаем проекционную матрицу glMatrixMode(GL_PROJECTION); glPopMatrix(); // Возвращаемся к наблюдению модели для нормальной визуализации glMatrixMode(GL_MODELVIEW); ) Отбор Отбор происходит при использовании положения курсора мыши для создания и при- менения в процессе выбора модифицированного наблюдаемого объема При создании меньшего наблюдаемого объема, расположенного на сцене под курсором мыши, запи- си соответствия генерируют только объекты, которые были бы нарисованы в предс-
Глава 12. Интерактивная графика 599 лах этого наблюдаемого объема Изучая буфер выбора, можно увидеть, какие объекты (если они есть) попали под щелчок мыши. Функция gluPickMatrix очень полезна и служит для создания матрицы, описы- вающей новой наблюдаемый объем. void gluPickMatrix(GLdouble х, GLdouble у, GLdouble width, GLdouble height, GLint viewport[4]); Параметры x и у представляют центр искомого наблюдаемого объема в координа- тах окна OpenGL. Вместо них можно подставить положение курсора мыши, и наблю- даемый объем будет центрирован непосредственно под курсором Параметры width и height задают размеры наблюдаемого объема в пикселях окна Чтобы можно бы- ло щелкать возле объекта, используйте большее значение, чтобы воспринимались только щелчки на самом объекте, применяйте меньшее значение. Массив viewport содержит оконные координаты определенного на данный момент поля просмотра Эту информацию можно получить, вызвав функцию glGet!ntegerv(GL_VIEWPORT, viewport) ; Напомним (см главу 2, “Используя OpenGL”), что оконные координаты OpenGL противоположны оконным координатам большинства систем с точки зрения подсчета пикселей по вертикали Обратите внимание на то, что в листинге 12 3 мы вычитаем координату у курсора мыши из высоты поля просмотра. В результате получаются правильные вертикальные оконные координаты OpenGL gluPickMatrix(xPos, viewport[3] - yPos, 2,2, viewport); Чтобы использовать gluPickMatrix, нужно вначале записать текущее состояние матрицы проекции (те записать текущий наблюдаемый объем). После этого вызыва- ется glLoadldentity, и создается единичный наблюдаемый объем Последующий вызов gluPickMatrix транслирует этот наблюдаемый объем в текущее положение В завершение нужно применить все перспективные проекции, которые предполага- лось применить к исходной сцене, в противном случае вы не получите правильного отображения Ниже показано, как все вышесказанное реализовано в программе (фраг- мент листинга 12 3) // Переключаемся на проектирование и записываем матрицу glMatrixMode(GL_PROJECTION); glPushMatrix(); 11 Меняем режим визуализации glRenderMode(GL_SELECT); 11 Устанавливаем в качестве нового наблюдаемого объема // единичный куб описанный вокруг положения курсора мыши // (xPos, yPos) и простирающийся на два пикселя по вертикали // и горизонтали glLoadldentity!); gluPickMatrix(xPos, viewport[3] - yPos, 2,2, viewport); // Применяем матрицу перспективной проекции fAspect = (float)viewport[2] I (float)viewport[3]; gluPerspective(45.Of, fAspect, 1.0, 425.0);
600 Часть I Классический OpenGL II Рисуем сцену RenderScene() ; // Собираем соответствия hits = glRenderMode(GL_RENDER); В данном фрагменте вначале записывается наблюдаемый объем Затем вводит- ся режим выбора, модифицируется наблюдаемый объем (в него включается только область, находящаяся под курсором мыши) и перерисовывается сцена с помощью функции RenderScene После визуализации сцены мы снова вызываем glRender- Mode, чтобы вернуть OpcnGL в нормальный режим визуализации и подсчитать число сгенерированных записей соответствия Рассмотрим следующий фрагмент При наличии соответствия (в данном примере возможно либо одно совпадение, либо не одного) мы передаем в буфер выбора эле- мент, содержащий имя выбранного объекта или функцию ProcessPlanet. Наконец, мы восстанавливаем матрицу проекции (тс старый наблюдаемый объем) и пере- ключаем активный стек матриц обратно на матрицу проекции модели (обычно это является установкой по умолчанию) // Если произошло одно совпадение, отображаем информацию if(hits == 1) ProcessPlanet(selectBuff[3]); // Восстанавливаем проекционную матрицу glMatrixMode(GL_PROJECTION); glPopMatrix(); // Возвращаемся к наблюдению модели для нормальной визуализации glMatrixMode(GL_MODELVIEW); Функция ProcessPlanet просто отображает в заголовке окна сообщение, инфор- мирующее, на какой планете щелкнул пользователь Соответствующий код нс пока- зан, поскольку он довольно тривиальный и состоит из оператора выбора и нескольких вызовов функции glutSetWindowTitle Результат выполнения программы PLANETS показан на рис 12 2, где виден ре- зультат щелчка на второй планете от Солнца Хотя мы собираемся сильно вдаваться в подробности, все же стоит кратко об- судить представленные в буфере выбора значения z В программе PLANETS все объекты или модели были разными и расположенными в собственном пространстве Что будет, если применить метод к нескольким объектам или моделям, которые могут перекрываться9 Вы получите несколько записей совпадения1 Как определить, на ка- ком из соотвсгс1вующею набора объектов щелкнул пользователь9 Данная ситуация может быть сложной и требовать предварительного обдумывания С помощью зна- чений z (кодов глубины) можно определить, какой объект ближе всего расположен к пользователю в пространстве наблюдения (вероятнее всего этот объект и являет- ся искомым) Кроме того, если вы неаккуратно написали код, для некоторых форм и геометрий будет трудно точно выяснить, что намеревается отобрать пользователь
Глава 12. Интерактивная графика 601 Рис. 12.2. Результат выполнения программы PLANETS после щелчка на планете Рис. 12.3. Две планеты со своими спутниками Иерархический отбор В программе PLANETS мы не помещали в стек никаких имен, а просто замещали существующее имя новым объектом, подлежащим визуализации. В буфер выбора воз- вращалось единственное имя, находящееся в стеке имен. При наличии соответствия можно получать несколько имен при условии, что в стеке имен находится более од- ной записи. Данная возможность полезна, например, в задачах, когда нужно знать не только то, что выбран конкретный болт, но и то, что он принадлежит к определенному колесу определенной машины и т.д. Чтобы продемонстрировать возвращение в стек имен нескольких имен, вернемся к астрономической теме из предыдущего примера. На рис. 12.3 показаны две планеты (ну, ладно-ладно, включите воображение) — большая голубая планета с одной луной и красная планета поменьше с двумя лунами.
602 Часть I Классический OpenGL Вместо того чтобы просто идентифицировать планету или луну, по которой щелк- нули мышкой, мы хотим также идентифицировать планету, вокруг которой вращается конкретная луна. В листинге 12.4 приведен код визуализации данной сцены Мы так помещаем имена лун в стек имен, чтобы помимо имени выбранной луны он содержал имя соответствующей планеты. Листинг 12.4. Код визуализации программы MOONS /////////////////////////////////////////////////////////////////// // Определяем имена объектов «define EARTH 1 «define MARS 2 #define MOON1 3 #define MOON2 4 /////////////////////////////////////////////////////////////////// // Вызывается для рисования сцены void RenderScene(void) // Очищаем окно текущим цветом очистки glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Записывается состояние матрицы и выполняются повороты glMatrixMode(GL_MODELVIEW); glPushMatrix(); // Транслирует всю сцену в поле зрения glTranslatef(0.Of, O.Of, -300.Of); // Инициализируется стек имен gllnitNames(); glPushName(O); // Рисуем Землю glPushMatrix(); glColor3f(O.Of, O.Of, l.Of); glTranslatef(-100.Of,O.Of,O.Of); glLoadName(EARTH); DrawSphere(30.Of); // Рисуем Луну glTranslatef(45.Of, O.Of, O.Of); glColor3f(0.85f, 0.85f, 0.85f); glPushName(MOON1); DrawSphere(5.Of); glPopName(); glPopMatrix(); // Рисуем Марс glPushMatrix(); glColor3f(1.Of, O.Of, O.Of); glTranslatef(100.Of, O.Of, O.Of); glLoadName(MARS); DrawSphere(20.Of); // Рисуем Луну-1 glTranslatef(-40.Of, 40.Of, O.Of); glColor3f(0.85f, 0.85f, 0.85f); glPushName(MOON1); DrawSphere(5.Of);
Глава 12. Интерактивная графика 603 Рис. 12.4. Результат выполнения программы MOONS glPopName() ; // Рисуем Луну-2 glTranslatef(O.Of, -80. Of, O.Of); glPushName(MOON2); DrawSphere(5.Of) ; glPopName(); glPopMatrix() ; // Восстанавливается состояние матрицы glPopMatrix(); // Матрица наблюдения модели glutSwapBuffers(); } В функции Processselection мы по-прежнему вызываем функцию Process- Planet, но на этот раз передаем ей буфер выбора целиком. // Если произошло одно совпадение, отображаем информацию if(hits == 1) ProcessPlanet(selectBuff); В листинге 12.5 показан более серьезный вариант функции ProcessPlanet. В этом случае нижнее имя в стеке имен всегда относится к планете, поскольку она вводилась первой. Если щелкнуть по луне, она также будет находиться в стеке имен. Указанная функция отображает имя выбранной планеты, а если щелчок пришелся на луну, то дополнительно отображается соответствующая информация. Результат выполнения программы показан на рис. 12.4.
604 Часть I Классический OpenGL Листинг 12.5. Код, анализирующий буфер выбора в программе MOONS /////////////////////////////////////////////////////////////////// // Анализируем буфер выбора, чтобы посмотреть, // какая планета/луна выбрана void ProcessPlanet(GLuint ‘pSelectBuff) { int id,count; char cMessage[64] ; strcpy(cMessage,"Error, no selection detected”); // Сколько имен находится в стеке имен count = pSelectBuff[0]; // Низ стека имен id = pSelectBuff[3]; // Выбираем Землю или Марс switch(id) { case EARTH: strcpy(cMessage,"You clicked Earth."); // Если в стеке имен есть другое имя, то это // выбранная луна, и именно по ней щелкнул пользователь if(count == 2) strcat(cMessage, " - Specifically the moon."); break; case MARS: strcpy(cMessage,"You clicked Mars."); // Мы знаем, что глубина стека имен равна всего двум. // Точная выбранная луна будет находиться здесь if(count == 2) if(pSelectBuff[4] == MOON1) strcat(cMessage," - Specifically Moon #1."); else strcat(cMessage," - Specifically Moon #2."); break; } // Отображается сообщение о выбранной планете и луне glutSetWindowTitle(cMessage) ; ) Обратная связь Подобно выбору обратная связь является режимом визуализации, который нс даст визуального результата в виде пикселей на экране Вместо этого в буфер обратной связи записывается информация, указывающая, как была бы визуализирована сцена Эга информация включает преобразованные данные вершин в оконных координатах, данные о цвете, полученные из расчетов освещения, и текстурные данные — по сути, все. что требуется для растеризации примитивов
Глава 12 Интерактивная графика 605 ТАБЛИЦА 12.1. Типы буферов обратной связи Данные о цвете Текстурные координаты вершин Всего Количество значений Тип координат GL_2D GL_3D GL_3D_COLOR GL_3D_COLOR_TEXTURE GL_4 D_COLOR_TEXTURE X, У, Z, W В режим обратной связи вы входите точно так же, как в режим выбора — вы- зывая функцию glRenderMode с аргументом GLJFEEDBACK Чтобы, заполнив буфер обратной связи, вернуться к нормальному режиму визуализации, нужно вызвать ту же функцию с аргументом GL_RENDER Буфер обратной связи Буфер обратной связи — это массив значений с плавающей запятой, задаваемый с помощью функции glFeedback void glFeedbackBuffer(GLsizei size, GLenum type, GLfloat *buffer); Приведенная функция принимает в качестве аргумента размер буфера обрат- ной связи, тип и объем искомой информации об изображении, а также указатель на собственно буфер Допустимые значения параметра type указаны в табл 12 1 Тип данных опре- деляет, сколько данных помещается в буфер обратной связи для каждой вершины Данные о цвете представляются одним значением в режиме индексирования цвета или четырьмя значениями в режиме RGBA. Данные обратной связи Буфер обратной связи содержит список токенов, за которым следуют данные о вер- шинах и, возможно, данные о цвете и текстуре Эти токены можно анализировать (см табл. 12 2), определяя типы примитивов, которые могли быть визуализированы При работе следует помнить об одном ограничении обратной связи, проявляющемся при использовании нескольких текстурных единиц в указанных случаях возвраща- ются только текстурные координаты первой текстурной единицы После точечных, растровых и пиксельных токенов следуют данные для одной вершины и, возможно, данные о цвете и текстуре Это зависит от типа данных (см. табл 12 1), заданного при вызове функции glFeedbackBuffer Токены линий воз- вращают два набора данных о вершинах, а непосредственно после токена многоуголь- ника указывается число вершин, следующих далее За определенным пользователем маркером (GL_PASS_THROUGH_TOKEN) следует одно значение с плавающей запятой, которое определил пользователь На рис 12 5 приведен пример схемы памяти буфе- ра обратной связи при заданном типе GL_3D На иллюстрации обозначены данные точки, токена и многоугольника (в таком порядке)
606 Часть I Классический OpenGL ТАБЛИЦА 12.2. Токены буфера обратной связи Токен Примитив GL_POINT_TOKEN GL_LINE_TOKEN GL_LINE_RESET_TOKEN GL_POLYGON_TOKEN GL_BITMAP_TOKEN GL_DRAW_PIXEL_TOKEN GL_COPY_PIXEL_TOKEN GL_PASS_THROUGH_TOKEN Точки Линия Отрезок при обновлении фактуры линии Многоугольник Растровый образ Нарисованный пиксельный прямоугольник Скопированный пиксельный прямоугольник Определенный пользователем маркер Буфер обратной связи [0] — GL_POINT TOKEN 11) — Координатах [2] — Координата у (3J — Координата г [4] - GL_PASS_THROUGH_TOKEN [5| — Значение, определенное пользователем |6) - GL.POLYGON.TOKEN [7] — Число вершин (8| — Координатах первой вершины [9] — Координата у первой вершины (101 — Координата г первой вершины 1111 — Координата х второй вершины [п] Координата г последней вершины Рис. 12.5. Пример схемы памяти для буфера обратной связи Маркеры Passthrough При выполнении кода визуализации буфер обратной связи по мере задания примити- вов заполняется токенами и данными о вершинах. Как и в режиме выбора, вы можете пометить примитивы, присвоив им имена. В режиме обратной связи также можно устанавливать маркеры между примитивами, вызывая функцию glPassThrough. void glPassThrough(GLfloat token); Эта функция помещает в буфер обратной связи константу GL_PASS_THROUGH_TOKEN, а за ней — значение, заданное при вызове функции. Этот процесс чем-то похож на именование примитивов в режиме выбора, причем является единственной возможностью пометить объекты в буфере обратной связи.
Глава 12 Интерактивная графика 607 Пример обратной связи Обратная связь прекрасно подходит для получения информации об оконных коорди- натах визуализируемых объектов. Позже эту информацию можно использовать для размещения в окне рядом с объектами средств управления или меток. Чтобы продемонстрировать обратную связь, определим с помощью выбора, по какому из двух объектов на экране щелкнул пользователь. После этого перейдем в режим обратной связи и снова визуализируем сцену, чтобы получить информацию о вершинах в оконных координатах С помощью этих данных определим минималь- ное и максимальное значения х и у точек объекта и используем эти данные, чтобы нарисовать прямоугольник вокруг объекта. В результате должно получиться графи- ческое выделение или отмена выделения одного или обоих объектов. Помечаем объекты для обратной связи В листинге 12.6 показан код визуализации из программы SELECT Не путайте данный пример с демонстрацией режима выбора' Несмотря на то что в нем для выбора объ- екта на экране задействован режим выбора, нашей целью является демонстрация про- цесса получения (посредством обратной связи) достаточной информации об объекте и рисование прямоугольника вокруг этого объекта с помощью обычных координатных команд Windows Обратите внимание на применение функции glPassThrough для снабжения объекта в буфере кадра меткой непосредственно после вызовов функции glLoadName (снабжает метками объекты в буфере выбора). В режиме визуализации GL_RENDER эти функции игнорируются, они дают эффект только при визуализации с целью выбора или организации обратной связи Листинг 12.6. Код визуализации программы SELECT /////////////////////////////////////////////////////////////////// // Имена объектов #define TORUS 1 #define SPHERE 2 /////////////////////////////////////////////////////////////////// // Визуализируем тор и сферу void DrawObjects(void) { // Записывается состояние матрицы и выполняются повороты glMatrixMode(GL_MODELVIEW); glPushMatrix(); 11 Транслирует всю сцену в поле зрения glTranslatef(-0.75f, O.Of, -2.5f); // Инициализируется стек имен gllnitNames(); glPushName(0); // Устанавливается цвет материала, желтый // тор glColor3f(l.Of, l.Of, O.Of); glLoadName(TORUS); glPassThrough((GLfloat)TORUS);
608 Часть I Классический OpenGL DrawTorus(40, 20); // Рисуем сферу glColor3f(0.5f, O.Of, O.Of); glTranslatef(1.5f, O.Of, O.Of); glLoadName(SPHERE); glPassThrough((GLfloat)SPHERE); DrawSphere(0.5f); // Восстанавливается состояние матрицы glPopMatrix(); // Матрица наблюдения модели } /////////////////////////////////////////////////////////////////// // Вызывается для рисования сцены void RenderScene(void) { // Очищаем окно текущим цветом очистки glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Рисуем объекты на сцене DrawObjects(); // Если что-то выбрано, рисуем вокруг него // ограничивающий прямоугольник if(selectedObject != 0) { int viewport[4]; 11 Получаем поле просмотра glGet!ntegerv(GL_VIEWPORT, viewport); // Повторно отображаем наблюдаемый объем, чтобы // (приблизительно) согласовать его с оконными координатами glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadldentity(); // Устанавливаем объем отсечения (левая, правая, нижняя, // верхняя, ближняя и дальняя плоскости) glOrthofviewport[0], viewport[2], viewport[3], viewport[1], -1, 1); glMatrixMode(GL_MODELVIEW); glDisable(GL_LIGHTING); glColor3f(l.Of, O.Of, O.Of); glBegin(GL_LINE_LOOP); glVertex2i(boundingRect.left, boundingRect.top) ; glVertex2i(boundingRect.left, boundingRect.bottom); glVertex2i(boundingRect.right, boundingRect.bottom); glVertex2i(boundingRect.right, boundingRect.top) ; glEnd(); glEnable(GL_LIGHTING); ) glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glutSwapBuffers();
Глава 12. Интерактивная графика 609 Рис. 12.6. Изображение программы SELECT после щелчка на сфере В данном примере код визуализации разбит на две функции: RenderScene и Dra- wObjects. RenderScene — это обычная высокоуровневая функция визуализации, но фактическое рисование объектов, которые можно выбирать, мы вынесли из этой функции. Функция RenderScene рисует объекты, но также изображает вокруг объ- екта (если он выбран) ограничивающий прямоугольник. Чтобы указать, какой из объектов выбран в данный момент, используется переменная selectedObject. Этап 1: выбор объекта На рис. 12.6 показан результат выполнения рассматриваемого кода визуализации с изображенными тором и сферой. Когда пользователь щелкает на одном из этих объектов, вызывается функция Processselection (см. листинг 12.7). Отметим, что общий принцип действия похож на реализованный нами в двух предыдущих приме- рах (листинги 12.3 и 12.5). Листинг 12.7. Обработка выбора в программе SELECT /////////////////////////////////////////////////////////////////// // Обработка выбора, который инициируется щелчком правой кнопки // мыши при курсоре в положении (xPos, yPos). ♦define BUFFER_LENGTH 64 void Processselection(int xPos, int yPos) ( // Память для буфера выбора static GLuint selectBuff[BUFFER_LENGTH]; // Счетчик соответствий и память поля просмотра
610 Часть I Классический OpenGL GLint hits, viewport[4]; // Настройка буфера выбора glSelectBuffer(BUFFER_LENGTH, selectBuff); // Получаем поле просмотра glGetlntegerv(GL_VIEWPORT, viewport); // Переключаемся на проекцию и записываем матрицу glMatrixMode(GL_PROJECTION); glPushMatrix(); // Меняем режим визуализации glRenderMode(GL_SELECT); // Устанавливаем новый наблюдаемый объем в виде единичного // куба с центром в положении курсора мыши (xPos, yPos), 11 занимающую два пикселя по вертикали и горизонтали glLoadldentity(); gluPickMatrix(xPos, viewport[3] - yPos, 2,2, viewport); // Применяем матрицу перспективной проекции gluPerspective(60.Of, fAspect, 1.0, 425.0); // Рисуем сцену DrawObjects(); I/ Собираем соответствия hits = glRenderMode(GL_RENDER); // Восстанавливаем проекционную матрицу glMatrixMode(GL_PROJECTION); glPopMatrix(); // Возвращаемся к проекции модели для нормальной визуализации glMatrixMode(GL_MODELVIEW); // Если произошло одно совпадение, отображаем информацию if(hits == 1) { MakeSelection(selectBuff[ 3 ] ); if(selectedObject == selectBuff[3]) selectedObject = 0; else selectedObject = selectBuff[3]; } glutPostRedisplay(); ) Этап 2: реализация обратной связи Определив, по какому объекту щелкнул пользователь (зга информация записана в пе- ременной selectedObject), мы настраиваем буфер обратной связи и снова визуали- зируем изображение в режиме обратной связи Код настройки режима обратной связи и вызова функции DrawObjects для повторного рисования сцены с тором и сферой приведен в листинге 12 8 На этот раз, однако, функции glPassThrough помещают маркеры объектов в буфер обратной связи
Глава 12 Интерактивная графика 611 Листинг 12.8. Загрузка и анализ буфера обратной связи /////////////////////////////////////////////////////////////////// // Переходим в режим обратной связи и рисуем прямоугольник // вокруг объекта ♦define FEED_BUFF_SIZE 32768 void MakeSelection(int nChoice) { // Память для буфера обратной связи static GLfloat feedBackBuff[FEED_BUFF_SIZE]; 11 Память для счетчиков и т.п. int size,i,j,count; // Исходные минимальное и максимальное значения boundingRect.right = boundingRect.bottom = -999999.Of; boundingRect.left = boundingRect.top = 999999.Of; // Настройка буфера обратной связи glFeedbackBuffer(FEED_BUFF_SIZE,GL_2D, feedBackBuff); // Входим в режим обратной связи glRenderMode(GL_FEEDBACK); // Перерисовываем сцену DrawObjects(); 11 Покидаем режим обратной связи size = glRenderMode(GL_RENDER); // Анализируем буфер обратной связи и получаем // максимальные и минимальные координаты X и Y окна 1 = 0; while(i < size) { // Поиск подходящего токена if(feedBackBuff[1] == GL_PASS_THROUGH_TOKEN) if(feedBackBuff[i+1] == (GLfloatJnChoice) { i+= 2; // Проходим цикл, пока не дойдем до следующего токена while(i < size && feedBackBuff[i] != GL_PASS_THROUGH_TOKEN) { // Получаем многоугольники if(feedBackBuff[i] == GL_POLYGON_TOKEN) { // Получаем все значения для этого // многоугольника count = (int)feedBackBuff[++i]; // Число вершин for(j =0; j < count; j++) // Цикл для каждой вершины { // Минимальная и максимальная координаты X if(feedBackBuff[1] > boundingRect.right) boundingRect.right = feedBackBuff[i]; if(feedBackBuff[i] < boundingRect.left) boundingRect.left = feedBackBuff[i];
612 Часть I Классический OpenGL // Минимальная и максимальная координаты Y if(feedBackBuff[i] > boundingRect.bottom) boundingRect.bottom = feedBackBuff[i]; if(feedBackBuff[i] < boundingRect.top) boundingRect.top = feedBackBuff[i]; } } else i++; // Получаем следующий индекс //и продолжаем поиск } break; ) Заполнив буфер обратной связи, мы ищем в нем GL_PASS_THROUGH_TOKEN При нахождении этого элемента получаем следующее значение и определяем, его ли мы искали Если — да, остается только циклически пройтись по всем многоугольни- кам этого объекта и определить минимальные и максимальные оконные координаты т и у Эти значения записываются в структуре boundingRect, а затем используют- ся функцией RenderScene для рисования ограничивающего прямоугольника вокруг объекта Резюме Выбор и обратная связь — две мощнейшие особенности OpenGL, способствующие активному взаимодействию пользователя со сценой Выбор и отбор используются для определения объекта или области сцены в координатах OpenGL, а не в оконных координатах Обратная связь возвращает ценную информацию о том, как объект или примитив действительно нарисован в окне. Эту информацию можно применить для того, чтобы реализовать в сцене такие элементы, как текст или ограничивающие прямоугольники
Глава 12 Интерактивная графика 613 Справочная информация gIFeedbackBuffer Цель: Установить буфер, используемый для данных обратной связи Включаемый файл: <gl.h> Синтаксис: void gIFeedbackBuffer(GLsizei size, GLenum type, GLfloat *buffer); Описание: Устанавливает буфер обратной связи и тип данных о вершинах. Обратная связь является одним из режимов визуализации, при котором OpenGL визуализирует данные не в буфере кадров, а посылает их в буфер, заданный переменной *buffer Такие блоки данных могут включать координаты точек х, у, z и w (в координатах окна); информацию о цвете для режима индексирования цвета или режима RGBA, а также текстурные координаты Объем и тип информации задается аргументом type Параметры: size (тип GLsizei) Максимальное число позиций, выделенных для *buf fer Если блок данных, записываемых в буфер обратной связи, превысит объем выделенной памяти, в буфер будет записана только часть блока, соответствующая по размерам выделенному буферу type (тип GLenum) Задает тип данных о вершинах, возвращаемых в буфер обратной связи Каждая вершина генерирует блок данных в буфере обратной связи Для всех указанных ниже типов блок данных содержит идентификатор токена примитива, за которым следуют данные вершин. По сути, данные вершин включают следующую информацию GL_2D пары координат х и у GL_3D тройки координат х, у и z GL_3D_COLOR- координаты х, у, z и информация о цвете (одно значения для режима индексирования, четыре — для режима RGBA) GL_3D_COLOR_TEXTURE координаты х, у, z, информация о цвете (одно или четыре значения); плюс четыре текстурных координаты. Если используется несколько текстур, возвращаются только координаты, соответствующие первой из них GL_4D_COLOR_TEXTURE. координаты х, у, z и ш, информация о цвете (одно или четыре значения), плюс четыре текстурных координаты buffer (тип GLfloat*) Что возвращает: Буфер, в котором будут храниться данные обратной связи Ничего См. также: glPassThrough, glRenderMode, glSelectBuffer
614 Часть I Классический OpenGL gllnitNames Цель: Инициализировать стек имен Включаемый файл: <gl,h> Синтаксис: void gllnitNames(void ); Описание: При визуализации в режиме выбора стек имен позволяет называть примитивы или группы примитивов, соотнося с ними целое число без знака Каждое имя, присваиваемое примитиву, заносится в стек имен с помощью glPushName или замещает текущее имя с помощью glLoadName Данная функция устанавливает стек имен в исходное состояние, когда в нем нет ни одного имени Что возвращает: Ничего См. также: gllnitNames, glPushName, glRenderMode, glSelectBuffer glLoadName Цель: Загрузить имя в стек имен Включаемый файл: <gl.h> Синтаксис: void glLoadName(GLuint name); Описание: Помещает заданное имя в верхнюю позицию стека имен При визуализации в режиме выбора стек имен позволяет именовать примитивы или группы примитивов Имя, заданное с помощью данной функции, в действительности замещает текущее имя в стеке имен Параметры: (тип GLuint) Что возвращает: Задает имя, помещаемое в стек имен Выбираемые имена представляют собой целые числа без знака Ничего См. также: gllnitNames, glPushName, glRenderMode, glSelectBuffer glPassThrough Цель: Поместить маркер в буфер обратной связи Включаемый файл: <gl,h> Синтаксис: void glPassThrough(GLfloat token); Описание: При работе OpenGL в режиме обратной связи пиксели в буфере кадров нс рисуются Вместо этого информация об изображаемых примитивах помещается в буфер обратной связи Функция позволяет помещать токен gl_pass_through_token среди данных буфера обратной связи, размещая за ним значение с плавающей запятой, заданное переменной token Вызывается в коде визуализации и проявляется только в режиме обратной связи
Глава 12 Интерактивная графика 615 Параметры: token (тип GLfloat) Что возвращает: См. также: Значение, помещаемое в буфер обратной связи после GL_PASS_THROUGH_TOKEN Ничего gIFeedbackBuffer, glRenderMode gIPopName Цель: Вытолкнуть (удалить) верхнюю позицию из стека имен Включаемый файл: <gl.h> Синтаксис: void gIPopName(void); Описание: Стек имен используется в режиме выбора для идентификации команд рисования Функция удаляет имя из верхней позиции стека имен. Текущую глубину стека имен можно получить, вызвав glGet с аргументом GL_NAME_STACK_DEPTH Применение операции выталкивания к пустому стеку имен дает ошибку OpenGL (см. glGetError) Что возвращает: Ничего См. также: gllnitNames, glLoadName, glRenderMode, glSelectBuffer, gIPushName gIPushName Цель: Задать имя, которое будет помещено в стек имен Включаемый файл: <gl.h> Синтаксис: void gIPushName(GLuint name); Описание: Стек имен используется в режиме выбора для идентификации команд рисования. Функция помещает имя в стек имен, чтобы позже с его помощью идентифицировать команды рисования. Максимальную глубину стека имен можно получить, вызвав glGet с аргументом GL_MAX_NAME_STACK_DEPTH, а текущую глубину — вызвав glGet с аргументом GL_NAME_STACK_DEPTH Максимальная глубина стека имен может зависеть от реализации, но все реализации обязаны поддерживать по крайней мерс 64 позиции Применение операции помещения в стек при превышении его глубины генерирует ошибку OpenGL и скорее всего будет проигнорировано Параметры: пате (тип GLuint) Имя, помещаемое в стек имен Что возвращает: Ничего См. также: gllnitNames, glLoadName, glRenderMode, glSelectBuffer, gIPopName
616 Часть I Классический OpenGL glRenderMode Цель: Установить один из трех режимов растеризации Включаемый файл: <gl. h> Синтаксис: GLint glRenderMode(GLenum mode}; Описание: При вызове функций рисования OpenGL может находиться в одном из трех режимов GL_RENDER: Режим визуализации (по умолчанию) Функции рисования порождают пиксели в буфере кадров GL_SELECT Режимы выбора Буфер кадров нс модифицируется Вместо этого выполняется запись в примитивы записи буфера выбора, которые рисовались бы в объеме наблюдения Буфер выбора нужно задать и выделить ему память заблаговременно, используя функцию gISelectBuffer GL_FEEDBACK: Режим обратной связи Буфер кадров не модифицируется Вместо этого координаты и атрибуты вершин, которые визуализировались бы в режиме визуализации, записываются в буфер обратной связи. Буфер обратной связи нужно задать и выделить ему память заблаговременно, используя функцию glFeedbackBuffer Параметры: mode (тип GLenum) Задает режим растеризации Возможны следующие значения- GL_RENDER, GL_SELECT или GL_FEEDBACK Значение по умолчанию равно GL_RENDER Что возвращает: Возвращаемое значение зависит от режима растеризации, установленного при предыдущем вызове функции GL_RENDER нуль GL_SELECT- число записей, внесенных в буфер выбора GL_FEEDBACK: число значений, занесенных в буфер обратной связи (обратите внимание на то, что это не то же, что и число записанных вершин) См. также: glFeedbackBuffer, gllnitNames, glLoadName, glPassThrough, glPushName, gISelectBuffer gISelectBuffer Цель: Установить буфер, используемый для значений выбора Включаемый файл: <gl.h> Синтаксис: void gISelectBuffer(GLsizei size, GLuint *buffer}; Описание: При работе OpenGL в режиме выбора (GL_SELECT) команды рисования не порождают пикселей в буфере кадров Вместо этого создаются записи совпадений, заносимые в буфер выбора, заданный этой функций Каждая запись совпадения состоит из следующих данных число имен в стеке имен на момент совпадения
Глава 12 Интерактивная графика 617 • минимальное и максимальное значения z всех вершин примитивов, пересекающих наблюдаемый объем, это значение масштабируется в диапазон от 0 0 до 1 0, • содержимое стека имен на момент совпадения, начиная с нижнего элемента Параметры: size (тип GLsize) buffer (тип GLuint*) Что возвращает: Число значений, которые можно записать в буфер, заданные посредством *buffer Указатель на память, в которую будут занесены записи совпадений Ничего См. также: glFeedbackBuffer, gllnitNames, glLoadName, glPushName, glRenderMode gluPickMatrix Цель: Определить указываемую область, которую можно использовать для идентификации выбора пользователя Включаемый файл: <glu.h> Синтаксис: void gluPickMatrix(GLdouble x, GLdouble y, GLdouble width, GLdouble height, GLint viewport[4]); Описание: Создает матрицу, на основе экранных координат определяющую меньший наблюдаемый объем, используемый в операции выбора. Применяя координаты указателя мыши и эту функцию в режиме выбора, вы можете определить, какой из объектов находит под курсором или рядом с ним Создаваемая при этом матрица умножается на текущую матрицу проекции Перед вызовом функции нужно вызвать glLoadldentity, а затем умножить полученную матрицу на матрицу перспективной проекции, использованную изначально для получения наблюдаемого объема Если вы используете gluPickMatrix для выбора поверхностей NURBS, перед использованием функции следует отключить свойство NURBS GLU_AUTO_LOAD_MATRIX Параметры: Центр обозначаемой области в координатах окна (тип GLdouble*) width, height (тип GLdouble*) viewport (тип GLint [4]) Ширина и высота желаемой области выбора в координатах окна Текущее поле просмотра Чтобы определить текущее поле просмотра, можно вызвать функцию glGetlntegerv с аргументом GL_VIEWPORT Что возвращает: Ничего См. также: glGet, glLoadldentity, glMultMatrix, glRenderMode, gluPerspective

ЧАСТЬ II OpenGL повсюду Одной из наиболее рекламируемых особенностей и преимуществ применения OpenGL для визуализации является широкая доступность этого интерфейса на различных аппаратных платформах и операционных системах Хотя функциональные возможности основной библиотеки OpenGL согласованы, при переносе программ с одной платформы на другую наблюдаются несколько особенностей До этого момента для обеспечения переносимости исходного кода в программах-примерах мы использовали GLUT Действительно, GLUT великолепно подходит для обучающих целей, однако профессиональные разработчики программного обеспечения знают, что для создания высококачественного нетривиального приложения часто требуется внедрить в код элементы, связанные с конкретной операционной системой, или обеспечить поддержку элементов GUI, которые пользователи ожидают получить в программе, запущенной в привычной им среде В трех последующих главах рассматривается использование OpenGL на трех популярных платформах Каждая операционная система имеет собственные преимущества и особенности, проявляющиеся при работе с OpenGL Все главы начинаются с описания общей проблемы настройки и запуска OpenGL, причем внимание акцентируется на особенностях OpenGL, специфических для рассматриваемой платформы, и на максимизации производительности на данной платформе

ГЛАВА 13 Wiggle: OpenGL в системе Windows Ричард С Райт-мл. Из ЭТОЙ ГЛАВЫ ВЫ УЗНАЕТЕ . . Действие Функция Запросить/выбрать ChoosePixelFormat/DescribePixelFormat/ пиксельный формат OpenGL SetPixelFormat Создать и использовать wglCreateContext/wglDeleteContext/ контекст визуализации OpenGL wglMakeCurrent Отреагировать на сообщение окна WM_PAINT/WM_CREATE/WM_DESTROY/WM_SIZE Использовать двойную SwapBuffers буферизацию в Windows OpenGL относится к чисто графическим API (Application Programming Interface — программный интерфейс приложения) со взаимодействием с пользователем и обра- боткой экрана или окна посредством среды хоста. Чтобы способствовать этому парт- нерству, каждая среда обычно имеет некоторое расширение, которое “приклеивает” OpenGL к собственным функциям управления окнами и пользовательского интер- фейса Данный “клей” — это код, который соотносит команды рисования OpenGL с конкретным окном Кроме него необходимы еще функции установки режимов бу- феров, насыщенности цвета и других характеристик процесса рисования. В системе Microsoft Windows данный код-клей реализован в виде набора функ- ций, добавленных к программному интерфейсу Windows Обычно их называют wiggle functions, поскольку они начинаются с префикса wgl, а не gl. Эти функции описыва- ются в данной главе, где мы отходим от использования библиотеки GLUT в структуре OpenGL и создаем завершенные приложения Windows, использующие преимущества всех возможностей операционной системы. Вы увидите, какими характеристиками должно обладать окно Windows для поддержки графики OpenGL Кроме того, вы узнаете, какие сообщения должно обрабатывать “правильное” окно OpenGL и как именно оно должно их обрабатывать Концепции в данной главе вводятся посте- пенно в ходе построения модельной программы OpenGL, формирующей основу для поддержки OpenGL в системе Windows До этого момента вам нс требовались предварительные знания о трехмерной гра- фике, необходимы были лишь базовые знания по программированию на С В данной же главе предполагается, что вы имеете хотя бы представление о программировании
622 Часть II OpenGL повсюду в Windows В противном случае нам пришлось бы написать книгу вдвое больше- го размера, в которой больше внимания уделялось бы деталям программирования в системе Windows и меньше — элементам программирования с помощью OpenGL Если система Windows для вас нова или вы делаете первые шаги в одном из кар- касов приложений и совсем не знакомы с процедурами Windows, маршрутизацией сообщений и тому подобным, рекомендуем обратиться к одной из книг, указанных в приложении А, “Что еще почитать”, а затем продолжить изучение главы Реализации OpenGL в системе Windows OpenGL стал доступен на платформе Win32 с выходом Windows NT версии 3 5. Позже он выпускался как дополнение к Windows 95, а после этого вошел в по- ставляемую версию операционной системы Windows 95 (OSR2) В настоящее время OpenGL является “родным” прикладным интерфейсом всех платформ Win32 (Win- dows 95/98/МЕ, Windows NT/2000/XP/2003), и его функции экспортируются из биб- лиотеки openg!32. dll Следует знать о четырех разновидностях OpenGL в системе Windows Generic, ICD, MCD и Extended. Все они имеют достоинства и недостатки как с точки зрения пользователя, так и с точки зрения разработчика По крайней мере вы должны иметь общее представление о том, как работают эти реализации и каков их отрицательные стороны. Общий OpenGL Общая реализация OpenGL (Generic OpenGL) представляет собой просто программ- ную реализацию, которая не использует специфическое аппаратное обеспечение с поддержкой трехмерной графики Реализация Microsoft, связанная с Windows, явля- ется именно общей реализацией Существует еще реализация OpenGL для Windows от Silicon Graphics Incorporated (SGI), которая в настоящее время уже не имеет широкого распространения Эта реализация использовала инструкции ММХ, однако соответ- ствующее оборудование не считается специализированным аппаратным обеспечени- ем с поддержкой трехмерной графики, поэтому ее также принято относить к общим программным реализациям Другая реализация, названная MESA (www.mesa3d.org), не является, строго говоря, реализацией OpenGL — она “работает сходным обра- зом”, — однако в большинстве случаев ее удобно считать реализацией OpenGL MESA также можно привязать к аппаратному обеспечению, но в этом случае данный интер- фейс следует рассматривать как частный случай мини-драйвера (см ниже) Хотя реализация MESA долгое время обновлялась согласно текущим наборам функций OpenGL, общая реализация Microsoft не обновлялась с момента выхода OpenGL версии 1 1. Впрочем, это не должно вас беспокоить, поскольку скоро мы покажем, как получить все функциональные возможности OpenGL, которые поддер- живает видеокарта.
Глава 13 Wiggle OpenGL в системе Windows 623 ICD ICD (Installable Client Driver - устанавливаемый драйвер клиента) был первым аппа- ратным интерфейсом драйвера, предложенным в системе Windows NT В ICD должен быть реализован весь конвейер OpcnGL с использованием комбинации программного обеспечения и специфического аппаратного обеспечения, для которого он был напи- сан Создание драйвера ICD с нуля представляет собой большой объем работы, всю ответственность за выполнение которой берет на себя производитель. ICD объединен с реализацией OpenGL от Microsoft. При обработке вызовов OpenGL приложения, связанные с opengl32.dll, автоматически направляются к ко- ду драйвера ICD Этот механизм идеален, поскольку для использования преимуществ нового аппаратного обеспечения OpenGL не требуется перекомпиляция приложения По сути, ICD — это часть драйвера дисплея, и он нс влияет на существующую системную библиотеку openGL32.dll. Описанная модель драйвера дает произво- дителю наиболее широкие возможности с точки зрения оптимизации комбинации “драйвер/устройство” MCD MCD (Mini-Client Driver — мини-драйвер клиента) представляет собой компромисс между программной и аппаратной реализациями. Большая часть первых устройств ПК с поддержкой трехмерной графики предлагали растеризацию только с аппарат- ным ускорением (см. раздел “Конвейер” в главе 2, “Используя OpenGL”) Модель драйвера MCD позволила приложениям использовать общую реализацию Microsoft для получения возможностей, недоступных на аппаратном уровне Например, сред- ства преобразования и освещения могли поставляться программным обеспечением OpcnGL от Microsoft, но реальная визуализации освещенных и затененных треуголь- ников обрабатывалась аппаратным обеспечением. Реализация драйвера MCD облегчает производителям аппаратного обеспечения создание драйверов для OpcnGL Поскольку большая часть работы была выполнена Microsoft, элементы, которые производители не реализовали на аппаратном уровне, передаются для обработки общей реализации Microsoft. Модель драйвера MCD открывала большие возможности в сфере переноса OpcnGL на рынок ПК Производителям аппаратного обеспечения был предложен набор инструментов для разработки программного обеспечения (Software Develop- ment Kit — SDK), изначально доступный только для Windows NT, что позволило им создать драйверы MCD для Windows 95 После того как мно1ие производите- ли аппаратного обеспечения создали драйверы MCD, компания Microsoft решила не лицензировать код открытой версии Благодаря этому на потребительском рынке временное преимущество получил принадлежащий Microsoft интерфейс поддержки трехмерной графики В настоящее время модель драйвера MCD большей частью вышла из употребле- ния, но несколько се реализаций все еще используется. Одной из причин “смерти” этой технологии стала невозможность эффективной поддержки наложения текстуры посредством AGP (Accelerated Graphics Port — ускоренный графический порт) от In- tel. Другой причиной явилось то, что SGI стала выпускать оптимизированный набор драйверов ICD для производителей, который сделал написание драйверов ICD почти
624 Часть II OpenGL повсюду таким же легким, как и написание драйверов MCD. (Этот шаг был ответом на вре- менный отказ Microsoft от поддержки OpenGL в системе Windows 95.) В настоящее время этот набор драйверов и модель заменен SDK и моделью MCD. Мини-драйвер Мини-драйвер в действительности не является драйвером дисплея. Точнее, его сле- дует называть ложной заменой библиотеки opengl32.dll, формирующей вызовы к драйверам аппаратного обеспечения с поддержкой трехмерной графики. Обычно такие мини-драйверы преобразуют вызовы OpenGL в приблизительные эквиваленты вызовов интерфейсов трехмерной графики от производителей аппаратного обеспече- ния. Первый мини-драйвер был написан компанией 3Dfx для их графической карты Voodoo Эта ложная библиотека DLL преобразовывала вызовы OpenGL в “родной” для Voodoo интерфейс Glide (программный интерфейс приложений трехмерной гра- фики от компании 3Dfx). Как известно, специалисты компании Id Software вначале написали версию попу- лярной игры Quake, в которой применялся OpenGL и которая работала с указанным выше мини-драйвером 3Dfx По этой причине мини-драйверы, разрабатываемые для других графических карт, иногда называли “драйверами Quake”. Многие произво- дители высокоуровневого аппаратного обеспечения OpenGL саркастически называли потребительские видеокарты “ускорителями Quake” и считали, что они не выдер- живают сравнения с их аппаратным обеспечением. Многие производители аппарат- ного обеспечения для игр подстроились под массовое движение и начали создавать мини-драйверы для своего оборудования. Хотя они популяризировали использование OpenGL в играх, мини-драйверы часто не имели всех функциональных возможно- стей OpenGL. Любое приложение, использующее OpenGL, не обязательно работало с мини-драйвером. Как правило, такие драйверы обеспечивали только минимальные функциональные возможности, необходимые для запуска популярных игр. К счастью, популярность и распространенность OpenGL привела к устареванию мини-драйверов на новейших потребительских ПК Тем не менее их еще можно встретить на старых ПК или системах со старым графическим оборудованием. Счи- тается, что преемником мини-драйвера стала интерфейсная библиотека DLL, прини- мающая вызовы функций OpenGL и преобразующая их в функциональные возмож- ности Direct 3D Расширенный OpenGL Если вы разрабатываете программное обеспечение для любой версии Microsoft Win- dows, то скорее всего будете использовать заголовочные файлы и библиотеки им- порта, работающей с opengl32.dll производства Microsoft. Названная библиотека DLL разработана для общей нейтрализации ошибок (или программной визуализа- ции), если аппаратное обеспечение с поддержкой трехмерной графики не установле- но, и является координирующим механизмом, работающим с официальной моделью драйвера OpenGL для аппаратных реализаций OpenGL Использование заголовочного файла и библиотеки импорта само по себе гарантирует доступ только к функциям и возможностям, присутствующим в OpenGL 1 1
Глава 13 Wiggle: OpenGL в системе Windows 625 Во вступлении мы кратко рассказывали об элементах, добавленных в OpenGL после версии 1.1, заканчивая версией OpenGL 2.0 с интегрированным языком затене- ния. Обратите внимание на то, что OpenGL 1.1 также является мощным и полноцен- ным графическим API, который подходит для множества графических приложений, включая игры и приложения бизнес-графики. Даже без дополнительных элементов OpenGL 1.2 (и последующих дополнений) производительность графического аппа- ратного обеспечения увеличивается на порядок, и в большинстве графических карт для ПК на специальном аппаратном обеспечении реализован весь конвейер OpenGL Помните, что OpenGL 1.1 может давать удивительно быструю и весьма сложную трехмерную визуализацию! Тем не менее многие приложения требуют использования новейших достижений OpenGL (или по крайней мере существенно выигрывают от этого). Чтобы получить доступ к самым свежим возможностям OpenGL (которые поддерживаются довольно широко), следует использовать механизм расширения OpenGL, с помощью которого вы обращаетесь к специфическим (зависящим от производителя) элементам OpenGL О расширениях OpenGL рассказывалось в главе 2, а специфика применения этого ме- ханизма расширений в системе Windows рассмотрены в разделе этой главы “OpenGL и расширения WGL”. Возможно, вы думаете, что среда, в которой разрабатывается трехмерная графика, довольно бестолковая и неупорядоченная (особенно если вы планируете переносить приложения, скажем, на платформу Macintosh, где элементы OpenGL обновляется с каждым выпуском операционной системы). Тем не менее существуют стратегии, позволяющие сделать подобную разработку более управляемой. Для начала можно вызвать следующую функцию, чтобы во время выполнения приложение сообщило, какую версию OpenGL поддерживает аппаратный драйвер. gIGetString(GL_VERSION); Описанным способам вы элегантно выясняете, сможет ли приложение полноценно заработать в системе пользователя. Поскольку OpenGL и его расширения загружаются динамически, нет никаких причин, мешающих вашей программе по крайней мерс запуститься и представить пользователю дружественное и информативное сообщение об ошибке или диагностическое сообщение. Кроме того, нужно аккуратно спланировать, какие элементы OpenGL должно иметь приложение. Можно ли написать его так, чтобы оно использовало только элементы OpenGL 1.1? Можно ли будет использовать приложение без аппаратной поддержки, когда пользователь полагается только на встроенные программные сред- ства визуализации? Если на любой из этих вопросов вы ответили утвердительно, вначале код визуализации приложения нужно написать, используя только библиотеку импорта из OpenGL 1.1. Таким образом вы получите для приложения наибольшую потенциальную аудиторию. Когда базовый код визуализации готов, стоит вернуться назад и рассмотреть вопро- сы оптимизации производительности или специальные эффекты, доступные посред- ством новых элементов OpenGL, которые вы хотите ввести в программу Предусмот- рев в начале программы проверку версии OpenGL, можно реализовывать несколько схем визуализации или функций, которые в определенных ситуациях смогут повысить производительность или дать дополнительные эффекты при визуализации Например, статические карты текстуры могут заменяться программами обработки фрагментов,
626 Часть II OpenGL повсюду а стандартный туман — объемным, сформированным с помощью программ обработки вершин Использование этих мощных элементов позволяет представить программу в выгодном свете, но если полагаться исключительно на них, вы существенно сокра- тите аудиторию и объемы продаж Тем нс менее иногда приложение действительно должно полагаться на новые элементы OpenGL. Например, медицинский пакет визуализации может требовать наличия трехмерного текстурирования или подмножестве построения изображений В подобных нишах узкоспециализированного или вертикального рынка запуск при- ложения будет требовать просто минимальной поддержки OpenGL В таких случаях для программного обеспечения помимо другим минимальных системных требований нужно указывать требуемую версию OpenGL Как и раньше, необходимую проверку приложение может производить при запуске Стандартная визуализация Windows Библиотека GLUT предоставляет только одно окно, и вызов функций OpenGL все- гда завершается выводом результата в это окно Реальные же приложения Windows очень часто имеют более одного окна. Диалоговые окна, средства управления и даже меню по сути являются окнами; создать полезную программу, содержащую только одно окно практически невозможно Так откуда же OpenGL при выполнении кода визуализации узнает, где рисовать9 Прежде чем ответить на этот вопрос, напомним, как мы вообще рисуем в окне без использования OpenGL Контекст устройства GDI Обычно при рисовании окна без использования OpenGL вы работаете с функция- ми интерфейса графического устройства (Graphics Device Interface — GDI) Windows Каждое окно имеет контекст устройства, который фактически получает графическую информацию, а все функции GDI принимают контекст устройства в качестве аргумен- та, указывающего, на какое окно должна действовать функция Можно использовать несколько контекстов устройства, но каждое из них должно отвечать одному окну На прилагающемся к книге компакт-диске записана программа WINRECT, которая рисует красный квадрат на синем фоне Результат выполнения этой программы, по- казанный на рис 13 1, должен быть вам знаком Практически такое же изображение создавалось второй программой OpenGL, приведенной в главе 2 (GLRECT) В от- личие от того примера в программе WINRECT не используется библиотека GLUT, в данном случае мы обошлись только API Windows Соответствующий код можно назвать характерным для программирования в среде Windows Функция WinMain за- пускает машину и поддерживает поток сообщений, а функция WndProc обрабатывает сообщения для основного окна Вы должны быть знакомы с программированием в Windows на таком уровне, чтобы представлять в деталях процесс создания и отображения окна, поэтому мы рассмотрим только код, отвечающий за рисование фона и квадрата, и нс будем при- водить программу полностью Итак, вначале нужно создать синюю и красную кисти для заполнения и рисования
Глава 13. Wiggle: OpenGL в системе Windows 627 Рис. 13.1. Результат выполнения программы WINRECT Обработчики этих кистей объявляются глобально. // Обработчики кистей GDI, которые мы будем использовать // для рисования HBRUSH hBlueBrush,hRedBrush; Затем в функции WinMain с помощью макроса RGB создаются красная и синяя кисти. // Для операций рисования и заполнения создаются синяя //и красная кисти // Указывается интенсивность красной, зеленой, // синей составляющих цвета hBlueBrush = CreateSolidBrush(RGB( 0, 0, 255)); hRedBrush = CreateSolidBrush(RGB( 255, 0, 0)); При задании стиля окна указывается, что при рисовании фона в структуре класса, соответствующей окну, будет задействована синяя кисть. wc.hbrBackground = hBlueBrush; // Для рисования фона применяется синяя кисть Размер и положение окна (ранее установленные посредством glutPositionWin- dow и glutReshapeWindow) задаются при создании окна. // Создается основное окно приложения hWnd = CreateWindow( IpszAppName, IpszAppName, WS_OVERLAPPEDWINDOW, 100, 100, // Размер и положение окна 250, 250, NULL, NULL, hlnstance, NULL); Наконец, собственно рисование внутренних элементов окна обрабатывается в функции WndProc обработчиком сообщений WM_PAINT. case WM_PAINT: { PAINTSTRUCT ps; HBRUSH hOldBrush;
628 Часть II OpenGL повсюду Н Начало рисования BeginPaint(hWnd,&ps); // Выбирается и применяется красная кисть hOldBrush = Selectobject(ps.hdc,hRedBrush); // Рисуется прямоугольник, закрашиваемый текущей // выбранной кистью Rectangle(ps.hdc,100,100,150,150); // Отменяется выбор кисти SelectObject(ps.hdc,hOldBrush); // Конец рисования EndPaint(hWnd,&ps); } break; Вызов BeginPaint подготавливает окно для рисования и устанавливает элемент hdc структуры PAINTSTRUCT равным контексту устройства, который будет исполь- зован для рисования в этом окне Этот обработчик контекста устройства исполь- зуется как первый параметр во всех функциях GDI и определяет, с каким окном должны работать эти функции. Затем выбирается красная кисть для операций рисо- вания, и рисуется сплошной прямоугольник с вершинами в точках с координатами (100,100,150,150). Затем отменяется выбор кисти, и функция EndPaint заканчивает операцию рисования Прежде чем вы придете к заключению, что точно так же должен работать OpenGL, вспомните, что интерфейс GDI работает в среде Windows. Другие среды не име- ют контекстов устройства, обработчиков окна и тп Хотя общие принципы могут быть похожими, они определенно не идентичны и могут вести себя по-разному С другой стороны, OpenGL был разработан как интерфейс, абсолютно переноси- мый между различными средами и аппаратными платформами (и в любом случае OpenGL появился раньше, чем Windows!). Если добавить к функциям OpenGL па- раметр контекста устройства, код OpenGL станет бесполезен на любой платформе, отличной от Windows. Тем не менее OpenGL имеет идентификатор контекста, который называется кон- текстом визуализации Контекст визуализации во многом похож на контекст устрой- ства GDI, поскольку это контекст визуализации, который помнит текущие цвета, уста- новки состояния и тому подобное, подобно тому, как контекст устройства старается сохранить текущий цвет кисти или пера Пиксельные форматы Концепция Windows контекста устройства в сфере трехмерной графики ограничена, поскольку она разрабатывалась для приложений двухмерной графики В Windows вы запрашиваете идентификатор контекста устройства для любого окна Контекст устройства зависит от типа устройства Если рабочий стол отображается в 16-битовом цвете, контекст устройства Windows понимает только 16-биговый цвет Например,
Глава 13. Wiggle- OpenGL в системе Windows 629 вы не можете сообщить Windows, что одно окно должно отображаться в 16-битовом цвете, а другое — в 8-битовом. Хотя Windows позволяет создавать контекст устройства в памяти, все равно нужно предоставить для эмуляции существующий контекст устройства (в данном случае — контекст окна). Даже если в качестве параметра окна передано значение NULL, Win- dows будет использовать контекст устройства, соответствующий рабочему столу. Про- граммист никак не может контролировать внутренние характеристики контекста окна Любое окно или устройство, которое будет визуализировано средствами трехмер- ной графики, имеет гораздо больше характеристик, чем просто насыщенность цвета, особенно если использовать аппаратное устройство визуализации (графическую кар- ту с поддержкой трехмерной графики). До этого момента мы позволяли библиотеке GLUT самой заниматься всем этим. При инициализации GLUT мы сообщали, какие буферы требуются (двойной или обычный буфер цвета, буфер глубины, трафарета и значения альфа). Прежде чем OpenGL сможет визуализировать окно, его нужно сконфигурировать согласно требованиям к визуализации. Какая визуализации нужна — аппаратная или программная? Требуется использовать обычную или двойную буферизацию9 Какие требования предъявляются к буферам трафарета, целевого значения альфа или накоп- ления? Чтобы перейти от окна, характеризуемого только буферами глубины и цвета, к окну с буферами трафарета и цвета, требуется вначале уничтожить первое окно и заново создать новое окно с нужными характеристиками. Описание пиксельного формата Трехмерные характеристики окна задаются один раз, обычно сразу после создания окна. В сумме все эти настройки называют пиксельным форматом. В Windows пик- сельный формат описывается структурой PIXELFORMATDESCRIPTOR Данная струк- тура определяется следующим образом: typedef struct tagPIXELFORMATDESCRIPTOR { WORD nSize; WORD nVersion; // Размер структуры // Версия структуры (должно быть 1) DWORD dwFlags; // Свойства буфера пикселей BYTE iPixelType; // Тип пиксельных данных (RGBA // или индексирование цвета) BYTE cColorBits; // Число битовых плоскостей цвета //в буфере цвета BYTE cRedBits; // Сколько битов описывает красный цвет BYTE cRedShift; // Смещение битов красного цвета BYTE cGreenBits; // Сколько битов описывает зеленый цвет BYTE cGreenShift; // Смещение битов зеленого цвета BYTE cBiueBits; // Сколько битов описывает синий цвет BYTE cBlueShift; // Смещение битов синего цвета BYTE cAlphaBits; // Сколько битов описывает альфа-фактор цели BYTE cAlphaShift; // Смещение битов альфа-фактора цели BYTE cAccumBits; // Сколько битов описывает буфер накопления BYTE cAccumRedBits; // Сколько битов описывает красный цвет // в буфере накопления BYTE cAccumGreenBits; // Сколько битов описывает зеленый цвет // в буфере накопления
630 Часть II OpenGL повсюду BYTE cAccumBlueBits; // Сколько битов описывает синий цвет // в буфере накопления BYTE cAccumAlphaBits; // Сколько битов описывает альфа-фактор // в буфере накопления BYTE cDepthBits; // Сколько битов выделено на буфер глубин BYTE cStencj.lBj.ts; // Сколько битов выделено на буфер трафарета BYTE cAuxBuffers; // Сколько дополнительных буферов BYTE iLayerType; // Устаревший параметр - игнорируется BYTE bReserved; // Число плоскостей сверху и снизу DWORD dwLayerMask; // Устаревший параметр - игнорируется DWORD dwVisibleMask; // Прозрачный цвет плоскости снизу DWORD dwDamageMask; // Устаревший параметр - игнорируется } PIXELFORMATDESCRIPTOR; Для устройства OpenGL (аппаратного или программного) значения этих элементов нс являются произвольными Для окна доступно только ограниченное число пиксель- ных форматов. Говорят, что пиксельные форматы экспортируются драйвером OpenGL или программной схемой визуализации Большинство этих элементов структуры по- нятны, но некоторые требуют дополнительных объяснений nSize Размер структуры, устанавливается равным sizeof(PIXELFORMATDESCRIPTOR); n Ver si on Устанавливается равным 1 dwFlags Набор битовых меток, задающих свойства буфера пикселей Большинство из меток нс являются взаимоисключающими, но некоторые применяются только по запросу или при описании пиксельного формата Приемлемые значения меток перечислены в табл 13 1 iPixelType Тип буфера цвета Допустимы только два значения PFD_TYPE_RGBA И PFD_TYPE_COLORINDEX PFD_TYPE_COLORINDEX позволяет запрашивать или описывать формат пикселя как режим с индексированием цвета Для современного аппаратного обеспечения этот режим визуализации считается устаревшим cColorBits Число битов, представляющих насыщенность цвета в буфере цвета Типичные значения 8, 16, 24 и 32 32-битовыс буферы цвета могут использоваться для хранения целевых значений альфа Целевые значения альфа поддерживается только в общих реализациях Microsoft в системах Windows 2000, Windows ХР и более поздних cRedBits Число битов в буфере цвета, выделенных для хранения красного компонента цвета cGreenBits Число битов в буфере цвета, выделенных для хранения зеленого компонента цвета cBlueBits Число битов в буфере цвета, выделенных для хранения синего компонента цвета cAlphaBj.es Число битов, используемых для одной записи в буфере альфа Целевое значение альфа нс поддерживается общей реализацией Microsoft, но поддерживается многими аппаратными реализациями
Глава 13 Wiggle OpenGL в системе Windows 631 cAccumBits cDepthBits cStencilBits cAuxBuffers iLayerType bReserved dwLayerMask dwVisibleMasк dwDamageMask Число битов, используемых для одной записи в буфере накопления Число битов, используемых для одной записи в буфере глубины. Типичные значения. О, 16, 24 и 32 Чем больше битов выделено на буфер глубины, чем точнее будет проверка по глубине Число битов, используемых для одной записи в буфере трафарета Число дополнительных буферов. В реализациях, поддерживающих вспомогательные буферы, визуализацию можно перенаправить из буфера цвета в дополнительный буфер, а позже переключить этот буфер на экран Параметр устарел (игнорируется) Число поддерживаемых реализацией накладывающихся и перекрываемых плоскостей. Биты 0-3 задают число накладывающихся плоскостей (до 15), а биты 4-7 — число перекрываемых плоскостей (также до 15) Параметр устарел (игнорируется) Прозрачный цвет перекрываемой плоскости Параметр устарел (игнорируется) Перечисление пиксельных форматов Пиксельный формат для окна идентифицируется целочисленным индексом Экспор- тируется несколько пиксельных форматов, из которых мы можем выбирать Чтобы задать пиксельный формат для окна, нужно выбрать один из доступных форматов, экспортированных драйвером Чтобы определить характеристики пиксельного фор- мата, можно использовать функцию DescribePixelFormat. Кроме того, с помощью данной функции можно определить, сколько пиксельных форматов экспортировано драйвером. Для перечисления всех форматов, доступных для окна, можно применить следующий код PIXELFORMATDESCRIPTOR pfd; // Дескриптор пиксельного формата int nFormatCount; // Число экспортированных // пиксельных форматов // Получить число пиксельных форматов // Потребуется контекст устройства pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); nFormatCount = DescribePixelFormat(hDC, 1, 0, NULL); // Извлечь все пиксельные форматы for(int i = 1; i <= nFormatCount; i++) { // Получить описание пиксельного формата DescribePixelFormat(hDC, i, pfd.nSize, &pfd);
632 Часть II OpenGL повсюду ТАБЛИЦА 13.1. Метки, допустимые при описании буфера визуализации пикселей Битовая метка Описание PFD_DRAW_TO_WINDOW PFD_DRAW_TO_BITMAP Выход буфера отображается в окне Выход буфера записывается в растровое изображение Windows PFD_SUPPORT_GDI Буфер поддерживает рисование GDI в Windows Большинство реализаций позволяет это только для однобуферных окон ипи растровых изображений PFD_SUPPORT_OPENGL PFD_GENERIC_ACCELERATED Буфер поддерживает рисование OpenGL Буфер ускоряется драйвером устройства MCD, который ускоряет этот формат PFD_GENERIC_FORMAT Буфер визуализируется программной реализацией Для драйверов MCD устанавливается бит и метка PFD_GENERIC_ACCELERATED Если значение не задано, аппаратный драйвер является драйвером ICD PFD_NEED_PALETTE Буфер включает устройство управления палитрой Данный флаг устанавливается Windows при работе в 8-битовом (256 цветов) режиме и требует цветовой палитры 3-3-2 PFD_NEED_SYSTEM_PALETTE Метка указывает, что аппаратное обеспечение OpenGL поддерживает визуализацию в режиме 256 цветов Чтобы активизировать аппаратное ускорение, нужно реализовать палитру 3-3-2 Хотя данная метка и описывается в документации, ее можно считать устаревшей Среди основных аппаратных ускорителей не существует ни одного, который бы поддерживал ускоренную визуализацию в режиме 256 цветов в системе Windows PFD_DOUBLEBUFFER PFD_STEREO Буфер цвета является двойным Буфер цвета является стереоскопическим Эта возможность не поддерживается общей реализацией Microsoft Большинство производителей ПК поддерживают в аппаратном обеспечении стереорежим с помощью индивидуальных расширений PFD_SWAP_LAYER_BUFFERS Метка используется, если поддерживаются накладывающиеся и перекрываемые плоскости Если она установлена, эти плоскости можно независимо переставлять в буфере цвета PFD_DEPTH_DONTCARE Метка используется только при запросе пиксельного формата Она указывает, что не требуется буфер глубины В некоторых реализациях отсутствие распределения памяти для буфера глубины позволяет сэкономить память и повысить производительность PFD_DOUBLE_BUFFER_DONTCARE Метка используется только при запросе пиксельного формата Она указывает, что не планируется использовать двойную буферизацию Хотя можно принудительно визуализировать только в переднем буфере данная метка позволяет сэкономить память и повысить производительность
Глава 13. Wiggle: OpenGL в системе Windows 633 Рис. 13.2. Программа GLView демонстрирует все пиксельные форматы для данного устройства Функция DescribePixelFormat возвращает максимальный индекс пиксельного формата. Первый вызов этой функции можно использовать так, как показано в коде, — для определения числа доступных форматов. На компакт-диске, прилагаемом к книге, в папке, соотнесенной с данной главой, имеется весьма интересная утилита GLView. При запуске этой программы перечисляются все пиксельные форматы, доступные для драйвера дисплея при данном разрешении и насыщенности цвета. На рис. 13.2 показан результат выполнения этой программы при выбранном формате пикселей с двойной буферизацией. (При выборе пиксельного формата с обычной буферизацией анимация блока была бы мерцающей.) На компакт-диск также вынесен исходный код библиотеки базовых классов Mi- crosoft (Microsoft Foundation Classes — MFC). Этот код немного сложнее типичных программ-примеров, и утилита GLView представлена скорее как инструмент, чем как пример программирования. Важный код программы перечисления пиксельных фор- матов был представлен ранее, и его размер не превышает дюжины строк. Если вы уже знакомы с MFC, то изучая представленный исходный код поймете, как интегрировать визуализацию OpenGL в любой производный класс окна. В списковом окне перечислены все доступные пиксельные форматы и отображены их характеристики (тип драйвера, насыщенность цвета и т.д.). В окне, показанном в правом верхнем углу, отображен вращающийся куб, для создания которого применя- лось окно, нарисованное при выделенном формате пикселей. Функция glGetString позволяет определять имя производителя драйвера OpenGL, а также другую инфор- мацию, касающуюся версии. Наконец, в списковом окне показаны все расширения OpenGL и WGL, экспортированные драйвером (расширения WGL рассмотрены позже в данной главе). Поэкспериментировав с этой программой, вы обнаружите, что, как показано на рис. 13.3, для создания окна OpenGL можно применять не все пиксельные форматы. Даже если драйвер экспортирует эти форматы, это не означает, что с их помощью можно создать разрешенное с точки зрения OpenGL окно. Важнейшим критерием
634 Часть II. OpenGL повсюду — -----— - - ~ Рис. 13.3. Демонстрация в программе GLView неприемлемого пиксельного формата является то, что насыщенность цвета пиксельного формата должна согласовывать- ся с насыщенностью цвета рабочего стола. Т.е. вы не можете создать 16-битовый пиксельный формат при 32-битовом рабочем столе, и наоборот. Отметим особо один момент: в любом случае перечисляется не меньше 24 пик- сельных форматов (иногда больше). Если вы запустили общую реализацию Microsoft, то увидите точно 24 формата (все — принадлежащие Microsoft). При наличии аппарат- ного ускорителя (MCD либо ICD) вы заметите, что ускоренные пиксельные форматы перечисляются раньше, а после них идут 24 общих пиксельного формата, принад- лежащих Microsoft. Это означает, что при наличии аппаратного ускорителя можно выбирать из двух реализаций OpenGL. Первая — это пиксельные форматы с аппа- ратным ускорением, принадлежащие аппаратному ускорителю. Вторая — пиксельные форматы из программной реализации Microsoft. Знание этого нюанса может оказаться полезным. С одной стороны, это означает, что для визуализации средствами растровых или печатных устройств всегда доступ- на программная реализация. С другой стороны, при необходимости (возможно, это нужно в процессе отладки) можно принудительно использовать программную реали- зацию, даже если приложение может в принципе выбирать аппаратное ускорение. Выбор и установка пиксельного формата Перечисление всех доступных пиксельных форматов и изучение каждого из них с целью выбора подходящего для ваших нужд может стать довольно утомительным. Windows предлагает сокращенную функцию, немного упрощающую этот процесс Функция ChoosePixelFormat позволяет создавать структуру пиксельного формата, содержащую желаемые параметры трехмерного окна. Затем функция ChoosePix- elFormat находит ближайшее возможное соответствие (предпочтение отдается фор- матам пикселей с аппаратным ускорением) и возвращает индекс наиболее подходяще- го варианта. Затем с помощью вызова другой функции Windows (SetPixelFormat) устанавливается пиксельный формат. Применение двух названных функций иллю- стрируется в следующем фрагменте кода:
Глава 13 Wiggle OpenGL в системе Windows 635 int nPixelFormat; static PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), // Размер структуры 1, // Версия структуры // Рисовать в окне (а не на // растровом изображении) PFD_DRAW_TO_WINDOW | // Поддержка вызовов OpenGL в окне // Режим двойной буферизации // Режим RGBA-цвета // Желателен 32-битовый цвет // Не используется для выбора режима // Не используется для выбора режима // Не используется для выбора режима // Размер буфера глубины // Без буфера трафарета // Без вспомогательных буферов PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, PFD_TYPE_RGBA, 32, О,О,О,О,О,О, О, О, О,О,О,О,О, 16, О, О, О, // Параметр устарел или зарезервирован О, // Без накладывающихся и перекрываемых плоскостей О, // Маска уровня устарела или зарезервирована О, // Без прозрачного цвета для перекрываемых плоскостей 0); // Параметр устарел // Выбрать пиксельный формат, наилучшим образом согласующийся 11с описанным в pfd при данном контексте устройства nPixelFormat = ChoosePixelFormat(hDC, &pfd); // Установить пиксельный формат для контекста устройства SetPixelFormat(hDC, nPixelFormat, &pfd); Вначале структура PIXELFORMATDESCRIPTOR заполняется желательными характе- ристиками трехмерного окна В данном случае мы потребовали пиксельный формат с двойной буферизацией, при котором визуализация выполняется в окне, поэтому был указан 32-битовый цвет и 16-битовый буфер глубины Если текущая реализа- ция поддерживает максимум 24-битовый цвет допустимо вазвращение 24-битового формата Разрешение буфера глубины также подвержено изменениям Возможно, ре- ализация поддерживает только 24- или 32-битовый буфер глубины В любом случае функция ChoosePixelFormat всегда возвращает приемлемый пиксельный формат, а если возможно, то возвращает аппаратно-ускоряемый формат Иногда требуется более сложный выбор пиксельного формата В таких случаях нужно перечислить и изучить все доступные пиксельные форматы или использо- вать расширение WGL, представленное далее в этой главе Впрочем, в большинстве случаев приведенного кода достаточно для подготовки окна к получению команд визуализации OpenGL Контекст визуализации OpenGL Типичное приложение Windows может состоять из множества окон, причем позво- лительно даже задавать пиксельный формат для каждого отдельного окна (используя
636 Часть II OpenGL повсюду контекст этого окна)1 Тем не менее для каждого окна функцию SetPixelFormat можно вызывать только один раз. В связи с этим возникает вопрос: когда вы вы- зываете команду OpenGL, как она “знает”, в какое окно посылать свои результаты? В предыдущих главах мы использовали каркас GLUT, обеспечивающий единствен- ное окно, в котором и отображался результат выполнения команд OpenGL. Напомним также, что при нормальном рисовании на основе GDI в системе Windows каждое окно имеет собственный контекст устройства. Чтобы добиться переносимости основных функций OpenGL, во всех средах долж- но быть реализовано средство, позволяющее задавать перед выполнением команд OpenGL текущее окно визуализации. Точно так же, как функции GDI Windows ис- пользуют контексты устройств, среда OpenGL включает в себя то, что называется контекстом визуализации. Точно так же, как контекст устройства помнит настройки, касающиеся режимов рисования и команд для GDI, контекст визуализации помнит настройки и команды OpenGL. Контекст визуализации OpenGL создается посредством вызова функции wglCre- ateContext Эта функция принимает один параметр: контекст окна с приемлемым пиксельным форматом. Тип данных контекста визуализации OpenGL — HGLRC. Со- здание контекста визуализации OpenGL иллюстрируется в следующем коде. HGLRC hRC; // Контекст визуализации OpenGL HDC hDC; // Контекст устройства Windows 11 Выбирается и устанавливается пиксельный формат hRC = wglCreateContext(hDC); Так создается контекст визуализации, совместимый с указанным окном. В при- ложении может существовать несколько контекстов визуализации (например, если изображается два окна с различными режимами рисования, перспективами и т.д.). Чтобы команды OpenGL “понимали”, с каким окном они должны работать, в любой момент времени в одном потоке может быть активным только один контекст визуа- лизации. Когда контекст визуализации является активным, его называют текущим. Текущий контекст визуализации также соотносится с контекстом устройства, а следовательно, с конкретным окном. Таким образом OpenGL узнает окно, в кото- ром следует выполнять визуализацию. Контекст визуализации OpenGL можно даже переносить из одного окна в другое, при условии, что оба окна имеют одинаковый пиксельный формат Чтобы придать контексту визуализации статус текущего и соот- нести его с конкретным окном, вызывается функция wglMakeCurrent. Эта функция принимает два параметра: контекст устройства данного окна и контекст визуализации OpenGL void wglMakeCurrent(HDC hDC, HGLRC hRC); Собираем все вместе На нескольких предыдущих страницах мы рассмотрели довольно много материала. Мы отдельно описали каждый кусок головоломки, но теперь пришло время собрать н\ все воедино Помимо кода, касающегося собственно OpenGL, мы должны еще
Глава 13. Wiggle: OpenGL в системе Windows 637 Рис. 13.4. Результат выполнения программы GLRECT с прыгающим квадратом изучить минимальные требования, удовлетворение которым гарантирует поддержку OpenGL любой программой Windows. В данном разделе мы будем разбирать про- грамму GLRECT. Она должна выглядеть знакомой, поскольку это первый пример с использованием GLUT из главы 2. Впрочем, сейчас это уже полноценная программа Windows, написанная с использованием только С и API Win32. Результат выполнения этой новой программы показан на рис. 13.4. Создание окна Началом любой программы на основе GUI Windows является функция WinMain. В этой функции регистрируется тип окна, создается окно и начинается подкачка сообщений. Функция WinMain для первого примера приведена в листинге 13.1. Листинг 13.1. Функция WinMain программы GLRECT // Точка входа всех программ Windows int APIENTRY WinMain( HINSTANCE hlnstance, HINSTANCE hPrevInstance, LPSTR IpCmdLine, int nCmdShow) { MSG msg; // Структура сообщений Windows WNDCLASS wc; // Структура классов Windows HWND hWnd; // Память для обработчика окна // Регистрируется стиль окна wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; wc.IpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hlnstance = hlnstance; wc.hlcon = NULL; wc.hCursor = LoadCursor(NULL, IDC_ARROW); // Фоновая кисть для окна OpenGL не требуется wc.hbrBackground = NULL; wc.IpszMenuName = NULL; wc.lpszClassName = IpszAppName; // Регистрируется класс окна if(Registerclass(&wc) == 0)
638 Часть II OpenGL повсюду return FALSE; // Создается главное окно приложения hWnd = CreateWindow( IpszAppName, IpszAppName, II OpenGL требует WS_CLIPCHILDREN и WS_CLIPSIBLINGS WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, // Положение и размер окна 100, 100, 250, 250, NULL, NULL, hlnstance, NULL); // Выход, если окно не было создано if(hWnd == NULL) return FALSE; // Отображается окно ShowWindow(hWnd, SW_SHOW) ; UpdateWindow(hWnd); 11 Сообщения приложения обрабатываются до закрытия приложения while( GetMessage(&msg, NULL, 0, 0)) TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } В данном листинге содержится характерный стандартный начальный код GUI Windows Вначале с помощью CreateWindow выбирается стиль окна В общем случае можно использовать любой стиль окна, но необходимо установить стили WS_CLIPCHILDREN и WS_CLIPSIBLINGS. Эти стили требовались в ранних версиях Windows, но в более поздних они были отброшены как чересчур строгое требова- ние Их задачей является отделение контекста визуализации OpenGL от визуализации в других окнах. Тем не менее контекст визуализации OpenGL должен в любой момент времени соотноситься только с одним окном. Относительно приведенного кода следует сделать еще одно замечание, касающе- еся использования CS_OWNDC в стиле окна Зачем нужна эта кажущаяся безобидной меткаВ 9 Вам требуется контекст устройства как для визуализации GDI, так и для пе- реключения страниц с двойной буферизацией OpenGL Чтобы понять, при чем тут CS_OWNDC, нужно вернуться немного назад — к цели и использованию контекста окна Первым делом требуется контекст устройства Прежде, чем вы сможете нарисовать что-либо в окне, требуется контекст окна Вам он нужен в любом случае, вне зависимости от того, каким программированием вы занимаетесь — посредством OpenGL, GDI или даже DirectX Любая операция рисова- ния в Windows (даже если вы рисуете в битовом образе в памяти) требует контекста
Глава 13 Wiggle OpenGL в системе Windows 639 чстройства, идентифицирующего конкретный объект, в котором производится рисо- вание Чтобы извлечь контекст устройства для окна, вызывается следующая простая функция HDC hDC = GetDC(hWnd); Переменная hDC является обработчиком контекста окна, которое идентифициру- ется обработчиком окна hWnd Контекст устройства используется для всех функций GDI, рисующих в окне Кроме того нужен контекст устройства для создания контекста визуализации OpenGL, присвоения ему статуса текущего и переключения буферов. Чтобы сообщить Windows, что контекст устройства для данного окна больше не требуется, вызывается следующая функция с теми же двумя значениями ReleaseDC(hWnd, hDC}; Стандартная логика программирования Windows заключается в том, что вы из- влекаете контекст устройства, используете его для рисования, а затем освобождаете его, как только это станет возможным Подобный опыт пришел еще из эпохи до Win32, когда в системах Windows 3 1 и более ранних на системные ресурсы (в част- ности, контекст устройства) выделялось очень мало памяти В связи с этим возникает вопрос, что произойдет, если Windows исчерпает системные ресурсы9 Если вам по- везет, вы получите сообщение об ошибке Если вы работали над чем-то важным, операционная система может попытаться что-то сообщить, хотя чаще вместо этого происходит отказ системы, и вся ваша работа уходит в никуда (Похоже, Windows работает именно таким образом') Лучший способ уберечь пользователей от этой катастрофы — гарантировать успешное выполнение функции GetDC Если вы получили контекст устройства, то сделали работу насколько возможно быстро (обычно в пределах одного обработчика событий), а затем освободили контекст устройства, так что другие программы могут его использовать Тот же совет применим и к другим системным ресурсам — перьям, шрифтам, кистям и тд Входим в мир Win32 Windows NT и последующие операционные системы на основе Win32 были для про- граммистов в среде Windows просто благословением, причем число хороших момен- тов было большим, чем мы можем сейчас перечислить В том числе появилась воз- можность использовать требуемые системные ресурсы, пока не задействована вся до- ступная память или пока не произойдет сбой приложения (По крайней мерс не сбой операционной системы') При этом следует заметить, что с точки зрения компьютер- ного времени функция GetDC является достаточно дорогой Получив контекст устрой- ства при создании окна и нс освобождая его до уничтожения окна, вы можете суще- ственно ускорить рисование в окне Вы можете привязываться к кистям, шрифтам и другим ресурсам, которые пришлось бы создавать или извлекать (возможно, с по- вторной инициализацией) при каждой операции, делающей окно недействительным Одним из популярных примеров, иллюстрирующих описанное преимущество Win32, является программа, создающая случайные прямоугольники и располагаю- щая их в случайных точках окна Отличие кода, написанного старым способом, от кода, написанного новым способом, очевидно с первого взгляда Да, Win32 — это действительно круто'
640 Часть II OpenGL повсюду Три шага вперед, два шага назад Windows 95, 98 и ME поставили программирование Win32 на поток, но при этом остались несколько старых 16-битовых ограничений. Проблема с потерей систем- ных ресурсов была существенно смягчена, но не решена. Операционной системе по-прежнему могло не хватить ресурсов, но (согласно Microsoft) это было малове- роятно. К сожалению, жизнь оказалась не настолько простой. В Windows NT при завершении работы приложения все выделенные системные ресурсы автоматически возвращались операционной системе. Если в Windows 95, 98 или ME программа за- вершалась сбоем или приложение не могло освободить выделенные ему ресурсы, вы получали утечку ресурсов. В конечном итоге вы начинали чрезмерно нагружать систему и могли прийти к нехватке системных ресурсов (или контекстов устройства) Так что же происходит, когда Windows не имеет достаточно контекстов устрой- ства для продолжения работы9 В таких случаях система просто отбирает ресурсы у того, кто ей не нравится. Это означает, что если вы вызовете функцию GetDC и не вызовете ReleaseDC, Windows 95, 98 или ME может просто присвоить ваш контекст устройства, попав в тяжелое для себя положение. Когда вы в следующий раз вызо- вете wglMakeCurrent или SwapBuffers, ваш обработчик контекста устройства уже может быть недействительным. Приложение может завершиться сбоем или просто остановиться. Поэтому проконсультируйтесь в службе технической поддержки, как вам объяснять заказчику, что за сбой вашего приложения на самом деле ответствен- ность несет Microsoft! Не все потеряно В действительности вы можете указать Windows создать контекст устройства, ис- пользовать который может только ваше окно. Эта возможность полезна, поскольку при каждом вызове GetDC нужно повторно выбрать шрифты, режим отображения и т.д Если у вас имеется собственный контекст устройства, подобную инициализа- цию можно выполнить всего один раз. Кроме того, не нужно беспокоиться о том, что обработчик контекста устройства может потеряться. Ваши действия просты: при регистрации окна задать CS_OWNDC в качестве одного из стилей классов. Распростра- ненной ошибкой является использование значения CS_OWNDC в качестве стиля окна при вызове Create Помните, что существуют стили окна и стили классов; их не нужно путать. Регистрация стиля окна обычно выглядит примерно так: WNDCLASS wc; // Структура класса Windows // Регистрация стиля окна wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; wc.IpfnWndProc = (WNDPROC) WndProc; wc.IpszClassName = IpszAppName; // Регистрация класса окна if(Registerclass(Swc) == 0) return FALSE; После этого при создании окна задается имя класса.
Глава 13 Wiggle OpenGL в системе Windows 641 hWnd = CreateWindow( wc.IpszClassName, szWindowName, ... Программисты, работающие с графикой, всегда должны использовать CS_OWNDC в регистрации класса окна Это гарантирует создание кода, максимально устойчивого на всех платформах Windows Другое соображение касается того, что многие ста- рые аппаратные драйверы OpenGL несовершенны, поскольку в них предполагается задание CS_OWNDC Возможно, эти драйверы изначально писались для NT, поэтому в драйвере не учитывается возможность того, что контекст устройства может ока- заться недопустимым Драйвер также может “спотыкаться”, если контекст устрой- ства не содержит конфигурацию этого устройства (как при применении сценария GetDC/ReleaseDC) Независимо от специфики некоторые драйверы могут быть нс очень устойчивыми, если не задать метку CS_OWNDC Многие (если не все) производители решают эту проблему при разработке обновленных версий драйверов Тем нс менее подобных потенциальных проблем можно избежать, причем все, что для этого требуется, — минимальная модификация кода. Использование контекста визуализации OpenGL Телом программы GLRECT является процедура обработки окна WndProc Она по- лучает от операционной системы сообщения, касающиеся окна, и соответствующим образом реагирует на них Такая модель программирования, названная программиро- ванием, управ темым сообщениями (или событиями), является основой современно- го GUI Windows Создаваемое окно вначале получает от операционной системы сообщение WM_CREATE Это идеальный момент для создания и настройки контекста визуализации OpenGL Окно, подлежащее удалению, получает сообщение WM_DESTROY Разумеет- ся, в это время логичнее всего выполнить код очистки В листинге 13 2 для примера приведен формат SetDCPixelFormat, используемый для выбора и настройки пик- сельного формата, и процедура обработки окна для данного приложения Указанная функция содержит тс же базовые функциональные возможности, что используются с каркасом GLUT Листинг 13.2. Установка пиксельного формата и обработка создания и удаления контекста визуализации OpenGL /////////////////////////////////////////////////////////////////// // Выбрать пиксельный формат для данного контекста устройства void SetDCPixelFormat(HDC hDC) { int nPixelFormat; static PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), // Размер структуры 1, // Версия структуры PFD_DRAW_TO_WINDOW | // Рисовать в окне (а не в растровом изображении в памяти) PFD_SUPPORT_OPENGL I // Поддержка вызовов OpenGL в окне PFD_DOUBLEBUFFER, // Двойная буферизации PFD_TYPE_RGBA, // Режим RGBA-цвета
642 Часть II OpenGL повсюду 32, // Требуется 32-битовый цвет О,О,О,О,О,О, //Не используется для выбора режима 0,0, //Не используется для выбора режима 0,0,0,0,0, // Не используется для выбора режима 16, // Размер буфера глубины О, // Здесь не используется О, // Здесь не используется 0, // Здесь не используется 0, // Здесь не используется 0,0,0 }; // Здесь не используется // Выбрать пиксельный формат, наилучшим образом // согласующийся с описанным в pfd nPixelFormat = ChoosePixelFormat(hDC, &pfd); // Установить пиксельный формат для контекста устройства SetPixelFormat(hDC, nPixelFormat, &pfd); } /////////////////////////////////////////////////////////////////// // Процедура окна, обрабатывающая все сообщения, предназначенные // данной программе LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM 1Param) static HGLRC hRC = NULL; // Постоянный контекст визуализации static HDC hDC = NULL; // Частный контекст устройства GDI switch (message) { // Создание окна, его настройка для OpenGL case WM_CREATE: // Записывается контекст устройства hDC = GetDC(hWnd); // Выбирается пиксельный формат SetDCPixelFormat(hDC); // Создается контекст визуализации, ему присваивается // статус текущего hRC = wglCreateContext(hDC); wglMakeCurrent(hDC, hRC); // Создается таймер, срабатывающий 30 раз в секунду SetTimer(hWnd,33,1,NULL); break; // Окно уничтожается, запускаются процедуры очистки case WM_DESTROY: // Удаляется созданный таймер KillTimer(hWnd, 101); // Отменяется выбор текущего контекста визуализации, // контекст удаляется wglMakeCurrent(hDC,NULL);
Глава 13 Wiggle OpenGL в системе Windows 643 wglDeleteContext(hRC); // Указываем, что после удаления окна приложение // должно завершиться PostQuitMessage(0); break; // Изменяется размер окна case WM_SIZE: // Вызываем функцию, модифицирующую объем отсечения // и поле просмотра ChangeSize(LOWORD(lParam), HIWORD(lParam)); break; // Под управлением таймера прямоугольник двигается //и рикошетом отскакивает от стенок. // Просто вызываем функцию Onldle, затем окно помечается // как недействительное, // так что оно будет перерисовываться case WM_TIMER: IdleFunction() ; InvalidateRect(hWnd,NULL,FALSE); } break; // Функция рисования. Данное сообщение посылается системой // Windows всегда, когда требуется обновление экрана case WM_PAINT: { // Вызывает код рисования OpenGL RenderScene(); // Вызывается функция переключения буферов SwapBuffers(hDC); // Проверяется приемлемость нарисованной клиентской //области ValidateRect(hWnd,NULL); } break; default: // Продолжаем обработку, если остались необработанные // сообщения return (DefWindowProc(hWnd, message, wParam, IParam)); return (OL); } Инициализация контекста визуализации Первое, что вы делаете при создании окна, — извлекаете контекст устройства (помни- те, вы привязаны к нему) и устанавливаете пиксельный формат // Записывается контекст устройства hDC = GetDC(hWnd);
644 Часть II OpenGL повсюду // Выбирается пиксельный формат SetDCPixelFormat(hDC); После этого создается контекст визуализации OpenGL, обозначаемый как текущий // Создается контекст визуализации, ему присваивается статус // текущего hRC = wglCreateContext(hDC); wglMakeCurrent(hDC, hRC); С последней задачей — созданием таймера Windows для окна — вы сталкиваетесь при обработке сообщения WM_CREATE Позже этот таймер будет использован для управления циклом анимации // Создается таймер, срабатывающий 30 раз в секунду SetTimer(hWnd,33,1,NULL); break; На данный момент контекст визуализации OpenGL уже создан и соотнесен с ок- ном, имеющим допустимый пиксельный формат Начиная с этого момента все коман- ды визуализации OpenGL будут направляться этому контексту и этому окну Сворачивание контекста визуализации Когда процедура обработки окна получает сообщение WM_destroy, контекст визу- ализации OpenGL должен удаляться Прежде чем уничтожить контекст визуализа- ции с помощью функции wglDeleteContext, нужно повторно вызвать функцию wglMakeCurrent, но на этот раз с NULL в качестве параметра, характеризующего контекст визуализации OpenGL // Отменяется выбор текущего контекста визуализации, // контекст удаляется wglMakeCurrent(hDC,NULL); wglDeleteContext(hRC); Перед удалением контекста визуализации нужно уничтожить вес таблицы отобра- жения, текстурные объекты и другие элементы, для хранения которых была выделена память OpenGL Другие сообщения Чтобы позволить OpenGL визуализировать в окне, требуется всего лишь создать и удалить контекст визуализации OpenGL Но чтобы приложение “хорошо себя ве- ло”, следует придерживаться некоторых правил, касающихся обработки сообщений Например, обрабатывая сообщение wm_SIZE при изменении размера окна, нужно устанавливать поле просмотра // Изменяется размер окна case WM_SIZE: I/ Вызываем функцию, модифицирующую объем отсечения и поле // просмотра ChangeSize(LOWORD(lParam), HIWORD(lParam)); break; В ответ на сообщение wm_SIZE выполняется такая же обработка, что и при вызове функции, которую вы передаете функции glutReshapeFunc в пршраммах с исполь-
Глава 13 Wiggle OpenGL в системе Windows 645 зованием GLUT. Процедура обработки окна также получает два параметра IParam и wParam Здесь LOWORD представляет собой новую ширину окна, a HIWORD — новую высоту В данном примере для обработки холостого состояния используется обработ- чик событий WM_TIMER. В действительности процесс не является холостым, но предшествующий вызов функции SetTimer приводит к сравнительно регулярно- му поступлению сообщения WM_TIMER (“сравнительно” потому, что точный интервал нс гарантирован) Другие сообщения Windows генерируются при обработке таких действий, как ма- нипуляции с клавиатурой (WM_CHAR, WM_KEYDOWN), движения мыши (WM_MOUSEMOVE) и управление палитрой (Данные сообщения рассмотрены ниже ) Сообщение WM_PAINT Сообщение WMJPAINT требует особого внимания Оно посылается окну, когда Win- dows требуется нарисовать или перерисовать содержимое этого окна Чтобы указать Windows в любом случае перерисовывать окно, оно объявляется недействительным с помощью вызова в обработчике событий WM_TIMER такой функции IdleFunction(); InvalidateRect(hWnd,NULL,FALSE); Здесь функция IdleFunction обновляет положение квадрата, a InvalidateRect указывает Windows перерисовать окно (после того, как квадрат переместился) В большинстве книг по программированию в Windows обработчик собы- тий WMJPAINT демонстрируется с парой хорошо известных функций Begin- Paint/EndPaint. Функция BeginPaint извлекает контекст устройства, который можно применять для GDI-рисования, a EndPaint освобождает контекст и прове- ряет приемлемость окна. Выше, при обсуждении того, зачем нужен стиль класса CS_OWNDC, отмечалось, что в высокопроизводительных графических приложениях использование этой пары функций обычно является не слишком удачной идеей При- веденный ниже код предлагает примерно те же функциональные возможности, но при меньших служебных издержках // Функция рисования. Данное сообщение посылается системой Windows // всегда, когда требуется обновление экрана case WM_PAINT: { // Вызывает код рисования OpenGL RenderScene(); // Вызывается функция переключения буферов SwapBuffers(hDC); // Проверяется приемлемость нарисованной клиентской области ValidateRect(hWnd,NULL); } break; Поскольку в данном примере используется контекст устройства (hDC), его нс нуж- но постоянно получать и освобождать Ранее мы уже упоминали о функции Swap- Buffers, но не описывали ее подробно Итак, эта функция принимает в качестве аргумента контекст устройства и переключает буферы при визуализации с двойной бчферизацией Именно поэтому нужно, чтобы при визуализации контекст устройства быт готов и доступен
646 Часть II OpenGL повсюду Обратите внимание на то, что вы должны вручную проверить приемлемость окна посредством вызова функции ValidateRect после визуализации Не использовав в нужном месте функциональные возможности пары BeginPaint/EndPaint, вы ни- как не сможете сообщить Windows, что завершили рисование содержимого окна Одна из альтернатив использованию функции WM_TIMER для объявления окна недей- ствительным (те инициализации его перерисовывания) — просто не проверять его приемлемость Если процедура окна обработала сообщение WM_PAINT и окно не бы- ло объявлено приемлемым, операционная система генерирует еще одно сообщение WM_PAINT Эта цепная реакция приводит к бесконечному потоку сообщений о перери- совывании Если использовать такой подход к анимации, остается мало возможностей для обработки других сообщений окна Поэтому, хотя визуализация может происхо- дить очень быстро, пользователю может оказаться сложно или невозможно изменить размер окна или использовать меню Палитры Windows В главе 5, “Цвет, материалы и освещение, основы,” мы обсуждали различные режимы цвета, доступные на современных ПК с системой Windows. Графические карты с ап- паратным ускорением трехмерной графики для ПК поддерживают цветовое разреше- ние 16 бит или выше Если снизить требования до 8-битового цвета (256 цветов), ско- рее всего придется работать с общей программной реализацией Microsoft Хотя дан- ный графический режим становится все менее распространенным, приложение все же имеет шансы оказаться в подобной среде Следует помнить, что не все трехмерные приложения требуют аппаратного ускорения, а многих пользователей вообще могут не волновать вопросы выбора между программной и аппаратной визуализацией Согласование цветов Что произойдет, если попытаться нарисовать пиксель определенного цвета, исполь- зуя в функции glColor конкретные RGB-коды? Если графические карты работают в режиме 24-битового цвета, каждый пиксель отображается тем цветом, который задан 24-битовым значением (три 8-битовых). В 15- и 16-битовых режимах цвета Windows передает 24-битовый код цвета драйверу дисплея, который перед отобра- жением сокращает цвет до 15- или 16-битового значения Внутренние расчеты цвета с учетом освещения и текстуры обычно (в зависимости от реализации) выполняют- ся с полной точностью Сокращение точности воспроизведения цвета с 24 бит до 16 бит приводит к потере точности визуального отображения, впрочем, приемлемой во многих приложениях На дисплее Windows с разрешением всего 8 бит (256 цветов) система Windows создает палитру цветов для устройства отображения. Палитра — это список кодов цветов, заданных при максимальном разрешении Когда приложению требуется один из этих цветов, оно использует индекс, а не код цвета. В принципе элементы па- литры могут быть произвольными, обычно они выбираются исходя из требований конкретного приложения
Глава 13 Wiggle OpenGL е системе Windows 647 Если Windows работает в режиме, поддерживающем 256 цветов, логично распре- делить эти цвета равномерно по пространству цветов RGB. (См. пример с кубом цветов в главе 5 ) В таком случае все приложения будут иметь сравнительно разум- ный набор цветов и при выборе цвета будет отображаться наиболее близкий к нему из имеющегося набора. При работе в режиме палитры цветов OpenGL требуется па- литра именно такого типа К сожалению, в других приложениях подобный выбор цветов не всегда практичен Поскольку 256 цветов для устройства можно произвольным образом выбирать из более чем 16 миллионов различных цветов, качество графики приложения можно существенно улучшить, разумно подобрав доступные цвета (что обычно и делает- ся). Например, для создания морского пейзажа могут потребоваться дополнительные оттенки синего. Приложения автоматизированного проектирования и моделирования могут модифицировать палитры с целью получения гладкого затенения поверхности конкретного цвета Например, сцена точного воспроизведения разреза трубы мо- жет требовать порядка 200 оттенков серого. Таким образом, приложения для ПК обычно меняют палитру согласно собственным требованиям, что позволяет получить множество сцен и изображений с практически фотографической точностью. В 256- цветных растровых изображениях Windows (формат BMP) даже имеется массив из 256 элементов, в котором записаны 24-битовые RGB-коды, определяющие палитру записанного изображения Приложение может создавать палитру с помощью функции Windows Сге- atePalette, идентифицируя палитру обработчиком, имеющим тип HPALETTE В ка- честве аргумента эта функция принимает логическую структуру палитры (LOG- PALETTE), содержащую 256 элементов, каждый из которых задает 8-битовые коды красного, зеленого и синего компонентов Прежде чем мы изучим создание палитры, рассмотрим совместное использование одной системной палитры многозадачными приложениями в режиме 8-битового цвета Разрешение конфликтов палитр Многозадачность Windows позволяет использовать экран нескольким приложениям одновременно. Если аппаратное обеспечение поддерживает отображение в любой мо- мент времени только 256 цветов, все приложения должны совместно использовать одну и ту же системную палитру Если одно приложение меняет системную палитру, изображения в другом окне могут поменять цвет, что даст нежелательный психодели- ческий эффект. Чтобы разрешить применение палитры несколькими приложениями, Windows посылает набор сообщений Приложения извещаются о том, что другое при- ложение изменило системную палитру, и о гом, когда их окно находится в фокусе и изменение палитры разрешено. Когда приложение находится в фокусе и ожидает поступление информации с клавиатуры или мыши, Windows посылает основному окну приложения запрос WM_QUERYNEWPALETTE о том, необходимо ли создать новую палитру Создание палит- ры означает, что приложение копирует элементы палитры из своей личной палитры в системную Для этого приложение должно вначале выбрать палитру в контексте устройства для обновляемого окна, а затем вызвать функцию RealizePalette
648 Часть II OpenGL повсюду Другим сообщением, посылаемым Windows с целью создания палитры, является wm_PALETTECHANGED Это сообщение посылается окнам, которые могут создать свои палитры, но в настоящий момент могут не быть в фокусе При отправке этого сооб- щения также можно проверить значение wParam Если wParam содержит обработчик текущего окна, получающего сообщение, значит, сообщение wm_QUERYNEWPALETTE уже обработано, и палитру не нужно создавать повторно Пример обработчика собы- тий для двух указанных сообщений приводится в листинге 13 3 Листинг 13.3. Обработчики сообщений, применяемые для управления палитрой Windows /////////////////////////////////////////////////////////////////// // Windows сообщает приложению, что оно может модифицировать // системную палитру. По сути, это сообщение запрашивает // у приложения новую палитру case WM_QUERYNEWPALETTE: // Если палитра была создана if(hPalette) { int nRet; 11 Выбирается палитра в текущем контексте устройства SelectPalette(hDC, hPalette, FALSE); // Отобразить элементы текущей выбранной палитры // в системную палитру. В ответ возвращается число // модифицированных элементов палитры. nRet = RealizePalette(hDC) ; // Перерисовать, инициировать повторное // отображение палитры в // текущем окне InvalidateRect(hWnd,NULL,FALSE); return nRet; 1 break; /////////////////////////////////////////////////////////////////// 11 Это окно может устанавливать палитру, даже при том, что оно // не является текущим активным окном case WM_PALETTECHANGED: 11 Ничего не делать, если палитра не существует, или если это // окно, меняющее палитру if((hPalette != NULL) && ((HWND)wParam != hWnd)) { // Выбрать палитру в контексте устройства SelectPalette(hDC,hPalette,FALSE); // Отобразить элементы в системную палитру RealizePalette(hDC); // Повторно отобразить текущие цвета в новую реализованную // палитру UpdateColors(hDC); return 0; break;
Глава 13 Wiggle OpenGL в системе Windows 649 Палитра Windows определяется обработчиком типа HPALETTE К такому типу от- носится переменная hPalette, фигурирующая в листинге 13.3. Обратите внимание для того чтобы избежать потенциальной ошибки, перед обработкой сообщений осво- бождения палитры, значение переменной hPalette сравнивается со значением NULL Если приложение нс запущено в режиме 8-битового цвета, эти сообщения в прило- жении отсутствуют Создание палитры для OpenGL К сожалению, если вы предполагаете использовать приложение на 8-битовом аппа- ратном обеспечении (которое еще достаточно распространено), решение вопросов, связанных с палитрой, является необходимым злом Итак, что вы будете делать, если код будет выполняться на машине, поддерживающей всего 256 цветов? Для приложений, подобных программам воспроизведения изображений, мы реко- мендуем выбрать диапазон цветов, наиболее точно соответствующий набору исход- ных цветов Вопрос выбора наилучшей сокращенной палитры для данного полно- цветного изображения изучается давно, и в данной книге мы его касаться не будем В большинстве случаев при визуализации с использованием OpenGL требуется наи- более широкий универсальный диапазон цветов Здесь можно порекомендовать выбор палитры цвеюв, равномерно распределенных по кубу цвета. В таком случае при зада- нии цвета, отсутствующею в палитре, Windows выберет ближайший (по кубу цвета) цвет Как отмечалось ранее, такая схема не всегда идеальна, но для сцен, визуализи- руемых с помощью OpcnGL, это лучшее, ч го можно посоветовать. Как правило (если нс используется интенсивное наложение текстуры, содержащей множество цветов), результаты подобной визуализации в целом приемлемы Сказанное иллюстрируется на рис 13 5, где приведен результат выполнения про- 1раммы GLPALETTE Эта программа создаст вращающийся куб, на каждую грань которого наложена текстура со знакомым лицом Запустите эту программу в полно- цветном режиме (16 бит или больше) и в режиме 256 цветов Разницу невозможно пе- редать в данной кише, но на компьютере вы заметите, что даже в режиме 8-битового цвета OpcnGL может довольно точно воспроизвести изображение, несмотря на огра- ниченный диапазон доступных цветов Нужна ли палитра? Чтобы определить, требуется ли приложению палитра, следует изучить структуру PIXELFORMATDESCRIPTOR, возвращаемую при вызове DescnbePixelFormat Для этого проверяется элемент dwFlags данной структуры, и если его значение равно PFD_NEED_PALETTE, для приложения необходимо создать палитру. DescnbePixelFormat (hDC, nPixelFormat, // sizeof(PIXELFORMATDESCRIPTOR), &pfd); // Требует ли данный пиксельный формат палитру? if(1(pfd dwFlags & PFD_NEED_PALETTE)) return NULL; // Палитра не требуется // Код создания палитры
650 Часть II. OpenGL повсюду Рис. 13.5. "Мона Лиза”, узнаваемая даже в режиме 8-битового цвета Структура палитры Чтобы создать палитру, вначале нужно выделить память для структуры Windows, называемой LOGPALETTE. Эта структура заполняется информацией, которая описы- вает палитру и передается функции Win32, именуемой CreatePalette. Структура LOGPALETTE определяется следующим образом: typedef struct tagLOGPALETTE { // логическая палитра WORD palVersion; WORD palNumEntries; PALETTEENTRY palPalEntry[1]; ) LOGPALETTE; Первые два элемента — это заголовок палитры, в котором содержится ее вер- сия (всегда — 0x300) и число цветных элементов (для 8-битовых режимов — 256). Затем каждый элемент определяется как структура PALETTEENTRY, содержащая RGB- компоненты цветного элемента. В памяти в конце структуры располагаются допол- нительные элементы. Выделение памяти для логической палитры иллюстрируется в следующем коде: LOGPALETTE *pPal; // Указатель на память для логической палитры // Выделяется место для логической структуры палитры и // всех элементов палитры pPal = (LOGPALETTE*)malloc(sizeof(LOGPALETTE) + nColors*sizeof(PALETTEENTRY)); Здесь nColors задает число цветов, которые будут помещены в палитру; в нашем случае это всегда 256. Каждый элемент палитры представляет собой структуру PALETTEENTRY, которая определяется следующим образом:
Глава 13 Wiggle' OpenGL в системе Windows 651 Интенсивность Интенсивность Интенсивность синего зеленого красного Рис. 13.6. Пример выбора палитры 3-3-2 разряд Младший разряд typedef struct tagPALETTEENTRY { // элементы палитры BYTE peRed; BYTE peGreen; BYTE peBlue; BYTE peFlags; } PALETTEENTRY; Компоненты peRed, peGreen и peBlue в сумме задают 8-битовое значение, кото- рое указывает относительную насыщенность всех компонентов цвета. Таким образом, каждый из 256 элементов палитры содержит определение цвета в 24-битовом форма- те. Число peFlags предусматривает возможность использования элементов палитры и для других целей. В нашем случае это значение устанавливается равным NULL. Палитра 3-3-2 Теперь начинается самое сложное требуется не только равномерно распределить 256 элементов палитры по RGB-кубу, но и определенным образом их упорядочить Причем порядок должен быть таким, чтобы OpenGL мог найти требуемый цвет или цвет палитры, ближайший к нему В 8-битовом режиме цвета для записи красного и зеленого компонентов доступно по 3 бит, а для записи синего — 2 бит. Такую схему принято называть палитрой 3-3-2 Таким образом, RGB-куб имеет размер 8x8x3 единиц по красной, зеленой и синей осям, соответственно. Чтобы найти в палитре требуемый цвет, эталонная система координат 8-8-8 (режим 24-битового цвета) масштабируется в код цвета 3-3-2. Затем это 8-битовое значение используется как индекс массива, представляющего палитру. Насыщенность красного цвета от 0 до 7 в палитре 3-3-2 должна соответствовать насыщенности от 0 до 255 в палитре 8-8-8. Формирование индекса палитры согласно красному, зеленому и синему компонентам показано на рис. 13.6. При построении палитры последовательно проходятся все значения от 0 до 255 Затем индекс разбивается на насыщенность красного, зеленого и синего, представ- ленную этими значениями (в терминах палитры 3-3-2). Каждый компонент множится на 255 и делится на максимальное представленное значение, что дает плавный пе- реход насыщенности от 0 до 7 для красного и зеленого цвета и от 0 до 3 — для синего. Чтобы продемонстрировать расчет компонентов, часть элементов палитры приведена в табл. 13.2. Построение палитры В действительности палитра 3-3-2 задается структурой PIXELFORMATDESCRIPTOR, возвращаемой функцией DescribePixelFormat. Элементы cRedBits, cGreenBits
652 Часть II OpenGL повсюду ТАБЛИЦА 13.2. Примеры элементов палитры 3-3-2 Элемент палитры Двоичный код (В G R) Синий компонент Зеленый компонент Красный компонент 0 00 000 000 0 0 0 1 00 000 001 0 0 1 * 255/7 2 00 000 010 0 0 2 * 255/7 3 00 000 011 0 0 3 t 255/7 9 00 001 001 0 1 * 255/7 1 * 255/7 10 00 001 010 0 1 * 255/7 2 * 255/7 137 10 001 001 2 * 255/3 1 * 255/7 1 * 255/7 138 10 001 010 2 * 255/7 1 * 255/7 2 1 255/3 255 И 111 111 3 * 255/3 7 * 255/7 7 * 255/7 и cBlueBits, равные 3, 3 и 2, соответственно, задают число битов, которыми можно представлять соответствующий компонент Более того, значения cRedShift, cGreenShift и cBlueShift задают, насколько нужно сместить значение соответ- ствующего компонента (в нашем случае имеем смещение 0, 3 и 6 для красного, зеленого и синего компонентов) Эти наборы значений составляют индекс палит- ры (см рис. 13.6). С помощью кода, приведенного в листинге 13.4, создастся палитра (если она нуж- на), и возвращается се идентификатор Приведенная функция получает из PIXELFOR- MATDESCRIPTOR число битов, представляющих компоненты, и информацию о сдвиге, предусматривая любые последующие изменения палитры, например необходимость создания палитры 2-2-2 Листинг 13.4. Функция, создающая палитру для использования с OpenGL // Если необходимо, создается палитра 3-3-2 для указанного // контекста устройства HPALETTE GetOpenGLPalette(HDC hDC) { HPALETTE hRetPal = NULL; // PIXELFORMATDESCRIPTOR pfd; // LOGPALETTE *pPal; // // int nPixelFormat; // int nColors; // int i; // BYTE RedRange,GreenRange,BlueRa: Обработчик создаваемой палитры Дескриптор пиксельного формата Указатель на ячейку памяти с началом логической палитры Индекс пиксельного формата Число элементов в палитре Переменная-счетчик // Диапазон изменения кодов компонентов цвета (7, 7 и 3) // Получить индекс пиксельного формата и извлечь описание // пиксельного формата nPixelFormat = GetPixelFormat(hDC); DescribePixelFormat(hDC, nPixelFormat, sizeof(PIXELFORMATDESCRIPTOR) , &pfd); // Требует ли палитра для этого пиксельного формата? // Если - нет, палитру создавать не нужно, // просто возвращается NULL
Глава 13 Wiggle OpenGL в системе Windows 653 if(!(pfd.dwFlags & PFD_NEED_PALETTE)) return NULL; // Число элементов в палитре. 8 бит дает 256 элементов nColors = 1 « pfd cColorBits; // Выделяется память для логической структуры палитры плюс всех // элементов палитры pPal = (LOGPALETTE*)malloc(sizeof(LOGPALETTE) + nColors*sizeof(PALETTEENTRY)); // Заполняется заголовок палитры pPal->palVersion = 0x300; // Windows 3.0 pPal->palNumEntries = nColors; // размер таблицы // Создается маска из одних единиц. Таким образом // создается число, которое представлено // заданными х младшими битами, где х = pfd.cRedBits, // pfd.cGreenBits и pfd.cBlueBits. RedRange = (1 « pfd.cRedBits) -1; 111 для палитр 3-3-2 GreenRange = (1 « pfd.cGreenBits) - 1; //7 для 3-3-2 BlueRange = (1 « pfd.cBlueBits) -1; //3 для палитр 3-3-2 // Последовательно проходятся все элемента палитры for(i = 0; i < nColors; i++) { // Все компоненты заполняются 8-битовыми эквивалентами pPal->palPalEntry[1].peRed = (i » pfd.cRedShift) & RedRange; pPal->palPalEntry[i].peRed = (unsigned char)( (double) pPal->palPalEntry[i].peRed * 255.0/RedRange); pPal->palPalEntry[1].peGreen = (i » pfd.cGreenShift) & GreenRange; pPal->palPalEntry[i].peGreen = (unsigned char)( (double)pPal->palPalEntry[i] peGreen * 255.О/GreenRange); pPal->palPalEntry[i].peBlue = (i >> pfd.cBlueShift) & BlueRange; pPal->palPalEntry[i].peBlue = (unsigned char)( (double)pPal->palPalEntry[i].peBlue * 255.О/BlueRange); pPal->palPalEntry[i].peFlags = (unsigned char) NULL; // Создается палитра hRetPal = CreatePalette(pPal); // Идем дальше и создаем палитру для данного контекста // устройства SelectPalette(hDC,hRetPal,FALSE); RealizePalette(hDC); // Освобождает память, используемую для логической структуры // палитры fгее(pPal); // Возвращает обработчик новой палитры return hRetPal;
654 Часть II OpenGL повсюду Создание палитры и управление ею Палитру Windows нужно создать и реализовать до создания контекста визуализации OpenGL или присвоения ему статуса текущего Функция, приведенная в листин- ге 13 4, требует только наличия контекста устройства после установки пиксельного формата Затем она возвращает обработчик палитры (если он требуется) В листин- ге 13 5 показана последовательность операций при создании и уничтожении окна. Данный код похож на предыдущий, только теперь учитывается возможное существо- вание палитры Листинг 13.5. Создание и уничтожение палитры // Создание окна, подготовка его для использования OpenGL case WM_CREATE: // Записывается контекст устройства hDC = GetDC(hWnd); 11 Выбирается пиксельный формат SetDCPixelFormat(hDC); // Если требуется, создается палитра hPalette = GetOpenGLPalette(hDC) ; // Создается контекст визуализации, ему присваивается // статус текущего hRC = wglCreateContext(hDC); wglMakeCurrent(hDC, hRC); break; // Окно удаляется; очистка case WM_DESTROY: // Отменяется выбор текущего контекста визуализации // с последующим удалением этого контекста wglMakeCurrent(hDC,NULL); wglDeleteContext(hRC); // Если палитра была создана, здесь она удаляется if(hPalette != NULL) DeleteObject(hPalette); // Указываем приложению завершить работу после // прекращения действий с окном PostQuitMessage(0); break; Ограничения В действительности в системную палитру отображаются не все 256 элементов палит- ры В системе Windows 20 элементов зарезервировано для статических системных цветов, в том числе 16 стандартных цветов VGA/EGA Это предотвращает изменения стандартных компонентов Windows (строки заголовков, кнопки и тд ) при изменении приложением системной палитры После того как приложение реализует собствен- ную палитру, данные 20 цветов не записываются повторно К счастью, некоторые из этих цветов соответствуют цветам палитры 3-3-2 или весьма близки к ним (цвета, которые нс соответствуют точно, близки настолько, что в большинстве случаев вы не заметите разницы)
Глава 13 Wiggle OpenGL в системе Windows 655 Осталось сделать последнее замечание относительно визуализации OpenGL с ис- пользованием палитры Методы, представленные в данной книге, позволяют задавать цвета с полным набором RGBA-компонентов, причем преобразование в ближайшие элементы палитры происходит только в момент растеризации OpenGL имеет устарев- ший режим визуализации, именуемый режимом индексирования цветов (color index mode), в котором можно задавать цвета непосредственно с помощью индексов эле- ментов палитры Режим индексирования цветов не поддерживает такие современные возможности, как наложение текстуры, и не ускоряется аппаратно на ПК (а также на Macintosh) Поэтому режим индексирования цвета следует считать “умершим”, и в книге он не рассматривается OpenGL и шрифты Windows Одной из приятнейших особенностей Windows является поддержка шрифтов True- Type. С тех пор как эта операционная система стала 32-битовой, эти шрифты можно называть “родными” для Windows Шрифты TrueType улучшают внешний вид текста, поскольку они аппаратно-независимы и их легко масштабировать, сохраняя плавные формы. Шрифты TrueType являются векторными Это означает, что определения сим- волов состоят из набора определений точек и кривых. При масштабировании символа общая форма и внешний вид остаются гладкими. Выдача текстовой информации является частью практически всех приложений Windows, и трехмерные приложения не исключение Microsoft обеспечивает под- держку шрифтов TrueType в OpenGL посредством двух функций. Первую, wglUse- FontOutlines, можно использовать для создания трехмерных моделей шрифтов, ко- торые могут использоваться для формирования трехмерных текстовых эффектов Вто- рая, wglUseFontBitmaps, создает из шрифта набор растровых изображений симво- лов, которые могут применяться для отображения двухмерного текста в окне OpenGL с двойной буферизацией Трехмерные шрифты и текст В качестве аргумента функция wglUseFontOutlines принимает обработчик кон- текста устройства На основе шрифта TrueType, выбранного в настоящий момент для текущего контекста устройства, она создает набор таблиц отображения Каждая такая таблица отображения визуализирует только один символ шрифта В листин- ге 13 6 приведена функция SetupRC из программы-примера TEXT3D, демонстриру- ющая весь процесс создания шрифта, выбора его в контексте устройства, создания таблиц отображения и, наконец, удаления шрифта (Windows). Листинг 13.6. Создание набора трехмерных символов void SetupRC(HDC hDC) { // Установка характеристики шрифта HFONT hFont; GLYPHMETRICSFLOAT agmf[128); И Отбрасываем LOGFONT logfont;
656 Часть II OpenGL повсюду logfont.IfHeight = -10; logfont.IfWidth = 0; logfont.IfEscapement = 0; logfont.IfOrientation = 0; logfont.IfWeight = FW_B0LD; logfont.IfItalic = FALSE; logfont IfUnderline = FALSE; logfont.IfStrikeOut = FALSE; logfont.IfCharSet = ANSI_CHARSET; logfont.IfOutPrecision = OUT_DEFAULT_PRECIS; logfont.IfClipPrecision = CLIP_DEFAULT_PRECIS; logfont.IfQuality = DEFAULT_QUALITY; logfont.IfPitchAndFamily = DEFAULT_PITCH; strcpy(logfont.IfFaceName,"Arial"); 11 Создаем шрифт и таблицу отображения hFont = CreateFontlndirect(&logfont); SelectOb^ect (hDC, hFont); // Создаем таблицы отображения для глифов с 0 по 128 // с экструзией 0,1 //и отклонением по умолчанию. Нумерация таблиц // отображения начинается с 1000 // (число может быть любым). nFontList = glGenLists(128); wglUseFontOutlines(hDC, 0, 128, nFontList, O.Of, 0.5f, WGL_FONT_POLYGONS, agmf); DeleteOb^ect(hFont); ) При создании набора трехмерных символов ключевым является вызов функции wglUseFontOutlines wglUseFontOutlines(hDC, 0, 128, nFontList, O.Of, 0.5f, WGL_FONT_POLYGONS, agmf); Первый параметр — это обработчик контекста устройства, в котором выбран же- лаемый шрифт Следующие два параметра задают диапазон символов (называемых глифами), которые будут использованы в шрифте В данном случае используются символы с 1 по 127 (Индексы отсчитываются от нуля ) Третий параметр, nFontList, указывает начало диапазона созданных ранее таблиц отображения Отмстим, что пе- ред использованием функций WGL для работы со шрифтами нужно выделить память под таблицы отображения Следующий параметр — гармоничное отклонение (chordal deviation) Его можно представлять как параметр, управляющий гладкостью шрифта (наиболее гладкому внешнему виду соответствует значение 0.0) Экструзия набора символов равна 0.5f По определению трехмерные симво- лы считаются лежащими на плоскости ху Экструзия определяет, как далеко по оси z простирается символ Параметр WGL_FONT_POLYGONS сообщает, что симво- лы создаются ns треугольников и квадратов, так что эти символы — сплошные
Глава 13 Wiggle OpenGL в системе Windows 657 На основе заданной информации для каждой буквы рассчитываются и записывают- ся нормали Единственным иным допустимым значением этого параметра является WGL_FONT_LINES, дающее каркасную версию набора символов (нормали при этом нс 1 снсрируются') Последним аргументом является массив типа GLYPHMETRICSFLOAT, определяе- мый следующим образом typedef struct _GLYPHMETRICSFLOAT { FLOAT gmfBlackBoxX; // Размер ячейки символа в направлении х FLOAT gmfBlackBoxY; // Размер ячейки символа в направлении у POINTFLOAT gmfptGlyphOrigin; // Начало ячейки символов FLOAT gmfCelllncX; // Горизонтальное расстояние до начала следующей ячейки FLOAT gmfCelllncY; // Вертикальное расстояние до начала следующей ячейки }; GLYPHMETRICSFLOAT Windows заполняет этот массив согласно характеристикам выбранного шрифта Эти значения могут оказаться полезны, когда требуется определить размер строки визуализированного трехмерного текста Визуализация трехмерного текста Таблица отображения, вызываемая для любого символа, визуализирует этот символ и смещает текущее положение вправо (по положительному направлению оси х) на ширину ячейки символа Результат подобен вызову функции glTranslate после каждого символа с указанием трансляции в положительном направлении оси д Что- бы трактовать массив символов (строку) как массив смещения от первой таблицы отображения шрифта, можно использовать функцию glCallLists вместе с функ- цией glListBase Простои метод вывода текста показан в листинге 13 7 Результат выполнения соответствующей программы TEXT3D приведен на рис 13 7 Листинг 13.7. Визуализация строки трехмерного текста void RenderScene(void) glClear(GL_COLOR_BUFFER_BIT I GL_DEPTH_BUFFER_BIT); // Синий трехмерный текст glColor3ub(0, 0, 255); glPushMatrix(), glListBase(nFontList); glCallLists (6, GL_UNSIGNED_BYTE, "OpenGL"); glPopMatrix();
658 Часть II. OpenGL повсюду Двухмерные шрифты и текст Функция wglUseFontBitmaps подобна своему трехмерному аналогу. Эта функция не выдавливает растровый образ в три измерения, а создает растровые образы гли- фов шрифта. Изображения выводятся на экран посредством растровых функций, описанных в главе 7, “Построение изображений с помощью OpenGL”. Каждый ви- зуализированный символ смещает текущее растровое положение вправо (как и при отображении трехмерного текста). В листинге 13.8 показан код, устанавливающий систему координат для окна (функ- ция ChangeSize), создающий растровый (эскизный) шрифт (функция SetupRC) и ви- зуализирующий текст (функция RenderScene). Результат выполнения соответствую- щей программы TEXT2D показан на рис. 13.8. Листинг 13.8. Создание и использование двухмерного шрифта /////////////////////////////////////////////////////////////////// // Размер окна изменился. Обновить параметры согласно // координатам окна void ChangeSize(GLsizei w, GLsizei h) { GLfloat nRange = 100.Of; GLfloat fAspect; /I Предотвращает деление на нуль if(h == 0) h = 1; fAspect = (GLfloat)w/(GLfloat)h; // Размер поля просмотра устанавливается равным // размеру окна glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadldentity(); gluOrtho2D(0,400, 400, 0); // Преобразования наблюдения glMatrixMode(GL_MODELVIEW); glLoadldentity(); } /////////////////////////////////////////////////////////////////// // Настройка. Используем шрифт Windows для создания
Глава 13 Wiggle' OpenGL в системе Windows 659 // растровых образов void SetupRC(HDC hDC) // Настройка характеристик шрифта HFONT hFont; LOGFONT logfont; logfont.IfHeight = -20; logfont.IfWidth = 0; logfont.IfEscapement = 0; logfont.IfOrientation = 0; logfont.IfWeight = FW_BOLD; logfont.IfItalic = FALSE; logfont.IfUnderline = FALSE; logfont.IfStrikeOut = FALSE; logfont.IfCharSet = ANSI_CHARSET; logfont.IfOutPrecision = OUT_DEFAULT_PRECIS; logfont.IfClipPrecision = CLIP_DEFAULT_PRECIS; logfont.IfQuality = DEFAULT_QUALITY; logfont.IfPitchAndFamily = DEFAULT_PITCH; strcpy(logfont.IfFaceName,"Arial"); // Создаем шрифт и таблицу отображения hFont = CreateFontIndirect(&logfont); SelectObject (hDC, hFont); // Создаем таблицы отображения для глифов от 0 до 128 nFontList = glGenLists(128); wglUseFontBitmaps(hDC, 0, 128, nFontList); DeleteObject(hFont); // Исходный шрифт больше не нужен // Черный фон glClearColor(O.Of, O.Of, O.Of, l.Of ); } /////////////////////////////////////////////////////////////////// // Рисуем все (в данном случае - только текст) void RenderScene(void) glClear(GL_COLOR_BUFFER_BIT); // Синий трехмерный текст. Обратите внимание на установку // цвета перед растровым положением glColor3f(l.Of, l.Of, l.Of); glRasterPos2i(0, 200); glListBase(nFontList) ; glCallLists (13, GL_UNSIGNED_BYTE, "OpenGL Rocks!"); Обратите внимание на то, что функция wglUseFontBitmaps гораздо проще рас- смотренного выше кода. Ей требуется только обработчик контекста устройства, пер- вый и последний символ плюс имя первой таблицы отображения wglUseFontBitmaps(hDC, 0, 128, nFontList); Поскольку растровые шрифты создаются согласно реальной форме и отобража- ются непосредственно в пиксели экрана, компонент If Height структуры LOGFONT используется точно так же, как при растеризации шрифтов GDI.
660 Часть II. OpenGL повсюду Рис. 13.8. Результат выполнения программы TEXT2D Полноэкранная визуализация По мере того как OpenGL становился популярным в среде производителей игр для ПК, возникал вопрос: как с помощью OpenGL выполнить полноэкранную визуализа- цию? На самом деле, если вы читаете эту главу с начала, то уже знаете, как с помощью OpenGL выполнять полноэкранную визуализацию — точно так же, как визуализацию в любом другом окне! Правильно данный вопрос следует формулировать так: как создать окно, занимающее весь экран и без рамки по краям? Как только удастся это сделать, визуализация в данном окне не будет отличаться от визуализации в любом другом окне, разобранной в примерах этой книги. Хотя данный вопрос не связан строго с OpenGL, он представляет интерес для большинства читателей, поэтому остановимся на нем подробнее. Создание окна без рамки Первой задачей является создание окна, не имеющего рамки или подписи. Эта процедура достаточно проста. Ниже приводится код создания окна из программы GLRECT. Единственное, что мы сделали, — использовали стиль окна WS_POPUP вме- сто WS_OVERLAPPEDWINDOW. // Создается основное окно приложения hWnd = CreateWindow(IpszAppName, IpszAppName, // OpenGL требует WS_CLIPCHILDREN //и WS_CLIPSIBLINGS WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, // Расположение и размер окна 100, 100, 250, 250, NULL, NULL, hlnstance, NULL);
Глава 13. Wiggle: OpenGL в системе Windows 661 Рис. 13.9. Окно без подписи и рамки Результат выполнения программы с таким кодом показан на рис. 13.9. Как видно, если не настроить надлежащим образом стиль, окно не будет иметь ни подписи, ни рамки. Не забывайте, что теперь окно не содержит кнопки, щелкнув на которой, можно его закрыть. Чтобы теперь выйти из программы, пользователю нужно нажать Alt+F4. Большинство так называемых “благожелательных к пользователю” (user-friendly) программ для завершения ожидают от пользователя ввода с клавиатуры (например, нажатия клавиши <Esc> или <Q>) . Создание окна на весь экран Создание окна, размер которого равен размеру экрана, почти так же тривиально, как и создание окна без подписи или рамки. Параметры функции CreateWindow позво- ляют задавать, где будет располагаться левый верхний угол окна, а также указывать ширину и высоту окна. Для создания полноэкранного окна в качестве левого верхнего угла следует всегда использовать (0,0). Единственная проблема связана с определение размера рабочего стола, чтобы вы могли задать правильную ширину и высоту окна. Данную информацию легко получить, используя функцию Windows GetDeviceCaps. В листинге 13.9 (фрагмент программы FSCREEN) показан новый вариант функ- ции WinMain. Чтобы использовать GetDeviceCaps, требуется обработчик контекста устройства. Поскольку мы находимся в процессе создания основного окна, нужно использовать контекст окна рабочего стола. Листинг 13.9. Создание полноэкранного окна // Точка входа всех программ Windows int APIENTRY WinMain( HINSTANCE hlnstance, HINSTANCE hPrevInstance, LPSTR IpCmdLine, int nCmdShow) // Структура сообщений Windows // Структура классов Windows // Память для хранения обработчика окна MSG msg; WNDCLASS wc; HWND hWnd; HWND hDesktopWnd; // Память для хранения обработчика окна рабочего стола HDC hDesktopDC; // Память для хранения контекста устройства окна int nScreenX, nScreenY; // Размеры окна
662 Часть II OpenGL повсюду 11 Регистрация стиля окна wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; wc.IpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hlnstance = hlnstance; wc.hlcon = NULL; wc.hCursor = LoadCursor(NULL, IDC_ARROW); // Фоновая краска для окна OpenGL не требуется wc.hbrBackground = NULL; wc.IpszMenuName = NULL; wc. IpszClassName = IpszAppName; // Регистрация класса окна if(Registerclass(&wc) == 0) return FALSE; // Получаем обработчик окна и контекст устройства // рабочего стола hDesktopWnd = GetDesktopWindow(); hDesktopDC = GetDC(hDesktopWnd); // Получаем размер экрана nScreenX = GetDeviceCaps(hDesktopDC, HORZRES); nScreenY = GetDeviceCaps(hDesktopDC, VERTRES); // Реализация контекста устройства рабочего стола ReleaseDC(hDesktopWnd, hDesktopDC); // Создаем основное окно приложения hWnd = CreateWindow(IpszAppName, IpszAppName, // OpenGL требует WS_CLIPCHILDREN и WS_CLIPSIBLINGS WS_POPUP I WS_CLIPCHILDREN | WS_CLIPSIBLINGS, // Положение и размер окна 0, 0, nScreenX, nScreenY, NULL, NULL, hlnstance, NULL); // Выход, если окно не было создано if(hWnd == NULL) return FALSE; // Отображаем окно ShowWindow(hWnd,SW_SHOW); UpdateWindow(hWnd); // Обработка сообщений приложения до закрытия приложени while( GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); ) return msg.wParam;
Глава 13 Wiggle- OpenGL в системе Windows 663 Ключевым в этом коде являются строки, задающие обработчик окна рабочего стола и контекст устройства. Далее контекст устройства можно использовать для получения горизонтального и вертикального разрешения экрана hDesktopWnd = GetDesktopWindow(); hDesktopDC = GetDC(hDesktopWnd); // Получаем размер экрана nScreenX = GetDeviceCaps(hDesktopDC, HORZRES); nScreenY = GetDeviceCaps(hDesktopDC, VERTRES); // Реализация контекста устройства рабочего стола ReleaseDC(hDesktopWnd, hDesktopDC); Если в системе несколько мониторов, следует обратить внимание на то, что воз- вращаемые значения соответствуют основному устройству отображения (монитору). Кроме того, вы можете принудительно сделать окно самым верхним (используя стиль окна WS_EX_TOPMOST). Однако, если вы это сделаете, может случиться так, что окно перестанет находиться в фокусе, но останется поверх всех остальных актив- ных окон. Это может запутать пользователя, если программа перестанет отвечать на ввод с клавиатуры Вас может заинтересовать функция Win32 ChangeDisplaySettings, подробно о которой можно прочесть в документации по набору инструментальных средств раз- работки программного обеспечения (Software Development Kit — SDK) Windows Эта функция позволяет динамически менять размер окна во время выполнения и восста- навливать его по завершении работы приложения Эта возможность желательна, если требуется полноэкранное окно, но с большим или меньшим разрешением экрана, чем принято по умолчанию Если вы меняете настройки рабочего окна, то вам не следует создавать окно визуализации или устанавливать пиксельный формат, пока не будут изменены настройки рабочего стола Контексты визуализации OpenGL, созданные в одной среде (настройки рабочего стола), скорее всего будут неприемлемы в другой Многопоточная визуализация Мощной особенностью программного интерфейса Win32 является многопотоковость Тем не менее в книге, посвященной компьютерной графике, тема организации поточ- ной обработки рассматриваться не будет По сути, для приложения поток является единицей выполнения Большинство программ выполняет команды последовательно от начала программы до ее завершения. Поток выполнения — это маршрут, проложен- ный по машинному коду, проходимый процессором при извлечении и выполнении команд Создавая с помощью программного интерфейса Win32 несколько потоков, вы можете прокладывать по исходному коду множественные маршруты, которые будут проходиться одновременно. Многопотоковость проще всего представлять как возможность вызова двух функ- ций, которые выполняются одновременно. Разумеется, процессор не может одно- временно выполнять две части кода, поэтому он переключается между потоками в процессе нормального течения программы почти так же, как многозадачная опера- ционная система переключается между задачами
664 Часть II OpenGL повсюду Программа, тщательно сконструированная под многопотоковое выполнение, пре- восходит однопоточное приложение по многим параметрам На однопроцессор- ном компьютере, например, один поток может обрабатывать запросы ввода-вывода, а другой — обслуживать графический интерфейс пользователя На многопроцессор- ном компыоюрс с симметричной многопроцессорной обработкой (Symmetric Multi- Processing — SMP) выполнением программы в действительности можс> одновременно заниматься несколько процессоров Впрочем, обратите внимание на то, что обработка по схеме SMP не поддерживается старыми версиями Windows (95/98/МЕ) Многопоточная обработка требует тщательного планирования и обычно при неправильном использовании на однопроцессорном компьютере приводит к более медленной или менее эффективной работе приложении Кроме того, если программа нс прошла всестороннего тестирования, она может никогда нс сбогпь при работе на однопроцессорном компьютере, но при этом на многопроцсссоном компьютере все се недостатки проявятся во всей полноте Некоторые реализации OpenGL выгодно используют преимущества многопро- цессорных систем Если, например, блоки преобразования и освещения конвейера OpenGL нс ускоряются аппаратно, драйвер может создать другой поток, так что эти расчеты будут выполняться одним процессором, гогда как друюй будет заниматься передачей преобразованных данных на схему растеризации Вы можете подумать, что использование двух потоков для визуализации OpenGL ускорит визуализацию в целом Возможно, один поюк стоит за!рузить рисованием фоновых объектов на сцене, тогда как другой будет рисовать более динамические обьскты Однако практически всегда такая конфигурация является неудачным реше- нием Хотя вы можете создать два контекста визуализации OpenGL для двух различ- ных потоков, большинство драйверов откажет, если вы попытаетесь визуализировать оба этих контекста в одном окне Технически такая мноюпотоковость возможна, и общая реализация Microsoft (а также многие аппаратные реализации) ее допускает В реальном же мире дополнительная нагрузка на драйвер с двумя контекстами, пыта- ющимися совместно использовать один буфер кадров, скорее всего перевесит любой выигрыш производительности, который вы надеетесь попучшь блаюдаря использо- ванию нескольких потоков Многопотоковость может улучшить визуализацию OpenGL в многопроцессорной системе или даже в однопроцессорной системе по крайней мере двумя способами Согласно первому сценарию, создаются два различных окна, каждое из которых имеет свой контекст визуализации и поток выполнения Данный сценарий может не при- ниматься некоторыми драйверами (некоторые игровые консоли даже нс позволяют двум приложениям одновременно использовать OpenGL1). ио для многих професси- ональных реализаций OpenGL он не представляет проблемы Второй сценарии реализуется при написании игры или приложения имитации в реальном времени Вы можос иметь рабочий поюк с физическими расчетами, ис- кусственным интеллектом или обработчиком взаимодсиспзия с пользователем, тогда как другой поток будет выполнять визуализацию OpenGL Этот сценарий требует аккуратного совместного использования данных нсско гькими потоками, но он может существенно повысить производительность па двухпроцессорном компьютере, и да- же однопроцессорный компьютер может повысить быстроту реагирования програм- мы Хотя выше мы сказали, что многопоточное программирование в данной книге нс
Глава 13 Wiggle OpenGL в системе Windows 665 рассма1ривасгся, на компакт-диске представлена программа RTHREAD, создающая и использующая поток визуализации Кроме того, данная программ демонстрирует использование расширений OpenGL WGL OpenGL и расширения WGL На платформе Windows вы нс имеете прямого доступа к драйверу OpenGL Все вы- зовы функций OpenGL проходят через системный файл opengl32.dll Поскольку эта библиотека понимает точки входа (имена функции) только OpenGL 1 1, нужен механизм, посредством которого можно получить указатель на функцию OpenGL, поддерживаемую непосредственно драйвером К счастью, реализация OpenGL си- стемы Windows содержит функцию wglGetProcAddress, позволяющую извлекать указатель на функцию OpenGL, поддерживаемую драйвером, но не обязательно под- держиваемую библиоюкой opengl32.dll PROC wglGetProcAddress(LPSTR IpszProc); Приведенная функция принимает названия функций или расширений OpenGL и возвращает указатель на функцию, который вы можете использовать для непосред- ственного вызова этой функции Чтобы воспользоваться этой возможностью, нужно знать прототип функции, на основе которого создается указатель на нее и впослед- ствии вызывается сама функция Расширения OpenGL (и другие элементы, появившиеся после версии 1 1) можно разбить на две группы Некоторые из них представляют собой просто новые констан- ты и перечислимые типы, которые распознаются аппаратным драйвером поставщика Другие требуют вызова новых функций, добавленных к программному интерфей- су приложения Число расширений велико, особенно если вы добавляете новейшие функциональные возможности OpenGL и расширения, поставляемые производителя- ми Полный обзор всех расширений OpenGL потребовал бы отдельной книги (если не энциклопедии1) Полный перечень расширений можно найти в Internet, в частности на Web-сайтах, указанных в приложении А, “Что еще почитать ” К счастью, программный доступ к большинству приложений OpenGL дает следу- ющие два заголовочных файла (♦include <wglext h> ♦include <glext.h> Эти файлы можно найти на Web-сайте, посвященном расширениям OpenGL, но они также поддерживаются большинством производителей видеокарт (см их сай- 1ы поддержки) Кроме того, последние версии на момент выхода книги включены в папку \common на компакт-диске Заюловок wglext.h содержит несколько расши- рений, характерных для Windows, a glext.h содержит как стандаршые расширения OpenGL, так и множешво расширений, поставляемых производителями Простые расширения Поскольку в книге рассматриваю гея известные элементы OpenGL вплоть до версии 2 0, вы, возможно, уже обнаружили, что данные расширения для Windows использу- Ю1ся во MHOIMX программах-примерах Например, в 1лавс 9, “Наложение гексгуры
666 Часть II OpenGL повсюду следующий шаг”, мы показали, как с помощью вызова указанной ниже функции добавлять зеркальные блики к текстурной геометрии, используя отдельный зеркаль- ный цвет OpenGL. glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, 11 GL_SEPARATE_S PECULAR_COLOR); Однако данная возможность отсутствует в OpenGL 11, и ни константа GL_LIGHT_MODEL_COLOR_CONTROL, НИ GL_SEPARATE_SPECULAR_COLOR не опреде- лены в версии gl.h для системы Windows Тем не менее они присутствуют в glext. h, а файл автоматически включен во все примеры книги посредством за- головочного файла OpenGLSB.h. С другой стороны, функция glLightModeli суще- ствует с версии OpenGL 1.0. Простые расширения подобного типа просто передают новые токены существующим точкам входа (функциям) и требуют только, чтобы бы- ли определены константы и расширение или элемент поддерживались аппаратным обеспечением Даже если версия OpenGL заявлена как 1.1, данная возможность все же может быть включена в драйвер. Изначально этот элемент был расширением, позже его ввели в основные функциональные возможности OpenGL. Проверить наличие это- го и других легкодоступных расширений (для которых не требуются указатели на функции) можно с помощью следующей функции GITools: bool gltlsExtSupportedfconst char *szExtension); Для случая отдельного зеркального цвета можно просто использовать такой код: if(gltIsExtSupported(GL_EXT_separate_specular_color)) RenderOnce(); else UseMultiPassTechnique(); Здесь вы вызываете функцию RenderOnce, если расширение (или элемент) под- держивается, и функцию UserMultiPassTechnique — для альтернативной и бо- лее медленной визуализации, дающей тот же эффект (рисуются и смешиваются два изображения). Использование новых точек входа Более сложный пример расширения использовался в программе IMAGING (глава 7) В этом случае необязательное подмножество построения изображений (imaging sub- set) не только отсутствует в версии файла gl.h для Windows, но также является необязательным во всех последующих версиях OpenGL Здесь мы имеем пример эле- мента, который либо присутствует, либо продолжать дальше не имеет смысла Сле- довательно, вначале вы проверяете наличие подмножества построения изображений через его строку расширения // Проверяется наличие контекста построения изображений; нужно // сделать после // создания окна, иначе не будет контекста OpenGL, которому // можно направить запрос if(glt!sExtSupported("GL_ARB_imaging") == 0) { printf("Imaging subset not supported\r\n" ) ; return 0; }
Глава 13 Wiggle OpenGL в системе Windows 667 Использованный прототип функций typedefs находится в glext.h, и они при- меняются для создания указателей на все функции, которые могут потребоваться На платформе Macintosh стандартные системные заголовки уже содержат эти функции #ifndef __APPLE___ // Данные определения типов находятся в glext.h PFNGLHISTOGRAMPROC glHistogram = NULL; PFNGLGETHISTOGRAMPROC glGetHistogram = NULL; PFNGLCOLORTABLEPROC glColorTable = NULL; PFNGLCONVOLUTIONFILTER2DPROC glConvolutionFilter2D = NULL; #endif Теперь используется функция glTools gltGetExtensionPointer для извлечения указателя на нужную функцию. Эта функция представляет собой просто переноси- мую оболочку для функции Windows wglGetProcAddress и более сложного метода, реализованного в Apple для получения указателей на функции #ifndef ___APPLE__ glHistogram = gltGetExtensionPointer("glHistogram"); glGetHistogram = gltGetExtensionPointer("glGetHistogram"); glColorTable = gltGetExtensionPointer("glColorTable"); glConvolutionFilter2D = gltGetExtensionPointer("glConvolutionFilter2D"); #endif После такого кода вы просто используете расширения так же, как если бы они были нормально поддерживаемыми элементами программного интерфейса приложе- ния API // Начинаем сбор данных гистограммы, 256 значений освещенности glHistogram(GL_HISTOGRAM, 256, GL_LUMINANCE, GL_FALSE); glEnable(GL_HISTOGRAM); Расширения WGL Кроме названных, доступно еще несколько расширений WGL, работающих в среде Windows, например расширение, введенное в главе 2 К точкам входа расширений WGL обращаются точно так же, как и к точкам входа других расширений — посред- ством функции wglGetProcAddress Правда, существует исключение Обычно сре- ди множества расширений WGL с использованием glGetString(GL_EXTENSIONS) официально объявляются только два Это ранее упомянутое расширение обмена ин- тервалов и расширение WGL_ARB_extensions_string Последнее предоставляет еще одну точку входа, используемую исключительно для организации запросов к рас- ширениям WGL Прототип функции строки расширения ARB имеет следующий вид const char *wglGetExtensionsStringARB(HDC hdc); Эта функция извлекает список расширений WGL точно так же, как рассмотренная ранее функция glGetString Используя заголовочный файл wglext.h, вы можете извлечь указатель на эту функцию PFNWGLGETEXTENSIONSSTRINGARBPROC ‘wglGetExtensionsStringARB; wglGetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC) wglGetProcAddress("wglGetExtensionsStringARB");
668 Часть II OpenGL повсюду ТАБЛИЦА 13.3. Атрибуты пиксельного формата Константа Описание WGL_NUMBER_PIXEL_FORMATS_ARB Число пиксельных форматов для этого устройства WGL_DRAW_TO_WINDOW_ARB Не нуль если пиксельный формат может использоваться с окном WGL_DRAW_TO_BITMA P_ARB Не нуль, если пиксельный формат может использоваться с аппаратно-независимым растром (Device Independent Bitmap — DIB) памяти Функция glGetString возвращает идентификатор WGL_ARB_extensions_ string, но, как показано в предыдущем фрагменте кода, очень часто разработчи- ки пропускают згу проверку и просто ищут точку входа Такой подход в общем случае безопасен для большинства расширений OpenGL, но следует помнить, чго это нс совеем корректно Некоторые производители экспортируют расширения на “жс- периментальной” основе, и эти расширения могу! нс поддерживаться официально, также функции могут нс работать корректно, если вы пропустите строку проверки расширения Кроме того, одну функцию или несколько функций можно использовать несколько расширений Проверка доступности только самих функций нс даст инфор- мации о доступности конкретного расширения или поддерживаемых расширений Расширенные пиксельные форматы Пожалуй, одним из важнейших расширений WGL, доступных в среде Windows, яв- ляется WGL_ARB_pixel_format Это расширение прсдла!ает механизм, позволяю- щий проверять и выбирать элементы пиксельного формата, нс существовавшие при первом создании PIXELFORMATDESCRIPTOR Например, сели драйвер поддерживает визуализацию с множественной выборкой (применяемой, например, с целью защи- ты от наложения всей сцены), с помощью старых полей PIXELFORMATDESCRIPTOR пиксельный формат с такой поддержкой выбрать нельзя никак Если же данное рас- ширение поддерживается, драйвер экспортирует следующие функции BOOL wglGetPixelFormatAttribivARB(HDC hdc, GLint iPixelFormat, GLint iLayerPlane, GLuint nAttributes, const GLint *piAttributes, GLint *piValues); BOOL wglGetPixelFormatAttribfvARB(HDC hdc, GLint iPixel Format, GLint iLayerPlane, GLuint nAttributes, const GLint *piAttributes, GLfloat *pfValues) ; Приведенные два варианта одной! функции позво 1яюг запрашивать конкретный индекс пиксельного формата и извлекать массив, содержащий данные по параметрам >101 о формат 11срвый api умент (hdc) представляет собой контекст устройства окна, в коюром буди шло 1ьюван пиксельный формат, после чего следует индекс пиксель- ною формата ApiyMcm iLayerPlane задаст, какую плоскость уровня запрашивать (О - если рсашзация нс поддерживает плоскости уровней) Следующий аргумент, nAttnoutes >адас! сколько атрибутов запрашивается для данного формата, а мас- сив piAttrxoutes содержит список запрашиваемых имен атрибутов Список воз- можных афибутов приводится в табл 13 3 Последний аргумент является массивом, запо шясмым соответствующими атрибутами пиксельного формата
Глава 13 Wiggle OpenGL в системе Windows 669 (Продолжение табл 13 3) Константа Описание WGL_DE РТН_ВITS_ARB WGL_STENCIL_BITS_ARB WGL_ACCELERATION_ARB WGL_NEED_PALETTE_ARB WGL_NEED_SYSTEM_PALETTE_ARB WGL_SWAP_LAYER_BUFFERS_ARB WGL_SWAP_METHOD_ARB WGL_NUMBER_OVERLAYS_ARB WGL_NUMBER_UNDERLAYS_ARB WGL_TRANSPARENT_ARB WGL_TRANSPARENT_RED_VALUE_ARB WGL_TRANSPARENT_GREEN_VALUE_ARB WGL_TRANSPARENT_BLUE_VALUE_ARB WGL_TRANSPARENT_ALPHA_VALUE_ARB WGL_SHARE_DEPTH_ARB WGL_SHARE_STENCIL_ARB WGL_SHARE_ACCUM_ARB WGL_SUPPORT_GDI_ARB WGL_S UP PORT_O PENGL_ARB WGL_DOUBLE_BUFFER_ARB WGL_STEREO_ARB WGL_PIXEL_TYPE_ARB WGL_TYPE_RGBA_ARB WGL_TYPE_COLORINDEX_ARB WGL_COLOR_BITS_ARB Число битов в буфере глубины Число битов в буфере трафарета Одно из значений, указанных в табл 13 4, которое задает используемый аппаратный драйвер Не нуль, если требуется палитра Не нуль, если аппаратное обеспечение поддерживает только одну палитру в режиме 256 цветов Не нуль, если аппаратное обеспечение поддерживает переключение плоскостей уровней Метод, посредством которого выполняется переключение буферов в пиксельных форматах с двойной буферизацией Возможные значения перечислены в табл 13 5 Число накладывающихся плоскостей Число перекрываемых плоскостей Нв нуль, если поддерживается прозрачность Прозрачный красный цвет Прозрачный зеленый цвет Прозрачный синий цвет Прозрачный компонент альфа Не нуль, если плоскости уровней совместно используют буфер цвета с основной плоскостью Не нуль, если плоскости уровней совместно используют буфер трафарета с основной плоскостью Не нуль, если плоскости уровней совместно используют буфер накопления с основной плоскостью Не нуль если поддерживается визуализации GDI (только передний буфер) Не нуль если поддерживается OpenGL Не нуль если поддерживается двойная буферизация Не нуль, если поддерживаются левый и правый буферы Режимы RGBA-цвета Режим индексирования цвета Число битовых плоскостей в буфере цвета
670 Часть II OpenGL повсюду (Окончание табл 13 3) Константа Описание WGL_RED_BITS_ARB Число красных битовых плоскостей в буфере цвета WGL_RED_SHIFT_ARB WGL_GREEN_BITS_ARB Смещение для красных битовых плоскостей Число зеленых битовых плоскостей в буфере цвета X X X X 0 0 0 0 'w 'to Q S a c § й M M M > 2 1 w w I w ас и w и н H ac H 4 W H W H 1 Hj Смещение для зеленых битовых плоскостей Число синих битовых плоскостей в буфере цвета Смещение для синих битовых плоскостей Число битовых плоскостей для параметра альфа в буфере цвета WGL_ALPHA_SHIFT_ARB WGL_ACCUM_BITS_ARB WGL_ACCUM_RED_BITS_ARB Смещение для альфа-плоскостей Число битовых плоскостей в буфере накопления Число битовых плоскостей красного цвета в буфере накопления WGL_ACCUM_GREEN_BIT S_ARB Число битовых плоскостей зеленого цвета в буфере накопления WGL_ACCUM_BLUE_BIT S_ARB Число битовых плоскостей синего цвета в буфере накопления WGL_ACCUM_ALPHA_BITS_ARB Число битовых плоскостей альфа в буфере накопления WGL_AUX_BUFFERS_ARB Число вспомогательных буферов Если вы желаете вызвать функцию wglGetPixelFormatAttrib, контекст визуа- лизации OpenGL должен быть текущим (как и для других расширений). Это означа- ет, что вначале нужно создать временное окно, задать пиксельный формат, используя PIXELFORMATDESCRIPTOR, а затем извлечь и использовать указатель на одну из функ- ций wglGetPixelFormatAttribARB. Удобным местом для этого может быть экран приветствия или первичное диалоговое окно Options, предоставляемое пользовате- лю При этом вы не должны пытаться использовать рабочий стол Windows, поскольку приложение им не владеет' В приведенном ниже простом примере запрашивается один атрибут — число под- держиваемых пиксельных форматов (это позволяет узнать, в какой среде придется искать нужный формат) int attrib[] = { WGL_NUMBER_PIXEL_FORMATS_ARB }; int nResults[0]; wglGetPixelFormatAttributeivARB(hDC, 1, 0, 1, attrib, nResults); // nResults[0] теперь содержит число экспортированных // пиксельных форматов Более подробный пример, демонстрирующий поиск конкретного пиксельного фор- мата (включая формат с множественной выборкой), рассмотрен ниже в программе SPHEREWORLD32.
Глава 13. Wiggle: OpenGL в системе Windows 671 Рис. 13.10. Первоначальное диалоговое окно Options программы SPHEREWORLD32 ТАБЛИЦА 13.4. Метки ускорения для wgl_acceleration_arb Константа Описание WGL_NO_ACCELERATION_ARB Программная визуализация, без ускорения WGL_GENERIC_ACCELERATION_ARB Ускорения посредством драйвера MCD WGL_FULL_ACCELERATION_ARB Ускорение посредством драйвера ICD ТАБЛИЦА 13.5. Коды переключения буферов для wgl_swap_method_arb Константа Описание WGL_SWAP_EXCHANGE_ARB WGL_SWAP_COPY_ARB WGL_SWAP_UNDEFINED_ARB Переключение меняет передний и задний буферы Задний буфер копируется в передний Задний буфер копируется в передний, но содержимое заднего буфера остается неопределенным после переключения буферов Максимально используем Win32 SPHEREWORLD32 — это работающая под Win32 версия “мира сфер”, который мы многократно переделываем для иллюстрации различных концепций. SPHERE- WORLD32 позволяет выбирать оконный или полноэкранный режим, менять при необходимости настройки дисплея, а также находить и выбирать пиксельный формат с множественной выборкой. Наконец, с помощью работающих под Windows шрифто- вых эффектов вы отображаете на экране частоту смены кадров и другую информацию. В полноэкранном режиме вы даже можете использовать Alt+Tab для переключения из программы, причем до повторного выбора окно будет минимизировано. Полный исходный код этой “окончательной” программы Win32, предоставленный в листинге 13.10, снабжен расширенными комментариями, объясняющими каждый аспект программы. В отображаемом исходном диалоговом окне (см. рис. 13.10) мож- но выбрать полноэкранный или оконный режим, визуализацию с множественными выборками (если она доступна), а также указать, требуется ли расширение переклю- чения интервалов. Вид работающей программы показан на рис. 13.11.
672 Часть II. OpenGL повсюду Рис. 13.11. Результат выполнения программы SPHEREWORLD32 Листинг 13.10. Исходный код программы SPHEREWORLD32 // SphereWorld32.с // OpenGL. Суперкнига // Автор: Ричард С. Райт—мл. // В данной программе демонстрируется полнофункциональная // устойчивая структура OpenGL для Win32 //////////////////////////////////////////////////////////////////? // Включаются файлы ♦include <windows.h> // Структура Win32 (не MFC) ♦include <gl\gl.h> // OpenGL ♦ include <gl\glu.h> 11 библиотека GLU ♦include <stdio.h> // Стандартный ввод/вывод (sprintf) ♦ include " . .\..\common\wglext.h" // Заголовок расширения WGL ♦ include " . .\..\common\glext.h" 11 Заголовок расширения OpenGL ♦ include "..\..\common\gltools.h" // Библиотека GLTools ♦include "resource.h" // Ресурсы диалога // Исходные опции визуализации, заданный пользователем struct STARTUPOPTIONS { DEVMODE devMode; // Какой режим отображения использовать int nPixelFormat; // Какой пиксельный формат использовать int nPixelFormatMS;
Глава 13 Wiggle: OpenGL в системе Windows 673 // Пиксельный формат с множественной выборкой BOOL bFullScreen; // Полный экран? BOOL bFSAA; BOOL bVerticalSync; }; /////////////////////////////////////////////////////////////////// // Глобальные переменные модуля static HPALETTE hPalette = NULL; // Обработчик палитры static HINSTANCE ghlnstance = NULL; // Обработчик экземпляра модуля static LPCTSTR IpszAppName = "SphereWorld32"; // Имя приложения static GLint nFontList; // Стандартная таблица отображения для шрифта static struct STARTUPOPTIONS startupoptions; // Информация об опциях при запуске static LARGE_INTEGER CounterFrequency; static LARGE_INTEGER FPSCount; static LARGE_INTEGER CameraTimer; ♦ define NUM_SPHERES 30 // Число сфер GLTFrame spheres[NUM_SPHERES]; // Положение сфер GLTFrame framecamera; // Положение и ориентация камеры // Информация по свету и материалу GLfloat fLightPos[4] = { -100.Of, 100.Of, 50.Of, l.Of }; // Точечный источник GLfloat fNoLight[] = { O.Of, O.Of, O.Of, O.Of }; GLfloat fLowLight[] = { 0.25f, 0.25f, 0.25f, l.Of }; GLfloat fBrightLight[] = { l.Of, l.Of, l.Of, l.Of }; // Матрица тени GLTMatrix mShadowMatrix; // Идентификаторы текстуры ♦ define GROUND_TEXTURE 0 ♦ define TORUS_TEXTURE 1 ♦ define SPHERE_TEXTURE 2 ♦define NUM_TEXTURES 3 GLuint textureobjects[NUM_TEXTURES]; const char *szTextureFiles[] = {"grass.tga", "wood.tga", "orb.tga"}; // Таблицы отображения сферы и тора GLuint ITorusList, ISphereList; /////////////////////////////////////////////////////////////////// // Прямые объявления // Объявление процедур Window LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM IParam); // Процедура диалога при запуске BOOL APIENTRY StartupDlgProc (HWND hDlg, UINT message, UINT wParam, LONG IParam); // Находим лучший доступный пиксельный форма; включается, //если доступна множественная выборка void FindBestPF(HDC hDC, int *nRegularFormat, int *nMSFormat); BOOL ShowStartupOptions(void); // Первоначальный диалог при запуске
674 Часть II OpenGL повсюду void ChangeSize(GLsizei w, GLsizei h); // Меняем проекцию и поле просмотра void RenderScene(void); // Рисуем все void SetupRC(HDC hDC); // Устанавливаем контекст визуализации void ShutdownRC(void); // Выключаем контекст визуализации HPALETTE GetOpenGLPalette(HDC hDC); // Создаем палитру 3-3-2 void Drawinhabitants(GLint nShadow); // Рисуем население // мира сфер void DrawGround(void); // Рисуем землю /////////////////////////////////////////////////////////////////// // Указатели на функции расширения PFNWGLGETPIXELFORMATATTRIBIVARBPROC wglGetPixelFormatAttribivARB = NULL; PFNGLWINDOWPOS2IPROC glWindowPos2i = NULL; PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = NULL; /////////////////////////////////////////////////////////////////// // Размер окна изменился. Обновляем, чтобы согласовать // с координатами окна void ChangeSize(GLsizei w, GLsizei h) ( GLfloat fAspect; // Предотвращает деление на нуль, когда окно слишком маленькое // (нельзя сделать окно нулевой ширины). if(h == 0) glViewport(0, 0, w, h); fAspect = (GLfloat)w / (GLfloat)h; // Система координат обновляется перед модификацией glMatrixMode(GL_PROJECTION); glLoadldentity(); // Устанавливается объем отсечения gluPerspective(35.Of, fAspect, l.Of, 50.Of); glMatrixMode(GL_MODELVIEW); glLoadldentity(); } /////////////////////////////////////////////////////////////////// // Рисуем землю как набор лент треугольников void DrawGround(void) { GLfloat fExtent = 20.Of; GLfloat fStep = l.Of; GLfloat у = -0.4f; GLfloat iStrip, iRun; GLfloat s = O.Of; GLfloat t = O.Of; GLfloat texStep = l.Of I (fExtent * .075f); I/ Земля представляет собой мозаичную текстуру glBindTexture(GL_TEXTURE_2D, textureObjects[GROUND_TEXTURE]); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
Глава 13 Wiggle OpenGL в системе Windows 675 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Раскладываем ленты и повторяем текстурные координаты for(iStrip = -fExtent; iStrip <= fExtent; iStrip += fstep) { t = O.Of; glBegin(GL_TRIANGLE_STRIP); for(iRun = fExtent; iRun >= -fExtent; iRun -= fstep) { glTexCoord2f(s, t); glNormal3f(O.Of, l.Of, O.Of); glVertex3f(iStrip, y, iRun); glTexCoord2f(s + texStep, t); glNormal3f(O.Of, l.Of, O.Of); glVertex3f(iStrip + fstep, y, iRun); t += texStep; } glEnd(); s += texStep; } } /////////////////////////////////////////////////////////////////// // Рисуем случайных "жителей" и дуэт "вращающийся тор/сфера" void Drawinhabitants(GLint nShadow) static GLfloat yRot = O.Of; // Угол поворота для анимации GLint i; if(nShadow == 0) { yRot += 0.5f; glColor4f(l.Of, l.Of, l.Of, l.Of); } else glColor4f(0.Of, O.Of, .Of, ,75f); // Цвет тени // Рисуем случайным образом расположенные сферы glBindTexture(GL_TEXTURE_2D, textureObjects[SPHERE_TEXTURE]); for(i = 0; 1 < NUM_SPHERES; i++) { glPushMatrix(); gltApplyActorTransform(&spheres[i]); glCallList(ISphereList); glPopMatrix(); } glPushMatrix(); glTranslatef(O.Of, O.lf, -2.5f); glPushMatrix(); glRotatef(-yRot * 2.Of, O.Of, l.Of, O.Of); glTranslatef(1.Of, O.Of, O.Of); glCallList(ISphereList); glPopMatrix(); if(nShadow == 0) // Сам по себе тор будет зеркальным glMaterialfv(GL_FRONT, GL_SPECULAR, fBrightLight);
676 Часть II OpenGL повсюду } glRotatef(yRot, O.Of, l.Of, O.Of); glBindTexture(GL_TEXTURE_2D, textureObjects[TORUS_TEXTURE]); glCallList(ITorusList); glMaterialfv(GL_FRONT, GL_SPECULAR, fNoLight); glPopMatrix(); } //IIIIIIII III III III IIIIIIII III IIII III III III III III IIIIII III II III III/ II Рисуем все void RenderScene(void) { static int iFrames = 0; II Подсчитываем кадры для расчета частоты смены кадров // каждые 100 кадров static float fps = O.Of; // Рассчитываем частоту смены кадров // Очищаем окно glClear(GL_COLOR_BUFFER_BIT I GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glPushMatrix(); gltApplyCameraTransform(&frameCamera); II Движение камеры/мира // Помещаем источник света до начала других преобразований glLightfv(GL_LIGHTO, GL_POSITION, fLightPos); II Рисуем землю glColor3f(l.Of, l.Of, l.Of); DrawGround(); H Вначале рисуем тень glDisable(GL_DEPTH_TEST); glDisable(GL_LIGHTING) ; glDisable(GL_TEXTURE_2D); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_STENCIL_TEST); glPushMatrix(); glMultMatrixf(mShadowMatrix); Drawinhabitants(1); glPopMatrix(); glDisable(GL_STENCIL_TEST); glDisable(GL_BLEND); glEnable(GL_LIGHTING); glEnable(GL_TEXTURE_2D); glEnable(GL_DEPTH_TEST); II Нормально рисуем "жителей" Drawinhabitants(0); glPopMatrix(); II Рассчитываем частоту смены кадров (каждые 100 кадров) iFrames++; if(iFrames == 100) { float fTime; II Получаем текущий счетчик LARGE_INTEGER ICurrent;
Глава 13 Wiggle- OpenGL в системе Windows 677 QueryPerformanceCounter(&lCurrent); fTime = (float)(ICurrent.QuadPart - FPSCount.QuadPart) / (float)CounterFrequency.QuadPart; fps = (float)iFrames / fTime; // Обновляем счетчик кадров и таймер iFrames = 0; QueryPerformanceCounter(&FPSCount); } // Если доступно расширение положения окна, отображаем // частоту смены кадров и сообщаем, если была // активизирована множественная выборка //и если была включена вертикальная синхронизация if(glWindowPos2i != NULL) { int iRow = 10; char cBuffer[64]; // Отключаем проверку по глубине, освещение и наложение //текстуры gIDisable(GL_DEPTH_TEST); gIDisable(GL_LIGHTING) ; gIDisable(GL_TEXTURE_2D) ; glColor3f(l.Of, l.Of, l.Of); 11 Устанавливаем положение и отображаем сообщение glWindowPos2i(0, iRow); glListBase(nFontList) ; glCallLists (13, GL_UNSIGNED_BYTE, "OpenGL Rocks!"); iRow+= 20; // Отображаем частоту смены кадров sprintf(eBuffer,"FPS: %.If", fps); glWindowPos2i(0, iRow); glCallLists(strlen(cBuffer), GL_UNSIGNED_BYTE, eBuffer); iRow += 20; // Множественная выборка использовалась? if(startupoptions.bFSAA == TRUE && startupoptions.nPixelFormatMS != 0) { glWindowPos2i(0, iRow); glCallLists(25 ,GL_UNSIGNED_BYTE, "Multisampled Frame Buffer"); iRow += 20; } // Вертикальная синхронизация? if(wglSwapIntervalEXT != NULL && startupoptions.bVerticalSync == TRUE) { glWindowPos2i(0, iRow); glCallLists(9 ,GL_UNSIGNED_BYTE, "VSync On"); iRow += 20; } /I Возвращаем все назад glEnable(GL_DEPTH_TEST); glEnable(GL_LIGHTING) ; glEnable(GL_TEXTURE_2D);
678 Часть II OpenGL повсюду } } ////////////////////////////////////////////////////////////////у // Настройка. Создаем шрифт/растры, загружаем текстуры, // создаем таблицы отображения void SetupRC(HDC hDC) { GLTVector3 vPoints[3] = {{ O.Of, -0.4f, O.Of ), {10.Of, -0.4f, O.Of ), { 5.Of, -0.4f, -5.Of }); int iSphere; int i; // Настраиваем характеристики шрифта HFONT hFont; LOGFONT logfont; logfont.IfHeight = -20; logfont.IfWidth = 0; logfont.IfEscapement = 0; logfont.IfOrientation = 0; logfont.IfWeight = FWJ3OLD; logfont.IfItalic = FALSE; logfont.IfUnderline = FALSE; logfont.IfStrikeOut = FALSE; logfont.IfCharSet = ANSI_CHARSET; logfont.IfOutPrecision = OUT_DEFAULT_PRECIS; logfont.IfClipPrecision = CLIP_DEFAULT_PRECIS; logfont.IfQuality = DEFAULT_QUALITY; logfont.IfPitchAndFamily = DEFAULT_PITCH; strcpy(logfont.IfFaceName,"Arial") ; 11 Создаем шрифт и таблицу отображения hFont = CreateFontIndirect(&logfont) ; SelectObject (hDC, hFont); //Создаем таблицы отображения для глифов от 0 до 128 nFontList = glGenLists(128); wglUseFontBitmaps(hDC, 0, 128, nFontList); DeleteObject(hFont); // Исходный шрифт больше не нужен // Сероватый фон glClearColor(fLowLight[0], fLowLight[1], fLowLight[2], fLowLight[3]); // Очищаем буфер трафарета (заполняем нулями), значение // увеличивается на один, если кто-либо рисует в этом буфере // Если функция трафарета активизирована, записывать можно // только там, где значение трафарета равно нулю. // Это не дает прозрачной тени рисоваться на самой себе gIStencilOp(GL_INCR, GL_INCR, GL_INCR); glClearStencil(0) ; glStencilFunc(GL_EQUAL, 0x0, 0x01); // Отбрасываем задние част многоугольников glCullFace(GL_BACK) ; glFrontFace(GL_CCW);
Глава 13 Wiggle OpenGL в системе Windows 679 glEnable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); // Настройка параметров освещения glLightModelfv(GL_LIGHT_MODEL_AMBIENT, fNoLight); glLightfv(GL_LIGHTO, GL_AMBIENT, fLowLight); glLightfv(GL_LIGHTO, GL_DIFFUSE, fBrightLight); glLightfv(GL_LIGHTO, GL_SPECULAR, fBrightLight); glEnable(GL_LIGHTING) ; glEnable(GL_LIGHT0) ; // Расчет матрицы тени gltMakeShadowMatrix(vPoints, fLightPos, mShadowMatrix) ; // Согласовывается цвет материала glEnable(GL_COLOR_MATERIAL); glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE) ; glMateriali(GL_FRONT, GL_SHININESS, 128); gltlnitFrame(&frameCamera); // Инициализация камеры // Случайным образом помещаем сферических жителей for(iSphere = 0; iSphere < NUM_SPHERES; iSphere++) { gltlnitFrame(&spheres[iSphere]); // Инициализация кадра // Располагаем случайные точки между -20 и 20 с шагом 0.1 spheres[iSphere].vLocationf0] = (float)((rand() % 400) - 200) * O.lf; spheres[iSphere].vLocation(1] = O.Of; spheres[iSphere].vLocationf2] = (float)((rand() % 400) - 200) * O.lf; ) 11 Настройка карт текстуры glEnable(GL_TEXTURE_2D); glGenTextures(NUM_TEXTURES, textureObjects); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); // Загрузка обучающей текстуры for(i = 0; 1 < NUM_TEXTURES; i++) { GLubyte *pBytes; GLint iWidth, iHeight, iComponents; GLenum eFormat; glBindTexture(GL_TEXTURE_2D, textureObjects[i]); // загрузка данной карты текстуры pBytes = gltLoadTGA(szTextureFiles[i], &iWidth, &iHeight, &iComponents, &eFormat); gluBuild2DMipmaps(GL_TEXTURE_2D, iComponents, iWidth, iHeight, eFormat, GL_UNSIGNED_BYTE, pBytes); free(pBytes); // Трилинейное множественное отображение (mipmapping) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
680 Часть II OpenGL повсюду GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); } // Получаем указатель на функцию положения окна, // если она существует glWindowPos2i = (PFNGLWINDOWPOS2IPROC) wglGetProcAddress("glWindowPos2i"); // Получаем указатель на функцию переключения интервалов, // если она существует wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC) wglGetProcAddress("wglSwapIntervalEXT"); if(wglSwapIntervalEXT != NULL && startupoptions.bVerticalSync == TRUE) wglSwapIntervalEXT(1); // Активизируем, если множественная выборка была доступна // и была выбрана if(startupoptions.bFSAA == TRUE && startupoptions.nPixelFormatMS != 0) glEnable(GL_MULTISAMPLE_ARB); // Если доступен отдельные зеркальный цвет, делаем // тор блестящим if(gltlsExtSupportedf"GL_EXT_separate_specular_color")) glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR); // Инициализируем таймеры QueryPerformanceFrequency(&CounterFrequency); QueryPerformanceCounter(&FPSCount); CameraTimer = FPSCount; // Строим таблицы отображения для тора и сфер // (То же можно сделать и для земли) ITorusList = glGenLists(2); ISphereList = ITorusList + 1; glNewList(ITorusList, GL_COMPILE); gltDrawTorus(0.35f, 0.15f, 61, 37); glEndList(); glNewList(ISphereList, GL_COMPILE); gltDrawSphere(0.3f, 31, 16); glEndList(); } /////////////////////////////////////////////////////////////////Л // Выключаем контекст визуализации void ShutdownRC(void) { glDeleteLists(nFontList, 128); // Удаляем таблицы отображения шрифта glDeleteLists(ITorusList, 2); // Удаляем таблицы отображения объектов glDeleteTextures(NUM_TEXTURES, textureObjects); // Освобождаем текстуру //////////////////////////////////////////////////////////////////, // Если требуется, для указанного контекста устройства создается
Глава 13 Wiggle' OpenGL в системе Windows 681 // палитра 3-3-2 HPALETTE GetOpenGLPalette(HDC hDC) { HPALETTE hRetPal = NULL; // Обработчик создаваемой палитры PIXELFORMATDESCRIPTOR pfd; // Дескриптор пиксельного формата LOGPALETTE *pPal; // Указатель на память для логической палитры int nPixelFormat; // Индекс пиксельного формата int nColors; // Число элементов в палитре int i; // Переменная-счетчик BYTE RedRange,GreenRange, BlueRange; // Диапазон изменения всех компонентов цвета (7,7 и 3) // Получаем индекс пиксельного формата и извлекаем // описание пиксельного формата nPixelFormat = GetPixelFormat(hDC); DescribePixelFormat(hDC, nPixelFormat, sizeof(PIXELFORMATDESCRIPTOR),&pfd); // Требует ли этот пиксельный формат палитру? Если нет, // палитра не создается и возвращается NULL if(!(pfd.dwFlags & PFD_NEED_PALETTE)) return NULL; // Число элементов палитры. 8 бит дает 256 элементов nColors = 1 « pfd.cColorBits; // Распределяется память для структуры логоческой палитры плюс // всех элементов палитры pPal = (LOGPALETTE*)malloc(sizeof(LOGPALETTE)+ nColors*sizeof(PALETTEENTRY)); // Заполняем заголовок палитры pPal->palVersion = 0x300; // Windows 3.0 pPal->palNumEntries = nColors; // размер таблицы // Строим маску из всех единиц. Таким образом создается //число, представленное установленными // х младшими битами, где х = pfd.cRedBits, pfd.cGreenBits и // pfd.cBlueBits. RedRange = (1 « pfd.cRedBits) -1; GreenRange = (1 « pfd.cGreenBits) - 1; BlueRange = (1 « pfd.cBlueBits) -1; // Последовательно проходим по всем элементам палитры for(i =0; 1 < nColors; i++) { // Заполняем 8-битовые эквиваленты всех компонентов pPal->palPalEntry[i].peRed = (i >> pfd.cRedShift) & RedRange; pPal->palPalEntry[i].peRed = (unsigned char)( (double) pPal->palPalEntry[i].peRed * 255.О/RedRange); pPal->palPalEntry[i].peGreen = (i » pfd.cGreenShift) & GreenRange; pPal->palPalEntry[i].peGreen = (unsigned char)( (double)pPal->palPalEntry[i].peGreen * 255.О/GreenRange) ; pPal->palPalEntry[i].peBlue = (i » pfd.cBlueShift)
682 Часть II OpenGL повсюду & BlueRange; pPal->palPalEntry[i].peBlue = (unsigned char)( (double)pPal->palPalEntry[i].peBlue * 255.О/BlueRange); pPal->palPalEntry[i].peFlags = (unsigned char) NULL; } // Создаем палитру hRetPal = CreatePalette(pPal); // Продолжаем и освобождаем палитру для этого контекста //устройства SelectPalette(hDC,hRetPal,FALSE); RealizePalette(hDC); // Освобождаем память, использованную для хранения //структуры логической палитры free(pPal); // Возвращаем обработчик новой палитры return hRetPal; ) /////////////////////////////////////////////////////////////////// // Точка входа всех программ Windows int APIENTRY WinMain( HINSTANCE hlnstance, HINSTANCE hPrevInstance, LPSTR IpCmdLine, int nCmdShow) { MSG msg; // Структура сообщений Windows WNDCLASS wc; 11 Структура классов Windows HWND hWnd; // Память для обработчика окна UINT uiStyle,uiStyleX; ghlnstance = hlnstance; // записывает обработчик экземпляра // Получить опции запуска или выключиться if(ShowStartupOptions() == FALSE) return 0; if(startupoptions.bFullScreen == TRUE) if(ChangeDisplaysettings(&startupOptions.devMode, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL) { // Заменить ресурсами строки и реальными шириной и высотой MessageBox(NULL, TEXT("Cannot change to selected desktop resolution."), NULL, MB_OK | MB_ICONSTOP) ; return -1; // Регистрируем стиль окна wc.style wc.IpfnWndProc wc.cbClsExtra wc.cbWndExtra wc.hlnstance wc.hlcon wc.hCursor // Фоновая краска = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; = (WNDPROC) WndProc; = 0; = 0; = hlnstance; = NULL; = LoadCursor(NULL, IDC_ARROW); для окна OpenGL не требуется
Глава 13 Wiggle OpenGL в системе Windows 683 wc.hbrBackground = NULL; wc.IpszMenuName = NULL; wc.IpszClassName = IpszAppName; // Регистрация класса окна if(Registerclass(Swc) == 0) return FALSE; // Выбираем стили окна if(startupoptions.bFullScreen == TRUE) uiStyle = WS_POPUP; uiStyleX = WS_EX_TOPMOST; } else { uiStyle = WS_OVERLAPPEDWINDOW; uiStyleX = 0; } // Создается основное трехмерное окно hWnd = CreateWindowEx(uiStyleX, wc.IpszClassName, IpszAppName, uiStyle, 0, 0, startupoptions.devMode.dmPelsWidth, startupoptions.devMode.dmPelsHeight, NULL, NULL, hlnstance, NULL); // Выход, если окно не было создано if(hWnd == NULL) return FALSE; // Проверяем, чтобы менеджер окна был невидимым ShowWindow(hWnd,SW_SHOW); UpdateWindow(hWnd); // Обработка сообщений приложения до его закрытия while( GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } // Восстанавливаем настройки дисплея if(startupoptions.bFullScreen == TRUE) ChangeDisplaySettings(NULL, 0); return msg.wParam; /////////////////////////////////////////////////////////////////// // Процедура окна; обрабатывает все сообщения этой программы LRESULT CALLBACK WndProc(HWND hWnd, UINT message, 11 WPARAM wParam, LPARAM IParam) { static HGLRC hRC; // Постоянный контекст визуализации static HDC hDC; // Частный контекст устройства GDI switch (message) // Создание окна, настройка OpenGL case WM_CREATE: // Запись контекста устройства hDC = GetDC(hWnd);
684 Часть II OpenGL повсюду // Экран и рабочий стол, возможно, изменились, // поэтому повторяем такие действия FindBestPF(hDC, &startupOptions.nPixelFormat, &startupOptions.nPixelFormatMS); // Установка пиксельного формата if(startupOptions.bFSAA == TRUE && (startupoptions.nPixelFormatMS != 0)) SetPixelFormat(hDC, startupOptions.nPixelFormatMS, NULL); else SetPixelFormat(hDC, startupOptions.nPixelFormat, NULL // Создаем контекст визуализации и делаем его текущим hRC = wglCreateContext(hDC); wglMakeCurrent(hDC, hRC); // Создаем палитру hPalette = GetOpenGLPalette(hDC); SetupRC(hDC); break; // Проверка нажатия клавиши ESC case WM_CHAR: if(wParam == 27) DestroyWindow(hWnd); break; // Окно находится либо в полноэкранном режиме, //либо невидимо case WM_ACTIVATE: { // Игнорируем следующие строки, // если находимся в полноэкранном режиме if(startupOptions.bFullScreen == TRUE) { // Создаем структуру расположения окна WINDOWPLACEMENT wndPlacement; wndPlacement.length = sizeof(WINDOWPLACEMENT); wndPlacement.flags = WPF_RESTORETOMAXIMIZED; wndPlacement.ptMaxPosition.x = 0; wndPlacement.ptMaxPosition.у = 0; wndPlacement.ptMinPosition.x = 0; wndPlacement.ptMinPosition.у = 0; wndPlacement.rcNormalPosition.bottom = startupOptions.devMode.dmPelsHeight; wndPlacement.rcNormalPosition.left = 0; wndPlacement.rcNormalPosition.top = 0; wndPlacement.rcNormalPosition right = startupOptions.devMode.dmPelsWidth; // Переключаемся от окна if(LOWORD(wParam) == WA_INACTIVE) { wndPlacement.showCmd = SW_SHOWMINNOACTIVE; SetWindowPlacement(hWnd, &wndPlacement); ShowCursor(TRUE);
Глава 13 Wiggle OpenGL в системе Windows 685 else // Переключаемся обратно на окно { wndPlacement.showCmd = SW_RESTORE; SetWindowPlacement(hWnd, &wndPlacement); ShowCursor(FALSE); } } ) break; // Окно удаляется; очистка case WM_DESTROY: ShutdownRC(); // Отменяем выбор текущего контекста //визуализации и удаляем его wglMakeCurrent(hDC,NULL); wglDeleteContext(hRC); // Удаляем палитру if(hPalette != NULL) DeleteObject(hPalette); // Сообщаем приложению завершиться после //закрытия окна PostQuitMessage(O); break; 11 Размер окна изменился case WM_SIZE: // Вызываем функцию, модифицируемую объем // отсечения и поле просмотра ChangeSize(LOWORD(IParam), HXWORD(IParam)) ; break; // Функция рисования. Данное сообщение посылается //Windows всегда, // когда требуется обновление экрана case WM_PAXNT: { // Клавиатура опрашивается только, когда окно //в фокусе if(GetFocus() == hWnd) { float fTime; float fLinear, fAngular; // Получить время, прошедшее с момента //последней визуализации кадра LARGE_INTEGER ICurrent; QueryPerformanceCounter(&ICurrent); fTime = (float)(ICurrent.QuadPart - CameraTimer.QuadPart) / (float)CounterFrequency.QuadPart; CameraTimer = ICurrent; // Движение камеры управляется по времени. // Следующий код удерживает // движение постоянным вне зависимости частоты // смены кадров. Чем выше частота смены кадров,
86 Часть II OpenGL повсюду // тем более гладкой будет анимация и движение, // но не ускоренное движение fLinear = fTime * l.Of; fAngular = (float)gltDegToRad(60.Of * fTime); // Движение камеры, опрос клавиатуры if(GetAsyncKeyState(VK_UP)) gltMoveFrameForward(&frameCamera, fLinear); if(GetAsyncKeyState(VK_DOWN)) gltMoveFrameForward(&frameCamera, -fLinear); if(GetAsyncKeyState(VK_LEFT)) gltRotateFrameLocalY(&frameCamera, fAngular); if(GetAsyncKeyState(VK_RIGHT)) gltRotateFrameLocalY(&frameCamera, -fAngular); ) // Вызов кода рисования OpenGL RenderScene(); // Вызов функции переключения буферов SwapBuffers(hDC); // Специально не проверяем достоверность, получаем // бесконечный ряд // сообщений рисования ... результат подобен // наличию цикла // визуализации //ValidateRect(hWnd,NULL) ; ) break; // Windows сообщает приложению, что оно может // модифицировать системную // палитру. По сути, данное сообщение запрашивает // у приложения новую палитру case WM_QUERYNEWPALETTE: // Если палитра была создана if(hPalette) { int nRet; // выбираем палитру в текущий контекст устройства SelectPalette(hDC, hPalette, FALSE); I/ Отображаем элементы из текущей выбранной // палитры в системную палитру. Возвращаемое // значение представляет собой число // модифицированных элементов палитры nRet = RealizePalette(hDC); // Перерисовываем, инициируем повторное // отображение палитры в текущем окне InvalidateRect(hWnd,NULL,FALSE); return nRet; ) break; // Данное окно может устанавливать палитру, даже если // оно не является текущим активным окном case WM_PALETTECHANGED: // Ничего не делать, если палитра не существует
Глава 13 Wiggle' OpenGL в системе Windows 687 // или используется окно, // изменившее палитру if((hPalette != NULL) && ((HWND)wParam != hWnd)) { // Выбираем палитру в контексте устройства SelectPalette(hDC,hPalette,FALSE); // Отображаем элементы в системную палитру RealizePalette(hDC); // Повторно отображаем текущие цвета //в новую выпущенную палитру UpdateColors(hDC) ; return 0; } break; default: // Передаем дальше, если сообщение не обработано return (DefWindowProc(hWnd, message, wParam, IParam)); } return (0L); ) /////////////////////////////////////////////////////////////////// // Процедура диалога при запуске BOOL APIENTRY StartupDlgProc (HWND hDlg, UINT message, // UINT wParam, LONG IParam) { switch (message) // Инициализация диалогового окна case WM_INITDIALOG: ( int nPF; HDC hDC; // Контекст диало! HGLRC hRC; DEVMODE devMode; unsigned int iMode; unsigned int nWidth; // Текущие y< unsigned int nHeight; char cBuffer[64]; HWND hListBox; PIXELFORMATDESCRIPTOR pfd = { // Мы не будем чересчур разборчивыми sizeof(PIXELFORMATDESCRIPTOR) , // Контекст диалогового окна // Текущие установки // Полный цвет PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, PFD_TYPE_RGBA, // Полный цвет 32, // Насыщенность 0,0,0,0,0,0,0, // Игнорируется 0,0,0,0, // Буфер накопл' 16, // Биты глубины 8, // Биты трафаре' 0,0,0,0,0,0 }; // Некоторое используется, некоторое нет // Насыщенность цвета // Игнорируется // Буфер накопление // Биты глубины // Биты трафарета
Часть II OpenGL повсюду II Инициализация опций визуализации startupoptions.bFSAA = FALSE; startupOptions.bFullScreen = FALSE; startupoptions.bVerticalSync = FALSE; // Создается "временный" контекст визуализации OpenGL hDC = GetDC(hDlg); // Один раз устанавливаем пиксельный формат.... nPF = ChoosePixelFormat(hDC, &pfd); SetPixelFormat(hDC, nPF, &pfd); DescribePixelFormat(hDC, nPF, sizeof(PIXELFORMATDESCRIPTOR), &pfd); // Создается контекст GL hRC = wglCreateContext(hDC) ; wglMakeCurrent(hDC, hRC); // Устанавливает текст диалога SetDlgltemText(hDlg, IDC_VENDOR, (const char *)glGetString(GL_VENDOR)); SetDlgltemText(hDlg, IDC_RENDERER, (const char *)glGetString(GL_RENDERER)); SetDlgltemText(hDlg, IDC_VERSION, (const char *)glGetString(GL_VERSION)); //По умолчанию вертикальная синхронизация выключена if(gltIsExtSupported("WGL_EXT_swap_control")) EnableWindow(GetDlgItem(hDlg, IDC_VSYNC_CHECK), TRUE); // Находит пиксельные формат с множественной выборкой // и без нее FindBestPF(hDC, &startupOptions.nPixelFormat, &startupOptions.nPixelFormatMS); // Закончили с контекстом GL wglMakeCurrent(hDC, NULL); wglDeleteContext(hRC) ; // Перечисление режимов отображения iMode = 0; nWidth = GetSystemMetrics(SM_CXSCREEN); // Текущие настройки nHeight = GetSystemMetrics(SM_CYSCREEN); hListBox = GetDlgItem(hDlg, IDC_DISPLAY_COMBO); while(EnumDisplaySettings(NULL, iMode, &devMode)) { int iltem; sprintf(cBuffer,"%d x %d x %dbpp @%dhz", devMode.dmPelsWidth, devMode.dmPelsHeight, devMode.dmBitsPerPel, devMode.dmDisplayFrequency) ; iltem = SendMessage(hListBox, CB_ADDSTRING, 0, (LPARAM)cBuffer); SendMessage(hListBox, CB_SETITEMDATA, iltem, iMode); if(devMode.dmPelsHeight == nHeight && devMode.dmPelsWidth == nWidth) SendMessage(hListBox, CB_SETCURSEL, iltem, 0);
Глава 13 Wiggle OpenGL в системе Windows 689 iMode++; ) // Устанавливаем другие параметры по умолчанию III III II II Оконный или полноэкранный режим CheckDlgButton(hDlg, IDC_FS_CHECK, BST_CHECKED); // FSAA, но только при наличии поддержки if(startupoptions.nPixelFormatMS != 0) EnableWindow(GetDlgItem(hDlg, IDC_MULTISAMPLED_CHECK), TRUE); return (TRUE); break; // Обработка командных сообщений case WM_COMMAND: // Проверяем и вносим изменения if(LOWORD(wParam) == IDOK) { И Опции чтения /////////////////////////////////// // Режим отображения HWND hListBox = GetDlgItem(hDlg, IDC_DIS PLAY_COMBO); int iMode = SendMessage(hListBox, CB_GETCURSEL, 0, 0); iMode = SendMessage(hListBox, CB_GETITEMDATA, iMode, 0); EnumDisplaySettings(NULL, iMode, &startupOptions.devMode); // Полноэкранный или оконный режим? if(IsDlgButtonChecked(hDlg, IDC_FS_CHECK)) startupoptions.bFullScreen = TRUE; else startupoptions.bFullScreen = FALSE; // FSAA if(IsDlgButtonChecked(hDlg, IDC_MULTISAMPLED_CHECK)) startupoptions.bFSAA = TRUE; else startupoptions.bFSAA = FALSE; // Вертикальная синхронизация if(IsDlgButtonChecked(hDlg, IDC_VSYNC_CHECK)) startupoptions bVerticalSync = TRUE; else startupoptions.bVerticalSync = FALSE; EndDialog(hDlg,TRUE); ) if(LOWORD(wParam) == IDCANCEL) EndDialog(hDlg, FALSE); ) break; case WM_CLOSE: EndDialog(hDlg,FALSE); // To же, что и отмена
690 Часть II OpenGL повсюду break; ) return FALSE; ) /////////////////////////////////////////////////////////////////// // Отображение экрана запуска (просто модальное // диалоговое окно) BOOL ShowStartupOptions(void) recurn DialogBox (ghlnstance, MAKEINTRESOURCE(IDD_DLG_INTRO), NULL, StartupDlgProc); } /////////////////////////////////////////////////////////////////// 11 Выбор пиксельного формата с желаемыми атрибутами // Возврат наилучшего доступного "правильного" пиксельного // формата и // наилучшего доступного формата с множественной выборкой // (0, если недоступен) void FindBestPF(HDC hDC, int *nRegularFormat, int *nMSFormat) { *nRegularFormat = 0; *nMSFormat = 0; // Простая проверка, просто ищем точку входа if(glt!sWGLExtSupported(hDC, "WGL_ARB_pixel_format")) if(wglGetPixelFormatAttribivARB == NULL) wglGetPixelFormatAttribivARB = (PFNWGLGETPIXELFORMATATTRIBIVARBPROC) wglGetProcAddress("wglGetPixelFormatAttribivARB"); // Вначале пытаемся использовать новый расширенный путь wgl if(wglGetPixelFormatAttribivARB != NULL) { // Важны только следующие атрибуты int nBestMS = 0; int i; int iResults[9]; int lAttributes [9] = { WGL_SUPPORT_OPENGL_ARB, // 0 WGL_ACCE LERATION_ARB, // 1 WGL_DRAW_TO_WINDOW_ARB, //2 WGL_DOUBLE_BUFFER_ARB, // 3 WGL_PIXEL_TYPE_ARB, // 4 WGL_DEPTH_BITS_ARB, // 5 WGL_STENCIL_BITS_ARB, // 6 WGL_SAMPLE_BUFFERS_ARB, //7 WGL_SAMPLES_ARB }; //8 // Сколько пиксельных форматов? int nFormatCount[] = { 0 }; int attrib[1 = { WGL_NUMBER_PIXEL_FORMATS_ARB ); wglGetPixelFormatAttribivARB(hDC, 1, 0, 1, attrib, nFormatCount); // Последовательно проходим все форматы и изучаем каждый for(i =0; i < nFormatCount[0]; i++)
Глава 13 Wiggle'OpenGL в системе Windows 691 // Запрос пиксельного формата wglGetPixelFormatAttribivARB(hDC, i+1, 0, 9, iAttributes, iResults); // Совпало? Нужна поддержка OpenGL ПЛЮС наличие // ускорения ПЛЮС возможность рисовать в окне if(iResults[0] == 1 && iResults[l] == WGL_FULL_ACCELERATION_ARB && iResults[2] == 1) if(iResults[3] == 1) // Double buffered if(iResults[4] == WGL_TYPE_RGBA_ARB) // Полный цвет if(iResults[5] >= 16) // Любая глубина, больше 16 if(iResults[6] > 0) // Любая глубина трафарета (не нуль) { // Есть кандидат, ищем вариант с наибольшим // числом выборок, если доступна множественная // дискретизация if(iResults[7] == 1) // Множественная выборка { if(iResults[8] > nBestMS) // Ищем наибольшее число выборок { *nMSFormat = i; // Множественные выборки nBestMS = iResults[8]; // Ищем лучшее ) ) else // Множественной выборки нет { // Достаточно хорошо для "обычного". // Проверка проходит через такие этапы *nRegularFormat = i; ) } ) // Старомодный способ ... // или множественная выборка PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR) , 1, PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, PFD_TYPE_RGBA, // Полноцветное изображение 32, // Насыщенность цвета 0,0,0,0,0,0,0, // Игнорируется
692 Часть II OpenGL повсюду О,О,О,0, // Буфер накопления 24, // Буферы глубины 8, // Буферы трафарета О,0,0,0,0,0 }; // Что-то используется, что-то - нет *nRegularFormat = ChoosePixelFormat(hDC, &pfd); ) Резюме В данной главе рассматривается использование OpenGL на платформе Win32 Вы прочли о различных моделях драйверов и реализациях, доступных для Windows, и узнали, чего стоит ожидать при работе с этой операционной системой Кроме того, вы узнали, как перечислить пиксельные форматы и выбрать из них нужный, чтобы получить требуемую аппаратную или программную поддержку визуализации. Теперь вы представляете базовый каркас программы под Win32, замещающий каркас GLUT, так что теперь можете писать код, “родной” для приложений Win32 Кроме того, было показано, как создать палитру 3-3-2, позволяющую визуализа- цию OpenGL при наличии всего 256 цветов, а также рассказано, как создать полно- экранное окно для игр или приложений-имитаторов Также мы обсудили некоторые особенности OpenGL при работе в среде Windows, например поддержку шрифтов TrueType и нескольких потоков визуализации В заключение была представлена завершенная программа SPHEREWORLD32, предназначенная для Win32 В этой программе демонстрируется, как использовать особенности в среде Windows и доступные расширения WGL Кроме того, показа- но, как создать программу, запускающуюся везде — от старого 8-битового цветного дисплея до последних 32-битовых полноцветных ЗО-ускоритслей Справочная информация ChoosePixelFormat Цель: Выбрать пиксельный формат, ближайший к тому, что задан с помощью PIXELFORMATDESCRIPTOR, поддерживаемый данным контекстом устройства Включаемый файл: <wingdi. h> Синтаксис: int ChoosePixelFormat(HDC hDC, CONST PIXELFORMATDESCRIPTOR *ppfd); Описание: Позволяет определять наилучший доступный пиксельный формат при данном контексте устройства, основываясь на желаемых характеристиках, описанных в структуре PIXELFORMATDESCRIPTOR. Возвращаемый индекс формата затем используется в функции SetPixelFormat
Глава 13 Wiggle OpenGL в системе Windows 693 Параметры: hDC (тип HDC) ppfd (тип PIXELFORMAT DESCRIPTOR*) nSize WORD: nVersion (тип WORD) dwFlag (тип DWORD) iPixel Type (тип BYTE) cColorBits (тип BYTE) cAlphaBits (тип BYTE) cAccumBits (тип BYTE) cDepthBits (тип BYTE) cStencilBits (тип BYTE) cAuxBuffers (тип BYTE) iLayer Type (тип BYTE) Что возвращает: См. также: Контекст устройства, для которого эта функция ищет наилучший пиксельный формат Указатель на структуру, описывающую идеальный пиксельный формат Все содержимое этой структуры не имеет никакого отношения к использованию данной функции Полное описание структуры PIXELFORMATDESCRIPTOR см в описании функции DescribePixelFormat Существенны следующие элементы функции Размер структуры, обычно равен sizeof(PIXELFORMATDESCRIPTOR) Номер версии структуры, устанавливается равным 1 Набор меток, задающих свойства буфера пикселей Тип цветового режима (RGBA или индекс цвета) Глубина буфера цвета Глубина буфера альфа Глубина буфера накопления Глубина буфера глубины Глубина буфера трафарета Число дополнительных буферов (не поддерживается Microsoft) Тип уровня (не поддерживается Microsoft) Индекс ближайшего пиксельного формата, соответствующего заданному логическому формату, или нуль, если подходящих пиксельных форматов не найдено DescribePixelFormat, SetPixelFormat
694 Часть II OpenGL повсюду DescribePixelFormat Цель: Получить подробную информацию о пиксельном формате Включаемый файл: <wingdi.h> Синтаксис: int DescribePixelFormat(HDC hDC, int iPixelFormat, UINT nBytes, LPPIXELFORMATDESCRIPTOR wqppfd); Описание: Заполняет структуру PIXELFORMATDESCRIPTOR информацией о пиксельном формате, заданном для данного контекста устройства Возвращает максимальный доступный пиксельный формат для данного контекста устройства Если значение ppfd равно NULL, функция по-прежнему возвращает максимальный приемлемый пиксельный формат для контекста устройства Некоторые поля структуры PIXELFORMATDESCRIPTOR не поддерживаются общей реализацией OpenGL от Microsoft, но эти значения могут поддерживаться отдельными производителями аппаратного обеспечения Параметры: hDC (тип HDC) iPixelFormat (тип int) nBytes (тип UINT) Контекст устройства, содержащий искомый пиксельный формат Искомый пиксельный формат для заданного контекста устройства Размер структуры, на которую указывает ppfd Если это значение равно 0, никакие данные в буфер не копируются Значение должно устанавливаться равным sizeof(PIXELFORMATDESCRIPTOR) ppfd (тип LPPIXELFORMAT DESCRIPTOR) Указатель на PIXELFORMATDESCRIPTOR, который при возвращении будет содержать подробную информацию об искомом пиксельном формате Структура PIXELFORMATDESCRIPTOR определяется следующим образом typedef struct tagPIXELFORMATDESCRIPTOR WORD nSize, WORD nVersion, 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, BYTE cAuxBuffers, BYTE iLayerType, BYTE bReserved, DWORD dwLayerMask, DWORD dwVisibleMask, DWORD dwDamageMask; PIXELFORMATDESCRIPTOR; nSize содержит размер структуры Значение всегда должно устанавливаться равным sizeof (PIXELFORMATDESCRIPTOR) nVersion содержит номер версии структуры Значение всегда должно устанавливаться равным 1 dwFlags содержит набор битовых метод (см табл. 13 1) описывающих свойства пиксельного формата За исключением оговоренных случаев, данные метки нс являются взаимно исключающими
Глава 13 Wiggle OpenGL в системе Windows 695 iPixelType задает тип пиксельных данных и режим выбора цвета Приемлемы такие значения. GL_TYPE_RGBA (цветовой режим RGBA) и GL_TYPE_COLORINDEX (режим индекса цвета) cColorBits задает число битовых плоскостей цвета, используемых в буфере цвета, исключая битовые плоскости альфа в режиме RGBA В режиме индексирования цвета задает размер буфера цвета cRedBits задает число битовых плоскостей красного цвета во всех буферах цвета RGBA cRedShift задает смещение для битовых плоскостей красного цвета во всех буферах цвета RGBA cGreenBits задает число битовых плоскостей зеленого цвета во всех буферах цвета RGBA cGreenShift задает смещение для битовых плоскостей зеленого цвета во всех буферах цвета RGBA cBlueBits задает число битовых плоскостей синего цвета во всех буферах цвета RGBA cBlueShift задает смещение для битовых плоскостей синего цвета во всех буферах цвета RGBA cAlphaBits задает число битовых плоскостей параметра альфа во всех буферах цвета RGBA. Данное значение не поддерживается общей реализацией Microsoft в версиях Windows до Windows 2000 cAlphaShift задает смещение для битовых плоскостей параметра альфа во всех буферах цвета RGBA cAccumBits задает общее число битовых плоскостей в буфере накопления (см главу 7, “Построение изображений с помощью OpenGL ”) cAccumRedBits задает общее число битовых плоскостей красного цвета в буфере накопления cAccumGreenBits задает общее число битовых плоскостей зеленого цвета в буфере накопления cAccumBlueBits задает общее число битовых плоскостей синего цвета в буфере накопления cAccumAlphaBits задает общее число битовых плоскостей параметра альфа в буфере накопления cDepthBits задает глубину буфера глубины cStencilBits задает глубину буфера трафарета cAuxBuffers задает число дополнительных буферов Это значение не поддерживается общей реализацией Microsoft iLayerType — значение устарело Не используйте его bReserved содержит число накладывающихся и перекрываемых плоскостей, поддерживаемых реализацией. Биты 0-3 задают число накладывающихся плоскостей (до 15 штук), а биты 4-7 задают число перекрываемых плоскостей (также до 15 штук) dwLayerMask — значение устарело. Не используйте его dwVisibleMask используется вместе с dwLayerMask, чтобы определить, перекрывает ли один уровень другой. Уровни не поддерживаются текущей реализацией Microsoft dwDamageMask — значение устарело Не используйте его
696 Часть II OpenGL повсюду Что возвращает: Максимальный пиксельный формат, поддерживаемый заданным контекстом устройства, или 0, если это невозможно См. также: ChoosePixelFormat, GetPixelFormat, SetPixelFormat GetPixelFormat Цель: Извлечь индекс пиксельного формата, выбранного в настоящий момент для контекста устройства Включаемый файл: <wingdi.h> Синтаксис: int GetPixelFormat(HDC hDC); Описание: Извлекает выбранный пиксельный формат для заданного контекста устройства Индекс пиксельного формата представляет собой положительное целочисленное значение Параметры: hDC (ТИП HDC) Искомый контекст устройства Что возвращает: Индекс выбранного в настоящий момент пиксельного формата для контекста устройства или 0, если это невозможно См. также: DescribePixelFormat, ChoosePixelFormat, SetPixelFormat SetPixelFormat Цель: Установить пиксельный формат контекста устройства Включаемый файл: <wingdi.h> Синтаксис: BOOL SetPixelFormat(HDC hDC, int nPixelFormat, CONST PIXELFORMATDESCRIPTOR *ppfd); Описание: Фактически задает пиксельный формат для контекста устройства После выбора пиксельного формата для данного устройства изменить его нельзя Эту функцию следует вызывать до создания контекста визуализации OpenGL для устройства Параметры: hDC (тип HDC) Контекст устройства, пиксельный формат которого нужно установить пPixel Format Индекс устанавливаемого пиксельного формата (тип int) ppfd (тип Указатель на структуру PIXELFORMATDESCRIPTOR, LPPIXELFORMAT содержащую дескриптор логического пиксельного формата DESCRIPTOR) Эта структура используется для записи спецификации логического пиксельного формата Значение не должно влиять на работу этой функции Что возвращает: TRUE, если заданный пиксельный формат был установлен для контекста устройства, FALSE, если произошла ошибка См. также: DescribePixelFormat, GetPixelFormat, ChoosePixelFormat
Глава 13 Wiggle OpenGL в системе Windows 697 SwapBuffers Цель: Быстро скопировать содержимое заднего буфера окна в передний буфер Включаемый файл: <wingdi.h> Синтаксис: BOOL SwapBuffers(HDC hDC); Описание: При выборе пиксельного формата с двойной буферизацией с Параметры: hDC (тип HDC) окном соотнесено два буфера изображения передний (для отображаемого изображения) и задний (для скрытого) Команды рисования передаются заднему буферу. Данная функция позволяет копировать содержимое скрытого буфера в отображемый передний буфер, разрешая гладкое рисование или анимацию. Обратите внимание на то, что буферы могут копировать или просто переключаться в зависимости от реализации. После выполнения команды содержимое заднего буфера не определено Задает контекст устройства окна, содержащего кадровый Что возвращает: и закадровый буферы TRUE, если буферы были переключены См. также: glDrawBuffer wgICreateContext Цель: Создать контекст визуализации, подходящий для рисования в конкретном контексте устройства Включаемый файл: <wingdi.h> Синтаксис: HGLRC wgICreateContext(HDC hDC); Описание: Создает контекст визуализации OpenGL, подходящий для данного контекста устройства окна Пиксельный формат для контекста устройства должен быть установлен до создания контекста визуализации Когда работа приложения с контекстом визуализации завершена, следует вызвать wglDeleteContext Параметры: hDC (тип HDC) Контекст устройства, в котором будет рисовать новый контекст визуализации Что возвращает: Обработчик нового контекста визуализации или NULL, если произошла ошибка См. также: wglCreateLayerContext, wglDeleteContext, wglGetCurгentContext, wglMakeCurrent
698 Часть II OpenGL повсюду wgICreateLayerContext Цель: Создать новый контекст визуализации OpenGL, подходящий для рисования на заданной плоскости уровня Включаемый файл: <wingdi.h> Синтаксис: HGLRC wgICreateLayerContext(HDC hDC, int iLayerPlane) ; Описание: Создает контекст визуализации OpenGL, подходящий для данной плоскости уровня. Если поддерживаются накладывающиеся и перекрываемые плоскости, (только при аппаратной реализации), каждая из них требует отдельного контекста визуализации OpenGL. Плоскость уровня с индексом 0 является основной (именно здесь вы обычно выполняете визуализацию) Положительные индексы соответствуют накладывающимся плоскостям, отрицательные — перекрываемы м Параметры: hDC (тип HDC) Контекст устройства, в котором будет рисовать новый накладывающийся или перекрываемый контекст визуализации iLayerPlane (тип int) Что возвращает: Индекс плоскости уровня, для которого создается контекст Обработчик нового контекста визуализации или NULL, если произошла ошибка См. также: wglCreateContext, wglDeleteContext, wglGetCurrentContext, wglMakeCurrent wgICopyContext Цель: Скопировать выбранную группу состояний визуализации из одного контекста OpenGL в другой Включаемый файл: <wingdi.h> Синтаксис: BOOL wgICopyContext(HGLRC hSource, HGLRC hDest, UINT mask); Описание: Функцию можно использовать для синхронизации состояния визуализации двух контекстов визуализации OpenGL С помощью данной функции можно копировать любые приемлемые метки состояния, которые можно задать с помощью glPushAttrib Чтобы скопировать все атрибуты, можно использовать значение GL_ALL_ATTRIB_BITS
Глава 13 Wiggle OpenGL в системе Windows 699 Параметры: hSource (тип HGLRC) hDest (тип HGLRC) Исходный контекст визуализации, из которого копируется информация о состоянии Целевой контекст визуализации, в который копируется информация о состоянии mask (тип UINT) Что возвращает: Обработчик удаляемого контекста визуализации TRUE, если информация о состоянии контекста визуализации скопирована См. также: glPushAttrib, wglCreateContext, wglGetCurrentContext, wglMakeCurrent wglDeleteContext Цель: Удалить контекст визуализации, когда он уже не требуется приложению Включаемый файл: <wingdi.h> Синтаксис: BOOL wglDeleteContext(HGLRC hglrc}; Описание: Удаляет контекст визуализации OpenGL. Освобождается память и ресурсы, занимаемые контекстом Параметры: hglrc (тип HGLRC) Обработчик удаляемого контекста визуализации Что возвращает: TRUE, если контекст визуализации удаляется, FALSE, если произошла ошибка Для одного потока ошибкой является удаление контекста визуализации, являющегося текущим контекстом другого потока См. также: wglCreateContext, wglGetCurrentContext, wglMakeCurrent wgIDescribeLayerPlane Цель: Извлечь информацию о налагающихся и перекрываемых плоскостях данного пиксельного формата Включаемый файл: <wingdi. h> Синтаксис: BOOL wgIDescribeLayerPlane (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nBytes, LPLAYERPLANEDESCRIPTOR plpd}; Описание: Функция нужна для того же, для чего и функция DescribePixelFormat, но она извлекает информацию о налагающихся и перекрываемых плоскостях Многоуровневые плоскости нумеруются положительными и отрицательными целыми числами Значение 0 соответствует основной плоскости, отрицательные значения - перекрываемым плоскостям, положительные — налагающимся
700 Часть II OpenGI. повсюду Параметры: hdc (ТИП HDC) Обработчик контекста устройства, плоскости уровней которого описываются iPixelFormat (тип int) iLayerPlane (тип int) Пиксельный формат искомой плоскости уровня Идентификатор налагающейся или перекрываемой плоскости Значение 0 соответствует основной плоскости, отрицательные значения — перекрываемым плоскостям, положительные — налагающимся nBytes (тип UINT) Размер LAYERPLANEDESCRIPTOR в байтах plpd (тип L PLAYER-Указатель на структуру LAYERPLANEDESCRIPTOR PLANEDESCRIPTOR) Что возвращает: TRUE, если вызов функции завершен успешно, и элементы данных структуры LAYERPLANEDESCRIPTOR заполнены Данная структура определяется следующим образом: typedef struct tagLAYERPLANEDESCRIPTOR { WORD nSize; WORD nVersion; DWORD dwFlags; BYTE iPixel Type; BYTE cColorBits; BYTE cRedBits; BYTE cRedShift; BYTE cGreenBits; BYTE cGreenShift; BYTE cBlueBits; BYTE cBlueShift; BYTE cAlphaBits; BYTE cAlpha Shift; BYTE cAccumBits, BYTE cAccumRedBits; BYTE cAccumGreenBits; BYTE cAccumBlueBits; BYTE cAccumAlphaBits; BYTE cDepthBits; BYTE cStencilBits; BYTE cAuxBuffers; BYTE iLayerType; BYTE bReserved; COLOREF crTransparent; } LAYERPLANEDESCRIPTOR; nSize содержит размер структуры, который должен быть равен sizeof(LAYERPLANEDESCRIPTOR) nVersion содержит номер версии структуры (значение должно быть равно 1)
Глава 13 Wiggle OpenGL в системе Windows 701 dwFlags содержит набор битовых меток, описывающих свойства пиксельного формата Исключая случаи, где это оговорено, метки не являются взаимно исключающими LPD_SUPPORT_OPENGL поддерживает визуализацию OpenGL LPD_SUPPORT_GDI поддерживает рисование GDI LPD_DOUBLEBUFFER указывает, что плоскость уровня обрабатывается с двойной буферизацией LPD_STEREO указывает, что плоскость уровня является стереоскопической LPD_SWAP_EXCHANGE означает, что в режиме двойной буферизации содержимое переднего и заднего буферов переставлено местами LPD_SWAP_COPY означает, что в режиме двойной буферизации содержимое заднего буфера копируется в передний буфер. Сам задний буфер при этом не затрагивается LPD_TRANSPARENT указывает, что элемент crTransparent структуры содержит код цвета, который должен рассматриваться как прозрачный цвет LPD_SHARE_DEPTH указывает, что плоскость уровня совместно с основной плоскостью использует буфер глубины LPD_SHARE_STENCIL указывает, что плоскость уровня совместно с основной плоскостью использует буфер трафарета LPD_SHARE_ACCUM указывает, что плоскость уровня совместно с основной плоскостью использует буфер накопления i Pixel Туре задаст тип пиксельных данных (по сути, — режим выбора цвета) Допустимо значение LPD_TYPE_RGBA (режим RGBA) или LPD_TYPE_COLORINDEX (режим индексирования цвета) cColorBits задает число битовых плоскостей цвета, используемых буфером цвета, исключая альфа-плоскости в режиме RGBA В режиме индексирования цвета данный параметр задает размер буфера цвета cRedBits задает число битовых плоскостей красного цвета в каждом буфере RGBA cRedShift задает смещение для битовых плоскостей красного цвета в каждом буфере RGBA cGreenBits задает число битовых плоскостей зеленого цвета в каждом буфере RGBA cGreenShift задает смещение для битовых плоскостей зеленого красного цвета в каждом буфере RGBA cBlueBits задает число битовых плоскостей синего цвета в каждом буфере RGBA cBlueShift задает смещение для битовых плоскостей синего цвета в каждом буфере RGBA cAlphaBits задает число битовых плоскостей альфа в каждом буфере RGBA Нс поддерживается общими реализациями Microsoft вплоть до Windows 2000 cAlphaShift задает смещение для битовых плоскостей альфа в каждом буфере RGBA Нс поддерживается реализациями Microsoft cAccumBi ts — общее число битовых плоскостей в буфере накопления cAccumRedBits — общее число битовых плоскостей красного цвета в буфере накопления cAccumGreenBits — общее число битовых плоскостей зеленого цвета в буфере накопления
702 Часть II OpenGL повсюду cAccumBlueBits — общее число битовых плоскостей синего цвета в буфере накопления cAccumAlphaBits — общее число битовых плоскостей альфа в буфере накопления cDepthBi ts задает глубину буфера глубины cStencilBits задает глубину буфера трафарета cAuxBuffers — задает число вспомогательных буферов Не поддерживается общей реализацией Microsoft iLayerType — номер плоскости уровня Положительные значения соответствуют налагающимся уровням, отрицательные — перекрываемым bReserved не используется Параметр должен иметь значение 0 crTransparent — при установленной метке LPD_TRANSPARENT указывает код прозрачного цвета. Обычно соответствует черному цвету. Цвет задается как значение COLORREF (Windows) Кроме того, для его построения можно использовать макрос RGB (Windows) См. также: DescribePixelFormat, wglCreateLayerContext wglGetCurrentContext Цель: Извлечь обработчик контекста визуализации текущего потока OpenGL Включаемый файл: <wingdi.h> Синтаксис: HGLRC wglGetCurrentContext(void); Описание: Каждый поток приложения может иметь собственный контекст визуализации OpenGL С помощью данной функции можно определить, какой контекст визуализации активен в настоящий момент для указанного потока Что возвращает: Если вызывающий поток имеет текущий контекст визуализация, функция возвращает обработчик этого контекста Если — нет, функция возвращает NULL См. также: wglCreateContext, wglDeleteContext, wglMakeCurrent, wgIGetCurrentDC wgIGetCurrentDC Цель: Получить контекст устройства окна, соотнесенный с текущим контекстом визуализации OpenGL Включаемый файл: <wingdi.h> Синтаксис: HDC wgIGetCurrentDC(void); Описание: Позволяет получать контекст устройства окна, соотнесенного с текущим контекстом визуализации OpenGL. Обычно используется для получения контекста устройства окна с целью объединения в одном окне функций рисования OpenGL и GDI
Глава 13 Wiggle OpenGL в системе Windows 703 Что возвращает: Если вызывающий поток имеет текущий контекст визуализации, функция возвращает обработчик соотнесенного с ним контекста устройства окна. Если — нет, функция возвращает NULL См. также: wglGetCurrentContext wglGetProcAddress Цель: Получить адрес функции расширения Включаемый файл: <gl.h> Синтаксис: PROC wglGetProcAddress(LPSTR IpszProc); Описание: Извлекает адрес функции расширения Если функция расширения недоступна, возвращается указатель NULL Параметры: IpszProc (тип LPSTR) Что возвращает: Имя функции расширения Ничего wglMakeCurrent Цель: Сделать данный контекст визуализации OpenGL текущим для вызывающего потока и соотнести его с заданным контекстом устройства Включаемый файл: <wingdi.h> Синтаксис: BOOL wglMakeCurrent(HDC hDC, HGLRC HRC); Описание: Делает заданный контекст визуализации текущим для вызывающего потока. Этот контекст визуализации соотнесен с данным контекстом окна Контекст устройства не обязательно должен быть тем же, что использовался в вызове функции wglCreateContext при условии, что пиксельный формат для обоих одинаков, и оба существуют на одном физическом устройстве (а не, например, один на экране, а второй — на принтере) Любые ожидающие выполнения команды OpenGL, соответствующие предыдущему контексту визуализации, выполняются перед тем, как новый контекст визуализации станет текущим Кроме того, с помощью указанной функции можно изменить статус активного контекста, вызвав ее со значением NULL в качестве параметра hRC Параметры: hDC (ТИП HDC) Контекст устройства, который будет использоваться для всех операций рисования OpenGL, выполняемых вызывающим потоком hRC (ТИП HGLRC) Контекст визуализации, получающий статус активного для вызывающего потока
704 Часть II OpenGL повсюду Что возвращает: TRUE при успешном вызове, FALSE, если произошла ошибка В случае ошибки вызывающий поток теряет текущий активный контекст визуализации (если он был) См. также: wgICreateContext, wglDeleteContext, wglGetCurrentcontext, wglGetCurrentDC wglShareLists Цель: Разрешить нескольким контекстам визуализации совместно использовать таблицы отображения Включаемый файл: <wingdi.h> Синтаксис: BOOL wglShareLists(HGLRC hRCl, HGLRC hRC2); Описание: Таблицы отображения представляют собой список “предварительно скомпилированных” команд и функций OpenGL (см главу 11, “Все о конвейере: более быстрое прохождение геометрии”) Память для хранения таблиц отображения выделяется отдельно в каждом контексте визуализации Поскольку таблицы отображения создаются в этом контексте визуализации, он имеет доступ к собственной памяти таблиц отображения Указанная функция позволяет нескольким контекстам визуализации совместно использовать эту память Данная возможность особенно полезна, когда для экономии памяти выгодно, чтобы несколько контекстов визуализации или потоков совместно использовали большие таблицы отображения Память для хранения таблиц отображения может совместно использоваться любым числом контекстов визуализации Память нс освобождается до тех пор, пока не будет удален последний из использующих ее контекстов визуализации При совместном использовании таблиц отображения несколькими потоками создание и использование таблиц отображения следует аккуратно синхронизировать Параметры: hRCl (ТИП HGLRC) Контекст визуализации, которому будет доступна память для хранения таблиц отображения hRC2 (ТИП HGLRC) Контекст визуализации, который разделит свою память для хранения таблиц отображения с hRCl. После разделения памяти создание таблиц отображения для контекста hRC2 не допускается Что возвращает: TRUE, если память для хранения таблиц отображения используется несколькими потоками/контскстами, FALSE в противном случае См. также: gllsList, glNewList, glCallList, glCallLists, gIListBase, gIDeleteLists, glEndList, glGenLists
Глава 13 Wiggle OpenGL в системе Windows 705 wglSwapLayerBuffers Цель: Переключить передний и задний буферы в налагающихся, перекрываемых и основной плоскостях, принадлежащих заданному контексту устройства Включаемый файл: <wingdi.h> Синтаксис: BOOL wglSwapLayerBuffers(HDC hDC, UINT fuPlanes); Описание: При выборе пиксельного формата с двойной буферизацией Параметры: hDC (тип HDC) окно получает буферы для видимого и скрытого изображений Команды рисования передаются в задний буфер Данная функция позволяет копировать содержимое скрытого заднего буфера в отображаемый передний буфер с целью поддержания гладкого рисования или анимации Обратите внимание на то, что переключения буферов в действительности нс происходит После выполнения этой команды содержимое заднего буфера становится неопределенным Контекст устройства окна, содержащего передний и задний fuPlanes буферы Контекст устройства окна, содержащего задний и передний (тип UINT) буферы Что возвращает: TRUE, если буферы были переключены См. также: glSwapBuffers wglUseFontBitmaps Цель: Включаемый файл: Синтаксис: Описание: Создать набор растровых изображений таблиц отображения OpenGL для текущего выбранного шрифта GDI <wingdi.h> BOOL wglUseFontBitmaps(HDC hDC, DWORD dwFirst, DWORD dwCount, DWORD dwListBase) ; Принимает шрифт, выбранный в настоящее время в контексте устройства, заданном параметром hDC, и создает растровую таблицу отображения для каждого символа, начиная с dwFirst и заканчивая dwCount Таблицы отображения создаются в выбранном в настоящее время контексте визуализации и идентифицируются числами, начинающимися с dwListBase Обычно данная функция позволяет рисовать текст на сцене OpenGL с двойной буферизацией, поскольку GDI Windows не позволяет выполнять операции в заднем буфере окна с двойной буферизацией Кроме того, эта функция также позволяет помечать находящиеся на экране объекты OpenGL
706 Часть II OpenGL повсюду Параметры: hDC (тип HDC) Контекст устройства GDI Windows, из которого извлекается определение шрифта Чтобы изменить используемый шрифт, можно в контексте устройства создать и выбрать требуемый шрифт dwFirst (тип DWORD) dwCount (тип DWORD) dwListBase (тип DWORD) Что возвращает: ASCII-код первого символа шрифта, используемого для построения таблиц отображения Число последовательных символов шрифта после dwFirst Основное значение таблицы отображения, используемое для первого символа таблицы отображения TRUE, если таблицы отображения можно создать, FALSE — в противном случае См. также: wglUseFontOutlines, gllsList, glNewList, glCallList, glCallLists, glListBase, glDeleteLists, glEndList, glGenLists wglllseFontOutlines Цель: Создать набор трехмерных таблиц отображения OpenGL для выбранного в настоящий момент шрифта GDI Включаемый файл: <wingdi.h> Синтаксис: BOOL wglUseFontOutlines(HDC hDC, DWORD first, DWORD count, DWORD listBase, FLOAT deviation, FLOAT extrusion, int format, LPGLYPHMETRICSFLOAT Ipgmf); Описание: Принимает выбранный в настоящее время шрифт TrueType в контекст устройства GDI hDC и создает трехмерный эскиз count символов, начиная с first Нумерация таблиц отображения начинается со значения listBase. Эскиз может формироваться отрезками или многоугольниками, заданными параметром format Ячейка символа имеет по осям х и у длину 1 0 Параметр extrusion содержит длину вдоль отрицательного направления оси z, на которую “выдавливается” символ Параметр deviation имеет значение большее или равное 0 и определяет хордальное отклонение от исходного эскиза шрифта. Функция работает только со шрифтами TrueType Дополнительные данные о символах предоставляются в массиве Ipgmf структур GLY PHMETRIС S FLOAT Параметры: hDC (ТИП HDC) first (ТИП DWORD) count (тип DWORD) Контекст устройства шрифта Первый символ шрифта, с которого начинается перевод в таблицы отображения Число символов шрифта, переводимых в таблицы отображения
Глава 13 Wiggle OpenGL в системе Windows 707 listBase (тип DWORD) deviation (тип FLOAT) extrusion (тип FLOAT) format (тип int) Ipgmf (тип LPGLYPHMETRICS FLOAT) Что возвращает: См. также: Начальное значение таблицы отображения, используемое для записи первого символа таблицы отображения Максимальное хордальное отклонение от истинных контуров Экструзия по отрицательному направлению оси z Значение, задающее способ формирования символов в таблице отображения из линий или многоугольников. Возможны следующие значения WGL_FONT_LINES использовать для формирования символов линии WGL_FONT_POLYGONS. использовать для формирования символов многоугольники Адрес массива, получающего метрики глифов. Каждый элемент массива заполняется данными, касающимися таблицы отображения его символа. Все структуры определяются следующим образом typedef struct_GLYPHMETRICSFLOAT { // gmf FLOAT gmfBlackBoxX; FLOAT gmfBlackBoxY; POINTFLOAT gmfptGlyphOrigin; FLOAT gmfCelllncX; FLOAT gmfCelllncY; } GLYPHMETRICSFLOAT; gmfBlackBoxX задает ширину наименьшего прямоугольника, полностью вмещающего символ gmfBlackBoxY задает высоту наименьшего прямоугольника, полностью вмещающего символ gmfptGlyphOrigin задает координаты хну левого верхнего угла прямоугольника, полностью вмещающего символ Структура POINTFLOAT определяется следующим образом: typedef struct_POINTFLOAT { // ptf FLOAT x; // горизонтальная координата точки FLOAT у; // вертикальная координата точки } POINTFLOAT; gmfCelllncX задает горизонтальное расстояние от начала ячейки текущего символа до начала ячейки следующего символа gmfCelllncY задает вертикальное расстояние от начала ячейки текущего символа до начала ячейки следующего символа TRUE, если таблицы отображения можно создать; FALSE — в противном случае wglUseFontBitmaps, gllsList, glNewList, glCallList, glCallLists, gIListBase, gIDeleteLists, glEndList, glGenLists

ГЛАВА 14 OpenGL в системе MacOS X Майкл Свит (Michael Sweet) Из ЭТОЙ ГЛАВЫ ВЫ УЗНАЕТЕ . Действие Функция Выбор подходящих пиксельных aglChoosePixelFormat форматов для визуализации OpenGL Управление контекстами aglCreateContext, aglDestroyContext, визуализации OpenGL aglSetCurrentContext, aglSetDrawable Рисование с двойной буферизацией aglSwapBuffers Создание растровых шрифтов aglUseFont В данной главе обсуждаются интерфейсы OpenGL в системе MacOS X Разобра- ны AGL и NSOpenGL — интерфейсы приложений Carbon и Cocoa, соответственно. Вы узнаете, как для приложений Carbon и Cocoa создавать области рисования, фор- мировать контексты OpenGL и управлять ими. Кроме того, в главе показано, как использовать GLUT в системе MacOS X Основы OpenGL в системе MacOS X представлен тремя программными интерфейсами при- ложений: AGL для приложений Carbon, NSOpenGL для приложений Cocoa и CGL для приложений, требующих прямого доступа к экрану В данной главе обсуждается использование AGL и NSOpenGL, интерфейс CGL предлагает похожие функцио- нальные возможности, но поскольку он обходит систему организации окон (OpenGL в окнах не используется, существует только полноэкранный режим), мы его рассмат- ривать не будем Тем не менее на Web-сайте книги7 находится руководство по CGL (см приложение А, “Что еще почитать’’) Помимо трех стандартных интерфейсов инструментарий разработчика Apple включает порт GLUT, который можно задействовать для компиляции и использо- вания всех примеров на основе GLUT, фигурирующих в книге 7 Оригинала - Примеч иерее
710 Часть II OpenGL повсюду ТАБЛИЦА 14.1. Каркасы MacOS X, связанные с OpenGL Название Описание AGL ApplicationServices Carbon Cocoa OpenGL Обеспечивает интерфейс OpenGL к окнам Carbon (QuickDraw) Обеспечивает общий интерфейс ко всем службам приложений GUI Используется во всех приложениях OpenGL Обеспечивает общий интерфейс к MacOS X и более ранним версиям Используется вместе с каркасами AGL и OpenGL Обеспечивает классы интерфейса пользователя Aqua Используется вместе с каркасом OpenGL Обеспечивает общий интерфейс OpenGL к графическому аппаратному обеспечению Используется с другими каркасами для поддержки визуализации OpenGL Каркас Все программные интерфейсы приложения, рассмотренные в данной главе, представ- лены в MacOS X посредством каркасов. Каркас (framework) — это набор заголовочных файлов и библиотек, предлагающих специфические функциональные возможности. В табл. 14 1 перечислены каркасы, которые рассматриваются в данной главе. Использование программного интерфейса GLUT API GLUT предоставлен в папке примеров на компакт-диске Apple Developer Tools. Чтобы использовать интерфейс GLUT, скопируйте эту папку на диск, а затем в окне Xcode приложения добавьте каркас GLUT во все списки каркасов. Если вы созда- ете свое программное обеспечение с помощью make-файла, используйте следую- щие ОПЦИИ' -framework /path/to/GLUT.framework -framework AGL -framework OpenGL -framework Carbon -framework Applicationservices Использование программных интерфейсов AGL и Carbon Программные интерфейсы приложений AGL и Carbon предлагают относительно про- стой интерфейс для программ С и C++, позволяющий отображать графику OpenGL. Поскольку приложения, основанные на Carbon, поддерживаются в версиях MacOS от 8 до X, интерфейсы AGL и Carbon можно использовать для разработки приложений, работающих в нескольких версиях MacOS. Программы такого типа требуют каркасов AGL, ApplicationServices, Carbon и OpenGL. В приложении Xcode начать можно с приложения, основанного на Car- bon, а затем добавить каркасы AGL и OpenGL Можно также использовать следующие опции компоновщика- -framework AGL -framework OpenGL -framework Carbon -framework Applicationservices Как будет показано в следующих разделах, функции AGL начинаются с префикса agl
Глава 14 OpenGL в системе MacOS X 711 Пиксельные форматы AGL предлагает несколько пиксельных форматов, предоставляющих разные возмож- ности визуализации. Например, видеокарта может обеспечить полноэкранную защи- ту от наложения при 16-битовом буфере глубины, но не при 24- или 32-битовом буфере глубины вследствие ограниченности памяти или других причин, завися- щих от реализации. Функция aglChoosePixelFormat позволяет приложению выбирать подходящий пиксельный формат, используя список целочисленных атрибутов, описывающих же- лаемые выходные характеристики В табл 14.2 перечислены константы атрибутов, определяемые программным интерфейсом AGL. Чтобы найти пиксельные форматы RGB с двойной буферизацией, можно исполь- зовать следующий код’ GLint attributes!] = { AGL_RGBA, AGL_DOUBLEBUFFER, AGL_RED_SIZE, 4, AGL_GREEN_SIZE, 4, AGL_BLUE_SIZE, 4, AGL_DEPTH_SIZE, 16, AGL_NONE }; AGLPixelFormat format; format = aglChoosePixelFormat(NULL, 0, attributes); Обратите внимание на то, что последним значением должно быть AGL_NONE. По- сле вызова aglChoosePixelFormat возвращается значение AGLPixelFormat, ко- торое предоставляет информацию по тому, какой пиксельный формат следует ис- пользовать Если соответствующий формат не найден, возвращается указатель NULL, и нужно попытаться использовать другие атрибуты или сообщить пользователям, что в их системе отобразить окно невозможно Управление контекстами AGL предоставляет четыре функции управления контекстами визуализации OpenGL: aglCreateContext, aglDestroyContext, aglSetCurrentContext и aglSetDraw- able. Функция aglCreateContext создает контекст OpenGL, используя значение AGLPixelFormat, возвращаемое функцией aglChoosePixelFormat. AGLContext context; context = aglCreateContext(format, NULL); Функция aglCreateContext принимает два аргумента пиксельный формат и контекст, с которым совместно использует информацию по таблице отображения, текстурному объекту и массиву вершин Если вы не желаете делить информацию с другим контекстом, укажите в качестве аргумента NULL. Создав контекст, нужно связать его с окном или закадровым буфером, вызвав для этого функцию aglSetDrawable
712 Часть II. OpenGL повсюду ТАБЛИЦА 14.2. Список констант атрибутов пиксельных форматов AGL Константа Описание AGL_ACCELERATED Указывает, что требуется пиксельный формат с аппаратным ускорением AGL_ACCUM_ALPHA_SIZE Следующее за этой константой число задает минимальное количество битов параметра альфа в буфере накопления AGL_ACCUM_BLUE_SIZE Следующее за этой константой число задает минимальное количество битов синего цвета в буфере накопления AGL_ACCUM_GREEN_SIZE Следующее за этой константой число задает минимальное AGL_ACCUM_RED_SIZE количество битов зеленого цвета в буфере накопления Следующее за этой константой число задает минимальное AGL_ALPHA_SIZE количество битов красного цвета в буфере накопления Следующее за этой константой число задает минимальное AGL_AUX_BUFFERS количество битов параметра альфа Следующее за этой константой число задает количество AGL_BACKING_S TORE вспомогательных буферов Указывает, что должны использоваться только форматы. AGL_BLUE_SIZE в полном объеме поддерживающие возможность вспомогательной памяти Следующее за этой константой число задает минимальное AGL_BUFFER_SIZE количество битов для записи информации о синем цвете Следующее за этой константой число задает число AGL_CLOSEST_POLICY желательных битов индекса цвета Указывает, что заданные размеры должны использовать как идеальное требование к пиксельному формату, и что пиксельный формат должен использовать ближайшее AGL_DEPTH_SIZE допустимое число битов Следующее за этой константой число задает минимальное AGL_DOUBLEBUFFER количество битов глубины Указывает, что желательна визуализация с двойной буферизацией AGL_FULLSCREEN Указывает, что пиксельный формат предназначается для AGL_GREEN_SIZE полноэкранной визуализации Следующее за этой константой число задает минимальное AGL_LEVEL количество битов для записи информации о зеленом цвете Следующее далее число задает уровень буфера, 0 — основной буфер, 1 — первый накладывающийся буфер; -1 — AGL_MINIMUM_POLICY первый перекрываемый буфер и тд Указывает, что заданные размеры должны использоваться как минимальное требование к пиксельному формату, и что пиксельный формат должен использовать минимальное AGL_MAXIUM_POLICY допустимое количество битов Указывает, что заданные размеры должны использоваться как минимальное требование к пиксельному формату и что пиксельный формат должен использовать максимальное AGL_NONE AGL_OFFSCREEN допустимое количество битов Задает конец списка атрибутов Указывает, что пиксельный формат предназначен для AGL_PIXEL_SIZE закадрового буфера Следующее за этой константой число задает общее количество битов для записи информации о пикселе
Глава 14 OpenGL в системе MacOS X 713 (Окончание табл. 14 2 ) Константа Описание AGL_RED_SIZE Следующее за этой константой число задает минимальное количество битов для записи информации о красном цвете AGL_RGBA AGL_STENCIL_SIZE Указывает, что желательна визуализации RGBA Следующее за этой константой число задает минимальное количество битов трафарета AGL_STEREO Указывает, что желательна стереовизуализации; в таком случае создаются разные изображения для левого и правого глаза WindowPtr window; aglSetDrawable(context, GetWindowPort(window)); Далее следует вызвать функцию aglSetCurrentContext, чтобы использовать контекст для визуализации OpenGL. aglSetCurrentContext(context); Наконец, после завершения работы с контекстом вызывается функция aglDe- stroyContext, освобождающая ресурсы, использовавшиеся контекстом. aglDestroyContext(context); Выполнение визуализации с двойной буферизацией Чтобы активизировать визуализацию с двойной буферизацией, используется подхо- дящий пиксельный формат. Таким образом, в коде программы, отвечающем за рисо- вание, просто нужно задать функцию aglSwapBuf fers после визуализации OpenGL (благодаря этому визуализация станет видимой). aglSwapBuffers(context); Первая программа AGL В листинге 14.1 показано стандартное приложение Carbon, создающее окно для ви- зуализации OpenGL и отображающее вращающийся куб. На щелчки и перетаски- вание мышью приложение реагирует изменением характера вращения куба. При- ложение завершает работу после того, как пользователь закрывает окно. Результат показан на рис 14.1. Листинг 14.1. Пример программы CARBON * Включаем необходимые заголовки ... */ ♦include <stdio.h> ♦include <stdlib.h> ♦include <Carbon/Carbon.h> ♦include <AGL/agl.h>
714 Часть II. OpenGL повсюду Рис. 14.1. Пример вращающегося куба AGL * Глобальные переменные... */ float CubeRotation[ 3 ], /* Вращение куба */ CubeRate[3]; /* Скорость вращения куба */ int CubeMouseButton, /* Нажатая кнопка */ CubeMouseX, /* Координата X начального положения указателя мыши */ CubeMouseY; /* Координата Y начального положения указателя мыши */ int CubeX, /* Координата X левого верхнего угла окна */ CubeY, /* Координата Y левого верхнего угла окна */ CubeWidth, /* Ширина окна */ CubeHeight; /* Высота окна */ int CubeVisible; /* Видимо ли окно? */ AGLContext Cubecontext; /* Контекст OpenGL*/ /* * Функции... */ void DisplayFunc(void); static pascal OSStatus EventHandler(EventHandlerCallRef nextHandler EventRef event, void *userData);
Глава 14 OpenGL в системе MacOS X 715 void IdleFunc(void); void MotionFunc(int x, int y); void MouseFunc(int button, int state, int x, int y); void ReshapeFunc(int width, int height); * 'main()' - Основная точка входа программы int /* Выход - состояние выхода*/ main(int argc, /* Вход - число аргументов командной строки */ char *argv[]) /* Вход - аргументы командной строки */ AGLPixelFormat WindowPtr int Str255 Rect EventHandlerUPP EventLoopTimerUPP EventLoopTimerRef ProcessSerialNumber format; /* Пиксельный формат OpenGL*/ window; /* Окно */ winattrs; /* Атрибуты окна */ title; /* Заголовок окна */ rect; /* Определение прямоугольника */ handler; /* Обработчик событий */ thandler; /* Обработчик таймера */ timer; /* Таймер, применяемый * для анимации окна */ psn; /* Порядковый номер процесса */ static EventTypeSpec events!] = /* Интересующие нас события... */ { { kEventClassMouse, kEventMouseDown }, { kEventClassMouse, kEventMouseUp }, { kEventClassMouse, kEventMouseMoved ], { kEventClassMouse, kEventMouseDragged }, { kEventClassWindow, kEventWindowDrawContent }, { kEventClassWindow, kEventWindowShown ), { kEventClassWindow, kEventWindowHidden }, { kEventClassWindow, kEventWindowActivated }, { kEventClassWindow, kEventWindowDeactivated }, { kEventClassWindow, kEventWindowClose }, { kEventClassWindow, kEventWindowBoundsChanged } ); static GLint attributes!] = /* Атрибуты OpenGL */ { AGL_RGBA, AGL_GREEN_SIZE, 1, AGL_DOUBLEBUFFER, AGL_DEPTH_SIZE, 16, AGL_NONE ); //Устанавливаем начальные параметры окна const int origWinHeight = 628; const int origWinWidth = 850; const int origWinXOffset = 50; const int origWinYOffset = 50; * Создаем окно ...
716 Часть II OpenGL повсюду CubeContext = 0; CubeVisible = 0; SetRect(&rect, origWinXOffset, origWinYOffset, origWinWidth, origWinHeight); winattrs = kWindowStandardHandlerAttribute | kWindowCloseBoxAttribute | kWindowCollapseBoxAttribute | kWindowFullZoomAttribute | kWindowResizableAttribute | kWindowLiveResizeAttribute; winattrs &= GetAvailableWindowAttnbutes(kDocumentWindowClass); strcpy(title + 1, "Carbon OpenGL Example"); title[0] = strlen(title + 1); CreateNewWindow(kDocumentWindowClass, winattrs, &rect, &window); SetWTitle(window, title); handler = NewEventHandlerUPP(EventHandler); InstallWindowEventHandler(window, handler, sizeof(events) / sizeof(events[0]), events, NULL, 0L); thandler = NewEventLoopTimerUPP((void (*)(EventLoopTimerRef, void *))IdleFunc); InstallEventLoopTimer(GetMainEventLoop(), 0, 0, thandler, 0, &timer); GetCurrentProcess(&psn); SetFrontProcess(&psn); DrawGrowIcon(window); ShowWindow(window); /* * Создаем контекст OpenGL и связываем его с окном ... */ format = aglChoosePixelFormat(NULL, 0, attributes); CubeContext = aglCreateContext(format, NULL); if ('CubeContext) puts("Unable to get OpenGL context!"); return (1); } aglDestroyPixelFormat(format); aglSetDrawable(CubeContext, GetWindowPort(window)); /* * Задаем остальные глобальные переменные ... */ CubeX = 50; CubeY = 50; CubeWidth = 4 00; CubeHeight = 400; CubeRotation[0] = 45.Of; CubeRotation[1] = 45.Of; CubeRotation[2] = 45.Of;
Глава 14 OpenGL в системе MacOS X 717 CubeRate[O] = l.Of; CubeRate[l] = l.Of; CubeRate[2] = l.Of; // Устанавливаем исходный размер куба ReshapeFunc(origWinWidth - origWinXOffset, origWinHeight - origWinYOffset); /* * Бесконечный цикл ... */ for (;;) { if (CubeVisible) SetEventLoopTimerNextFireTime(timer, 0.05); RunApplicationEventLoop(); if (CubeVisible) { /* * Анимируем куб ... */ DisplayFunc() ; ) } * 'DisplayFunc()' - Рисуем куб */ void DisplayFunc(void) { int i, j; /* Переменные цикла */ float aspectRatio,windowWidth, windowHeight; static const GLfloat corners[8][3] = /* Угловые вершины */ { { l.Of, l.Of, l.Of ) { l.Of,-l.Of, l.Of } {-l.Of,-l.Of, l.Of } {-l.Of, l.Of, l.Of } { l.Of, l.Of,-l.Of } { l.Of,-l.Of,-l.Of ) {-l.Of,-l.Of,-l.Of } {-l.Of, l.Of,-l.Of } /* Передняя правая верхняя */ /* Передняя правая нижняя */ /* Передняя левая нижняя */ /* Передняя левая верхняя */ /* Задняя правая верхняя */ /* Задняя правая нижняя */ /* Задняя левая нижняя */ /* Задняя левая верхняя */ ); static const int sides[6][4] = /* Грани */ { { 0, 1, 2, 3 }, /* Передняя */ { 4, 5, 6, 7 ), /* Задняя */ { 0, 1, 5, 4 }, /* Правая */ { 2, 3, 7, 6 }, /* Левая */ { 0, 3, 7, 4 }, /* Верхняя */ { 1, 2, 6, 5 } /* Нижняя */ }; static const GLfloat colors[6][3] = /* Цвета */
718 Часть II OpenGL повсюду { l.Of, O.Of, O.Of } , /* Красный */ O.Of, l.Of, O.Of } , /* Зеленый */ l.Of, l.Of, O.Of } , /* Желтый */ O.Of, O.Of, l.Of } , /* Синий */ l.Of, O.Of, l.Of } , /* Пурпурный */ O.Of, l.Of, l.Of } /* Голубой */ ); /* * Устанавливаем текущий контекст OpenGL ... * / aglSetCurrentContext(CubeContext); /* * Очищаем окно... * / glViewport(0, 0, CubeWidth, CubeHeight); glClearColor(0.Of, O.Of, O.Of, O.Of); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) ; /* * Настройка матриц ... * / glMatrixMode(GL_PROJECTION); glLoadldentity!); aspectRatio = (GLfloat)CubeWidth I (GLfloat)CubeHeight; if(CubeWidth <= CubeHeight) ( windowwidth = 2. Of; windowHeight = 2.Of I aspectRatio; glOrtho(-2.Of, 2.Of, -windowHeight, windowHeight, 2.Of, -2.Of); ) else { windowwidth = 2.Of * aspectRatio; windowHeight = 2. Of; glOrtho(-windowHeight, windowHeight, -2.Of, 2.Of, 2.Of, -2.Of); ) glMatrixMode(GL_MODELVIEW); glLoadldentity(); glRotatef(CubeRotation[0], l.Of, O.Of, O.Of); glRotatef(CubeRotation[1], O.Of, l.Of, O.Of); glRotatef(CubeRotation(2), O.Of, O.Of, l.Of); /* * Рисуем куб ... */ glEnable(GL_DEPTH_TEST); glBegin(GL_QUADS); for (i = 0; i < 6; i ++) { glColor3fv(colors[i]); for (j = 0; j < 4; 3 ++)
Глава 14 OpenGL в системе MacOS X 719 glVertex3fv(corners[sides[i][j]]) ; } glEnd(); /* * Переключаем передний и задний буферы ... */ aglSwapBuffers(CubeContext); ) /* * 'EventHandler()1 - Обработка событий, связанных * с окном и мышью, из менеджера окон */ static pascal OSStatus // Выход - noErr при успехе или код ошибки EventHandler(EventHandlerCallRef nextHandler, /* Вход - следующий вызываемый обработчик */ EventRef event, /* Вход - начало отсчета событий */ void *userData) /* Вход - пользовательские данные (не используется) */ { UInt32 kind; /* Тип события */ Rect rect; /* Новый размер окна */ EventMouseButton button; /* Кнопка мыши */ Point point; /* Положение мыши */ kind = GetEventKind(event) ; if (GetEventClass(event) == kEventClassWindow) { switch (kind) { case kEventWindowDrawContent : if (CubeVisible && CubeContext) DisplayFunc(); break; case kEventWindowBoundsChanged : GetEventParameter(event, kEventParamCurrentBounds, typeQDRectangle, NULL, sizeof(Rect), NULL, &rect); CubeX = rect.left; CubeY = rect.top; if (CubeContext) aglUpdateContext(CubeContext); ReshapeFunc(rect.right - rect.left, rect.bottom - rect.top); if (CubeVisible && CubeContext) DisplayFunc(); break; case kEventWindowShown : CubeVisible = 1; if (CubeContext) DisplayFunc(); break; case kEventWindowHidden : CubeVisible = 0;
20 Часть II OpenGL повсюду break; case kEventWindowClose : ExitToShell() ; break; else { switch (kind) { case kEventMouseDown : GetEventParameter(event, kEventParamMouseButton, typeMouseButton, NULL, sizeof(EventMouseButton), NULL, «button); GetEventParameter(event, kEventParamMouseLocation, typeQDPoint, NULL, sizeof(Point), NULL, «point); if (point.v < CubeY || (point.v > (CubeY + CubeHeight - 8) && point.h > (CubeX + CubeWidth - 8))) return (CallNextEventHandler(nextHandler, event)); MouseFunc(button, 0, point.h, point.v); break; case kEventMouseUp • GetEventParameter(event, kEventParamMouseButton, typeMouseButton, NULL, sizeof(EventMouseButton), NULL, «button); GetEventParameter(event, kEventParamMouseLocation, typeQDPoint, NULL, sizeof(Point), NULL, «point); if (point.v < CubeY || (point.v > (CubeY + CubeHeight - 8) && point.h > (CubeX + CubeWidth - 8))) return (CallNextEventHandler(nextHandler, event)); MouseFunc(button, 1, point.h, point.v) ; break; case kEventMouseDragged : GetEventParameter(event, kEventParamMouseLocation, typeQDPoint, NULL, sizeof(Point), NULL, «point); if (point v < CubeY |I (point.v > (CubeY + CubeHeight - 8) && point.h > (CubeX + CubeWidth - 8))) return (CalINextEventHandler(nextHandler, event)); MotionFunc(point.h, point.v); break; default : return (CallNextEventHandler(nextHandler, event));
Глава 14 OpenGL в системе MacOS X 721 * Возвращается после обработки события ... return (noErr); } /* * 'IdleFunc()' - Поворот и перерисовывание куба */ void IdleFunc(void) CubeRotation!О] += CubeRate[0]; CubeRotation[ 1 ] += CubeRate[ 1 ] ; CubeRotation[2] += CubeRate[2]; QuitApplicationEventLoop(); * 'MotionFunc()' - Обработка движения указателя мыши */ void MotionFunc(int x, /* Вход - координата X */ int у) /* Вход - координата Y */ { /* * Получаем движение мыши ... */ х -= CubeMouseX; у -= CubeMouseY; /* * Обновляем частоту вращения куба согласно движению мыши * и нажатию кнопки ... */ switch (CubeMouseButton) { case 0 : /* Кнопка 1 */ CubeRate[0] = O.Olf * у; CubeRate[1] = O.Olf * x; CubeRate[2] = O.Of; break; case 1 : /* Кнопка 2 */ CubeRate[0] = O.Of; CubeRate[1] = O.Olf * y; CubeRate[2] = O.Olf * x; break; default : /* Кнопка 3 */ CubeRate[0] = O.Olf * y; CubeRate[1] = O.Of; CubeRate[2] = O.Olf * x; break;
722 Часть II OpenGL повсюду ) /* * 'MouseFunc()' - Обработка событий, связанных с нажатием * и освобождением кнопок мыши */ void MouseFunc(int button, /* Вход - нажатая кнопка */ int state, /* Вход - состояние кнопки (0 = нажата) */ int х, /* Вход - положение X */ int у) /* Вход - положение Y */ { /* * Реагирует только на нажатую кнопку ... */ if (state) return; /* * Записываем состояние мыши ... CubeMouseButton = button; CubeMouseX = х; CubeMouseY = у; /* * Обнуление частот вращения ... */ CubeRatefO] = O.Of; CubeRate[l] = O.Of; CubeRate[2] = O.Of; ) /* * 'ReshapeFunc()1 - Изменение размеров окна */ void ReshapeFunc(int width, /* Вход - ширина окна */ int height) /* Вход - высота окна */ { CubeWidth = width; CubeHeight = height; ) Использование растровых шрифтов Для использования в визуализации OpenGL растровых шрифтов каркас AGL предла- гает функцию aglUseFont. Действуя совместно с функцией Carbon GetFNum и функ- цией OpenGL glGenLists, она извлекает растровые изображения и создает таблицы отображения для каждого символа, который вы желаете видеть в шрифте. В приве- денном ниже коде демонстрируется, как извлечь видимые символы ASCII из шрифта Courier New Bold с высотой 14 пикселей
Глава 14 OpenGL в системе MacOS X 723 GLint listbase; short font; Str255 fontname; strcpy(fontname + 1, "Courier New"); fontname[0] = strlen(fontname + 1); GetFNum(fontname, &font); listbase = glGenLists(96); aglUseFont(context, font, bold, 14, ' ', 96, listbase); Чтобы выбрать шрифт для визуализации, вместе с его стилем (в данном случае — полужирным) используется номер шрифта. Пятый и шестой аргумент являются на- чальным символом и числом извлекаемых символов, соответственно. Переменная listbase в этом примере указывает на начало блока из 96 последовательных таблиц отображения, используемых для представления всех символов. После извлечения символов можно рисовать текст, следующим образом используя комбинацию функций glRasterPos (), glPushAttribf), glListBase(), glCall- ListsO иglPopAttrib(): char * *s = "Hello, World!"; glPushAttrib(GL_LIST_BIT); glListBase(listbase - ' '); glRasterPos3f(O.Of, O.Of, O.Of); glCallLists(strlen(s), GL_BYTE, s); glPopAttrib(); В примере, приведенном в листинге 14.2, данный код используется для рисования имен на всех сторонах куба. Результат показан на рис. 14 2. Листинг 14.2. Программа CARBONFONTS * Включаем необходимые заголовки ... */ ♦include <stdio.h> ♦include <stdlib.h> ♦include <Carbon/Carbon.h> ♦include <AGL/agl.h> /* * Глобальные переменные... */ float CubeRotation[3], /* Вращение куба */ CubeRate[3]; /* Скорость вращения куба */ int CubeMouseButton, /* Нажатая кнопка */ CubeMouseX, /* Координата X начального * положения курсора мыши */ CubeMouseY; /* Координата Y начального * положения курсора мыши */ int CubeX, /* Координата X верхнего * левого угла окна */
724 Часть II. OpenGL повсюду Рис. 14.2. Вращающийся куб Carbon со шрифтами CubeY, /* Координат Y верхнего * левого угла окна */ CubeWidth, /* Ширина окна */ CubeHeight; /* Высота окна */ int CubeVisible; /* Видимо ли окно? */ AGLContext CubeContext; /* Контекст OpenGL */ GLuint CubeFont; /* Таблица отображения для шрифта */ /* * Функции ... */ void DisplayFunc(void); static pascal OSStatus EventHandler(EventHandlerCallRef nextHandler, EventRef event, void *userData); void IdleFunc(void); void MotionFunc(int x, int y); void MouseFunc(int button, int state, int x, int y); void ReshapeFunc(int width, int height); * 'main()' - Основная точка входа программы */ int /* Выход - состояние выхода main(int argc, /* Вход - число аргументов командной строки */ char *argv[])
Глава 14 OpenGL в системе MacOS X 725 /* Вход - аргументы командной { AGLPiхе1Format format; WindowPtr window; int winattrs; Str255 title; Rect rect; EventHandlerUPP handler; EventLoopTimerUPP thandler; EventLoopTimerRef timer; ProcessSerialNumber psn; short font; Str255 fontname; static EventTypeSpec events /* Интересующие нас события { { kEventClassMouse, { kEventClassMouse, { kEventClassMouse, { kEventClassMouse, { kEventClassWindow, { kEventClassWindow, { kEventClassWindow, { kEventClassWindow, { kEventClassWindow, { kEventClassWindow, { kEventClassWindow, [] строки */ /* Пиксельный формат OpenGL /* Окно */ /* Атрибуты окна */ /* Заголовок окна */ /* Определение * прямоугольника */ /* Обработчик событий */ /* Обработчик таймера */ /* Таймер для анимации окна /* Обработка * порядковых чисел */ /* Номер шрифта */ /* Имя шрифта */ static GLint attributes!] = { AGLJRGBA, AGL_GREEN_SIZE, AGL_DOUBLEBUFFER, AGL_DEPTH_SIZE, 16, AGL_NONE ); kEventMouseDown }, kEventMouseUp }, kEventMouseMoved }, kEventMouseDragged }, kEventWindowDrawContent ), kEventWindowShown }, kEventWindowHidden }, kEventWindowActivated }, kEventWindowDeactivated }, kEventWindowClose }, kEventWindowBoundsChanged } /* Атрибуты OpenGL */ //Установка начальных параметров окна const int origWinHeight = 628; const int origWinWidth = 850; const int origWinXOffset = 50; const int origWinYOffset = 50; /* * Создаем окно . . . */ CubeContext = 0; CubeVisible = 0; SetRect(&rect, origWinXOffset, origWinYOffset, origWinWidth, origWinHeight); winattrs = kWindowStandardHandlerAttribute | kWindowCloseBoxAttribute |
726 Часть II OpenGL повсюду kWindowCollapseBoxAttribute I kWindowFullZoomAttribute | kWindowResizableAttribute | kWindowLiveResizeAttribute; winattrs &= GetAvailableWindowAttributes(kDocumentWindowClass strcpy(title + 1, "Carbon OpenGL Example"); title[0] = strlen(title + 1); CreateNewWindow(kDocumentWindowClass, winattrs, &rect, &window); SetWTitle(window, title); handler = NewEventHandlerUPP(EventHandler); InstallWindowEventHandler(window, handler, sizeof(events) / sizeof(events[0]), events, NULL, 0L); thandler = NewEventLoopTimerUPP((void (*)(EventLoopTimerRef, void *))IdleFunc); InstallEventLoopTimer(GetMainEventLoop(), 0, 0, thandler, 0, &timer); GetCurrentProcess(&psn); SetFrontProcess(&psn); DrawGrowIcon(window); Showwindow(window); * Создаем контекст OpenGL и связываем его с окном ... */ format = aglChoosePixelFormat(NULL, 0, attributes); CubeContext = aglCreateContext(format, NULL); if (!CubeContext) { puts("Unable to get OpenGL context!"); return (1); } aglDestroyPixelFormat(format); aglSetDrawable(CubeContext, GetWindowPort(window)); /* * Устанавливаем оставшиеся глобальные переменные ... */ CubeX = 50; CubeY = 50; CubeWidth = 400; CubeHeight = 400; CubeRotation[0] = 45.0f; CubeRotation[1] = 45.Of; CubeRotation[2] = 45.Of; CubeRate[0] = l.Of; CubeRatefl] = l.Of; CubeRate[2] = l.Of; /* * Настройка шрифта ... */ strcpy(fontname + 1, "Courier New");
Глава 14 OpenGL в системе MacOS X 727 fontname[0] = strlen(fontname + 1); GetFNum(fontname, &font); CubeFont = glGenLists(96); aglUseFont(CubeContext, font, bold, 14, 1 ', 96, CubeFont); // Устанавливаем исходный размер куба ReshapeFunc(origWinWidth - origWinXOffset, origWinHeight - origWinYOffset); /* * Бесконечный цикл ... */ for (;;) { if (CubeVisible) SetEventLoopTimerNextFireTime(timer, 0.05); RunApplicationEventLoop(); if (CubeVisible) ( /* * Анимация куба ... */ DisplayFunc(); } } * 'DisplayFunc()' - Рисуем куб. */ void DisplayFunc(void) { int i, j; /* Переменные цикла */ float aspectRatio,windowWidth, windowHeight; static const GLfloat corners[8][3] = /* Угловые вершины */ { { l.Of, l.Of, l.Of ), /* Передняя правая верхняя */ { l.Of,-l.Of, l.Of }, /* Передняя правая нижняя */ {-1.Of,-1.Of, l.Of }, /* Передняя левая нижняя */ {-l.Of, l.Of, l.Of ), /* Передняя левая верхняя */ { l.Of, l.Of,-l.Of }, /* Задняя правая верхняя */ { 1.Of,-1.Of,-1.Of ), /* Задняя правая нижняя */ {-1.Of,-1.Of,-1.Of ), /* Задняя левая нижняя */ {-l.Of, l.Of,-l.Of } /* Задняя левая верхняя */ ); static const int sides[6][4] = /* Грани */ { { 0, 1, 2, 3 ), /* Передняя */ { 4, 5, 6, 7 }, /* Задняя */ { 0, 1, 5, 4 }, /* Правая */ { 2, 3, 7, 6 }, /* Левая */ { 0, 3, 7, 4 }, /* Верхняя */ { 1, 2, 6, 5 ) /* Нижняя */ };
'28 Часть II OpenGL повсюду static const GLfloat colors[6][3] = /* Цвета */ { { l.Of, O.Of, O.Of }, /* Красный */ { O.Of, l.Of, O.Of }, /* Зеленый */ { l.Of, l.Of, O.Of }, /* Желтый */ { O.Of, O.Of, l.Of }, /* Синий */ { l.Of, O.Of, l.Of }, /* Пурпурный */ { O.Of, l.Of, l.Of } /* Голубой */ * Устанавливаем текущий контекст OpenGL ... */ aglSetCurrentContext(CubeContext); /* * Очищаем окно... glViewport(0, 0, CubeWidth, CubeHeight); glClearColor(0.Of, O.Of, O.Of, O.Of); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); * Настройка матриц ... */ glMatrixMode(GL_PROJECTION); glLoadldentity(); aspectRatio = (GLfloat)CubeWidth / (GLfloat)CubeHeight; if(CubeWidth <= CubeHeight) { windowWidth = 2. Of; windowHeight = 2.Of / aspectRatio; glOrtho(-2.Of, 2.Of, -windowHeight, windowHeight, 2.Of, -2.Of); else { windowWidth = 2.Of * aspectRatio; windowHeight = 2. Of; glOrtho(-windowHeight, windowHeight, -2.Of, 2.Of, 2.Of, -2.Of); } glMatrixMode(GL_MODELVIEW); glLoadldentity(); glRotatef(CubeRotation[0], l.Of, O.Of, O.Of); glRotatef(CubeRotation[1], O.Of, l.Of, O.Of); glRotatef(CubeRotation[2], O.Of, O.Of, l.Of); /* * Рисуем куб ... */ glEnable(GL_DEPTH_TEST); glBegin(GL_QUADS); for (i = 0; i < 6; i ++)
Глава 14 OpenGL в системе MacOS X 72 glColor3fv(colors[i]); for (j =0; j <4; j ++) glVertex3fv(corners[sides[i][ j ] ]) ; } glEnd(); /* * Рисуем линии, выходящие из куба ... */ glColor3f(l.Of, l.Of, l.Of); glBegin(GL_LINES); glVertex3f(O.Of, O.Of, -1.5f); glVertex3f(0.Of, O.Of, 1.5f); glVertex3f(-1.5f, O.Of, O.Of); glVertex3f(1.5f, O.Of, O.Of); glVertex3f(O.Of, 1.5f, O.Of); glVertex3f(O.Of, -1.5f, O.Of); glEnd(); /* * Формируем текст на каждой стороне ... */ glPushAttrib(GL_LIST_BIT); glListBase(CubeFont - ' '); glRasterPos3f(0.Of, O.Of, -1.5f); glCallLists(4, GL_BYTE, "Задняя сторона"); glRasterPos3f(0.Of, O.Of, 1.5f); glCallLists(5, GL_BYTE, "Передняя сторона"); glRasterPos3f(-1.5f, O.Of, O.Of); glCallLists(4, GL_BYTE, "Левая сторона"); glRasterPos3f(1.5f, O.Of, O.Of); glCallLists(5, GL_BYTE, "Правая сторона"); glRasterPos3f(O.Of, 1.5f, O.Of); glCallLists(3, GL_BYTE, "Верхняя сторона"); glRasterPos3f(O.Of, -1.5f, O.Of); glCallLists(6, GL_BYTE, "Нижняя сторона"); glPopAttrib(); /* * Переключаем передний и задний буферы ... */ aglSwapBuffers(CubeContext); ) /* * 'EventHandler()' - Обработка событий, связанных с окном и мышью, * из менеджера окна */ static pascal OSStatus /* Выход - noErr при успехе или код ошибки */ EventHandler(EventHandlerCallRef nextHandler, /* Вход - следующий вызываемый обработчик */ EventRef event, /* Вход - начало отсчета событий */
730 Часть II OpenGL повсюду void *userData) /* Вход - пользовательские данные (не используются) */ { UInt32 kind; /* Тип события */ Rect rect; /* Новый размер окна */ EventMouseButton button; /* Кнопка мыши */ Point point; /* Положение курсора мыши */ kind = GetEventKind(event); if (GetEventClass(event) == kEventClassWindow) { switch (kind) { case kEventWindowDrawContent : if (CubeVisible && CubeContext) DisplayFunc(); break; case kEventWindowBoundsChanged : GetEventParameter(event, kEventParamCurrentBound typeQDRectangle, NULL, sizeof(Rect), NULL, &rect); CubeX = rect.left; CubeY = rect.top; if (CubeContext) aglUpdateContext(CubeContext); ReshapeFunc(rect.right - rect.left, rect.bottom - rect.top); if (CubeVisible && CubeContext) DisplayFunc(); break; case kEventWindowShown : CubeVisible = 1; if (CubeContext) DisplayFunc(); break; case kEventWindowHidden : CubeVisible = 0; break; case kEventWindowClose : ExitToShellO; break; ) ) else { switch (kind) { case kEventMouseDown : GetEventParameter(event, kEventParamMouseButton, typeMouseButton, NULL, sizeof(EventMouseButton), NULL, &button); GetEventParameter(event, kEventParamMouseLocatio
Глава 14 OpenGL в системе MacOS X 731 typeQDPoint, NULL, sizeof(Point), NULL, &point); if (point.v < CubeY || (point.v > (CubeY + CubeHeight - 8) && point.h > (CubeX + CubeWidth - 8))) return (CallNextEventHandler(nextHandler, event)); MouseFunc(button, 0, point.h, point.v); break; case kEventMouseUp : GetEventParameter(event, kEventParamMouseButton, typeMouseButton, NULL, sizeof(EventMouseButton), NULL, &button); GetEventParameter(event, kEventParamMouseLocation, typeQDPoint, NULL, sizeof(Point), NULL, &point); if (point.v < CubeY || (point.v > (CubeY + CubeHeight - 8) && point.h > (CubeX + CubeWidth - 8))) return (CallNextEventHandler(nextHandler, event)); MouseFunc(button, 1, point.h, point.v); break; case kEventMouseDragged : GetEventParameter(event, kEventParamMouseLocation, typeQDPoint, NULL, sizeof(Point), NULL, &point); if (point.v < CubeY || (point.v > (CubeY + CubeHeight - 8) && point.h > (CubeX + CubeWidth - 8))) return (CallNextEventHandler(nextHandler, event)); MotionFunc(point.h, point.v); break; default : return (CallNextEventHandler(nextHandler, event)); } ) /* * Возвращается после обработки события ... */ return (noErr); ) /* * 'IdleFunc()' - Поворачиваем и перерисовываем куб IdleFunc(void) { CubeRotation[0] += CubeRate[0]; CubeRotation[1] += CubeRate[1] ; CubeRotation[2] += CubeRate[2] ; QuitApplicationEventLoop();
732 Часть II OpenGL повсюду * 'MotionFunc()' - Обработка движения мыши */ void MotionFunc(int х, /* Вход - координата X */ int у) /* Вход - координата Y */ { /* * Получаем движение мыши ... */ х -= CubeMouseX; у -= CubeMouseY; /* * Обновляем скорость вращения куба согласно движению мыши и * нажатию кнопки ... */ switch (CubeMouseButton) { case 0 : /* Кнопка 1 */ CubeRate[O] = O.Olf * у; CubeRate[l] = O.Olf * x; CubeRate[2] = O.Of; break; case 1 : /* Кнопка 2 */ CubeRate[O] = O.Of; CubeRatefl] = O.Olf * y; CubeRate[2] = O.Olf * x; break; default : /* Кнопка 3 */ CubeRate(O] = O.Olf * y; CubeRate[l] = O.Of; CubeRate[2] = O.Olf * x; break; } ) /* * ’MouseFunc()' - Обработка событий, связанных * с нажатием/освобождением кнопки мыши */ void MouseFunc(int button, /* Вход - нажатая кнопка */ int state, /* Вход - состояние кнопки (0 = нажата) */ int х, /* Вход - координата X */ int у) /* Вход - координата Y */ { /* * Реагирует только на нажатие кнопки ... */ if (state) return; /* * Записываем состояние мыши ...
Глава 14 OpenGL в системе MacOS X 733 CubeMouseButton = button; CubeMouseX = x; CubeMouseY = y; /* * Обнуление скоростей вращения ... */ CubeRate[O] = O.Of; CubeRate[l] = O.Of; CubeRate[2] = O.Of; ) /* * 'ReshapeFunc()' - Изменение размеров окна */ void ReshapeFunc(int width, /* Вход - ширина окна */ int height) /* Вход - высота окна */ { CubeWidth = width; CubeHeight = height; Использование программного интерфейса Cocoa Программный интерфейс приложения Cocoa подходит для приложений, разработан- ных с использованием Objective С и классов интерфейса пользователя Cocoa. Cocoa предоставляет единственный класс визуализации OpenGL, который для выполнения визуализации OpenGL нужно сделать производным подклассом. Программы Cocoa рассматриваемого типа требуют каркасов ApplicationSer- vices, Cocoa и OpenGL. В приложении-компоновщике проектов начать можно с при- ложения, основанного на Cocoa, а затем добавить каркас OpenGL или использовать следующие опции компоновщика: -framework Cocoa -framework OpenGL -framework ApplicationServices КлаСС NSOpenGL Основой всех компонентов Cocoa, связанных c OpenGL является класс NSOpenGL. Чтобы посредством Cocoa реализовать дисплей OpenGL, необходимо разложить этот класс на подклассы, реализовав три необходимых метода. basicPixelFormat, drawRect и initWithFrame. Метод basicPixelFormat используется для создания копии массива атрибутов (подобно функции aglChoosePixelFormat) В приведен- ном ниже коде показана типичная реализация, запрашивающая пиксельный формат с двойной буферизацией и буфера глубины с разрешением по крайней мере 16 бит + (NSOpenGLPixelFormat*) basicPixelFormat { static NSOpenGLPixelFormatAttribute attributes[] =
734 Часть II. OpenGL повсюду ТАБЛИЦА 14.3. Константы функции NSOpenGLPixelFormatAttribute Константа Описание NSOpenGLPFAAccelerated Задает, что требуется пиксельный формат с аппаратным ускорением NSOpenGLPFAAccumAlphaSize Следующее за константой число задает минимальное число битов для записи в буфере накопления параметра альфа NSOpenGLPFAAccumBlueSize Следующее за константой число задает минимальное число битов для записи в буфере накопления синего цвета NSOpenGLPFAAccumGreenSize Следующее за константой число задает минимальное число битов для записи в буфере накопления зеленого цвета NSOpenGLPFAAccumRedSize Следующее за константой число задает минимальное число битов для записи в буфере накопления красного цвета NSOpenGLPFAAlphaSize Следующее за константой число задает минимальное число битов для записи параметра альфа NSOpenGLPFAAuxBuffers Следующее за константой число задает число вспомогательных буферов NSOpenGLPFABackingStore Задает, что должен использоваться только формат, в полном объеме поддерживающий возможность вспомогательной памяти NSOpenGLPFABlueSize Следующее за константой число задает минимальное число битов для записи синего цвета NSOpenGLPFABufferSize Следующее за константой число задает желательное число битов для записи цветового индекса NSOpenGLPFAClosestPolicy Задает, что заданные размеры должны использоваться как идеальное требование к пиксельному формату и что пиксельный формат должен использовать ближайшее возможное число битов NSOpenGLPFADepthSize Следующее за константой число задает минимальное число битов для записи глубины NSOpenGLPFADoubleBuffer Задает, что желательна двойная буферизация NSOpenGLPFAGreenSize Следующее за константой число задает минимальное число битов для записи зеленого цвета NSOpenGLPFALevel Следующее за константой число задает уровень буфера, 0 — основной буфер, 1 — первый накладывающийся буфер, -1 — первый подложенный буфер и тд NSOpenGLPFAWindow, NSOpenGLPFADoubleBuffer, NSOpenGLPFADepthSize, (NSOpenGLPixelFormatAttribute)16, (NSOpenGLPixelFormatAttribute)nil }; return ([[[NSOpenGLPixelFormat alloc] initWithAttributes] autoRelease]); } Все значения атрибутов принадлежат к типу NSOpenGLPixelFormatAttribute, список атрибутов завершается значением nil (“нуль”). Все допустимые значения атрибутов перечислены в табл 14 3.
Глава 14 OpenGL в системе MacOS X 735 Константа Описание NSOpenGLPFAMinimumPolicy Задает, что заданные размеры должны использоваться как минимальное требование к пиксельному формату, и что пиксельный формат должен использовать минимальное возможное число битов N SOpe nGL P FAMa x imuinPo 1 i с у Задает, что заданные размеры должны использоваться как минимальное требование к пиксельному формату, и что пиксельный формат должен использовать максимальное возможное число битов NSOpenGLPFAOffScreen Задает, что пиксельный формат предназначен для закадрового буфера NSOpenGLPFAPixelSize Следующее за константой число задает общее число битов, используемых для записи информации о пикселе NSOpenGLPFARedSi ze Следующее за константой число задает минимальное число битов для записи красного цвета NSOpenGLPFAStencilSize Следующее за константой число задает минимальное число буферов трафарета NSOpenGLPFAStereo Задает, что желательна стереовизуализация; в таком случае создаются разные изображения для левого и правого глаз За все вызовы функций OpenGL, необходимых для перерисовывания элементов управления окном,отвечает метод drawRect; при необходимости для настройки пре- образований поля просмотра и наблюдения можно использовать аргумент rect. - (void)drawRect:(NSRect)rect { int width, height; // Получаем текущий ограничивающий прямоугольник ... width = rect.size.width; height = rect.size.height; // Устанавливаем поле просмотра ... glViewport(О, 0, width, height); // Устанавливаем матрицу проекции ... glMatrixMode(GL_PROJECTION); glLoad!dentity(); glOrtho(-2.Of, 2.Of, -2.Of * height / width, 2.Of * height / width, -2.Of, 2.Of);
736 Часть II. OpenGL повсюду Рис. 14.3. Программа COCOACUBE // Вызываем функции OpenGL для рисования сцены ... 1 Наконец, метод initWithFrame инициализирует область рисования OpenGL. - (id)initWithFrame:(NSRect)frameRect { NSOpenGLPixelFormat *pf; // Получаем пиксельный формат и возвращаем новое окно ... pf = [MyClass basicPixelFormat]; self = [super initWithFrame: frameRect pixelFormat: pf ]; return (self); } Класс NSOpenGL обрабатывает создание контекста OpenGL, который будет исполь- зоваться с предоставленным пиксельным форматом. Первая программа Cocoa Рассмотрев основы использования класса NSOpenGL в приложениях Cocoa, преоб- разуем пример куба Carbon в формат Cocoa. Весь код Carbon содержится в классе под названием Cube, основанном на NSOpenGL и реализующем методы, описанные в предыдущем разделе. Кроме того, реализуются методы обработки операций с мы- шью, так что, щелкая кнопками и используя перетаскивание, вы можете вращать куб. Полное приложение Cocoa, отображающее вращающийся куб приведено в листин- ге 14.3. Результат визуализации показан на рис. 14.3. Листинг 14.3. Программа сосоа.т ♦import <Сосоа/Сосоа.h> ♦import <Carbon/Carbon.h>
Глава 14. OpenGL в системе MacOS X 737 #import <OpenGL/gl.h> #import <OpenGL/glext.h> #import <OpenGL/glu.h> // Определение интерфейса для нашего NIB CustomView //В компоновщике NIB из NSOpenGLView порождаем подкласс Cube. // Затем присваиваем его пользовательской проекции, // заполняющей окно @interface Cube : NSOpenGLView { bool initialized; NSTimer *timer; float rotation[3], rate[3]; int mouse_x, mouse_y; GLuint font; } @end // II 'mainO' - Основная точка входа программы И int // Выход - состояние выхода main(int argc, // Вход - число аргументов // командной строки const char *argv[]) // Выход - аргументы командной строки { return (NSApplicationMain(argc, argv)); } // // Класс Cube, основанный на NSOpenGLView // (^implementation Cube : NSOpenGLView { bool NSTimer initialized; ♦timer; // Инициализация завершена? // Таймер для анимации float rotation[3], // Вращение куба rate[3]; // Скорость вращения куба int mouse_x, // Координата X начального // положения курсора мыши mouse_y; // Координата Y начального // положения курсора мыши // // 'basicPixelFormat()' - Устанавливаем пиксельный формат для окна // + (NSOpenGLPixelFormat*) basicPixelFormat { static NSOpenGLPixelFormatAttribute attributes[] = // Атрибуты OpenGL { NSOpenGLPFAWindow, NSOpenGLPFADoubleBuffer,
738 Часть II. OpenGL повсюду NSOpenGLPFADepthSize, (NSOpenGLPixelFormatAttribute)16, (NSOpenGLPixelFormatAttribute)nil }; return ([[[NSOpenGLPixelFormat alloc] initWithAttributes:attributes]autorelease]); } II // 'resizeGL()' - Изменение размеров окна И - (void) resizeGL { ) И II 'idleO' - Обновление и отображение с использование текущих // скоростей вращения ... И - (void)idle:(NSTimer *)timer { // Поворот... rotation[0] += rate[0]; rotation[1] += rate[1]; rotation[2] += rate[2]; 11 Перерисовывание окна ... [self drawRect:[self bounds]]; } II II 'mouseDown()' - Обработка нажатия левой кнопки мыши ... И - (void)mouseDown:(NSEvent *)theEvent ( NSPoint point; // Положение курсора мыши // Получаем и записываем положение курсора мыши point = [self convertPoint:[theEvent locationlnWindow] fromView:nil]; mouse_x = point.x; mouse_y = point.y; I/ Обнуление скоростей вращения ... rate[0] = O.Of; ratefl] = O.Of; rate[2] = O.Of; } II II 'rightMouseDown()' - Обработка нажатия правой кнопки мыши ... И - (void)rightMouseDown:(NSEvent *)theEvent ( NSPoint point; // Положение курсора мыши // Получение и запись положение курсора мыши point = [self convertPoint:[theEvent locationlnWindow] fromView:nil]; mouse_x = point.x; mouse_y = point.у;
Глава 14. OpenGL в системе MacOS X 739 // Обнуляем скорости вращения ... rate[0] = O.Of; rated] = O.Of; rate[2] = O.Of; } // // 1otherMouseDown()1 - Обработка нажатия средней клавиши мыши ... // - (void)otherMouseDown:(NSEvent *)theEvent { NSPoint point; // Положение курсора мыши // Получение и запись положения курсора мыши point = [self convertPoint:[theEvent locationlnWindow] fromView-.nil] ; mouse_x = point.x; mouse_y = point.y; // Обнуление скоростей вращения ... rate[0] = O.Of; rate[l] = O.Of; rate[2] = O.Of; // 'mouseDragged()1 - Обработка перетаскивания // при нажатой левой кнопке мыши // - (void)mouseDragged:(NSEvent *)theEvent { NSPoint point; // Положение курсора мыши // Получение положение курсора мыши // и обновление скоростей вращения ... point = [self convertPoint:[theEvent locationlnWindow] fromView:nil]; rate[0] = O.Olf * (mouse_y - point.y); rated] = O.Olf * (mouse_x - point.x); } // // 'rightMouseDragged()' - Обработка перетаскивания при нажатой // правой кнопке мыши // - (void)гightMouseDragged:(NSEvent *)theEvent ( NSPoint point; // Положение курсора мыши // Получение положения курсора мыши //и обновление скоростей вращения ... point = [self convertPoint:[theEvent locationlnWindow] fromView:nil]; rate[0] = O.Olf * (mouse_y - point.y); rate[2] = O.Olf * (mouse_x - point.x); } // // 'otherMouseDragged()' - Обработка перетаскивания
740 Часть II OpenGL повсюду // при нажатой средней клавише мыши И - (void)otherMouseDragged:(NSEvent *)theEvent { NSPoint point; // Положение курсора мыши // Получение положения курсора мыши //и обновление скоростей вращения... point = [self convertpoint:[theEvent locationlnWindow] fromView:nil]; rate[l] = O.Olf * (mouse_y - point.y); rate[2] = O.Olf * (mouse_x - point.x); } - (void)drawRect:(NSRect)rect ( int width, // Ширина окна height; // Высота окна int i, j; // Переменные цикла float aspectRatio, windowWidth, windowHeight; static const GLfloat corners[8][3] = // Угловые вершины { ! l.Of, l.Of, l.Of), /* Передняя правая верхняя [ l.Of,-l.Of, l.Of}, /* Передняя правая нижняя * :-i.of,-i.of, i.ofj, /* Передняя левая нижняя */ !-1.0f, l.Of, l.Of}, /* Передняя левая верхняя * { l.Of, l.Of,-l.Of}, /* Задняя правая верхняя */ { 1.Of,-1.Of,-1.Of}, /* Задняя правая нижняя */ {-1.Of,-1.Of,-1.Of}, /* Задняя левая нижняя */ {-l.Of, l.Of,-l.Of} /* Задняя левая верхняя */ static const int sides[6][4] = /* Грани */ { { 0, 1, 2, 3 }, /* Передняя ’ < 4, 5, 6, 7 b /* Задняя */ { 0, 1, 5, 4 }, /* Правая */ { 2, 3, 7, 6 b /* Левая */ { 0, 3, 7, 4 b /* Верхняя *> { 1, 2, 6, 5 ) /* Нижняя */ ) /' static const : GLfloat colors[6][3] = /* Цвета */ { 1. Of, O.Of, O.Of }, /* Красный *> { 0. Of, l.Of, O.Of }, /* Зеленый *i { 1. .Of, l.Of, O.Of }, /* Желтый */ { 0. Of, O.Of, l.Of }, /* Синий */ { 1. .Of, O.Of, l.Of }, /* Пурпурный { 0. Of, l.Of, l.Of } /* Голубой *> }; /* * Устанавливаем текущий контекст OpenGL ... */ if (.’initialized) { rotation[0] = 45.Of;
Глава 14 OpenGL в системе MacOS X rotation[l] = 45.Of; rotation[2] = 45.Of; rate[0] = l.Of; rate[l] = l.Of; rate[2] = l.Of; initialized = true; } // Используем текущий ограничивающий прямоугольник // для окна с кубом ... width = rect.size.width; height = rect.size.height; // Очищаем окно... glViewport(0, 0, width, height); glClearColor(O.Of, O.Of, O.Of, O.Of); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Настройка матриц ... glMatrixMode(GL_PROJECTION); glLoadldentity() ; aspectRatio = (GLfloat)width / (GLfloat)height; if(width <= height) { windowWidth = 2.Of; windowHeight = 2.Of / aspectRatio; glOrtho(-2.Of, 2.Of, -windowHeight, windowHeight, 2.Of, -2.Of); ) else { windowWidth = 2.Of * aspectRatio; windowHeight = 2.Of; glOrtho(-windowHeight, windowHeight, -2.Of, 2.Of, 2.Of, -2.Of); ) glMatrixMode(GL_MODELVIEW); glLoadldentity(); glRotatef(rotation[0], l.Of, O.Of, O.Of); glRotatef(rotation[l], O.Of, l.Of, O.Of); glRotatef(rotation[2], O.Of, O.Of, l.Of); // Рисуем куб ... glEnable(GL_DEPTH_TEST); glBegin(GL_QUADS); for (i = 0; i < 6; i ++) ( glColor3fv(colors[i]); for (j = 0; j < 4; j ++) glVertex3fv(corners[sides[i][j]]); ) glEnd(); // Рисуем линии, выходящие из куба ... glColor3f(1.Of, l.Of, l.Of);
742 Часть II OpenGL повсюду glBegin(GL_LINES); glVertex3f(O.Of, O.Of, -1.5f); glVertex3f(O.Of, O.Of, 1.5f); glVertex3f(-1.5f, O.Of, O.Of); glVertex3f(1.5f, O.Of, O.Of); glVertex3f(0.Of, 1.5f, O.Of); glVertex3f(O.Of, -1.5f, O.Of); glEnd(); // Переключаем передний и задний буферы ... [[self openGLContext]flushBuffer]; ) // // 'acceptsFirstResponder()' ... // - (BOOL)acceptsFirstResponder { return (YES); } - (BOOL) becomeFirstResponder { return (YES); } - (BOOL) resignFirstResponder { return (YES); ) // // 'initWithFrame()' - Инициализация куба // - (id)initWithFrame:(NSRect)frameRect NSOpenGLPixelFormat *pf; // Получаем новый пиксельный формат и возвращаем // новое окно с кубом ... pf = [Cube basicPixelFormat]; self = [super initWithFrame: frameRect pixelFormat: pf]; return (self); } // // awakeFromNib()1 - Выполняем после загрузки пользовательского // интерфейса из файла NIB ... // - (void)awakeFromNib { // Задаем исходные значения ... initialized = false; // Начинаем вращение куба [self drawRect:[self bounds]]; // Запускаем таймер ... timer = [NSTimer timerWithTimelnterval:(0.05f) target:self
Глава 14 OpenGL в системе MacOS X 743 selector:@selector(idle:) userinfo:nil repeats:YES]; [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode]; [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSEventTrackingRunLoopMode]; Резюме OpenGL поддерживается в системе MacOS X и доступен посредством нескольких программных интерфейсов, предлагаемых Apple: AGL, CGL, Cocoa и GLUT. Про- граммный интерфейс приложения AGL обеспечивает поддержку OpenGL в приложе- ниях, созданных на основе Carbon, и поддерживается в версиях MacOS от 8 до X, ин- терфейс CGL API предоставляет полноэкранную поддержку OpenGL и используется в играх и других полноэкранных приложениях, интерфейс Cocoa является интерфей- сом Objective С для сложных графических пользовательских интерфейсов, a GLUT предлагает простой межплатформенный интерфейс для программ и приложений. Справочная информация agIChoosePixelFormat Цель: Позволить выбирать пиксельный формат для визуализации OpenGL Включаемый файл: <AGL/agl.h> Синтаксис: AGLPixelFormat agIChoosePixelFormat(const AGLDevice *gdevs, GLint ndev, const GLint ★attribs); Описание: Определяет совместимый пиксельный формат для всех перечисленных устройств. Если значение ndev равно 0, выбирается пиксельный формат, совместимый со всеми устройствами отображения Параметры: ★gdevs (тип const AGLDevice) ndev (тип GLint) ★attribs (тип const GLint) Что возвращает: Массив графических устройств Число графических устройств Массив целочисленных атрибутов, завершающихся константой AGL_NONE Совместимый пиксельный формат или NULL, если заданным условиям не удовлетворяет ни один пиксельный формат См. также: aglCreateContext
744 Часть II OpenGL повсюду aglCreateContext Цель: Создать контекст OpenGL для визуализации Включаемый файл: <AGL/agl.h> Синтаксис: AGLContext aglCreateContext(AGLPixelFormat pix, AGLContext share); Описание: Параметры: Создает новый контекст визуализации OpenGL. Если аргумент share не равен NULL, новый контекст будет совместно использовать таблицы отображения, текстурные объекты и массивы вершин с заданным контекстом pix (тип Используемый пиксельный формат, возвращаемый функцией AGLPixelFormat) aglChoosePixelFormat share Контекст OpenGL, с которым нужно совместно использовать (тип AGLContext) таблицы отображения, текстурные объекты и массивы вершин, ИЛИ NULL Что возвращает: Новый контекст OpenGL или null, если создать его нельзя См. также: aglChoosePixelFormat, agIDestroyContext, aglSetCurrentContext, aglSetDrawable agIDestroyContext Цель: Уничтожить контекст визуализации OpenGL Включаемый файл: <AGL/agl.h> Синтаксис: GLboolean agIDestroyContext(AGLContext ctx); Описание: Параметры: Уничтожает заданный контекст OpenGL ctx (тип AGLContext) Что возвращает: Удаляемый контекст OpenGL GL_FALSE, если уничтожить контекст нельзя; GL_TRUE — в противном случае См. также: aglCreateContext
Глава 14. OpenGL в системе MacOS X 745 aglSetCurrentContext Цель: Установить текущий контекст для визуализации OpenGL Включаемый файл: <AGL/agl.h> Синтаксис: GLboolean aglSetCurrentContext(AGLContext ctx); Описание: Параметры: Устанавливает текущий контекст OpenGL для визуализации ctx (тип AGLContext) Что возвращает: Используемый контекст OpenGL GL_FALSE, если использовать контекст нельзя; GL_TRUE — в противном случае См. также: aglCreateContext, aglSetDrawable aglSetDrawable Цель: Задать передний или задний буфер, соотнесенный с контекстом Включаемый файл: <AGL/agl.h> Синтаксис: GLboolean aglSetDrawable(AGLContext ctx, Описание: AGLDrawable draw); Связывает передний или задний буфер с контекстом OpenGL. Параметры: ctx Ее следует вызывать после создания контекста, но перед его использованием для визуализации Контекст OpenGL, с которым выполняется связывание (тип AGLContext) draw Передний или задний буфер (тип AGLDrawable) Что возвращает: gl_false, если связать объекты нельзя; GL_TRUE — См. также: в противном случае aglCreateContext, aglSetCurrentContext
746 Часть II. OpenGL повсюду aglSwapBuffers Цель: Переключить передний и задний буферы в окне OpenGL Включаемый файл: <AGL/agl.h> Синтаксис: void aglSwapBuffers(AGLContext ctx); Описание: Переключает передний и задний буферы окна OpenGL с двойной буферизацией, связанные с конкретным контекстом. Обычно данная функция вызывается после рисования сцены или кадра с помощью функций OpenGL Параметры: ctx (тип AGLContext) Что возвращает: См. также: В каком контексте OpenGL выполнять переключение Ничего aglCreateContext aglUseFont Цель: Создать набор растровых таблиц отображения Включаемый файл: <AGL/agl.h> Синтаксис: GLboolean aglUseFont(AGLContext ctx, GLint fontID, Style face, GLint size, int first, int count, int base); Описание: Создает count таблиц отображения, содержащих растровые образы символов указанного шрифта. Для выделения таблиц отображения под растровые образы используется функция glGenLists Параметры: ctx (тип AGLContext) fontID (тип GLint) face Style size (тип GLint) first (тип int) count (тип int) base (тип int) Что возвращает: Задает текущий контекст визуализации Задает используемый шрифт Задает используемый стиль шрифта Задает используемый размер шрифта Задает используемый первый символ шрифта Задает число используемых символов шрифта Задаст первую используемую таблицу отображения, возвращаемую функцией glGenLists GL_TRUE, если функция выполнена успешно; GL_FALSE — в протианом случае См. также: aglCreateContext
ГЛАВА 15 GLX: OpenGL в системе Linux Ник Хемел (Nick Haemel) ИЗ ЭТОЙ ГЛАВЫ ВЫ УЗНАЕТЕ . . . Действие Функция Выбор подходящих визуальных режимов glxChooseVisual, Управление контекстами визуализации OpenGL Создание окон OpenGL Рисование с двойной буферизацией Создание растровых шрифтов Рисование за кадром glXChooseFBConfig glXCreateContext, glXDestroyContext, glXMakeCurrent glXCreateWindow glXSwapBuffers glXUseXFont glXCreateGLXPixmap, glXCreatePbuffer В данной главе рассматривается GLX — расширение OpenGL, используемое для поддержки приложений OpenGL в системе X Window на платформах UNIX и Linux. С помощью нескольких распространенных наборов инструментов GUI вы научитесь создавать контексты OpenGL и управлять ими, а также создавать области рисования OpenGL. Кроме того, вы узнаете, как использовать GLUT. Основы В операционных системах UNIX и Linux используется расширение OpenGL систе- мы X Window, именуемое GLX Всех функции, работающие под GLX, начинаются с префикса glx и, как правило, включаются в библиотеку GL. Функции GLX явля- ются “клеем”, связывающим OpenGL, XI1 и графическое аппаратное обеспечение, обеспечивающее ускоренную визуализацию Использование библиотек OpenGL и Х11 Положение библиотек и заголовочных файлов OpenGL и XII зависит от системы. Иногда это стандартные места для включаемых файлов и библиотек — /usr/include и /usr/lib, в таком случае требуется только присоединить библиотеки с помощью команды компоновки дсс -о myprogram myprogram.o -1GLU -1GL -IXext -1X11 -Im
748 Часть II OpenGL повсюду В других случаях библиотеки часто располагаются в таких папках, как /usr/XHR6/include или /usr/XHR6/lib, тогда требуются операции компиляции и компоновки: gcc -I/usr/XllR6/include -с myprogram.с дсс -о myprogram myprogram.о -L/usr/XllR6/lib -1GLU -1GL -IXext -1X11 -Im При желании (если вы пишите приложение для одной платформы) можно жест- ко запрограммировать действия компилятора и компоновщика; однако большинство приложений OpenGL предполагается использовать на различных платформах, поэто- му, возможно, удобнее вычислить подходящую опцию перед компоновкой. Одним из распространенных методов является создание конфигурационного сценария, ини- циализирующего необходимые опции с помощью программного обеспечения GNU autoconf. В листинге 15.1 приведен пример файла configure.in, который можно использовать с autoconf. Листинг 15.1. Пример файла configure.in для программного обеспечения autoconf dnl Требуемый файл в пакете ... AC_INIT(myprogram.с) dnl Находит компилятор С ... AC_PROG_CC dnl Находит X Window System... AC_PATH_XTRA LIBS="$LIBS -IXext -1X11 $X_EXTRA_LIBS" CFLAGS="$CFLAGS $X_CFLAGS" LDFLAGS="$X_LIBS $LDFLAGS" if test "x$x_includes" != x; then ac_cpp=$ac_cpp -I$x_includes" fi dnl OpenGL использует математические функции ... AC_SEARCH_LIBS(pow, m) dnl В некоторых реализациях OpenGL используется dlopen()... AC_SEARCH_LIBS(dlopen, dl) dnl Ищет библиотеки OpenGL или Mesa ... AC_CHECK_HEADER(GL/gl.h) AC_CHECK_HEADER(GL/glu.h) AC_CHECK_HEADER(GL/glx.h) AC_CHECK_LIB(GL, glXMakeCurrent, LIBS="-1GLU -1GL $LIBS", AC_CHECK_LIB(MesaGL, glXMakeCurrent, LIBS="-lMesaGLU -IMesaGL $LIBS")) dnl Генерируется make-файл программы AC_OUTPUT(Makefile) Для создания сценария конфигурации используется autoconf Макросы AC_PATH_XTRA и AC_CHECK_LIB обрабатывают поиск библиотек XII и OpenGL, а макрос AC_OUTPUT генерирует файлы, использующие то, что найдет сценарий. Файл Makefile.in, используемый сценарием конфигурации, приведен в листинге 15.2.
Глава 15 GLX: OpenGL в системе Linux 749 Листинг 15.2. Шаблон make-файла Makefile .in # # Пример шаблона make-файла для сценария конфигурации # # # Компилятор и опции ... # СС = @СС@ CFLAGS = @CFLAGS@ LDFLAGS = @LDFLAGS@ LIBS = @LIBS@ # # Программы # myprogram: myprogram.о $(CC) $(LDFLAGS) -o myprogram myprogram.о $(LIBS) Сценарий конфигурации подставляет переменные, заданные в Makefile.in, со- здавая окончательный make-файл, который и будет использован для компоновки при- ложения. Вы задаете переменные, окружая их названия знаками например, @СС@ — компилятор С, @CFLAGS@ — опции компилятора и т.д. Чтобы запустить сценарий конфигурации для генерации make-файла, а затем ис- пользовать команду make для компоновки программы, используйте следующий код: . /configure make Ту же технику можно использовать для создания включаемых файлов, чтобы make- файлы включали общие опции компилятора и компоновщика для больших проектов, занимающих несколько папок. Кроме того можно перечислить несколько шаблонов make-файлов в макросе AC_OUTPUT. Для создания всех примеров данной книги, предназначенных для систем UNIX и Linux, используются различные варианты файлов configure.in и Makefile.in. Использование библиотеки GLUT Библиотека GLUT обычно не является стандартной частью дистрибутивов UNIX и Linux, однако она доступна в виде исходного кода, и ее можно относительно просто скомпилировать и установить, использовав материал, предоставленный на компакт- диске Итак, вначале скопируйте на жесткий диск папку glut-3.7. Затем запустите следующие команды: cd glut-3.7 ./mkmkflies.imake make make install
750 Часть II. OpenGL повсюду После установки GLUT просто добавьте к команде компоновки библиотеку GLUT: gcc -о myprogram myprogram.o -Iglut -1GLU -1GL -IXext -1X11 -Im Или можете добавить ее к шаблону make-файла: LIBS = -Iglut 0LIBS0 OpenGL в системе Linux Хотя в большинстве коммерческих версий UNIX предлагается идеальная поддерж- ка OpenGL, в системе Linux она сильно зависит от видеокарты и используемого дистрибутива Linux. Кроме того, существуют как бесплатные, так и коммерческие версии OpenGL. Наиболее популярную бесплатную реализацию X Window System предлагает про- ект Xfree86, который включен во все дистрибутивы Linux. В версии Xfree86 4.x использована инфраструктура прямой визуализации (Direct Rendering Infrastructure — DRI), разрешающая ускоренную визуализацию OpenGL. Информацию об этих про- ектах можно найти по следующим адресам: http://www.xfree86.org/ http://dri.sf.net/ Поддержка OpenGL активизируется с помощью файла XF86Config или XF86Config-4, обычно установленного в папку /etc/xll. Ключевым фрагментом этого файла является раздел Module, который выглядит следующим образом: Section "Module" Load "GLcore" Load "glx" Load "dri" # OpenGL support # OpenGL X protocol interface # Direct rendering infrastructure Строки Load загружают модули GLcore, glx и dri, которые обеспечивают под- держку OpenGL. Если основной драйвер поддерживает OpenGL, выполнение про- грамм будет ускоряться аппаратно. В противном случае применяется программ- ная эмуляция. Кроме того, доступны коммерческие реализации X Window System. Одним из по- пулярных пакетов, обеспечивающих хорошую поддержку OpenGL, является Summit от Xi Graphics, доступный по следующему адресу: http://www.xi-graphics.com/ Эмуляция OpenGL: Mesa Библиотека Mesa может использоваться в системах, не поддерживающих напрямую OpenGL, что позволяет экспериментировать с новыми возможностями или расшире- ниями OpenGL, не поддерживаемыми вашей видеокартой, или выполнять автоном- ную визуализацию с использованием OpenGL. Библиотеку Mesa можно найти по следующему адресу: http://mesa3d.sf.net/ Помимо того что Mesa можно использовать как самостоятельную библиотеку, она также служит основой реализации Xfree86 стандарта OpenGL.
Глава 15 GLX: OpenGL в системе Linux 751 Расширение OpenGL для X Window System Расширение OpenGL для X Window System, именуемое GLX, обеспечивает интер- фейс между приложением, X Window System и графическим драйвером с целью обеспечения ускоренной визуализации. Если графическое аппаратное обеспечение не поддерживает некоторую возможностью, она автоматически эмулируется программ- но. Чтобы проверить, поддерживает ли ваш Х-сервер это расширение, из командной строки запускается программа xdpyinfo: xdpyinfo | grep GLX На момент выхода книги насчитывалось пять версий расширения GLX (1.0, 1.1, 1.2, 1.3 и 1.4); пожалуй, самой используемой была версия 1.2, однако в данной главе также рассматриваются функциональные возможности P-буфера, доступные с версии GLX 1.3. GLX предлагает несколько функций, управляющих визуальными режимами, кон- текстами и поверхностями рисования. Основы X Window System X Window System предлагает прозрачный для сети интерфейс, позволяющий созда- вать окна, рисовать их на экране, получать ввод от пользователя и т.д. Программа, управляющая одним или несколькими экранами, клавиатурой, мышью и другими раз- ноплановыми устройствами ввода, обычно называется Х-сервером. Связью с серве- ром управляет указатель Display. Для соединения с сервером используется функция XOpenDisplay() и имя дисплея. Display *display; display = XOpenDisplay(getenv("DISPLAY")); По умолчанию имя дисплея предоставляется в переменной среды DISPLAY. Уста- новив соединение с дисплеем, можно создавать окна, рисовать графику и получать ввод от пользователя. Выбор режима визуализации С каждым окном на экране соотнесен режим визуализации. С каждым режимом соотнесен класс — DirectGray, DirectColor, PseudoColor или TrueColor, — определяющий его свойства. В большинстве операций визуализации OpenGL ис- пользуется режим TrueColor, допускающий использование произвольных красных, зеленых и синих кодов цвета. Другие типы режимов визуализации отображают коды цвета или индексы в конкретные RGB-коды, что делает их пригодными только для специализированных приложений. Функция glXChooseVisual () определяет нужный режим визуализации, подхо- дящий для конкретного экрана и набора элементов OpenGL. Эта функция принимает указатель Display, номер экрана (обычно 0) и список атрибутов. Список атрибутов представляет собой массив целых чисел. Каждый атрибут состоит из имени токе- на (например, GLX_RED_SIZE) и (для некоторых атрибутов) значения Определенные
752 Часть II. OpenGL повсюду ТАБЛИЦА 15.1. Константы списка атрибутов визуальных режимов GLX Константа Описание GLX_ACCUM_ALPHA_SIZE Следующее за константой число задает минимальное число битов для записи в буфере накопления параметра альфа GLX_ACCUM_BLUE_SIZE Следующее за константой число задает минимальное число битов для записи в буфере накопления кода синего цвета GLX_ACCUM_GREEN_SIZE Следующее за константой число задает минимальное число битов для записи в буфере накопления кода зеленого цвета GLX_ACCUM_RED_SIZE Следующее за константой число задает минимальное число битов для записи в буфере накопления кода красного цвета GLX_ALPHA_SIZE Следующее за константой число задает минимальное число битов для записи параметра альфа GLX_AUX_BUFFERS Следующее за константой число задает необходимое количество дополнительных буферов GLX_BLUE_SIZE Следующее за константой число задает минимальное число битов для записи кода синего цвета GLX_BUFFER_SIZE Следующее за константой число задает желаемое число битов для записи индекса цвета GLX_DEPTH_SIZE Следующее за константой число задает минимальное число требуемых битов глубины GLX_DOUBLEBUFFER Указывает, что желательна двойная буферизация GLX_GREEN_SIZE Следующее за константой число задает минимальное число битов для записи кода зеленого цвета GLX_LEVEL Следующее за константой число задает уровень буфера; 0 — основной буфер, 1 — первый накладывающийся буфер, -1 — первый перекрываемый буфер и тд GLX_RED_SIZE Следующее за константой число задает минимальное число битов для записи кода красного цвета GLX_RGBA Указывает, что желателен режим RGBA GLX_STENCIL_SIZE Следующее за константой число задает минимальное число требуемых битов трафарета GLX_STEREO Указывает, что желательна стереовизуализации, в таком режиме создаются отдельные изображения для левого и правого глаза GLX_USE_GL Указывает, что желателен режим визуализации OpenGL Этот атрибут игнорируется, поскольку glXChooseVisual () возвращает только режимы визуализации OpenGL в настоящее время токены списка атрибутов, перечислены в табл. 15.1. Чтобы найти режим визуализации RGB с двойной буферизацией, можно исполь- зовать следующий код: int attributes[] = { GLXJRGBA, GLX_DOUBLEBUFFER, GLX_RED_SIZE, 4, GLX_GREEN_SIZE, 4, GLX_BLUE_SIZE, 4, GLX_DEPTH_SIZE, 16, 0
Глава 15 GLX: OpenGL в системе Linux 753 Display ‘display; XVisuallnfo ‘vinfo; vinfo = glXChooseVisualfdisplay, Defaultscreen(display), attributes); При его выполнении возвращается указатель XVisuallnfo, предоставляющий ин- формацию о значении, которое следует использовать Если соответствующий режим визуализации не найден, возвращается указатель NULL, и стоит повторить запрос с другими атрибутами или сообщить пользователям, что в их системе окно отобра- зить нельзя. Найдя нужный режим визуализации, вы можете создавать контекст OpenGL для использования с окном или пиксельным отображением. Управление контекстами OpenGL Контексты OpenGL используются для управления рисованием в одном окне или пик- сельном образе. Для идентификации контекстов применяется тип данных GLXCon- text. Функция glXCreateContext () создает новый контекст и принимает указатель Display, указатель XVisuallnfo, контекст GLXContext и булево значение, указы- вающее, какой контекст нужно создавать, прямой или непрямой. GLXContext context; context = glXCreateContext(display, vinfo, 0, True); Указатели на положение информации о дисплее и режиме визуализации инициа- лизируются так, как показано в предыдущих примерах кода. Третий аргумент задает контекст GLXContext, с которым будут использоваться таблицы отображения, тек- стурные объекты и другие записанные данные. Скорее всего, вы будете задавать этот аргумент при создании для приложения нескольких окон OpenGL. Если другие контексты у вас не определены, используйте в качестве аргумента контекста 0. Четвертый аргумент задает, какой контекст создается — прямой (True) или непря- мой (False) Прямые контексты позволяют библиотеке OpenGL общаться непосред- ственно с графическим аппаратным обеспечением, что дает наиболее скоростную визуализацию. Непрямые контексты посылают команды рисования OpenGL через Х-сервер, разрешая удаленное отображение изображений (через сеть). Прямые кон- тексты нельзя использовать вместе с непрямыми, и наоборот Отметим, что данный момент весьма существенен при некоторых типах закадровой визуализации. Также отметим, что обычно требуется прямой контекст. Завершив работу с контекстом OpenGL, вызовите функцию glXDestroyCon- text (), чтобы освободить использованные им ресурсы glXDestroyContext(display, context); Создание окна OpenGL Имея указатель Display и контекст GLXContext, вы можете использовать функцию XCreateWindow () для создания окна
754 Часть II OpenGL повсюду Window window; XSetWindowAttributes winattrs; winattrs.event_mask = ExposureMask | VisibilityChangeMask I StructureNotifyMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask; winattrs.border_pixel = 0; winattrs.bit_gravity = StaticGravity; winmask = CWBorderPixel | CWBitGravity | CWEventMask; window = XCreateWindow(display, DefaultRootWindow(display), 0, 0, 400, 400, 0, vinfo->depth, Inputoutput, vinfo->visual, winmask, &winattrs); Создав окно, вы можете отобразить его, использовав функцию XMapWindowO и связав контекст OpenGL с окном с помощью функции glXMakeCurrent (). XMapWindow(display, window); glXMakeCurrent(display, window, context); Если у вас несколько окон OpenGL, прежде, чем рисовать в одном из них что-либо, нужно вызывать функцию glXMakeCurrent () В противном случае она вызывается один раз за все время работы программы. Прежде чем вы сможете рисовать в окне, нужно дождаться события MapNotify, которое сообщает приложению, что Х-сервер отобразил окно на экране Вы можете дождаться события MapNotify, запустив функцию XNextEvent(), или проследить, произошло ли это событие при выполнении обычного цикла событий в приложении. Окна с двойной буферизацией Окна с двойной буферизацией создаются с помощью режимов визуализации с двой- ной буферизацией Для получения таких режимов вызывается функция glXChoose- Visual () С атрибутом GLX_DOUBLEBUFFER После создания окна функция gIXSwapBuf fers () переключает для него передний и задний буферы gIXSwapBuffers(display, window); Функция gIXSwapBuf fers () выталкивает все незаконченные команды рисования OpenGL из стека команд и может блокировать работу до следующего обратного хода кадровой развертки. Собираем все вместе На рис 15 1 показано приложение OpcnGL, полностью написанное с помощью функ- ций Xlib и GLX, рассмотренных в данном разделе, это приложение отображает вра- щающийся куб Щелчком кнопки мыши пользователь может остановить вращение, а посредством перетаскивания — повернуть куб вокруг оси. Программа реагиру- ет на события ConfigureNotify, отслеживая моменты изменения размеров окна, и события MapNotify и UnmapNotify, когда окно отображается или сворачивается в пиктограмму. Исходный код приложения приводится в листинге 15 3.
Глава 15. GLX: OpenGL в системе Linux Рис. 15.1. Пример вращающегося куба Xlib Листинг 15.3. Программа xlib.c * Включаем необходимые заголовки... ♦include <stdio.h> ♦include <stdlib.h> ♦include <X11/Xlib.h> ♦include <X11/Xatom.h> ♦include <GL/glx.h> ♦include <GL/gl.h> ♦include <sys/select.h> ♦include <sys/types.h> ♦include <sys/time.h> /* * Глобальные переменные... */ float CubeRotation[3], CubeRate[3]; int CubeMouseButton, CubeMouseX, CubeMouseY; int CubeWidth, CubeHeight; /* * Функции... */ void DisplayFunc(void); /* * Вращение куба */ /* Скорость вращения куба */ /* Нажатая кнопка */ /* Координата X начального * положения курсора мыши */ /* Координата Y начального * положения курсора мыши */ /* Ширина окна */ /* Высота окна */
756 Часть II. OpenGL повсюду void IdleFunc(void); void MotionFunc(int x, int y); void MouseFunc(int button, int state, int x, int y); void ReshapeFunc(int width, int height); * 'main()' - Основная точка входа программы int /* Выход - состояние выхода */ main(int argc, /* Вход - число аргументов командной строки */ char *argv[]) /* Вход - аргументы командной строки*/ { Bool /* Отображается GLXContext Display Window XVisuallnfo mapped; ли окно? */ context; ♦display; window; *vinfo; XSetWindowAttributes winattrs; int winmask; XEvent event; XWindowAttributes windata; struct timeval timeout; /* Контекст OpenGL */ /* Связь с дисплеем * системы X Window */ /* Окно X Window */ /* Информация о режимах * визуализации */ /* Атрибуты окна */ /* Маска для атрибутов */ /* Данные события */ /* Данные окна */ /* Интервал времени ожидания для функции select() */ fd_set input; /* Входное множество * для функции selectO */ int ready; /* Событие готово? */ static int attributes[] = /* Атрибуты OpenGL */ { GLX_RGBA, GLX_DOUBLEBUFFER, GLX_RED_SIZE, 8, GLX_GREEN_SIZE, 8, GLX_BLUE_SIZE, 8, GLX_DEPTH_SIZE, 16, 0 ); * Открывается соединение с Х-сервером... */ display = XOpenDisplay(getenv("DISPLAY")); /* * Находит нужный режим визуализации для окна OpenGL... */ vinfo = glXChooseVisual(display, DefaultScreen(display), attributes); /* Создается окно... */ winattrs.event_mask = ExposureMask | VisibilityChangeMask | StructureNotifyMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask;
Гпава 15 GLX: OpenGL в системе Linux 757 winattrs.border_pixel = 0; winattrs.bit_gravity = StaticGravity; winmask = CWBorderPixel | CWBitGravity | CWEventMask; window = XCreateWindow(display, DefaultRootWindow(display), 0, 0, 400, 400, 0, vinfo->depth, Inputoutput, vinfo->visual, winmask, &winattrs); XChangeProperty(display, window, XA_WM_NAME, XA_STRING, 8, 0, (unsigned char *)argv[0], strlen(argv[0])); XChangeProperty(display, window, XA_WM_ICON_NAME, XA_STRING, 8, 0, (unsigned char *)argv[0], strlen(argv[0])); XMapWindow(display, window); /* * Создается контекст OpenGL,.. * / context = glXCreateContext(display, vinfo, 0, True); glXMakeCurrent(display, window, context); /* * Устанавливаем оставшиеся глобальные переменные... */ CubeWidth = 400; CubeHeight = 400; CubeRotation[0] = 45.Of; CubeRotation[1] = 45.Of; CubeRotation[2] = 45.Of; CubeRate[0] = l.Of; CubeRate[1] = l.Of; CubeRate[2] = l.Of; /* * Бесконечный цикл... */ mapped = False; for (;;) * Используем select() для асинхронного реагирования * на события; если окно не отображено, ожидаем бесконечно * долго; в противном случае время ожидания равно 20 мс, * затем поворачиваем куб ... * / if (mapped) FD_ZERO(&input) ; FD_SET(ConnectionNumber(display), &input); timeout.tv_sec = 0; timeout.tv_usec = 20000; ready = select(ConnectionNumber(display) + 1, &input, NULL, NULL, &timeout); else ready = 1;
Часть II OpenGL повсюду if (ready) { /* * Событие готово, обрабатываем его... */ XNextEvent( display, Seventh- switch (event.type) { case MapNotify : mapped = True; case ConfigureNotify : XGetWindowAttributes(display, window, Swindata); ReshapeFunc(windata.width, windata.height); break; case UnmapNotify : mapped = False; break; case ButtonPress : MouseFunc(event.xbutton.button, 0, event.xbutton.x, event.xbutton.у); break; case ButtonRelease : MouseFunc(event.xbutton.button, 1, event.xbutton.x, event.xbutton.у); break; case MotionNotify : if (event.xmotion.state S (ButtonlMask | Button2Mask | Button3Mask)) MotionFunc(event.xmotion.x, event.xmotion.у); break; * Перерисовываем изображение, если окно было отображено... */ if (mapped) * Обновляем вращение куба... */ IdleFunc(); /* * Рисуем куб... */ DisplayFunc() ; /* * Переключаем передний и задний буферы... */ glXSwapBuffers(display, window);
Глава 15 GLX: OpenGL в системе Linux 759 } } ) /* * 'DisplayFunc()' - Рисуем куб */ void DisplayFunc(void) { int i, j; /* Переменные цикла */ static const GLfloat corners[8][3] = /* Угловые вершины */ { { l.Of, l.Of, l.Of }, { l.Of,-l.Of, l.Of }, {-l.Of,-l.Of, l.Of }, {-l.Of, l.Of, l.Of ), { l.Of, l.Of,-l.Of ), { l.Of,-l.Of,-l.Of }, {-l.Of,-l.Of,-l.Of }, {-l.Of, l.Of,-l.Of } /* Передняя правая верхняя */ /* Передняя правая нижняя */ /* Передняя левая нижняя */ /* Передняя левая верхняя */ /* Задняя правая верхняя */ /* Задняя правая нижняя */ /* Задняя левая нижняя */ /* Задняя левая верхняя */ const int sides[6][4] = /* Грани */ /* Передняя */ /* Задняя */ /* Правая */ /* Левая */ /* Верхняя */ /* Нижняя */ const GLfloat colors[6][3] = /* Цвета */ { l.Of, O.Of, O.Of }, /* Красный */ { O.Of, l.Of, O.Of }, /* Зеленый */ { l.Of, l.Of, O.Of ), /* Желтый */ { O.Of, O.Of, l.Of ), /* Синий */ { l.Of, O.Of, l.Of }, /* Пурпурный */ { O.Of, l.Of, l.Of } /* Голубой */ /*' * Очищаем окно. glViewport(0, 0, CubeWidth, CubeHeight); glClearColor(O.Of, O.Of, O.Of, O.Of); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); /* * Настройка матриц... */ glMatrixMode(GL_PROJECTION); glLoadldentity(); glOrtho(-2.Of, 2.Of, -2.Of * CubeHeight / CubeWidth, 2.Of * CubeHeight / CubeWidth, -2.Of, 2.Of);
760 Часть II OpenGL повсюду glMatrixMode(GL_MODELVIEW); glLoadldentity(); glRotatef(CubeRotation[0], l.Of, O.Of, O.Of); glRotatef(CubeRotation[1], O.Of, l.Of, O.Of); glRotatef(CubeRotation[2], O.Of, O.Of, l.Of); /* * Рисуем куб... */ glEnable(GL_DEPTH_TEST); glBegin(GL_QUADS); for (1 =0; i < 6; i ++) { glColor3fv(colors[i]); for (j =0; j <4; j ++) glVertex3fv(corners[sides[i H j]]); ) glEnd(); ) /* * 'IdleFunc()' - Поворачиваем и перерисовываем куб void IdleFunc(void) < CubeRotation[0] += CubeRate[0]; CubeRotation[l] += CubeRate[l]; CubeRotation[2] += CubeRate[2]; } /* * 'MotionFunc()' - Обработка движения указателя мыши */ void MotionFunc(int x, /* Вход - координата X */ int у) /* Вход - координата Y */ { /* * Получаем движение мыши... */ х -= CubeMouseX; у -= CubeMouseY; /* * Обновляем скорость вращения куба согласно движению мыши * и нажатой клавише ... */ switch (CubeMouseButton) { case 0 : /* Кнопка 1 */ CubeRate[0] = O.Olf * у; CubeRatefl] = O.Olf * x; CubeRate[2] = O.Of; break; case 1 : /* Кнопка 2 */
Глава 15 GLX' OpenGL в системе Linux 761 CubeRate[0] = O.Of; CubeRate[1] = O.Olf * y; CubeRate[2] = O.Olf * x; break; default : /* Кнопка 3 */ CubeRate[0] = O.Olf * y; CubeRate[1] = O.Of; CubeRate[2] = O.Olf * x; break; } } /* * 'MouseFunc()' - Обрабатываем события, связанные * с нажатием/освобождением кнопок мыши */ void MouseFunc(int button, /* Вход - нажатая кнопка */ int state, /* Вход - состояние кнопки (1 = нажата) */ int х, /* Вход - координата X */ int у) /* Вход - координата Y */ { /* * Реагирует только на нажатие кнопки... */ if (state) return; /* * Записываем состояние мыши... */ CubeMouseButton = button; CubeMouseX = х; CubeMouseY = у; /* * Обновление скоростей вращения... */ CubeRate[0] = O.Of; CubeRate[1] = O.Of; CubeRate[2] = O.Of; } /* * 'ReshapeFunc()' - Изменение размеров окна */ void ReshapeFunc(int width, /* Вход - ширина окна */ int height) /* Вход - высота окна */ { CubeWidth = width; CubeHeight = height;
762 Часть II OpenGL повсюду Создание растровых шрифтов для OpenGL Расширение GLX предлагает функцию gIXUseXFont (), преобразующую шрифт X Window в растровые изображения OpenGL. Каждое такое изображение помещается в таблицу отображения, что позволяет отображать строку текста с помощью функции glCallLists (). Итак, вначале используется функция XLoadQueryFont () • XFontStruct *font; font = XLoadQueryFont(display, "-*-courier-bold-r-normal—14-*-*- Приведенный код загружает полужирный шрифт Courier Bold с высотой символа 14 пикселей (вообще, может использоваться любой шрифт X Window) После загрузки шрифта X вы вызываете функцию glGenLists () с целью создания таблиц отображе- ния для тех символов, которые желаете использовать, и функцию gIXUseXFont () — для загрузки растровых изображений из таблиц отображения. В приведенном ни- же коде в 96 таблиц отображения загружаются символы — от пробела (код 32) до удаления (код 127) GLuint listbase; listbase = glGenLists(96); gIXUseXFont(font->fid, ' ', 96, listbase); Далее можно представлять текст, следующим образом используя комбина- цию функций glRasterPos(), glPushAttribf), gIListBase(), glCallLists() и glPopAttrib(): char *s = "Hello, World!"; glPushAttrib(GL_LIST_BIT); gIListBase(CubeFont - ' '); glRasterPos3f(O.Of, O.Of, O.Of); glCallLists(strlen(s), GL_BYTE, s); glPopAttrib(); В примере, приведенном в листинге 15.4, этот код используется для рисования меток на сторонах куба. Получаемый результат показан на рис. 15 2 Листинг 15.4. Программа xiibfonts.c * Включаем необходимые заголовки... */ ♦include <stdio.h> ♦include <stdlib.h> ♦include <X11/Xlib.h> ♦include <X11/Xatom.h> ♦include <GL/glx.h> ♦include <GL/gl.h> ♦include <sys/select.h> ♦include <sys/types.h> ♦include <sys/time.h>
Глава 15. GLX: OpenGL в системе Linux 763 Рис. 15.2. Пример вращающегося куба Xlib с текстом * Глобальные переменные... */ float CubeRotation[3], /* Вращение куба */ CubeRate[3J; /* Скорость вращения куба */ int CubeMouseButton, /* Нажатая кнопка */ CubeMouseX, /* Координата X начального * положения курсора мыши */ CubeMouseY; /* Координата Y начального * положения курсора мыши */ int CubeWidth, /* Ширина окна */ CubeHeight; /* Высота окна */ GLuint CubeFont; /* Таблицы отображения, * на основе которых формируется шрифт */ /* * Функции... */ void DisplayFunc(void); void IdleFunc(void); void MotionFunc(int x, int y); void MouseFunc(int button, int state, int x, int y); void ReshapeFunc(int width, int height); /* * 'mainO' - Основная точка входа программы */ int /* Выход - состояние выхода */ main(int argc, /* Вход - число аргументов командной строки */ char *argv[])
764 Часть II OpenGL повсюду /* Вход - аргументы командной строки*/ { Bool mapped; GLXContext context; Display ‘display; Window window; XVisuallnfo *vinfo; XFontStruct ‘font; XSetWindowAttributes winattrs; int winmask; XEvent event; XWindowAttributes windata; struct timeval timeout; fd_set input; int ready; static int attributes[] = { GLX_RGBA, GLX_DOUBLEBUFFER, GLX_RED_SIZE, 8, GLX_GREEN_SIZE, 8, GLX_BLUE_SIZE, 8, GLX_DEPTH_SIZE, 16, 0 }; /* Отображается ли окно? */ /* Контекст OpenGL */ /* Связь с дисплеем * системы X Window */ /* Окно X Window */ /* Информация о режимах * визуализации */ /* Информация по шрифту */ /* Атрибуты окна */ /* Маска для атрибутов */ /* Данные события */ /* Данные окна */ /* Интервал времени ожидания * для функции select() */ /* Входное множество * для функции select() */ /* Событие готово? */ /* Атрибуты OpenGL */ * Открывается соединение с Х-сервером... */ display = XOpenDisplay(getenv("DISPLAY")); /‘ * Находит нужный режим визуализации для окна OpenGL... */ vinfo = glXChooseVisual(display, Defaultscreen(display), attributes); * Создается окно... winattrs.event_mask = ExposureMask I VisibilityChangeMask I StructureNotifyMask | ButtonPressMask | ButtonReleaseMask I PointerMotionMask; winattrs.border_pixel = 0; winattrs.bit_gravity = StaticGravity; winmask = CWBorderPixel I CWBitGravity | CWEventMask; window = XCreateWindow(display, DefaultRootWindow(display), 0, 0, 400, 400, 0, vinfo->depth, InputOutput, vinfo->visual, winmask,
Глава 15 GLX: OpenGL в системе Linux 765 &winattrs); XChangeProperty(display, window, XA_WM_NAME, XA_STRING, 8, 0, (unsigned char *)argv[0], strlen(argv[0])); XChangeProperty(display, window, XA_WM_ICON_NAME, XA_STRING, 8, 0, (unsigned char *)argv[0], strlen(argv[0])); XMapWindow(display, window); /* * Создается контекст OpenGL... */ context = glXCreateContext(display, vinfo, 0, True); glXMakeCurrent(display, window, context); /* * Устанавливаем оставшиеся глобальные переменные... */ CubeWidth = 400; CubeHeight = 400; CubeRotation[0] = 45.Of; CubeRotation[l] = 45.Of; CubeRotation[2] = 45.Of; CubeRate[0] = l.Of; CubeRatefl] = l.Of; CubeRate[2] = l.Of; /* * Настройка шрифта... font = XLoadQueryFont(display, "-*-courier-bold-r-normal—14-*-*-*-*-*-*-*"); CubeFont = glGenLists(96); gIXUseXFont(font->fid, ' ', 96, CubeFont); /* * Бесконечный цикл... * / mapped = False; for (;;) { /* * Используем select() для асинхронного реагирования * на события; если окно не отображено, ожидаем * бесконечно долго; в противном случае время ожидания * равно 20 мс, затем поворачиваем куб ... */ if (mapped) { FD_ZERO(Sinput); FD_SET(ConnectionNumber(display), &input); timeout.tv_sec = 0; timeout.tv_usec = 20000; ready = select(ConnectionNumber(display) + 1, &input, NULL, NULL, &timeout); } else
56 Часть II OpenGL повсюду ready = 1; if (ready) { /* * Событие готово, обрабатываем его... */ XNextEvent(display, &event); switch (event.type) { case MapNotify : mapped = True; case ConfigureNotify : XGetWindowAttributes(display, window, &windata); ReshapeFunc(windata.width, windata.height); break; case UnmapNotify : mapped = False; break; case ButtonPress : MouseFunc(event.xbutton.button, 0, event.xbutton.x, event.xbutton.y); break; case ButtonRelease : MouseFunc(event.xbutton.button, 1, event.xbutton.x, event.xbutton.y); break; case MotionNotify : if (event.xmotion.state & (ButtonlMask | Button2Mask | Button3Mask)) MotionFunc(event.xmotion.x, event.xmotion.у); break; * Перерисовываем изображение, если окно было отображено ... */ if (mapped) { /* * Обновляем вращение куба... IdleFunc() ; /* * Рисуем куб... */ DisplayFunc() ; /* * Переключаем передний и задний буферы ...
Глава 15 GLX'OpenGL в системе Linux 767 glXSwapBuffers(display, window); } } } /* * 'DisplayFunc()' - Рисуем куб */ void DisplayFunc(void) { int i, j; /* Переменные цикла */ static const GLfloat corners[8][3] = /* Угловые вершины */ ( { l.Of, l.Of, l.Of ), /* Передняя правая верхняя */ { l.Of, -l.Of, l.Of ), /* Передняя правая нижняя */ { -l.Of, -l.Of, l.Of }, /* Передняя левая нижняя */ { -l.Of, l.Of, l.Of }, /* Передняя левая верхняя */ { l.Of, l.Of, -l.Of }, /* Задняя правая верхняя */ { l.Of, -l.Of, -l.Of }, /* Задняя правая нижняя */ { -l.Of, -l.Of, -l.Of ), /* Задняя левая нижняя */ { -l.Of, l.Of, -l.Of } /* Задняя левая верхняя */ ); static const int sides[6][4] = /* Грани */ { 0, 1, 2, 3 ), /* Передняя 4, 5, 6, 7 }, /* Задняя */ 0, 1, 5, 4 }, /* Правая */ 2, 3, 7, 6 ), /* Левая */ 0, 3, 7, 4 ), /* Верхняя *, 1, 2, 6, 5 ) /* Нижняя */ }; static const GLfloat colors[6][3] = /* Цвета */ { l.Of, O.Of, O.Of }, /* Красный */ O.Of, l.Of, 0 Of }, /* Зеленый */ l.Of, l.Of, O.Of ), /* Желтый */ O.Of, O.Of, l.Of }, /* Синий */ l.Of, O.Of, l.Of }, /* Пурпурный O.Of, l.Of, l.Of } /* Голубой */ * Очищаем окно... */ glViewport(0, 0, CubeWidth, CubeHeight); glClearColor(0.Of, O.Of, O.Of, O.Of); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); /* * Настройка матриц... */ glMatrixMode(GL_PROJECTION); glLoadldentity(); glOrtho(-2.Of, 2.Of, -2.Of * CubeHeight I CubeWidth, 2.Of * CubeHeight / CubeWidth, -2.Of, 2.Of);
768 Часть II. OpenGL повсюду glMatrixMode(GL_MODELVIEW); glLoadldentity(); glRotatef(CubeRotation!0], l.Of, O.Of, O.Of); glRotatef(CubeRotation[l], O.Of, l.Of, O.Of); glRotatef(CubeRotation[2], O.Of, O.Of, l.Of); /* * Рисуем куб... * / glEnable(GL_DEPTH_TEST); glBegin(GL_QUADS); for (i = 0; i < 6; i ++) { glColor3fv(colors[i]); for (j =0; j <4; j ++) glVertex3fv(corners[sides[i][j]]); } glEndO; /* * Рисуем линии, выходящие из куба ... * / glColor3f(l.Of, l.Of, l.Of); glBegin(GL_LINES); glVertex3f(O.Of, O.Of, -1.5f); glVertex3f(O.Of, O.Of, 1.5f); glVertex3f(-1.5f, O.Of, O.Of); glVertex3f(1.5f, O.Of, O.Of); glVertex3f(O.Of, 1.5f, O.Of); glVertex3f(O.Of, -1.5f, O.Of); glEndO; /* * Формируем текст на каждой стороне ... */ glPushAttrib(GL_LIST_BIT) ; glListBase(CubeFont - ' '); glRasterPos3f(O.Of, O.Of, -1.5f); glCallLists(4, GL_BYTE, "Back"); glRasterPos3f(O.Of, O.Of, 1.5f); glCallLists(5, GL_BYTE, "Front"); glRasterPos3f(-1.5f, O.Of, O.Of); glCallLists(4, GL_BYTE, "Left"); glRasterPos3f(1.5f, O.Of, O.Of); glCallLists(5, GL_BYTE, "Right"); glRasterPos3f(O.Of, 1.5f, O.Of); glCallLists(3, GL_BYTE, "Top"); glRasterPos3f(O.Of, -1.5f, O.Of); glCallLists(6, GL_BYTE, "Bottom"); glPopAttrib(); * 'IdleFunc()' - Поворачиваем и перерисовываем куб */ void IdleFunc(void)
Гпава 15 GLX' OpenGL в системе Linux 769 { CubeRotation[0] += CubeRate[0]; CubeRotation!1] += CubeRate[lJ; CubeRotation[2] += CubeRate[2]; } /* * 'MotionFunc()' - Обработка движения указателя мыши */ void MotionFunc(int x, /* Вход - координата X */ int у) /* Вход - координата Y */ { /* * Получаем движение мыши ... */ х -= CubeMouseX; у -= CubeMouseY; /* * Обновляем скорость вращения куба согласно движению * мыши и нажатой кнопке ... */ switch (CubeMouseButton) { case 0 : /* Кнопка 1 */ CubeRate[0] = O.Olf * у; CubeRate[1] = O.Olf * x; CubeRate[2] = O.Of; break; case 1 : /* Кнопка 2 */ CubeRate[0] = O.Of; CubeRate[1] = O.Olf * y; CubeRate[2] = O.Olf * x; break; default : /* Кнопка 3 */ CubeRate[0] = O.Olf * y; CubeRate[1] = O.Of; CubeRate[2] = O.Olf * x; break; } } /* * 'MouseFunc!)1 - Обрабатываем события, связанные * с нажатием/освобождением кнопок мыши */ void MouseFunc(int button, /* Вход - нажатая кнопка */ int state, /* Вход - состояние кнопки (1 = нажата) */ int х, /* Вход - координата X */ int у) /* Вход - координата Y */ { /* * Реагируем только на нажатие кнопки...
770 Часть II OpenGL повсюду *! if (state) return; /* * Записываем состояние мыши... */ CubeMouseButton = button; CubeMouseX = x; CubeMouseY = y; /* * Обновление скоростей вращения... * / CubeRate[0] = O.Of; CubeRate[l] = O.Of; CubeRate[2] = O.Of; * 'ReshapeFunc()' - Изменение размеров окна */ void ReshapeFunc(int width, /* Вход - ширина окна */ int height) /* Вход - высота окна */ { CubeWidth = width; CubeHeight = height; } Закадровая визуализация GLX поддерживает два типа закадровой (offscreen) визуализации, каждый из которых имеет свои преимущества: пиксельное отображение GLX и Р-буферы. Использование пиксельного отображения GLX Пиксельное отображение GLX является оригинальным типом закадровой визуали- зации и обычно поддерживает режимы визуализации TrueColor и PseudoColor Как правило, оно используется в тех случаях, когда переносимость важнее произ- водительности, хотя пиксельное отображение GLX доступно на всех платформах, оно не ускоряется аппаратно. Пиксельное отображение GLX также часто поддержи- вает большие насыщенности цвета, чем графическое аппаратное обеспечение, что делает его идеальным для автономной визуализации изображения при ограниченной памяти видеокарты. Как и при работе с окнами OpenGL, активизация пиксельного отображения GLX начинается с вызова функции gixchoosevisual () с целью нахождения подходя- щего визуального режима Поскольку некоторые системы и видеокарты предлагают только режимы визуализации OpenGL с двойной буферизацией, следует проверить доступность режимов визуализации и с обычной, и с двойной буферизацией.
Глава 15 GLX: OpenGL в системе Linux 771 Display *display; XVisuallnfo *vinfo; static int attributes[] = { GLX_RGBA, GLX_RED_SIZE, 8, GLX_GREEN_SIZE, 8, GLX_BLUE_SIZE, 8, GLX_DEPTH_SIZE, 16, 0, /* Резервируем место для GLX_DOUBLEBUFFER */ 0 }; display = XOpenDisplay(getenvf"DISPLAY")); vinfo = glXChooseVisual(display, DefaultScreen(display), attributes); if (Ivinfo) { /* * Если нет доступных режимов с обычной буферизацией, * пробуем режим с двойной ... * / attributes[9] = GLX_DOUBLEBUFFER; vinfo = glXChooseVisual(display, Defaultscreen(display), attributes); } Найдя подходящий режим визуализации, с помощью функции XCreatePixmap() можно создать пиксельное отображение Pixmap; это отображение будет содержать действительные пиксели вашего закадрового буфера. Затем с помощью функции glx- CreateGLXPixmap () это отображение связывается с GLX для OpenGL-визуализации. Pixmap pixmap; GLXPixmap glxpixmap; pixmap = XCreatePixmap(display, DefaultRootWindow(display), 1024, 1024, vinfo->depth); glxpixmap = glXCreateGLXPixmap(display, vinfo, pixmap); Наконец, с помощью функции glxCreateContext () создается контекст для пик- сельного отображения GLX, причем если нужен непрямой контекст визуализации, в качестве четвертого аргумента используется значение False. GLXContext context; context = glXCreateContext(display, vinfo, 0, False); glXMakeCurrent(display, glxpixmap, context); После этого в пиксельном отображении можно рисовать, используя функции OpenGL и считывая результаты с помощью функции glReadPixels (). В листин- ге 15.5 показан вариант предыдущей программы, где создается пиксельное отобра- жение GLX, рисуется куб, считывается изображение с помощью glReadPixels () и записывается результат в файл изображения PPM с названием glxpixmap.ppm.
772 Часть II OpenGL повсюду Листинг 15.5. Пример битового отображения GLX /* * Включаем необходимые заголовки... */ #include <stdio.h> #include <stdlib.h> «include <X11/Xlib.h> «include <X11/Xatom.h> «include <GL/glx.h> «include <GL/gl.h> /* * Глобальные переменные... */ float CubeRotation[3], /* Вращение куба */ CubeRate[3]; /* Скорость вращения куба */ int CubeWidth, CubeHeight; /* * Функции... */ void DisplayFunc(void) ; /* Ширина окна */ /* Высота окна */ * 'main()' - Основная точка входа программы */ int /* Выход - состояние выхода */ main(int argc, /* Вход - число аргументов командной строки */ char *argv[]) /* Вход - аргументы командной строки*/ { GLXContext context; Display *display; Pixmap pixmap; GLXPixmap glxpixmap; XVisuallnfo *vinfo; FILE *fp; int y; unsigned char pixels[3072]; static int attributes!] = /* Контекст OpenGL */ /* Связь с дисплеем * системы X Window */ /* Пиксельный образ */ /* Пиксельное * отображение GLX */ /* Информация о режимах * визуализации */ /* Указатель файла PPM */ /* Текущая строка */ /* Одна строка * RGB-пикселей */ /* Атрибуты OpenGL */ GLX_RGBA, GLX_RED_SIZE, 8, GLX_GREEN_SIZE, 8, GLX_BLUE_SIZE, 8, GLX_DEPTH_SIZE, 16, 0, /* Резервируем место * для GLX_DOUBLEBUFFER */
Глава 15 GLX' OpenGL в системе Linux 773 О }; * Открывается соединение с Х-сервером... display = XOpenDisplay(getenv("DISPLAY")); /* * Находим подходящий режим визуализации для GLX... */ vinfo = glXChooseVisual(display, DefaultScreen(display), attributes); if (Ivinfo) { /* * Если нет доступных режимов с обычной буферизацией, * пробуем режим с двойной... * / attributes[9] = GLX_DOUBLEBUFFER; vinfo = glXChooseVisual(display, Defaultscreen(display), attributes); } if (ivinfo) ( puts("No OpenGL visual available!"); return (1); ) /* * Создаем пиксельное отображение... */ pixmap = XCreatePixmap(display, DefaultRootWindow(display), 1024, 1024, vinfo->depth); glxpixmap = glXCreateGLXPixmap(display, vinfo, pixmap); /* * Создается контекст OpenGL... */ context = glXCreateContext(display, vinfo, 0, False); glXMakeCurrent(display, glxpixmap, context); * Устанавливаем оставшиеся глобальные переменные... CubeWidth = 1024; CubeHeight = 1024; CubeRotation[0] = 45.Of; CubeRotation[1] = 45.Of; CubeRotation[2] = 45.0f; CubeRatefO] = l.Of; CubeRate[l] = l.Of; CubeRate[2] = l.Of; * Рисуем куб...
774 Часть II. OpenGL повсюду DisplayFunc(); /* * Считываем RGB-пиксели и записываем результат как файл PPM */ if ((fр = fopen("glxpixmap.ppm", "wb")) == NULL) perror("Невозможно создать glxpixmap.ppm"); else { /* * Записывает изображение PPM сверху вниз... */ fputs("Рб{\)п1024 1024 255{\}n", fp); for (y = 1023; у >= 0; у —) { glReadPixels(0, у, 1024, 1, GL_RGB, GL_UNSIGNED_BYTE pixels); fwrite(pixels, 1024, 3, fp); } fclose(fp); ) * Удаляем все использованные ресурсы и закрываем дисплей... */ glXDestroyContext(display, context); glXDestroyGLXPixmap(display, glxpixmap); XFreePixmap(display, pixmap); XCloseDisplay(display); return (0); ) * 'DisplayFunc()' - Рисуем куб */ void DisplayFunc(void) { int i, j; /* Переменные цикла */ static const GLfloat corners[8][3] = /* Угловые вершины */ ( { l.Of, l.Of, l.Of), /* Передняя правая верхняя */ { l.Of,-l.Of, l.Of), /* Передняя правая нижняя */ (-1.Of,-1.Of, l.Of), /* Передняя левая нижняя */ (-l.Of, l.Of, l.Of), /* Передняя левая верхняя */ { l.Of, l.Of,-l.Of), /* Задняя правая верхняя */ { l.Of,-l.Of,-l.Of), /* Задняя правая нижняя */ {-1.Of,-1.Of,-1.Of}, /* Задняя левая нижняя */ (-l.Of, l.Of,-l.Of) /* Задняя левая верхняя */
Глава 15 GLX OpenGL в системе Linux static const int sides[6][4] = /* Грани */ { 0, 1, 2, 3 }, /* Передняя *, 4, 5, 6, 7 }, /* Задняя */ 0, 1, 5, 4 }, /* Правая */ 2, 3, 7, 6 }, /* Левая */ 0, 3, 7, 4 ), /* Верхняя */ 1, 2, 6, 5 ) /* Нижняя */ }; static const GLfloat colors[6][3] = /* Цвета */ { l.Of, O.Of, O.Of }, /* Красный */ O.Of, l.Of, O.Of }, /* Зеленый */ l.Of, l.Of, O.Of }, /* Желтый */ O.Of, O.Of, l.Of ), /* Синий */ l.Of, O.Of, l.Of }, /* Пурпурный */ O.Of, l.Of, l.Of } /* Голубой */ }; /* * Очищаем окно... * / glViewport(0, 0, CubeWidth, CubeHeight); glClearColor(O.Of, O.Of, O.Of, O.Of); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); /* * Настройка матриц... * / glMatrixMode(GL_PROJECTION); glLoadldentity(); glOrtho(-2.Of, 2.Of, -2.Of * CubeHeight / CubeWidth, 2.Of * CubeHeight / CubeWidth, -2.Of, 2.Of); glMatrixMode(GL_MODELVIEW); glLoadldentity(); glRotatef(CubeRotation[0], l.Of, O.Of, O.Of); glRotatef(CubeRotation[1], O.Of, l.Of, O.Of); glRotatef(CubeRotation[2], O.Of, O.Of, l.Of); /* * Рисуем куб... */ glEnable(GL_DEPTH_TEST); glBegin(GL_QUADS); for (i = 0; i < 6; i ++) glColor3fv(colors[i]); for (j = 0; j < 4; 3 ++) glVertex3fv(corners[sides[1][j]]); } glEnd();
776 Часть II OpenGL повсюду Использование Р-буферов Второй тип закадровых буферов — Р-буферы — поддерживаются реализациями GLX 1 3 Вместо пиксельных образов X P-буферы используют видеопамять и уско- ряются аппаратно, что дает более быструю закадровую визуализацию. Тем не менее использование видеопамяти часто ограничивает максимальный размер P-буфер, кро- ме того, эти буферы поддерживаются не везде. В отличие от окон OpenGL и пиксельного отображения GLX при использовании P-буферов вы вместо вызова glXChooseVisual () начинаете с выбора конфигурации буфера кадров (функция glXChooseFBConfig()). Display *display; int nconfigs; GLXFBConfig *configs; static int attributes[] = { GLX_RGBA, GLX_RED_SIZE, 8, GLX_GREEN_SIZE, 8, GLX_BLUE_SIZE, 8, GLX_DEPTH_SIZE, 16, 0, /* Резервируем место для GLX_DOUBLEBUFFER */ 0 }; display = XOpenDisplay(getenv("DISPLAY")); configs = glXChooseFBConfig(display, Defaultscreen(display), attributes, &nconfigs); if (Iconfigs) { attributes[3] = GLX_DOUBLEBUFFER; configs = glXChooseFBConfig(display, Defaultscreen(display), attributes, &nconfigs); ) Получив список конфигураций буфера кадра, соответствующих запросу, с помо- щью функции glXCreatePbuffer () вы можете создать P-буфер. В качестве аргу- мента данная функция принимает переменную, задающую дисплей, конфигурацию буфера кадров и список атрибутов Р-буфера. GLXPbuffer pbuffer; static int pbattrsf] = { GLX_PBUFFER_WIDTH, 1024, GLX_PBUFFER_HEIGHT, 1024, 0 }; pbuffer = glXCreatePbuffer(display, *configs, pbattrs); Список атрибутов P-буфера состоит из значений GLX_PBUFFER_WIDTH и GLX_ PBUFFER_HEIGHT, задающих ширину и высоту Р-буфера Создав P-буфер, можно, основываясь на конфигурации буфера кадров Р-буфера,
Глава 15 GLX'OpenGL в системе Linux 777 вызвать функцию glXCreateNewContext () для формирования контекста (если тре- буется непрямой контекст визуализации, в качестве пятого параметра указывается значение True). GLXContext context; context = glXCreateNewContext(display, ‘configs, GLX_RGBA_BIT, 0, False); glXMakeCurrent(display, pbuffer, context); Далее можно рисовать в пиксельном отображении, используя функции OpenGL и считывая результаты с помощью функции glReadPixels (). В листинге 15.6 при- ведена версия предыдущей программы, создающая P-буфер, рисующая куб, считыва- ющая изображение с помощью glReadPixels () и записывающая результат в файл изображения PPM с названием pbuffer.ppm. Листинг 15.6. Пример Р-буфера * Включаем необходимые заголовки... */ #include <stdio.h> ♦include <stdlib.h> ♦include <X11/Xlib.h> ♦include <X11/Xatom.h> ♦include <GL/glx.h> ♦include <GL/gl.h> /* * Глобальные переменные... */ float CubeRotation[3], /* Вращение куба */ CubeRate[3]; /* Скорость вращения куба */ int CubeWidth, /* Ширина окна */ CubeHeight; /* Высота окна */ /* * Функции... */ void DisplayFunc(void); /* * 'main()' - Основная точка входа программы */ int /* Выход - состояние выхода */ main(int argc, /* Вход - число аргументов командной строки */ char *argv[]) /* Вход - аргументы командной строки*/ { GLXContext context; Display ‘display; GLXPbuffer pbuffer; int nconfigs; GLXFBConfig ‘configs; /* Контекст OpenGL */ /* Связь с дисплеем * системы X Window */ /* Р-буфер */ /* Число конфигураций */ /* Конфигурация буфера * кадров GLX */
778 Часть II OpenGL повсюду FILE *fp; /* Указатель файла PPM */ int у; /* Текущая строка */ unsigned char pixels[3072]; /* Одна строка RGB-пикселей */ static int attributes[] = /* Атрибуты OpenGL */ { GLX_RGBA, GLX_RED_SIZE, 8, GLX_GREEN_SIZE, 8, GLX_BLUE_SIZE, 8, GLX_DEPTH_SIZE, 16, 0, /* Резервируем место * для GLX_DOUBLEBUFFER */ 0 }; static int pbattrs[] = /* Атрибуты Р-буфера */ { GLX_PBUFFER_WIDTH, 1024, GLX_PBUFFER_HEIGHT, 1024, 0 ); /* * Открывается соединение с Х-сервером... */ display = XOpenDisplay(getenv("DISPLAY")); /* * Получаем конфигурацию буфера кадров, * соответствующую запросу... */ configs = glXChooseFBConfig(display, Defaultscreen(display), attributes, &nconfigs); if (Jeonfigs) { attributes[3] = GLX_DOUBLEBUFFER; configs=glXChooseFBConfig(display, Defaultscreen(display), attributes, &nconfigs); } if (!configs) { puts("No OpenGL framebuffer configurations available!"); return (1); ) /* * Создаем Р-буфер... */ pbuffer = glXCreatePbuffer(display, ‘configs, pbattrs); /* * Создается контекст OpenGL... */ context = glXCreateNewContext(display, ‘configs, GLX_RGBA_BIT, 0, True); glXMakeCurrent(display, pbuffer, context); /* * Устанавливаем остальные глобальные переменные ...
Глава 15 GLX' OpenGL в системе Linux 779 CubeWidth = 1024; CubeHeight = 1024; CubeRotation[0] = 45.Of; CubeRotation!1] = 45.Of; CubeRotation[2] = 45.Of; CubeRate[0] = l.Of; CubeRatefl] = l.Of; CubeRate[2] = l.Of; /* * Рисуем куб ... */ DisplayFunc(); /* * Считываем RGB-пиксели и записываем результат как файл PPM * / if ((fp = fopen("pbuffer.ppm", "wb")) == NULL) perror("Невозможно создать pbuffer.ppm"); else { /* * Записывает изображение PPM сверху вниз... fputs("Рб(\}nl024 1024 255{\)n", fp); for (y = 1023; у >= 0; у —) { glReadPixels!0, у, 1024, 1, GL_RGB, GL_UNSIGNED_BYTE, pixels); fwrite(pixels, 1024, 3, fp); ) fclose(fp); ) /* * Удаляем все использованные ресурсы и закрываем дисплей... */ glXDestroyContext(display, context); glXDestroyPbuffer(display, pbuffer); XCloseDisplay(display); return (0); } /* * 'DisplayFunc()' - Рисуем куб */ void DisplayFunc(void) { int i, j; /* Переменные цикла */ static const GLfloat corners[8][3] = /* Угловые вершины */ ( { l.Of, l.Of, l.Of), /* Передняя правая верхняя */ { l.Of,-l.Of, l.Of}, /* Передняя правая нижняя */
780 Часть II OpenGL повсюду {-l.Of,-l.Of, l.Of), /* Передняя левая нижняя * {-l.Of, l.Of, l.Of), /* Передняя левая верхняя { l.Of, l.Of,-l.Of}, /* Задняя правая верхняя * { 1.Of,-1.Of,-1.Of}, /* Задняя правая нижняя */ {-1.Of,-1.Of,-1.Of}, /* Задняя левая нижняя */ {-l.Of, l.Of,-l.Of} /* Задняя левая верхняя */ }; static const int sides[6][4] = /* Грани */ { 0, 1, 2, 3 }, /* Передняя */ { 4, 5, 6, 7 : }, /* Задняя */ { 0, 1, 5, 4 : }, /* Правая */ { 2, 3, 7, 6 }, /* Левая */ { 0, 3, 7, 4 }, /* Верхняя */ { 1, 2, 6, 5 : } /* Нижняя */ 1 t static const GLfloat colors[6][3] = /* Цвета */ { l.Of, O.Of, O.Of }, /* Красный */ { O.Of, l.Of, O.Of ), /* Зеленый */ { l.Of, l.Of, O.Of }, /* Желтый */ { O.Of, O.Of, l.Of }, /* Синий */ { l.Of, O.Of, l.Of ), /* Пурпурный */ { O.Of, l.Of, l.Of } /* Голубой */ /* * Очищаем окно glViewport(0, 0, CubeWidth, CubeHeight); glClearColor(0.Of, O.Of, O.Of, O.Of); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); * Настройка матриц... */ glMatrixMode(GL_PROJECTION); glLoadldentity(); glOrtho(-2.Of, 2.Of, -2.Of * CubeHeight / CubeWidth, 2.Of * CubeHeight / CubeWidth, -2.Of, 2.Of); glMatrixMode(GL_MODELVIEW); glLoad!dentity(); glRotatef(CubeRotation[0], l.Of, O.Of, O.Of); glRotatef(CubeRotation[1], O.Of, l.Of, O.Of); glRotatef(CubeRotation[2], O.Of, O.Of, l.Of); /* * Рисуем куб... */ glEnable(GL_DEPTH_TEST); glBegin(GL_QUADS); for (i =0; i < 6; i ++) { glColor3fv(colors[i]);
Глава 15. GLX- OpenGL в системе Linux 781 for (j = 0; j < 4; j ++) glVertex3fv(corners[sides[i][j]]); } glEnd(); } Использование библиотеки Motif Библиотека Motif — один из старейших наборов инструментов, используемых в UNIX/Lmux, и она все еще является стандартом в приложениях, разрабатываемых исключительно для коммерческих версий UNIX. Motif основана на библиотеке X 1п- trinsics (Xt), которая обеспечивает базовую поддержку нескольких других наборов инструментов, например Athena (Xaw), 3D Athena (Xaw3d) и neXtaW. Существует два элемента управления OpenGL: один предназначен для общих на- боров инструментов, основанных на Xt, и называется GLwDrawingArea, другой инте- грирован с Motif и называется GLwMDrawingArea. По функциональным возможностя- ми оба равны, поэтому в примерах мы будем использовать только набор инструментов Motif и элемент управления GLwMDrawingArea. Элементы управления OpenGL поставляются в отдельной библиотеке, назван- ной GLw. Чтобы включить их в приложение, используется команда связывания -IGLw. Типичная команда компоновки приложения OpenGL, основанного на Mo- tif, выглядит так. дсс -о myprogram myprogram.о -IGLw -1GL -IXm -IXt -IXext -1X11 GLwDrawingArea И GLwMDrawingArea: Элементы управления OpenGL Подобно большинству элементам управления Motif и Xt элементы OpenGL использу- ют имена файлов заголовков, имеющие вид аббревиатур. Включаемый файл для об- щего элемента управления GlwDrawingArea — <GL/GLwDrawA. h>, а элемент управ- ления Motif — <GL/GLwMDrawA. h>. Включив подходящий заголовочный файл, ис- пользуйте функцию XtVaCreateManagedWidget для создания элемента управления OpenGL widget = XtVaCreateManagedWidget( "name", glwMDrawingAreaWidgetClass, parent, GLwNrgba, True, GLwNdoublebuffer, True, GLwNdepthSize, 16, ... и другие требуемые аргументы ресурсов ... NULL); Первый аргумент - это имя ресурса элемента управления, его можно использовать, чтобы соотнести с элементом управления дополнительные ресурсы (в том числе ресурсы OpenGL).
782 Часть II OpenGL повсюду ТАБЛИЦА 15.2. Ресурсы элементов управления OpenGL Имя ресурса Тип ресурса Соответствующий атрибут GLX из табл. 15.1 GLwNaccumAlphaSize int GLX_ACCUM_ALPHA_SIZE GLwNaccumBlueSize int GLX_ACCUM_BLUE_SIZE GLwNaccumGreenSize int GLX_ACCUM_GREEN_SIZE GLwNaccumRedSize int GLX_ACCUM_RED_SIZE GLwNalphaSize int GLX_ALPHA_SIZE GLwNattribList int Нет Непосредственно задает список атрибутов GLX GLwNauxBuffers boolean GLX_AUX_BUFFERS GLwNblueSize int GLX_BLUE_SIZE GLwNbufferSize int GLX_BUFFER_SIZE GLwNdepthSize int GLX_DEPTH_SIZE GLwNdoublebuffer boolean GLX_DOUBLEBUFFER GLwNgreenSize int GLX_GREEN_SIZE GLwNlevel int GLXJLEVEL GLwNredSize int GLX_RED_SIZE GLwNrgba boolean GLX_RGBA GLwNstencilSize int GLX_STENCIL_SIZE GLwNstereo boolean GLX_STEREO Второй аргумент задает класс элемента управления; glwMDrawingAreaWidget- Class задает элемент управления OpenGL Motif. Для общего элемента управления OpenGL Xt используйте glwDrawingAreaWidgetClass. Третий аргумент задает родительский элемент управления, которым обычно явля- ется управляющий элемент (такой, как XmForm). Остальные аргументы задают ресурсы элемента управления в форме пар “имя/значение”, список аргументов завершается указателем NULL. Как правило, при- ложения Motif и Xt используют для конфигурации элемента управления и файлов ресурса жестко закодированные ресурсы, а для меток и основных настроек внешнего вида и поведения — ресурсы нейтрализации неисправностей. В этом случае задаются визуальные атрибуты OpenGL, так что в RGB-режиме с двойной буферизацией (True- Color) для рисования будет использован правильный режим визуализации. Доступные ресурсы элементов управления OpenGL перечислены в табл. 15 2. Большинство из них прямо соответствует атрибутам GLX и используются для построения списка атри- бутов GLX. Ресурс GLwNattribList непосредственно задает список атрибутов GLX. Обратные вызовы Создав элемент управления, вы должны соотнести с одной или несколькими функ- циями обратного вызова обратные вызовы. Вызовы, определенные для элементов управления OpenGL, перечислены в табл. 15 3 Обратные вызовы элементов управления OpenGL принимают три аргумента- ука- затель элемента управления окном, указатель пользовательских данных и указатель на структуру данных GLwDrawingAreaCallbackStruct. Элементы этой структуры перечислены в табл. 15 4. Для обработки всех типов обратных вызовов с помощью одной функции можно использовать элемент reason
Глава 15 GLX. OpenGL в системе Linux 783 ТАБЛИЦА 15.3. Ресурсы обратных вызовов элементов управления OpenGL Имя ресурса Описание GLwNexposeCallback GLwNginitCallback GLwNinputCallback GLwNresizeCallback Обратный вызов перерисовывания Обратный вызов инициализации Обратный вызов ввода мышью или с клавиатуры Обратный вызов изменения размера элемента управления окном ТАБЛИЦА 15.4. Элементы GLwDrawingAreaCallbackStruct Имя Тип Описание event XEvent* Событие X Window, соотнесенное с обратным вызовом ввода или действия height Dimension Новая высота элемента управления при обратных вызовах действия и изменения размера reason int Причина обратного вызова GLwCR_EXPOSE, GLwCR_GINlT, GLwCR_INPUT ИЛИ GLwCR_RESIZE width Dimension Новая ширина элемента управления при обратных вызовах действия и изменения размера Обратный вызов GLwNexposeCallback Когда менеджер окна сообщает о том, что весь элемент или его часть были мо- дифицированы и требуют перерисовки, за необходимые действия отвечает функция обратного вызова GLwNexposeCallback. Как правило, данная функция устанавливает текущий контекст OpenGL и рисует в элементе управления окном. Компоненты width и height структуры обратного вы- зова содержат текущую ширину и высоту элемента управления, а компонент member содержит данные о событиях X Window, на основании которых можно определить, последуют ли далее дополнительные события или перерисовка ограничивается толь- ко небольшой областью. Обратный вызов GLwNginitCallback Функция обратного вызова GLwNginitCallback обрабатывает все действия, связан- ные с инициализацией элемента управления OpenGL. Обычно эта функция создает контекст OpenGL, загружает текстуры и шрифты, а также инициализирует таблицы отображения для общих элементов дисплея. Чтобы создать контекст OpenGL, в обратном вызове можно запросить ресурс GLwNvisuallnfo. В приведенном ниже коде иллюстрируется создание контекста OpenGL с использованием значения resource. Widget application_shell; Widget drawing_area; XVisuallnfo *info; GLXcontext context; XtVaGetValues(drawing_area, GLwNvisuallnfo, &info, NULL); context = glXCreateContext(XtDisplay(application_shell), info, NULL, GL_TRUE);
784 Часть II. OpenGL повсюду Данный ресурс и окно OpenGL для элемента управления не создаются, пока не будет вызвана функция обратного вызова. Обратный вызов GLwNinputCallback. Функция обратного вызова GLwNinputCallback обрабатывает пользовательский ввод в форме нажатия клавиш, движения мыши и взаимодействия с клавиатурой. Компонент event функции обратного вызова указывает на событие ButtonPress, ButtonRelease, MotionNotify, KeyPress или KeyRelease, инициирующее обрат- ный вызов. Обратный вызов GLwNresizeCallback Функция обратного вызова GLwNresizeCallback вызывается, когда приложение или пользователь изменяют размеры элемента управления окном OpenGL. Компоненты width и height функции обратного вызова содержат новую ширину и высоту эле- мента управления окном, и их можно использовать для отслеживания изменений размера элемента. Обычно за обратным вызовом изменения размера следует вызов перерисовывания, несущий такую же информацию, поэтому во многих приложениях обратный вызов изменения размера не требуется Функции Библиотека GLw предлагает две вспомогательных функции, работающие с обоими элементами управления окнами OpenGL Функция GLwDrawingAreaMakeCurrent () устанавливает текущий контекст OpenGL для элемента, и ее следует вызывать до рисования этого элемента. GLwDrawingAreaMakeCurrent(drawing_area, context); Рисуя в элементе управления с двойной буферизацией, не забудьте вызвать функцию GLwDrawingAreaSwapBuffers() для переключения переднего и задне- го буферов GLwDrawingAreaSwapBuffers(drawing_area); Собираем все вместе В листинге 15 7 приведена Motif-версия рассмотренного в листинге 15.4 примера xhbfonts, дающая такой же результат. Вначале программа создает “оболочку прило- жения” Xt, включающую основное окно Затем она добавляет элемент Motif XmForm, управляющий элементом OpenGL, и сам элемент управления OpenGL. Widget Cubeshell; XtAppContext CubeContext; Widget CubeGLArea; Widget form; XtAppContext context; static char *fallback[] = /* Оболочка приложения */ /* Контекст приложения */ /* Область рисования OpenGL */ /* Элемент управления формой */ /* Контекст приложения */ /* Ресурсы нейтрализации неисправностей */
Глава 15 GLX: OpenGL в системе Linux 785 "Motif.geometry: 400x400", NULL Инициализация окна приложения и элементов управления окном... CubeShell = XtVaAppInitialize( &CubeContext, "Motif", NULL, 0, &argc, argv, fallback, XmNtitle, "Motif Example", XmNiconName, "Motif", NULL); form = XtVaCreateManagedWidget( "form", xmFormWidgetClass, CubeShell, NULL); * Создаем область рисования OpenGL... True, True, XmATTACH_FORM, XmATTACH_FORM, XmATTACH_FORM, XmATTACH_FORM, CubeGLArea = XtVaCreateManagedWidget( "drawingArea", glwMDrawingAreaWidgetClass, form, GLwNrgba, GLwNdoublebuffer, GLwNdepthSize, XmNtopAttachment, XmNbottomAttachment, XmNleftAttachment, XmNrightAttachment, NULL); Элемент управления OpenGL присоединяется к боковым сторонам формы, зани- мая, таким образом, все окно В типичном приложении со строкой меню удобнее соединить верх элемента управления OpenGL со строкой меню. После создания элементов управления устанавливаются функции обратного вызо- ва, которые будут использоваться с элементами управления OpenGL XtAddCallback(CubeGLArea, GLwNexposeCallback, (XtCallbackProc)DisplayCB, NULL) ; XtAddCallback(CubeGLArea, GLwNginitCallback, (XtCallbackProc)InitCB, NULL); XtAddCallback(CubeGLArea, GLwNresizeCallback, (XtCallbackProc)ReshapeCB, NULL); XtAddCallback(CubeGLArea, GLwNinputCallback, (XtCallbackProc)InputCB, NULL); Наконец, вы “реализуете” оболочку приложения, чтобы отобразить окно, и вызы- ваете функцию XtAppMainLoop (), чтобы начать цикл событий приложения- XtRealizeWidget(CubeShell); XtAppMainLoop(CubeContext);
786 Часть II OpenGL повсюду Функции обратного вызова используют те же функции, что описывались в примере xlibfonts, для инициализации контекста OpenGL и шрифта, рисования куба и текста, а также для обработки ввода с помощью мыши. Для поворота куба каждые 20 мс используется новая функция TimeOutCB() (регистрируется посредством функции XtAppAddTimeOut ()). void TimeOutCB(void) { CubeRotation[0] += CubeRate[0]; CubeRotation[1] += CubeRate[1]; CubeRotation[2] += CubeRate[2]; if (CubeRate[0] || CubeRate[l] II CubeRate[2]) XmRedisplayWidget(CubeGLArea); XtAppAddTimeOut(CubeContext, 20, (XtTimerCallbackProc)TimeOutCB, NULL); } Функция XmRedisplayWidget () указывает элементу OpenGL перерисовать себя, и приложение может использовать ее для обновления отображенных на дисплее дан- ных, основываясь на новых (возможно, асинхронных) данных или пользовательском вводе. Листинг 15.7. Исходный код программы Motif * Включаем необходимые заголовки... #include <stdio.h> ♦include <stdlib.h> ♦include <Xm/Xm.h> ♦include <Xm/Form.h> ♦include <GL/GLwMDrawA.h> /* * Глобальные переменные... */ float CubeRotation[3], CubeRate[3]; int CubeMouseButton, CubeMouseX, CubeMouseY; int CubeWidth, CubeHeight; GLuint CubeFont; Widget Cubeshell; XtAppContext CubeContext; Widget CubeGLArea; GLXContext CubeGLContext; /* Вращение куба */ /* Скорость вращения куба */ /* Нажатая кнопка */ /* Координата X начального * положения курсора мыши */ /* Координата Y начального * положения курсора мыши */ /* Ширина окна */ /* Высота окна */ /* Таблицы отображения, на основе * которых формируется шрифт */ /* Оболочка приложения */ /* Контекст приложения */ /* Область рисования OpenGL */ /* Контекст рисования OpenGL */
Глава 15 GLX- OpenGL в системе Linux 787 * Функции... * / void DisplayCB(void); void InitCB(Widget w, void *ud, GLwDrawingAreaCallbackStruct *cd); void InputCB(Widget w, void *ud, GLwDrawingAreaCallbackStruct *cd); void ReshapeCB(Widget w, void *ud, GLwDrawingAreaCallbackStruct *cd); void TimeOutCB(void); /* * 'main()' ~ Основная точка входа программы */ int /* Выход - состояние выхода */ main(int argc, /* Вход - число аргументов командной строки */ char *argv[]) /* Вход - аргументы командной строки */ { Widget form; /* Элемент управления формой */ XtAppContext context; /* Контекст приложения */ static char *fallback[] = /* Ресурсы нейтрализации неисправностей */ { "Motif.geometry: 400x400", NULL }; /* * Инициализация окна приложения и элементов управления... */ CubeShell = XtVaAppInitialize( «.CubeContext, "Motif", NULL, 0, &argc, argv, fallback, XmNtitle, "Motif Example", XmNiconName, "Motif", NULL); form = XtVaCreateManagedWidget( "form", xmFormWidgetClass, CubeShell, NULL); * Создаем область рисования OpenGL... */ CubeGLArea = XtVaCreateManagedWidget( "drawingArea", glwMDrawingAreaWidgetClass, form, GLwNrgba, True, GLwNdoublebuffer, True, GLwNdepthSize, 16, XmNtopAttachment, XmATTACH_FORM, XmNbottomAttachment, XmATTACH_FORM,
788 Часть II. OpenGL повсюду XmNleftAttachment, XmATTACH_FORM, XmNrightAttachment, XmATTACH_FORM, NULL); /* * Устанавливаем обратные вызовы и время ожидания... */ XtAddCallback(CubeGLArea, GLwNexposeCallback, (XtCallbackProc)DisplayCB, NULL); XtAddCallback(CubeGLArea, GLwNginitCallback, (XtCallbackProc)InitCB, NULL); XtAddCallback(CubeGLArea, GLwNresizeCallback, (XtCallbackProc)ReshapeCB, NULL); XtAddCallback(CubeGLArea, GLwNinputCallback, (XtCallbackProc)InputCB, NULL); /* * Устанавливаем оставшиеся глобальные переменные... */ CubeWidth = 400; CubeHeight = 400; CubeRotation[0] = 45.Of; CubeRotationfl] = 45.Of; CubeRotation[2] = 45.0f; CubeRate{0] = l.Of; CubeRate[l] = l.Of; CubeRate[2] = l.Of; /* * Бесконечный цикл... */ XtRealizeWidget(CubeShell); XtAppMainLoop(CubeContext); return (0); * 1DisplayCB()' - Обратный вызов дисплея */ void DisplayCB(void) { int i, j; /* Переменные цикла */ XVisuallnfo *info; /* Визуальный режим области рисования */ XFontStruct *font; /* Информация по шрифту X */ static const GLfloat corners[8][3] = /* Угловые вершины */ { { l.Of, l.Of, l.Of), /* Передняя правая верхняя */ { l.Of,-l.Of, l.Of), /* Передняя правая нижняя */ {-1.Of,-1.Of, l.Of), /* Передняя левая нижняя */ {-l.Of, l.Of, l.Of), /* Передняя левая верхняя */ { l.Of, l.Of,-l.Of), /* Задняя правая верхняя */ { 1.Of,-1.Of,-1.Of), /* Задняя правая нижняя */ {-1.Of,-1.Of,-1.Of}, /* Задняя левая нижняя */ {-l.Of, l.Of,-l.Of) /* Задняя левая верхняя */
Глава 15 GLX- OpenGL в системе Linux static const int sides[6][4] = /* Грани */ { 0, 1, 2, 3 }, /* Передняя */ { 4, 5, 6, 7 }, /* Задняя */ { 0, 1, 5, 4 ), /* Правая */ { 2, 3, 7, 6 }, /* Левая */ { 0, 3, 7, 4 }, /* Верхняя */ ( 1, 2, 6, 5 } /* Нижняя */ ): static const GLfloat colors[6][3] = /* Цвета */ { l.Of, O.Of, O.Of }, /* Красный */ { O.Of, l.Of, O.Of }, /* Зеленый */ { l.Of, l.Of, O.Of }, /* Желтый */ { O.Of, O.Of, l.Of }, /* Синий */ { l.Of, O.Of, l.Of }, /* Пурпурный */ { O.Of, l.Of, l.Of } ); /* /* Голубой */ * Очищаем окно ... */ glViewport(0, 0, CubeWidth, CubeHeight); glClearColor(O.Of, O.Of, O.Of, O.Of); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); * Настройка матриц ... */ glMatrixMode(GL_PROJECTION); glLoadldentity(); glOrtho(-2.Of, 2.Of, -2.Of * CubeHeight / CubeWidth, 2.Of * CubeHeight / CubeWidth, -2.Of, 2.Of); glMatrixMode(GL_MODELVIEW); glLoadldentity(); glRotatef(CubeRotation[0], l.Of, O.Of, O.Of); glRotatef(CubeRotation[l], O.Of, l.Of, O.Of); glRotatef(CubeRotation[2], O.Of, O.Of, l.Of); /* * Рисуем куб ... */ glEnable(GL_DEPTH_TEST); glBegin(GL_QUADS); for (i = 0; i < 6; 1 ++) { glColor3fv(colors[i]); for (j = 0; ] < 4; j ++) glVertex3fv(corners[sides[i][j]]); } glEnd(); /* * Рисуем линии, выходящие из куба ... */ glColor3f(l.Of, l.Of, l.Of); glBegin(GL_LINES);
790 Часть II. OpenGL повсюду glVertex3f(O.Of, O.Of, -1.5f); glVertex3f(O.Of, O.Of, 1.5f); glVertex3f(-1.5f, O.Of, O.Of); glVertex3f(1.5f, O.Of, O.Of); glVertex3f(O.Of, 1.5f, O.Of); glVertex3f(O.Of, -1.5f, O.Of); glEnd(); /* * Формируем текст на каждой стороне ... * / glPushAttrib(GL_LIST_BIT); glListBase(CubeFont - ' '); glRasterPos3f(O.Of, O.Of, -1.5f); glCallLists(4, GL_BYTE, "Back"); glRasterPos3f(O.Of, O.Of, 1.5f); glCallLists(5, GL_BYTE, "Front"); glRasterPos3f(-1.5f, O.Of, O.Of); glCallLists(4, GL_BYTE, "Left"); glRasterPos3f(1.5f, O.Of, O.Of); glCallLists(5, GL_BYTE, "Right"); glRasterPos3f(O.Of, 1.5f, O.Of); glCallLists(3, GL_BYTE, "Top"); glRasterPos3f(O.Of, -1.5f, O.Of); glCallLists(6, GL_BYTE, "Bottom"); glPopAttrib() ; /* * Переключаем передний и задний буферы ... */ GLwDrawingAreaSwapBuffers(CubeGLArea); ) /* * 'InitCB()' - Обратный вызов инициализации */ void InitCB(Widget w, /* Вход - элемент управления окном */ void *ud, /* Вход - данные пользователя */ GLwDrawingAreaCallbackStruct *cd) /* Вход - данные обратного вызова */ { XVisuallnfo *info; /* Визуальный режим области рисования */ XFontStruct *font; /* Информация по шрифту X Window */ /* * Инициализируем контекст OpenGL для рисования ... */ XtVaGetValues(CubeGLArea, GLwNvisuallnfo, &info, NULL); CubeGLContext = glXCreateContext(XtDisplay(CubeShell), info, NULL, GL_TRUE); if (!CubeGLContext) { puts("Невозможно создать контекст OpenGL!"); exit(l); ) GLwDrawingAreaMakeCurrent(CubeGLArea, CubeGLContext);
Глава 15 GLX- OpenGL в системе Linux 791 * Настройка шрифта... * / font = XLoadQueryFont(XtDisplay(CubeShell), ”-*-courier-bold-r-normal—14-*-*-*-*-*-*-*"); CubeFont = glGenLists(96); glXUseXFont(font->fid, ' 96, CubeFont); /* * Активизируем процедуру времени ожидания для вращения куба... */ XtAppAddTimeOut(CubeContext, 20, (XtTimerCallbackProc)TimeOutCB, NULL); ) /* * 'InputCB()' - Обратный вызов ввода */ void InputCB(Widget w, /* Вход - элемент управления окном */ void *ud, /* Вход - данные пользователя */ GLwDrawingAreaCallbackStruct *cd) /* Вход - данные обратного вызова */ { int х, /* Координата X */ у; /* Координата Y */ switch (cd->event->type) { case ButtonPress : /* * Записываем исходные кнопку + положение мыши ... */ CubeMouseButton = cd->event->xbutton.button; CubeMouseX = cd->event->xbutton.x; CubeMouseY = cd->event->xbutton.y; * Обновление скоростей вращения... */ CubeRate[0] = O.Of; CubeRate[l] = O.Of; CubeRate[2] = O.Of; break; case MotionNotify : /* * Получаем движение мыши ... */ x = cd->event->xmotion.x - CubeMouseX; у = cd->event->xmotion.у - CubeMouseY; /* * Обновляем скорость вращения куба согласно движению * мыши и нажатой кнопке ... * / switch (CubeMouseButton)
792 Часть II OpenGL повсюду { case 0 : /* Кнопка 1 */ CubeRate[0] = O.Olf * у; CubeRate[1] = O.Olf * x; CubeRate[2] = O.Of; break; case 1 : /* Кнопка 2 */ CubeRate[0] = O.Of; CubeRate[1] = O.Olf * y; CubeRate[2] = O.Olf * x; break; default : /* Кнопка 3 */ CubeRate[0] = O.Olf * y; CubeRate[1] = 0 Of; CubeRate[2] = O.Olf * x; break; } break; } ) /* * 'ReshapeCB()' - Обратный вызов изменения размеров */ void ReshapeCB(Widget w, /* Вход - элемент управления окном */ void *ud, /* Вход - данные пользователя */ GLwDrawingAreaCallbackStruct *cd) /* Вход - данные обратного вызова */ { /* * Записываем текущую ширину и высоту ... */ CubeWidth = cd->width; CubeHeight = cd->height; } /* * 'TimeOutCB()' - Поворачиваем и перерисовываем куб TimeOutCB(void) { CubeRotation[0] += CubeRate[0]; CubeRotation[1] += CubeRate[1]; CubeRotation[2] += CubeRate[2]; if (CubeRate[0] || CubeRate[l] || CubeRate[2]) XmRedisplayWidget(CubeGLArea); XtAppAddTimeOut(CubeContext, 20, (XtTimerCallbackProc)TimeOutCB, NULL);
Глава 15 GLX OpenGL в системе Linux 793 Резюме В этой главе мы взяли основополагающие принципы OpenGL и расширили их для среды Linux. Вы узнали, как настраивать подходящий режим визуализации для при- ложения и как контексты визуализации обрабатываются в среде Linux Кроме того, теперь вы можете работать с контекстами с двойной буферизацией Вы узнали, как генерировать и использовать растровые шрифты Наконец, мы представили основы использования P-буферов для закадровой визуализации в системе Linux Справочная информация glXChooseFBConfig Цель: Получить список конфигураций буфер кадров, соответствующих заданной спецификации Включаемый файл: <GL/glx.h> Синтаксис: GLXFBConfig *glXChooseFBConfig(Display *dpy, int screen, const int *attribList, int *nelements); Описание: Находит ряд конфигураций буфера кадров, согласующихся с заданными атрибутами GLX Параметры: ★dp у screen ★attribList Связь с дисплеем системы X Window Запрашиваемый экран Список атрибутов GLX, заканчивающийся символом конца строки ★nelements Указатель на целочисленную константу, которая будет содержать количество найденных конфигураций буфера кадров Что возвращает: Указатель на массив подходящих конфигураций буфера кадров или NULL, если Х-дисплей не поддерживает запрос буфера кадров Чтобы освободить память, используемую массивом, применяется функция XFree () См. также: glXGetFBConfigs, glXGetVisualFromFBConfig, glXCreatePbuffer glXChooseVisual Цель: Выбрать визуальное представление системы X, используемое для визуализации OpenGL Включаемый файл: <GL/glx.h> Синтаксис: XVisuallnfo *glXChooseVisual(Display *dpy, int screen, int *attribList);
794 Часть II OpenGL повсюду Описание: Находит визуальное представление, согласующееся с заданными атрибутами визуализации GLX, которое можно использовать для создания окна или пиксельного отображения для визуализации OpenGL Параметры: *dpy screen ★attribList Что возвращает: Связь с дисплеем системы X Window Число экранов Список атрибутов, заканчивающийся символом конца строки Указатель на подходящую структуру с визуальной информацией или NULL См. также: glXCreateContext, glXCreateGLXPixmap glXCreateContext Цель: Создать контекст рисования OpenGL Включаемый файл: <GL/glx.h> Синтаксис: GLXContext glXCreateContext(Display *dpy, XVisuallnfo *vis, GLXContext shareList, Bool direct); Описание: Создает контекст для визуализации OpenGL. Контекст может быть прямо или непрямо связан с аппаратным обеспечением. Также он может совместно использовать таблицы отображения, текстуры и тому подобное с другими контекстами OpenGL такого же типа Параметры: *dpy ★vis shareList Связь с дисплеем системы X Window Используемое визуальное представление системы X Window Контекст OpenGL, с которым данный контекст будет совместно использовать таблицы отображения, текстуры и т.п direct TRUE, если желателен контекст, напрямую связанный с аппаратным обеспечением; False — в противном случае Что возвращает: Новый контекст OpenGL или NULL, если создать контекст невозможно См. также: glXChooseVisual, glXCreateNewContext glXCreateGLXPixmap Цель: Создать в заднем буфере пиксельный образ для визуализации OpenGL Включаемый файл: <GL/glx.h> Синтаксис: GLXPixmap glXCreateGLXPixmap(Display *dpy, XVisuallnfo *vis, Pixmap pixmap};
Глава 15 GLX OpenGL в системе Linux 795 Описание: Параметры: *dpy *vis pixmap Что возвращает: См. также: Создает пиксельный образ GLX, который можно использовать для закадровой визуализации сцен OpenGL. С пиксельными образами GLX можно использовать только непрямые контексты визуализации Связь с дисплеем системы X Window Используемое визуальное представление системы X Window Используемый пиксельный образ системы X Window Новый пиксельный образ GLX glXChooseVisual, glXCreateContext glXCreateNewContext Цель: Создать новый контекст OpenGL Включаемый файл: <GL/glx.h> Синтаксис: GLXContext glXCreateNewContext(Display *dpy, GLXFBConfig config, int renderType, GLXContext shareList, Bool direct); Описание: Параметры: Создает контекст для визуализации OpenGL и является функциональным эквивалентом glXCreateContext (). Контекст может быть прямо или непрямо связан с аппаратным обеспечением. Также он может совместно использовать таблицы отображения, текстуры и тому подобное с другими контекстами OpenGL такого же типа *dpy Связь с дисплеем системы X Window config Используемая конфигурация буфера кадров renderType Представление цвета в контексте. GLX_RGBA_TYPE — визуализация RGBA; GLX_COLOR_INDEX_TYPE — индексирование цвета shareList Контекст OpenGL, с которым данный контекст будет совместно использовать таблицы отображения, текстуры и т.п direct TRUE, если желателен контекст, напрямую связанный с аппаратным обеспечением, False — в противном случае Что возвращает: Новый контекст OpenGL или NULL, если создать контекст невозможно См. также: glXChooseFBConfig, glXCreateContext, glXDestroyContext, glXGetFBConfigs
796 Часть II OpenGL повсюду glXCreatePbuffer Цель: Создать закадровый буфер пикселей для визуализации OpenGL Включаемый файл: <GL/glx.h> Синтаксис: GLXPbuffer glXCreatePbuffer(Display *dpy, GLXFBCOnfig config, const int *attribList); Описание: Создает закадровый буфер пикселей для визуализации OpenGL. Размеры данного P-буфера задаются с помощью атрибутов GLX_WIDTH И GLX_HEIGHT Параметры: *dpy Связь с дисплеем системы X Window config Конфигурация буфера кадров ★attribList Список атрибутов GLX, заканчивающийся символом конца строки Что возвращает: Новый P-буфер, или null, если создать его невозможно См. также: glXGetFBConfigs, glXChooseFBConfigs, glXDestroyPbuffer gIXDestroyContext Цель: Удалить контекст OpenGL Включаемый файл: <GL/glx.h> Синтаксис: void gIXDestroyContext(Display *dpy, GLXContext ctx); Описание: Удаляет контекст визуализации OpenGL, освобождая все соотнесенные с ним системные ресурсы Параметры: ★dpy Связь с дисплеем системы X Window ctx Контекст OpenGL Что возвращает: Ничего См. также: glxCreateContext gIXDestroyGLXPixmap Цель: Удалить пиксельный образ GLX Включаемый файл: <GL/glx.h> Синтаксис: void glXDestroyGLXPixmap(Display *dpy, GLXPixmap pix) ; Описание: Удаляет ресурсы пиксельного образа GLX При этом отдельно нужно удалить ресурсы пиксельного образа системы X и контекст OpenGL
Глава 15 GLX-OpenGL в системе Linux 797 Параметры: *dpy pix Что возвращает: Связь с дисплеем системы X Window Пиксельный образ GLX Ничего См. также: glXCreateGLXPixmap gIXDestroyPbuffer Цель: Удалить закадровый пиксельный буфер Включаемый файл: <GL/glx.h> Синтаксис: void gIXDestroyPbuffer(Display *dpy, GLXPbuffer pbuf); Описание: Освобождает все ресурсы, используемые заданным Р-буфером Параметры: *dpy Связь с дисплеем системы X Window pbuf Р-буфер Что возвращает: Ничего См. также: glXCreatePbuffer glXGetFBConfigs Цель: Получить список поддерживаемых конфигураций буфера кадров Включаемый файл: <GL/glx.h> Синтаксис: GLXFBConfig *glXGetFBConfigs(Display *dpy, int screen, int *nelements); Описание: Получает список поддерживаемых конфигураций буфер кадров для заданного дисплея и экрана Параметры: *dpy Связь с дисплеем системы X Window screen Запрашиваемый экран *nelements Указатель на целочисленную константу, которая будет содержать количество необходимых конфигураций буфера кадров Что возвращает: Указатель на массив конфигураций буфера кадров или NULL, если дисплей не поддерживает запрос буфера кадров Для освобождения памяти, используемой массивом, применяется функция XFree () См. также: glXChooseFBCOnfig, glXGetVisualFromFBConfig, glXCreatePbuffer
798 Часть II. OpenGL повсюду glXGetVisualFromFBConfig Цель: Получить визуальное представление для указанной конфигурации буфера кадров Включаемый файл: <GL/glx.h> Синтаксис: XVisuallnfо ‘glXGetVisualFromFBConfig(Display *dpy, GLXFBConfig config); Описание: Находит режим визуализации, соответствующий данной конфигурации буфера кадров Параметры: ★dpy Связь с дисплеем системы X Window config Конфигурация буфера кадров Что возвращает: Указатель на структуру xvisuallnfo, содержащую всю визуальную информацию системы X для данной конфигурации буфера кадров См. также: glXGetFBConfigs, glXChooseFBConfigs, glXCreatePbuffer glXMakeCurrent Цель: Установить текущий контекст OpenGL для визуализации Включаемый файл: <GL/glx.h> Синтаксис: Bool glXMakeCurrent(Display *dpy, GLXDrawable Описание: drawable, GLXContext ctx); Устанавливает текущий контекст OpenGL и цель рисования Параметры: *dpy (окно или пиксельное отображение), используемую при визуализации. Если для предыдущего контекста существуют невыполненные команды рисования OpenGL, они выполняются до изменения текущего контекста. Чтобы получить режим рисования в окне, функции передается аргумент None; для выполнения незавершенных команд OpenGL и освобождения текущего контекста — аргумент NULL Связь с дисплеем системы X Window ctx Контекст OpenGL drawable Окно или пиксельный образ Что возвращает: TRUE, если контекст установлен успешно; False — См. также: в противном случае glXCreateContext, glXCreateNewContext, gIXSwapBuffers
Глава 15 GLX' OpenGL в системе Linux 799 gIXSwapBuffers Цель: Переключить передний и задний буферы дисплея Включаемый файл: <GL/glx.h> Синтаксис: void gIXSwapBuffers(Display *dpy, GLXDrawable drawable); Описание: Переключает задний буфер на передний, при необходимости выполняя синхронизацию обратного хода кадровой развертки Параметры: *dpy Связь с дисплеем системы X Window drawable Окно или пиксельный образ Что возвращает: Ничего См. также: glXCreateContext, glXCreateNewContext, glXMakeCurrent gIXUseXFont Цель: Включаемый файл: Синтаксис: Описание: Параметры: font first count listbase Что возвращает: См. также: Создать набор растровых таблиц отображения <GL/glx.h> void gIXUseXFont(Font font, int first, int count, int listbase); Создает count таблиц отображения, содержащих растровые изображения символов заданного шрифта. Для выделения таблиц отображения под растровые образы используется функция glGenLists () Задает используемый шрифт Задает первый используемый символ шрифта Задает число используемых символов шрифта Задает первую используемую таблицу отображения, возвращаемую функцией glGenLists () Ничего glXCreateContext

ЧАСТЬ III OpenGL: следующее поколение Теперь вы переходите к тому, что авторы считают самой впечатляющей частью книги, — а возможно, и самой впечатляющей разработкой в сфере трехмерной графики для ПК после создания аппаратного ускорения преобразований и освещения Трехмерный конвейер, который используется OpenGL, близок к стандартному конвейеру, применяющемуся при создании трехмерной графики в реальном времени, вне зависимости от используемого программного интерфейса приложения Тем не менее графические технологии эволюционировали до такого уровня, что структура конвейера полностью реализовала свой потенциал Действительно, технологии создания фотореалистичных изображений требуют гораздо большего числа операций на вершину или даже на фрагменты, находящиеся между вершинами. Чтобы повысить реалистичность и расширить графику за пределы возможностей стандартного конвейера, поставщики разрабатывают более гибкое аппаратное обеспечение и не только встраивают в него возможность обработки фиксированного набора команд и переменных состояния, но также реально расширяют графический код в блоках обработки графики (Graphics Processing Unit — GPU) Пожалуй, программируемый конвейер является одной из величайших разработок в сфере потребительской графики Один из производителей назвал его “Кинематографическими вычислениями” (“Cinematic Computing”), и это определение совсем неплохое Сейчас мы уже готовы получать в реальном времени фотореалистичные эффекты, создание которых ранее требовало часов или даже дней. И в этой новой области также есть место эволюционировавшему стандарту OpenGL В следующих главах рассмотрены новые изобретения в сфере трехмерной графики от управления памятью при обработке графики до расширенных возможностей буферов и, наконец, полнофункционального языка графического программирования, программы на котором выполняются на аппаратном уровне

ГЛАВА 16 Буферные объекты: это ваша видеопамять; используйте ее! Бенджамин Липчак Из ЭТОЙ ГЛАВЫ ВЫ УЗНАЕТЕ . . . Действие Функция Создать, связать и удалить буферные объекты Косвенно передать данные в буферный объект Записать данные прямо в буферный объект glGenBuffers/glBindBuffer/glDeleteBuffers glBufferData/glBufferSubData glMapBuffer/glUnmapBuffer Современные видеокарты имеют примерно столько же памяти, сколько и вся остальная система, в которой они установлены. Величина памяти видеокарты обыч- но составляет порядка величины системной памяти (скажем, 25%). Таким образом, имеется богатый ресурс, который можно успешно эксплуатировать или же растрачи- вать впустую. В видеопамяти традиционно хранятся следующие элементы. • Передние буферы (то, что вы видите на экране) • Задние буферы (то, что вы не видите при двойной буферизации) • Буферы глубины (требуются для удаления скрытых поверхностей) • Другие накопители информации о пикселях — плоскости трафарета, накладываю- щиеся плоскости и т.д. Даже при большом разрешении и насыщенности цвета, скажем, 1920 х 1280 и 32 бит на пиксель, графика занимает примерно от 25 до 40 Мбайт памяти. Сотнями оставшихся мегабайт памяти (в зависимости от доступной видеокарты) распоряжать- ся можете вы. Дополнительная память чаще всего используется для кэширования текстурных карт, чтобы их не требовалось постоянно передавать из системной памяти на ви- деокарту при каждом использовании новой текстуры. Вместо этого они локально содержатся в видеопамяти и доступны при необходимости. Когда кэш текстур запол- няется, старые (давно не использовавшиеся) текстуры, удаляются, чтобы освободить место для новых
804 Часть III OpenGL-следующее поколение Рис. 16.1. Разнообразные данные хранятся в локальной видеопамяти, бла- годаря чему блок обработки графики (Graphics Processing Unit — GPU) име- ет к ним быстрый доступ, те экономит на обращении к системной памяти Некоторые реализации OpenGL также пытаются кэшировать в видеопамяти гео- метрические данные, например информацию, представленную в таблицах отображе- ния или массивах вершин К сожалению, драйвер не знает, как часто будет меняться геометрия или сколько вообще будет геометрической информации Касательно масси- вов вершин он не знает, когда приложение действительно меняет данные в массивах Этой информацией владеет только приложение, поэтому, даже если драйвер попыта- ется что-либо сделать, максимум, на что он будет способен, — предположить, а этого совсем недостаточно для гарантирования оптимальной производительности. Чтобы попытаться предоставить драйверу часть указанной информации, разра- батывались различные расширения — GL_EXT_compiled_vertex_array, GL_EXT_ draw_range_elements, GL_NV_vertex_array_range и GL_ATI_vertex_array_ object Кульминацией этого процесса стало единое расширение GL_ARB_vertex_ buf fer_object, передававшее приложению полный контроль над хранением инфор- мации о геометрии в локальной видеопамяти с целью оптимизации визуализации. Данная возможность была включена в OpenGL 1 5 как один из элементов ядра. Различные типы данных, совместно использующих память видеокарты (фактиче- ски, соревнующихся за нее), показаны на рис 16 1 Для начала нужны массивы вершин Буферные объекты являются хранилищами данных в локальной видеопамяти Здесь можно записать все, что требуется, и извлечь эту информацию позже Если вы желаете записать здесь список бакалейных товаров, то можете это сделать Одна- ко единственной потетой вещью, которую стоит здесь хранить, являются массивы вершин и индексы массивов Вы можете проинформировать OpenGL, что масси-
Глава 16 Буферные объекты, это ваша видеопамять, используйте ее' 805 вы вершин помещены в буферном объекте, после чего их использование становит- ся поразительно быстрым Однако для начала нужны сами массивы вершин. Если приложение использу- ет непосредственный режим (пары glBegin/glEnd), вы не можете воспользоваться преимуществами буферных объектов, не вспомнив то, что говорилось о массиве вер- шин в главе 11, “Все о конвейере более быстрое прохождение геометрии” Имея рабочие массивы вершин, поместить их в буферные объекты относительно просто. Кроме того, так можно легко и точно сравнить производительность двух вариантов! В программе мы построим массивы вершин с геометрией сферического облака частиц Чем больше мы нагрузим геометрию, тем большее улучшение получим после ее ускорения Итак, займемся вершинами' Число частиц на сферу определяется конфигурацией Если используемая реали- зация OpenGL нс может обработать определенное здесь число сфер (или, наоборот, она проглотит их и потребует еще), — просто измените константу GLint numSphereVertices = 30000; Генерация сферического облака частиц Для этой программы потребуется немного геометрии, но мы не желаем использовать код для загрузки чего-то фантастического и не собираемся тратить время на его объяснение. Поэтому остановимся на том, что умеренно просто сгенерировать и что при этом достаточно интересно сфера, сформированная облаком частиц Итак, выше мы решили, сколько вершин нам требуется (задается константой в предыдущем разделе) Все, что нужно, — это случайным образом рассеять эти точки по поверхности сферы. Звучит сложно? Совсем нет1 Нужно всего лишь сге- нерировать случайную точку в пространстве Затем мы берем вектор, соединяющий начало координат (0,0,0) с этой точкой, и нормируем его, чтобы он стал единич- ным Теперь этот вектор представляет точку, находящуюся на расстоянии 1 от начала координат в каком-то случайном направлении. Повторяем эту процедуру 30 000 раз и получаем сферическое облако частиц. Соответствующий код приведен ниже. for (i =0; i < numSphereVertices; i++) { GLfloat rl, r2, r3, scaleFactor; // Выбираем случайный вектор rl = (GLfloat)(rand() - (RAND_MAX/2)); r2 = (GLfloat)(rand() - (RAND_MAX/2)); r3 = (GLfloat)(rand() - (RAND_MAX/2)); // Определяем нормировочный множитель scaleFactor = l.Of / sqrt(rl*rl + r2*r2 + r3*r3); sphereVertexArray[(i*3)+0] = rl * scaleFactor; sphereVertexArray[(i*3)+1] = r2 * scaleFactor; sphereVertexArray!(1*3)+2] = r3 * scaleFactor;
806 Часть III OpenGL: следующее поколение Рис. 16.2. Случайные векторы нормировкой переводятся на поверхность единичной сферы Активизация массивов вершин Итак, данные подготовлены. Теперь мы должны активизировать массивы и устано- вить указатели массивов, чтобы OpenGL знал, где при визуализации найти геометри- ческие объекты. glNormalPointer(GL_FLOAT, 0, sphereVertexArray); glVertexPointer(3, GL_FLOAT, 0, sphereVertexArray); glEnableClientState(GL_NORMAL_ARRAY); glEnableClientState(GL_VERTEX_ARRAY); Обратите внимание на то, что мы активизировали два массива: один — для поло- жений вершин, а другой — для нормалей вершин. Нормали требуются при расчете освещения, просто в нашем случае получилось так, что для единичной сферы (сферы с радиусом 1) с центром в начале координат положение вершины совпадает с по- ложением вектора нормали! Таким образом в этом конкретном случае мы можем использовать одинаковые данные в обоих массивах. Данные массива вершин визуально представлены на рис. 16 3. Побольше сфер, пожалуйста! Может показаться, что тридцать тысяч вершин — это очень много, но чтобы по- ставить реализацию OpenGL “на колени”, нам все же потребуется гораздо больше геометрических объектов. Поэтому нарисуем куб 3 х 3 х 3 из сфер, задав для них разные цвета. Как показано в листинге 16 1, чтобы по отдельности изменять размер и положение каждой сферы в промежуточных вызовах функции glDrawArrays, мы можем повторно использовать одни и те же массивы вершин, просто меняя матрицу наблюдения модели
Глава 16. Буферные объекты: это ваша видеопамять; используйте ее! 807 Рис. 16.3. Данные массива вершин аключают точки, которые также будут использоваться в качестве нормалей поверхности Листинг 16.1. Сферический массив вершин, нарисованный 27 раз // Вызывается для рисования сцены void RenderScene(void) { static GLTStopwatch stopwatch; static int frameCounter = 0; // Получаем исходное время if (frameCounter == 0) gltStopwatchReset(&stopWatch); frameCounter++; if (frameCounter == 100) { frameCounter = 0; fprintf(stdout, "FPS: %f\n", 100.Of / gltStopwatchRead(SstopWatch)); gltStopwatchReset(&stopWatch); ) // Отслеживаем угол камеры glMatrixMode(GL_PROJECTION); glLoadldentity(); gluPerspective(45.Of, l.Of, lO.Of, 10000.Of); glMatrixMode(GL_MODELVIEW); glLoadldentity(); gluLookAt(cameraPos[0], cameraPos[1], cameraPos[2], O.Of, O.Of, O.Of, O.Of, l.Of, O.Of); // Очищаем окно текущим цветом очистки glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if (animating) { RegenerateSphere(); SetRenderingMethod();
808 Часть III OpenGL следующее поколение } // Рисуем объекты на сцене DrawModels(); // Выводим команды рисования из стека glutSwapBuffers(); glutPostRedisplay(); } frameCounter++; if (frameCounter == 100) { long thisTime; frameCounter = 0; _ftime(&timeBuffer); thisTime = (timeBuffer.time * 1000) + timeBuffer.millitm; fprintf(stdout, "FPS: %f\n", 100.Of * 1000.Of / (thisTime - lastTime)); lastTime = thisTime; } // Отслеживаем угол камеры glMatrixMode(GL_PROJECTION); glLoadldentity(); gluPerspective(45.Of, l.Of, 10.Of, 10000.Of); glMatrixMode(GL_MODELVIEW); glLoadldentity(); gluLookAt(cameraPos[0], cameraPos[1], cameraPos[2], O.Of, O.Of, O.Of, O.Of, l.Of, O.Of); // Очищаем окно текущим цветом очистки glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) ; if (animating) { RegenerateSphere(); SetRenderingMethod(); // Рисуем объекты на сцене DrawModels(); // Выводим команды рисования из стека glutSwapBuffers(); glutPostRedisplay(); } Изучая данный код, следует отметить два момента, процедуру измерения про- изводительности и процедуру анимации Нам нужен способ тестирования динами- чески меняющейся геометрии, поэтому при включенной анимации мы генерируем новый массив вершины для каждого кадра Случайные анимированные точки выгля- дят как бы статическими Поскольку данная глава посвящена максимальному повышению производитель- ности процесса обработки геометрической информации, было бы неправильным не измерить каким-то образом эту производительность Поэтому для каждых 100 визуа- лизированных кадров мы находим время, затраченное на визуализацию Разделив 100
Глава 16 Буферные объекты это ваша видеопамять; используйте ее' 809 кадров на это время, мы получим грубую оценку числа визуализированных кадров за секунду. Это число позволяет сравнивать производительность процесса обработки массива вершин с производительностью процесса обработки буферного объекта По- скольку данная величина выводится в переменную stdout, се значение вы увидите в окне консоли, а нс в графическом окне программы Перемещение в буферные объекты Верите вы или нет, но самое трудное мы уже сделали, так как генерация или за- грузка данных массива вершин является тяжелым процессом. Все, что нам теперь нужно сделать, — указать OpenGL сохранить данные массива вершин внутри буфер- ного объекта Прежде чем заняться этим, нужно разобраться в одном вопросе Буферные объек- ты являются относительно новым элементом OpenGL Данная возможность впервые была введена как расширение GL_ARB_vertex_buf fer_obzject и стала функцией ядра вскоре после ратификации OpcnGL 1.5. Некоторые новые возможности, на- пример текстуры глубины и тени, легко интегрируются в приложения, поскольку все, что им требуется, — новые определения токенов в заголовочном файле К сожалению, чтобы заработали буферные объекты, нужно приложить немного больше усилий Эти объекты вводят новые точки входа API, в которых нужно разобраться, прежде чем вы сможете их применять Как и при использовании любой функции, вы вначале должны проверить до- ступность нужного расширения или соответствующей версии OpenGL и лишь затем использовать функцию В данном случае мы проверяем наличие либо OpcnGL 1 5, где используются буферные объекты, либо расширения, предлагающего эквивалентные функциональные возможности // Проверяем доступность требуемых функциональных возможностей! version = gIGetString(GL_VERSION); if ((version[0] == '1') && (version[l] == '.') && (version[2] >= '5') && (version[2] <= '9')) { glVersionl5 = GL_TRUE; 1 if (!glVersionl5 && !glt!sExtSupported("GL_ARB_vertex_buffer_oboect")) fprintf(stderr, "Neither OpenGL 1.5 nor " " GL_ARB_vertex_buffer_ob;)ect extension" " is available!\n") ; Sleep(2000); exit(0); } Теперь, когда мы знаем, что искомая возможность поддерживается, нужны ука- затели на функции, чтобы получить точки входа На платформах Windows функция wglGetProcAddress запрашивает указатели функций, основываясь на строке, со- держащей имя точки входа На других платформах реализованы другие средства создания таких указателей, поэтому далее мы абстрагируем их в одну библиотечную
810 Часть III. OpenGL: следующее поколение функцию gltGetExtensionPointer. Обратите внимание на то, что, если доступно только расширение, имена функций имеют суффикс ARB. Если доступен OpenGL 1.5, суффикс не требуется. // Загружаем указатели на функции if (glVersionl5) { glBindBuffer = gltGetExtensionPointer("glBindBuffer"); glBufferData = gltGetExtensionPointer("glBufferData"); glBufferSubData = gltGetExtensionPointer("glBufferSubData"); glDeleteBuffers = gltGetExtensionPointer("glDeleteBuffers"); glGenBuffers = gltGetExtensionPointer("glGenBuffers"); glMapBuffer = gltGetExtensionPointer("glMapBuffer"); glUnmapBuffer = gltGetExtensionPointer("glUnmapBuffer"); } else { glBindBuffer = gltGetExtensionPointer("glBindBufferARB"); glBufferData = gltGetExtensionPointer("glBufferDataARB"); glBufferSubData = gltGetExtensionPointer("glBufferSubDataARB"); glDeleteBuffers = gltGetExtensionPointer("glDeleteBuffersARB"); glGenBuffers = gltGetExtensionPointer("glGenBuffersARB"); glMapBuffer = gltGetExtensionPointer("glMapBufferARB"); glUnmapBuffer = gltGetExtensionPointer("glUnmapBufferARB"); } if (.'glBindBuffer || .'glBufferData || .'glDeleteBuffers || !glGenBuffers || !glMapBuffer || !glUnmapBuffer) { fprintf(stderr, "Not all entrypoints were available!\n") ; Sleep(2000); exit(0); Управление буферными объектами Буферные объекты трактуются подобно другим объектам OpenGL, например тек- стурным. Они создаются, их состояние инициализируется при первом связывании с командой glBindBuffer. Чтобы получить список доступных имен, вначале мож- но вызвать функцию glGenBuffers, но эта команда в действительности не создает буферные объекты. Кроме того, перед созданием нужно связать имя с объектом Буферные объекты, эксплуатация которых завершена, удаляются посредством функции glDeleteBuffers. Если на момент удаления буфер является связанным, связывание отменяется и неявно связывается нулевой буферный объект; таким об- разом мы указываем OpenGL вернуться к использованию традиционных массивов вершин (небуферных объектов). // Генерируем буферный объект glGenBuffers(1, &bufferID); glBindBuffer(GL_ARRAY_BUFFER, bufferlD); glDeleteBuffers(1, &bufferlD);
Глава 16 Буферные объекты это ваша видеопамять; используйте ее1 811 Визуализация с помощью буферных объектов Связывание буферного объекта указывает OpenGL извлекать информацию о массиве вершин из данных буферного объекта, не применяя набор указателей на массив вер- шин (с помощью команд, подобных glVertexPointer). Тем не менее такие указате- ли все же используются, однако теперь (когда буферный объект связан) они являются не указателями на данные в клиентской памяти, а интерпретируются как смещения внутри массива данных, записанных в буферном объекте. В программе с буферными объектами данные, используемые в качестве массива вершин и массива нормалей, начинаются сразу с первой позиции буферного объекта, поэтому указывается нулевое смещение. Поскольку данные упакованы плотно, без заполнения или чередования, параметр шага также равен нулю. glBindBuffer(GL_ARRAY_BUFFER, bufferlD); // Нулевой шаг, нулевое смещение gINormalPointer(GL_FLOAT, 0, 0); glVertexPointer(3, GL_FLOAT, 0, 0); glDrawArrays(GL_POINTS, 0, numSphereVertices); Загрузка данных в буферные объекты Выше мы описали, как создавать буферные объекты и указывать OpenGL исполь- зовать их в качестве источника информации о визуализируемой геометрии. Однако мы упустили важную часть “головоломки”: загрузку данных в буферный объект. Вначале буферный объект представляет собой пустое хранилище данных, поэтому нужно заполнить его, и только после этого он станет полезным. В следующих двух разделах мы и опишем два способа загрузки данных в хранилище под названием “буферный объект”. Копирование данных в буферный объект Первый вариант загрузки данных в буферный объект аналогичен используемому для загрузки данных текстурного изображения в текстурный объект Вы предоставляете функции glTexImage указатель на данные текселей, и OpenGL копирует их во внут- реннее хранилище текстур Если вы предоставите нулевой указатель, glTexImage со- здаст текстуру требуемого размера, только в этом случае тексели останутся неиници- ализированными. Если вы желаете переопределить фрагмент текстуры, то можете вы- звать функцию glTexSublmage, сообщив ей, где и сколько данных нужно заменить. Для буферных объектов процедура аналогична. Можно вызвать glBufferData и установить размер хранилища данных, указав, как организовать к нему доступ. Из предоставленного указателя копируются данные (если указатель не нулевой, — в таком случае данные остаются неинициализированными). Для переопределения части данных можно вызвать функцию glBufferSubData Точки входа glTexImage и glTexSublmage принимают параметр, указывающий заданную цель текстуры — GL_TEXTURE_2D или gl_TEXTURE_3D. Подобным образом glBufferData, glBufferSubData и другие точки входа буферных объектов также
812 Часть III OpenGL- следующее поколение принимают параметр цели. Эта “цель” определяет, с каким типом буферных объектов мы работаем, и значение этого параметра может быть равным GL_array_buffer или GL_ELEMENT_ARRAY_BUFFER Первый вариант используется для хранения данных массива вершин, включая информацию о цветах, нормалях, текстурных координатах и положениях Второй — для хранения индексов массива, используемых функцией glDrawElements Вернемся к нашей программе Если анимация отсутствует, мы просто создаем хра- нилище данных, вызывая glBufferData и указывая, что данные будут статичными Все размеры буферов изменяются в “основных машинных единицах”, или байтах glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * numSphereVertices * 3, sphereVertexArray, GL_STATIC_DRAW); Однако, если мы используем анимацию, нет нужды повторно создавать хранилище данных для каждого нового кадра анимации, учитывая то, что размер данных при этом остается неизменным Вместо этого мы создаем хранилище данных один раз, вступая в режим анимации с нулевым указателем (так что никакие данные пока нс скопирова- ны) и указывая, что данные будут потоковыми (используются лишь один раз). После этого для каждого кадра мы с помощью glBufferSubData обновляем информацию // Устанавливаем потоковый буферный объект // Данные будут загружаться при последующих вызовах функции // glBufferSubData glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * numSphereVertices * 3, NULL, GL_STREAM_DRAW); glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(GLfloat) * numSphereVertices * 3, sphereVertexArray); Подсказки по использованию, которые мы передаем функции glBufferData, со- ответствуют названию и являются подсказками Одни реализации OpcnGL совершен- но их игнорируют, тогда как другие могут основывать на них критичные решения, существенно влияющие на производительность буферных объектов Если данные ни- когда нс будут меняться, драйвер может поместить их в локальную видеопамять Если вы указываете, что данные — динамические, драйвер может поместить их туда, где обновлять их будет “дешевле”, например в память AGP Три возможные подсказки описаны в табл 16 1 Обратите внимание на то, что помимо вариантов DRAW подсказки имеют варианты READ и COPY, однако последние всего лишь зарезервированы для будущего исполь- зования расширениями, опирающимися на функциональные возможности буферных объектов В настоящее время единственной доступной моделью использования явля- ется рисование по данным буферных объектов Непосредственное отображение буферных объектов Описанный удачный непрямой метод копирования, используемый для загрузки бу- ферных объектов, похож на аналогичную парадигму, применяющуюся при обработке текстурных объектов. Однако данный метод требует, чтобы после закладки данных в клиентскую память OpcnGL скопировал их в память буферного объекта Возникает
Глава 16 Буферные объекты это ваша видеопамять, используйте ее1 813 ТАБЛИЦА 16.1. Подсказки по использованию буферных объектов Подсказка по использованию Описание GL_DYNAMIC_DRAW Данные, записанные в буферном объекте, скорее всего часто меняться не будут, но, вполне вероятно, несколько раз будут использоваться в качестве источника информации для рисования Подсказка сообщает OpenGL поместить данные туда, где однократное обновление не будет слишком болезненным GL_STATIC_DRAW Данные, записанные в буферном объекте, скорее всего часто меняться не будут, но, вполне вероятно, много раз будут использоваться в качестве источника информации для рисования Подсказка сообщает реализации поместить данные туда, где их будет удобно использовать для быстрого рисования, но обновление, возможно, не будет таким быстрым GL_STREAM_DRAW Данные, записанные в буферном объекте, скорее всего будут часто меняться и, вполне вероятно, будут использованы всего один раз (максимум два-три раза) Подсказка сообщает реализации, что эти данные срочные, например — анимированная геометрия, которая будет использована единожды, а затем изменится Важно, чтобы такие данные были помещены туда, где их можно быстро обновить, даже если это условие выполнится за счет быстрой визуализации вопрос- а можем ли мы убрать промежуточный этап и сгенерировать геометрические данные непосредственно в памяти буферного объекта9 Можем Соответствующая процедура называется отображением буферного объ- екта Вызывая функцию glMapBuffer, можно получить указатель на хранилище данных буферного объекта, отображенных в клиентское адресное пространство Это означает, что в нем можно выполнять запись, чтение — в общем делать все, что требуется Единственный момент вы не можете использовать эту память в качестве параметра источника или цели других точек входа OpenGL, например glTexImage, glLightfv, glDrawPixels и glReadPixels Пример отображения буферного объ- екта приводится ниже glBindBuffer(GL_ARRAY_BUFFER, bufferlD); // Предотвращаем освобождение конвейера при выполнении команды, // помечая хранилище данных буферного объекта как пустое glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * numSphereVertices * 3, NULL, animating ? GL_STREAM_DRAW : GL_STATIC_DRAW) ; sphereVertexArray = (GLfloat *)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY); Перед вызовом glMapBuffer вначале вызываем функцию glBufferData с нуле- вым указателем. Мы поступаем так даже в том случае, когда буферный объект уже создан, поскольку это помогает предотвратить снижение производительности при отображении. Если какой-то элемент конвейера еще использует старые данные из бу- ферного объекта, функция glMapBuffer должна дождаться очистки конвейера, после чего вернуть приложению указатель на отображенный буфер. (В противном случае приложение могло бы изменить данные, нужные предыдущим командам визуализа- ции, искажая, таким образом, визуализацию ) Очищая хранилище данных буферного
814 Часть III. OpenGL: следующее поколение ТАБЛИЦА 16.2. Режимы доступна к отображенным буферным объектам Режим доступа Описание GL_READ_ONLY Если менять данные не нужно, а просто требуется изучить информацию в хранилище данных или скопировать ее, полезно использовать режим “только чтение" В таком случае реализация попытается отобразить хранилище данных наиболее эффективно с точки зрения последующего считывания Попытки записи в хранилище отображенных данных скорее асего будут медленными или приведут к сбою приложения GL_READ_WRITE Если требуется записывать данные и считывать их, полезно использовать данный режим Такое отображение может быть менее эффективным, поскольку реализация потребует компромисса между отображением хранилища данных, эффективным с точки зрения чтения, и отображением, эффективным с точки зрения записи GL_WRITE_ONLY Если считывать данные не нужно, а требуется только менять их, следует использовать режим “только запись". Реализация попытается отобразить хранилище данных наиболее эффективно с точки зрения будущей записи Попытки чтения из хранилища данных скорее всего будут медленными или приведут к сбою приложения объекта с помощью нулевого указателя, мы сообщаем OpenGL, что любые данные, загруженные ранее в буферный объект, больше не требуются. После этого отобра- жение дает новое хранилище данных, исключая неявную синхронизацию конвейера, которая бы произошла в противном случае. Вызывая функцию glMapBuffer, необходимо предоставить метку доступа, сооб- щающую OpenGL, как вы будете обращаться к хранилищу данных буферного объекта после его отображения. Три возможных режима доступа описаны в табл. 16.2. Вы не можете начать рисование из буферного объекта, не инвертировав отобра- жение с помощью команды glUnmapBuffer. Эта команда позволяет OpenGL узнать, что вы внесли свои изменения в данные и готовы вернуть контроль над хранили- щем данных OpenGL. Однако во время отображения буферного объекта что-то может пойти не так. Вследствие различных системных событий (изменение видеорежима, переход в режим пониженного энергопотребления/пробуждение) буферный объект может перейти в такое состояние, когда его целостность не гарантируется. Возмож- но, была временно затребована память или в результате сбоя питания ее содержимое было повреждено и стало неопределенным. В таких редких случаях функция glUn- mapBuffer возвращает значение gl_false, означающее, что приложение отвечает за повторную поставку всех данных. К сожалению, это неизбежно, если вы хоти- те, чтобы приложение было устойчивым. В нашей программе все, что мы должны сделать, — это вернуться к процедуре генерации и повторить все действия. if (!glUnmapBuffer(GL_ARRAY_BUFFER)) { // Вследствие некоторого системного события данные превратились // в мусор... // Пробуем снова! RegenerateSphere();
Глава 16. Буферные объекты: это ваша видеопамять; используйте ее! 815 Рис. 16.4. Массив 3x3x3 сферических облаков в крошечном цветном кубе На рис. 16.4 показан результат выполнения кода, приведенного в листинге 16.2. Изображение выглядит одинаково для традиционного кода с массивами вершин и для кода с буферными объектами. Однако, посмотрев на количество кадров, отобража- емых за секунду, вы заметите существенную разницу. При использовании режима отображенных буферных объектов можно наблюдать повышение производительно- сти примерно от 25% до 200% (или больше, в зависимости от реализации OpenGL и числа задействованных вершин). Листинг 16.2. Код регенерации и загрузки данных массива вершин // Вызывается для регенерации точек на сфере void RegenerateSphere(void) { GLint i; if (mapBufferObject && useBufferObject) { // Удаляем память co старым массивом вершин if (sphereVertexArray) free(sphereVertexArray); glBindBuffer(GL_ARRAY_BUFFER, bufferlD); // Предотвращаем освобождение конвейера при выполнении // команды, помечая хранилище данных буферного объекта // как пустое glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * numSphereVertices * 3, NULL, animating ? GL_STREAM_DRAW : GL_STATIC_DRAW); sphereVertexArray = (GLfloat *)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
816 Часть III OpenGL следующее поколение else if (!sphereVertexArray) { // Теперь нужно вернуть память старого массива вершин sphereVertexArray = (GLfloat *)malloc(sizeof(GLfloat)* numSphereVertices * 3); if (!sphereVertexArray) { fprintf(stderr, "Unable to allocate memory for vertex arrays!"); Sleep(2000); exit(O); } } for (i =0; i < numSphereVertices; i++) { GLfloat rl, r2, r3, scaleFactor; // Выбираем случайный вектор rl = (GLfloat)(rand() - (RAND_MAX/2)); r2 = (GLfloat)(rand() - (RAND_MAX/2)); r3 = (GLfloat)(rand() - (RAND_MAX/2)); // Определяем нормировочный множитель scaleFactor = l.Of I sqrt(rl*rl + r2*r2 + r3*r3); sphereVertexArray[(1*3)+0] = rl * scaleFactor; sphereVertexArray!(i*3)+l] - r2 * scaleFactor; sphereVertexArray!(i*3)+2] = r3 * scaleFactor; ) if (mapBufferObject && useBufferObject) { if (!glUnmapBuffer(GL_ARRAY_BUFFER)) // Вследствие некоторого системного события данные // превратились в мусор... // Пробуем снова! RegenerateSphere(); sphereVertexArray = NULL; } } // Переключаемся между буферными объектами и старыми добрыми // массивами вершин void SetRenderingMethod(void) { if (useBufferObject) { glBindBuffer(GL_ARRAY_BUFFER, bufferlD); // Нулевой шаг, нулевое смещение gINormalPointer(GL_FLOAT, 0, 0); glVertexPointer(3, GL_FLOAT, 0, 0); if (!mapBufferObject) ! if (animating)
Глава 16 Буферные объекты это ваша видеопамять, используйте ее' 817 glBufferSubData(GL_ARRAY_BUFFER, О, sizeof(GLfloat) * numSphereVertices * 3, sphereVertexArray); } else { // Если анимация отсутствует, следующая процедура // вызывается один раз для установки нового // статичного буферного объекта glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * numSphereVertices * 3, sphereVertexArray, GL_STATIC_DRAW); ) } } '{ glBindBuffer(GL_ARRAY_BUFFER, 0); glNormalPointer(GL_FLOAT, 0, sphereVertexArray); glVertexPointer(3, GL_FLOAT, 0, sphereVertexArray); } Несказанное Разбирая программу, мы не коснулись нескольких моментов Один из них — запросы о состоянии Как и все состояния OpenGL, новое состояние, связанное с буферными объектами, может быть целью запроса Подробности см в справочном разделе, но здесь мы все же кратко расскажем о точках входа запроса о состоянии, которые вводятся буферными объектами. • glGetBufferParameteriv — Запрос подсказки об использовании буферного объекта, метки доступа, состояния и размера отображенного объекта • glGetBufferPointerv — Запрос отображенного адреса буферного объекта • glGetBuf ferSubData — Запрос данных из хранилища буферного объекта • gllsBuffer — Запрос информации о том, соответствует ли имя буферного объекта существующему буферному объекту В примере мы не использовали индексы массивов Преимуществом использова- ния gIDrawElements является устранение из массы вершин избыточной информа- ции Если некоторая вершина используется несколько раз (например, вершина, общая для нескольких треугольников), в массиве вершин ее нужно представить и обработать только один раз, но обращаться к ней можно многократно с помощью индексов масси- ва Наши сферы были образованы отдельными точками, каждая из которых использо- валась только один раз Поэтому использование индексов массива не дало бы никакого
818 Часть III OpenGL-следующее поколение преимущества. Однако, если вы собираетесь использовать их в своем приложении, знайте, что их также можно помещать в буферные объекты. Все, что при этом требу- ется, — использовать не цель gl_array_buffer, a gl_element_array_buffer. Резюме Реализации OpenGL традиционно рассматриваются с позиции их возможности эф- фективно получать от приложения информацию о геометрии и визуализировать ее Этот момент совсем не связан с техническими препятствиями. Просто приложение никак не может сообщить драйверу, насколько большой набор данных будет исполь- зоваться или как часто он будет обновляться, хотя при решении вопроса “где хранить данные” эта информация является критичной для драйвера. Проблема упиралась в отсутствии связи, и введение буферных объектов ее решило. Буферные объекты создаются и удаляются точно так же, как текстурные. Имеется возможность копирования данных в буферные объекты аналогично копированию тек- стурных объектов. Однако буферные объекты также предлагают мощный механизм отображения своей памяти в адресное пространство приложения, поэтому хранилище данных может обновляться, не требуя дополнительной операции копирования. Справочная информация gIBindBuffer Цель: Связать буферный объект Включаемый файл: <glext.h> Синтаксис: void gIBindBuffer(GLenum target, GLuint buffer); Описание: Связывает буферный объект с массивом вершин или другой целью. Если буферный объект не был связан ранее, при выполнении данной функции он создается. Последующие изменения цели или запросы цели повлияют на состояние связанного буферного объекта или вернут его в прежнее Параметры: target (тип GLenum) Тип данных, представляющих источник информации для буферного объекта. Значением может быть одна из следующих констант- GL_ARRAY_BUFFER: буферный объект будет хранить данные массива вершин gl_element_array_buffer: буферный объект будет хранить индексы массива buffer (тип GLuint) Что возвращает: Имя связываемого буферного объекта Ничего См. также: glGenBuffers, glDeleteBuffers, gllsBuffer
Глава 16 Буферные объекты это ваша видеопамять; используйте ее' 819 glBufferData Цель: Создать и инициализировать хранилище данных буферного объекта Включаемый файл: <glext.h> Синтаксис: void glBufferData(GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage); Описание: Создает и инициализирует хранилище данных буферного объекта, основываясь на заданном размере и подсказках о производительности. Все ранее существовавшие данные удаляются. Если указатель данных ненулевой, данные копируются в “информационный склад” Если информационное хранилище буферного объекта отображено, отображение инвертируется Параметры: target (тип GLenum) Цель — буферный объект, для которой создастся информационное хранилище. Значением может быть одна из следующих констант- GL_ARRAY_BUFFER: создается информационное хранилище буферного объекта массива вершин GL_ELEMENT_ARRAY_BUFFER: создается информационное хранилище буферного объекта индекса массива size (тип GLsizeiptr) da ta(THn const GLvoid*) Размер нового информационного хранилища буферного объекта, измеряемый в стандартных машинных единицах Указатель на данные, которые будут скопированы в информационное хранилище для инициализации, или NULL, если данные копироваться не будут usage (тип GLenum) Подсказка по производительности, указывающая, как будет осуществляться доступ к информационному хранилищу Значением может быть одна из следующих констант: GL_DYNAMIC_COPY- данные будут меняться часто и использоваться как для считывания, так и для записи средствами OpenGL. (В настоящее время механизма считывания информации OpenGL не существует.) GL_DYNAMIC_DRAW: данные будут меняться часто и использоваться для записи в OpenGL GL_DYNAMIC_READ: данные будут меняться часто и использоваться для считывания из OpenGL (В настоящее время механизма считывания информации OpenGL не существует) GL_STATIC_COPY. данные будут меняться редко и использоваться как для считывания из OpenGL, так и для записи в OpenGL (В настоящее время механизма считывания информации OpenGL нс существует)
820 Часть III OpenGL-следующее поколение GL_STATIC_DRAW: данные будут меняться редко и использоваться для записи в OpenGL GL_STATIC_READ: данные будут меняться редко и использоваться для считывания из OpenGL. (В настоящее время механизма считывания информации OpenGL не существует) GL_stream_COPY: данные будут использоваться один-два раза как для чтения, так и для записи (В настоящее время механизма считывания информации OpenGL не существует) GL_stream_draw: данные будут использоваться один-два раза для записи в OpenGL GL_stream_read: данные будут использоваться один-два раза для считывания из OpenGL. (В настоящее время механизма считывания информации OpenGL не существует) Что возвращает: Ничего См. также: glBindBuffer, gIBufferSubData, glMapBuffer, glUnmapBuffer gIBufferSubData Цель: Обновить подмножество информационного хранилища буферного объекта Включаемый файл: <glext.h> Синтаксис: void glBuffer SubData(GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid *data); Описание: Замещает часть информационного хранилища буферного объекта, основываясь на заданных размере и смещении в информационном хранилище. Содержимое информационного хранилища, не попадающее в замещаемый фрагмент, остается без изменений. Если на момент вызова функции информационное хранилище буферного объекта отображено или любая часть заданного фрагмента не приходится на информационного хранилище буферного объекта, генерируется ошибка Параметры: target (тип GLenum) Цель — буферный объект, информационное хранилище которого будет обновляться. Значением может быть одна из следующих констант. GL_ARRAY_BUFFER- обновляется информационное хранилище буферного объекта массива вершин GL_ELEMENT_ARRAY_BUFFER‘ обновляется информационное хранилище буферного объекта индекса массива
Глава 16 Буферные объекты это ваша видеопамять, используйте ее1 821 offset (тип GLintptr) Смещение от начала информационного хранилища буферного объекта, с которого начинается замещение, измеряемое в стандартных машинных единицах size (тип GLsizeiptr) data (тип const GLvoid*) Размер замещаемой области информационного хранилища, измеряемый в стандартных машинных единицах Указатель на данные, которые будут скопированы в информационное хранилище с целью инициализации, или NULL, если данные копировать не требуется Что возвращает: Ничего См. также: gIBindBuffer, glBufferData, glMapBuffer, glUnmapBuffer gIDeleteBuffers Цель: Удалить один или несколько буферных объектов Включаемый файл: <glext,h> Синтаксис: void gIDeleteBuffers(GLsizei n, const GLuint ★buffers) ; Описание: Удаляет буферные объекты Содержимое уничтожается, а имена помечаются как неиспользуемые. Если подобный буферный объект в настоящее время связан в текущем контексте, все связи обнуляются. Если на удаление заявлено неиспользуемое или пустое имя буферного объекта, оно просто игнорируется Параметры: п (тип GLsizei) Число удаляемых буферных объектов buffers (тип Указатель на массив, содержащий имена удаляемых буферных const GLuint*) объектов Что возвращает: Ничего См. также: gIGenBuffers, gIBindBuffer, gllsBuffer gIGenBuffers Цель: Вернуть неиспользуемые имена буферных объектов Включаемый файл: <glext.h> Синтаксис: void gIGenBuffers(GLsizei n, GLuint ^buffers) ; Описание: Возвращает неиспользуемые имена буферных объектов Впоследствии имена можно связывать и создавать с помощью gIBindBuffer Параметры: п (тип GLsizei) Число возвращаемых имен буферных объектов buffers Указатель на массив, заполняемый неиспользуемыми именами (тип GLuint*) буферных объектов Что возвращает: Ничего См. также: gIBindBuffer, gIDeleteBuffers, gllsBuffer
822 Часть III OpenGL-следующее поколение gIGetBufferParameteriv Цель: Запросить свойства буферного объекта Включаемый файл: <glext.h> Синтаксис: void glGetBufferParameteriv(GLenum target, GLenum value, GLint *da ta); Описание: Запрашивает состояние связанного в настоящий момент буферного объекта Параметры: target (тип GLenum) Цель — буферный объект, состояние которого запрашивается. Значением может быть одна из следующих констант: GL_array_buffer. запрашивается состояние буферного объекта массива вершин GL_element_array_buffer: запрашивается состояние буферного объекта индекса массива value (тип GLenum) Запрашиваемое состояние буферного объекта. Значением может быть одна из следующих констант: GL_buffer_access: метка доступа буферного объекта, возможные значения которой — gl_read_only, GL_WRITE_ONLY ИЛИ GL_READ_WRITE GL_buffer_MAPPED: метка, указывающая, отображается ли в данный момент буферный объект GL_buffer_size: размер буферного объекта, измеряемый в стандартных машинных единицах GL_buffer_usage: шаблон использования буферного объекта, который может иметь такие значения: GL_dynamic_C0py, gl_dynamic_read, gl_dynamic_write, gl_static_copy, gl_static_read, gl_static_write, gl_stream_copy, GL_STREAM_READ ИЛИ GL_STREAM_WRITE data (тип GLint*) Указатель на ячейку памяти, куда будут заноситься результаты запроса Что возвращает: Ничего См. также: glBindBuffer, glBufferData, glMapBuffer, glUnmapBuffer gIGetBufferPointerv Цель: Запросить указатель на информационное хранилище отображаемого буферного объекта Включаемый файл: <glext.h> Синтаксис: void glGetBufferPointerv(GLenum target, GLenum pname, GLvoid **params);
Глава 16 Буферные объекты, это ваша видеопамять; используйте ее1 823 Описание: Возвращает указатель, сообщающий, куда отображается информационное хранилище буферного объекта. Если буферный объект не отображается, возвращается значение NULL. В зависимости от реализации значение NULL также может возвращаться при запросе клиента, не отображающего буферный объект Параметры: target (тип GLenunt) Цель — буферный объект, указатель на информационное хранилище которого запрашивается. Значением может быть одна из следующих констант. GL_ARRAY_BUFFER. запрашивается указатель буферного объекта массива вершин GL_ELEMENT_ARRAY_BUFFER. запрашивается указатель буферного объекта индекса массива pname (тип GLenunt) params (тип GLvoid**) Что возвращает: Запрашиваемый указатель, значение должно быть равно BUFFER_MAP_POINTER Указатель на ячейку памяти, где будут записаны результаты запроса Ничего См. также: glBindBuffer, glMapBuffer gIGetBufferSubData Цель: Запросить подмножество информационного хранилища буферного объекта Включаемый файл: <glext.h> Синтаксис: void glGetBuffer SubData(GLenunt target, GLintptr offset, GLsizeiptr size, GLvoid *data); Описание: Возвращает данные из заданной части информационного хранилища текущего буферного объекта. Если на момент запроса буферный объект уже отображен, генерируется ошибка Параметры: target (тип GLenunt) Цель — буферный объект, данные которого запрашиваются. Значением может быть одна из следующих констант: GL_ARRAY_BUFFER- запрашиваются данные буферного объекта массива вершин GL_ELEMENT_ARRAY_BUFFER запрашиваются данные буферного объекта индекса массива offset (тип GLintptr) Смещение от начала информационного хранилища буферного объекта, с которого начинаются запрошенные данные, измеряемое в стандартных машинных единицах
824 Часть III OpenGL -следующее поколение size (тип GLsizeiptr) data (тип GLvoid*) Что возвращает: См. также: Размер запрашиваемой области информационного хранилища, измеряемый в стандартных машинных единицах Указатель на ячейку памяти, куда будут возвращены запрошенные данные Ничего gIBindBuffer, glBufferData, glBufferSubData, gIMapBuffer, glUnmapBuffer gllsBuffer Цель: Включаемый файл: Синтаксис: Описание: Параметры: buffer (тип GLuint) Что возвращает: См. также: Запросить, представляет ли данное имя буферный объект <glext.h> GLboolean gllsBuffer(GLuint buffer); Запрашивает, соответствует ли указанное имя буферному объекту Запрашиваемое имя буферного объекта GL_TRUE (тип GLboolean), если буферный объект с таким именем ранее был связан и еще не удален В противном случае возвращается значение GLJFALSE gIBindBuffer, gIDeleteBuffers, gIGenBuffers gIMapBuffer Цель: Включаемый файл: Синтаксис: Описание: Параметры: target (тип GLenum) Отобразить информационное хранилище буферного объекта <glext.h> GLvoid ‘gIMapBuffer(GLenum target, GLenum access); Отображает информационное хранилище буферного объекта в адресное пространство клиента После этого данные можно непосредственно считывать и/или записывать (в зависимости от установленной метки доступа к буферу) относительно возвращенного указателя Если на момент запроса буферный объект уже отображен, генерируется ошибка Значения параметров, передаваемые командам OpenGL, могут быть не связанными с возвращаемым указателем Цель — буферный объект, информационное хранилище которого отображается Значением может быть одна из следующих констант GL_ARRAY_BUFFER- отображается информационное хранилище буферного объекта массива вершин GL_ELEMENT_ARRAY_BUFFER- отображается информационное хранилище буферного объекта индекса массива
Глава 16 Буферные объекты это ваша видеопамять; используйте ее1 825 access (тип GLenum) Метка доступа, указывающая, какие операции с информационным хранилищем буферного объекта допустимы чтение, запись, чтение и запись. Значением может быть одна из следующих констант’ GL_READ_ONLY: доступ к информационному хранилищу буферного объекта ограничен так, что запись в него недопустима GL_READ_WRITE: чтение и запись не ограничены GL_WRITE_ONLY. доступ к информационному хранилищу буферного объекта ограничен так, что чтение из него недопустимо Что возвращает: Указатель (тип GLvoid*) на информационное хранилище буферного объекта, отображаемый в адресное пространство клиента См. также: glBindBuffer, glBufferData, glBufferSubData, glUnmapBuffer glUnmapBuffer Цель: Инвертировать отображение информационного хранилища буферного объекта Включаемый файл: <glext.h> Синтаксис: GLboolean glUnmapBuffer(GLenum target); Описание: Инвертирует отображение информационного хранилища буферного объекта, поскольку прежде чем к информационному хранилищу снова можно будет обращаться средствами OpenGL, отображение следует отменить. Если на момент запроса буферный объект еще не отображен, генерируется ошибка Параметры: target (тип GLenum) Цель — буферный объект, отображение информационного хранилища которого отменяется. Значением может быть одна из следующих констант’ GL_ARRAY_BUFFER: отменяется отображение информационного хранилища буферного объекта массива вершин GL_ELEMENT_ARRAY_BUFFER- отменяется отображение информационного хранилища буферного объекта индекса массива Что возвращает: Если не произойдет ошибка, связанная с платформой (например, изменение видеорежима или режима энергосбережения), возвращается значение GL_true (тип GLboolean) Указанные события могут дерево in । информационное хранилище буферного объекта в неопределенное состояние, и в таком случае возвр.. значение GL_FALSE, а клиент должен повторно заселп информационное хранилище См. также: glBindBuffer, glMapBuffer

ГЛАВА 17 Запросы о преградах: зачем делать больше работы, чем требуется? Бенджамин Липчак ИЗ ЭТОЙ ГЛАВЫ ВЫ УЗНАЕТЕ . . . Действие Функция Создание и удаление объектов запроса glGenQueries/glDeleteQueries Определение запросов о перекрывании glBeginQuery/glEndQuery ограничивающего многоугольника Извлечение результатов запроса о преградах glGetQueryObjectiv Сложные сцены содержат сотни объектов и многие тысячи многоугольников. Осмотрите комнату, в которой сейчас находитесь. Посмотрите на мебель, объекты, других людей или животных и подумайте, какие мощности нужно задействовать, чтобы точно визуализировать их сложный внешний вид. Возможно, кто-то сидит за компьютером на ящике в пустой студии, но большинство все же находится в среде, над точной визуализацией которой пришлось бы попотеть. Подумайте теперь о вещах, которые вы не можете видеть: объекты, скрытые дру- гими объектами, лежащие в ящиках или даже в соседней комнате. С большинства точек наблюдения эти объекты невидимы. Если вы будете визуализировать сцену, эти объекты будут нарисованы, но со временем что-то будет нарисовано поверх них. Так зачем тогда вообще делать работу впустую9 Так знайте же, существуют запросы о преградах (occlusion queries)' В этой главе мы опишем новую мощную функцию, включенную в OpenGL 1.5, с помощью которой можно существенно сэкономить на обработке вершин и наложении текстур за счет рисования небольших нетекстурных фрагментов Часто подобный компромисс весьма выгоден. Ниже мы рассмотрим использование детектирования преград и докажем, что оно позволяет существенно увеличить частоту смены кадров. Мир до запросов о преградах Чтобы продемонстрировать увеличение производительности при использовании за- просов о преградах, нужна экспериментальная контрольная группа. Для этого мы
828 Часть III. OpenGL: следующее поколение Рис. 17.1. Основной преградой является сетка, образованная шестью стенами нарисуем сцену без детектирования преград. Выбранная сцена довольно сложна, в ней присутствует множество объектов, видимых и невидимых в различные мо- менты времени. Вначале нарисуем “основную преграду”. Преградой (occluder) будем называть большой объект на сцене, имеющий тенденцию закрывать (или скрывать) другие объекты сцены. Как правило преграда прорисована не очень детально, тогда как объекты, которые она закрывает, имеют существенно большую степень детализации. Хорошими примерами подобных преград являются стены, потолки и полы. В на- шей сцене (рис. 17.1) основной преградой является решетка, образованная шестью стенками. Из листинга 17.1 видно, что эти сцены в действительности являются мас- штабированными кубами. Листинг 17.1. Основная преграда и шесть масштабированных и транслированных сплошных кубов /.' г.уг-Лйг.стся для рисования закрывающей сетки voiH сr-.wOccluder(void) . 'Iot3f(0.5f, 0.25f, O.Of); .-H&hMatrixO; ; Icalef(30.Of, 30.Of, l.Of); c., i ranslatef (O.Of, O.Of, 50.Of); ,’2 jcSolidCube (lO.Of); glTranslatef(O.Of, O.Of, -lOO.Of); glutSolidCube(10.Of); glPopMatrix();
Глава 17 Запросы о преградах: зачем делать больше работы, чем требуется? 829 glPushMatrix(); glScalef(l.Of, 30.Of, 30.Of); glTranslatef(50.Of, O.Of, O.Of); glutSolidCube(10.Of); glTranslatef(-100.Of, O.Of, O.Of); glutSolidCube(lO.Of); glPopMatrix(); glPushMatrix(); glScalef(30.Of, l.Of, 30.Of); glTranslatef(O.Of, 50.Of, O.Of); glutSolidCube(lO.Of); glTranslatef(O.Of, -100.Of, O.Of); glutSolidCube(10.Of); glPopMatrix(); В каждой ячейке сетки мы собираемся поместить сферу со сложной мозаичной текстурой. Эти сферы являются “закрываемыми объектами” (occludees) — объектами, возможно закрываемыми преградой. Чтобы усилить нагрузку, требуется очень много вершин и текстуры, и на этом фоне выигрыш от использования запросов о преградах будет нагляднее. Поступим точно так же, как в предыдущей главе, где мы демонстри- ровали буферные объекты, — сделаем ставку на вершины' На рис. 17.2 показано изображение, полученное при выполнении кода из листин- га 17 2. Если объем работы показался вам чересчур большим, можете уменьшить степень мозаичности в функции glutSolidSphere с сотен до меньших величин Если же, наоборот, ваша реализация OpenGL даже не почувствовала нагрузки, — сделайте мозаичное представление более подробным. Листинг 17.2. Рисование в цветном кубе 27 сфер с детальным мозаичным представлением // Вызывается для рисования сферы void DrawSphere(GLint sphereNum) { glutSolidSphere(50.Of, 100, 100); ) void DrawModels(void) { // Включаем текстурирование только для сфер glEnable(GL_TEXTURE_2D); glEnable(GL_TEXTURE_GEN_S); glEnable(GL_TEXTURE_GEN_T); // Рисуем 27 сфер в цветном кубе for (г = 0; г < 3; г++) { for (g = 0; g < 3; g++)
830 Часть III. OpenGL: следующее поколение Рис. 17.2. В качестве закрываемых объектов выступают 27 детально прорисованных сфер for (b = 0; Ь < 3; Ы-+) { glColor3f(r * 0.5f, g * 0.5f, b * 0.5f); glPushMatrix() ; glTranslatef(100.Of * r - 100.Of, 100.Of * g - 100.Of, 100.Of * b - 100.Of); DrawSphere((r* 9) + (g* 3) +b); glPopMatrix(); } } 1 gIDisable(GL_TEXTURE_2D); gIDisable(GL_TEXTURE_GEN_S); gIDisable(GL_TEXTURE_GEN_T); Выполнение кода листинга 17.2 создает требуемое изображение. Если бы нас устраивала производительность визуализации, главу можно было бы завершить пря- мо сейчас. Однако при достаточно замысловатом мозаичном представлении сфер, при наложении сложной множественной текстуры или использовании для визуализа- ции сферы затенения фрагментов частота смены кадров будет просто неприемлемой. Поэтому читайте дальше!
Глава 17. Запросы о преградах: зачем делать больше работы, чем требуется? 831 Ограничивающий объем Name Тетраэдр Число граней 4 Число вершин 4 % избыточных пикселей 136% Куб Октаэдр Додекаэдр Икосаэдр 6 8 12 20 8 6 20 12 120% 81% 58% 34% Рис. 17.3. Различные ограничивающие объемы: достоинства и недостатки Рамки Теория метода детектирования затенения состоит в том, что, если ограничивающий объем (рамка) объекта не виден, сам объект также не виден. Ограничивающим (bound- ing) называется объем, полностью содержащий объект. Весь смысл детектирования закрываемых объектов заключается в рисовании просто ограничивающего объема, позволяющего определить, можно ли избежать рисования сложного объекта. Таким образом, чем сложнее ограничивающий объем, тем больше он не подходит для задачи оптимизации, которой мы пытаемся добиться. Простейшим ограничивающим объемом является куб. Восемь вершин, шесть гра- ней. Подобный ограничивающий объем легко создать для любого объекта, просто найдя его минимальную и максимальную координаты по осям х, у и z. Для наших сфер с радиусом 50 единиц идеально подойдет ограничивающий объем в виде куба со стороной 100 единиц. Используя подобные простые и произвольные ограничивающие объемы, помните о компромиссе. Ограничивающий объем может иметь очень малое число вершин, но он будет захватывать гораздо большее число пикселей, чем исходный объект. Разместив в стратегических местах несколько дополнительных вершин, вы можете превратить ограничивающий объем в более полезную форму и существенно снизить служебные издержки, связанные с дополнительным рисованием. К счастью, ограни- чивающий объем рисуется без всякой текстуры или затенения, поэтому затраты на его рисование часто ниже, чем для исходного объекта. На рис. 17.3 приведен пример того, как различные формы ограничивающих объемов влияют на число пикселей и вершин. При рисовании ограничивающих объемов мы собираемся активизировать запросы о преградах, которые подсчитают число фрагментов, прошедших проверку по глубине (а также сличение с трафаретом, если оно активизировано). Следовательно, для нас не важно, как выглядят ограничивающие объемы. Фактически мы даже можем не ри- совать их на экране. Именно поэтому перед визуализацией ограничивающего объема мы избавляемся от всего ненужного, включая запись в буфере цвета. glShadeModel(GL_FLAT); glDisable(GL_LIGHTING);
832 Часть III OpenGL-следующее поколение gIDisable(GL_COLOR_MATERIAL); gIDisable(GL_NORMALIZE); // Текстурирование уже деактивизировано glColorMask(0, 0, 0, 0); Ну что ж, хватит разговоров, займемся собственно запросами. Вначале нам нужно определиться с их именами. В рассматриваемом примере мы затребовали 27 имен (по одному на каждый запрос о сфере) и предоставили указатель на массив, в котором должны храниться новые имена. // Генерируем имена запросов о препятствии glGenQueries(27, querylDs); Завершив работу, удаляем объекты запросов, указывая массив, содержащий эти 27 имен" glDeleteQueries(27, querylDs); Объекты запросов о препятствии не связываются подобно другим объектам OpenGL (например, текстурным или буферным) Они создаются путем вызова функ- ции glBeginQuery. Это действие отмечает начало запроса Объект запроса имеет внутренний счетчик, отслеживающий число фрагментов, которые имеют шансы дой- ти до буфера кадров, — если мы не отключили маску записи буфера цвета В начале запроса счетчик обнуляется. Далее мы рисуем ограничивающий объем. Внутренний счетчик объекта запроса увеличивается на единицу всякий раз, когда фрагмент проходит проверку по глубине (следовательно, он не закрывается основной преградой — нарисованной ранее сеткой). Для некоторых алгоритмов надо точно знать число нарисованных фрагментов, но в нашем случае важно только знать, равен счетчик нулю (все фрагменты отброшены при проверке по глубине) или нет (какая-то часть ограничивающего объема видна). Завершив рисование ограничивающего объема, отмечаем окончание запроса, вы- зывая функцию glEndQuery Это сообщает OpenGL, что мы закончили запрос, и раз- решает подавать следующий запрос или затребовать результаты этого Поскольку мы рисуем 27 сфер, то производительность можно повысить, используя 27 различных объектов запроса Таким образом, путем промежуточного считывания результатов запросов мы можем, не нарушая конвейер, выстроить в очередь рисование всех 27 ограничивающих объемов. В листинге 17 3 иллюстрируется визуализация ограничивающих объемов, обособ- ленных началом и концом запроса Затем мы перерисовываем основную преграду и рисуем нужные сферы Обратите внимание на код визуализации ограничивающего объема там, где мы оставляем активизированной маску записи буфера цвета Таким образом можно увидеть и сравнить различные формы ограничивающих объемов Листинг 17.3. Начало запроса, рисование ограничивающего объема, завершение запроса, перерисовывание на реальной сцене // Вызывается для рисования объектов сцены void DrawModels(void) GLint r, g, b; if (occlusionDetection || showBoundingVolume)
пава 17 Запросы о преградах: зачем делать больше работы, чем требуется? 833 // Рисуем ограничивающие объемы после рисования основной // преграды DrawOccluder(); // Все, что нам нужно знать об ограничивающем объеме, - // получающиеся коды глубины glShadeMode1(GL_FLAT); glDisable(GL_LIGHTING); glDisable(GL_COLOR_MATERIAL); glDisable(GL_NORMALIZE); // Текстурирование уже деактивизировано if (!showBoundingVolume) glColorMask(0, 0, 0, 0); // Рисуем 27 сфер в цветном кубе for (г = 0; г < 3; г++) { for (g = 0; g < 3; g++) { for (b = 0; b < 3; b++) { if (showBoundingVolume) glColor3f(r * 0.5f, g * 0.5f, b * 0.5f); glPushMatrix(); glTranslatef(100.Of * r - 100.Of, 100.Of * g - 100.Of, 100.Of * b - 100.Of); glBeginQuery(GL_SAMPLES_PASSED, queryIDs[(r*9)+(g*3)+b]); switch (boundingVolume) { case 0: glutSolidCube(100.Of); break; case 1: glScalef(150.Of, 150.Of, 150.Of); glutSolidTetrahedron(); break; case 2: glScalef(90.Of, 90.Of, 90.Of); glutSolidOctahedron(); break; case 3: glScalef(40.Of, 40.Of, 40.Of); glutSolidDodecahedron(); break; case 4: glScalef(65.Of, 65.Of, 65.Of); glutSolid!cosahedron(); break; } glEndQuery(GL_SAMPLES_PASSED); glPopMatrix();
834 Часть III OpenGL следующее поколение if (!showBoundingVolume) glClear(GL_DEPTH_BUFFER_BIT); 11 Восстанавливаем нормальное состояние рисования glShadeModel(GL_SMOOTH) ; glEnable(GL_LIGHTING); glEnable(GL_COLOR_MATERIAL); glEnable(GL_NORMALIZE); glColorMask(1, 1, 1, 1); } DrawOccluder(); // Включаем текстурирование только для сфер glEnable(GL_TEXTURE_2D); glEnable(GL_TEXTURE_GEN_S); glEnable(GL_TEXTURE_GEN_T); // Рисуем 27 сфер в цветном кубе for (г = 0; г < 3; г++) { for (g = 0; g < 3; g++) { for (b = 0; b < 3; b++) glColor3f(r * 0 5f, g * 0.5f, b * 0.5f); glPushMatrix(); glTranslatef(100.Of * r - 100.Of, 100.Of * g - 100.Of, 100.Of * b - 100.Of); DrawSphere((r*9) + (g*3)+b) ; glPopMatrix(); ) ) } glDisable(GL_TEXTURE_2D); glDisable(GL_TEXTURE_GEN_S); glDisable(GL_TEXTURE_GEN_T); Функция Drawsphere содержит логику, согласно которой мы решаем, рисовать ли сферу Результаты запросов содержатся в 27 объектах запросов Ну что ж, давайте найдем, какие объекты скрыты, а какие придется рисовать Запрос объекта запроса Момент истины близок Присяжные вернулись, чтобы огласить вердикт Мы хотим нарисовать только необходимое, поэтому надеемся, ч го все запросы дадут ответ “ни один из фрагментов нс виден” Разумеется, сети говорить о поставленной задаче, такой otbci невозможен
Глава 17 Запросы о преградах зачем делать больше работы, чем требуется? 835 Вне зависимости от того, под каким углом мы смотрим на сетку, если не увеличи- вать масштаб, в поле зрения попадет минимум 9 сфер В худшем случае вы увидите все сферы на трех гранях сетки 19 штук Тем не менее даже в худшем случае не требуется рисовать 8 сфер Т е. в пересчете на вершины получаем почти 30% эконо- мии В лучшем же случае экономия составляет 66%, так как 18 сфер отбрасываются Если увеличить масштаб, сфокусировав взгляд на одной сфере, мы можем добиться невероятной экономии, не рисуя 26 сфер! Итак, как определить свою удачу9 Нужно просто запросить объект запроса Это звучит немного путано, но речь идет об обычном и понятном запросе состояния OpenGL Просто так получилось, что в нем фигурирует нечто, названное объектом запроса В функции glGetQueryObjectiv (листинг 17 4) мы смотрим, равен ли нулю счетчик фрагментов, прошедших проверку по глубине, и если да — сферы не рисуем Листинг 17.4. Проверка результатов запроса и рисование сферы только тогда, когда это необходимо // Вызывается для рисования сферы void DrawSphere(GLint sphereNum) { GLboolean occluded = GL_FALSE; if (occlusionDetection) { GLint passingSamples; 11 Проверяем, скроется ли сфера за преградой glGetQueryObjectiv(querylDs[sphereNum], GL_QUERY_RESULT, &passingSamples); if (passingSamples == 0) occluded = GL_TRUE; } if (!occluded) { glutSolidSphere(50.Of, 100, 100); 1 Вот и все Последовательно проверяются запросы по всем сферам и определя- ется, требуется ли рисовать рассматриваемую сферу В программу включен режим деактивизации детектирования преград, чтобы можно было посмотреть, насколько при этом падает производительность В зависимости от того, сколько сфер видно, детектирование преград повышает производительность в два или более раз Можно также проверить, доступен ли результат немедленно Если мы не бу- дем визуализировать 27 ограничивающих объемов, а затребуем каждый результат немедленно, операция визуализации ограничивающего объема может еще находить- ся в конвейере и результат может быть не готов Чтобы проверить, готов ли результат можно запросить функцию GL_QUERY_RESULT_AVAILABLE Если он не готов, запрос GL_QUERY_RESULT будет остановлен до получения результата Поэтому в ожидании результата можно указать приложению сделать что-то полезное Мы запланировали на промежуточные моменты массу работы, направленной на то, чтобы к моменту окончания 27-го запроса был готов результат первого запроса
836 Часть III. OpenGL-следующее поколение Другие запросы состояния включают запрос о текущем активном имени запроса (какой запрос активен между парой glBeginQuery/glEndQuery, если такой суще- ствует) и числе битов в счетчике прошедших элементов (зависит от реализации) В принципе реализация может иметь счетчик с нулевым числом битов. В этом случае запросы о преградах бесполезны, и их не следует использовать. В листинге 17 5 по- казано, как данная возможность проверяется при инициализации приложения (непо- средственно после проверки расширения и точек входа). Листинг 17.5. Проверка доступности запросов о преградах // Убеждаемся, что доступны требуемые функциональные возможности! version = glGetString(GL_VERSION); if ((version[0] == '1') && (version[l] == && (version[2] >= '5') && (version[2] <= ’9')) { glVersionl5 = GL_TRUE; } if (!glVersionl5 && !glt!sExtSupported("GL_ARB_occlusion_query")) { fprintf(stderr, "Neither OpenGL 1.5 nor GL_ARB_occlusion_query" " extension is available!\n"); Sleep(2000); exit(0); } // Загружаем указатели на функции if (glVersionl5) { glBeginQuery = gltGetExtensionPointer{"glBeginQuery"); glDeleteQueries = gltGetExtensionPointer("glDeleteQueries"); glEndQuery = gltGetExtensionPointer("glEndQuery"); glGenQueries = gltGetExtensionPointer("glGenQueries"); glGetQueryiv = gltGetExtensionPointer("glGetQueryiv"); glGetQueryObjectiv = gltGetExtensionPointer("glGetQueryObjectiv"); glGetQueryObjectuiv = gltGetExtensionPointer("glGetQueryObjectuiv"); gllsQuery = gltGetExtensionPointer("gllsQuery"); } else { glBeginQuery = gltGetExtensionPointer("glBeginQueryARB”); glDeleteQueries = gltGetExtensionPointer("glDeleteQueriesARB"); glEndQuery = gltGetExtensionPointer("glEndQueryARB"); glGenQueries = gltGetExtensionPointer("glGenQueriesARB"); glGetQueryiv = gltGetExtensionPointer("glGetQueryivARB"); glGetQueryObjectiv = gltGetExtensionPointer("glGetQueryObjectivARB"); glGetQueryObjectuiv = gltGetExtensionPointer("glGetQueryObjectuivARB”); gllsQuery = gltGetExtensionPointer("gllsQueryARB"); ) if (!glBeginQuery || !glDeleteQueries || !glEndQuery || !glGenQueries || 'glGetQueryiv || !glGetQueryObjectiv ||
Глава 17 Запросы о преградах: зачем делать больше работы, чем требуется? 837 IglGetQueryObjectuiv || igllsQuery) { fprintf(stderr, "Not all entrypoints were available!\n") ; Sleep(2000); exit(O); } // Убеждаемся, что число битов счетчика запросов не равно нулю glGetQueryiv(GL_SAMPLES_PASSED, GL_QUERY_COUNTER_BITS, SqueryCounterBits); if (queryCounterBits == 0) { fprintf(stderr, "Occlusion queries not really supported!\n") ; fprintf(stderr, "Available query counter bits: 0\n"); Sleep(2000); exit(0); Еще одним запросом, о котором вам следует знать, является gllsQuery. Эта команда просто проверяет, принадлежит ли заданное имя существующему объекту запроса (в таком случае возвращается GL_TRUE, в противном — GL_FALSE) Резюме Иногда при визуализации сложных сцен мы растрачиваем аппаратные ресурсы на визуализацию объектов, которые никогда не будут видны. Дополнительной работы можно избежать, проверяя, какие объекты проявятся на окончательном изображении. Рисуя вокруг объекта ограничивающий объем, мы быстро аппроксимируем объект на сцене. Если преграды на сцене закроют ограничивающий объем, они спрячут и реальный объект Облекая визуализацию ограничивающего объема в запрос, мы можем подсчитать число пикселей, которые он занимал бы. Если число пикселей равно нулю, мы можем гарантировать, что исходный объект также не будет нарисован, поэтому его визуализацию можно пропустить. В зависимости от сложности объектов на сцене и того, насколько часто они закрываются, повышение производительности может быть невероятным. Справочная информация gIBeginQuery Цель: Отметить начало запроса о преградах Включаемый файл: <glext.h> Синтаксис: void gIBeginQuery(GLenum target, GLuint id); Описание: Начинает запрос о преградах, имеющий указанное имя, обнуляя счетчик прошедших выборок. Если объект запроса не существует, он создается. Если запрос с указанным именем уже обрабатывается или задано нулевое имя объекта запроса, генерируется ошибка
838 Часть III OpenGL следующее поколение Параметры: target (тип GLenum) id (тип GLuint) Что возвращает: Тип объекта запроса Значение должно быть равно GL_SAMPLES_PASSED Имя объекта запроса Ничего См. также: glEndQuery, glGenQueries, glDeleteQueries, gllsQuery glDeleteQueries Цель: Удалить один или несколько объектов запроса Включаемый файл. <glext.h> Синтаксис: void glDeleteQueries(GLsizei n, const GLuint *ids); Описание: Удаляет объекты запроса При этом содержимое удаляется, а имена помечаются как неиспользуемые Если на удаление заявлено имя неиспользуемого объекта запроса или нулевое, оно просто игнорируется Параметры: п (тип GLsizei) Чисто удаляемых объектов запроса ids (тип Указаге п> на массив, содержащий имена удаляемых объектов const GLuint*) запроса Что возвращает: Ничего См. также: glBeginQuery, glEndQuery, glGenQueries, gllsQuery glEndQuery Цель: Отмстить конец запроса о преградах Включаемый файл: <glext h> Синтаксис: void glEndQuery(GLenum target); Описание: Помечает конец текущего запроса о преградах Если никакой запрос о преградах в настоящее время нс обрабатывается, будет сгенерирована ошибка Параметры: target Тип завершаемого объекта запроса Значение должно быть (тип GLenum) равно GL_SAMPLES_PASSED Что возвращает: Ничсю См. также: glBeginQuery, glDeleteQueries, glQueryObjectiv, glGetQueryObjectuiv
Глава 17 Запросы о преградах зачем делать больше работы, чем требуется7 839 gIGenQueries Цель: Вернуть неиспользуемые имена объектов запроса Включаемый файл: <glext.h> Синтаксис: void gIGenQueries(GLsizei л, const GLuint *ids); Описание: Возвращает неиспользуемые имена объектов запроса Впоследствии с помощью функции gIBeginQuery можно создавать и запускать объекты запроса Параметры: л (тип GLsizei) Число возвращаемых имен объектов запроса ids (тип GLuint*) Указатель на массив, заполняемый неиспользуемыми именами объектов запроса Что возвращает: Ничего См. также: glDeleteQuenes, gIBeginQuery, gllsQuery gIGetQueryiv Цель: Запросить состояние цели — объекта запроса Включаемый файл: Синтаксис: <glext.h> void gIGetQueryiv(GLenum target, GLenum pname, GLint *params); Описание: Запрашивает состояние заданного объекта запроса Данное состояние характеризует нс конкретный объект запроса, а все объекты запроса Параметры: target (тип GLenum) pname (тип GLenum) Тип запрашиваемого объекта запроса Значение должно быть равно GL_SAMPLES_PASSED Запрашиваемое состояние объекта запроса Значением может быть одна из следующих констант GL_CURRENT_QUERY имя активного в настоящее время объекта запроса о преградах Если активных объектов запроса о преградах нет, возвращается нуль GL_QUERY_COUNTER_BITS зависящее от реализации число битов в счетчике, используемом для накопления прошедших выборок params (тип GLint*) Что возвращает: Указатель на положение в памяти, где будут записаны результаты запроса Ничего См. также: glGetQueryObjectiv, glGetQueryObjectuiv, gllsQuery
840 Часть III OpenGL-следующее поколение gIGetQueryObject Цель: Запросить состояние объекта запроса Включаемый файл: <glext.h> Синтаксис: void glGetQueryObjectiv(GLuint id, GLenum pname, Описание: GLint *params); void glGetQueryObjectuiv(GLuint id, GLenum pname, GLuint *params); Запрашивает состояние объекта запроса с заданным именем Параметры: id (тип GLuint) Если имя не соответствует существующему объекту запроса, или объект запроса в настоящее время активен (находится внутри glBeginQuery/glEndQuery), выдается ошибка Имя объекта запроса, состояние которого запрашивается pname Запрашиваемое состояние объекта запроса Значением может (тип GLenum) быть одна из следующих констант params (тип GL_QUERY_RESULT: указывает значение счетчика прошедших выборок объекта запроса GL_QUERY_RESULT_AVAILABLE указывает, доступен ли результат сразу Если результат запроса будет доступен через некоторое время, возвращается значение GL_FALSE В противном случае возвращается значение GL_TRUE, которое также указывает, что доступны и результаты всех предыдущих запросов Указатель на ячейку памяти, куда записываются результаты GLint*/GLuint*) запроса Что возвращает: Ничего См. также: glBeginQuery, glEndQuery, glGetQueryiv, gllsQuery gllsQuery Цель: Запросить, соответствует ли данное имя объекту запроса Включаемый файл: <glext.h> Синтаксис: GLboolean gl!sQuery(GLuint id); Описание: Запрашивает, является ли указанное имя именем объекта запроса Параметры: id (тип GLuint) Искомое имя объекта запроса Что возвращает: Если объект запроса с искомым именем был создан (с помощью glBeginQuery) и не был удален, возвращается значение GL_TRUE (тип GLboolean) В противном случае возвращается значение GL_FALSE См. также: glBeginQuery, glEndQuery, glGenQueries, glDeleteQueries
ГЛАВА 18 Текстуры глубины и тени Бенджамин Липчак ИЗ ЭТОЙ ГЛАВЫ ВЫ УЗНАЕТЕ Действие Функция Рисование сцены с позиции источника света gluLookAt/gluPerspective Копирование текселей из буфера глубины в текстуру глубины glCopyTex!mage2D Генерация текстурных координат, линейных в системе наблюдения glTexGen Настройка сравнения с тенью glTexParameter Тени являются важной зрительной подсказкой как в реальности, так и в визу- ализированных сценах. На самом низком уровне тени предоставляют информацию о расположении объектов относительно друг друга и источников света, даже если эти источники на сцене не видны Когда речь идет об играх, тени могут сделать игровой мир совершенно жутким. Представьте, что вы заворачиваете за угол в подземелье, освещенном лишь факелами, и входите в тень своего самого страшного кошмара Питер Пен отдыхает' В главе 5, “Цвет, материалы и освещение, основы”, мы описывали малотехноло- гичный способ проектирования объекта на плоскую поверхность, по сути “сплющи- вание” его с целью создания тени Существует и другая технология использования буфера трафарета, именуемая объемами тени, которая, хотя и применяется широ- ко, требует существенной предварительной обработки геометрии и высокой частоты заполнения буфера трафарета В OpenGL 1.4 появился более элегантный подход к ге- нерации теней- отображение тени (shadow mapping). Теория метода отображения тени проста. Вопрос: какие части сцены попадут в тень9 Ответ, части, на которые не падает непосредственно свет. Представьте, что вы наблюдаете сцену, находясь в источнике света Что “увидел” бы источник све- та, будь он камерой? Все, что наблюдается с позиции источника света, освещается Все остальное попадает в тень. Различие между наблюдением с позиции камеры и наблюдением с позиции источника света иллюстрируется на рис 18 1 При визуализации сцены с позиции источника света побочным эффектом является заполнение буфера глубины полезной информацией Благодаря этой информации мы для каждого пикселя знаем относительное расстояние от источника света до ближай- шей поверхности. Эти поверхности освещаются источником света Все остальные поверхности, удаленные от источника света, остаются в тени.
842 Часть III OpenGL следующее поколение Рис. 18.1. С позиции камеры и источника света сцена выглядит по-разному Как же мы поступаем9 Мы копируем буфер глубины в текстуру и проектируем его на сцену, визуализируя с позиции камеры. С помощью этой спроектированной тексту- ры автоматически определяется, какие части объектов освещены, а какие находятся в тени Звучит просто, но каждый этап этого процесса требует внимательности. Да будет свет! Нашим первым действием будет изображение сцены с точки зрения источника света Используем несколько встроенных объектов GLUT, чтобы продемонстрировать прин- цип действия данной технологии при наложении теней на неплоские поверхности — объекты сцены Чтобы изменить точку наблюдения, можно вручную задать матрицу наблюдения модели, но для этого примера мы используем вспомогательную функцию gluLookAt, несколько облегчающую это изменение gluLookAt(lightPos[0], lightPos[1], lightPos[2], O.Of, O.Of, O.Of, O.Of, l.Of, O.Of); Подгоняем сцену под окно Помимо матрицы наблюдения модели нужно установить проекционную матрицу, что- бы минимизировать размер сцены в окне. Даже если свет весьма удален от объектов на сцене, чтобы наилучшим образом использовать пространство в карте тени, удоб- но, чтобы сцена заполняла все доступное пространство Мы устанавливаем ближнюю и дальнюю плоскости отсечения, основываясь на расстоянии от источника света до наиболее близких и наиболее далеких объектов в сцене. Кроме того, поле зрения выбирается так, чтобы сцена была видна вся и максимально близка.
Глава 18 Текстуры глубины и тени 843 // Записываем точность измерения глубины (где это нужно) lightToSceneDistance = sqrt(lightPos[0] * lightPosfO] + lightPos[1] * lightPos[1] + lightPos[2] * lightPos[2]); nearPlane = lightToSceneDistance - 150.Of; if (nearPlane < 50.Of) nearPlane = 50.Of; // С помощью сцены заполняем текстуру глубины fieldOfView = 17000.Of / lightToSceneDistance; glMatrixMode(GL_PROJECTION); glLoadldentity(); gluPerspective(fieldOfView, l.Of, nearPlane, nearPlane + 300.Of); Без излишеств Изображая сцену в первом приближении (с точки зрения источника света), мы в дей- ствительности не хотим се видеть Все, что нам требуется, — получающийся в резуль- тате буфер глубины Поэтому мы рисуем в заднем буфере и нс собираемся переклю- чать буферы Эту итерацию можно ускорить еще больше, маскируя запись в буфер цветов Кроме того, поскольку нас интересуют только коды глубины, нам совершен- но безразлично освещение, плавное затенение и все остальное, никак нс влияющие на результат Поэтому все эти излишества отключаются Все, что нужно, — задать чистую геометрию glShadeModel(GL_FLAT); glDisable(GL_LIGHTING); glDisable(GL_COLOR_MATERIAL); glDisable(GL_NORMALIZE); glColorMask(0, 0, 0, 0); Результат выполнения кода, приведенною в листинге 18 1, невидим, но для иллю- страции на рис. 18 2 визуализировано содержимое буфера глубины Листинг 18.1. Визуализация в карту тени с точки зрения источника света // Вызывается для рисования объектов сцены void DrawModels(void) { // Рисуется плоскость, на которой располагаются объекты glColor3f(O.Of, O.Of, 0.90f); // Синий glNormal3f(O.Of, l.Of, 0 Of); glBegin(GL_QUADS); glVertex3f(-100.Of, -25.Of, -lOO.Of); glVertex3f(-100.Of, -25.Of, 100 Of); glVertex3f(lOO.Of, -25 Of, lOO.Of); glVertex3f(lOO.Of, -25.Of, -lOO.Of); glEnd(); // Рисуем красный куб glColor3f(1.Of, O.Of, O.Of), glutSolidCube(48.Of);
844 Часть III. OpenGL: следующее поколение Рис. 18.2. Если бы мы могли видеть буфер глубины, он выглядел бы так // Рисуем зеленую сферу glColor3f(O.Of, l.Of, O.Of); glPushMatrix() ; glTranslatef(-60.Of, O.Of, O.Of); glutSolidSphere(25.Of, 50, 50); glPopMatrix() ; // Рисуем желтый конус glColor3f(l.Of, l.Of, O.Of); glPushMatrix () ; glRotatef(-90.Of, l.Of, O.Of, O.Of); glTranslatef(60.Of, O.Of, -24.Of); glutSolidCone(25.Of, 50.Of, 50, 50); glPopMatrix(); // Рисуем пурпурный тор glColor3f(l.Of, O.Of, l.Of); glPushMatrix(); glTranslatef(O.Of, O.Of, 60.0f); glutSolidTorus(8.Of, 16.Of, 50, 50); glPopMatrix(); // Рисуем голубой октаэдр glColor3f(O.Of, l.Of, l.Of); glPushMatrix(); glTranslatef(O.Of, O.Of, -60.Of); glScalef(25.Of, 25.Of, 25.Of); glutSolidOctahedron(); glPopMatrix(); } // Вызывается для регенерации карты тени void RegenerateShadowMap(void) { GLfloat lightToSceneDistance, nearPlane, fieldOfView; GLfloat lightModelview[16], lightProjection[16];
Глава 18 Текстуры глубины и тени 845 // Записываем точность измерения глубины (где оно полезно) lightToSceneDistance = sqrt(lightPos[0] * lightPos[0] + lightPos[1] * lightPos[1] + lightPos[2] * lightPos[2]); nearPlane = lightToSceneDistance - 150.Of; if (nearPlane < 50.Of) nearPlane = 50.Of; // Заполняем текстуру глубины с помощью сцены fieldOfView = 17000.Of / lightToSceneDistance; glMatrixMode(GL_PROJECTION); glLoadldentity() ; gluPerspective(fieldOfView, l.Of, nearPlane, nearPlane + 300.0f); glGetFloatv(GL_PROJECTION_MATRIX, lightProjection); // Переключаемся на точку зрения источника света glMatrixMode(GL_MODELVIEW); glLoadldentity(); gluLookAt(lightPos[0], lightPos[l], lightPos[2], O.Of, O.Of, O.Of, O.Of, l.Of, O.Of); glGetFloatv(GL_MODELVIEW_MATRIX, lightModelview); glViewport(0, 0, shadowSize, shadowSize); // Очистить только буфер глубины glClear(GL_DEPTH_BUFFER_BIT); 11 Здесь нам нужны только коды глубины glShadeModel(GL_FLAT); gIDisable(GL_LIGHTING); gIDisable(GL_COLOR_MATERIAL); gIDisable(GL_NORMALIZE); glColorMask(0, 0, 0, 0); 11 Преодолели неточность glEnable(GL_POLYGON_OFFSET_FILL); // Рисуем объекты на сцене DrawModels() ; // Копируем коды глубины в текстуру глубины glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, 0, 0, shadowSize, shadowSize, 0); 11 Восстанавливаем нормальное состояние рисования glShadeModel(GL_SMOOTH); glEnable(GL_LIGHTING); glEnable(GL_COLOR_MATERIAL); glEnable(GL_NORMALIZE); glColorMask(l, 1, 1, 1); gIDisable(GL_POLYGON_OFFSET_FILL); // Устанавливаем матрицу текстуры для проекции карты тени glMatrixMode(GL_TEXTURE); glLoadldentity(); glTranslatef(0.5f, 0.5f, 0.5f); glScalef(0.5f, 0.5f, 0.5f); glMultMatrixf(lightProjection) ; glMultMatrixf(lightModelview);
846 Часть III OpenGL следующее поколение Новый тип текстуры Итак, требуется скопировать глубины из буфера глубины в текстуру, чтобы их можно было использовать как карту тени OpenGL позволяет копировать коды непосред- ственно в текстуру посредством функции glCopyTex!mage2D До версии OpenGL 1 4 эта возможность присутствовала только для кодов цвета, однако сейчас доступны также текстуры глубины Текстуры глубины просто добавляют новый тип текстурных данных Суще- ствуют стандартные форматы для данных о красном, зеленом и синем цветах и/или параметре альфа (яркости или интенсивности) Теперь к этому списку мы добавляем стандартный формат глубины Запрашивать можно следующие форма- ты внутреннего представления gl_depth_component16, gl_depth_component24 и GL_depth_C0Mp0NENT32, отличающиеся числом битов на тексель. Как правило, подходит формат, соответствующий точности буфера глубины Чтобы сделать жизнь проще, в OpenGL предлагается общий внутренний формат gl_depth_component, который сам выбирает конкретный формат, соответствующий буферу глубины После рисования в буфере глубины картинки, которую “видит” источник света, нужно скопировать эти данные непосредственно в текстуру глубины. Таким образом, не придется использовать glReadPixels и glTex!mage2D glCopyTexImage2D(GL_TEXTURE_2D, О, GL_DEPTH_COMPONENT, О, 0, shadowsize, shadowSize, 0); Обратите внимание на то, что код, приведенный в листинге 18 1 (рисование вида сцены с позиции источника света и регенерация карты тени), следует выполнять толь- ко тогда, когда на сцене движутся объекты или источник света Если перемещается только камера, текстуру глубины можно использовать без изменений Помните, что при перемещении камеры вид сцены с позиции источника света не меняется (Сама камера невидима) Таким образом в данном случае можно повторно использовать существующую карту тени Сперва рисуются тени?! Да, вначале действительно рисуются тени Вы можете спросить “Если тени опреде- ляются как отсутствие света, зачем вообще их рисовать9” Строго говоря, рисовать тень не нужно, если у вас есть единственный точечный источник света. Оставив тени черными, вы получите эффект, отвечающий вашей цели Однако если вам не нуж- ны угольно-черные тени, а требуется, чтобы были видны детали внутри затененных областей, нужно сымитировать на сцене рассеянный свет GLfloat lowAmbient[4] = 0 If, O.lf, O.lf, l.Of; GLfloat lowDiffuse[4] = 0.35f, 0.35f, 0.35f, l.Of; glLightfv(GL_LIGHTO, GL_AMBIENT, lowAmbient); glLightfv(GL_LIGHTO, GL_DIFFUSE, lowDiffuse); // Рисует объекты на сцене DrawModels();
Глава 18. Текстуры глубины и тени 847 Рис. 18.3. Пока не нарисованы освещенные области, вся сцена находится в тени Чтобы предоставить информацию о форме, мы добавили также диффузное осве- щение. Если же использовать только рассеянный свет, закрашенные одним цветом области будут иметь неоднозначную форму. На рис. 18.3 показана сцена, пока что полностью лежащая в тени. Некоторые реализации OpenGL поддерживают расширение GL_ARB_shadow_am- bient, делающее первую итерацию (рисование теней) необязательной. В этом случае и затененные, и освещенные области рисуются одновременно. Подробнее эта схема оптимизации рассмотрена ниже. И был свет Прямо сейчас у нас имеется весьма плохо освещенная сцена. Чтобы получить тени, нужны ярко освещенные области, которые контрастно подчеркнут тускло освещен- ные фрагменты, превратив их в тени. Возникает вопрос: как определить, какие обла- сти освещать? Данный вопрос является ключевым моментом при отображении тени. Определив, где рисовать, мы раскрасим эти области в яркие оттенки, просто исполь- зуя большие коэффициенты освещения (вдвое больше, чем для затененных областей). GLfloat ambientLight[] = 0.2f, 0.2f, 0.2f, l.Of; GLfloat diffuseLight[] = 0.7f, 0.7f, 0.7f, l.Of; glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight); glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);
848 Часть III OpenGL-следующее поколение Рис. 18.4. Большой стрелкой в центре показаны преобразования, кото- рые нужно применить к текстурным координатам, линейным в системе наблюдения Проектирование карты тени: “зачем?” Помните код матрицы текстуры из листинга 18.1? Пришло время его объяснить. Основной нашей целью является проектирование карты тени (вида с позиции источ- ника света) сцены обратно на сцену так, будто свет исходит от источника света, но наблюдение ведется с позиции камеры Мы проектируем полученные глубины, пред- ставляющие расстояние от источника света до первого объекта, на который падают лучи света. Перевод текстурных координат в нужную систему координат потребует немного математических расчетов. Если вас интересует только вопрос “как?”, а не вопрос “зачем?”, можете сразу переходить к следующему разделу. В главе 4, “Геометрические преобразования: конвейер”, мы объясняли процесс преобразования вершин из пространства объектов в систему координат наблюдения, затем — в усеченное пространство нормированных координат устройства и, нако- нец, — в пространство окна. В работе с этими преобразованиями участвуют две матри- цы. одна представляет проекцию с позиции источника света, вторая — обычную про- екцию камеры Два набора соответствующих преобразований показаны на рис. 18.4. Процесс проектирования текстуры обычно начинается с генерации текстурных координат, линейных в системе наблюдения В результате этого процесса автомати- чески создаются текстурные координаты. В отличие от генерации текстурных ко- ординат, линейных в системе объекта, эти координаты не привязаны к геометрии. В данном случае подразумевается как бы кинопроектор, проектирующий текстуру на сцену. Однако здесь проекция выполняется не на плоский экран, как в кинотеатре. Представьте, что вы идете перед проектором, и фильм проектируется на ваше тело, имеющее неправильную форму Тот же процесс происходит и здесь В конце нам нужно получить текстурные координаты, указывающие в нашу карту тени из усеченного пространства источники света. Начинаем с проектирования тек- стурных координат, линейных в системе наблюдения, в пространство “глаза камеры”. Следовательно, вначале нужно перейти во внешнюю систему координат, потом — в систему с центром в источнике света, а затем — в усеченное пространство источ-
Глава 18. Текстуры глубины и тени 849 ника света. Это преобразование можно обобщить следующей последовательностью матричных умножений. Л/ = ^источник х ^источник х Мкамера Однако это еще не все. Усеченного пространства источника света недостаточно. Помните, что для всех координат (х, у и z) усеченное пространство ограничивается диапазоном [-1,1]. Текстуру глубины карты тени, подобно всем стандартным двух- мерным текстурам, нужно индексировать в диапазоне [0,1]. Кроме того, глубины, с ко- торыми нужно сравнивать значения, принадлежат диапазону [0,1], поэтому коорди- нату z также нужно перевести в этот диапазон Для этого следует выполнить масшта- бирование с коэффициентом одна вторая (S) и сместить результат на одну вторую (В). М — В X S X Ристочник Х •ЛИ'ИСТОЧНИК X МУкамерэ Если вы не знакомы с матричной формой записи, принятой в OpenGL, у вас мо- жет возникнуть вопрос, почему эти матрицы идут в обратном порядке. Ведь вначале нужно применить матрицу, обратную к относящейся к камере матрице проекции мо- дели, а матрица трансляция (или смещения) на одну вторую применяется последней. В чем дело? В действительности все очень просто. OpenGL действует матрицей (М) на координату (Т) в обратном порядке. Поэтому чтобы представлять порядок преоб- разований, применяющихся к координатам, следует читать выражения справа налево: T' = MxT = BxSx Ристочник X Л/Уисточник X X Т Это представление является стандартным. Больше о нем сказать нечего. Двигаем- ся дальше Проектирование карты тени: “как?” Понятно, что для того, чтобы получить что-то, связанное с текстурой карты тени, к текстурным координатам, линейным в системе наблюдения, нужно применить мат- ричные преобразования. Однако как применить эти преобразования? Подобно положениям вершин текстурные координаты также автоматически под- вергаются матричным преобразованиям Однако вместо матриц проекции модели и проектирования текстурные координаты умножаются только на одну матрицу тек- стуры Специально ничего активизировать не надо; матрица текстуры всегда приме- няется ко всем текстурным координатам вне зависимости от того, сгенерированы ли они автоматически или явно предоставлены с помощью точек входа непосредствен- но режима, массивов вершин и т.д. Вы могли даже не знать о применении матрицы текстуры, поскольку применение по умолчанию единичной матрицы указывает на то, что текстурные координаты должны оставаться неизменными. Чтобы произвести необходимые действия с текстурными координатами, мы будем использовать указанную матрицу текстуры вместе с генерацией текстурных коорди- нат. Чтобы задать матрицу текстуры, начнем с единичной матрицы и умножим ее на все требуемые преобразования, рассмотренные в предыдущем разделе
850 Часть III OpenGL следующее поколение glGetFloatv(GL_PROJECTION_MATRIX, lightProjection); glGetFloatv(GL_MODELVIEW_MATRIX, lightModelview); // Устанавливается матрица текстуры для проектирования карты тени glMatrixMode(GL_TEXTURE); glLoadldentity(); glTranslatef(0.5f, 0.5f, 0.5f); glScalef(0.5f, 0.5f, 0.5f); glMultMatrixf(lightProjection); glMultMatrixf(lightModelview); При установке матриц проекции света и проекции модели перед рисованием ви- да сцены с позиции источника света, удобно запросить и записать эти матрицы, чтобы позже применить их к матрице текстуры Действия масштабирования и сме- щения, переводящие [—1,1] в [0,1], легко выразить через вызовы функций glScalef и glTranslatef. А где умножение на матрицу, обратную к связанной с камерой матрице проекции модели? Хорошо, что спросили' OpenGL предупреждает потребность в этом пре- образовании при генерации линейных в системе наблюдения текстурных координат. Умножение справа на обратную к текущей матрице проекции модели включается в уравнения плоскости глаза. Все, что вам нужно, — гарантировать, что на момент вызова функции glTexGenfv задана связанная с камерой матрица проекции. glMatrixMode(GL_MODELVIEW); glLoadldentity(); gluLookAt(cameraPos[0], cameraPos[1], cameraPos[2], O.Of, O.Of, O.Of, O.Of, l.Of, O.Of); GLfloat sPlane[4] = (l.Of, O.Of, O.Of, O.Of}; GLfloat tPlane[4] = {O.Of, l.Of, O.Of, O.Of}; GLfloat rPlane[4] = {O.Of, O.Of, l.Of, O.Of}; GLfloat qPlane[4] = (O.Of, O.Of, O.Of, l.Of}; /I Задание плоскости глаза для проектирования карты тени на сцену glEnable(GL_TEXTURE_GEN_S); glEnable(GL_TEXTURE_GEN_T); glEnable(GL_TEXTURE_GEN_R); glEnable(GL_TEXTURE_GEN_Q); glTexGenfv(GL_S, GL_EYE_PLANE, sPlane); glTexGenfv(GL_T, GL_EYE_PLANE, tPlane); glTexGenfv(GL_R, GL_EYE_PLANE, rPlane); glTexGenfv(GL_Q, GL_EYE_PLANE, qPlane); glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR); glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR) ; glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR) ; glTexGeni(GL_Q, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
Глава 18 Текстуры глубины и тени 851 Сравнение с тенью Итак, мы визуализировали вид сцены с позиции источника света и скопировали его в карту тени. У нас есть текстурные координаты для обращения к спроектированной карте тени Сцена слабо освещена и подготовлена для реального света Мы почти за- вершили сцену Осталось только смешать ингредиенты. Первым делом имеется один важный этап, который мы можем выполнить в процессе установки и “забыть” о нем // Удаление скрытых поверхностей glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); // Устанавливаем состояние текстуры, которое никогда не меняется glGenTextures(1, &shadowTextureID); glBindTexture(GL_TEXTURE_2D, shadowTexturelD); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); Мы устанавливаем проверку по глубине с условием “меньше или равно” (GL_LEQUAL), чтобы можно было рисовать освещенные поверхности поверх зате- ненных. В противном случае (поскольку геометрически объекты идентичны) итера- ция, связанная с рисованием освещенных поверхностей, никогда нс пройдет провер- ку по глубине, и ее выполнение ничего не изменит на сцене с тускло освещенны- ми объектами После этого мы генерируем карту тени и связываемся с ней (в нашем примере это единственная текстура) Режим обертки текстурных координат устанавливается равным GL_CLAMP (ограничение согласно допустимому диапазону) Повторять про- ектирование нет смысла Например, если свет влияет только на часть сцены, а камера увеличивает изображение другой (неосвещенной) части сцены, наново повторять кар- ту тени для всей сцены нецелесообразно Все, что нужно, - это ограничить текстур- ные координаты согласно допустимому диапазону, чтобы карта тени проектировалась только на освещенную часть сцены Текстуры глубины содержат только один исходный компонен!, представляющий глубину. Однако текстурная среда ожидает четырех компонен юв. задающих крас- ный, зеленый, синий цвета и значение альфа. При отображении информации о глу- бине OpenGL предлагает выбор текстура глубины может представляться режимами GL_ALPHA (0,0,0,D), GL_LUMINANCE (D,D,D,1) И GL_INTENSITY (D,D,D,D) Поскольку нам нужно, чтобы информация о глубине затонула вес четыре канала, выбираем режим интенсивности glTexParameteri(GL_TEXTURE_2D,GL_DEPTH_TEXTURE_MODE,GL_INTENSITY); Разумеется, чтобы карта тени “заиграла”, нужно активизировать текстурирова- ние. Режим сравнения устанавливается равным gl_COMPARE_r_to_texture Если этого не сделать, все, что мы получим, — значение глубины в текстуре Нас это нс устраивает, поскольку требуется глубина по сравнению с компонентом R текстур- ных координат // Задается сравнение с тенью glEnable(GL_TEXTURE_2D); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);
852 Часть III OpenGL: следующее поколение Компонент R текстурных координат представляет расстояние от источника света до поверхности объекта. Значение глубины, записанное в карте тени, представляет расстояние от источника света до первой поверхности, на которую падает свет Срав- нивая эти две величины, можно сказать, является ли поверхность первой на пути луча света/ или она находится дальше от источника, а следовательно, лежит в тени первой (освещенной) поверхности. D' = (Я <= £>)?1 • О Доступны и другие функции сравнения. Фактически OpenGL 1.5 позволяет ис- пользовать те же операторы сравнения, что применяются при сравнении глубин. По умолчанию применяется значение gl_lequal, поэтому в нашем случае ничего ме- нять не нужно. Существует еще две настройки, которые нужно рассмотреть- фильтры увеличения и уменьшения В нашем случае будет использоваться точечная дискретизация glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); Стоит сказать, что некоторые реализации могут сглаживать края теней, если акти- визировать билинейную или трилинейную фильтрацию В таких реализациях выпол- няется несколько операций сравнения, результаты которых усредняются. Называется подобная схема взвешенной фильтрацией соседей (percentage-closer filtering). Отлично. У нас есть набор нулей и единиц Однако мы не собирается рисовать черные и белые точки. Что теперь? Все просто Нужно всего лишь установить режим текстурной среды GL_MODULATE, затем умножить нули и единицы на цвет, получаю- щийся после расчета освещения: glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GLJMODULATE); Наконец-то мы это сделали! Мы нарисовали освещенные области Но постойте’ Там, где должны быть тени, мы просто нарисовали черные области поверх областей, раскрашенных ранее при применении освещения Как сохранить рассеянный свет для затененных областей? Для этого применяется альфа-тест. Мы запросили режим текстуры глубины с учетом интенсивности Следовательно, нули и единицы пред- ставлены как в компоненте альфа, так и в компонентах цвета Используя альфа-тест, можно указать OpenGL отбросить фрагменты, в которых мы не получили единицу // Активизация альфа-теста с целью отбрасывания затененных // фрагментов glAlphaFunc(GL_GREATER, 0.9f); glEnable(GL_ALPHA_TEST); Отлично. Вот теперь все. Результат выполнения конечной программы, представ- ленной в листинге 18.5, показан на рис 18 5 Листинг 18.2. Рисование на сцене общей тени и света // Вызывается для рисования сцены void RenderScene(void)
Глава 18. Текстуры глубины и тени 853 Рис. 18.5. После итерации “общая тень" выполняется итерация “яркое освещение" // Отслеживаем угол камеры glMatrixMode(GL_PROJECTION); glLoadldentity(); gluPerspective(45.0f, l.Of, l.Of, 1000.Of); glMatrixMode(GL_MODELVIEW); glLoadldentity(); gluLookAt(cameraPos[0], cameraPos[1], cameraPos[2], O.Of, O.Of, O.Of, O.Of, l.Of, O.Of); glViewport(0, 0, windowwidth, windowHeight); // Отслеживаем положение источника света glLightfv(GL_LIGHT0, GL_POSITION, lightPos); // Очищаем окно текущим цветом очистки glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if (showShadowMap) { // С образовательной целью отображаем карту тени glMatrixMode(GL_PROJECTION); glLoadldentity(); glMatrixMode(GL_MODELVIEW); glLoadldentity(); glMatrixMode(GLJTEXTURE); glPushMatrix(); glLoadldentity(); glEnable(GL_TEXTURE_2D); glDisable(GL_LIGHTING); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
854 Часть III OpenGL следующее поколение glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // Показываем карту тени при реальном размере относительно // окна glBegin(GL_QUADS); glTexCoord2f(O.Of, O.Of); glVertex2f(-l.Of, -l.Of); glTexCoord2f(l.Of, O.Of); glVertex2f(((GLfloat)shadowSize/(GLfloat)windowWidth) *2.0-1.Of,-l.Of); glTexCoord2f(l.Of, l.Of); glVertex2 f(((GLfloat)shadowSize/(GLfloat)windowWidth) *2.0-1.Of, ((GLfloat)shadowSize/(GLfloat)windowHeight) *2.0-1.Of); glTexCoord2f(0.Of, l.Of); glVertex2f(-l.Of, ((GLfloat)shadowSize/ (GLfloat)windowHeight)*2.0-1.Of); glEndO; gIDisable(GL_TEXTURE_2D); glEnable(GL_LIGHTING) ; glPopMatrix(); glMatrixMode(GL_PROJECTION); gluPerspective(45.Of, l.Of, l.Of, 1000.Of); glMatrixMode(GL_MODELVIEW); ) else if (noShadows) { // Установка простого освещения glLightfv(GL_LIGHTO, GL_AMBIENT, ambientLight); glLightfv(GL_LIGHTO, GL_DIFFUSE, diffuseLight); 11 Рисует объекты на сцене DrawModels(); } else { GLfloat sPlane[4] = l.Of, O.Of, O.Of, O.Of; GLfloat tPlane[4] = O.Of, l.Of, O.Of, O.Of; GLfloat rPlane[4] = O.Of, O.Of, l.Of, O.Of; GLfloat qPlane[4] = O.Of, O.Of, O.Of, l.Of; if (!ambientShadowAvailable) { GLfloat lowAmbient[4] = O.lf, O.lf, O.lf, 1 Of; GLfloat lowDiffuse[4] = 0.35f, 0.35f, 0.35f, l.Of; // Поскольку невозможно задать значение для проверки // "рассеянной тени", вначале // нужна итерация с рисованием рассеянного света . . glLightfv(GL_LIGHTO, GL_AMBIENT, lowAmbient); glLightfv(GL_LIGHTO, GL_DIFFUSE, lowDiffuse);
Глава 18 Текстуры глубины и тени 85S // Рисует объекты на сцене DrawModels(); // Активизация альфа-теста с целью отбрасывания // затененных фрагментов glAlphaFunc(GL_GREATER, 0.9f); glEnable(GL_ALPHA_TEST); ) glLightfv(GL_LIGHTO, GL_AMBIENT, ambientLight); glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight); // Сравнение с тенью glEnable(GL_TEXTURE_2D); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Устанавливаем плоскость наблюдения для проектирования // карты тени на сцену glEnable(GL_TEXTURE_GEN_S); glEnable(GL_TEXTURE_GEN_T); glEnable(GL_TEXTURE_GEN_R); glEnable(GL_TEXTURE_GEN_Q); glTexGenfv(GL_S, GL_EYE_PLANE, sPlane); glTexGenfv(GL_T, GL_EYE_PLANE, tPlane); glTexGenfv(GL_R, GL_EYE_PLANE, rPlane); glTexGenfv(GL_Q, GL_EYE_PLANE, qPlane); // Рисует объекты на сцене DrawModels(); glDisable(GL_ALPHA_TEST); glDisable(GL_TEXTURE_2D); glDisable(GL_TEXTURE_GEN_S); glDisable(GL_TEXTURE_GEN_T); glDisable(GL_TEXTURE_GEN_R); glDisable(GL_TEXTURE_GEN_Q); } if (gIGetError() ' = GL_NO_ERROR) fprintf(stderr, "GL Error'\n"); // Выводит команды рисования из стека glutSwapBuffers(), Эта функция выполняет необходимую инициализацию в контексте визуализации. d SetupRC() const GLubyte ‘version; fprintf(stdout, "Shadow Mapping Demo\n\n"); // Убеждаемся в доступности требуемых функциональных // возможностей1 version = gIGetString(GL_VERSION); if (((version[0] ' = '1') II (version[l] '= ' ') ||
856 Часть III OpenGL: следующее поколение (version[2] < '4') || (version[2] > '9')) && // 1.4+ (!glt!sExtSupported("GL_ARB_shadow"))) { fprintf(stderr, "Neither OpenGL 1.4 nor GL_ARB_shadow" " extension is available!\n"); Sleep(2000); exit(O); } // Проверка наличия необязательных расширений if (glt!sExtSupported("GL_ARB_shadow_ambient")) { ambientShadowAvailable = GL_TRUE; else ( fprintf(stderr, "GL_ARB_shadow_ambient extension" " not available!\n"); fprintf(stderr, "Extra ambient rendering pass " will be required.\n\n" ) ; Sleep(2000); } fprintf(stdout, "Controls:\n") ; fprintf(stdout, "\tc\t\tControl camera movement\n"); fprintf(stdout, "\tl\t\tControl light movement\n\n") ; fprintf(stdout, "\tx/X\t\tMove +/- in x direction\n"); fprintf(stdout, "\ty/Y\t\tMove +/- in у direction\n"); fprintf(stdout, "\tz/2\t\tMove +/- in z direction\n\n"); fprintf(stdout, "\tf/F\t\tChange polygon offset factor" " +/-\n\n"); fprintf(stdout, ”\ts\t\tToggle showing current shadow" " map\n"); fprintf(stdout, "\tn\t\tToggle showing scene without" " shadows\n\n") ; fprintf(stdout, "\tq\t\tExit demo\n\n"); // Черный фон glClearColor(0.Of, O.Of, O.Of, l.Of ); // Удаление скрытых поверхностей glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); glPolygonOffset(factor, O.Of); // Устанавливаем состояние освещения, которое никогда // не будет меняться glShadeModel(GL_SMOOTH); glEnable(GL_LIGHTING); glEnable(GL_COLOR_MATERIAL); glEnable(GL_NORMALIZE); glEnable(GL_LIGHT0) ; // Устанавливаем состояние текстуры, которые никогда // не будет меняться glGenTextures(1, &shadowTextureID) ; glBindTexture(GL_TEXTURE_2D, shadowTexturelD); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
Глава 18 Текстуры глубины и тени 857 glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_INTENSITY); if (ambientShadowAvailable) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FAIL_VALUE_ARB, 0.5f); glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR); glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR); glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR); glTexGeni(GL_Q, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR); RegenerateShadowMap(); Два из трех — неплохо В листинге 18.2 вы должны были обратить внимание на код, привязанный к пе- ременной ambientShadowAvailable. Для всего этого примера минимальным тре- бованием является поддержка OpenGL 1.4 или по крайней мере поддержка расши- рения GL_ARB_shadow Если, однако, ваша реализация поддерживает расширение GL_ARB_shadow_ambient, можно существенно уменьшить объем работы. До этого момента мы описали три итерации визуализации: рисование сцены с по- зиции источника света в карту тени, рисование сцены, слабо освещенной рассеянным светом, и рисование освещения (в сравнении с тенью) Помните, что карту тени нуж- но генерировать только при изменении положения источника света или объектов на сцене Таким образом, существует три итерации, а иногда — всего две Используя рас- ширение GL_ARB_shadow_ambient, можно вообще избежать итерации, связанной с построением рассеянного света. Вместо нулей и единиц, порождаемых при сравнении с тенью, указанное расши- рение позволяет замещать нули другими значениями, если при сравнении получается отрицательный результат Таким образом, если это значение установить равным од- ной второй, затененные области будут освещены вполовину, те. так же, как у нас получалось после итерации рассеянного света if (ambientShadowAvailable) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FAIL_VALUE_ARB, 0.5f); Мало того, таким образом мы снимаем потребность в активизации альфа-теста. Несколько слов о смещении многоугольника Даже на поверхности, ближайшей к источнику света, всегда обнаруживаются неболь- шие различия в значениях с плавающей запятой, соотнесенных с текстурной коорди- натой R и глубиной из карты тени Это может привести к “самозатенению”, когда эта неточность приводит к затенению самой поверхности. Чтобы устранить эту проблему, при визуализации в карту тени можно внести смещение глубины:
858 Часть III. OpenGL: следующее поколение Рис. 18.6. Неточность сравнения с тенью проявляется как “мерцание" (“z- fighting") буфера глубины // Боремся с неточностью glEnable(GL_POLYGON_OFFSET_FILL); glPolygonOffset(factor, O.Of); Хотя смещение глубины гарантирует, что ненужные поверхности затеняться не будут, оно также искусственно смещает положение теней. Поэтому смещение много- угольников следует использовать очень аккуратно. Например, на рис. 18.6 показано, что получится, если не использовать достаточное смещение глубины. Резюме Отображение тени является полезной технологией для получения реалистичного освещения без чрезмерной дополнительной обработки. Чтобы определить, какие объ- екты на сцене будут освещены, а какие попадут в тень, можно использовать вид сцены с позиции источника света. Текстуры глубины — это специальные текстуры, разрабо- танные для хранения содержимого буфера глубины с целью использовать в качестве карты тени. Основой проектирования текстуры является генерация текстурных коор- динат, линейных в системе наблюдения. Для изменения ориентации текстуры (воз- вращения текстурных координат в усеченное пространство источника света) можно использовать матрицу текстуры. Сравнение с тенью позволяет различать затенен- ные и освещенные области. Для уменьшения числа итераций визуализации удобно применять расширение GL_ARB_shadow_ambient.
Глава 18 Текстуры глубины и тени 859 Справочная информация glAlphaFunc Цель: Установить функцию альфа-теста Включаемый файл: <gl.h> Синтаксис: void glAlphaFunc(GLenum func, GLclampf ref); Описание: Устанавливает функцию альфа-теста, которая будет использоваться при сравнении с эталонным значением альфа При активизированном альфа-тесте значение альфа каждого фрагмента сравнивается с приведенным эталонным значением согласно реляционному оператору функции сравнения Если значение не проходит проверку, фрагмент отбрасывается Данный тест выполняется после теста разрезания, но перед сличением с трафаретом и проверкой глубины Параметры: func (тип GLenum) Реляционный оператор, используемый в операциях сравнения альфа-теста Значением может быть одна из следующих констант GL_ALWAYS альфа-тест всегда проходится GL_EQUAL альфа-тест проходится, если значение альфа фрагмента равно эталонному GL_GEQUAL- альфа-тест проходится, если значение альфа фрагмента больше или равно эталонному GL_GREATER альфа-тест проходится, если значение альфа фрагмента больше эталонного GL_LEQUAL альфа-тест проходится, если значение альфа фрагмента меньше или равно эталонному GL_LESS альфа-тест проходится, если значение альфа фрагмента меньше эталонного GL_NEVER альфа-тест не проходится никогда GL_NOTEQUAL альфа-тест проходится, если значение альфа фрагмента не равно эталонному (тип GLclampf) Эталонная величина, с которой сравниваются поступающие значения альфа Это значение ограничивается согласно допустимому диапазону [0,1] Что возвращает: Ничего См. также: glDepthFunc, glScissor, glStencilFunc
860 Часть III OpenGL следующее поколение glColorMask Цель: Установить маску записи для передачи цветов в буфер кадров Включаемый файл: <gl.h> Синтаксис: void glColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); Описание: Устанавливает маску записи, с помощью которой можно разрешить или запретить запись отдельных компонентов цвета в буфер кадров. Значение TRUE указывает, что компонент следует записывать. В противном случае компонент не будет записан в буфер Параметры: red (тип GLboolean) Селектор маски записи для красного компонента цвета green (тип GLboolean) Селектор маски записи для зеленого компонента цвета blue (тип GLboolean) Селектор маски записи для синего компонента цвета alpha (тип GLboolean) Селектор маски записи для альфа-компонента цвета Что возвращает: Ничего См. также: glDepthMask, glStencilMask glCopyTexSublmage Цель: Заменить часть текстурного изображения пикселями из буфера кадров Включаемый файл: <gl.h> Синтаксис: void glCopyTexSublmagelD(GLenum target, Glint level, GLint xoffset, GLint x, GLint.y, GLsizei width); void glCopyTexSub!mage2D(GLenum target, Glint level, GLint xoffset, GLint yoffset, .GLint x, GLint y, GLsizei width, GLsizei height); void glCopyTexSub!mage3D(GLenum target, Glint level, GLint xoffset, GLint yoffset, .GLint zoffset, GLint x, GLint y, GLsizei width); Описание- Копирует коды цвета или глубины из буфера кадров, замещая часть или все тексели заданного ранее текстурного изображения Если внутренний формат текстуры является цветным, коды цвета копируются из текущего буфера GL_READ_BUFFER Если внутренний формат соответствует информации о глубине, из буфера глубины копируются коды глубины Параметры I [елевая текстура Значение должно быть равно ( ’. | слот) GL_TEXTURE_1D для glCopyTexSublmagelD, GL_TEXTURE_2D для glCopyTexSub!mage2D И GL_TEXTURE_3D для glCopyTexSub!mage3D
Глава 18 Текстуры глубины и тени 861 level (тип GLint) xoffset (тип GLint) yoffset (тип GLint) zoffset (тип GLint) x (тип GLint) Частично или полностью замещаемый уровень множественной текстуры Координата х точки, с которой начинается замещение Координата у точки, с которой начинается замещение Координата z точки, с которой начинается замещение Значение х левого края области (в координатах окна), копируемой из буфера кадров у (тип GLint) Значение у нижнего края области (в координатах окна), копируемой из буфера кадров width Ширина области, копируемой из буфера кадров (тип GLsizei) height (тип GLsizei) Что возвращает: Высота области, копируемой из буфера кадров Ничего См. также: glCopyPixels, glCopyTexImage, glReadBuffer, glTexImage, glTexSublmage gl PolygonOffset Цель: Установить смещение по глубине, применямое к многоугольникам Включаемый файл: <gl. h> Синтаксис: void glPolygonOffset (GLfloat factor, GLfloat units); Описание: Устанавливает для многоугольников смещение вглубь и единицы его измерения. Используя токены GL_POLYGON_OFFSET_POINT, GL_POLYGON_OFFSET_LINE И GL_POLYGON_OFFSET_FILL, можно по-отдельности задавать смещение вглубь для многоугольников в точечном, линейном и сплошном режиме соответственно Значение смещения прибавляется или вычитается для всех фрагментов многоугольника Если эта возможность активизирована, она используется в процессе растеризации Параметры: factor (тип GLfloat) Масштабный коэффициент, используемый для генерации переменного смещения глубины путем умножения на максимальную крутизну характеристики глубины многоугольника units (тип GLfloat) Что возвращает: Постоянное смещение, умножаемое на минимальную степень детализации (разрешение) буфера глубины Ничего См. также: gIDepthFunc, glPolygonMode

ГЛАВА 19 Программируемый конвейер: это не тот OpenGL, который помнит ваш отец Бенджамин Липчак Из ЭТОЙ ГЛАВЫ ВЫ УЗНАЕТЕ • За что отвечает конвейер OpenGL с фиксированными функциональными возмож- ностями • Каскады конвейера, которые можно заменить новыми программируемыми схема- ми затенения • Расширения шейдеров, дающие новые функциональные возможности Графическое аппаратное обеспечение традиционно разрабатывается для быстрого выполнения стандартного набора жестко запрограммированных вычислений Разные этапы вычислений можно опускать, параметры — настраивать, но сами вычисления остаются фиксированными Именно поэтому старая парадигма дизайна графических процессоров (GPU) называется фиксированными функциональными возможностями (fixed functionality). Существует тенденция к разработке универсальных графических процессоров Подобно центральным процессорам, эти графические процессоры можно программи- ровать, снабжая произвольными последовательностями команд, выполняющих прак- тически все возможные вычисления. Самым большим отличием является то, что графические процессоры настроены на операции с плавающей запятой, являющиеся наиболее распространенными в мире графики Вы можете представить это себе следующим образом. Фиксированные функци- ональные возможности подобны рецепту печенья. OpenGL позволяет немного из- менить рецепт в некоторых местах. Изменить количество ингредиента, температуру духовки Вам нс нужны шоколадные чипсы9 Прекрасно Отключите их. Однако что бы вы ни делали, вы все равно получите печенье. Рассмотри программирование Хотите выбрать собственные ингредиенты? Пре- красно Хотите использовать для готовки микроволновую печь, сковородку или гриль? Как вам будет угодно. Вместо печенья вы можете приготовить пирог, филе или разо- греть остатки вчерашнего ужина Возможности бесконечны. Вся кухня, все ингре- диенты, бытовые электроприборы, горшки, котелки, сковороды и противни в вашем распоряжении Существуют входы и выходы, наборы команд и временное хранилище этапа программируемого конвейера.
864 Часть III OpenGL следующее поколение В данной главе мы рассмотрим привычный конвейер OpenGL, а затем опишем его части, которые можно заменить программируемыми этапами. Начнем со старого Прежде чем мы приступим к замене этапов, рассмотрим традиционный конвейер ви- зуализации OpenGL. На нескольких первых этапах ведется обработка вершин. Затем растеризируются примитивы, дающие фрагменты. Наконец, на фрагменты налагает- ся текстура, туман и применяются другие операции, определенные для фрагментов, после чего все они передаются в буфер кадров Такой конвейер с фиксированными функциональными возможностями представлен на рис. 19 1 в виде схемы В следующих разделах отдельно рассмотрены этапы обработки вершин и этапы обработки фрагментов. Фиксированная обработка вершин Этапы, связанные с обработкой вершин, начинаются с введенного набора атрибутов вершин В число этих атрибутов входят положение в пространстве объекта, нор- маль, основной и вторичный цвета, координаты тумана и текстурные координаты Окончательным результатом выполнения этих этапов обработки являются положение в усеченном пространстве, основной и вторичный цвета передней и задней сторо- ны, координата тумана, текстурные координаты и размер точки. Все, что происходит между этими вехами, разбито на четыре этапа Преобразование вершин В схеме фиксированных функциональных возможностей положение вершины преоб- разовывается из пространства объекта в усеченное пространство Для этого коорди- наты пространства объекта вначале умножаются на матрицу проекции модели, что переводит их в пространство наблюдения Затем результат умножается на матрицу проекции, переходя в усеченное пространство. Приложение может управлять содержимым этих двух матриц, но указанные мат- ричные умножения происходят всегда. Единственная возможность “пропустить” этот этап связана с загрузкой единичных матриц, когда в итоге вы получаете то же поло- жение, с которого начали Нормаль каждой вершины также преобразовывается, и на этот раз переход проис- ходит из пространства объекта в пространство наблюдения (с целью последующего использования при освещении сцены) Нормаль умножается на матрицу, обратную к матрице проекции модели, после чего (необязательно) масштабируется или норми- руется При наложении эффектов освещения нормаль должна представляться единич- ным вектором, поэтому, если вы не передали единичные векторы нормали, а матрица проекции модели такова, что векторы сохранят единичную длину, векторы нужно либо перемасштабировать (если проекция модели порождает только равномерное масштабирование), либо нормировать Преобразования и нормали рассмотрены в главах 4, “Геометрические преобразо- вания конвейер”, и 5, “Цвет, материалы и освещение, основы”
Глава 19 Программируемый конвейер 865 Вершины и соотнесенные Фрагменты и соотнесенные с ними атрибуты с ними данные Рис. 19.1. Конвейер визуализации с фиксированными функциональ- ными возможностями представляет старый подход Освещение В качестве входных данных этап освещения принимает цвет нормали и положение вершин На его выход поступает два цвета (первичный и вторичный) и в некоторых случаях еще один набор цветов (для передней и задней граней) На данном этапе определяются цветные свойства материалов, свойства света и разнообразные пере- ключатели glEnable/glDisable.
866 Часть III OpenGL следующее поколение Конфигурация освещения позволяет многое, можно активизировать несколько ис- точников света (до восьми или больше, в зависимости от реализации), каждый из которых имеет массу параметров — положение, цвет, тип и тд Можно задавать свой- ства материалов, имитируя различные поверхности Кроме того, можно активизи- ровать двухстороннее освещение, чтобы для передних и задних многоугольников генерировались различные цвета Этап применения освещения можно пропустить целиком, деактивизировав его. Однако, если он активизирован, всегда используются одни и те же жестко закодиро- ванные уравнения Подробности о фиксированных функциональных возможностях этапа освещения рассмотрены в главах 5, "Цвет, материалы и освещение: основы”, и 6, “Подробнее о цветах и материалах” Генерация и преобразование текстурных координат Последним этапом конвейера с фиксированными функциональными возможностя- ми, связанным с обработкой вершин, является обработка текстурных координат OpenGL может автоматически генерировать любую текстурную координату Суще- ствует несколько вариантов выбора уравнения генерации Фактически для каждого компонента каждой текстурной координаты можно выбрать свой режим Если же ге- нерация дсактивизирована, текущая текстурная координата соотносится с вершиной, которую она использует Вне зависимости от того, активизирована ли генерация текстуры, все текстур- ные координаты всегда преобразовываются с помощью матрицы текстуры Если эта матрица — единичная, текстурные координаты не меняются Этап обработки текстурных координат рассмотрен в главах 8, “Наложение тексту- ры основы”, и 9, “Наложение текстуры следующий шаг” Отсечение Если в ходе преобразований, описанных в предыдущих разделах, какая-либо из вер- шин выпадает из объема наблюдения, требуется отсечение Отсеченные вершины отбрасываются, и, в зависимости от типа изображаемого примитива, на пересече- нии примитива и объема наблюдения могут генерироваться новые вершины Цвета, текстурные координаты и другие свойства вершин соотносятся е новыми сгенери- рованными вершинами посредством интерполяции их значений вдоль обрезанного края Пример усеченного примитива показан на рис 19 2 Приложение также может разрешать задание пользовательских плоскостей отсече- ния, которые так дополнительно ограничивают объем отсечения, что отсекаться мо- гут даже примитивы внутри объема наблюдения Данная технология довольно часто используются в медицинских изображениях для представления разреза в объемных данных (например, в ЯМР-томографии для исследования тканей внутри тела) Фиксированная обработка фрагментов На вход этапов, связанных с обработкой фрагментов, поступает собственно фраг- мент и соотнесенные с ним данные Эти данные состоят из различных значений, полученных интерполяцией вдоль отрезка или по треугольнику, и содержат одну или
Глава 19. Программируемый конвейер ... 867 Рис. 19.2. Все три вершины треугольника \ \ отсекаются, при этом вводится шесть новых \ \ вершин N----------------------- несколько текстурных координат, основной и вторичный цвета, а также координату тумана. Результатом обработки фрагмента является один цвет, передаваемый после- дующим операциям с фрагментами (в том числе проверке глубины и смешению). Как и ранее, существует четыре этапа обработки. Наложение текстуры и текстурная среда Наложение текстуры является важнейшим этапом обработки фрагмента. Здесь в каче- стве входа берутся все текстурные координаты фрагмента и его основной цвет. Выхо- дом является новый основной цвет. На данный процесс влияет то, какие текстурные единицы активизированы для текстурирования, какие текстурные изображения свя- заны с этими единицами, и какая функция текстуры установлена текстурной средой. Для обработки каждой активизированной текстурной единицы используется одно- мерная, двухмерная, трехмерная или кубическая карта текстуры, связанная с этой еди- ницей. В зависимости от формата текстуры и функции текстуры, заданной для этой единицы, результат поиска текстуры либо замещает основной цвет фрагмента, либо смешивается с ним. Позже получающиеся цвета всех активизированных текстурных единиц подаются на вход следующей активизированной текстурной единицы. Ре- зультат, полученный на выходе последней активизированной текстурной единицы, считается результатом всего этапа текстурирования. На поиск текстуры влияет множество настраиваемых параметров, в том числе ре- жимы обертки текстурных координат, цвета границы, фильтры уменьшения и увели- чения, ограничение и смещение уровня детализации, текстуры глубины и состояние сравнения с тенью, а также параметр, определяющий, генерируются ли автоматиче- ски цепочки множественных отображений (mipmap). Стандартные функциональные возможности этапа наложения текстуры рассмотрены в главах 8 и 9.
868 Часть III OpenGL'следующее поколение Сумма цветов На вход этапа вычисления суммарного цвета подаются две величины- первичный и вторичный цвет На выходе получается один цвет Ничего сверхъестественного здесь нет. Если суммирование цветов или освещение активизировано, красный, зеле- ный и синий каналы первичного и вторичного цветов суммируются, а затем ограничи- ваются согласно допустимому диапазону [0,1] Если суммирование цветов не активи- зировано, в качестве результата выдается первичный цвет Отметим, что в конвейере с фиксированными функциональными возможностями параметр альфа вторичного цвета никогда не используется. Наложение тумана Если туман активизирован, цвет фрагмента смешивается с постоянным цветом тума- на согласно рассчитанному параметру тумана Этот параметр вычисляется согласно одному из трех жестко запрограммированных уравнений: линейному, экспоненци- альному или экспоненциальному второго порядка. В данных уравнениях параметр тумана привязывается к текущей координате тумана, в качестве которой может вы- ступать приблизительное расстояние от вершины до глаза или произвольное значение, устанавливаемое приложением для каждой вершины Подробнее о фиксированных функциональных возможностях этапа наложения ту- мана рассказано в главе 6. Защита от наложения Наконец, если фрагмент принадлежит примитиву, для которого активизировано сгла- живание, одним из предоставляемых элементов данных является параметр охвата. В большинстве случаев это значение равно 1.0, но для фрагментов на краю гладкой точки, линии или многоугольника охват задается как величина из диапазона от 0.0 и 1.0 Параметр альфа фрагмента умножается на параметр охвата, что при последу- ющем смешении дает гладкие края указанных примитивов. Подробнее этот вопрос рассмотрен в главе 6 Добавим нового Прогулка по дороге воспоминаний требовалась как для того, чтобы освежить вашу память, так и для того, чтобы вы представили, какие настраиваемые, но все же жестко закодированные вычисления выполняются на каждом этапе Теперь забудьте все, что вы только что прочли Мы собираемся заменить большую часть данного материала и ввести новый мировой порядок схемы затенения, или шейдеры (shaders) Схемы затенения иногда называют программами построения теней, и эти терми- ны взаимозаменяемы Шейдеры являются определяемыми приложением программа- ми, которые принимают на себя обязанности этапов конвейера с фиксированными функциональными возможностями Мы предпочитаем термин шейдер, позволяющий избежать путаницы с типичным определением программы, под которым может под- разумеваться любое приложение.
Глава 19 Программируемый конвейер . 869 На рис 19.3 показан упрощенный конвейер, где ранее жестко закодированные каскады заменены индивидуальными программируемыми шейдерами. Программируемые шейдеры вершин Как видно из рис 19 3, входы и выходы шейдера вершин остаются теми же, что были в соответствующих каскадах с фиксированными функциональными возможностями. Необработанные вершины и все их атрибуты передаются не на каскад фиксирован- ных преобразований, как было ранее, а шейдеру вершин На выход шейдера вершин поступают текстурные координаты, цвета, размер точки и координаты тумана, сле- дующие далее по каскаду, те. выдается та же информация, что поступала на выход каскада освещения конвейера с фиксированными функциональными возможностями Таким образом, шейдер вершин заменяет собой три каскада старого конвейера Замещение преобразования вершины Все, что делается в шейдере вершин, зависит целиком от вас Абсолютным миниму- мом (разумеется, при условии, что вы собираетесь что-то нарисовать) будет подача на выход положений вершин в отсеченном пространстве. Подача на выход любой другой информации является необязательной, предоставляемой исключительно по вашему желанию Теперь именно вы должны предложить, как генерировать положе- ния вершин в отсеченном пространстве Традиционно (для имитации преобразований с фиксированными функциональными возможностями) входные положения умножа- ются на матрицы наблюдения модели и проекции, в результате чего получается тре- буемая информация — положения в отсеченном пространстве Однако предположим, что фиксированная проекция не использовалась и верши- ны уже принадлежат отсеченному пространству. В таком случае больше не нужны никакие преобразования Просто скопируйте входное положение в выходное Пред- положим другую ситуацию требуется превратить декартовы координаты в полярные Без проблем1 Вы просто вводите в шейдер дополнительные команды, выполняющие соответствующие вычисления Замещение освещения Если цвет вершин не важен, то расчет освещения не нужен Вы просто копируете цвет с входа в цвет на выходе, или, если цвета не будут использоваться позже, можете вообще не выводить их, оставив неопределенными Внимание! Если в таком случае вы попытаетесь использовать их позже, не подав своевременно на выход схемы зате- нения, “неопределенные” будет означать “ошибочные”! Если вы желаете сгенерировать более интересные цвета, возможности у вас по- истине бесконечны Можно сымитировать освещение конвейера с фиксированными функциональными возможностями, добавив команды, выполняющие обычный на- бор вычислений, возможно, изменив его по своему усмотрению Кроме того, вы можете раскрасить вершины согласно их положению, нормалям или любой дру- гой информации
870 Часть III OpenGL- следующее поколение Вершины и соотнесенные Фрагменты и соотнесенные с ними атрибуты с ними данные Рис. 19.3. Эта схема похожа на предыдущую, но в действительности шейдеры могут делать все, на что способны старые каскады с фиксированными функциональными возможностями, плюс много чего другого Замещение обработки текстурных координат Если вам не требуется генерация текстурных координат, кодировать ее в шейдере не нужно. То же относится и к преобразованиям текстурных координат. Если они вам не нужны, их незачем реализовывать. Можно просто скопировать входные текстур- ные координаты в их выходные аналоги Или же (как в рассмотренном выше случае с цветами), если вы не собираетесь использовать текстурные координаты позже, мож- но вообще не тратить время на их вывод. Например, если видеокарта поддерживает восемь текстурных единиц, но вы собираетесь использовать только три из них для
Глава 19 Программируемый конвейер 871 Рис. 19.4. Растеризация превращает вершины во фрагменты наложения текстуры, нет смысла в подаче на выход остальных пяти Так вы просто неэкономно расходуете ресурсы Входной и выходной интерфейсы шейдеров вершин практически не отличается от их аналогов в конвейере с фиксированными функциональными возможностями Однако, чтобы выполнить в шейдере требуемые вычисления, нужно вручную напи- сать много кода. Вам кажется, что именно здесь было бы логично привести пример шейдера? К сожалению, данная глава посвящена только ответам на вопросы “что?”, “где?” и “зачем?”. Ответу на вопрос “как?” посвящены последующие четыре главы, поэтому будьте терпеливы. Через несколько страниц вы получите столько шейдеров, сколько и не надеялись увидеть. “Клей” конвейера с фиксированными функциональными возможностями Между шейдерами вершин и фрагментов остается пара каскадов с фиксированными функциональными возможностями, которые выступают как некий клей между двумя схемами затенения. Одним из них является описанный ранее этап отсечения, когда текущий примитив усекается в соответствии с наблюдаемым пространством, при- чем в ходе этого процесса, возможно, добавляются или удаляются вершины. После отсечения происходит деление перспективных координат на W, в результате чего получаются нормированные координаты устройства Эти координаты преобразуются согласно заданным полю просмотра и диапазону глубин, что дает в конечном итоге координаты в пространстве окна. После этого происходит растеризация. Растеризация — это каскад с фиксированными функциональными возможностями, отвечающий за получение обработанных вершин примитива и превращение их во фрагменты. Каким бы ни был примитив — точка, линия или многоугольник, — этот каскад дает фрагменты, “заполняющие” примитив и интерполирующий все цвета и текстурные координаты, так что каждому фрагменту присваиваются соответствую- щие значения Данный процесс иллюстрируется на рис. 19 4. В зависимости от того, насколько разнесены вершины примитива, отношение чис- ла фрагментов к числу вершин может быть довольно большим. В то же время, для сильно мозаичного объекта три вершины треугольника могут отображаться в один фрагмент Существует следующее общее правило: число обрабатываемых фрагментов значительно больше числа обрабатываемых вершин, однако как и для всех правил, у этого есть исключения.
872 Часть III OpenGL' следующее поколение Растеризация также отвечает за создание линий желаемой толщины и точек же- лаемого размера. К линиям и многоугольникам может применяться фактура. Здесь генерируются значения частичного охвата для краев гладких точек, линий и мно- гоугольников; позже (при сглаживании) эти значения умножатся на параметр альфа фрагмента. Если требуется, на этапе растеризации отбираются передние или задние грани многоугольников и применяется смещение глубины Помимо точек, линий и многоугольников растеризация также генерирует фраг- менты для растровых и пиксельных прямоугольников (рисуемых с помощью коман- ды glDrawPixels) Впрочем, эти примитивы не рассчитываются согласно нормаля- ми вершин — обычно при присвоении фрагментам интерполированных данных эти значения выводятся из текущего растрового положения Подробнее данный вопрос рассмотрен в главе 7, “Построение изображений с помощью OpenGL” Программируемые шейдеры фрагментов Шейдерам фрагментов доступны те же текстурные координаты, координаты тумана и цвета, что и каскаду текстурирования с фиксированными функциональными воз- можностями. На выходе шейдера фрагментов ожидается тот же один цвет, что и на выходе каскада тумана с фиксированными функциональными возможностями Точно так же, как в шейдерах вершин, вы можете теперь выбирать собственные действия, уникальным образом переводящие информацию, полученную на входе, в выходные данные Замещение текстурирования Важнейшей возможностью шейдеров фрагментов является возможность поиска тек- стуры Поиск текстуры не отличается от того, что было при фиксированных функ- циональных возможностях, поскольку большинство состояний текстуры задается вне шейдера фрагментов То есть указывается то же текстурное изображение и устанав- ливаются те же его параметры, что и при отсутствии шейдеров Основным отличием является то, что вы решаете, когда в шейдере выполнять поиск (и выполнять ли его вообще) и что использовать в качестве текстурной координаты. Теперь не обязательно ограничиваться использованием текстурной координаты О для обращения к текстурному изображению 0. Можно смешивать и сопоставлять координаты с различными текстурами, используя одну и ту же текстуру с други- ми координатами или одну и ту же координату с разными текстурами. Можно даже вычислять текстурную координату внутри шейдера При фиксированных функцио- нальных возможностях такая гибкость была недоступна Текстурная среда ранее включала функцию текстуры, определявшую, как входной цвет фрагмента смешивается с цветом, полученным по результатам поиска текстуры Теперь эта функция игнорируется, и уже от шейдера зависит, как цвета фрагментов будут объединены с текстурами Можно вообще не выполнять поиск текстуры, а при генерации конечного цвета использовать другую информацию. Шейдер фрагментов может просто скопировать на выход поданный на вход основной цвет Подобный “пропускающий” шейдер, объединенный со сложным шейдером вершин, может пол- ностью удовлетворять ваши потребности
Глава 19 Программируемый конвейер 873 Замещение суммирования цветов Замещение суммирования цветов является довольно простым На этом этапе к пер- вичному цвету просто прибавляется вторичный. Если это все, что вам требуется, — просто добавьте соответствующую команду. Если вы не используете вторичный цвет, игнорируйте его Замещение тумана Наложение тумана нельзя сымитировать так же просто, как суммирование цветов, но его все же можно представить достаточно просто. Во-первых, вам нужно рассчи- тать параметр тумана — уравнение, в которое входит координата тумана для данно- го фрагмента, а также некоторое постоянное значение (например, плотность) Фик- сированные функциональные возможности предписывают использовать следующие уравнения- линейное, экспоненциальное или экспоненциальное второго порядка, но в шейдере вы можете написать собственное уравнение. После этого вы смешиваете постоянный цвет тумана с собственными цветом фрагмента, согласно параметру ту- мана определяя, в каких пропорциях смешиваются компоненты. Все эти возможности можно реализовать, используя всего пару команд, можно вообще не писать никаких команд и забыть о тумане Выбор за вами Введение в расширения шейдеров Хватит гипотетических рассуждений Если вы дочитали до этого места, то уже “нагу- ляли аппетит” для работы с реальными шейдерами. В следующих разделах вводятся различные расширения OpenGL, позволяющие использовать возможности програм- мируемых шейдеров Эти расширения были разработаны и опробованы Наблюдатель- ным советом за архитектурой OpenGL (Architecture Review Board — ARB), и в таком качестве они широко поддерживаются промышленными производителями видеокарт. Низкоуровневые расширения К низкоуровневым относятся расширения GL_ARB_vertex_program и GL_ARB_ fragment_program, используемые для замещения каскадов обработки вершин и фрагментов с фиксированными функциональными возможностями, соответственно Ситуация здесь очень похожа на сравнение языка ассемблера с С первый набор расширений работает на низком уровне, предлагая непосредственный доступ к воз- можностям и ресурсам графического процессора Точно так же, как и при работе с языком ассемблера, вы соглашаетесь на программирование на более громоздком, детальном и часто сложном уровне, а взамен получаете полный контроль над аппа- ратным обеспечением и повышенную производительность Строго говоря, в действительности вы не кодируете на уровне ассемблера, по- скольку каждый производитель аппаратного обеспечения предлагает уникальную структуру графического процессора с “родным” представлением инструкций и на- бором команд Все эти процессоры вводят собственные пределы числа регистров, констант и команд Все, что вы получаете в этих низкоуровневых расширениях, можно назвать наименьшим общим знаменателем функциональных возможностей, доступных у всех производителей
874 Часть III OpenGL следующее поколение Первое впечатление от низкоуровневых шейдеров позволяют получить листин- ги 19.1 и 19.2 Считайте эти схемы аналогами традиционной первой программы “Hello World” (хотя, в отличие от таких программ, наши шейдеры не дают на экране приветственного сообщения). Листинг 19.1. Простой шейдер GL ARB vertex program !!ARBvpl.O # Шейдер вершин "Hello World" # Обратите внимание на то, что комментарии идут после знака '#' ATTRIB iPos = vertex.position; # ввод положения ATTRIB iPrC = vertex.color.primary; # ввод первичного цвета OUTPUT oPos = result.position; # вывод положения OUTPUT oPrC = result.color.primary; # вывод основного цвета OUTPUT oScC = result.color.secondary; # вывод вторичного цвета PARAM mvp[4] = { state.matrix.mvp }; # произведение матрицы наблюдения модели на матрицу проекции TEMP tmp; # временный регистр DP4 tmp.x, iPos, mvp[0]; # Умножаем входное положение на матрицу MVP DP4 tmp.у, iPos, mvp[l]; DP4 tmp.z, iPos, mvp[2]; DP4 tmp.w, iPos, mvp[3]; MOV oPos, tmp; # Подача на выход координат в усеченном пространстве MOV oPrC, 1РгС; # Копирование первичного цвета со входа на выход RCP tmp.w, tmp.w; # Регистр tmp теперь содержит не W, a 1/W MUL tmp.xyz, tmp, tmp.w; # Регистр tmp теперь содержит координаты, # разделенные на перспективу MAD oScC, tmp, 0.5, 0.5; # Отображаем [-1,1] в [0,1] и выдаем результат END ЛИСТИНГ 19.2. Простой шейдер GL ARB fragment program !!ARBfpl.O # Шейдер "Hello World" ATTRIB iPrC = fragment.color.primary; # Первичный цвет на входе ATTRIB iScC = fragment.color.secondary; # Вторичный цвет на входе OUTPUT oCol = result.color; # Цвет на выходе LRP oCol.rgb, 0 5, iPrC, iScC; # Смешение двух цветов 50/50 MOV oCol.a, iPrC.a; # Параметр альфа вторичного цвета игнорируется END Если приведенные шейдеры не понятны интуитивно, не расстраивайтесь' Все они объясняются в главе 20, “Низкоуровневое затенение, кодирование в металле” По сути, шейдеры вершин имитируют преобразования вершин с фиксированными функциональными возможностями — умножение положений вершин в пространстве
Глава 19. Программируемый конвейер ... 875 Рис. 19.5. Согласно положениям объектов на сцене, цветам присваиваются пастельные оттенки объекта на матрицу наблюдения модели/проекции. Затем первичный цвет копируется на выход без изменений. Наконец, на основе разделенных на перспективу нормиро- ванных координат устройства генерируется вторичный цвет. Поскольку полученные таким образом нормированные координаты будут принадлежать диапазону [—1,1], их нужно разделить на 2 и прибавить к результату 1/2, порождая коды цвета в диапазоне [0,1]. Шейдеру фрагментов остается простая задача смешения первичного цвета со вторичным. Для примера на рис. 19.5 показана сцена, визуализированная с помощью данных шейдеров. Высокоуровневые расширения Программирование графических процессоров на языке высокого уровня означает меньше кода, более читабельный код, а следовательно, более высокую производитель- ность. Все это называется языком затенения OpenGL (OpenGL Shading Language — GLSL), иногда именуемым языком шейдеров OpenGL (OpenGL Shader Language). Этот язык во многом похож на С, но имеет встроенные типы данных и функции, полезные в шейдерах вершин и фрагментов. Существует четыре расширения: GL_ARB_shader_objects, GL_ARB_vertex_ shader, GL_ARB_fragment_shader и GL_ARB_shading_language_100. Первое из них описывает механизм загрузки и переключения между шейдерами, причем оно используется следующими двумя расширениями, одно из которых предназначено для шейдеров вершин, а другое — для шейдеров фрагментов. Четвертое расширение опи- сывает собственно язык GLSL и также совместно используется шейдерами вершин и фрагментов.
876 Часть III OpenGL следующее поколение Названия низкоуровневых и высокоуровневых расширений довольно похожи. GL_ ARB_*_program и GL_ARB_*_shader. Просто помните, что низкоуровневые называ- ются программами (program), а высокоуровневые — шейдерами (shader). Отличие подчеркивается только названиями расширений. Фактически же все они относятся к шейдерам. Обратите внимание на то, как в листингах 19.3 и 19.4 выполняются вычисления, приведенные в программах “Hello World”, — кода меньше, причем он читабельнее Листинг 19.3. Простой шейдер вершин GLSL void main(void){ // Шейдер вершин Hello World // Обратите внимание на то, что комментарии идут после '//' // Обычное преобразование MVP vec4 clipCoord = gl_ModelViewProjectionMatrix * gl_Vertex; gl_Position = clipCoord; // Копирования первичного цвета gl_FrontColor = gl_Color; // Расчет NDC vec3 ndc = clipCoord.xyz / clipCoord.w; // Отображение из [-1,1] в [0,1] перед выдачей выхода gl_SecondaryColor = (ndc * 0.5) + 0.5; } Листинг 19.4. Простой шейдер фрагментов GLSL // Шейдер фрагментов Hello World void main(void) { // Смешиваем первичный и вторичный цвета 50/50 gl_FragColor = mix(gl_Color, vec4(vec3(gl_SecondaryColor), 1.0), 0.5); } Если приведенный код недостаточно читабелен для вас, он подробно разбирается в главе 21, “Высокоуровневое затенение”. Резюме В данной главе мы обрисовали традиционные каскады конвейера, связанные с об- работкой вершин и фрагментов, и установили каскады, которые можно полностью заменить программируемыми аналогами. Были кратко введены низкоуровневые и вы- сокоуровневые расширения шейдеров, применяющиеся при замене каскадов с фик- сированными функциональными возможностями. Компиляторы высокоуровневых шейдеров развиваются очень быстро и подобно компиляторам С вскоре смогут генерировать аппаратный код, находящийся на том же или даже более высоком уровне, что и код, вручную написанный на ассемблере Хотя в настоящее время низкоуровневые расширения очень популярны, в ближайшем будущем при развитии технологии компиляторов графических процессоров будет ожидается господство высокоуровневых расширений
ГЛАВА 20 Низкоуровневое затенение: кодирование в металле Бенджамин Липчак ИЗ ЭТОЙ ГЛАВЫ ВЫ УЗНАЕТЕ Действие Функция Задание текста шейдера Переключение между шейдерами Создание и удаление шейдеров Задание параметров программы Запрос параметров программы Задание атрибутов вершин glProgramStringARB glBindProgramARB glGenProgramsARB/glDeleteProgramsARB glProgramEnvParameter*ARB/ glProgramLocalParameter*ARB glGetProgramEnvParameter*ARB/ glGetProgramLocalParameter*ARB glVertexAttrib*ARB/glVertexAttribPointerARB, glEnableVertexAttribArrayARB/ glDisableVertexAttribArrayARB Запрос атрибутов вершин glGetVertexAttrib*vARB/ glGetVertexAttribPointervARB Запрос состояния программного объекта glGetProgramivARB/glGetProgramStringARB/ gllsProgramARB Низкоуровневые шейдеры предлагают относительно прямой доступ к графиче- скому аппаратному обеспечению текущего поколения Каждый такт шейдера можно запрограммировать векторной инструкцией, оперирующей с четырьмя компонента- ми сразу. Низкоуровневые шейдеры вершин и фрагментов используют одни и те же команды загрузки и управления шейдерами и предлагают практически одинаковый набор инструкций Отличается лишь информация на их входе и выходе Как описывается в главе 19, “Программируемый конвейер- это нс тот OpenGL, который помнит ваш отец”, шейдеры вершин принимают необработанные вершины и их атрибуты (положение, нормаль, цвета, текстурные координаты и т.д) С другой стороны, шейдеры фрагментов принимают в качестве входа фрагменты и соотне- сенные с ними данные, подавая на выход конечный цвет фрагмента и, возможно, новую глубину
878 Часть III OpenGL следующее поколение Мы могли бы посвятить шейдерам целую книгу, но вместо этого просто поста- раемся охватить в данной главе все важнейшие аспекты этих схем. Помните, что в качестве полного руководства вы всегда можете использовать спецификации расши- рений (GL_ARB_vertex_program и GL_ARB_fragment_program). Возможно, после прочтения главы эти спецификации станут намного понятнее! Управление низкоуровневыми шейдерами В следующих разделах преимущественно описывается то, что происходит внутри шейдера. Впрочем, вначале вам нужно загрузить шейдеры в OpenGL и иметь воз- можность включения и выключения схем затенения, а также переключения между ними После этого можете начинать писать шейдеры. Создание и связывание шейдеров Подобно текстурным объектам, буферным объектам, запросам о преградах и дру- гим объектам OpenGL, низкоуровневые шейдеры загружаются в объекты — в данном случае программный объект Итак, вначале вы генерируете неиспользуемое имя про- граммного объекта, а затем создаете его, связывая в первый раз // Создаем объекты шейдеров; устанавливаем шейдеры glGenProgramsARB(2, ids); glBindProgramARB(GL_VERTEX_PROGRAM_ARB, ids[0]); glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, ids[ 1 ] >; Когда программный объект находится в исходном состоянии, с ним не соотнесен никакой шейдер Если вы попытаетесь активизировать его и что-то нарисовать, это приведет к ошибке Таким образом, далее нужно загрузить несколько шейдеров Загрузка шейдеров Вы передаете шейдеры OpenGL в виде строк ASCII, используя функцию glPro- gramStringARB, принимающую тип, формат, длину шейдера и указатель на строку, содержащую текст шейдера glProgramStringARB(GL_VERTEX_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, strlen(vpString), vpString); glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, strlen(fpString), fpString); Первый аргумент указывает, что вы замещаете в данный момент — текущий свя- занный низкоуровневый шейдер вершин или фрагментов Аргумент формата используется только с позиции будущей расширяемости (например, чтобы в будущем принимать представления Unicode или двоичные байт-коды) В настоящее время допускается только значение GL_PROGRAM_ FOR- MAT_ASCII_ARB Строки не обязательно должны заканчиваться символом конца строки Функция glProgramStringARB исследует только число симво юв, которые ей передают в тре-
Глава 20 Низкоуровневое затенение кодирование в металле 879 тьсм аргументе, и все эти символы должны быть частью текста шейдера Символ кон- ца строки, если он присутствует, в аргумент длины включаться не должен Удобно, что такое же поведение имеет стандартная строковая функция С strlen, возвращая длину строки без символа конца строки Получив команду glProgramStringARB, OpenGL приступает к анализу шейдера. Если все хорошо, шейдер компилируется и оптимизируется, насколько это необхо- димо соответствующему аппаратному обеспечению, после чего он будет готов для визуализации, когда вы активизируете затенение вершин и/или фрагментов Если имеются синтаксические или семантические ошибки или шейдер слишком сложен для реализации, генерируется ошибка. В этом случае текущий связанный шей- дер не замещается, и любой шейдер, имевший данный статус (если такие есть), его не теряет Чтобы найти где и по какой причине произошла ошибка, можно запросить положение ошибки и строку ошибки Положение ошибки — это смещение в байтах от начала строки шейдера до места, где произошла ошибка Если ошибок нет, вы получите значение -1. Если ошибка представляет собой семантическое ограничение, которое можно обнаружить только после анализа всего шейдера (например, попытка использования одной текстурной единицы для наложения двух- и трехмерной текстуры), положение ошибки устанав- ливается равным длине шейдера Строка ошибки является самым полезным инструментом диагностики возникшей проблемы Она сообщает тип ошибки и предлагает дополнительную информацию, например номер строки, в которой произошла ошибка Использовать строку ошиб- ки для поиска ошибок в шейдере существенно проще, чем исследовать положение ошибки, пытаясь вручную подсчитать сотни символов1 Код, использованный для настройки низкоуровневых шейдеров, приводится в листинге 20 1 Листинг 20.1. Настройка низкоуровневых шейдеров // Создаем, настраиваем и активизируем шейдеры glGenProgramsARB(2, ids); glBindProgramARB(GL_VERTEX_PROGRAM_ARB, ids[0]); glProgramStringARB(GL_VERTEX_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, strlen(vpString), vpString); glGet!ntegerv(GL_PROGRAM_ERROR_POSITION_ARB, &errorPos); if (errorPos != -1) { fprintf(stderr, "Error in vertex shader at position %d!\n", errorPos); fprintf(stderr, "Error string: %s\n", gIGetString(GL_PROGRAM_ERROR_STRING_ARB)); Sleep(5000); exit(0); } glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, ids[l]); glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, strlen(fpString) , fpString); glGetlntegerv(GL_PROGRAM_ERROR_POSiTION_ARB, SerrorPos);
880 Часть III OpenGL- следующее поколение if (errorPos != -1) { fprintf(stderr, "Error in fragment shader at position %d!\n", errorPos); fprintf(stderr, "Error string: %s\n", glGetString(GL_PROGRAM_ERROR_STRING_ARB)); Sleep(5000); exit(0); } if (useVertexShader) glEnable(GL_VERTEX_PROGRAM_ARB); if (useFragmentShader) glEnable(GL_FRAGMENT_PROGRAM_ARB); Попробуйте добавить в код листинга 20.1 опечатку и посмотрите, насколько хо- рошо строка ошибки вашей реализации OpenGL поможет отследить проблему Удаление шейдеров Как и другие объекты OpenGL, шейдеры нужно удалять после завершения работы с ними. Удаление шейдеров освобождает ресурсы и позволяет снова использовать освободившиеся имена glDeleteProgramsARB(2, ids); Настройка расширений Прежде чем мы перейдем к реальным шейдерам, нужно рассмотреть еще один во- прос Низкоуровневые шейдеры вершин и фрагментов не являются частью основной реализации OpenGL В предыдущих главах мы рассматривали функциональные воз- можности, которые относились к расширениям, но с недавнего времени включены в основную библиотеку OpenGL Однако, исходя из роста популярности высокоуров- невых схем расширения, можно предположить, что низкоуровневые шейдеры никогда нс войдут в ядро и навсегда останутся расширениями, одобренными ARB Когда дело доходит до использования, статус расширения не играет особой роли По крайней мере на платформе Windows все функциональные возможности, введен- ные после версии OpenGL 1 1 (все равно, относятся они к ядру или к расширению) требуют перед использованием запроса указателей функции точки входа Перед этим нужно проверить наличие расширения в строке расширения В приводимом коде, на- пример, используются вторичные функции цвета, требующие либо OpenGL 1 4, либо расширения GL_EXT_secondary_color. В листинге 20 2 показано, как проверить, поддерживает ли реализация OpenGL требуемые возможности Листинг 20.2. Проверка наличия возможностей OpenGL // Проверяем, доступны ли требуемые функциональные возможности if (!glt!sExtSupported("GL_ARB_vertex_program")) { fprintf(stderr, "GL_ARB_vertex_program extension"
Глава 20 Низкоуровневое затенение кодирование в металле 881 ” is unavailable!\n"); Sleep(2000); exit(0) ; if (!glt!sExtSupported("GL_ARB_fragment_program")) { fprintf(stderr, "GL_ARB_fragment_program extension" " is unavailable! \n") ; Sleep(2000); exit(0) ; version = glGetString(GL_VERSION); if (((version[0] != '1') || (versionfl] !='.') || (version[2] < '4') || (version[2] > '9')) && // 1.4 + (!glt!sExtSupported("GL_EXT_secondary_color"))) { fprintf(stderr, "Neither OpenGL 1.4 nor GL_EXT_secondary_color" " extension is available!\n") ; Sleep(2000); exit(0); } glGenProgramsARB = gltGetExtensionPointer("glGenProgramsARB"); glBindProgramARB = gltGetExtensionPointer("glBindProgramARB"); glProgramStringARB = gltGetExtensionPointer("glProgramStringARB"); glDeleteProgramsARB = gltGetExtensionPointer("glDeleteProgramsARB"); if (glt!sExtSupported("GL_EXT_secondary_color”)) glSecondaryColor3f = gltGetExtensionPointer("glSecondaryColor3fEXT"); else glSecondaryColor3f = gltGetExtensionPointer("glSecondaryColor3f"); if (!glGenProgramsARB || (glBindProgramARB || (glProgramStringARB I| IglDeleteProgramsARB || !glSecondaryColor3f) fprintf(stderr, "Not all entrypoints were available!\n" ) ; Sleep(2000); exit(0); Наборы команд В каждом цикле (или такте команд) низкоуровневого шейдера вершин или фрагмен- тов может выполняться свой код операции (opcode) — ADD, MUL или MOV, соответ- ствующий сложению, умножению или копированию, соответственно Данные коды операций являются основными структурными единицами шейдера После большин- ства таких кодов указывается один выходной аргумент и один или несколько входных аргументов, разделенных запятыми Каждая команда завершается точкой с запятой
882 Часть III OpenGL следующее поколение MUL myResult, myTemp, 2.0; # умножить каждый компонент myTemp на 2, # результат записать в myResult За несколькими исключениями наборы команд шейдеров вершин и фрагментов идентичны В следующих разделах мы сперва обсудим существенные отличия меж- ду двумя типами схем, а затем рассмотрим команды, уникальные для каждого ти- па шейдеров Общие команды Наборы команд можно классифицировать по числу входных аргументов, тому, явля- ются входы векторами или скалярами, а также тому, является результат вектором или скаляром. В данном случае под вектором мы понимаем четырехкомпонентный вектор, тогда как скаляр сформирован одним компонентом В табл 20.1 классифицируются все инструкции, общие для шейдеров вершин и фрагментов Все команды, выдающие векторный результат, работают независимо с каждым компонентом вектора Например, команда MUL в действительности выполняет четыре не связанных между собой операции умножения MUL myResult, myTempl, myTemp2; # Это то же самое, что и такие операции: # myResult.х = myTempl х * myTemp2.x # myResult.у = myTempl.у * myTemp2.у # myResult.z = myTempl.z * myTemp2.z # myResult.w = myTempl.w * myTemp2.w С другой стороны, команды, выдающие один скалярный результат, в действитель- ности копируют его во все компоненты вектора результата. RCP myResult, myTemp.х; # Это то же самое, что и такие операции: # myResult.х = myResult.у = myResult.z = myResult.w = 1.0/myTemp.x Команды, существующие только для вершин Уникальными для низкоуровневых шейдеров вершин являются только три команды: ARL, ЕХР и LOG Их описание приводится в табл. 20 2 ARL является специальной командой, используемой для загрузки регистра адре- са — типа регистра в виде однокомпонентного целого числа со знаком, используемого для относительной адресации Перед загрузкой регистра адреса адрес усекается (т.е превращается в наибольшее целое число, меньшее или равное аргументу) Относи- тельная адресация рассматривается ниже, в разделе “Адресация”. Команды ЕХР и LOG являются аппроксимациями с низкой точностью функций ЕХ2 и LG2, кроме того, они помещают результат только в третий компонент выходного вектора Первые два компонента заполняются другими минимально полезными па- раметрами аппроксимации, а четвертый компонент равен 1 Поскольку эти команды нс дают никаких дополнительных преимуществ (разве что возможное повышение производительности в некоторых реализациях OpenGL), они были удалены из набора команд шейдера фрагментов
Глава 20 Низкоуровневое затенение кодирование в металле 883 ТАБЛИЦА 20.1. Общие команды Команда Входные аргументы Выходной i аргумент Описание ABS 1 вектор вектор Находит модуль входного вектора ADD 2 вектора вектор Суммирует два вектора DP3 2 вектора скаляр Находит скалярное произведение первых трех компонентов входных векторов DP4 2 вектора скаляр Находит скалярное произведение всех четырех компонентов входных векторов DPH 2 вектора скаляр Находит скалярное произведение первых трех компонентов входных векторов и прибавляет четвертый компонент второго входного аргумента DST 2 вектора вектор Вычисляет вектор расстояния для двух входных векторов, имеющих специальный формат, подробности см. в спецификации ЕХ2 1 скаляр скаляр Находит 2 в степени, равной входному аргументу FLR 1 вектор вектор Находит наибольшее целое число, меньшее или равное входному аргументу FRC 1 вектор вектор Находит дробную часть аргумента LG2 1 скаляр скаляр Вычисляет логарифм по основанию 2 от аргумента LIT 1 вектор вектор Вычисляет коэффициенты освещения для данного входного вектора, имеющего специальный формат; подробности см. в спецификации MAD 3 вектора вектор Перемножает первые два вектора и прибавляет к результату третий MAX 2 вектора вектор Находит максимум всех компонентов для двух векторов MIN 2 вектора вектор Находит минимум всех компонентов для двух векторов MOV 1 вектор вектор Копирует вектор MUL 2 вектора вектор Перемножает два вектора POW 2 скаляра скаляр Вычисляет значение, равное первому входному аргумента в степени второго аргумента RCP 1 скаляр скаляр Находит величину, обратную к входному аргументу RSQ 1 скаляр скаляр Находит величину, равную обратной к корню квадратному из модуля входного аргумента SGE 2 вектора вектор Компонент выходного аргумента равен 1, если соответствующий компонент первого входного вектора больше или равен соответствующему компоненту второго вектора; в противном случае компонент выходного аргумента равен 0 SLT 2 вектора вектор Компонент выходного аргумента равен 1, если соответствующий компонент первого входного вектора меньше соответствующего компонента второго вектора, в противном случае компонент выходного аргумента равен 0 SUB 2 вектора вектор Вычитает второй вектор из первого SWZ 1 вектор вектор Копирование с возможностями настройки по адресам (swizzling), подробности см в спецификации XPD 2 вектора вектор Вычисляет векторное произведение первых трех компонентов обоих входных векторов; четвертый компонент не определен
884 Часть III. OpenGL: следующее поколение ТАБЛИЦА 20.2. Команды, существующие только для вершин Команда Входные аргументь Выходной j аргумент Описание ARL 1 вектор адрес Загружает регистр адреса ЕХР 1 скаляр вектор Приближенно вычисляет 2 в степени, равной входному аргументу, выходной аргумент имеет специальный формат, подробности см в спецификации LOG 1 скаляр вектор Приближенно вычисляет логарифм по основанию 2 от входного аргумента, выходной аргумент имеет специальный формат, подробности см в спецификации Команды, существующие только для фрагментов После того как низкоуровневое программное расширение для вершин было одобре- но ARB, началась работа над соответствующим расширением для фрагментов. Был рассмотрен вопрос включения в него всех команд расширения для вершин, в оконча- тельный набор не попали только три команды, приведенные в табл. 20 2. На современном аппаратном обеспечении возможность относительной адресации в шейдерах фрагментов пока еще недоступна, поэтому в универсальный набор команд не включена команда arl Кроме того, команды низкой точности, ехр и log, не представляют особого интереса по сравнению с командами максимальной точности, поэтому они также были отброшены. Помимо названных выше команд, представленных в обоих типах шейдеров (вер- шин и фрагментов), был добавлен набор операций, особенно полезных при работе с фрагментами. Все они перечислены в табл 20.3. Шейдеры фрагментов вводят новый тип команд: команды текстуры Остальные команды попадают в категорию АЛУ (Arithmetic and Logic Unit — ALU, арифметико- логическое устройство), поскольку выполняют арифметические операции К новой категории текстурных относятся три команды — тех, тхв и тхр Все эти три команды выполняют поиск текстуры по заданной цели текстуры (1D, 2D, 3D, CUBE) указанной текстурной единицы. Например, чтобы выбрать информацию из кубической карты в текстурную единицу 0, применяется следующая команда- ТЕХ myResult, myTexCoord, texture[0], CUBE; Уникальную команду KIL можно использовать для остановки дальнейшего выпол- нения шейдера фрагментов и отбрасывания фрагмента; эта команда также относится к категории текстурных Она не производит поиск текстуры, но ее можно реализо- вать на аппаратном уровне, используя те же ресурсы, что и для других рассмотрен- ных команд. Типы переменных Набор команд превосходен, но эти коды операций не смогут ничего сделать без данных и места для хранения результата. В качестве входов и/или выходов низ- коуровневых шейдеров можно использовать шесть различных типов переменных, описанных в табл. 20 4.
Глава 20 Низкоуровневое затенение: кодирование в металле 885 ТАБЛИЦА 20.3. Команды, существующие только для фрагментов Входные Выходной Команда аргументы аргумент Описание СМР 3 вектора вектор Копирует компонент из второго входного аргумента, cos 1 скаляр скаляр если компонент первого входного аргумента меньше нуля, в противном случае копируется компонент из третьего входного аргумента Вычисляет косинус входного аргумента, KIL 1 вектор отсутствует представленного в радианах, причем не обязательно в диапазоне-Pl, PI Удаляет текущий обрабатываемый фрагмент и обходит LRP 3 вектора вектор последующие каскады конвейера, если какой-либо компонент входного вектора меньше нуля Линейно интерполирует второй и третий входные SCS 1 скаляр 2 скаляра векторы, основываясь на первом входном векторе И ИСПОЛЬЗУЯ формулу Г = Ю«Ц 4- (1 - 70) * 12 Записывает в первый компонент выходного аргумента SIN 1 скаляр скаляр косинус, а во второй — синус входного аргумента, представленного в радианах и принадлежащего диапазону-Pl, PI Вычисляет синус входного аргумента, представленного ТЕХ 1 вектор вектор в радианах, причем не обязательно в диапазоне -Pl, PI Выполняет непроективный поиск текстуры, используя ТХВ 1 вектор вектор в качестве текстурных координат первые три компонента входного вектора Выполняет непроективный поиск текстуры, используя ТХР 1 вектор вектор первые три компонента входного вектора в качестве текстурных координат, а четвертый компонент — в качестве смещения уровня детализации Выполняет проективный поиск текстуры, когда первые три компонента входного вектора вначале делятся на четвертый, а затем используются в качестве текстурных координат Все переменные представляют четырехкомпонентныс векторы, образованные зна- чениями с плавающей запятой, исключая регистры адреса, представляющие собой целые числа со знаком Временные переменные Временные переменные — это рабочие лошадки низкоуровневых шейдеров Если вы нс пишите результат операции в выходной регистр, скорее всего вы записываете его во временную переменную. Прежде чем переменную можно будет использо- вать в качестве временной, ее нужно объявить Можно объявить несколько перемен- ных одновременно TEMP diffuseColor, specColor, myTexCoord; Теперь имена этих переменных можно использовать в качестве входных или вы- ходных аргументов любых команд
886 Часть III OpenGL следующее поколение ТАБЛИЦА 20.4. Типы переменных Тип переменной Объявление Доступ Описание Временная TEMP чтение/запись Стандартный универсальный регистр временного хранения Параметр PARAM только чтение Регистр констант, не меняющихся за время выполнения шейдера Атрибут ATTRIB только чтение Вход шейдера Выход OUTPUT только запись Выход шейдера Адрес ADDRESS только запись Целочисленная константа со знаком, доступная только в шейдерах вершин Псевдоним ALIAS не определено Другое имя, данное переменной одного из других типов Параметры Параметрами являются переменные, нс изменяющиеся за один проход шейдера. Их можно представлять как константы, хотя некоторые параметры можно менять за пределами схемы В любом случае вы нс можете записывать информацию в пара- метр в процессе выполнения шейдера Вы можете использовать их только как входы команд Существует три типа параметров, внутритекстовые (inline) константы, свя- занные с состоянием (state-bound) параметры и параметры программы Параметры можно объявлять с именами переменных, но, как будет показано в следующих раз- делах, это нс обязательно Внутритекстовые константы Внутритекстовые константы действительно являются постоянными величинами. Им присваиваются конкретные значения внутри текста шейдера, которые никогда не ме- няются Вы можете задать все четыре компонента вектора равными одному значению, присвоить каждому компоненту уникальное значение, а также не задавать компонен- ты (в этом случае они заполняются значениями по умолчанию) PARAM two =2.0; # все 4 компонента # содержат значение 2 PARAM quarters = (0.0, 0.25, 0.5, 0.75}; # 4 уникальных значения PARAM pi ={3.14159}; # вектор заполняется # так: PI, 0, 0, 1 Помните о тонкости использования фигурных скобок! Например, 2 0 и {2 0} дадут одинаковое значение первого компонента, но три других компонента будут отличать- ся Данная особенность характерна для всех низкоуровневых шейдеров. Можно нс объявлять параметры, поскольку допускается использовать их непо- средственно в качестве входных аргументов команд MUL tripleCoord, myCoord, 3.0f; MUL scaledResult, (0.1, 0.2, 0 3, 0.4}, myResult;
Глава 20 Низкоуровневое затенение: кодирование в металле 887 Параметры, связанные с состоянием Для удобства к множеству состояний OpenGL можно обращаться в форме параметров, связанных с состоянием (state-bound parameters) При изменении состояния OpenGL параметры автоматически обновляются. Это позволяет более прямолинейно имити- ровать фиксированные функциональные возможности. Например, вместо того, чтобы вручную загружать матрицу наблюдения модели/проекции (modelview/projection — MVP) в четыре вектора параметров, для преобразования положений вершин можно просто использовать матрицу MVP, уже доступную в состоянии OpenGL: DP4 result.position.х, vertex.position, state.matrix.mvp.row[0]; DP4 result.position.y, vertex.position, state.matrix.mvp.row[1]; DP4 result.position.z, vertex.position, state.matrix.mvp.row[2]; DP4 result.position.w, vertex.position, state.matrix.mvp.row[3]; В число состояний, к которым возможна привязка, входят все матрицы преобра- зований и свойства генерации текстурных координат, цвет материала, освещение, туман, плоскости отсечения, размер точки и затухание, цвета текстурной среды, а также диапазон глубин. Некоторые параметры состояния, с которыми возможно связывание, существуют только для шейдеров вершин или шейдеров фрагментов. Полный список параметров состояния, с которыми возможно связывание, низкоуров- невых шейдеров вершин и фрагментов можно найти в спецификациях расширений GL_ARB_vertex_program и GL_ARB_f ragment_program. Параметры программы Помимо внутритекстовых констант, жестко закодированных в тексте, и параметров, привязанных к конкретному состоянию OpenGL, можно использовать третью катего- рию общих параметров. В данные параметры можно загружать любую переменную, которую позже можно заменить другим значением. Программные параметры делятся на две категории: локальные параметры про- граммы и параметры среды программы. Локальные параметры задаются для одно- го шейдера, тогда как параметры среды совместно используются всеми шейдерами данного типа. Т е. шейдеры вершин имеют один набор параметров среды, а шейдеры фрагментов — другой. Данные параметры загружаются командами glProgramLocal- Parameter4*ARB и glProgramEnvParameter4*ARB, которые в качестве аргументов принимают число слотов и четыре значения. Для обращения к ним в тексте шейдера используются команды program, local [п] и program.env[n] Таким образом, если параметры постоянны, зачем использовать программные па- раметры вместо простого жесткого кодирования констант в шейдере? Определенно, схема была бы читабельнее, если бы в ее тексте значение константы фигурировало явно Впрочем, представьте, что вы визуализируете сцену с колеблющимся пламенем свечи, причем яркость мерцания отличается для разных кадров визуализации. В таком случае в схеме мог присутствовать примерно такой код. MUL finalColor, litColor, program.local[0] ; # local 0 содержит коэффициент мерцания, # принадлежащий диапазону [0,1] Вы можете использовать данную схему в таком же виде, для новых кадров всего лишь обновляя значение локального параметра.
888 Часть III. OpenGL: следующее поколение glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, О, 0.75f, 0.75f, 0.75f, 0.75f); renderscene(); glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 0, 0.2f, 0.2f, 0.2f, 0.2f); renderscene(); glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 0, 0.5f, 0.5f, 0.5f, 0.5f); renderscene(); Во время выполнения шейдера вершин или фрагментов значение параметра по- стоянное, но оно меняется со временем для разных визуализируемых примитивов. Значения программных параметров (или параметров, связанных с состоянием) може- те менять, когда вам удобно (но вне пар glBegin/glEnd). Массивы параметров В OpenGL существует возможность объявления массива параметров, обращаться к ко- торым можно посредством абсолютной либо относительной адресации При абсолют- ной адресации вы указываете точный индекс массива, который желаете использовать. Относительная адресация рассматривается ниже в разделе “Адресация”. Ниже приводится несколько примеров объявлений массива параметров. Если хо- тите, можете объявить размер массива или разрешить автоматическую установку данной величины. Однако помните, что, если вы объявите размер, но позже задади- те величины, предполагающие превышение этого размера, шейдер с этой ситуаци- ей не справится. # В данном случае размер массива задается явно - 10 векторов PARAM myArray[10] = {2.0, {0.1, 0.2, 0.3, 0.4], program.env[0..5], state.fog.color, -1.0}; # В данном случае размер массива автоматически ограничивается # 6 векторами PARAM myOtherArray[] = { state.matrix.mvp, state.matrix.texture[0] .row[1..2] }; Абсолютная адресация массивов — это просто способ снабжения его индексом для использования в какой-то команде # С помощью команды умножения с последующим сложением # (multiply then add - MAD) выполняется удвоение величины, # а затем из результата вычитается единица MAD scaledAndBiased, myColor, myArray[0], myArray[9]; Атрибуты Подобно параметрам атрибуты также относятся к входной информации, единствен- ной допустимой операцией с которой является чтение. Однако в отличие от па- раметров атрибуты обычно меняются в процессе выполнения Каждая следующая затеняемая вершина имеет новое положение, а возможно, и новые входные цвета и текстурные координаты То же справедливо для всех фрагментов. Как и параметры, атрибуты можно объявлять или же использовать непосредствен- но в команде
Глава 20 Низкоуровневое затенение: кодирование в металле 889 ТАБЛИЦА 20.5. Атрибуты вершин Атрибут Связываемые компоненты Описание vertex.position (x,y,z,w) Положение в пространстве объекта vertex.normal (X,y,z,1) Нормаль vertex.color (r.g.b.a) Первичный цвет vertex.color.primary (r.g.b.a) Первичный цвет vertex.color.secondary (r.g.b.a) Вторичный цвет vertex.fogcoord (f.0,0,1) Координата тумана vertex.texcoord (s.t.r.q) Текстурная координата в текстурной единице 0 vertex.texcoord[n] (s.t.r.q) Текстурная координата в текстурной единице п vertex.attrib[n] (x,y,z,w) Общий атрибут л TEMP nDotC; ATTRIB vNorm = vertex.normal; # Объявленный атрибут DP3 nDotC, vNorm, vertex.color.primary; # Цвет не объявлялся Шейдеры вершин и фрагментов имеют собственные наборы входных атрибутов, поэтому мы рассмотрим их отдельно. Атрибуты вершин В табл. 20.5 перечислены все входные атрибуты, доступные для шейдеров вершин. Они соответствуют всем текущим состояниям вершин OpenGL, которые можно ме- нять для вершины внутри пары glBegin/glEnd. Одним из атрибутов вершин, с которым вы скорее всего никогда раньше не в< г- чались, является общий атрибут. Такие атрибуты были введены для того, что-.;, вершин можно было задавать любой тип данных — не обязательно один и > • ранее доступных при фиксированных функциональных возможностях Ьи> ер касательные векторы, все, что пожелаете, чтобы ввести их в схему затенен!!:: <.р- шин, можете это сделать посредством общего атрибута. Для установки общих атрибутов можно использовать множество раяювчшо стей команды glVertexAttrib*ARB. Кроме того, общие атрибуты можно оомс 1 в массив вершин, используя команды glVertexAttribPointerARB и gl-!.rn ertexAttribArrayARB Следует помнить, что в некоторых реализациях общие атрибуты перскр,,,.... атрибутами конвейера с фиксированными функциональными возможное > и ности, вызов glVertexAttrib с атрибутом 0 гарантированно даст то1 >. о. , что и вызов glVertex, и наоборот Тем не менее ко всем возможным , стам псевдонимов следует относиться очень внимательно. Конфликты, возмо,> общими атрибутами и атрибутами конвейера с фиксированными функпи ' возможностями, перечислены в табл 20.6. Если вызвать одну из двух команд, указанных в одной строке таСы ш .г становится неопределенной (например, вызвав glVertexAttrib для атрпт 1 делаем неопределенными настройки нормали, заданные е помощью glNor-i^
890 Часть III OpenGL' следующее поколение ТАБЛИЦА 20.6. Совмещение атрибутов вершин Общее связывание Накладывающийся атрибут Накладывающееся связывание vertex.attrib[0] vertex.attrib[1] vertex.attrib[2] vertex.attrib[3] Положение вершины vertex. position Нет нет Нормаль vertex.normal Первичный цвет vertex. color, vertex.color.primary vertex.attrib[4] vertex.attrib[5] vertex.attrib[6] vertex.attrib[7] vertex.attrib[8] vertex.attrib[8+n] Вторичный цвет vertex. color. secondary Координата тумана vertex. fogcoord Нет нет Нет нет Текстурная координата 0 vertex. texcoord Текстурная координата n vertex. texcoord [ n ] ТАБЛИЦА 20.7. Атрибуты фрагментов Связывание с атрибутом Компоненты Описание fragment.position (x,y,z,1/w) Положение в пространстве окна с учетом масштабирования fragment.color (r.g.b.a) Первичный цвет fragment.color.primary (r.g.b.a) Первичный цвет fragment.color.secondary (r.g.b.a) Вторичный цвет fragment.texcoord (s.t.r.q) Текстурная координата 0 fragment.texcoord[n] (s.t.r.q) Текстурная координата п fragment.fogcoord (f,0,0,1) Координата тумана/расстояние оборот). Кроме того, в пределах одного шейдера нельзя связаться с обоими атрибута- ми, указанными в одной строке таблицы, поскольку в этом случае анализ схемы будет невозможен. Благодаря этому можно обнаружить случайные ошибки, связанные с ис- пользованием псевдонимов, например, когда в пределах одной схемы вы пытаетесь использовать и vertex. attrib [ 4 ], и vertex .color. secondary Атрибуты фрагментов К атрибутам фрагментов относится положение фрагмента, а также другие данные, значение которых в различных точках примитива находится путем интерполяции Здесь не используются общие атрибуты — только те же интерполируемые величины, что применяются в конвейере с фиксированными функциональными возможностя- ми. Все атрибуты фрагментов и их связывание в шейдере фрагментов перечисле- ны в табл. 20 7. Выходные аргументы Выходными аргументами являются регистры с возможностью только записи, которые могут использоваться для хранения результата выполнения команды. Подобно вход- ным атрибутам, выходные атрибуты также отличаются для низкоуровневых шейдеров вершин и фрагментов
Глава 20 Низкоуровневое затенение кодирование в металле 891 ТАБЛИЦА 20.8. Выходные атрибуты шейдеров вершин Связывание с выходом Компоненты Описание result.position (x,y,z,w) Положение в усеченном пространстве result.color (r.g.b.a) Первичный цвет передней грани result.color.primary (r.g.b.a) Первичный цвет передней грани result.color.secondary (r.g.b.a) Вторичный цвет передней грани result.color.front (r.g.b.a) Первичный цвет передней грани result.color.front.primary (r.g.b.a) Первичный цвет передней грани result. color . front. secondary (r,g,b,a) Вторичный цвет передней грани result.color.back (r.g.b.a) Первичный цвет задней грани result.color.back.primary (r.g.b.a) Первичный цвет задней грани result.color.back.secondary (r.g.b.a) Вторичный цвет задней грани result.fogcoord (f.‘.‘,*) Координата тумана result.pointsize (s,*,*,*) Размер точки result.texcoord (s.t.r.q) Текстурная координата 0 result.texcoord[n] (s.t.r.q) Текстурная координата п ТАБЛИЦА 20.9. Выходные атрибуты шейдеров фрагментов Связывание с выходным атрибутом Компоненты Описание result.color result.depth (r.g.b.a) Цвет (*,*,d,*) Координата глубины Выходные атрибуты шейдеров вершин Набор регистров выхода шейдеров вершин большей частью представляет цвета и ко- ординаты, которые будут использоваться при интерполяции точек примитива, к кото- рому принадлежит вершина, и которые будут доступны в качестве входных атрибутов шейдеров фрагментов Все выходные атрибуты низкоуровневых шейдеров вершин перечислены в табл 20 8. Выходные атрибуты, описываемые как координата тумана и размер точки, явля- ются скалярами Поэтому используется только первый их компонент, остальные иг- норируются Размер точки используются только при растеризации и влияет на размер генерируемого примитива точки На вход шейдера фрагментов он не поступает Обратите внимание на то, что на выход шейдера вершин поступает четыре величи- ны, описывающих цвет, а шейдер фрагментов принимает только две Дело в том, что ориентация примитива определяется в ходе растеризации и именно тогда выясняется, какие цвета передаются шейдеру — цвета передних или задних граней Любой выходной атрибут, не записанный шейдером вершин, становится неопреде- ленным, поэтому если вы позже попытаетесь его использовать в качестве входа шей- дера фрагментов, то получите ошибочные данные Мораль" убедитесь, что согласова- ли шейдер фрагментов с шейдером вершин, генерирующим необходимые величины' Выходные атрибуты шейдеров фрагментов Шейдеры фрагментов имеют только два выходных атрибута — окончательный цвет и глубину (см. табл 20 9)
892 Часть III OpenGL следующее поколение Выходной цвет передается последующим операциям с фрагментами — альфа-тесту и смешению, — и в конечном итоге записывается в буфере кадров Выходная информация о глубине обрабатывается несколько отлично от другой выходной информации Все остальные выходные аргументы становятся неопреде- ленными, если вы их не запишете, а глубина фрагмента представляется значением по умолчанию, полученным в ходе растеризации. Если вы запишите значение глубины, оно заменит глубину растеризации, и именно оно будет передано на последующие каскады сличения с трафаретом и проверки глубины. Псевдонимы В действительности псевдоним не являются отдельным типом регистра. Это просто способ дать существующему регистру новое имя переменной. Временные перемен- ные являются ограниченными ресурсами, поэтому псевдонимы позволяют давать новые значимые имена “мусорным” регистрам Приведем пример: TEMP baseMap, outColor; ALIAS lightMap = baseMap; MOV outColor, fragment.color; TEX baseMap, fragment.texcoord[0], texture[0], 2D; MUL outColor, outColor, baseMap; # Результат следующего поиска текстуры помещается в ту же # физическую временную переменную, что и предыдущий, но ему дается # новое имя, что делает схему более читабельной ТЕХ lightMap, fragment.texcoordf1], texture[l], 2D; MUL outColor, outColor, lightMap; Адреса Регистры адреса используются для относительной адресации массивов параметров Такой тип адресации предлагает доступ к массиву с использованием произвольным образом вычисленного индекса Относительная адресация допустима только в низко- уровневых шейдерах вершин, в схемах для фрагментов ее нет. Используется только первый компонент регистра адреса Остальные три компо- нента вообще могут не существовать, их нельзя ни считывать, ни записывать. Перед использованием относительной адресации нужно объявить регистр адреса, а затем записать в него информацию с помощью команды ARL (Address Register Load — за- грузка регистра адреса) ADDRESS myAddress; TEMP computedAddress, chosenOffset; PARAM offsets[] = { 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0 }; # ARL - это та же команда FLR, выполняющая только запись # в регистры адреса ARL myAddress.х, computedAddress; MOV chosenOffset, offsets[myAddress.x];
Глава 20 Низкоуровневое затенение' кодирование в металле 893 Модификаторы входных и выходных атрибутов К регистрам входа и выхода можно применять несколько операций. Все они рассмот- рены в следующих разделах. Инверсия входа Первой операцией, которую мы рассмотрим, будет инверсия входа. Поместив минус перед любым аргументом команды, вы инвертируете его значение. MOV negativeVal, -positiveVal; Настройка входа по адресам Другим модификатором входных аргументов является суффикс настройки по адресам (swizzle). В результате компоненты входного регистра переупорядочиваются. В при- веденном ниже примере меняется порядок компонентов параметрического вектора. PARAM someConstant = { 1, 2, 3, 4 }; # В результате переупорядочения получаем 4,3,2,1 MUL result, result, someConstant.wzyx; Результатом описываемой операции может быть любая комбинация компонентов х, у, z и w. . zzzz, . xywy или даже избыточный вариант .xyzw. Кроме того, резуль- татом может быть один компонент, причем . х эквивалентно . хххх. Низкоуровневые шейдеры фрагментов позволяют также использовать в настройке по адресам буквы г, g, Ь и а, поскольку в схемах затенения фрагментов цвета фигурируют гораздо чаще, чем координаты В связи с этим отметим, что .abgr аналогично .wzyx При работе со скалярными командами, действующими на один канал входа (cos, ЕХ2, exp, LG2, log, pow, rcp, rsq, scs и sin), вы обязаны использовать суффикс, чтобы указать один компонент, на который будет действовать функция RCP oneOverZ, myCoord.z; Маска записи выходного аргумента Суффиксы настройки по адресам определяют, какие компоненты будут доступны из каждого входного регистра Подобным образом суффиксы маски записи определя- ют, в какие компоненты выходного аргумента производится запись, а какие остают- ся нетронутыми MOV myResult.xyw, foo; # третий компонент остается без изменений В масках записи могут использоваться те же буквы компонентов, что и в на- стройке по адресам. В шейдерах вершин могут применяться буквы х, у, z и w, тогда как шейдеры фрагментов могут дополнительно включать в маски записи буквы г, g, Ь и а Последняя возможность всего лишь повышает читабельность, поскольку myColor. rgb немного легче понять с первого взгляда, чем myColor. xyz
894 Часть III OpenGL- следующее поколение Ограничение выходного аргумента Последним модификатором является ограничение значения выходного аргумента со- гласно допустимому диапазону, или насыщение. Результат команды ограничивается согласно допустимому диапазону [0,1 ] Этот модификатор наиболее полезен для цве- тов, а следовательно, доступен только в низкоуровневых шейдерах фрагментов. Чтобы ограничить выходной аргумент согласно допустимому диапазону, вы про- сто добавляете суффикс _SAT к команде шейдера фрагментов ADD myResult, primarycolor, secondarycolor; # Результат может выйти за диапазон [0,1] ADD_SAT myResult, primaryColor, secondarycolor; # В этом случае результат усекается до диапазона [0,1] Единственной командой, для которой данный суффикс бессмысленен, является не дающая выходного аргумента команда KIL. Обратите внимание на то, что OpenGL автоматически ограничивает окончатель- ный цвет и глубину в шейдере фрагментов перед использованием их на последующих каскадах конвейера, поэтому не обязательно добавлять суффикс _SAT самостоятель- но при финальной записи в result. color или result. depth Наличие в выходном аргументе модификатора ограничения просто облегчает ограничение промежуточ- ных вычислений Если требуется ограничить значение регистра в шейдере вершин, лучшим выходом будет использование команд MIN и МАХ Данная последовательность также может применяться в шейдере фрагментов для ограничения согласно любому диапазону, а не только промежутку [0,1]: MIN myValue, myValue, 1; MAX myValue, myValue, 0; Потребление ресурсов и запросы Реализации OpenGL имеют ограниченное число ресурсов, доступных для низкоуров- невых шейдеров. Эти ресурсы включают временные переменные, параметры, коман- ды и кое-что еще. Если нужно, чтобы шейдер выполнялся быстро (или даже просто выполнялся), уделите внимание данным ограничениям и попытайтесь минимизиро- вать потребление ресурсов Ограничения программы грамматического разбора Первый набор ограничений связан с программой грамматического разбора. Здесь определяется максимальное количество ресурсов, которые могут присутствовать в шейдере, чтобы OpenGL по крайней мере попытался скомпилировать ее Если, вызвав glProgramStringARB, вы превысите любой из этих пределов, грамматиче- ский разбор схемы будет невозможен, а после выдачи строки ошибки вы сможете узнать, какого ресурса не хватило В табл. 20 10 перечислены все ресурсы, метод запроса с помощью glGetPro- gramivARB пределов программы грамматического разбора, касающихся этого ресур-
Глава 20 Низкоуровневое затенение кодирование в металле 895 ТАБЛИЦА 20.10. Запросы о пределах ресурсов программы грамматического разбора Ресурс Запрос Минимум Минимум для шейдера для шейдера вершин фрагментов Команды GL_MAX_PROGRAM -INSTRUCTIONS_ARB 128 72 Временные переменные GL_MAX_PROGRAM —TEMPORARIES_ARB 12 16 Параметры GL_MAX_PROGRAM _PARAMETERS_ARB 96 24 Параметры среды GL_MAX_PROGRAM 96 24 программы _ENV_PARAMETERS_ARB Локальные параметры GL_MAX_PROGRAM 96 24 программы _LOCAL_PARAMETERS_ARB Атрибуты GL_MAX_PROGRAM _ATTRIBS_ARB 16 10 Адреса GL_MAX_PROGRAM _ADDRESS_REGISTERS_ARB 1 не определено Команды АЛУ GL_MAX_PROGRAM _ALU_INSTRUCTIONS_ARB не определено 48 Текстурные команды GL_MAX_PROGRAM _TEX_INSTRUCTIONS_ARB не определено 24 Команды разыменования GL_MAX_PROGRAM не определено 4 текстуры _TEX_INDIRECTIONS_ARB ТАБЛИЦА 20.11. Запросы о потреблении ресурсов программой грамматического разбора Ресурс Запрос Команды Временные переменные Параметры Атрибуты Адреса (только шейдеры вершин) Команды АЛУ (только шейдеры фрагментов) Текстурные команды (только шейдеры фрагментов) Команды разыменования текстуры (только шейдеры фрагментов) GL_PROGRAM_INSTRUCTIONS_ARB GL_PROGRAM_TEMPORARIES_ARB GL_PROGRAM_PARAMETERS_ARB GL_PROGRAM_ATTRIBS_ARB GL_PROGRAM_ADDRESS_REGISTERS_ARB GL_PROGRAM_ALU_INSTRUCTIONS_ARB GL_PROGRAM_TEX_INSTRUCTIONS_ARB GL_PROGRAM_TEX_INDIRECTIONS_ARB ca, а также минимальное количество данного ресурса, которое должно поддерживать- ся в любой реализации Отметим, что для шейдеров вершин и фрагментов пределы отличаются, кроме того, некоторые пределы применимы только к одному типу схем При успешном грамматическом разборе шейдера можно вызвать функцию glGet- ProgramivARB и узнать количество ресурсов каждого типа, подсчитанное про- граммой грамматического разбора. Соответствующие токены запроса перечислены в табл 20 11
896 Часть III OpenGL' следующее поколение Разыменование текстуры Возможно, вы задаете себе вопрос, что же такое “разыменование текстуры’’9 Хотя, возможно, вам это не будет интересно до первой загрузки большого шейдера, когда вы получите строку ошибки с жалобой по поводу разыменования текстуры Пожалуй, стоит все же объяснить, что это за ресурс В конвейере с фиксированными функциональными возможностями для выборки из текстур всегда используются интерполируемые текстурные координаты Однако шейдеры фрагментов позволяют использовать в качестве текстурной координаты лю- бой произвольным образом вычисленный временный регистр Фактически вы можете использовать результат одной операции поиска текстуры в качестве текстурной коор- динаты другого поиска. Подобная организация называется зависимым поиском Цепочка зависимых поисков — это просто выполнение одного зависимого поиска после другого, когда результат одного поиска становится текстурной координатой следующего и так повторяется насколько раз. Некоторые аппаратные реализации имеют внутренний предел длины цепочки зависимости, которая может применяться в шейдере фрагментов. Здесь кроется один из самых распространенных “подводных камней” шейдеров фрагментов, и если вы ознакомитесь с разыменованием текстуры, то сможете никогда не превышать “потолок” этого ресурса. В спецификации GL_ARB_fragment_program предлагается достаточно простой алгоритм, используемый программами грамматического разбора для подсчета опера- ций разыменования текстуры В данной спецификации прочтите параграф 24, “What is a texture indirection, and how is it counted?” Он достаточно прост, и вы сможе- те мысленно прогонять алгоритм при проверке текста своего шейдера фрагментов, определяя, где происходит разыменование текстуры и как устранить ненужные слу- чаи разыменования “Родные пределы” Пределы программы грамматического разбора определяют, что вы можете передать ей Все реализации должны подсчитывать ресурсы одинаково, сравнивая их с пре- делами программы грамматического разбора Однако, если шейдер находится в этих пределах и успешно разобран, вы входите в мир, где все аппаратное обеспечение разное. Здесь приходит время оптимизирующих компиляторов “Родные” пределы ресурсов более точно отражают то, что может предложить ап- паратное обеспечение Например, одни аппаратные реализации требуют выполнения восьми родных команд для вычисления синуса/косинуса, а в других для этого требу- ется всего один такт В обоих случаях программа грамматического разбора “считает”, что выполняется одна команды, но значения собственных счетчиков реализаций бу- дут отличаться Даже если на идеальном мифическом аппаратном обеспечении все команды бу- дут выполняться за один такт, пределы программы грамматического разбора и соб- ственные пределы по-прежнему могут отличаться Реализация может допускать вдвое большее количество ресурсов для программы грамматического разбора, чем в дей- ствительности поддерживается на аппаратном уровне В таком случае схема опти- мизации может попытаться так сократить шейдер, чтобы он вместилась в положен- ные пределы Подобная оптимизация включает переупорядочение команды, удаление
Глава 20 Низкоуровневое затенение- кодирование в металле 897 ТАБЛИЦА 20.12. Запросы о пределах родных ресурсов Ресурс Запрос Команды GL_MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB Временные переменные GL_MAX_PROGRAM_NATIVE_TEMPORARIES_ARB Параметры GL_MAX_PROGRAM_NATIVE_PARAMETERS_ARB Атрибуты GL_MAX_PROGRAM_NATIVE_ATTRIBS_ARB Адреса (только GL_MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB шейдеры вершин) Команды АЛУ (только GL_MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB шейдеры фрагментов) Текстурные команды (TOnbKOGL_MAX_PR0GRAM_NATIVE_TEX_INSTRUCTI0NS_ARB шейдеры фрагментов) Команды разыменования GL_MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB текстуры (только шейдеры фрагментов) ТАБЛИЦА 20.13. Запросы о потреблении родных ресурсов Ресурс Запрос Команды GL_PROGRAM_NATIVE_INSTRUCTIONS_ARB Временные переменные GL_PROGRAM_NATIVE_TEMPORARIES_ARB Параметры GL_PROGRAM_NATIVE_PARAMETERS_ARB Атрибуты GL_PROGRAM_NATIVE_ATTRIBS_ARB Адреса (только шейдеры вершин) GL_PROGRAM_NATIVE_ADDRESS_REGISTER; Команды АЛУ (только GL_PROGRAM_NATIVE_ALU_INSTRUCTIONS. шейдеры фрагментов) Текстурные команды GL_PROGRAM_NATIVE_TEX_INSTRUCTIONS. (только шейдеры фрагментов) Команды разыменования текстуры GL_PROGRAM_NATIVE_TEX_INDIRECTIONS_ (только шейдеры фрагментов) Удовлетворяет всем родным GL_PROGRAM_UNDER_NATIVE_LIMITS_ARB пределам'’ (0/1) S_ARB _ARB _ARB _ARB “мертвого кода” (невыполняемых участков программы), дублирующихся констант и сокращение временных регистров. Существенное сокращение потребления соб- ственных ресурсов возможно, поэтому большие шейдеры имеет смысл предоставлять компилятору для обработки В табл 20 12 и 20 13 перечислены запросы об установленных пределах, позво- ляющие выяснить потребление собственных ресурсов успешно скомпилированно- го и оптимизированного шейдера Все запросы выполняются с помощью команды glGetProgramivARB Обратите внимание на последнюю строку табл 20 12 Если glProgramStringARB возвращается без ошибки, можете выполнить один этот запрос и определить, попа- дают ли все ресурсы в положенные пределы Если результат — true, можно ожидать, что шейдер будет ускоряться аппаратно Если ответ отрицательный, схему можно вы- полнить на программном уровне, что может оказаться очень медленным Если хотите, можете отдельно запросить информацию по каждому ресурсу, чтобы понять, какие родные ресурсы близки к предельным значениям или превышают их
898 Часть III. OpenGL: следующее поколение Другие запросы Как обычно, если можно установить какое-то состояние OpenGL, по этому пово- ду можно направить запрос. Программные параметры запрашиваются с помощью команд glGetProgramEnvParameter*ARB и glGetProgramLocalParameter*ARB. Весь текст шейдера можно считать с помощью glGetProgramStringARB. Для про- верки текущих атрибутов вершины применяется команда glGetVertexAttrib*ARB, для проверки указателя на массив атрибутов вершин — glGetVertexAttribPoint- ervARB. Чтобы посмотреть, представляет ли данное имя существующий шейдера, вызывается команда gllsProgramARB. Подробности, касающиеся всех этих запро- сов, вы можете найти в справочном разделе. Опции шейдера На грамматику и поведение шейдера влияют опции. Соответствующая строка OP- TION вставляется в начало шейдера перед всеми командами. Существующие расши- рения GL_ARB_vertex_program и GL_ARB_fragment_program предлагают опции, рассмотренные в следующих разделах, однако в будущих расширениях могут вво- диться и другие опции. Опция инвариантности относительно положения (шейдер вершин) Обычно для вывода информации в result.position требуется шейдер вершин. Если указанная опция присутствует, вы не можете выводить информацию в re- sult, position, вместо этого вершина автоматически преобразовывается в усечен- ное пространство: !JARBvpl.O OPTION ARB_position_invariant; Использование этой опции не только удобно, когда не требуются сложные пре- образования вершин, оно также позволяет гарантировать, что преобразования будут одинаковыми при наличии и отсутствии шейдера вершин, поэтому можно не беспо- коиться об искажении точности при многопроходной визуализации. Опция наложение тумана (шейдер фрагментов) Другой полезной опцией является возможность наложения тумана в шейдерах фраг- ментов. Схемы затенения заменяют собой каскад тумана, а следовательно, отвечают за выполнение всех связанных с ним вычислений. Однако, задав одну из трех указан- ных опций тумана, вы снимите с себя всю работу (как в конвейере с фиксированными функциональными возможностями). Итак, вы можете выбирать следующие схемы: линейную, экспоненциальную и экспоненциальную второго порядка. !IARBfpl.0 OPTION ARB_fog_linear; ♦ В действительности вы не можете задавать OPTION ARB_fog_exp; # в своем шейдере более одной приведенной OPTION ARB_fog_exp2; # опции, поскольку в этом случае
Глава 20 Низкоуровневое затенение, кодирование в металле 899 # грамматический разбор даст ошибку Подсказка точности (шейдер фрагментов) Некоторые аппаратные реализации шейдеров фрагментов поддерживают несколько разновидностей расчетов с плавающей запятой и точности внутренней записи вели- чин, которые либо превышают, либо не достигают минимальных требований точ- ности OpenGL. Используя одну из приведенных опций, можно подсказать драйверу, чтобы шейдер фрагментов работал с большей или меньшей точностью, чем приме- няется по умолчанию. Помните, это просто подсказка, которую некоторые реализа- ции игнорируют. !iARBfpl.O OPTION ARB_precision_hint_nicest; # Как и выше, вы можете OPTION ARB_precision_hint_fastest; # использовать только одну # из приведенных опций Резюме Как вы могли догадаться из данной главы, по сложности низкоуровневые шейдеры сродни смеси ракетостроения и операций на головном мозге. И это при том, что мы даже не поговорили ни об одном приложении (это будет исправлено в главе 22, “Затенение вершин: сделай сам преобразование, освещение и генерацию текстуры”). Когда вы увидите эти шейдеры в действии, они покажутся вам менее страшными. В данной главе мы рассмотрели механику низкоуровневых шейдеров. На вас выва- лили множество кодов операций, типов переменных, а также модификаторов входных и выходных аргументов. Потерпите еще немного — скоро мы заставим всю эту ин- формацию работать на нас. Справочная информация glBindProgramARB Цель: Связать шейдер Включаемый файл: <glext.h> Синтаксис: void glBindProgramARB(GLenum target, GLuint program}; Описание: Связывает низкоуровневый шейдер с шейдером вершин или фрагментов. Если шейдер не был связан ранее, он создается. Последующие изменения или запроса целевого объекта влияют на состояние или возвращают состояние связанного шейдера Параметры: target (тип GLenum) Тип связываемого шейдера. Значением может быть одна из следующих констант:
900 Часть III OpenGL-следующее поколение program (тип GLuint) Что возвращает: См. также: GL_VERTEX_PROGRAM_ARB: связать шейдер вершин GL_FRAGMENT_PROGRAM_ARB связать шейдер фрагментов Имя связываемого шейдера Ничего glDeleteProgramsARB, glProgramStringARB, glGenProgramsARB glDeleteProgramsARB Цель: Удалить один или несколько шейдеров Включаемый файл: <glext.h> Синтаксис: void glDeleteProgramsARB(GLsizei n, const GLuint ★programs); Описание: Удаляет шейдер Содержимое удаляется, а имена помечаются как неиспользуемые. Если подобный буферный объект в настоящее время связан в текущем контексте, все подобные связи обнуляются Если на удаление заявлено неиспользуемое или нулевое имя, оно игнорируется Параметры: п (тип GLsizei) Число удаляемых шейдеров programs Указатель на массив, содержащий имена удаляемых шейдеров (тип GLuint*) Что возвращает: Ничего См. также: glBindProgramARB, glProgramStringARB, glGenProgramsARB, gllsProgramARB gIDisableVertexAttribArrayARB Цель: Деактивизировать массив атрибутов вершин Включаемый файл: <glext.h> Синтаксис: void gIDisableVertexAttribArrayARB(GLuint index}; Описание: Функция подобна glDisableClientState, но поскольку число атрибутов вершин не ограничено, обычные метки массива вершин использовать нельзя Вместо этого в данную точку входа передается число атрибутов вершин, указывающее, какой массив атрибутов вершин следует деактивизировать Параметры: index Идентификатор массива деактивизирусмых атрибутов вершин (тип GLuint) Что возвращает: Ничего См. также: glEnableVertexAttribArrayARB, glVertexAttribPointerARB
Глава 20 Низкоуровневое затенение- кодирование в металле 901 glEnableVertexAttribArrayARB Цель: Активизировать массив атрибутов вершин Включаемый файл: <glext. h> Синтаксис: void glEnableVertexAttribArrayARB(GLuint index); Описание: Функция подобна glEnableClientState, но, поскольку число атрибутов вершин неограничено, обычные метки массива вершин использовать нельзя Вместо этого в данную точку входа передается число атрибутов вершин, указывающее, какой массив атрибутов вершин следует активизировать Параметры: index Идентификатор массива деактивизируемых атрибутов вершин (тип GLuint) Что возвращает: Ничего См. также: glDeleteProgramsARB, glProgramStringARB, glGenProgramsARB glGenProgramsARB Цель: Вернуть неиспользуемые имена низкоуровневых шейдеров Включаемый файл: <glext. h> Синтаксис: void glGenProgramsARB(GLsizei n, GLuint ★programs); Описание: Возвращает неиспользуемые имена шейдеров Впоследствии эти имена можно связывать с помощью функции glBindProgramARB Параметры: п (тип GLsizei) programs (тип GLuint*) Что возвращает: Число требуемых имен низкоуровневых шейдеров Указатель на массив, заполняемый неиспользуемыми именами Ничего См. также: glBindProgramARB, glProgramStringARB, glDeleteProgramsARB, gllsProgramARB gIGetProgramivARB Цель: Запросить свойства шейдера Включаемый файл: <glext.h> Синтаксис: void gIGetProgramivARB(GLenum target, GLenum pname, GLint *params); Описание: Запрашивает свойство текущего низкоуровневого шейдера вершин или фрагментов
902 Часть III OpenGL: следующее поколение Параметры: target (тип GLenum) pname (тип GLenum) Тип рассматриваемого шейдера. Значением может быть одна из следующих констант: GL_vertex_program_ARB: запрос касается текущего шейдера вершин GL_FRAGMENT_PROGRAM_ARB: запрос касается текущего шейдера фрагментов Запрашиваемое свойство. Значением может быть одна из следующих констант: GL_PROGRAM_LENGTH_ARB: длина шейдера в байтах GL_program_format_arb: формат шейдера (всегда ASCII) gl_program_binding_arb: имя текущего связанного шейдера GL_program_instructions_arb: допустимое для грамматического разбора число конструкций в текущем шейдере GL_max_program_instructions_arb: максимально допустимое для грамматического разбора число конструкций GL_PROGRAM_NATIVE_INSTRUCTIONS_ARB: число собственных конструкций в текущем шейдере GL_max_program_native_instructions_arb: максимальное допустимое число собственных конструкций gl_program_temporaries_arb: число временных конструкций в текущем шейдере gl_max_program_temporaries_arb: максимально допустимое для грамматического разбора число временных конструкций gl_program_native_temporaries_arb. число собственных временных конструкций в текущем шейдере GL_max_program_native_temporaries_arb: максимально допустимое число собственных временных конструкций GL_program_parameters_arb: допустимое для грамматического разбора число параметров в текущем шейдере GL_max_program_parameters_arb- максимально допустимое для грамматического разбора число параметров GL_program_native_parameters_arb: число собственных параметров в текущем шейдере gl_max_program_native_parameters_arb: максимальное допустимое число собственных параметров GL_program_attribs_arb: допустимое для грамматического разбора число атрибутов в текущем шейдере gl_max_program_attribs_arb: максимально допустимое для грамматического разбора число атрибутов gl_program_native_attribs_arb: число собственных атрибутов текущего шейдера GL_max_program_native_attribs_arb: максимальное доступное число собственных атрибутов gl_program_address_registers_arb: допустимое для грамматического разбора число адресных регистров в текущем шейдере (только для шейдеров вершин)
Глава 20. Низкоуровневое затенение: кодирование в металле 903 GL_MAX_PROGRAM_ADDRESS_REGISTERS_ARB: максимально допустимое для грамматического разбора число адресных регистров (только для шейдеров вершин) GL_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB: ЧИСЛО собственных адресных регистров в текущем шейдере (только для шейдеров вершин) GL_MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB: максимальное доступное число собственных адресных регистров (только для шейдеров вершин) GL_MAX_PROGRAM_LOCAL_PARAMETERS_ARB: максимально допустимое для грамматического разбора число локальных параметров программы GL_MAX_PROGRAM_ENV_PARAMETERS_ARB: максимально допустимое для грамматического разбора число параметров программной среды GL_PROGRAM_UNDER_NATIVE_LIMITS_ARB: нуль, если текущий шейдер превышает предел собственных ресурсов; один — в противном случае GL_PROGRAM_ALU_INSTRUCTIONS_ARB: допустимое для грамматического разбора число инструкций АЛУ в текущем шейдере (только для шейдеров фрагментов) GL_MAX_PROGRAM_ALU_INSTRUCTIONS_ARB: максимально допустимое для грамматического разбора число инструкций АЛУ (только для шейдеров фрагментов) GL_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB: ЧИСЛО собственных инструкций АЛУ в текущем шейдере (только для шейдеров фрагментов) GL_MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB: максимально доступное число собственных инструкций АЛУ (только для шейдеров фрагментов) GL_PROGRAM_TEX_INSTRUCTIONS_ARB: допустимое для грамматического разбора число текстурных инструкций в текущем шейдере (только для шейдеров фрагментов) GL_MAX_PROGRAM_TEX_INSTRUCTIONS_ARB: максимально допустимое для грамматического разбора число текстурных инструкций (только для шейдеров фрагментов) GL_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB: ЧИСЛО собственных текстурных инструкций в текущем шейдере (только для шейдеров фрагментов) GL_MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB максимально доступное число собственных текстурных инструкций (только для шейдеров фрагментов) GL_PROGRAM_TEX_INDIRECTIONS_ARB: допустимое для грамматического разбора число разыменований текстуры в текущем шейдере (только для шейдеров фрагментов) GL_MAX_PROGRAM_TEX_INDIRECTIONS_ARB: максимально допустимое для грамматического разбора число разыменований текстуры (только для шейдеров фрагментов) GL_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB: ЧИСЛО собственных разыменований текстуры в текущем шейдере (только для шейдеров фрагментов) GL_max_program_native_tex_indirections_arb: максимально доступное число собственных разыменований (только для шейдеров фрагментов)
904 Часть III OpenGL: следующее поколение params Указатель на положение в памяти, куда будут записаны (тип GLint*) результаты запроса Что возвращает: Ничего См. также: glProgramStringARB, glGetProgramStringARB glGetProgramEnvParameter*vARB Цель: Запросить параметр программной среды Включаемый файл: <glext.h> Синтаксис: void glGetProgramEnvParameterdvARB(GLenum target, GLuint index, GLdouble *params); void glGetProgramEnvParameterfvARB(GLenum target, GLuint index, GLfloat *params); Описание: Извлекает один параметр среды из набора, совместно используемого всеми шейдерами вершин или фрагментов Возвращается четыре величины обычной точности с плавающей запятой или четыре величины двойной точности с плавающей запятой Параметры: target Тип шейдера, с которым связан запрашиваемый параметр (тип GLenum) среды Значением может быть одна из следующих констант: GL_vertex_program_arb: запрос касается параметра среды шейдера вершин GL_FRAGMENT_PROGRAM_ARB. запрос касается параметра среды шейдера фрагментов index (тип GLuint) Число запрашиваемых векторов параметров среды params Указатель на положение в памяти, куда будет занесен вектор (тип GLdouble*/ GLfloat*) параметров среды Что возвращает: Ничего См. также: glGetProgramLocalParameter*vARB, glProgramEnvParameter*ARB glGetProgramLocalParameter*vARB Цель: Запросить локальный параметр программы Включаемый файл: <glext.h> Синтаксис: void glGetProgramLocalParameterdvARB(GLenum target, GLuint index, GLdouble *paramsj; void glGetProgramLocalParameterfvARB(GLenum target, GLuint index, GLfloat *params);
Глава 20 Низкоуровневое затенение кодирование в металле 905 Описание: Извлекает один локальный параметр из связанного в настоящий момент шейдера вершин или фрагментов Возвращается четыре величины обычной точности с плавающей запятой или четыре величины двойной точности с плавающей запятой Параметры: target Тип шейдера, локальный параметр которого запрашивается (тип GLenum) Значением может быть одна из следующих констант GL_VERTEX_PROGRAM_ARB запрашивается локальный параметр текущего шейдера вершин GL_FRAGMENT_PROGRAM_ARB запрашивается локальный параметр текущего шейдера фрагментов index (тип GLuint) Номер запрашиваемого вектора локальных параметров params Указатель на положение в памяти, куда будет записан вектор (тип GLdouble*/ локальных параметров GLfloat*) Что возвращает: Ничего См. также: glGetProgramEnvParameter*vARB, glProgramLocalParameter*ARB gIGetProgramStringARB Цель: Запросить текст текущего шейдера Включаемый файл: <glext.h> Синтаксис: void gIGetProgramStringARB(GLenum target, GLenum pname, GLvoid * string); Описание: Считывает текст шейдера из связанного в настоящий момент шейдера вершин или фрагментов в объект, определяемый указателем, который предоставляется приложением Приложение должно вначале запросить длину текущего шейдера, убедившись в наличии достаточного места для всего шейдера Параметры: target Тип шейдера, текст которого запрашивается Значением может (тип GLenum) быть одна из следующих констант GL_VERTEX_PROGRAM_ARB. считывать текст шейдера вершин GL_FRAGMENT_PROGRAM_ARB считывать текст шейдера фрагментов pname Тип возвращаемой строки. Параметр должен иметь значение (тип GLenum) GL_PROGRAM_STRING_ARB string Указатель на положение в памяти, где будет храниться текст (тип GLvoid*) шейдера Что возвращает: Ничего См. также: glProgramStringARB, glGetProgramivARB
906 Часть III OpenGL: следующее поколение glGetVertexAttrib*vARB Цель: Запросить свойство атрибута вершины Включаемый файл: Синтаксис: Описание: Параметры: index (тип GLuint) pname (тип GLenum) params (тип GLdouble*/ GLfloat*/GLint*) Что возвращает: См. также: <glext.h> void glGetVertexAttribdvARB(GLuint index, GLenum pname, GLdouble *params); void glGetVertexAttribfvARB(GLuint index, GLenum pname, GLfloat *params); void glGetVertexAttribivARB(GLuint index, GLenum pname, GLint *params); Запрашивает свойство указанного (нумерованного) атрибута вершины Номер запрашиваемого атрибута вершины Запрашиваемое свойство атрибуты вершины. Значением может быть одна из следующих констант: GL_VERTEX_ATTRIB_ARRAY_ENABLED_ARB: единица, если массивы атрибутов вершин активизированы, в противном случае — нуль GL_VERTEX_ATTRIB_ARRAY_SIZE_ARB: ЧИСЛО компонентов на один элемент массива атрибутов вершин gl_vertex_attrib_array_stride_arb: шаг между элементами массива атрибутов вершин GL_VERTEX_ATTRIB_ARRAY_TYPE_ARB: ТИП ДЭННЫХ элементов массива атрибутов вершин GL_VERTEX_ATTRIB_ARRAY_NORMALIZED_ARB: единица, если данные с плавающей запятой нормированы при преобразовании в формат с плавающей запятой; в противном случае — нуль GL_CURRENT_VERTEX_ATTRIB_ARB: текущее состояние атрибута вершины Указатель на положение в памяти, где будут храниться результаты запроса Ничего glVertexAttrib*ARB, glVertexAttribPointerARB, glGetVertexAttribPointervARB glGetVertexAttribPointervARB Цель: Включаемый файл: Синтаксис: Запросить указатель на массив атрибутов вершин <glext.h> void glGetVertexAttribPointervARB(GLuint index, GLenum pname, GLvoid **pointer);
Глава 20. Низкоуровневое затенение: кодирование в металле 907 Описание: Извлекает указатель на данные указанного массива атрибутов вершин Параметры: index (тип GLuint) pname (тип GLenum) Номер запрашиваемого указателя на массив атрибутов вершин Запрашиваемое свойство массива атрибутов вершин. Параметр должен иметь значение GL_VERTEX_ATTRIB_ARRAY_POINTER_ARB pointer (тип GLvoid**) Что возвращает: См. также: Указатель на положение в памяти, где будет храниться запрошенный указатель Ничего glVertexAttribPointerARB, glGetVertexAttrib*ARB gllsProgramARB Цель: Выяснить, существует ли шейдер с указанным именем Включаемый файл: <glext.h> Синтаксис: GLboolean gllsProgramARB(GLuint program); Описание: Параметры: Запрашивает, соответствует ли данному имени шейдер program (тип GLuint) Что возвращает: Запрашиваемое имя (предположительно — шейдера) Если шейдер с таким именем связан и еще не удален, возвращается значение GL_TRUE (тип GLboolean). В противном случае возвращается значение GL_FALSE См. также: glBindProgramARB, glDeleteProgramsARB, glGenProgramsARB glProgramEnvParameter*ARB Цель: Установить параметр программной среды Включаемый файл: <glext.h> Синтаксис: void glProgramEnvParameter4dARB(GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); void glProgramEnvParameter4dvARB(GLenum target, GLuint index, const GLdouble *params); void glProgramEnvParameter4fARB(GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); void glProgramEnvParameter4fvARB(GLenum target, GLuint index, const GLfloat *params);
908 Часть III OpenGL следующее поколение Описание: Устанавливает один параметр программной среды, коллективно используемый всеми шейдерами вершин или фрагментов Параметры: target (тип GLenum) Тип шейдера, для которого устанавливается параметр программной среды Значением может быть одна из следующих констант: GL_VERTEX_PROGRAM_ARB: устанавливается параметр программной среды шейдера вершин GL_FRAGMENT_PROGRAM_ARB’ устанавливается параметр программной среды шейдера фрагментов index (тип GLuint) х, у, z, W (тип GLdouble/ GLfloat) params (тип GLdouble*/ GLfloat*) Что возвращает: Номер устанавливаемого вектора параметра программной среды Компоненты нового параметр программной среды Указатель на четыре компонента нового параметра программной среды Ничего См. также: glProgramLocalParameter*ARB, glGetProgramEnvParameter*ARB glPrograml_ocalParameter*ARB Цель: Установить локальный параметр программы Включаемый файл: <glext.h> Синтаксис: void glProgramLocalParameter4dARB(GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); void glProgramLocalParameter4dvARB(GLenum target, GLuint index, const GLdouble *params); void glProgramLocalParameter4fARB(GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); void glProgramLocalParameter4fvARB(GLenum target, GLuint index, const GLfloat *params); Описание: Устанавливает один локальный параметр для связанного в настоящий момент шейдера вершин или фрагментов Параметры: target (тип GLenum) Тип шейдера, локальный параметр которого устанавливается Значением может быть одна из следующих констант GL_VERTEX_PROGRAM_ARB устанавливается локальный параметр текущего шейдера вершин GL_FRAGMENT_PROGRAM_ARB устанавливается локальный параметр текущего шейдера фрагментов
Глава 20 Низкоуровневое затенение' кодирование в металле 909 index (тип GLuint) X, у, Z, W (тип GLdouble/ GLfloat) params (тип GLdouble*/ GLfloat*) Что возвращает: Номер устанавливаемого вектора локального параметра Компоненты нового локального параметра программы Указатель на четыре компонента нового локального параметра программы Ничего См. также: glProgramEnvParameter*ARB, glGetProgramLocalParameter*ARB glProgramStringARB Цель: Задать текст низкоуровневого шейдера Включаемый файл: <glext.h> Синтаксис: void glProgramStringARB(GLenum target, GLenum format, GLsizei len, const GLvoid *string); Описание: Посылает в OpenGL текст нового шейдера, где он разбирается, компилируется и оптимизируется При успешном завершении всех операций новый текст замещает существующий шейдер Однако если разбор или компиляция нового шейдера невозможны, текущий шейдер (если он есть) остается без изменений Параметры: target (тип GLenum) Тип шейдера, замещаемого новым текстом GL_VERTEX_PROGRAM_ARB устанавливается новый текст для связанного в настоящее время шейдера вершин GL_FRAGMENT_PROGRAM_ARB’ устанавливается новый текст для связанного в настоящее время шейдера фрагментов format (тип GLenum) len (тип GLsizei) string (тип GLvoid*) Что возвращает: Формат текста шейдера, GL_PROGRAM_FORMAT_ASCII_ARB Длина текста в байтах, не включая символ конца строки Указатель на новый текст шейдера Ничего См. также: glBindProgramARB, glGenProgramsARB, glDeleteProgramsARB
910 Часть III. OpenGL: следующее поколение glVertexAttrib*ARB Цель: Установить общий атрибут вершины Включаемый <glext. h> файл: Синтаксис: void glVertexAttriblsARB(GLuint index, GLshort x) ; void glVertexAttriblfARB(GLuint index, GLfloat x); void glVertexAttribldARB(GLuint index,GLdouble x); void glVertexAttrib2sARB(GLuint index, GLshort x, GLshort y); void glVertexAttrib2fARB(GLuint index, GLfloat x, GLfloat y); void glVertexAttrib2dARB(GLuint index, GLdouble x, GLdouble x); void glVertexAttrib3sARB(GLuint index, GLshort x, GLshort y, GLshort z); void glVertexAttrib3fARB(GLuint index, GLfloat x, GLfloat y, GLfloat z); void glVertexAttrib3dARB(GLuint index, GLdouble x, GLdouble x, GLdouble z); void glVertexAttrib4sARB(GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); void glVertexAttrib4fARB(GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat к); void glVertexAttrib4dARB(GLuint index, GLdouble x, GLdouble x, GLdouble z, GLdouble к); void glVertexAttrib4NubARB(GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); void glVertexAttriblsvARB(GLuint index,const GLshort *v); void glVertexAttriblfvARB(GLuint index,const GLfloat *v); void glVertexAttribldvARB(GLuint index,const GLdouble *v) ; void glVertexAttrib2svARB(GLuint index,const GLshort *v); void glVertexAttrib2fvARB(GLuint index,const GLfloat *v); void glVertexAttrib2dvARB(GLuint index,const GLdouble *v); void glVertexAttrib3svARB(GLuint index,const GLshort *v) ; void glVertexAttrib3fvARB(GLuint index,const GLfloat *v);
Глава 20 Низкоуровневое затенение'кодирование в металле 911 Описание: Параметры: index (тип GLuint) X, у, Z, W (все типы) v (все указатели типов) Что возвращает: См. также: void glVertexAttrib3dvARB(GLuint index,const GLdouble *v) ; void glVertexAttrib4bvARB(GLuint index,const GLbyte *v); void glVertexAttrib4svARB(GLuint index,const GLshort *v); void glVertexAttrib4ivARB(GLuint index,const GLint *v); void glVertexAttrib4ubvARB(GLuint index,const GLubyte *v) ; void glVertexAttrib4usvARB(GLuxnt index,const GLushort *v); void glVertexAttrib4uivARB(GLuint index,const GLuint *v); void glVertexAttrib4fvARB(GLuint index,const GLfloat *v); void glVertexAttrib4dvARB(GLuint index,const GLdouble *v) ; void glVertexAttrib4NbvARB(GLuint index, const GLbyte *v); void glVertexAttrib4NsvARB(GLuint index,const GLshort *v); void glVertexAttrib4NivARB(GLuint index,const GLint *v); void glVertexAttrib4NubvARB(GLuint index,const GLubyte »v); void glVertexAttrib4NusvARB(GLuint index,const GLushort *v); void glVertexAttrib4NuivARB(GLuint index,const GLuint *v); Устанавливает общий атрибут текущей вершины. Варианты функции с N вначале нормируют значение в диапазон [0,1] (для типов без знака) или [—1,1] (для типов со знаком) Номер устанавливаемого общего атрибута вершины Компоненты нового общего атрибута вершины. Если значения у, z или w не заданы, компоненты заполняются значениями 0, 0 и 1, соответственно Указатель на компоненты нового общего атрибута вершины Ничего glVertexAttribPointerARB, glGetVertexAttrib*vARB
912 Часть III OpenGL следующее поколение gIVertexAttribPointerARB Цель: Установить указатель и другие свойства массива общих атрибутов вершины Включаемый файл: <glext. h> Синтаксис: void gIVertexAttribPointerARB(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer); Описание: Устанавливает указатель массива, размер, тип и шаг для общего атрибута вершины Значения целочисленных типов могут нормироваться в диапазон [0.1] (для типов без знака) или [-1,1] (для типов со знаком) Параметры: index (тип GLuint) size (тип GLint) type (тип GLenum) Номер устанавливаемого указателя массива для общего атрибута вершины Число компонентов в элементах массива общих атрибутов вершин Тип данных массива общих атрибутов вершины Значением может быть одна из следующих констант- GL_DOUBLE. двойной точности с плавающей запятой GL_FLOAT обычной точности с плавающей запятой GLJBYTE- однобайтовое целое без знака GL_SHORT- двухбайовое целое со знаком GL_INT. четырехбайтовое целое со знаком GL_UBYTE: однобайтовое целое без знака GL_USHORT. двухбайтовое целое без знака GL_UINT четырехбайтовое целое без знака normalized (тип GLboolean) stride (тип GLsizei) pointer (тип const GLvoid*) Что возвращает: GL_TRUE, если данные с фиксированной запятой должны нормироваться, GL_FALSE — в противном случае Число байтов, пропускаемых между элементами массива Указатель на данные массива общих атрибутов вершин Ничего См. также: glGetVertexAttribPointervARB, glVertexAttrib*ARB
ГЛАВА 21 Высокоуровневое затенение: жизненно необходимая "мелочь" Бенджамин Липчак ИЗ ЭТОЙ ГЛАВЫ ВЫ УЗНАЕТЕ . . Действие Функция Создание объектов шейдеров/программ Задание и компиляция шейдеров glCreateShaderObj ectARB/ glCreateProgramObj ectARB glShaderSourceARB/ glCompileShaderARB Присоединение/открепление шейдеров и связей Переключение между программами Задание переменных с постоянными значениями Получение предупреждений и информации об ошибках glAttachObjectARB/glDetachObject ARB / glLinkProgramARB glUseProgramObjectARB glUniform*ARB glGetlnfoLogARB Теоретически все приложения можно писать на языке ассемблера Однако вы этого не делаете, и на то есть важные причины, эффективное использование времени раз- работки, читабельность, удобство эксплуатации и переносимость, а также множество других соображений. Благодаря современным компиляторам языков высокого уровня преимущества языка ассемблера становятся незначительными, поскольку компиля- торы могут генерировать код, выполняющийся так же эффективно (а в некоторых случаях даже лучше), как и код, вручную написанный на языке низкого уровня Так зачем мы вообще обсуждали низкоуровневое затенение, а не просто упомяну- ли о нем и перешли к высокоуровневым шейдерам, основанным на языке затенения OpenGL (OpcnGL Shading Language — GLSL)? Ответ связан с доступностью. Стан- дартизованные низкоуровневые расширения, рассмотренные в главе 20, “Низкоуров- невое затенение- кодирование в металле”, были доступны многие годы, причем имели статус “единственной игры в городе”. До недавнего времени разработчики, работав- шие с OpenGL, не имели высокоуровневой альтернативы. Поэтому единственным выбором разработчиков игр и приложений, желавших использовать возможности за- тенения, были низкоуровневые расширения
914 Часть III OpenGL- следующее поколение Принятие низкоуровневых расширений было сравнительно быстрым, посколь- ку эти расширения предлагали недоступные ранее функциональные возможности. Вполне вероятно, что миграция на GLSL будет более медленной, поскольку высо- коуровневые шейдеры большей частью предлагают только эквивалентные функцио- нальные возможности, делая это просто более удобным для эксплуатации образом. С другой стороны, низкоуровневые шейдеры имеют двухлетнее преимущество. При- ложения с длительным циклом разработки часто создаются по принципу “если не ломается, чинить не будем”, когда завершить работу старыми средствами выгоднее, чем переходить на новые. Впрочем GLSL вскоре станет предпочтительным инстру- ментом, а низкоуровневые шейдеры превратятся в сноску в учебниках по OpenGL. Управление высокоуровневыми шейдерами Утвержденные ARB расширения GLSL отличаются от основного интерфейса OpenGL и других расширений с точки зрения программного интерфейса, который они пред- ставляют. Здесь больше нет обязательной цепочки “генерация/связывание/удаление”; GLSL предлагает совершенно новые точки входа. Объекты шейдера В GLSL применяется два типа объектов: объекты шейдера и объекты программы. Наше рассмотрение начнется с объектов шейдера, которые загружаются с текстом шейдера, а затем компилируются. Создание и удаление Объекты шейдера создаются посредством вызова функции glCreateShaderObj ec- tARB и передачи ей типа требуемого шейдера (вершин или фрагментов). Эта функция возвращает обработчик объекта шейдера, используемый для последующего обраще- ния к объекту: GLhandleARB myVertexShader = glCreateShaderObj ectARB(GL_VERTEX_SHADER_ARB); GLhandleARB myFragmentShader = glCreateShaderObj ectARB(GL_FRAGMENT_SHADER_ARB); Будьте внимательны: возможно, создать объект не удастся, и в этом случае бу- дет возвращено значение 0. Это бывает при перерасходе OpenGL ресурсов памяти или обработчиков объектов. Завершив работу с объектами шейдеров, следует убрать ненужное. glDeleteObjectARB(myVertexShader); glDeleteObjectARB(myFragmentShader); Другие объекты OpenGL, в том числе текстурные объекты, при удалении использу- емого в настоящий момент объекта освобождают его привязку. Объекты GLSL ведут себя иначе; функция glDeleteObj ectARB просто помечает объект для последующего удаления, которое произойдет тогда, когда объект не будет больше использоваться.
Глава 21 Высокоуровневое затенение: жизненно необходимая “мелочь” 915 Задание текста шейдера Цель объекта шейдера — просто принять текст схемы и скомпилировать его. Текст шейдера может быть жестко закодирован в виде строкового литерала, считывать- ся из файла на диске или генерироваться по ходу дела. В любом случае он дол- жен представлять собой последовательность символов, которую можно загрузить в объект шейдера. GLcharARB ‘myStringPtrs[ 1 ]; myStringPtrs[0] = vertexShaderText; glShaderSourceARB(myVertexShader, 1, myStringPtrs, NULL); myStringPtrs[0] = fragmentShaderText; glShaderSourceARB(myFragmentShader, 1, myStringPtrs, NULL); Функция glShaderSourceARB принимает несколько отдельных строк. Ее второй аргумент представляет собой счетчик, указывающий количество указателей на стро- ки. Перед компиляцией эти строки последовательно выстраиваются в одну большую строку. Описанная возможность полезна, например, когда из библиотеки функций загружаются подпрограммы с возможностью повторного использования. GLcharARB ‘myStringPtrs[ 3 ]; myStringPtrs[0] = vsMainText; myStringPtrs[1] = myNoiseFuncText; myStringPtrs[2] = myBlendFuncText; glShaderSourceARBfmyVertexShader, 3, myStringPtrs, NULL); Если все строки заканчиваются символом конца строки, необязательно задавать длину строк в четвертом аргументе, используя вместо этого null. Однако если текст шейдера не заканчивается символом конца строки, длину нужно указывать, причем длина считается без символа конца строки (если он есть). Для строк, заканчивающих- ся символом конца строки можно использовать значение -1. В приведенном ниже коде вместе с массивом указателей строк передается указатель на массив длины. GLint fsLength = strlenffragmentShaderText); myStringPtrs[0] = fragmentShaderText; glShaderSourceARB(myFragmentShader, 1, myStringPtrs, &fsLength); Компиляция шейдеров Загрузив текст в объект шейдера, нужно его скомпилировать. При компиляции вы- полняется грамматический разбор шейдера и проверяется наличие ошибки. glCompileShaderARB(myVertexShader); glCompileShaderARB(myFragmentShader); Чтобы убедиться, что компиляция прошла успешно, по каждому объекту шей- дера можно запросить соответствующую метку. Кроме того, каждый объект имеет информационную запись, которая в случае сбоя компиляции содержит сообщения об ошибках. Кроме того, она может содержать предупреждения или другую полезную информацию, даже если компиляция прошла успешно. Подобные записи предназна- чены в первую очередь для отладки разрабатываемого приложения GLSL. glCompileShaderARB(myVertexShader); glGetObj ectParame te r ivARB(myVe rtexShader, GL_OBJECT_COMPILE_STATUS_ARB, &success);
916 Часть III OpenGL: следующее поколение if (!success) { GLbyte infoLog[MAX_INFO_LOG_SIZE]; glGetlnfoLogARB(myVertexShader, MAX_INFO_LOG_SIZE, NULL, infoLog); fprintf(stderr, "Error in vertex shader compilation!\n"); fprintf(stderr, "Info log: %s\n", infoLog); Sleep(10000); exit(0); ) Возвращаемая строка информационной записи всегда завершается символом кон- ца строки. Если вы не собираетесь выделять большой статический массив для хране- ния информационной записи, перед запросом записи можно узнать ее точный размер. glGetObjectParameterivARB(myVertexShader, GL_OBJECT_INFO_LOG_LENGTH_ARB, &infoLogSize); Объекты программы Вторым типом объектов, используемых GLSL, являются объекты программы. Этот объект выступает как контейнер для объектов шейдеров, связывая их в единый ис- полняемый файл Помните, что можно задать схему затенения GLSL для каждого замещаемого фрагмента обычного конвейера OpenGL. В настоящее время замещать можно только каскады обработки вершин и фрагментов, но данный список можно расширить, включив в него дополнительные этапы Создание и удаление Объекты программы создаются и удаляются точно так же, как объекты шейдеров. От- личие состоит только в том, что существует единственный тип объектов программы, поэтому при создании точка входа не имеет аргумента. GLhandleARB myProgram = glCreateProgramObjectARB(); glDeleteObjectARB(myProgram); Присоединение и открепление Как уже говорилось, объект программы — это контейнер. Если вам требуется GLSL, а не фиксированные функциональные возможности, то к этому контейнеру нужно присоединить объекты шейдеров. glAttachObjectARB(myProgram, myVertexShader) ; glAttachOb]ectARB(myProgram, myFragmentShader); Вы даже можете присоединить к объекту программы несколько объектов шей- деров одного типа Как и загрузка нескольких строк с исходным кодом шейдера в один объект шейдера, это позволяет включать в приложение библиотеки функций, совместно используемые несколькими объектами программы. Можете заменить схемой GLSL только часть конвейера, оставив остальное с фик- сированными функциональными возможностями Все, что для этого нужно, — не
Глава 21 Высокоуровневое затенение, жизненно необходимая “мелочь" 917 присоединять шейдеры для частей, которые вы хотите оставить Если же вы перехо- дите от GLSL к фиксированным функциональным возможностям на одном участке конвейера, то можете отсоединить ранее присоединенный объект шейдера Вы да- же можете отсоединить оба шейдера, вернувшись к полноценным фиксированным функциональным возможностям. glDetachObjectARB(myProgram, myVertexShader) ; glDetachObjectARB(myProgram, myFragmentShader) ; Связывание программ Прежде чем вы сможете использовать GLSL для визуализации, нужно связать объект программы. При этом все ранее скомпилированные объекты шейдеров связываются в один исполняемый файл. glLinkProgramARB(myProgram); Убедиться, что связывание удалось, можно, запросив метку объекта программы Кроме того, каждый объект имеет информационную запись, которая в случае сбоя связывания содержит сообщения об ошибках Даже если связывание прошло успеш- но, она может содержать предупреждения или другую полезную информацию glLinkProgramARB(myProgram); glGetObjectParameterivARB(myProgram, GL_OBJECT_LINK_STATUS_ARB, &success); if (’success) { GLbyte infoLog[MAX_INFO_LOG_SIZE]; glGetlnfoLogARB(myProgram, MAX_INFO_LOG_SIZE, NULL, infoLog); fprintf(stderr, "Error in program linkage!\n") ; fprintf(stderr, "Info log: %s\n", infoLog); Sleep(10000); exit(O); Верификация программ Если связывание прошло успешно, шансы на то, что на момент визуализации шейде- ры будут исполняемыми файлами, довольно велики Тем не менее на момент связыва- ния неизвестны некоторые вещи, например значения, присвоенные схемам выборки текстуры (описываются ниже) Возможно, схемы выборки текстуры получили непри- емлемое значение, или же несколько схем установлено с одинаковым значением На момент связывания вы не знаете, каким будет состояние на момент визуализации, поэтому можете пропустить ошибку. Используя верификацию, вы исследуете теку- щее состояние и выясняете, будут ли выполняться шейдеры GLSL при рисовании первого треугольника glValidateProgramARB(myProgram); glGetObjectParameterivARB(myProgram, GL_OBJECT_VALIDATE_STATUS_ARB, &success); if (!success) { GLbyte infoLog[MAX_INFO_LOG_SIZE];
918 Часть III OpenGL: следующее поколение glGetlnfoLogARB(myProgram, MAX_INFO_LOG_SIZE, NULL, infoLog); fprintf(stderr, "Error in program validation!\n"); fprintf(stderr, "Info log: %s\n", infoLog); Sleep(10000); exit(0); 1 Как и ранее, при сбое верификации в информационную запись объекта програм- мы включается объяснение и, возможно, подсказки по устранению подобных сбоев. Обратите внимание на то, что верификация объекта программы перед визуализацией необязательна, однако, если вы попытаетесь использовать объект программы, “прова- ливший” верификацию, команды визуализации наверняка породят ошибки OpenGL. Использование программ Наконец, вы готовы запустить программу. В отличие от других возможностей OpenGL, режим GLSL не переключается посредством glEnable/glDisable. glUseProgramObject(myProgram); Эту функцию можно использовать для активизации GLSL с конкретным объектом программы, а также для переключения между различными объектами программы. Чтобы деактивизировать GLSL и вернуться к фиксированным функциональным воз- можностям, применяется та же функция, но с параметром 0: glUseProgramObject(0); В любой момент вы можете запросить обработчик текущего объекта программы: currentProgObj = glGetHandleARB(GL_PROGRAM_OBJECT_ARB); Теперь, зная, как управлять шейдерами, аы можете сосредоточиться на их содер- жимом. Синтаксически GLSL очень близок к C/C++, поэтому для вас он не должен представлять особых проблем. Настройка расширений Подобно расширениям низкоуровневых шейдеров GLSL не является частью основ- ной библиотеки OpenGL (по крайней мере так было на момент написания книги). Следовательно, наличие нужных расширений следует проверить с помощью запроса, кроме того (см листинг 21.1) нужно получить указатели на точки входа функций. Необходимо проверить наличие четырех расширений GLSL: GL_ARB_shader_ objects, GL_ARB_vertex_shader, GL_ARB_fragment_shader и GL_ARB_shading_ language_100. Первое содержит функциональные возможности, совместно исполь- зуемые шейдерами вершин и фрагментов. Последнее отражает доступную версию языка затенения OpenGL. Скорее всего со временем будут доступны обновленные версии. Листинг 21.1. Проверка наличия возможностей OpenGL // Проверяем, доступны ли требуемые функциональные возможности if (!glt!sExtSupported("GL_ARB_vertex_shader") || !gltIsExtSupported("GL_ARB_fragment_shader") |I !glt!sExtSupported("GL_ARB_shader_objects") ||
Глава 21 Высокоуровневое затенение, жизненно необходимая “мелочь” 919 !gltIsExtSupported("GL_ARB_shading_language_100")) < fprintf(stderr, "GLSL extensions not available!\n") ; Sleep(2000); exit(0); ) glCreateShaderObjectARB = gltGetExtensionPointer("glCreateShaderObjectARB"); glCreateProgramObjectARB = gltGetExtensionPointer("glCreateProgramObjectARB"); glAttachObjectARB = gltGetExtensionPointer("glAttachObjectARB"); glDetachObjectARB = gltGetExtensionPointer("glDetachObjectARB"); glDeleteObjectARB = gltGetExtensionPointer("glDeleteObjectARB"); glShaderSourceARB = gltGetExtensionPointer("glShaderSourceARB"); glCompileShaderARB = gltGetExtensionPointer("glCompileShaderARB"); glLinkProgramARB = gltGetExtensionPointer("glLinkProgramARB"); glValidateProgramARB = gltGetExtensionPointer("glValidateProgramARB"); glUseProgramObjectARB = gltGetExtensionPointer("glUseProgramObjectARB"); glGetObjectParameterivARB = gltGetExtensionPointer("glGetObjectParameterivARB"); glGetlnfoLogARB = gltGetExtensionPointer("glGetlnfoLogARB"); glUniformlfARB = gltGetExtensionPointer("glUniformlfARB"); glGetUniformLocationARB = gltGetExtensionPointer("glGetUniformLocationARB"); if (!glCreateShaderObjectARB II !glCreateProgramObjectARB || (glAttachObjectARB || !glDetachObjectARB || (glDeleteObjectARB || (glShaderSourceARB || (glCompileShaderARB || (glLinkProgramARB || (glValidateProgramARB || (glUseProgramObjectARB || (glGetObjectParameterivARB || (glGetlnfoLogARB || (glUniformlfARB || (glGetUniformLocationARB) { fprintf(stderr, "Not all entrypoints were available!\n") ; Sleep(2000); exit(0); } Переменные Переменные и функции нужно объявлять заранее. В имени переменной разреша- ется использовать любые буквы (имена чувствительны к регистру), цифры и знак подчеркивания, но имя не должно начинаться с цифры. Кроме того, переменная не может начинаться с префикса gl_, который зарезервирован для встроенных перемен- ных и функций. Существует список зарезервированных ключевых слов, доступных в спецификации языка шейдеров OpenGL.
920 Часть III OpenGL- следующее поколение ТАБЛИЦА 21.1. Стандартные типы данных Тип Описание void Тип данных, требуемый для функций, не возвращающих значение Данный тип могут также использовать функции, не требующие аргументов bool Булева переменная, используемая преимущественно в условиях и циклах Ей можно присваивать зарезервированные значения true и false, а также значение любого выражения, которое вычисляется как булево int Переменная, представляемая целым числом со знаком, имеющим по крайней мере 16 бит Может выражаться в десятичной, восьмеричной или шестнадцатеричной форме. Используется преимущественно как счетчик циклов или индекс массивов float Переменная в формате с плавающей запятой, аппроксимирующая формат обычной точности. Может выражаться в научной форме записи (например, 0 0001 = 1е-4) bvec2 Двухкомпонентный булев вектор bvec3 Трехкомпонентный булев вектор bvec4 Четырехкомпонентный булев вектор ivec2 Двухкомпонентный целочисленный вектор ivec3 Трехкомпонентный целочисленный вектор ivec4 Четырехкомпонентный целочисленный вектор vec2 Двухкомпонентный вектор величин с плавающей запятой vec3 Трехкомпонентный вектор величин с плавающей запятой vec4 Четырехкомпонентный вектор величин с плавающей запятой mat2 Матрица 2x2 величин с плавающей запятой Матрицы развертываются по столбцам mat3 Матрица 3x3 величин с плавающей запятой mat4 Матрица 4x4 величин с плавающей запятой samplerlD Специальная константа, используемая встроенными функциями текстуры для обращения к конкретной одномерной текстуре Может объявляться только как аргумент с постоянным значением или аргумент функции sampler2D Константа, используемая для обращения к двухмерной текстуре sampler3D Константа, используемая для обращения к трехмерной текстуре samplerCube Константа, используемая для обращения к кубической текстуре samplerIDShadow Константа, используемая для обращения к одномерной текстуре глубины при сравнению с тенью sampler2DShadow Константа, используемая для обращения к двухмерной текстуре глубины при сравнению с тенью Стандартные типы Помимо типов, существующих в С (булевы, целочисленные и с плавающей запятой), GLSL вводит несколько типов данных, широко применяемых в схемах затенения Данные стандартные типы данных перечислены в табл 21 1. Структуры Структуры можно использовать для группирования стандартных типов данных в ти- пы данных, определяемые пользователем. Определяя структуру, можно объявить эк- земпляры структуры (это можно сделать и позже)
Глава 21 Высокоуровневое затенение жизненно необходимая “мелочь” 921 struct surface { float indexOfRefraction; float reflectivity; vec3 color; float turbulence; } myFirstSurf; surface mySecondSurf; Можно присваивать одну структуру другой (=) или сравнивать две структуры (==, '=) В обоих случаях структуры должны принадлежать к одному объявленному типу Две структуры считаются равными, если все составляющие их поля покомпонентно равны. Чтобы обратиться к отдельному полю структуры, используется селектор (). vec3 totalColor = myFirstSurf.color + mySecondSurf.color; Определение структуры должно содержать по крайней мере один элемент. Масси- вы, которые рассматриваются ниже, могут включаться в структуры, но только в том случае, когда указывается конкретный размер массива. В отличие от структур языка С, GLSL не допускает битовых полей структуры. Вложение одной структуры в дру- гую разрешается, если включаемая структура объявляется в шейдере заранее или одновременно с включающей структурой, но не позже ее. struct superSurface { vec3 points[30]; // Все нормально; массивы заданного размера surface surf; // Все нормально, поскольку поверхность // определена ранее struct velocity { // Все нормально, структура velocity // определяется одновременно с superSurface float speed; vec3 direction; } velo; subSurface sub; // НЕВЕРНАЯ ОПЕРАЦИЯ!! // Преждевременное объявление In- struct subSurface { int id; Массивы В GLSL возможно объявление одномерных массивов любого типа (включая стр\ к i \ ры). При этом размер массива объявлять не нужно, если он индексируется нс чснп ленной константой. В противном случае размер массива необходимо объявить vec4 lightPositions[8]; surface mySurfaces[]; const int numSurfaces = 5; surface myFiveSurfaces[numSurfaces]; При объявлении массива как параметра в объявлении функции или । структуры всегда требуется явно объявлять размер.
922 Часть III. OpenGL: следующее поколение ТАБЛИЦА 21.2. Спецификаторы типа Спецификатор Описание const В процессе объявления инициализируется постоянное значение. Во время выполнения шейдера оно имеет статус “только чтение" Кроме того оно используется с аргументом вызова функции, указывая, что этот аргумент является константой, которую функция изменить не может attribute Информация о вершине, имеющая статус “только чтение”; доступна только в шейдерах вершин Эти данные извлекаются из текущего состояния вершины или из массива вершин. Их следует объявлять глобально (вне uniform всех функций) Еще одно значение, которое остается постоянным в каждом цикле выполнения шейдера, но в отличие от const это значение не известно на момент компиляции и инициализируется вне шейдера Значение uniform совместно используется текущими активными шейдерами вершин и фрагментов, и его нужно объявлять глобально varying Выходы шейдера вершин (например, цвета или текстурные координаты), соответствующие интерполированным входам шейдера фрагментов (входы имеют статус “только чтение”). Должны объявляться глобально in Спецификатор, используемый с аргументом вызова функции и указывающий, что аргумент является только входом, и любые изменения переменной в вызываемой функции не должны влиять на значение вызываемой функции. Если спецификатор отсутствует, подобное поведение аргументов функции принимается по умолчанию out Спецификатор, используемый с аргументом вызова функции и указывающий, что аргумент является выходным, поэтому функции не нужно передавать никакой входной аргумент inout Спецификатор, используемый с аргументом вызова функции и указывающий, что аргумент является и входным, и выходным Значение получается от вызывающей функции и модифицируется вызываемой функцией Спецификаторы Переменные можно объявлять с необязательным спецификатором типа. Допустимые спецификаторы перечислены в табл 21.2. Встроенные переменные Встроенные переменные позволяют взаимодействовать с конвейером с фиксирован- ными функциональными возможностями. Отметим, что их не нужно объявлять перед использованием. Большая часть встроенных переменных перечислена в табл. 21.3 и 21.4. Переменные типа uniform и const описываются в спецификации GLSL. Выражения В последующих разделах описываются различные операторы и выражения GLSL.
Глава 21 Высокоуровневое затенение: жизненно необходимая “мелочь” 923 ТАБЛИЦА 21.3. Встроенные переменные шейдеров вершин Имя Тип Описание gl_Position vec4 Выходное значение преобразованного положения вершины, которое будет использоваться на каскадах сборки, отсечения и отбора конвейера с фиксированными функциональными возможностями; все шейдеры вершин обязаны записывать значение в эту переменную gl_PointSize float Выходное значение размера точки, подлежащей растеризации; измеряется в пикселях gl_ClipVertex vec4 Выходное значение координаты, используемой для ограничения пользовательскими плоскостями отсечения gl_Color vec4 Входной атрибут, соответствующий первичному цвету вершины gl_SecondaryColor vec4 Входной атрибут, соответствующий вторичному цвету вершины gl_Normal vec3 Входной атрибут, соответствующий нормали вершины gl_Vertex vec4 Входной атрибут, соответствующий положению вершины в пространстве объекта gl_MultiTexCoordn vec4 Входной атрибут, соответствующий текстурной координате п вершины gl_FogCoord float Входной атрибут, соответствующий координате тумана вершины gl_FrontColor vec4 Переменный выходной атрибут, представляющий первичный цвет передней грани gl_BackColor vec4 Переменный выходной атрибут, представляющий первичный цвет задней грани gl_FrontSecondaryColor vec4 Переменный выходной атрибут, представляющий вторичный цвет передней грани gl_BackSecondaryColor vec4 Переменный выходной атрибут, представляющий вторичный цвет задней грани gl_TexCoord[] vec4 Массив переменных выходных атрибутов, представляющих текстурные координаты gl_FogFragCoord float Переменный выходной атрибут, представляющий координату тумана Операторы За несколькими исключениями в GLSL доступны все знакомые операторы С. Полный их список приводится в табл. 21 5. В языке GLSL отсутствует несколько операторов Поскольку заботиться об указа- телях не требуется, не нужен оператор адреса (&) или оператор разыменования (*) Оператор приведения типа также не требуется, поскольку приведение типов в GLSL не допускается. Побитовые операторы (&, |,", «, », &=, |=, '=, «=, »=) и опе- раторы действий по модулю (%, %=) зарезервированы для будущего использования.
924 Часть III OpenGL: следующее поколение ТАБЛИЦА 21.4. Встроенные переменные шейдеров фрагментов Имя Тип Описание gl_FragCoord vec4 Входной атрибут co статусом “только чтение”, содержащий величины х, у, z и 1/w в пространстве окна gl_FrontFacing bool Входной атрибут со статусом “только чтение”, значение которого равно true, если относится к лицевой части примитива gl_FragColor vec4 Выходной цвет, используемый в последующих операциях с пикселями gl_FragDepth float Выходная глубина, используемая в последующих операциях с пикселями, если никакое значение ей не присвоено, используется глубина конвейера с фиксированными функциональными возможностями gl_Color vec4 Интерполированный вход со статусом “только чтение", содержащий первичный цвет gl_SecondaryColor vec4 Интерполированный входной атрибут со статусом “только чтение", содержащий вторичный цвет gl_TexCoord[] vec4 Массив интерполированных входных атрибутов со статусом “только чтение”, содержащих текстурные координаты gl_FogFragCoord float Интерполированный входной атрибут со статусом “только чтение”, содержащий координаты тумана ТАБЛИЦА 21.5. Операторы в порядке старшинства (от старшего к младшему) Оператор Описание && || " Способ группирования, вызов функции или конструктор Индекс массива, селектор вектора или матрицы Селектор поля структуры, селектор компонента вектора Префиксиный или постфиксный инкремент и декремент Унарное сложение, вычитание, логическое НЕ Умножение и деление Бинарное сложение и вычитание Меньше, больше, меньше или равно, больше или равно, равно, не равно Логические И, ИЛИ, исключающее ИЛИ Условный оператор Присваивание, арифметическое присваивание Последовательность Доступ к массиву Л 1Я индексирования массивов применяются целочисленные выражения, первый эле- мен1 массива имеет индекс 0 Если обратиться к массиву, используя индекс, который меньше нуля или больше или равен размеру массива, результат выполнение шейдера hi неопределенным myFifthColor, ambient, diffuse[6], specular[6];
Глава 21 Высокоуровневое затенение: жизненно необходимая “мелочь" 925 myFifthColor = ambient + diffuse[5] + specular[5]; //А вот так индексировать массив НЕ надо! vec4 mySyntaxError=ambient + diffuse[-l] + specular[6]; Конструкторы Конструкторами называются специальные функции, используемые преимуществен- но для инициализации переменных, особенно в многокомпонентных типах данных (но не в массивах) Они имеют вид вызовов функции, причем имя функции идентично имени типа. vec3 myNormal = vec3(0.0, 1.0, 0.0); Конструкторы позволяют не только инициализировать переменные путем объяв- ления, в любом месте шейдера они могут использоваться как выражения greenTint = myColor + vec3(0.0, 1.0, 0.0); Всем элементам вектора присваивается одно скалярное значение. ivec4 myColor = ivec4(255); // Все 4 компонента равны 255 В конструкторе можно смешивать и сравнивать скаляры, векторы и матрицы, если результат имеет достаточно компонентов для инициализации типа данных Любые лишние компоненты отбрасываются vec4 myVectorl = vec4(x, vec2(y, z), w); vec2 myVector2 = vec2(myVectorl); // z, w отбрасываются float myFloat = float(myVector2); // у отбрасывается Матрицы строятся по столбцам. Если вы указываете единственное скалярное зна- чение, оно используется в качестве диагональных матричных элементов, а все осталь- ные элементы установятся равными 0. // Результатом всех трех определений является // одна и та же матрица 2x2 mat2 myMatrixl = mat2(1.0, 0.0, 0.0, 1.0); mat2 myMatrix2 = mat2(vec2(1.0, 0.0), vec2(0.0, 1.0)); mat2 myMatrix3 = mat2(1.0); Конструкторы могут также использоваться для преобразования различных скаляр- ных типов, причем это — единственный способ выполнить преобразование типов Ни явное, ни неявное приведение типа не допускается. Преобразование типа int в тип float очевидно. При обратном преобразовании отбрасывается дробная часть числа. При преобразовании величин типа int или float в тип bool, значения 0 или 0.0 переходят в false, а все остальные — в true При преобразовании булевых величин в тип int или float true переходит в 1 или 1.0, a false — в 0 или 0.0 float myFloat = 4.7; int mylnt = int(myFloat); // mylnt = 4 bool myBool = bool(mylnt); // myBool = true myFloat = float(myBool); // myFloat = 1 Наконец, можно инициализировать структуры, указывая аргументы того же типа и в том же порядке, как и в определении структуры
926 Часть III. OpenGL: следующее поколение struct surface { float indexOfRefraction; float reflectivity; vec3 color; float turbulence; surface mySurf = surface(ior, refl, vec3(red, green, blue), turb); Селекторы компонентов Для обращения к отдельным компонентам вектора можно использовать точку и обо- значения компонентов в виде {x,y,z,w}, {r,g,b,a} или {s,t,p,q}. Такие различные фор- маты записи удобны для представления положений и нормалей, цветов и текстурных координат. Обратите внимание на использование в текстурных координатах буквы р вместо привычной г. Данный компонент был переименован, чтобы избежать путани- цы с компонентом г цвета. Селекторы, характеризующие различные форматы записи, смешивать не разрешается. vec3 myVector = {0.25, < float myR = myVector.г; vec2 myYZ = myVector.yz, float myQ = myVector.q; 0.75}; float myRY = myVector.ry; недопустимая операция, попытка доступа к компоненту, не принадлежащему vec3 недопустимая операция, смешаны две формы записи Селекторы компонентов можно использовать для изменения порядка компонентов или дублирования их. vec3 myZYX = myVector.zyx; // обратный порядок vec4 mySSTT = myVector.sstt; 11 дважды дублируются величины s и t Селекторы могут выступать в качестве меток записи с левой стороны оператора присваивания, позволяя таким образом указывать, какие компоненты модифициру- ются. В таком случае нельзя использовать селекторы компонентов более одного раза. vec4 myColor = vec4(0.0, 1.0, 2.0, 3.0); myColor.х = -1.0; // -1.0, 1.0, 2.0, 3.0 myColor.yz = vec2(-2.0, -3.0); // -1.0, -2.0, -3.0, 3.0 myColor.wx = vec2(0.0, 1.0); // 1.0, -2.0, -3.0, 0.0 myColor.zz = vec2(2.0, 3.0); // недопустимая операция Существует и другой способ обращения к отдельным компонентам вектора или матрицы — с помощью индексов массивов В этом случае можно использовать про- извольный рассчитанный индекс, обращаясь к вектору или матрице, как если бы они представляли собой массивы. Если обратиться к компоненту, отсутствующему в векторе или матрице, результат выполнения шейдера будет неопределенным. float myY = myVectorfl]; float myBug = myVector[-l]; It He пытайтесь делать такое!
Глава 21 Высокоуровневое затенение: жизненно необходимая “мелочь" 927 Если указать для матрицы один индекс массива, это будет пониматься как обра- щение к соответствующему столбцу матрицы как к вектору. Указывая второй индекс массива, вы обращаетесь к соответствующему компоненту этого вектора. mat3 myMatrix = mat3(1.0); vec3 myFirstColumn = myMatrix[O]; // первый столбец: 1.0, 0.0, 0.0 float element21 = myMatrix[2][1]; // последний столбец, // средняя строка: 0.0 Поток управления Низкоуровневые шейдеры допускают только один линейный поток выполнения. GLSL предлагает множество знакомых нелинейных механизмов, уменьшающих раз- мер кода, позволяющих реализовать более сложные алгоритмы и увеличивающих читабельность шейдеров. Циклы Вы можете использовать циклы for, while и do/while с тем же синтаксисом, что и в C/C++. Циклы можно вкладывать. Для преждевременного перехода к следующей итерации или выхода из цикла можно использовать команды continue и break. for (1=0; 1 < numLights; 1++) { if (!lightExists[l]) continue; color += light[1]; while (true) { if (lightNum < 0) break; color += light[lightNum]; lightNum—; do ( color += light[lightNum]; lightNum—; } while (lightNum > 0); if/else Для выбора из нескольких блоков кода применяются условия if и if/else. Данные условные операторы также могут быть вложенными. color = unlitColor; if (numLights > 0)
928 Часть III OpenGL: следующее поколение color = litColor; } if (numLights > 0) { color = litColor; } else { color = unlitColor; } discard Шейдеры фрагментов имеют специальный механизм управления потоком, назы- ваемый отбрасыванием Соответствующая команда discard прекращает выпол- нение схемы обработки текущего фрагмента. Все последующие каскады конвейе- ра, связанные с обработкой фрагмента, пропускаются, и фрагмент не записывается в буфер кадров // Допустим, в шейдере фрагментов производится альфа-тест if (color.а < 0.9) discard; Функции Функции используются для формирования блочной структуры кода шейдера Все шейдеры должны определять функцию main, с которой начинается выполнение схе- мы. Список параметров void в данном случае является необязательным void main(void) ( } Перед использованием функции нужно либо определить, либо объявить с про- тотипом. Подобные определения или объявления должны быть глобальными, те располагаться вне всех функций. Возвращаемые типы и типы всех аргументов функ- ции обязательны. Кроме того, аргументы могут иметь необязательный спецификатор in, out, inout или const (см табл. 21.2): // Объявление функции bool isAnyComponentNegative(const vec4 v); /I Определение функции bool isAnyComponentNegative(const vec4 v) { if ((v.x < 0.0) || (v.y < 0.0) || (v.z < 0.0) || (v.w < 0.0)) return true; else
Глава 21 Высокоуровневое затенение-жизненно необходимая “мелочь" 929 return false; // Использование функции void main() bool someNeg = isAnyComponentNegative(gl_MultiTexCoordO); 1 Структуры могут использоваться как аргументы и возвращаемые типы Массивы разрешается применять только как аргументы, и в этом случае объявление и опреде- ление будут включать имя массива и его размер, тогда как в вызове функции будет фигурировать просто имя массива без скобок и указания размера vec4 sumMyVectors(int howManyToSum, vec4 v[10J); void main() { vec4 myColors[10]; gl_FragColor = sumMyVectors(6, myColors); 1 Одно и то же имя можно присвоить нескольким функциям при условии, что воз- вращаемый тип или типы аргументов этих функций отличаются Это называется перегрузкой имени функции (function name overloading) и является полезной возмож- ностью, если, например, однотипную операцию требуется применять к векторам раз- личного размера float multiplyAccumulatelfloat a, float b, float c) { return (a * b) + с; // определение скаляра vec4 multiplyAccumulate(vec4 a, vec4 b, vec4 c) 1 return (a * b) + с; // определение 4-вектора Использование рекурсивных функций не допускается. Другими словами, в любом текущем стеке вызовов никакая функция не может присутствовать более одного раза. Некоторые компиляторы могут “отлавливать” такие случаи и выдавать ошибку, но в любом случае работа шейдера становится неопределенной Существует примерно 50 встроенных функций для всевозможных вычислений — от простых арифметических до тригонометрических. Полный их список с описанием можно найти в спецификации GLSL Функции поиска текстуры Встроенные функции поиска текстуры заслуживают специального упоминания Ес- ли другие встроенные функции лишь повышают удобство (относительно просто вы можете закодировать и собственные процедуры), встроенные функции поиска тек- стуры, перечисленные в табл. 21 6, жизненно важны даже для выполнения самых элементарных действия с текстурой.
930 Часть III OpenGL- следующее поколение ТАБЛИЦА 21.6. Встроенные функции поиска текстуры Прототип vec4 texturelD(samplerlD sampler, float coord [, float bias]) vec4 texturelDProj(samplerlD sampler, vec2 coord [, float bias]) vec4 texturelDProj(samplerlD sampler, vec4 coord [, float bias]) vec4 texturelDLod(samplerlD sampler, float coord, float lod) vec4 texturelDProjLod(samplerlD sampler, vec2 coord, float lod) vec4 texturelDProjLod(samplerlD sampler, vec4 coord, float lod) vec4 texture2D(sampler2D sampler, vec2 coord [, float bias]) vec4 texture2DProj(sampler2D sampler, vec3 coord [, float bias]) vec4 texture2DProj(sampler2D sampler, vec4 coord [, float bias]) vec4 texture2DLod(sampler2D sampler, vec2 coord, float lod) vec4 texture2DProjLod(sampler2D sampler, vec3 coord, float lod) vec4 texture2DProjLod(sampler2D sampler, vec4 coord, float lod) vec4 texture3D(sampler3D sampler, vec3 coord [, float bias]) vec4 texture3DProj(sampler3D sampler, vec4 coord [, float bias]) vec4 texture3DLod(sampler3D sampler, vec3 coord, float lod) vec4 texture3DProjLod(sampler3D sampler, vec4 coord, float lod) vec4 textureCube(samplerCube sampler, vec3 coord [, float bias]) vec4 textureCubeLod(samplerCube sampler, vec3 coord, float lod) vec4 shadowlD(samplerlDShadow sampler,vec3 coord (, float bias]) vec4 shadow2D(sampler2DShadow sampler,vec3 coord [, float bias]) vec4 shadowlDProj(samplerlDShadow sampler,vec4 coord, [, float bias]) vec4 shadow2DProj(sampler2DShadow sampler,vec4 coord, [, float bias]) vec4 shadowlDLod(samplerlDShadow sampler, vec3 coord, float lod) vec4 shadow2DLod(sampler2DShadow sampler, vec3 coord, float lod) vec4 shadowlDProjLod(samplerlDShadow sampler,vec4 coord,float lod) vec4 shadow2DProjLod(sampler2DShadow sampler,vec4 coord,float lod) Поиск выполняется для текстуры, тип которой закодирован в имени функции (1D, 2D, 3D, Cube) и которая в настоящее время связана со схемой выборки, представлен- ной параметром sampler. Варианты с Proj перед поиском выполняют проективное деление текстурных координат. Делителем при этом является последний компонент вектора координат. Версии с Lod, применяющиеся только в шейдерах вершин, задают уровень дета- лизации (level-of-detail - LOD) множественной текстуры, по которому дискретизуется искомая текстура. Варианты без Lod при использовании в шейдере вершин произво- дят выборку с основного уровня множественной текстуры В шейдерах фрагментов могут применяться только версии без Lod, причем уровень множественной текстуры, как правило, вычисляется на основе производных по координатам Однако шейдеры фрагментов могут использовать необязательное смещение (bias), которое будет при- бавлено к рассчитанному уровню детализации. В шейдерах вершин параметр bias не применяется. Варианты команд с shadow помимо поиска выполняют сравнение с текстурой глубины (см главу 18, “Текстуры глубины и тени”)
Глава 21 Высокоуровневое затенение: жизненно необходимая “мелочь" 931 Резюме В этой главе изучены все главные элементы языка затенения OpenGL (OpenGL Shader Language — GLSL) Мы обсудили все типы переменных, операторы, а также меха- низмы управления потоком. Кроме того, мы описали, как использовать точки входа для загрузки и компиляции объектов шейдера, а также связывания и использования объектов программы. Много вопросов не освещено, но мы доберемся и до них. Данная глава получилась как бы лекцией о шейдерах. Благодаря этому у вас сформировалась хорошая база для изучения последующих глав, где рассматриваются практические примеры применения шейдеров фрагментов и вершин с использовани- ем как низко-, так и высокоуровневых языков шейдеров Если вы хорошо разобрались в рассмотренных основах, последующие главы будут для вас более увлекательными. Справочная информация glAttachObjectARB Цель: Присоединить объект к другому контейнерному объекту Включаемый файл: <glext.h> Синтаксис: void glAttachObjectARB(GLhandleARB containerObj, GLhandleARB obj); Описание: Присоединяет объект к контейнерному объекту Если первый аргумент не является контейнерным объектом, если второй аргумент уже присоединен к заданному контейнерному объекту или второй объект не принадлежит к типу, который подходит для присоединения к контейнеру, генерируется сообщение об ошибке. Чтобы изменения вступили в силу, после glAttachObjectARB нужно вызвать функцию glLinkProgramARB Параметры: containerObj Контейнерный объект, к которому присоединяется другой (тип GLhandleARB) объект obj (тип GLhandleARB) Что возвращает: Объект, присоединяемый к контейнерному объекту Ничего См. также: glDetachObjectARB, glLinkProgramARB, glGetAttachedObj ectsARB
932 Часть III OpenGL-следующее поколение gIBindAttribLocationARB Цель: Установить положение в памяти для общего атрибута вершин Включаемый файл: <glext.h> Синтаксис: void gIBindAttribLocationARB(GLhandleARB programObj, GLuint index, const GLcharARB *name); Описание: Явно задает номер общего атрибута вершин для использования заданной переменной атрибута Любой атрибут, не связанный явно, будет связан автоматически Указанную процедуру можно вызывать когда угодно, даже перед присоединением шейдера вершин к объекту программы (это позволяет зарезервировать положение общего атрибута) Кроме того, чтобы новое положение вступило в действие, после gIBindAttribLocationARB следует вызвать функции glLinkProgramARB Параметры: programObj (тип GLhandleARB) index (тип GLuint) пате (тип const GLcharARB*) Что возвращает: Объект программы, содержащий атрибуты вершины Номер атрибута вершин, под которым будет записан данный атрибут Имя переменной атрибута Ничего См. также: glGetAttribLocationARB, glGetActiveAttribARB, glLinkProgramARB, glVertexAttrib*ARB, glVertexAttribPointerARB glCompileShaderARB Цель: Скомпилировать шейдер Включаемый файл: <glext.h> Синтаксис: void glCompileShaderARB(GLhandleARB shaderObj); Описание: Пытается скомпилировать текст шейдера, загруженный ранее в объект шейдера Если компиляция прошла успешно и схема готова к связыванию, метка объекта шейдера GL_OBJECT_COMPILE_STATUS_ARB устанавливается равной GL_TRUE, в противном случае она получает значение GL_FALSE Информация о компиляции может добавляться в информационный журнал
Глава 21 Высокоуровневое затенение-жизненно необходимая “мелочь" 933 Параметры: shaderObj (тип GLhandleARB) Что возвращает: Компилируемый объект шейдера Ничего См. также: glGetlnfoLogARB, glShaderSourceARB, glGetObjectParameter*vARB, glLinkProgramARB glCreateProgramObjectARB Цель: Создать объект программы Включаемый файл: <glext.h> Синтаксис: GLhandleARB glCreateProgramObjectARB(GLvoid); Описание: Эта функция создает новый объект программы и возвращает его обработчик Параметры: Отсутствуют Что возвращает: (тип GLhandleARB) Обработчик нового объекта программы См. также: glCreateShaderObjectARB, glDeleteObjectARB, glUseProgramObjectARB, glLinkProgramARB glCreateShaderObjectARB Цель: Создать объект шейдера заданного типа Включаемый файл: <glext,h> Синтаксис: GLhandleARB glCreateShaderObjectARB(GLenum shaderType); Описание: Создает новый объект шейдера заданного типа и возвращает его обработчик Параметры: shaderType (тип GLenum) Тип создаваемого объекта шейдера. Значением может быть одна из следующих констант’ GL_VERTEX_SHADER_ARB: создается объект шейдера вершин GL_FRAGMENT_SHADER_ARB. создается объект шейдера фрагментов Что возвращает: (тип GLhandleARB) Обработчик нового объекта шейдера См. также: glCreateProgramObjectARB, glDeleteObjectARB, glCompileShaderARB
934 Часть III OpenGL -следующее поколение glDeleteObjectARB Цель: Включаемый файл: Синтаксис: Описание: Параметры: obj (тип GLhandleARB) Что возвращает: См. также: Удалить объект <glext.h> void glDeleteObjectARB(GLhandleARB obj ) ; Если заданный объект не присоединен ни к какому контейнерному объекту и не является частью текущего состояния визуализации контекста, объект удаляется немедленно В противном случае он помечается для будущего удаления и удаляется только в том случае, если не будет присоединен к контейнерному объекту и не станет частью текущего состояния визуализации контекста При удалении контейнерного объекты все объекты, присоединенные к нему, открепляются Объект, подлежащий удалению Ничего glCreateProgramObject, glCreateShaderObjectARB, glGetObjectParameter*vARB, glUseProgramObjectARB gIDetachObjectARB Цель: Включаемый файл: Синтаксис: Описание: Параметры: containerObj (тип GLhandleARB) attachedObj (тип GLhandleARB) Что возвращает: См. также: Открепить объект от контейнерного объекта <glext.h> void gIDetachObjectARB(GLhandleARB containerObj, GLhandleARB attachedObj); Открепляет объект от контейнерного объекта Если открепляемый объект помечен для удаления и не прикреплен ни к какому другому контейнерному объекту, он удаляется Чтобы сделанные изменения вступили в силу, после gIDetachObjectARB нужно вызвать функцию glLinkProgramARB Контейнерный объект, от которого открепляется другой объект Открепляемый объект Ничего glAttachObjectARB, glLinkProgramARB, glGetAttachedObjectsARB gl GetActi ve Attri b ARB Цель: Получить информацию об активных атрибутах вершин Включаемый файл: <glext.h>
Глава 21 Высокоуровневое затенение: жизненно необходимая “мелочь” 935 Синтаксис: void glGetActiveAttribARB(GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); Описание: После попытки создать связь с объектом программы с помощью данной функции можно извлекать информацию о переменной атрибута вершины (в том числе ее имени, длине имени, размере и типе) Параметры: programObj Запрашиваемый объект программы (тип GLhandleARB) index (тип GLuint) maxLength (тип GLsizei) length (тип GLsizei*) Запрашиваемый атрибут вершины Максимальное число символов, в которое должно вместиться имя Указатель на положение в памяти, куда будет возвращено значение длины имени. Длина включает только действительно возвращаемые символы и не включает символ конца строки. Если использовать указатель NULL, значение длины возвращаться не будет size (тип GLint*) Указатель на положение в памяти, куда будет возвращен размер атрибута. Этот размер, измеряемый в единицах возвращаемого типа, в настоящее время всегда равен 1, но если массивы атрибутов войдут в OpenGL Shading Language, он станет частью программного интерфейса приложения type (тип GLenum*) Указатель на положение в памяти, куда будет возвращен тип атрибута. Тип может представляться одной из следующих констант GL_FLOAT: скалярное значение с плавающей запятой GL_FLOAT_VEC2_ARB двухкомпонентный вектор значений с плавающей запятой GL_FLOAT_VEC3_ARB: трехкомпонентный вектор значений с плавающей запятой GL_FLOAT_VEC4_ARB. четырехкомпонентный вектор значений с плавающей запятой GL_FLOAT_MAT2_ARB: матрица 2x2 значений с плавающей запятой GL_FLOAT_MAT3_ARB: матрица 3x3 значений с плавающей запятой GL_FLOAT_MAT4_ARB: матрица 4x4 значений с плавающей запятой name (тип GLcharARB*) Что возвращает: Указатель на положение в памяти, куда будет возвращено имя атрибута Ничего См. также: glGetAttribLocationARB, gIBindAttribLocationARB, glGetVertexAttnb*vARB, glGetVertexAttribPointervARB
936 Часть III OpenGL следующее поколение g I GetActi veil n if orm ARB Цель: Получить информацию о переменных с постоянным значением Включаемый файл: <glext. h> Синтаксис: void glGetActiveUniformARB(GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); Описание: После попытки создать связь с объектом программы с помощью данной функции можно извлечь информацию о переменной с постоянным значением (в том числе се имени, длине ее имени, размере и типе) Параметры: programObj (тип GLhandleARB) index (тип GLuint) maxLength (тип GLsizei) length (тип GLsizei*) Запрашиваемый объект программы Запрашиваемая переменная Максимальное число символов, в которое должно вместиться имя Указатель на положение в памяти, куда будет возвращена длина имени Длина включает только действительно возвращаемые символы и не включает символ конца строки Если использовать указатель NULL, длина возвращаться нс будет size (тип GLint*) Указатель на положение в памяти, куда будет возвращен размер массива переменных с постоянными значениями Данный размер выражается в единицах возвращаемого типа и для немассивов всегда равен 1 type (тип GLenum*) Указатель на положение в памяти, куда будет возвращен тип переменной Тип может представляться одной из следующих констант GL_FLOAT скалярное значение с плавающей запятой GL_FLOAT_VEC2_ARB: двухкомпонентный вектор значений с плавающей запятой GL_FLOAT_VEC3_ARB: трехкомпонентный вектор значений с плавающей запятой GL_FLOAT_VEC4_ARB‘ четырехкомпонентный вектор значений с плавающей запятой GL_INT скалярное целочисленное значение GL_INT_VEC2_ARB’ двухкомпонентный вектор целочисленных значений GL_INT__VEC3_ARB трехкомпонентный вектор целочисленных значений GL_INT_VEC4_ARB четырехкомпонентный вектор целочисленных значений GL_BOOL_ARB скалярное булево значение GL_BOOL_VEC2_ARB. двухкомпонентный вектор булевых значений
Глава 21 Высокоуровневое затенение' жизненно необходимая “мелочь' 937 GL_B00L_VEC3_ARB- трехкомпонентный вектор булевых значений GL_B00L_VEC4_ARB четырехкомпонентный вектор булевых значений GL_FL0AT_MAT2_ARB: матрица 2x2 значений с плавающей запятой GL_FL0AT_MAT3_ARB матрица 3x3 значений с плавающей запятой GL_FL0AT_MAT4_ARB матрица 4x4 значений с плавающей запятой GL_SAMPLER_1D_ARB. обработчик, используемый для доступа к одномерной текстуре GL_SAMPLER_2D_ARB обработчик, используемый для доступа к двухмерной текстуре GL_SAMPLER_3D_ARB обработчик, используемый для доступа к трехмерной текстуре GL_SAMPLER_CUBE_ARB. обработчик, используемый для доступа к кубической текстуре GL_SAMPLER_1D_SHADOW_ARB. обработчик, используемый для доступа к одномерной текстуре глубины при активизированном сравнении глубин GL_sampler_2D_SHADOW_ARB обработчик, используемый для доступа к двухмерной текстуре глубины при активизированном сравнении глубин пате (тип GLcharARB*) Что возвращает: Указатель на положение в памяти, куда возвращается имя переменной Ничего См. также: glGetUniformLocationARB, glGetUniform*vARB, glUniform*ARB gIGetAttachedObjectsARB Цель: Получить список объектов, присоединенных к контейнерному объекту Включаемый файл: <glext.h> Синтаксис: void glGetAttachedObj ectsARB(GLhandleARB containerObj, GLsizei maxCount, GLsizei *count, GLhandleARB *obj); Описание: Возвращает список всех объектов, содержащихся внутри другого объекта, т с все объекты шейдера, содержащиеся в объекте программы Параметры: containerObj (тип GLhandleARB) Запрашиваемый контейнерный объект
938 Часть III. OpenGL: спедующее поколение maxCount Максимальное число возвращаемых обработчиков объектов (тип GLsizei) count (тип GLsizei*) obj (тип GLhandleARB*) Что возвращает: См. также: Указатель на ячейку в памяти, в которую возвращается значение счетчика. Счетчик включает только действительно возвращаемые обработчики. Если использовать указатель null, значение счетчика возвращаться не будет Указатель на массив возвращаемых обработчиков объектов Ничего glAttachObj ectARB, glDetachObj ectARB, glDeleteObj ectARB gIGetAttribLocationARB Цель: Включаемый файл: Синтаксис: Описание: Параметры: programObj (тип GLhandleARB) пате (тип const GLcharARB*) Что возвращает: См. также: Получить положение переменной атрибута вершины <glext.h> GLint gIGetAttribLocationARB(GLhandleARB programObj, const GLcharARB *name); После успешного связывания объекта программы с помощью данной функции извлекается положение переменной атрибута вершины с заданным именем Запрашиваемый объект программы Имя переменной атрибута вершины; заканчивающееся символом конца строки (тип GLint) Положение атрибута вершины или -1, если такого активного атрибута нет или имя начинается с зарезервированного префикса “gl_”. Если имя соответствует активному матричному атрибуту, возвращается положение первого столбца матрицы glGetActiveAttribARB, glBindAttribLocationARB, glLinkProgramARB, glVertexAttrib*ARB, gIVertexAttribPointerARB gIGetHandleARB Цель: Включаемый файл: Синтаксис: Описание: Возвращает обработчик объекта <glext.h> GLhandleARB gIGetHandleARB(GLenum pname); Возвращает обработчик объекта, являющегося частью текущего состояния
Глава 21 Высокоуровневое затенение: жизненно необходимая “мелочь" 939 Параметры: pname (тип GLenum) Что возвращает: Возвращаемый обработчик объекта текущего состояния. Параметр должен иметь значение GL_PROGRAM_OBJECT_ARB (тип GLhandleARB) Текущий объект программы или 0, если в настоящее время такие объекты не используются См. также: glCreateProgramObjectARB, glUseProgramObjectARB glGetlnfoLogARB Цель: Получить информацию о последнем скомпилированном или связанном элементе Включаемый файл: <glext.h> Синтаксис: void glGetlnfoLogARB(GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *infoLog); Описание: Каждый объект шейдера имеет запись (info log) с информацией о последней попытке компиляции этого объекта шейдера Точно так же каждый объект программы имеет запись с информацией о последней попытке связывания или верификации Доступ к этим записям осуществляется посредством данной функции Параметры: obj (тип GLhandleARB) maxLength (тип GLsizei) length (тип GLsizei*) Объект, информационная запись которого запрашивается Максимальное число символов возвращаемой информационной записи Указатель на положение в памяти, куда будет занесена информационная запись Длина включает только действительно возвращаемые символы и не включает символ конца строки Если использовать указатель NULL, длина возвращаться не будет infoLog (тип GLcharARB*) Что возвращает: Указатель на положение в памяти, куда будет занесен текст информационной записи Ничего См. также: glCompileShaderARB, glLinkProgramARB, glValidateProgramARB glGetObjectParameter*vARB Цель: Получить информацию о заданном объект Включаемый файл: <glext. h> Синтаксис: void glGetObjectParameterfvARB(GLhandleARB obj, GLenum pname, GLfloat *params); void glGetObjectParameterivARB(GLhandleARB obj, GLenum pname, GLint *params); Описание: Запрашивает свойство заданного объекта программы или шейдера
Параметры: obj (тип Запрашиваемый объект GLhandleARB) pname Запрашиваемое свойство. Значением может быть одна из (тип GLenum) следующих констант’ GL_OBJECT_TYPE_ARB: возвращает GL_PROGRAM_OBJECT_ARB, если запрос касается объекта программы, и GL_SHADER_OBJECT_ARB — если объекта шейдера GL_OBJECT_SUBTYPE_ARB’ если запрос касается объекта шейдера, возвращает либо GL_VERTEX_SHADER_ARB, либо GL_FRAGMENT_SHADER_ARB GL_OBJECT_DELETE_STATUS_ARB’ возвращает 1 ИЛИ 1 0, если установлена метка удаления объекта; в противном случае возвращается 0 или 0.0 GL_OBJECT_COMPILE_STATUS_ARB возвращает 1 или 1 0, если компиляция объекта шейдера была успешной GL_OBJECT_LINK_STATUS_ARB. возвращает I или 1 0, если связывание объекта программы было успешным GL_OBJECT_VALIDATE_STATUS_ARB. возвращает 1 ИЛИ 1 0, если верификация объекта программы была успешной GL_OBJECT_INFO_LOG_LENGTH_ARB возвращает длину информационной записи объекта в символах (включая символ конца строки) или 0, если информационной записи не существует GL_OBJECT_ATTACHED_OBJECTS_ARB возвращает ЧИСЛО объектов шейдера, присоединенных к данному объекту программы GL_OBJECT_ACTIVE_UNIFORMS_ARB: возвращает число переменных с постоянными значениями, активных в данном объекте программы GL_OBJECT_ACTIVE_UNIFORM_MAX_LENGTH_ARB возвращает длину имени самой длинной активной переменной с постоянным значением данного объекта программы GL_OBJECT_SHADER_SOURCE_LENGTH_ARB возвращает длину текста шейдера данного объекта шейдера, включая символ конца строки GL_OBJECT_ACTIVE_ATTRIBUTES_ARB’ возвращает ЧИСЛО атрибутов вершин, активных в данном объекте программы GL_OBJECT_ACTIVE_ATTRIBUTE_MAX_LENGTH_ARB возвращает длину имени самой длинной активной переменной атрибута вершины params (тип Указатель на положение в памяти, где будет храниться результат GLfloat*/GLint*) Что возвращает: Ничего См. также: glShaderSourceARB, glCompileShaderARB, glLinkProgramARB, glAttachObjectARB, gIDetachObjectARB, glCreateShaderObjectARB, glCreateProgramObjectARB, glDeleteObjectARB
Глава 21 Высокоуровневое затенение жизненно необходимая “мелочь’ 941 gIGetShaderSourceARB Цель: Получить исходный текст объекта шейдера Включаемый файл: Синтаксис: Описание: Параметры: obj (тип GLhandleARB) maxLength (тип GLsizei) length (тип GLsizei*) source (тип GLcharARB*) Что возвращает: См. также: <glext.h> void gIGetShaderSourceARB(GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *source}; Возвращает конкатенацию всего исходного текста шейдера, заданного ранее с помощью функции glShaderSourceARB Запрашиваемый объект шейдера Максимальное число символов, которые нужно вернуть Указатель на положение в памяти, куда будет возвращен исходный текст. Длина включает только действительно возвращаемые символы и не включает символ конца строки Если использовать указатель NULL, длина возвращаться не будет Указатель на положение в памяти, куда будет возвращаться исходный текст шейдера Ничего glShaderSourceARB, glCreateShaderObjectARB, glCompileShaderARB glGetllniform*vARB Цель: Включаемый файл: Синтаксис: Описание: Параметры: programObj (тип GLhandleARB) location (тип GLint) Получить значение (значения) переменной с постоянным значением <glext.h> void glGetUniformfvARB(GLhandleARB programObj, GLint location, GLfloat *params); void glGetUniformivARB(GLhandleARB programObj, GLint location, GLint *params); Если объект программы был успешно связан, и было задано правильное положение переменной с постоянным значением (как при вызове функции glGetUniformLocationARB), указанная функция возвращает значение или значения переменной, записанной в этом положении Число возвращаемых значений определяется типом переменной Каждый элемент переменной массива нужно запрашивать независимо. Если переменная представляет матрицу, данные возвращаются по столбцам Запрашиваемый объект программы Положение запрашиваемой переменной
942 Часть III OpenGL'следующее поколение params (тип GLfloat*/GLint*) Что возвращает: Указатель на положение в памяти, где будут храниться значения переменной Ничего См. также: glUniform*ARB, gIGetUniformLocationARB, glGetActiveUniformARB gIGetUniformLocationARB Цель: Получить положение переменной с постоянным значением Включаемый файл: <glext.h> Синтаксис: GLint gIGetUniformLocationARB(GLhandleARB programObj, const GLcharARB *name); Описание: После успешного связывания объекта программы с помощью данной функции можно извлечь положение переменной с заданным именем Параметры: programObj (тип GLhandleARB) name (тип const GLcharARB*) Запрашиваемый объект программы Имя переменной Имя не может быть структурой, массивом структур или подкомпонентом вектора или матрицы. Для идентификации элементов структуры или массива можно использовать операторы . и [ ]. Первый элемент массива можно запрашивать, либо используя просто имя массива, либо добавляя к этому имени [ 0 ] Что возвращает: (тип GLint) Положение переменной или -1, если такой активной переменной не существует, или имя начинается с зарезервированного префикса “gl_” См. также: glGetActiveUniformARB, glLinkProgramARB, glUniform*ARB, glGetUniform*vARB glLinkProgramARB Цель: Связать объект программы Включаемый файл: <glext.h> Синтаксис: void glLinkProgramARB(GLhandleARB programObj); Описание: Пытается связать ранее скомпилированные объекты шейдера, содержащиеся в заданном объекте программы Если связывание прошло успешно и объект готов к использованию, метка объекта программы GL_OBJECT_LINK_STATUS_ARB получает значение GL_TRUE; в противном случае ей присваивается значение GL_FALSE. Данные о связи заносятся в информационную запись объекта программы
944 Часть III OpenGL: следующее поколение Синтаксис: void glUniformlfARB(GLint location, GLfloat vO) ; void glUniform2fARB(GLint location, GLfloat vO, GLfloat vl) ; void glUniform3fARB(GLint location, GLfloat vO, GLfloat vl, GLfloat v2); void glUniform4fARB(GLint location, GLfloat vO, GLfloat vl, GLfloat v2, GLfloat v3); void glUniformliARB(GLint location, GLint vO); void glUniform2iARB(GLint location, GLint vO, GLint vl) ; void glUniform3iARB(GLint location, GLint vO, GLint vl, GLint v2); void glUniform4iARB(GLint location, GLint vO, GLint vl, GLint v2, GLint v3); void glUniformlfvARB(GLint location, GLsizei count, const GLfloat *value); void glUniform2fvARB(GLint location, GLsizei count, const GLfloat *value); void glUniform3fvARB(GLint location, GLsizei count, const GLfloat *value); void glUniform4fvARB(GLint location, GLsizei count, const GLfloat *value); void glUniformlivARB(GLint location, GLsizei count, const GLint *value); void glUniform2ivARB(GLint location, GLsizei count, const GLint *value}; void glUniform3ivARB(GLint location, GLsizei count, const GLint *value); void glUniform4ivARB(GLint location, GLsizei count, const GLint *value); void glUniformMatrix2fvARB(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value) ; void glUniformMatrix3fvARB(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); void glUniformMatrix4fvARB(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); Описание: Загружает одно или несколько значений в заданную переменную используемого в настоящее время объекта программы Размер и тип функции должен соответствовать размеру и типу переменной, исключением являются только переменные булева типа С данными переменными могут использоваться функции, принимающие значения с плавающей запятой или целочисленные, и значения 0.0 или О преобразовываются в GL_FALSE, а все остальные значения — в GL_TRUE Если ни один объект программы не используется, данная функция генерирует сообщение об ошибке
Глава 21 Высокоуровневое затенение: жизненно необходимая “мелочь” 945 Параметры: location (тип GLint) count (тип GLsizei) transpose (тип GLboolean) vO, vl, v2, v3 (тип GLfloat/GLin value (тип GLfloat*/GLint*) Что возвращает: См. также: Положение загружаемой переменной, возвращаемое glGetUniformLocationARB. Если переменная представляет массив, то возвращается начальное положение, с которого начинается загрузка элементов массива Число загружаемых элементов массива или 1, если это не массив Указывает, нужно ли транспонировать заданные элементы матрицы перед их записью в состоянии OpenGL. Если значение равно GL_TRUE, считается, что матрица развернута по строкам, и перед записью они транспонируются в порядок по столбцам. Если значение равно GL_FALSE, считается, что матрица развернута по столбцам (GLSL работает с матрицами, которые развертываются по столбцам) Значения, загружаемые в переменную t) Указатель на значения, загружаемые в переменную Ничего glGetUniform*vARB, glGetUniformLocationARB, glGetActiveUniformARB, glLinkProgramARB glUseProgramObjectARB Цель: Включаемый файл: Синтаксис: Описание: Параметры: programObj (тип GLhandleARB) Что возвращает: См. также: Установить текущий объект программы <glext.h> void glUseProgramObjectARB(GLhandleARB programObj); После успешного связывания объекта программы с помощью данной функции как часть текущего состояния визуализации устанавливается его исполняемый код. Кроме того, функцию можно использовать для удаления всего исполняемого кода и возвращения к полноценному конвейеру с фиксированными функциональными возможностями. После установки исполняемый код можно изменить только путем повторного связывания (на этом этапе вызов данной функции необязателен) Используемый объект программы или 0, если требуется вернуться к фиксированным функциональным возможностям Ничего glLinkProgramARB, glGetHandleARB, glUniform*ARB
946 Часть III OpenGL следующее поколение gIValidateProgramARB Цель: Проверить возможность существования объекта программы с текущим состоянием Включаемый файл: <glext.h> Синтаксис: void gIValidateProgramARB(GLhandleARB programObj); Описание: Без учета текущего состояния невозможно узнать, будет ли выполняться объект программы при первом его использовании для визуализации (даже если этот объект успешно связан) Функция проверяет допустимость заданного объекта программы в контексте текущего состояния OpenGL, обнаруживая проблемы или случаи неэффективного использования Если верификация прошла успешно и объект программы гарантированно будет совместим с текущим состоянием, метка объекта программы gl_object_validate_status_arb получает значение GL_TRUE; в противном случае она устанавливается равной GL_FALSE Данные о верификации заносятся в информационную запись объекта программы Параметры: programObj Верифицируемый объект программы (тип GLhandleARB) Что возвращает: Ничего См. также: glGetlnfoLogARB, glGetOb;jectParameter*vARB, glLinkProgramARB, glUseProgramObjectARB
ГЛАВА 22 Затенение вершин: настраиваемое преобразование, освещение и генерация текстуры Бенджамин Липчак ИЗ ЭТОЙ ГЛАВЫ ВЫ УЗНАЕТЕ . . . • Как осветить вершины • Как сгенерировать текстурные координаты • Как рассчитать для вершины туман • Как рассчитать для вершины размер точки • Как растянуть и сжать объект • Как получить реалистичное изображение кожи человека с помощью смешения вершин Данная глава посвящена приложению шейдеров вершин. В двух предыдущих гла- вах мы рассмотрели основные механизмы высоко- и низкоуровневых шейдеров вер- шин, однако рано или поздно нужно отложить учебник в сторону и начать обучение на практике В данной главе представлено несколько шейдеров, выполняющих реальные задачи Данные схемы вполне можно использовать как отправную точку собственных экспериментов Пробуем воду Любой шейдер по крайней мере должен выдавать на выход координаты положения в усеченном пространстве Другая операция, обычно выполняемая в схемах затенения вершин, — расчет освещения и генерация текстурных координат — может быть необя- зательной. Например, если вы создаете текстуру глубины и вас интересуют только конечные глубины, нет смысла тратить команды шейдера на вывод цвета или текстур- ных координат Однако в любом случае нужно выдать положение в усеченном про- странстве, используемое на последующих этапах сборки примитивов и растеризации. В качестве первого примера шейдера приведем преобразование вершины, которое в конвейере с фиксированными функциональными способностями произошло бы ав-
948 Часть III. OpenGL: следующее поколение Рис. 22.1. Шейдер вершин преобразует положение в усеченное пространство и копирует цвет вершин со входа на выход тематически. Для усложнения задачи скопируем входной цвет в выходной. Помните, все, что не подано на выход, остается неопределенным. Если вы хотите, чтобы цвет был доступен позже на других каскадах конвейера, его следует передать со входа на выход, даже если шейдер вершин никак его не меняет. Для каждого примера шейдера мы будем предоставлять высокоуровневую и низ- коуровневую версии, выполняющие эквивалентные операции. Таким образом вы смо- жете изучить оба языка шейдеров, сравнивая их. Кроме того, если в вашей реализации OpenGL доступно лишь одно из двух расширений, вам не грозит полная дезориен- тация. На рис. 22.1 показан результат действия простых шейдеров, представленных в листингах 22.1 и 22.2. Листинг 22.1. Простой высокоуровневый шейдер вершин // simple.vs // // Типичное преобразование вершины // плюс копируем первичный цвет void main(void) // Умножаем положение в пространстве объекта на матрицу MVP gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; // Копируем первичный цвет gl_FrontColor = gl_Color;
Глава 22 Затенение вершин, настраиваемое преобразование, 949 Листинг 22.2. Простой низкоуровневый шейдер !IARBvpl.0 # simple.vp # # Типичное преобразование вершины # плюс копируем первичный цвет ATTRIB iPos = vertex.position; # входное положение ATTRIB i₽rC = vertex.color.primary; # входной первичный цвет OUTPUT oPos = result.position; # выходное положение OUTPUT oPrC = result.color.primary; # выходной первичный цвет PARAM mvp[4] = {state.matrix.mvp}; # произведение матрицы TEMP clip; # # # наблюдения модели на матрицу проекции (MVP) временный регистр DP4 clip.x, iPos, mvp[0]; # умножаем входное положение DP4 clip.y, iPos, mvp[l]; DP4 clip.z, iPos, mvp[2J; DP4 clip.w, iPos, mvp[3]; MOV oPos, clip; # # на матрицу MVP выходные координаты MOV oPrC, iPrC; # # в усеченном пространстве копируем первичный цвет END # со входа на выход Обратите внимание на то, насколько компактной и читабельной является высоко- уровневый шейдер GLSL по сравнению с низкоуровневой схемой GL_ARB_vertex_ program_shader При объяснении шейдеров мы будем обращаться именно к вер- сии GLSL Сравнивая два приведенных варианта, вы можете изучить, как высоко- уровневые выражения разбиваются на низкоуровневые составляющие Например, умножение положения в пространстве объекта (gl_Vertex) на конкатенацию мат- риц наблюдения модели и проекции (gl_ModelViewProjection) с целью получения положения вершины в усеченном пространстве (gl_Position) реализовано в низко- уровневом шейдере в виде ряда из четырех команд скалярного произведения (DP4). Диффузное освещение При расчете диффузного освещения учитывается ориентация поверхности отно- сительно направления падающего света. Уравнение диффузного отражения выгля- дит так Cdifr — max{N • L, 0} * Cmat * Си Здесь N — единичная нормаль вершины, L — единичный вектор, представляю- щий направление от вершины к источнику света. Cmat — Цвет материала поверхности, a Ch — цвет света Ссщг — получающийся в результате диффузный цвет. Поскольку в примере используется белый свет, данный член можно опустить, так как его дей- ствие заключается в умножении на вектор {1,1,1,!} На рис 22 2 показан результат
950 Часть III. OpenGL: следующее поколение Рис. 22.2. Шейдер вершин рассчитывает диффузное освещение действия кода, представленного в листингах 22.3 и 22.4 и являющегося реализацией уравнения диффузного освещения. Листинг 22.3. Высокоуровневый шейдер вершин с реализацией диффузного освещения // diffuse.vs // // Типичное преобразование вершины // плюс диффузное освещение белым светом uniform vec3 lightPosO; void main(void) // обычное преобразование MVP gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; vec3 N = normalize(gl_NormalMatrix * gl_Normal); vec4 V = gl_ModelViewMatrix * gl_Vertex; vec3 L = normalize(lightPosO - V.xyz); // подаем на выход диффузный цвет float NdotL = dot(N, L); gl_FrontColor = gl_Color * vec4(max(0.0, NdotL));
Глава 22 Затенение вершин, настраиваемое преобразование, 951 Листинг 22.4. Низкоуровневый шейдер вершин с реализацией диффузного освещения !JARBvpl.O # diffuse.vp # # Типичное преобразование вершины # плюс расчет диффузного освещения, # порожденного белым светом ATTRIB iPos = vertex.position; # входное положение ATTRIB iPrC = vertex.color.primary; # входной первичный цвет ATTRIB iNrm = vertex.normal; # входная нормаль OUTPUT oPos = result.position; # выходное положение OUTPUT oPrC = result.color.primary; # выходной первичный цвет PARAM mvp[4] = {state.matrix.mvp}; # преобразование MVP # (умножение матрицы наблюдения модели на матрицу проекции) PARAM mv[4] = {state.matrix.modelview}; # матрица # наблюдения модели # обратная к транспонированной матрице наблюдения модели: PARAM mvIT[4] = {state.matrix.modelview.invtrans}; PARAM lightPos = program.local[0]; # положение источника света в пространстве наблюдения TEMP N, V, L, NdotL; # временные регистры DP4 oPos.x, iPos, mvp[0]; # преобразуем входное положение матрицей MVP DP4 oPos.y, iPos, mvp[l]; DP4 oPos.z, iPos, mvp[2]; DP4 oPos.w, iPos, mvp[3]; DP4 V.x, iPos, mv[0]; # преобразуем входное положение матрицей MV DP4 V.y, iPos, mv[l]; DP4 V.z, iPos, mv[2); DP4 V.w, iPos, mv[3); SUB L, lightPos, V; # вектор от вершины к источнику света DP3 N.x, iNrm, mvIT[0]; # преобразуем норму в пространство наблюдения DP3 N.y, iNrm, mvITfl]; DP3 N.z, iNrm, mvIT[2]; DP3 N.w, N, N; # нормируем нормаль RSQ N.w, N.w; MUL N, N, , N.w; DP3 L.w, L, L; # нормируем вектор # источника света RSQ L.w, L.w; MUL L, L, L.w; DP3 NdotL, N, L; # N . L MAX NdotL, NdotL, 0.0; MUL oPrC, iPrC, NdotL; # диффузный цвет END
952 Часть III OpenGL- следующее поколение После расчета положения в усеченном пространстве (как вы делали в “простом” шейдере) “диффузный” шейдер также переводит положение вершины в простран- ство, привязанное к глазу. Все расчеты освещения выполняются в пространстве на- блюдения, поэтому вектор нормали нужно преобразовать из пространства объектов в пространство наблюдения. Специально для этой цели GLSL предлагает встроен- ную команду gl_NormalMatrix Данная команда задает инверсию транспонирования матрицы, образованной 3x3 левыми верхними элементами матрицы наблюдения мо- дели. Кроме того потребуется еще вектор источника света, указывающий направление от положения вершины к источнику света (чтобы его найти, нужно просто отнять от координат одной точки координаты другой). И нормаль, и векторы источников света должны быть единичными, поэтому их нужно нормировать. Для этого в GLSL предусмотрены встроенные функции, но в низкоуровневом шейдере векторы нужно нормировать вручную Превратить вектор в единичный просто Нужно масштабировать компоненты вектора с коэффициентом, обратным к длине вектора. Применение к вектору и его копии операции скалярного произведения (DP3) дает квадрат длины этого вектора. Операция обратного квадрат- ного корня (RSQ) превращает этот квадрат длины в требуемый масштабный коэффи- циент. После этого вы просто умножаете (MUL) на компоненты исходного вектора, получая в результате единичный вектор Скалярное произведение двух единичных векторов N и L будет принадлежать диапазону [-1,1] Однако поскольку нас интересует, сколько диффузного освещения отражается от поверхности, отрицательный вклад не имеет смысла. Поэтому резуль- тат скалярного произведения ограничивается согласно допустимому диапазону [0,1] с помощью операций max (GLSL) или МАХ (низкоуровневые схемы). Затем код цвета, определяемого вкладом диффузного освещения, можно умножить на код диффузного цвета материала вершины, получив окончательный цвет освещенного участка Отраженный свет При расчете отраженного света также учитывается ориентация поверхности относи- тельно направления падающего света Расчет осуществляется согласно следующему уравнению Сьрес = mai{N Н, О}5-’ * C,nat * Cj, Здесь Н — единичный вектор, представляющий направление между вектором источ- ника света и вектором наблюдения, или вектор биссектрисы, Scxp — коэффициент отражения света (specular exponent), управляющий плотностью зеркального блика CS|,ec — получающийся цвет отраженного света N, С1П,и и С,, — те же, что и для диффузного освещения Поскольку используется белый свет, данный член можно опустить На рис 22 3 иллюстрируется результат выполнения кодов, приведенных в листингах 22.5 и 22 6, где реализованы уравнения диффузного и отраженного осве- щения.
Глава 22 Затенение вершин настраиваемое преобразование, 953 Листинг 22.5. Высокоуровневый шейдер вершин с реализацией диффузного и отраженного освещения // specular.vs И // Типичное преобразование вершины // плюс реализация диффузного и отраженного света, // порожденного одним источником белого света uniform vec3 lightPosO; void main(void) { // типичное преобразование MVP gl_Position = gl_ModelViewPro]ectionMatrix * gl_Vertex; vec3 N = normalize(gl_NormalMatrix * gl_Normal); vec4 V = gl_ModelViewMatrix * gl_Vertex; vec3 L = normalize(lightPosO - V.xyz); vec3 H = normalized, + vec3(0.0, 0.0, 1.0)); const float specularExp = 128.0; // расчет диффузного освещения float NdotL = dot(N, L); vec4 diffuse = gl_Color * vec4(max(0.0, NdotL)); 11 расчет отраженного света float NdotH = dot(N, H); vec4 specular = vec4(pow(max(0.0, NdotH), specularExp)); I/ суммируем диффузные и отраженные компоненты gl_FrontColor = diffuse + specular; Листинг 22.6. Низкоуровневый шейдер вершин с реализацией диффузного и отраженного света !lARBvpl.O # specular.vp # # Типичное преобразование вершины # плюс реализация диффузного и отраженного света, # порожденного одним источником белого света ATTRIB iPos = vertex.position; # входное положение ATTRIB iPrC = vertex.color.primary; # входной первичный цвет ATTRIB iNrm = vertex.normal; # входная нормаль OUTPUT oPos = result.position; # выходное положение OUTPUT oPrC = result.color.primary; # выходной первичный цвет PARAM mvp[4] = {state.matrix.mvp); # матрица наблюдения модели на матрицу проекции PARAM mv[4] = {state.matrix.modelview}; # матрица наблюдения модели # обратная к транспонированной матрице наблюдения модели: PARAM mvIT[4] = {state.matrix.modelview.invtrans); PARAM lightPos = program.local[0];
954 Часть III. OpenGL: следующее поколение Рис. 22.3. Шейдер вершин рассчитывает диффузное и отраженное освещение # положение источника света в пространстве наблюдения TEMP N, V, L, Н, NdotL, NdotH; # временные регистры TEMP diffuse, specular; DP4 oPos.x, iPos, mvp[0]; # преобразуем входное положение матрицей MVP DP4 oPos.y, iPos, mvp[l]; DP4 oPos.z, iPos, mvp[2]; DP4 oPos.w, iPos, mvp[3]; DP4 V.x, iPos, mv[0]; # преобразуем входное положение матрицей MV DP4 V.y, iPos, mv[l]; DP4 V.z, iPos, mv[2]; DP4 V.w, iPos, mv[3]; SUB L, lightPos, V; # вектор источника света DP3 N.x, iNrm, mvIT[0]; # переводит норму в пространство наблюдения DP3 N.y, iNrm, mvIT[l]; DP3 N.z, iNrm, mvIT[2]; DP3 N.w, N, N; # нормируем нормаль RSQ N.w, N.w; MUL N, N, N.w; DP3 L.w, L, L; # нормируем вектор # источника света RSQ L.w, L.w; MUL L, L, L.w; ADD H.xyz, L, {0, 0, 1); DP3 H.w, H, H; # нормируем вектор
I лава 22 Затенение вершин' настраиваемое преобразование, 955 # биссектрисы RSQ H.w, H.w; MUL Н, Н, H.w; DP3 NdotL, N, L; MAX NdotL, NdotL, 0.0; MUL diffuse, iPrC, NdotL; # N . L DP3 NdotH, N, H; MAX NdotH, NdotH, 0.0; # N . H POW specular, NdotH.x, 128.0.x; # коэффициент отражения # равен 128 ADD oPrC, diffuse, specular; END # суммируем цвета Положение источника света представляется постоянным вектором, передаваемым шейдеру приложением. Это позволяет интерактивно менять положение источника света, не затрагивая саму схему затенения. В примере VertexShaders для перемещения источника света применяются клавиши со стрелками Мы использовали жестко закодированное значение “коэффициента отражения” — 128, дающее аккуратный плотный зеркальный блик Поэкспериментировав с различ- ными значениями, можно подобрать другой внешний вид Обратите внимание на представление данного параметра в низкоуровневой схеме — 128.0.x. Мы выбрали такой формат, поскольку ожидается скалярное значение, а низкоуровневая грамма- тика “недостаточно развита”, чтобы легко указывать скалярную константу, поэтому в любом случае нужно добавлять избыточный суффикс. Улучшенное отражение Зеркальные блики быстро меняются на поверхности объекта Пытаясь вычислить их параметры для вершин, а затем интерполируя результат внутри треугольника, мы получим плохое изображение Вместо аккуратного круглого “зайчика” получится грязный блик многоугольной формы Один способ улучшить ситуацию — отделить диффузное освещение от отраженно- го, передать одно из них как первичный цвет вершины, а другое — как вторичный При суммировании диффузного и отраженного цвета цвет насыщается (т е превышает зна- чение 1.0) везде, где присутствует зеркальный блик Если попытаться интерполиро- вать сумму этих цветов, эффект насыщения распространится по всему треугольнику Однако, если интерполировать цвета по-отдсльности, а затем просуммировать их для фрагмента, насыщение проявится только там, где это требуется, частично устранив эффект грязной окраски Чтобы найти сумму для фрагмента, достаточно просто акти- визировать GL_COLOR_SUM. Ниже приводится видоизмененный код GLSL, в котором разделяются цвета, порожденные вследствие использования разных механизмов // первичный цвет сформирован диффузным освещением float NdotL = dot(N, L); gl_FrontColor = gl_Color * vec4(max(0.0, NdotL)); // вторичный цвет сформирован отраженным светом float NdotH = dot(N, H); gl_FrontSecondaryColor = vec4(pow(max(0.0, NdotH), specularExp)); Код низкоуровневого шейдера модифицируется следующим образом
956 Часть III OpenGL: следующее поколение OUTPUT oPrC = result.color.primary; # выходной первичный цвет OUTPUT oScC = result.color.secondary; # выходной вторичный цвет DP3 NdotL, N, L; # N . L MAX NdotL, NdotL, 0.0; MUL oPrC, iPrC, NdotL; # первичный цвет сформирован # диффузным освещением DP3 NdotH, N, H; # N . Н MAX NdotH, NdotH, 0.0; POW oScC, NdotH.x, 128.0.x; # вторичный цвет сформирован # отраженным светом Разделение цветов немного улучшает положение вещей, но корнем проблемы явля- ется коэффициент отражения. Возводя его в степень, вы получаете значение, которое меняется быстрее, чем может позволить схема интерполяции вершин. Если геометрия представлена недостаточно мелкой мозаикой, вы вообще можете потерять зеркальный блик Чтобы этого избежать, на выход подается только коэффициент отражения (Я Я), который возводится в степень при обработке фрагмента Таким образом, вы безопасно интерполируете более медленно меняющуюся величину (N Н) Поскольку пока мы не рассматривали шейдеры фрагментов, вы не знаете, как выполнить это возведение в степень для фрагмента. Все, что нужно сейчас, — задать одномерную текстуру с по- мощью таблицы из s128 значений и передать (N-H) из шейдера вершин в текстурную координату (Подобные действия считаются пользовательской генераций текстуры ) После этого с помощью текстурной среды с фиксированными функциональными возможностями к интерполированному диффузному цвету (полученному в шейдере вершин) добавляется отраженный цвет (полученный через поиск текстуры) Реализация описанного принципа в коде GLSL приводится ниже. // первичный цвет сформирован диффузным освещением float NdotL = dot(N, L); gl_FrontColor = gl_Color * vec4(max(0.0, NdotL)); // копируем (N.H)*8-7 в текстурную координату float NdotH = max(0.0, (dot(N, H) * 8.0) - 7.0); gl_TexCoord[0] = vec4(NdotH, 0.0, 0.0, 1.0); Соответствующий модифицированный низкоуровневый код выглядит так: OUTPUT oPrC = result.color.primary; OUTPUT оТхС = result.texcoord[0]; DP3 NdotL, N, L; MAX NdotL, NdotL, 0.0; MUL oPrC, iPrC, NdotL; DP3 NdotH, N, H; MAD NdotH.x, NdotH, 8.0, {-7.0}; выходной первичный цвет выходная текстурная координата 0 # N . L # выходной диффузный цвет # N . H # (N . H) * 8 - 7
Глава 22. Затенение вершин: настраиваемое преобразование,... 957 Затенение "specular” Затенение "sepspec” Затенение "texpec” Рис. 22.4. Вид зерквльного блика улучшается при использовании отдельного отраженного цвета или текстуры с учетом коэффициента отражения MOV оТхС, {0.0, 0.0, 0.0, 1.0}; # инициализация других # компонентов MAX оТхС.х, NdotH, 0.0; # данные помещаем в текстурную # координату 0 Здесь произведение (N • Н) было ограничено согласно допустимому диапазону [0,1]. Однако если вы попытаетесь возвести любую величину из этого диапазона в 128-ю степень, то получите результат, настолько близкий к нулю, что значение текселя станет нулевым. В измеримые значения текселей будут отображены только верхняя 1/8 часть значений (7V • Н). Чтобы экономно использовать одномерную тек- стуру, можно ограничиться верхней 1/8 частью указанного диапазона и заполнить значениями этого диапазона всю текстуру, что повысит конечную точность представ- ления. Для этого нужно масштабировать (N • Н) в 8 раз и сместить на —7, отобразив диапазон [0,1] в [—7,1]. При использования граничных условий GL_CLAMP_TO_EDGE значения диапазона [—7,0] отобразятся в 0. Значения интересующего нас диапазона [0,1] дадут значения текселей между (7/8)128 и 1. С помощью функции текстурной среды GL_ADD отраженный вклад, полученный путем поиска текстуры, добавляется к диффузному цвету, выданному схемой затене- ния вершин. На рис. 22.4 для сравнения качества показаны три шейдера с учетом отраженно- го света. Существуют еще более точный метод: на выход шейдера вершин подается только вектор нормали, после чего кодируется кубическая текстура, и для любой координаты N значение соответствующего текселя будет равно (N • И)128. Соответ- ствующий код предлагается написать вам в качестве самостоятельного упражнения. Теперь, когда у вас есть подходящий зеркальный блик, можете немного усложнить задачу, заменив один источник белого света тремя цветными. В результате выполня- ются те же вычисления, только теперь имеется три различных положения источника света, а также следует учитывать цвет света. Как и для всех рассмотренных выше шейдеров, в данном примере с помощью клавиш со стрелками влево и вправо можно менять положения источников света. На рис. 22.5 показана сцена, реализованная с помощью кода, приведенного в листин- гах 22.7 и 22.8.
958 Часть III. OpenGL: следующее поколение Рис. 22.5. Три источника света лучше одного, хотя передать это в черно-белых тонах сложновато Листинг 22.7. Высокоуровневый шейдер с реализацией трех цветных источников света // Slights.vs // // Типичное преобразование вершины // 3 цветных источника света uniform vec3 lightPosO; uniform vec3 lightPosl; uniform vec3 lightPos2; varying vec4 gl_TexCoord[4]; void main(void) // Типичное MVP-преобразование gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; vec3 N = normalize(gl_NormalMatrix * gl_Normal); vec4 V = gl_ModelViewMatrix * gl_Vertex; //Цвета источников света vec4 lightCol[3]; lightCol[0] = vec4(1.0, 0.25, 0.25, 1.0); lightCol[l] = vec4(0.25, 1.0, 0.25, 1.0); lightCol[2] = vec4(0.25, 0.25, 1.0, 1.0); // Векторы источников света vec3 L[3], H[3]; L[0] = normalize(lightPosO - V.xyz);
Глава 22 Затенение вершин настраиваемое преобразование. 959 L[l] = normalize(lightPosl - V.xyz); L[2] = normalize(lightPos2 - V.xyz); gl_FrontColor = vec4(0.0); for (int 1 = 0; i < 3; i++) { 11 Вектор половинного угла H[i] = normalize(L[i] + vec3(0.0, 0.0, 1.0)); II Накапливаются диффузные вклады в цвет от разных // источников света gl_FrontColor += gl_Color * lightCol[i] * vec4(max(0.0, dot(N, L[i]))); // Помещаем коэффициенты отражения N.H в текстурную II координату gl_TexCoord[1+1] = vec4(max(0.0, dot(N, H[i]) * 8.0 - 7.0), 0.0, 0.0, 1.0); Листинг 22.8. Низкоуровневый шейдер с реализацией трех цветных источников света !JARBvpl.O # Slights.vp # # Типичное преобразование вершины # 3 цветных источника света ATTRIB iPos = vertex.position; # входное положение ATTRIB iPrC = vertex.color.primary; # входной первичный цвет ATTRIB iNrm = vertex.normal; # входная нормаль OUTPUT oPos = result.position; # выходное положение OUTPUT oPrC = result.color.primary; # выходной первичный цвет OUTPUT оТСО = result.texcoord[1] ; # выходная текстурная координата 1 OUTPUT oTCl = result.texcoord[2]; # выходная текстурная координата 2 OUTPUT oTC2 = result.texcoordf3]; # выходная текстурная координата 3 PARAM mvp[4] = { state.matrix.mvp }; # матрица наблюдения модели на матрицу проекции PARAM mv[4] = { state.matrix.modelview }; # матрица наблюдения модели # обратная к транспонированной матрице наблюдения модели: PARAM mvIT[4] = {state.matrix.modelview.invtrans); PARAM lightColO = {1.0, 0.25, 0.25, 1.0); # цвет источника света 0 PARAM lightColl = (0.25, 1.0, 0.25, 1.0); # цвет источника света 1 PARAM lightCol2 = {0.25, 0.25, 1.0, 1.0); # цвет источника света 2 PARAM lightPosO = program.local[0] ; # положение источника света 0 в пространстве наблюдения PARAM lightPosl = program.local[1] ; # положение источника света 1 в пространстве наблюдения
960 Часть III OpenGL: следующее поколение PARAM lightPos2 = program.local[2]; # положение источника света 2 в пространстве наблюдения TEMP N, V, L, Н, NdotL, NdotH, finalcolor; # временные регистры ALIAS diffuse = NdotL; ALIAS specular = NdotH; DP4 oPos.x, iPos, mvp[0]; # преобразуем входное положение матрицей MVP DP4 oPos.y, iPos, mvp[l]; DP4 oPos.z, iPos, mvp[2]; DP4 oPos.w, iPos, mvp[3]; DP4 V.x, iPos, mv[0]; # преобразуем входное положение матрицей MV DP4 V.y, iPos, mv[l]; DP4 V.z, iPos, mv[2]; DP4 V.w, iPos, mv[3]; DP3 N.x, iNrm, mvIT[0]; # преобразуем норму в пространство наблюдения DP3 N.y, iNrm, mvIT[l]; DP3 N.z, iNrm, mvIT[2]; DP3 N.w, N, N; # нормируем нормаль RSQ N.w, N.w; MUL N, N, N.w; # LIGHT 0 SUB L, lightPosO, V; # вектор источника света DP3 L.w, L, L; # нормируем вектор источника света RSQ L.w, L.w; MUL L, L, L.w; ADD H.xyz, L, {0, 0, 1}; DP3 H.w, H, H; # нормируем вектор биссектрисы RSQ H.w, H.w; MUL H, H, H.w; DP3 NdotL, N, L; # N . L0 MAX NdotL, NdotL, 0.0; MUL diffuse, iPrC, NdotL; # priCol * N.L0 # priCol * lightColO * N.L0 MUL finalColor, diffuse, lightColO; DP3 NdotH, N, H; # N . HO MAX NdotH, NdotH, 0.0; MOV oTCO, {0.0, 0.0, 0.0, 1.0}; MAD oTCO.x, NdotH, 8, {-!}; # помещаем NdotH * 8 - 7 в текстурную координату 0 # Источник света 1 SUB L, lightPosl, V; # вектор источника света DP3 L.w, L, L; # нормируем вектор источника света RSQ L.w, L.w; MUL L, L, L.w; ADD H.xyz, L, (0, 0, 1); DP3 H.w, H, H;
Глава 22 Затенение вершин' настраиваемое преобразование 961 # нормируем вектор биссектрисы RSQ H.w, H.w; MUL Н, Н, H.w; DP3 NdotL, N, L; # N . Ll MAX NdotL, NdotL, 0.0; MUL diffuse, iPrC, NdotL; # priCol * N.L1 # priCol * lightColO * N.L1 MAD finalcolor, diffuse, lightColl, finalcolor; DP3 NdotH, N, H; # N . Hl MAX NdotH, NdotH, 0.0; MOV oTCl, (0.0, 0.0, 0.0, 1.0}; MAD oTCl.x, NdotH, 8, {-7}; # помещаем NdotH * 8 - 7 в текстурную координату 1 # Источник света 2 SUB L, lightPos2, V; # вектор источника света DP3 L.w, L, L; # нормируем вектор источника света RSQ L.w, L.w; MUL L, L, L.w; ADD H.xyz, L, {0, 0, 1); DP3 H.w, H, H; # нормируем вектор биссектрисы RSQ H.w, H.w; MUL H, H, H.w; DP3 NdotL, N, L; # N . L2 MAX NdotL, NdotL, 0.0; MUL diffuse, IPrC, NdotL; # priCol * N.L2 # priCol * lightColO * N.L2 MAD oPrC, diffuse, lightCol2, finalcolor; DP3 NdotH, N, H; # N . H2 MAX NdotH, NdotH, 0.0; MOV oTC2, {0.0, 0.0, 0.0, 1.0}; MAD oTC2.x, NdotH, 8, {-7}; # помещаем NdotH * 8 - 7 в текстурную координату 2 END В данном примере интересно отметить использование цикла в версии GLSL В низкоуровневых схемах затенения циклы отсутствуют, поэтому мы “развернули” их в линейную последовательность, соответственно, код, который должен быть в цикле, повторился трижды (по разу на каждый источник света) Отмстим, что, хотя GLSL это допускает, некоторые реализации OpenGL могут не поддерживать циклы на ап- паратном уровне. Поэтому чересчур медленный запуск шейдера можо объясняться выполнением циклов на программном уровне Развертывание цикла в шейдере GLSL позволяет устранить проблему за счет снижения читабельности кода
962 Часть III. OpenGL: следующее поколение Туман Хотя туман задается на этапе растеризации данных о вершинах (после наложения текстуры), довольно часто реализации выполняют большинство необходимых вычис- лений для вершин, а затем интерполируют результаты для всего примитива. Такое со- кращение работы санкционировано спецификацией OpenGL, поскольку оно улучшает производительность при незначительной потере точности воспроизведения изображе- ния. Ниже приводится выражение параметра тумана через квадратичную экспоненту (это уравнение управляет смешением тумана с цветом “чистого” фрагмента). //= exp (-(d */с)2) В данном уравнении ff — рассчитанный параметр тумана; d — константа плотно- сти, управляющая “густотой” тумана, /с — координата тумана, которая обычно равна расстоянию от вершины до глаза или аппроксимируется модулем компоненты Z поло- жения вершины в пространстве наблюдения. В примерах этой главы рассчитывается не аппроксимация, а реальное расстояние. В первом шейдере с туманом мы вычисляем только координату тумана и поруча- ем конвейеру с фиксированными функциональными возможностями вычислить пара- метр тумана и выполнить смешение. Во втором примере параметр тумана вычисляет- ся в шейдере вершин, смешение выполняется также для вершины. Выполнение всех указанных операций для вершин эффективнее, чем для фрагментов, и в большинстве случаем это дает приемлемые результаты. На рис. 22.6 иллюстрируется затуманенная сцена, практически идентичная для шейдеров, приведенных в листингах 22.9 и 22.10. Листинг 22.9. Высокоуровневый шейдер вершин с генерацией координаты тумана // fogcoord.vs И // Типичное преобразование вершины //с учетом диффузного и отраженного освещения // и генерацией координаты тумана для вершин uniform vec3 lightPosO; void main(void) { // Типичное MVP-преобразование gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; vec3 N = normalize(gl_NormalMatrix * gl_Normal); vec4 V = gl_ModelViewMatrix * gl_Vertex; vec3 L = normalize(lightPosO - V.xyz); vec3 H = normalized, + vec3(0.0, 0.0, 1.0)); const float specularExp = 128.0; // расчет диффузного освещения float NdotL = dot(N, L); vec4 diffuse = gl_Color * vec4(max(0.0, NdotL)); // расчет отраженного света float NdotH = dot(N, H);
Глава 22. Затенение вершин: настраиваемое преобразование,... 963 Рис. 22.6. Применение тумана для вершин с использованием шейдера вершин vec4 specular = vec4(pow(max(0.0, NdotH), specularExp)); // расчет координаты тумана: расстояние от глаза gl_FogFragCoord = length(V); // суммируем диффузные и отраженные компоненты gl_FrontColor = diffuse + specular; Листинг 22.10. Низкоуровневый шейдер вершин с генерацией координаты тумана !lARBvpl.O # fogcoord.vs # # Типичное преобразование вершины # с учетом диффузного и отраженного света # и генерацией координаты тумана для вершин ATTRIB iPos = vertex.position; ATTRIB iPrC = vertex.color.primary; ATTRIB iNrm = vertex.normal; OUTPUT oPos = result.position; OUTPUT oPrC = result.color.primary; OUTPUT oFgC = result.fogcoord; PARAM mvp[4] = {state.matrix.mvp}; # входное положение # входной # первичный цвет # входная нормаль # выходное положение # выходной # первичный цвет # выходная # координата тумана
964 Часть III OpenGL- следующее поколение # матрица наблюдения модели на матрицу проекции PARAM mv[4] = {state.matrix.modelview}; # матрица наблюдения модели # обратная к транспонированной матрице наблюдения модели: PARAM mvIT[4] = {state.matrix.modelview.invtrans}; PARAM lightPos = program.local[0]; # положение источника света в пространстве наблюдения PARAM density = program.local[1]; # плотность тумана TEMP N, V, L, H, NdotL, NdotH; # временные регистры TEMP diffuse, specular, fogCoord; DP4 oPos.x, iPos, mvp[0]; # преобразуем входное положение матрицей MVP DP4 oPos.y, iPos, mvp[l]; DP4 oPos.z, iPos, mvp[2]; DP4 oPos.w, iPos, mvp[3]; DP4 V.x, iPos, mv[0]; # преобразуем входное положение матрицей MV DP4 V.y, iPos, mv[l]; DP4 V.z, iPos, mv[2]; DP4 V.w, iPos, mv[3]; SUB L, lightPos, V; # вектор источника света DP3 N.x, iNrm, mvITfO]; # преобразуем норму в пространство наблюдения DP3 N.y, iNrm, mvITfl]; DP3 N.z, iNrm, mvIT[2); DP3 N.w, N, N; # нормируем нормаль RSQ N.w, N.w; MUL N, N, N.w; DP3 L.w, L, L; # нормируем вектор источника света RSQ L.w, L.w; MUL L, L, L.w; ADD H.xyz, L, {0, 0, 1); DP3 H.w, H, H; # нормируем вектор биссектрисы RSQ H w, H.w; MUL H, H, H.w; DP3 NdotL, N, L; # N . L MAX NdotL, NdotL, 0.0; MUL diffuse, iPrC, NdotL; DP3 NdotH, N, H; # N . H MAX NdotH, NdotH, 0.0; POW specular, NdotH.x, 128.0.x; # коэффициент отражения равен 128 ADD oPrC, diffuse, specular; # суммируем цвета DP4 fogCoord.х, V, V; # fogCoord = |Ve| RSQ fogCoord.x, fogCoord.x; RCP oFgC.x, fogCoord.x; END
Глава 22 Затенение вершин настраиваемое преобразование, 965 В GLSL расчет расстояния от глаза (0,0,0,1) до вершины в пространстве наблюде- ния тривиален. Нужно всего лишь вызвать встроенную функцию длины, передав ей как аргумент положение вершины В низкоуровневом шейдере ту же операцию нуж- но выполнить вручную Вначале вы находите квадрат вектора положения вершины (скалярное произведение самого на себя) и вычисляете величину, обратную к корню квадратному из предыдущей — т.е. производите обычный набор действий для норми- ровки вектора Однако вместо умножения данного масштабного множителя “1/длина” на вектор вы просто находите обратное к нему, те. “1/длина” превращается в длину, которая и выдается как координата тумана рассматриваемой вершины Ниже приводится модифицированный код GLSL, в котором смешение тумана вы- полняется не в рамках обработки с фиксированными функциональными возможно- стями, а внутри шейдера. uniform float density; // расчет параметра тумана в виде квадратичной экспоненты const float е = 2.71828; float fogFactor = (density * length(V)); fogFactor *= fogFactor; fogFactor = clamp(pow(e, -fogFactor), 0.0, 1.0); // суммируем диффузные и отраженные компоненты, затем // смешиваем с цветом тумана на основе параметра тумана const vec4 fogColor = vec4(0.5, 0.8, 0.5, 1.0); gl_FrontColor = mix(fogColor, clamp(diffuse + specular, 0.0, 1.0), fogFactor); Модифицированный код низкоуровневого шейдера выглядит следующим образом PARAM density = program.local[1]; # плотность тумана PARAM fogColor = {0.5, 0.8, 0.5, 1.0); # цвет тумана PARAM е = 2.71828, 0, 0, 0; TEMP diffuse, specular, fogFactor, litColor; ADD litColor, diffuse, specular; # суммируем цвета MAX litColor, litColor, 0.0; # ограничиваем MIN litColor, litColor, 1.0; # в диапазон [0,1] # fogFactor = clamp(ел(-(d*|Ve|)л2)) DP4 fogFactor.x, V, V; POW fogFactor.x, fogFactor.x, 0.5.x; MUL fogFactor.x, fogFactor.x, density.x; MUL fogFactor.x, fogFactor.x, fogFactor.x; POW fogFactor.x, e.x, -fogFactor.x; MAX fogFactor.x, fogFactor.x, 0.0; # ограничиваем MIN fogFactor.x, fogFactor.x, 1.0; # в диапазон [0,1] SUB litColor, litColor, fogColor; # смешиваем цвет, порожденный освещением, с цветом тумана MAD oPrC, fogFactor.х, litColor, fogColor; END
966 Часть III. OpenGL: следующее поколение Рис. 22.7. Из-за изменения размера точек для разных вершин, удаленные точки кажутся меньше Размер точки Применение тумана приводит к поглощению цветов объектов, удаленных от точки наблюдения. Подобным образом можно задать затухание точек, чтобы точки, визу- ализированные близко к точке просмотра, были относительно большими, а точки, находящиеся дальше, постепенно исчезали. Подобно туману затухание точек служит хорошей визуальной подсказкой о перспективе. Кроме того, похожи и вычисления. Расстояние от вершины до глаза вычисляется точно так же, как координата тумана. Затем, чтобы получить размер точки, экспоненциально затухающий с расстоянием, расстояние возводится в квадрат, находится обратная величина, которая затем умно- жается на константу 100 000. Такое значение константы выбрано с учетом геометрии сцены, чтобы объекты, расположенные дальше вглубь сцены, при визуализации с ис- ходного положения камеры получали размер точки 1 (приблизительно), тогда как точки вблизи переднего края сцены имели размер точки около 10 (приблизительно). В данном простом приложении для передних и задних многоугольников устанав- ливается режим GL_POINT, вследствие чего все объекты на сцене рисуются по точкам. Кроме того, необходимо активизировать gl_vertex_program_point_size_arb, чтобы вместо обычного размера точек OpenGL применялись размеры точек, пода- ваемые на выход шейдера вершин. Результат выполнения соответствующего кода, приведенного в листингах 22.11 и 22.12, показан на рис. 22.7. Листинг 22.11. Высокоуровневый шейдер вершин, генерирующий размер точек // ptsize.vs И // Типичное преобразование вершины
Глава 22. Затенение вершин: настраиваемое преобразование,. . 967 // плюс затухание размера точек void main(void) // Типичное MVP-преобразование gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; vec4 V = gl_ModelViewMatrix * gl_Vertex; gl_FrontColor = gl_Color; //На основе расстояния от глаза рассчитывается размер точки float ptSize = length(V); ptSize *= ptSize; gl_PointSize = 100000.0 / ptSize; Листинг 22.12. Низкоуровневый шейдер вершин, генерирующий размер точек !iARBvpl.O # ptsize.vs # # Типичное преобразование вершины # плюс затухание размера точек ATTRIB iPos = vertex.position; # входное положение ATTRIB iPrC = vertex.color.primary; # входной первичный цвет OUTPUT oPos = result.position; # выходное положение OUTPUT oPrC = result.color.primary; # выходной первичный цвет OUTPUT oPtS = result.pointsize; # выходной размер точки PARAM mvp[4] = {state.matrix.mvp}; # матрица наблюдения модели на матрицу проекции PARAM mv[4] = {state.matrix.modelview}; # матрица наблюдения модели TEMP V, ptSize; # временные регистры DP4 oPos.x, iPos, mvp[0]; # преобразуем входное положение матрицей MVP DP4 oPos.y, iPos, mvp[l]; DP4 oPos.z, iPos, mvp[2]; DP4 oPos.w, iPos, mvp[3]; DP4 V.x, iPos, mv[0]; # преобразуем входное положение матрицей MV DP4 V.y, iPos, mv[l]; DP4 V.z, iPos, mv[2}; DP4 V.w, iPos, mv[3]; MOV oPrC, iPrC; # копируем цвет DP4 ptSize.x, V, V; # ptSize = 100000 / |Ve|A2 RSQ ptSize.x, ptSize.x; MUL ptSize.x, ptSize.x, ptSize.x; MUL oPtS.x, ptSize.x, 100000; END
968 Часть III. OpenGL: следующее поколение Рис. 22.8. Преобразование вершин модифицируется эффектами деформации (растяжение и сплющивание) Пользовательские преобразования вершин Итак, мы уже определили освещение, генерацию текстурных координат и координаты тумана. Что можно сказать о самих положениях вершин? В следующем примере в шейдере реализуется еще одно преобразование; затем мы перейдем к обычным преобразованиям с участием матрицы наблюдения модели/проекции. На рис. 22.8 показаны эффекты масштабирования положения вершины в простран- стве объекта согласно коэффициенту деформации (squash and stretch factor), значение которого можно задавать независимо для всех осей (что и сделано в листингах 22.13 и 22.14). Листинг 22.13. Высокоуровневый шейдер вершин с растяжением и сплющиванием // stretch.vs // // Типичное преобразование вершины // плюс растяжение/сплющивание uniform vec3 lightPosO; uniform vec3 squashstretch; void main(void) { // Типичное MVP-преобразование, за которым следует // растяжение/сплющивание vec4 stretchedCoord = gl_Vertex; stretchedCoord.xyz *= squashstretch;
Глава 22 Затенение вершин настраиваемое преобразование, 969 gl_Position = gl_ModelViewProjectionMatrix * stretchedCoord; vec3 stretchedNormal = gl_Normal; stretchedNormal *= squashStretch; vec3 N = normalize(gl_NormalMatrix * stretchedNormal); vec4 V = gl_ModelViewMatrix * stretchedCoord; vec3 L = normalize(lightPosO - V.xyz); vec3 H = normalize(L + vec3(0.0, 0.0, 1.0)); // рассчитанное диффузное освещение считаем первичным цветом float NdotL = dot(N, L); gl_FrontColor = gl_Color * vec4(max(0.0, NdotL)); // копируем (N.H)*8-7 в текстурную координату float NdotH = max(0.0, dot(N, H) * 8.0 - 7.0); gl_TexCoord[0] = vec4(NdotH, 0.0, 0.0, 1.0); Листинг 22.14. Низкоуровневый шейдер вершин с растяжением и сплющиванием !JARBvpl.O # stretch.vs # # Типичное преобразование вершины # плюс растяжение/сплющивание ATTRIB iPos = vertex.position; # входное положение ATTRIB iPrC = vertex.color.primary; # входной первичный цвет ATTRIB iNrm = vertex.normal; # входная нормаль OUTPUT oPos = result.position; # выходное положение OUTPUT oPrC = result.color.primary; # выходной первичный цвет OUTPUT oTxC = result.texcoord[0]; # выходная текстурная # координата 0 PARAM mvp[4] = { state.matrix.mvp }; # матрица наблюдения модели множится на матрицу проекции PARAM mv[4] = { state.matrix.modelview }; # матрица наблюдения модели # обратная к транспонированной матрице наблюдения модели: PARAM mvIT[4] = ( state.matrix.modelview.invtrans ); PARAM lightPos = program.local[0]; # положение источника света в пространстве наблюдения PARAM squashStretch = program.local[1]; # растягиваем масштабные коэффициенты TEMP N, V, L, H, NdotL, NdotH, ssV, ssN; # временные регистры MUL ssV, iPos, squashStretch; # растягиваем вершины в пространстве объекта MUL ssN, iNrm, squashStretch; # растягиваем нормали в пространстве объекта DP4 oPos.x, ssV, mvp[0]; # преобразовываем растянутое положение посредством MVP DP4 oPos.y, ssV, mvp[l]; DP4 oPos.z, ssV, mvp[2]; DP4 oPos.w, ssV, mvp[3]; DP4 V.x, ssV, mv[0];
970 Часть III. OpenGL-следующее поколение # преобразуем растянутое положение посредством MV DP4 V.y, ssV, mv[l); DP4 V.z, ssV, mv[2J; DP4 V.w, ssV, mv[3]; SUB L, lightPos, V; # вектор источника света DP3 N.x, ssN, mvIT[0]; # преобразуем растянутую нормаль DP3 N.y, ssN, mvIT[l]; DP3 N.z, ssN, mvIT[2]; DP3 N.w, N, N; # нормируем нормаль RSQ N.w, N.w; MUL N, N, N.w; DP3 L.w, L, L; # нормируем вектор источника света RSQ L.w, L.w; MUL L, L, L.w; ADD H.xyz, L, {0, 0, 1); DP3 H.w, H, H; # нормируем вектор биссектрисы RSQ H.w, H.w; MUL H, H, H.w; DP3 NdotL, N, L; # N . L MAX NdotL, NdotL, 0.0; MUL oPrC, iPrC, NdotL; # выдаем диффузное освещение DP3 NdotH, N, H; # N . H MAD NdotH.x, NdotH, 8.0, {-7.0}; # (N . H) * 8 - 7 MOV oTxC, (0.0, 0.0, 0.0, 1.0}; MAX oTxC.x, NdotH, 0.0; # помещаем результат в текстурную координату 0 END Смешение вершин Смешение вершин представляет собой интересную технику, используемую для ске- летной анимации Рассмотрим простую модель руки с локтевым суставом Предпле- чье и бицепс представлены цилиндрами. Когда рука полностью разогнута, никаких проблем со “стыковкой кожи” не возникает. Однако как только вы начинаете сгибать руку (рис 22 9), возникает разрыв натянутой кожи, и реалистичность теряется. Чтобы справиться с этой проблемой, при преобразовании каждой вершины нужно задействовать несколько матриц наблюдения модели. И предплечье, и бицепс уже имеют собственные матрицы наблюдения модели. Ориентация матрицы бицепса бу- дет связана с положением торса, в данном случае эта матрица задается относительно начала координат пространства объекта. Матрица предплечья представляет ориента- цию относительно бицепса. Ключевым моментом смешения вершин является частич- ное использование обеих матриц прн преобразовании вершин, близких к соединению (в рассматриваемом случае — локтю).
Глава 22. Затенение вершин: настраиваемое преобразование,... 971 Рис. 22.9. Простому локтевому суставу без смешения вершин просто необходима кожа Вы можете сами выбрать, насколько далеко от соединения распространяется влия- ние матриц наблюдения модели. Мы будем называть это областью зависимости (или влияния). Вершины, не попадающие в область зависимости, не требуют смешения. Для таких вершин используется только исходная матрица наблюдения модели, соот- несенная с объектом. Тем не менее вершины, входящие в область влияния, должны подвергаться двум преобразованиям: с участием собственной матрицы наблюдения модели и с участием матрицы, принадлежащей объекту с другой стороны локтя. В на- шем примере два эти положения в пространстве наблюдения смешиваются, порождая окончательное положение в пространстве наблюдения. Пропорции смешения двух положений в пространстве наблюдения зависят от весовых коэффициентов смешения вершин. При рисовании с помощью примити- вов glBegin/glEnd помимо использования нормалей, цветов и положений, также необходимо задать для каждой вершины весовой коэффициент. С этой целью мож- но применять функцию glVertexAttriblfARB. Вершины непосредственно возле локтя получают весовые коэффициенты 0.5, что приводит к соотношению вкладов матриц 50% на 50%. Вершины, расположенные возле самого края области влияния, получают весовые коэффициенты 1.0, т.е. собственная матрица объекта имеет 100% влияния. Внутри области зависимости весовые коэффициенты принимают промежу- точные значения, причем присвоение может быть линейным относительно расстояния до локтя или основываться на функции более высокого порядка. Отметим, что все остальные величины, в расчете которых фигурирует матрица на- блюдения модели, также нужно смешивать. В рассматриваемом примере шейдера это относится к диффузному и отраженному освещению. Сказанное означает, что вектор нормали, на который обычно действует матрица, обратная к транспонированной мат-
972 Часть III. OpenGL: следующее поколение Рис. 22.10. Жесткая "двухцилиндровая” рука теперь представляет собой криволинейный гибкий объект рице наблюдения модели, теперь должен преобразовываться дважды (как положение вершины). Результаты смешиваются с учетом тех же весовых коэффициентов, что применялись для смешения соответствующих вершин. С помощью смешения вершин можно создавать правдоподобную кожу на скеле- тообразной структуре, которую очень легко анимировать. На рис. 22. Ю показана рука в новом эластичном режиме, при создании которой область зависимости охватывала всю руку. Исходный код соответствующих шейдеров вершин приводится в листин- гах 22.15 и 22.16. Листинг 22.15. Высокоуровневый шейдер со смешением вершин // skinning.vs // // "Натягивание кожи" на вершины путем смешения двух матриц MV // uniform vec3 lightPos; uniform mat 4 mv2; uniform mat3 mv2IT; attribute float weight; void main(void) { // рассчитываем влияние каждой вершины vec4 Vl = gl_ModelViewMatrix * gl_Vertex; vec4 V2 = mv2 * gl_Vertex; vec4 V = (Vl * weight) + (V2 * (1.0 - weight));
Глава 22 Затенение вершин настраиваемое преобразование 973 gl_Position = gl_ProgectionMatrix * V; // рассчитываем влияние каждой нормали vec3 Nl = gl_NormalMatrix * gl_Normal; vec3 N2 = mv2IT * gl_Normal; vec3 N = normalize((Nl * weight) + (N2 * (1.0 - weight))); vec3 L = normalize(lightPos - V.xyz); vec3 H = normalized, + vec3(0.0, 0.0, 1.0)); // рассчитанное диффузное освещение считаем первичным цветом float NdotL = dot(N, L); gl_FrontColor = 0.1 + gl_Color * vec4(max(0.0, NdotL)); // копируем (N.H)*8-7 в текстурную координату float NdotH = max(0.0, dot(N, H) * 8.0 - 7.0); gl_TexCoord[0] = vec4(NdotH, 0.0, 0.0, 1.0); Листинг 22.16. Низкоуровневый шейдер co смешением вершин !iARBvpl.O # skinning.vp # # "Натягивание кожи" на вершины # путем смешения двух матриц MV # ATTRIB iPos = vertex.position; # входное положение ATTRIB iPrC = vertex.color.primary; # входной ATTRIB iNrm = vertex.normal; ATTRIB iWeight = vertex.attrib[1]; OUTPUT oPos = result.position; OUTPUT oPrC = result.color.primary; OUTPUT oTxC = result.texcoord[0]; # первичный цвет # входная нормаль # входной # весовой коэффициент # выходное положение # выходной # первичный цвет # выходная текстурная # координата 0 PARAM pro[4] = {state.matrix.pro}ection}; # матрица проекции PARAM mvl[4] = {state.matrix.modelview); # матрица наблюдения модели 1 PARAM mv2[4] = {state.matrix.programi0]); # матрица наблюдения модели 2 # обратная к транспонированной матрице наблюдения модели: PARAM mvlIT[4] = {state.matrix.modelview.invtrans}; PARAM mv2IT[4] = {state matrix.program!0].invtrans); PARAM lightPos = program local[0]; # положение источника света в пространстве наблюдения TEMP Nl, N2, N, VI, V2, V; # временные регистры TEMP L, H, NdotL, NdotH; DP4 VI.x, iPos, mvl[0]; # преобразуем входное положение матрицей MV1 DP4 VI.у, iPos, mvl[l); DP4 Vl.z, iPos, mvl[2]; DP4 VI w, iPos, mvl[3]; DP4 V2 x, iPos, mv2[0);
974 Часть III OpenGL следующее поколение # преобразуем входное положение матрицей MV2 DP4 V2.y, iPos, mv2[l]; DP4 V2.z, iPos, mv2[2]; DP4 V2.w, iPos, mv2[3]; SUB V, Vl, V2; # смешиваем вершину с весовым коэффициентом MAD V, V, iWeight.x, V2; DP4 oPos.x, V, prj[0); # преобразование в усеченное пространство DP4 oPos.y, V, prj[1]; DP4 oPos.z, V, prj[2]; DP4 oPos.w, V, prj[3]; SUB L, lightPos, V; # вектор источника света DP3 Nl.x, iNrm, mvlIT[0]; # преобразуем норму в пространство наблюдения DP3 Nl.y, iNrm, mvlIT[l); DP3 Nl.z, iNrm, mvlIT[2]; DP3 N2.x, iNrm, mv2lT[0]; # преобразуем норму в пространство наблюдения DP3 N2.y, iNrm, mv2lT[l]; DP3 N2.z, iNrm, mv2IT[2J; SUB N, Nl, N2; # смешиваем нормаль с весовым коэффициентом MAD N, N, iWeight.x, N2; DP3 N.w, N, N; # нормируем нормаль RSQ N.w, N.w; MUL N, N, N.w; DP3 L.w, L, L; # нормируем вектор источника света RSQ L.w, L.w; MUL L, L, L.w; ADD H.xyz, L, {0, 0, 1}; DP3 H.w, H, H; # нормируем вектор биссектрисы RSQ H.w, H.w; MUL H, H, H.w; DP3 NdotL, N, L; # N . L MAX NdotL, NdotL, 0.0; MAD oPrC, iPrC, NdotL, 0.1; # подаем на выход диффузное освещение DP3 NdotH, N, Н; # N . Н MAD NdotH.х, NdotH, 8.0, {-7 0); # (N . H) * 8 - 7 MOV oTxC, {0.0, 0.0, 0.0, 1.0}; MAX oTxC.x, NdotH, 0 0; # помещаем результат в текстурную координату 0 END В приведенном примере для доступа к первичной матрице смешения использо- ваны встроенные равномерные переменные (GLSL) или параметры (низкоуровневые схемы) матрицы наблюдения модели Для получения вторичной матрицы задейству- ется определенная пользователем равномерная матрица (GLSL) или программная
Глава 22. Затенение вершин: настраиваемое преобразование, 975 матрица (низкоуровневые схемы). Для преобразования нормали необходимо знать все матрицы, обратные к транс- понированным матрицам смешения Хотя в низкоуровневых схемах существует про- стой способ получения матрицы, обратной транспонированной, в высокоуровневых схемах такой возможности нет. Чтобы обратиться к матрице, обратной к транспони- рованной первичной матрице, применяется встроенная функция gl_NormalMatrix, однако для вторичной матрицы такая удобная сокращенная команда отсутствует. По- этому приходится вручную рассчитывать обратную ко второй матрице наблюдения модели и транспонировать ее, вызывая glUniformMatrix3fvARB. Резюме В данной главе представлены различные простые шейдеры, которые могут стать плацдармом для исследования низко- и высокоуровневых схем затенения вершин. В частности, были представлены примеры настраиваемого расчета освещения, гене- рации текстурных координат, тумана, размера точек и преобразования вершин. Уделить шейдерам вершин немного внимания важно, но в реальной жизни они часто играют лишь второстепенные роли, дополняя шейдеры фрагментов и выполняя такие вспомогательные действия, как подготовка текстурных координат. Шоу делают шейдеры фрагментов. Их рассмотрение мы начнем в следующей главе После этого мы еще раз вернемся к шейдерам вершин, объединим их с шейдерами фрагментов и скажем “Прощай!” фиксированным функциональным возможностям

ГЛАВА 23 Затенение фрагментов: поддерживаем обработку пикселей Бенджамин Липчак Из ЭТОЙ ГЛАВЫ ВЫ УЗНАЕТЕ . . . • Как менять цвета • Как обрабатывать изображения после рисования • Как осветить объект по фрагментам • Как выполнить процедурное наложение текстуры Как отмечалось в главе 19, “Программируемый конвейер: это не тот OpenGL, который помнит ваш отец”, шейдеры фрагментов замещают несколько каскадов кон- вейера с фиксированными функциональными возможностями наложение текстуры, суммирование цветов и наложение тумана. Именно в этом месте конвейера и про- исходит самое интересное. Вместо того чтобы как стадо коров послушно следовать по проложенному пути, применяя все активизированные текстуры на основе пред- определенной текстурной координаты, фрагменты могут выбрать собственный путь Возможно, это будет смешение и сопоставление текстурных координат Или расчет собственных текстурных координат. А может быть текстурирования вообще не будет, а будут рассчитаны какие-то необычные цвета. В данном случае хороши все варианты. По своей природе шейдеры вершин и фрагментов чаще всего используются в паре Доминантным партнером являются шейдеры вершин, которые, собственно, и дают ту “конфетку”, которую вы видите на экране, поэтому, как правило, им уделяется боль- ше внимания Тем нс менее шейдеры вершин играют важную вспомогательную роль Из соображений производительности максимум “черной работы” перекладывается на шейдеры вершин, поскольку они обычно выполняются существенно реже (исклю- чая случаи визуализации самых маленьких треугольников) После этого результаты помещаются в аргументы, подлежащие интерполяции, которые и подаются на вход шейдера. Шейдер вершин — это самоотверженный исполнитель, шейдер фрагмен- тов — жадный потребитель. В этой главе мы продолжаем обучение на примере, который начали разбирать в предыдущей главе Мы представим много шейдеров фрагментов, предполагая как дальнейшее изучение низко- и высокоуровневых шейдеров, так и подготовку плац- дарма для ваших собственных творческих исследований Поскольку схему затене- ния фрагментов редко можно наблюдать саму по себе, то, разобрав несколько таких схем, мы обсудим примеры совместной гармоничной работы шейдеров фрагментов и вершин
978 Часть III. OpenGL: следующее поколение Рис. 23.1. Шейдер фрагментов преобразует RGB-цвет в одно полутоновое значение Преобразование цвета Итак, мы уже практически завершили разработку нескольких примеров, иллюстри- рующих использование шейдеров фрагментов без поддержки шейдеров вершин. На- пример, мы легко можем разделить эти схемы в задачах, где требуется просто изме- нить существующий цвет. В таких случаях для получения начального цвета будем использовать этап освещения конвейера с фиксированными функциональными воз- можностями, а затем займемся собственно шейдерами фрагментов. Полутона Одной из задач, подходящих для самостоятельного решения, является имитация черно-белого фильма. Сформулировать ее можно следующим образом. Для задан- ной насыщенности красного, зеленого и синего каналов требуется рассчитать общую полутоновую (grayscale) интенсивность, которая и будет подаваться во все три кана- ла. Красный, зеленый и синий цвета по-разному отражают свет, и для учета этого в конечной интенсивности мы представим их с разными вкладами. В листингах 23.1 и 23.2 приводятся две версии шейдера — GLSL и ARB_f ragment_ program, соответственно. Поскольку из-за черно-белого цвета рисунков нюансы от- личий неразличимы, рис. 23.1 представлен просто как иллюстрация других моментов, касающихся шейдеров.
Глава 23 Затенение фрагментов поддерживаем обработку пикселей 979 Листинг 23.1. Высокоуровневый шейдер фрагментов с полутоновым преобразованием // grayscale.fs // // преобразование RGB-кодов в полутоновые значения void main(void) { // Преобразование в полутона float gray = dot(gl_Color.rgb, vec3(0.3, 0.59, 0.11)); // Копируем полутоновое значение в RGB-компоненты gl_FragColor = vec4(gray, gray, gray, 1 0); Листинг 23.2. Низкоуровневый шейдер фрагментов с полутоновым преобразованием !IARBfpl.0 # grayscale.fp # # преобразование RGB-кодов в полутоновые значения ATTRIB iPrC = fragment.color.primary; # входной первичный цвет OUTPUT oPrC = result.color; # выходной цвет DP3 oPrC.rgb, iPrC, {0.3, 0.59, 0.11); # вклады R, G и В отличаются MOV oPrC.a, 1.0; # альфа инициализируется # co значением 1 END Отметим ключевой момент для всех шейдеров то, что вы пишете в выходной цвет (gl_FragColor и result. color), передается по всему конвейеру OpenGL, в конечном счете попадая в буфер кадров В наших случаях входным первичным цветом является gl_Color и fragment.color.primary, соответственно Поэкспериментируйте со вкладами различных каналов Обратите внимание на то, как они суммируются до 1 В принципе вы можете сымитировать эффект передержан- ного снимка, когда величины при суммировании превышают 1, или недодержанного снимка, когда сумма значений меньше 1 Сепия В следующем примере мы раскрасим полутоновое изображение сепией (ярко- коричневой краской) Такой тон имитирует старые фотографии Для решения по- ставленной задачи мы вначале преобразуем изображение в полутоновый рисунок (см выше) После этого умножим код серого значения на вектор цвета, усиливаю- щий одни каналы цвета и ослабляющий другие Выражение данного преобразования в коде представлено в листингах 23 3 и 23 4
980 Часть III OpenGL-следующее поколение Листинг 23.3. Высокоуровневый шейдер фрагментов с преобразованием в коричневый цвет // sepia.fs 11 II Преобразование RGB-кодов в коричневый цвет void main(void) { // Преобразование RGB-кодов в полутоновые значения float gray = dot(gl_Color.rgb, vec3(0.3, 0.59, 0.11)); // Преобразование полутонового значения в коричневый цвет gl_FragColor = vec4(gray * vec3(1.2, 1.0, 0.8), 1.0); Листинг 23.4. Низкоуровневый шейдер фрагментов с преобразованием в коричневый цвет !IARBfpl.0 # sepia.fp # # Преобразование RGB-кодов в коричневый цвет ATTRIB iPrC = fragment.color.primary; # входной первичный цвет OUTPUT oPrC = result.color; # выходной цвет TEMP gray; DP3 gray, iPrC, {0.3, 0.59, 0.11); # преобразование в полутона MUL oPrC.rgb, gray, {1.2, 1.0, 0.8}; # преобразование в коричневый # цвет MOV oPrC.a, 1.0; # альфа инициализируется # со значением 1 END Точно так же можно перекрасить изображение в любой выбранный оттенок По- экспериментируйте с параметрами цвета' В данном примере мы выбрали коричневый цвет Для удовлетворения своих амбиций вы можете реализовать даже схему внеш- ней подстановки оттенка в зависимости от приложения (соответствующие константы имеют тип uniform в GLSL, в ARB_fragment_program применяются программные параметры) Это позволит пользователю самому выбирать цвет, и вам нс придется писать отдельную схему затенения для каждого оттенка Инверсия В следующем примере мы превратим изображение в негатив Подобные шейдеры настолько просты, что почти нс заслуживают упоминания. Все, что вам нужно сде- лать, — взять существующий цвет и вычесть его коды цвета из 1 Черный при этом становится белым, а белый — черным Красный переходит в голубой Пурпурный — в зеленый В общем, вы поняли На рис 23 2 показана инверсия цвета с помощью кодов, приведенных в листин- гах 23 5 и 23 6 Инверсия полутонового изображения так же проста — используйте свое воображение или изучите соответствующий! код, приведенный на компакт-диске
Глава 23. Затенение фрагментов: поддерживаем обработку пикселей 981 Рис. 23.2. Шейдер фрагментов инвертирует RGB-цвета, превращая изображение в негатив Листинг 23.5. Высокоуровневый шейдер фрагментов с инверсией цветов // colorinvert.fs // получение цветного негатива с помощью инверсии void main(void) // инверсия компонентов цвета gl_FragColor.rgb = 1.0 - gl_Color. rgb; gl_FragColor.a = 1.0; Листинг 23.6. Низкоуровневый шейдер фрагментов с инверсией цветов !'ARBfpl.O # colorinvert. fp # # получение цветного негатива с помощью инверсии ATTRIB iPrC = fragment.color.primary; # входной первичный цвет OUTPUT oPrC = result.color; # выходной цвет SUB oPrC.rgb, 1.0, iPrC; # инверсия RGB-цветов MOV oPrC.a, 1.0; # альфа инициализируется # co значением 1 END
982 Часть 111 OpenGL-следующее поколение Моделирование теплового излучения Теперь приступим к реализации первого поиска текстуры В приведенном ниже при- мере имитируется эффект горячего тела, возможно, знакомый вам по фильму “Хищ- ник”. Тепло представляется спектром цветов, меняющихся от черного к синему, а за- тем к желтому и красному Снова используем полутоновое преобразование, на этот раз получая с его по- мощью скалярное значение степени нагрева Это значение будет применяться как текстурная координата одномерной текстуры, состоящей из градиентов цветов от черного до красного Результат моделирования теплового излучения тела согласно листингам 23 7 и 23.8 показан на рис 23 3 Листинг 23.7. Высокоуровневый шейдер фрагментов с моделированием теплового излучения // heatsig.fs И II отображение полутонового значения в тепловой код uniform samplerlD samplerO; void main(void) { // Преобразование в полутоновое изображение float gray = dot(gl_Color.rgb, vec3(0.3, 0.59, 0.11)); // Поиск теплового кода gl_FragColor = texturelD(samplerO, gray); Листинг 23.8. Низкоуровневый шейдер фрагментов с моделированием теплового излучения !'ARBfpl.O # heatsig.fp # отображение полутонового значения в тепловой код ATTRIB iPrC = fragment color.primary; # входной первичный цвет OUTPUT oPrC = result.color; # выходной цвет TEMP gray; DP3 gray, iPrC, {0.3, 0.59, 0 11); # R,G,B -> серый TEX oPrC, gray, texture[0], ID; # поиск теплового кода END Обратите внимание на то, как в низкоуровневом шейдере мы обращаемся к тек- стурной единице texture!0], в которой желаем найти нужные нам значения Данная текстурная единица жестко закодирована в шейдере Сравните это со схемой GLSL, где используется специальная величина типа uniform, которая может задаваться в приложении
Глава 23. Затенение фрагментов: поддерживаем обработку пикселей 983 Рис. 23.3. Шейдер фрагментов имитирует вид горячего тела в инфракрасном диапазоне путем нахождения соответствующего цвета в одномерной текстуре Зависимый поиск текстуры Наложение текстуры в конвейере с фиксированными функциональными возможно- стями было весьма ограниченным, требовало использования при поиске текстурных координат, значения которых интерполировались для каждой вершины. Одной из мощнейших новых возможностей, открытых шейдерами фрагментов, стала возмож- ность расчета для фрагмента произвольных текстурных координат. Появилась даже возможность использовать результат поиска текстуры в качестве координаты другого поиска. Все описанные возможности связаны с зависимым поиском текстуры. (На- звание технологии объясняется тем, что поиск зависит от других предшествующих операций шейдера фрагментов.) Возможно, вы не заметили, но зависимый поиск текстуры применялся в предыду- щем разделе в шейдере с моделированием теплового излучения. Действительно, вна- чале мы рассчитали текстурную координату, выполнив полутоновое преобразование. После этого мы использовали это значение в качестве текстурной координаты при зависимом поиске текстуры в одномерной текстуре цветной кодировки температуры. Цепочку зависимости можно продолжить. Например, можно взять цвет тексту- ры, имитирующей вид горячего тела, и использовать его как текстурную координату для выполнения поиска в кубической текстуре (возможно, для выполнения гамма- коррекции цвета). Однако здесь следует быть внимательным: в некоторых реализаци- ях OpenGL длина цепочек зависимости ограничена на аппаратном уровне, поэтому учитывайте такую возможность, если не хотите, чтобы ваше приложение запускалось с программным, а не аппаратным ускорением!
984 Часть III. OpenGL: следующее поколение Рис. 23.4. В шейдере фрагментов расчет тумана выполняется для фрагментов Реализация тумана для фрагментов Вместо выполнения для каждой вершины смешения с туманом или расчета для каж- дой вершины параметра тумана и использования смешения с туманом в рамках кон- вейера с фиксированными функциональными возможностями, можно рассчитать па- раметр тумана и выполнить смешение самостоятельно в шейдере фрагментов. В при- веденном ниже примере имитируется режим тумана GL_EXP2 с единственным отли- чием — схема точнее большинства реализацией с фиксированными функциональны- ми возможностями, в которых экспоненциальное затухание применяется к вершинам, а не к фрагментам. Особенно разница заметна на геометрии со слабой мозаичностью, растянутой от переднего плана к заднему (например, пол, на котором стоят осталь- ные объекты сцены). Сравните полученные результаты с тем, что давали шейдеры из предыдущей главы, и отличия просто бросятся в глаза. Результат выполнения шейдера с реализацией тумана, представленной в листин- гах 23.9 и 23.10, показан на рис. 23.4. Листинг 23.9. Высокоуровневый шейдер с наложением тумана по фрагментам // fog.fs // // расчет тумана по пикселям uniform float density; void main (void) I const vec4 fogColor = vec4(0.5, 0.8, 0.5, 1.0); // расчет параметра тумана в виде квадратичной экспоненты
Глава 23 Затенение фрагментов поддерживаем обработку пикселей 985 // на основе координаты Z фрагмента const float е = 2.71828; float fogFactor = (density * gl_FragCoord.z); fogFactor *= fogFactor; fogFactor = clamp(pow(e, -fogFactor), 0.0, 1.0); // Смешиваем цвет тумана с входным цветом gl_FragColor = mix(fogColor, gl_Color, fogFactor); Листинг 23.10. Низкоуровневый шейдер с наложением тумана по фрагментам !’ARBfpl.O # fog.fp # # расчет тумана по пикселям ATTRIB iPrC = fragment.color.primary; # входной первичный цвет ATTRIB iFrP = fragment.position; # входное положение # фрагмента OUTPUT oPrC = result.color; # выходной цвет PARAM density = program.local[0]; # плотность тумана PARAM fogColor = (0.5, 0.8, 0.5, 1.0); # цвет тумана PARAM e = (2.71828, 0, 0, 0); TEMP fogFactor; # fogFactor = clamp(eA(-(d*Zw)A2)) MUL fogFactor.x, iFrP.z, density.x; MUL fogFactor.x, fogFactor.x, fogFactor.x; POW fogFactor.x, e.x, -fogFactor.x; MAX fogFactor.x, fogFactor x, 0 0; # ограничение согласно MIN fogFactor x, fogFactor x, 1.0; # диапазону [0,1] LRP oPrC, fogFactor.x, iPrC, fogColor; # смешиваем цвет, полученный за счет освещения, и цвет тумана END Здесь нужно прокомментировать несколько моментов Первый касается команд, используемых для смешения С одной стороны у нас есть встроенные функции сме- шения GLSL С другой — имеется характерная для ARB_f ragment_program коман- да LRP низкоуровневого шейдера В ARB_vertex_program команда LRP недоступна, и здесь смешение нужно выполнять посредством ряда команд, в частности SUB и MAD Второй момент связан с тем, что плотность нс кодируется жестко в шейдере, а представляет собой константу, заданную внешне Таким образом, плотность можно привязать к нажатию клавиши Когда пользователь нажимает клавиши со стрелками влево и вправо, в шейдере обновляется константа плотности (ей присваивается новое значение), при этом текст шейдера совершенно не меняется Существует следующее общее правило не должны жестко кодироваться только те константы, которые вы, возможно, в какой-то момент захотите поменять Жестко кодируя значение, вы даете оптимизирующему компилятору OpcnGL возможность использовать данную инфор- мацию для ускорения выполнения шейдера
986 Часть III OpenGL следующее поколение Создав туман, можно использовать сокращенную команду низкоуровневого рас- ширения ARB_fragment_program, которая позволяет в одной строке затребовать реализацию тумана. !IARBfpl.0 OPTION ARB_fog_exp2; Кроме указанной доступны еще опции — ARB_fog_exp и ARB_fog_linear Эти сокращенные команды были введены для того, чтобы облегчить разработчикам при- ложений, привыкшим к команде glEnable(GL_FOG), переход на новые средства управления. Обработка изображений Обработка изображений является еще одной сферой применения шейдеров фрагмен- тов, не зависящих от поддержки шейдеров вершин. После рисования сцены без шей- дера фрагментов с помощью ядра свертки можно продолжить обработку изображения Чтобы сохранить лаконичность шейдеров и повысить вероятность их аппаратного ускорения на различном оборудовании, ограничимся размером ядра 3x3 (однако вы вполне можете поэкспериментировать с большими ядрами) В нашем приложении-примере копирование содержимого буфера кадров в тек- стуру осуществляется посредством функции glCopyTexImage2D Размер текстуры выбирается равным наибольшей степени 2, при которой размер текстуры меньше размера окна Затем в центре окна с помощью шейдера фрагментов рисуется квадрат, размер которого равен размеру текстуры Текстурная координата принимает значения от (0,0), что соответствует левому нижнему углу, до (1,1), что соответствует правому верхнему углу квадрата Шейдер фрагментов принимает текстурную координату и выполняет поиск тексту- ры, помещая в центр окна ядро 3x3 Затем посредством восьми различных сдвигов выборка текстуры переносится в восемь квадратов, соседствующих с центральным. Позже шейдер применяет определенный фильтр, в результате чего находится новый цвет центра области 3 х 3 В наших шейдерах-примерах представлены различные фильтры, широко применяющиеся для обработки изображения Размывание Пожалуй, наиболее распространенным является фильтр размывания Его примене- ние сглаживает зазубренные края объектов Этот фильтр также называется фильтром нижних частот (Low-Pass Filter — LPF, ФНЧ), поскольку он, отфильтровывая высоко- частотные элементы, позволяет проходить низкочастотным составляющим Так как мы используем всего лишь ядро 3x3, при первом проходе размывание будет не очень впечатляющим Чтобы заметно размыть изображение, можно приме- нить большее ядро, или (как в нашем примере) подействовать фильтром размывания несколько раз. Результат пятикратного применения фильтра показан на рис. 23 5, а реализация описанных действий в коде приведена в листингах 23 11 и 23 12
Глава 23. Затенение фрагментов: поддерживаем обработку пикселей 987 Рис. 23.5. Размывание сцены с помощью шейдера фрагментов Листинг 23.11. Высокоуровневый шейдер с размыванием в ходе последующей обработки // blur.fs // // размывающее (нижних частот) ядро 3x3 uniform sampler2D samplerO; uniform vec2 tc_offset[9]; void main(void) vec4 sample[9]; for (int i = 0; i < 9; i++) { sample[i] = texture2D(samplerO, gl_TexCoord[0].st + tc_offset[i]); } //121 // 2 1 2 / 13 //121 gl_FragColor = (sample[0] + (2.0*sample[1]) + sample[2] + (2.0*sample[3]) + sample[4] + (2.0*sample[5]) + sample[6] + (2.0*sample[7]) + sample[8])/13.0;
988 Часть III OpenGL- следующее поколение Листинг 23.12. Низкоуровневый шейдер с размыванием в ходе последующей обработки !IARBfpl.0 # blur.fp # # размывающее (нижних частот) ядро 3x3 ATTRIB iTCO = fragment.texcoord[0]; # входная текстурная координата OUTPUT OPrC = result.color; # выходной цвет TEMP tcO, tel, tc2, tc3, tc4, tc5, tc6, tc7, tc8; ADD tcO, iTCO, program.local[0]; ADD tel, iTCO, program.local[1]; ADD tc2, iTCO, program.local[2]; ADD tc3, iTCO, program.local[3]; ADD tc4, iTCO, program.local[4]; ADD tc5, iTCO, program.local[5]; ADD tc6, iTCO, program.local[6]; ADD tc7, iTCO, program.local[7]; ADD tc8, iTCO, program.local[8]; TEX tcO, tcO, texture[0], 2D; TEX tel, tel, texture[0], 2D; TEX tc2, tc2, texture[0], 2D; TEX tc3, tc3, texture[0], 2D; TEX tc4, tc4, texture[0], 2D; TEX tc5, tc5, texture[0], 2D; TEX tc6, tc6, texture(O), 2D; TEX tc7, tc7, texture[0], 2D; TEX tc8, tc8, texture[0], 2D; # 12 1 # 2 1 2 / 13 #12 1 ADD tcO, tcO, tc2; ADD tc2, tc4, tc6; ADD tcO, tcO, tc2; ADD tcO, tcO, tc8; ADD tel, tel, tc3; ADD tc3, tc5, tc7; ADD tel, tel, tc3; MAD tcO, tel, 2.0, tcO; MUL oPrC, tcO, 0.076923; # 1/13 END Первое, что мы сделали в приведенных схемах затенения, — сгенерировали девять текстурных координат Для этого к интерполированным текстурным координатам добавляются заранее вычисленные постоянные смещения Смещения вычислялись с учетом размера текстуры так, чтобы соседние тексели (северный, южный, восточ- ный, западный, северо-восточный, юго-восточный, северо-западный, юго-западный) можно было получить с помощью простого поиска текстуры. В низкоуровневой вер- сии texture [ 0 ] указывает, что в поиске текстуры задействована текстурная единица 0 В версии GLSL с этой целью необходимо использовать специальную величину типа uniform, именуемую схемой дискретизации (sampler) (то же самое мы дела-
Глава 23 Затенение фрагментов поддерживаем обработку пикселей 989 ли в схеме “инфракрасного зрения”). Схема дискретизации загружается вне шейдера и указывает, какую текстурную единицу следует использовать. Ближайшие значения получаются точно так же, как в любом шейдере для обра- ботки изображений. Каждый шейдер имеет собственный фильтр, применяющийся к значениям области. При использовании фильтра размывания области код текселя умножается на ядро коэффициентов 3x3 (единицы и двойки), сумма которых равна 13 Получающиеся в результате значения суммируются и усредняются путем деления на 13, порождая новый цвет текселя Обратите внимание на то, что мы вполне могли бы сформировать ядро из коэффициентов 1/13 и 2/13, а не 1 и 2, но это потребовало бы большого числа дополнительных операций умножения Гораздо проще и дешевле в самом конце масштабировать результат с коэффициентом 1/13 Поэкспериментируйте с коэффициентами фильтра. Что будет, если поместить в каждом углу весовой коэффициент 1, а результат разделить на 4? Обратите вни- мание, что происходит, когда вы делите на величину, большую или меньшую суммы коэффициентов, сцена становится темнее или светлее. Это логично. Если бы вся сцена была белой, то в результате описанных действий коэффициенты фильтра умножались бы на 1 и складывались Если не разделить результат на сумму коэффициентов, на преобразованном изображении цвет уже не будет белым. Усиление резкости Противоположностью размывания является усиление резкости (sharpening) Благо- даря этой технике, в частности, можно более резко выразить края на изображении и повысить читабельность текста Усиление резкости иллюстрирущся на рис 23 6 (в данном случае соответствующий фильтр применен дважды) Ниже приводится код GLSL, в котором реализовано применения фильтра резкости // sharpen.fs // // ядро 3x3 усиления резкости uniform sampler2D samplerO; uniform vec2 tc_offset[9]; void main(void) ( vec4 sample[9]; for (int i = 0; i < 9; i++) { sample[i] = texture2D(samplerO, gl_TexCoord[0].st + tc_offset[i)); > // -1 -1 -1 // -1 9 -1 // -1 -1 -1 gl_FragColor = (sample[4] * 9.0) - (sample[0] + sample[1] + sample[2] + sample[3] + sample[5] + sample!6] + sample[7] + sample[8]);
990 Часть III. OpenGL: следующее поколение Рис. 23.6. Усиление резкости на сцене с помощью шейдера фрагментов Все низкоуровневые шейдеры для обработки изображений выглядят одинаково до момента поиска текстуры, поэтому сосредоточимся на том, чем они отличаются, т.е. на применении ядра свертки. Низкоуровневый код усиления резкости выглядит следующим образом: !IARBfpl.0 # sharpen.fp # # ядро 3x3 наведения резкости ADD tcO, -tcO, -tel; ADD tcO, tcO, -tc2; ADD tcO, tcO, -tc3; ADD tcO, tcO, -tc5; ADD tcO, tcO, -tc6; ADD tcO, tcO, -tc7; ADD tcO, tcO, -tc8; MAD oPrC, tc4, 9.0, tcO; END
Глава 23. Затенение фрагментов: поддерживаем обработку пикселей 991 Рис. 23.7. Расширение объектов с помощью шейдера фрагментов Обратите внимание на то. что коэффициенты этого ядра также дают в сумме 1, как и коэффициенты фильтра размывания. Благодаря этому фильтр в среднем не меняет уровень яркости. Просто яркость распределяется желательным образом. Расширение и эрозия Расширение и эрозия относятся к морфологическим фильтрам, т.е. они меняют фор- му объектов. Расширение увеличивает размер ярких объектов, а эрозия — уменьшает. (На темные объекты указанные процессы оказывают противоположное влияние.) Эф- фект троекратного применения фильтров расширения и эрозии к группе различных объектов показан на рис. 23.7 и 23.8, соответственно. Сутью расширения является нахождение ближайшего максимального значения. // dilation.fs // // максимум ядра 3x3 uniform sampler2D samplerO; uniform vec2 tc_offset[9]; void main(void) { vec4 sample[9]; vec4 maxValue = vec4(0.0); for (int i = 0; i < 9; i++) {
992 Часть III OpenGL'следующее поколение sampled] = texture2D( samplerO, gl_TexCoord[0] .st + tc_offset[i]) ; maxValue = max(sample[i], maxValue); ) gl_FragColor = maxValue; } IIARBfpl.0 # dilation.fp # # максимум ядра 3x3 шиш tcO, tcO, tel; tcO, tcO, tc2; tcO, tcO, tc3; tcO, tcO, tc4; tcO, tcO, tc5; tcO, tcO, tc6; tcO, tcO, tc7; oPrC, tcO, tc8, END Эрозия заключается в нахождении ближайшего минимального значения // erosion.fs // минимум ядра 3x3 uniform sampler2D samplerO; uniform vec2 tc_offset[9]; void main(void) { vec4 sample[9]; vec4 minValue = vec4(1.0); for (int 1=0; i < 9; i++) { sample[i] = texture2D(samplerO, gl_TexCoord[0].st + tc_offset[i]); minValue = min(sample[i], minValue); } gl_FragColor = minValue; ) IIARBfpl.0 # erosion.fp # # минимум ядра 3x3
Глава 23. Затенение фрагментов: поддерживаем обработку пикселей 993 Рис. 23.8. Применение эрозии к объектам с помощью шейдера фрагментов MIN tcO, tcO, tel; MIN tcO, tcO, tc2; MIN tcO, tcO, tc3; MIN tcO, tcO, tc4; MIN tcO, tcO, tc5; MIN tcO, tcO, tc6; MIN tcO, tcO, tc7; MIN oPrC, tcO, tc8, END Детектирование краев Последний класс фильтров, заслуживающих отдельного упоминания, — детекторы краев. Их суть выражена в названии: они применяются для выявления на изображе- нии краев. Краями называются места изображения, где происходит быстрая смена цвета, а фильтры детектирования краев находят такие быстрые изменения и подчер- кивают их. Существует три распространенных детектора краев: Лапласа (Laplacian), Собеля (Sobel) и Прюитта (Prewitt). Детекторы Собеля и Прюитта представляют собой гради- ентные фильтры, обнаруживающие изменения первых производных интенсивностей каналов цвета в одном направлении. Детектор Лапласа находит переходы через нуль второй производной, когда градиент интенсивности резко переходит от осветления
994 Часть III. OpenGL: следующее поколение Рис. 23.9. Реализация лапласова детектора краев в шейдере фрагментов (движения в сторону более светлых тонов), к затемнению (движению в сторону более темных тонов) или наоборот. Детектор Лапласа подходит для выявления краев любой ориентации. Поскольку отличия в результатах работы детекторов минимальны, на рис. 23.9 по- казаны результаты применения лишь одного из них — фильтра Лапласа. На досуге вы можете самостоятельно поэкспериментировать с другими фильтрами (соответствую- щий код приводится на компакт-диске). Код с реализацией фильтра Лапласа практически идентичен коду наведения рез- кости, который мы рассматривали выше. // laplacian.fs И // Лапласов детектор краев uniform sampler2D samplerO; uniform vec2 tc_offset[9]; void main(void) ( vec4 sample[9]; for (int i = 0; i < 9; i++) { sample[i] = texture2D(samplerO, gl_TexCoord[0].st + tc_offset[i]); } // -1
Глава 23 Затенение фрагментов-поддерживаем обработку пикселей 995 gl_FragColor = (sample[4] * 8.0) - (sample! 0] + sampled] + sample [2] + sample[3] + sample[5] + sample[6] + sample[7] + sample[8]); !iARBfpl.O # laplacian.fp # # Лапласов детектор краев ADD tcO, -tcO, -tel; ADD tcO, tcO, -tc2; ADD tcO, tcO, -tc3; ADD tcO, tcO, -tc5; ADD tcO, tcO, -tc6; ADD tcO, tcO, -tc7; ADD tcO, tcO, -tc8; MAD oPrC, tc4, 8.0, tcO; END Разумеется, отличие существует — центральное значение ядра равно не 9, как в ядре наведения резкости, а 8 Сумма коэффициентов теперь равна не 1, а 0. Именно из-за этого изображение выглядит темнее обычного. Вместо того чтобы в среднем сохранять первоначальную яркость изображения, ядро детектора краев будет давать 0 в тех местах изображения, где цвет не меняется. Освещение Поздравляем с возвращением к обсуждению шейдеров с расчетом освещения! В предыдущей главе мы изучили расчет освещения для вершин. Кроме того были описаны несколько трюков с использованием конвейера с фиксированными функци- ональными возможностями, позволяющих улучшить результаты обработки вершин: отделение отраженного освещения с последующим суммированием цветов, мощная функция текстуры в качестве коэффициента отражения. В этой главе мы рассчитаем освещение в шейдере фрагментов, добившись в результате еще большей точности. Приведенные в этой главе шейдеры вам знакомы В них реализованы те же урав- нения освещения, что и в предыдущей главе, поэтому код будет практически тем же Одним из отличий является совместное использование шейдеров вершин и фраг- ментов Шейдер вершин задает данные, которые необходимо интерполировать вдоль линии или внутри треугольника (например, нормали и векторы источников света).
996 Часть III OpenGL'следующее поколение После этого шейдер фрагментов выполняет всю основную работу, порождая конеч- ный цвет. Диффузное освещение Напомним, что уравнение диффузного освещения выглядит следующим образом Cchff = Tnax{N • L, 0} * Cmai * G, Итак, нужен шейдер вершин, генерирующий нормаль и векторы источников света Исходные коды необходимых схем приводятся в листингах 23.13 и 23.14 (высокоуров- невый и низкоуровневый варианты, соответственно). При их выполнении генериру- ются интерполируемые величины, необходимые для расчета диффузного освещения Листинг 23.13. Высокоуровневый шейдер, генерирующий интерполируемые величины диффузного освещения // diffuse.vs // // установка интерполируемых величин для диффузного освещения uniform vec3 lightPosO; varying vec3 N, L; void main(void) { // MVP-преобразование вершины // нормаль в пространстве наблюдения N = gl_NormalMatrix * gl_Normal; // вектор источника света в пространстве наблюдения vec4 V = gl_ModelViewMatrix * gl_Vertex; L = lightPosO - V.xyz; // Копируем первичный цвет gl_FrontColor = gl_Color; Листинг 23.14. Низкоуровневый шейдер, генерирующий интерполируемые величины диффузного освещения !IARBvpl.0 # diffuse.vp # # установка интерполируемых величин для ATTRIB iPos = vertex.position; ATTRIB iPrC = vertex.color.primary; ATTRIB iNrm = vertex.normal; OUTPUT oPos = result.position; OUTPUT oPrC = result.color.primary; OUTPUT oTCO = result.texcoord[0]; # выходная текстурная координата 0 OUTPUT oTCl = result.texcoord[1]; # выходная текстурная координата 1 диффузного освещения # входное положение # входной первичный цвет # входная нормаль # выходное положение # выходной первичный цвет
Глава 23 Затенение фрагментов поддерживаем обработку пикселей 997 PARAM mvp[4] = {state.matrix.mvp); # произведение матрицы наблюдения модели на матрицу проекции PARAM mv[4] = {state.matrix.modelview); # матрица наблюдения модели # обратная к транспонированной матрице наблюдения модели: PARAM mvIT[4] = {state.matrix.modelview.invtrans}; PARAM lightPos = program.local[0]; # положение источника света в пространстве наблюдения TEMP V; # временный регистр DP4 oPos.x, iPos, mvp[0]; # преобразуем входное положение матрицей MVP DP4 oPos.y, iPos, mvp[l]; DP4 oPos.z, iPos, mvp[2]; DP4 oPos.w, iPos, mvp[3]; DP4 V.x, iPos, mv[0]; # преобразуем входное положение матрицей MV DP4 V.y, iPos, mv[l]; DP4 V z, iPos, mv[2]; DP4 v.w, iPos, mv[3J; DP3 oTCO.x, iNrm, mvIT[0]; # преобразуем норму в пространство наблюдения DP3 оТСО.у, iNrm, mvITfl]; DP3 oTCO.z, iNrm, mvIT[2]; # помещаем N в текстурную координату О SUB oTCl, lightPos, V; # помещаем вектор источника света в текстурную координату 1 MOV oPrC, iPrC; # первичный цвет копируется с входа на выход END При использовании низкоуровневых шейдеров мы заносим нормаль и вектор ис- точника света в стандартную текстурную координату для последующей интерпо ,я- ции Однако обратите внимание на описательные имена N и L. которые мы дали ин гер- полируемым значениям (в GLSL они называются varyings - переменные) В шейдере фрагментов должны использоваться точно такие же имена В конечном счета данная возможность делает высокоуровневые шейдеры более чигабе шиымп и менее по щер- женными ошибкам Например, по невнимательности при разработке низкоуровневого шейдера можно случайно записать L в текстурную координату 0, гота как шей icp фраз ментов ожидает се в текстурной координате I Разумеется, при компиляции мы не получим никаких ошибок, связанных с зтой опечаткой GLSL же согласовываем переменные автоматически (по имени), во-первых, позво1яя нам об эюм нс saiy- мываться и, во-вторых, делая ненужным введение в код подробных комментариев, объясняющих содержимое каждой! интерполируемой величины Шейдеры фрагментов с реализацией диффузною освещения (листшпи 23 1S и 23 16) лаки изображение, показанное на рис 23 10 В отличие от цвеюв, по- рождаемых отраженным светом, цвета диффузною освещения не меняются быстро вдоль линии или треугольника, поэтому вы скорее всею нс заметите разницы меж- ду изображениями, полученными путем расчета диффузного освещения для вершин и фрагментов Именно поэтому в общем случае гораздо эффективнее выполнять рас-
998 Часть III. OpenGL: следующее поколение Рис. 23.10. Расчет диффузного освещения по фрагментам чет диффузного освещения в шейдере вершин, как мы и поступили в предыдущей । лаве. Ниже мы сделаем это в шейдере фрагментов просто для примера. Листинг 23.15. Высокоуровневый шейдер фрагментов с реализацией диффузного освещения // diffuse.fs // расчет диффузного освещения по пикселям varying vec3 N, L; void main(void) { // выдаем диффузный цвет float intensity = max(0.0, dot(normalize(N), normalize(L))); gl_FragColor = gl_Color; gl_FragColor.rgb *= intensity; Листинг 23.16. Низкоуровневый шейдер фрагментов с реализацией диффузного освещения !IARBfpl.O # diffuse.fp # расчет диффузного освещения по пикселям ATTRIB iPrC = fragment.color.primary; # входной первичный цвет
Глава 23 Затенение фрагментов' поддерживаем обработку пикселей 999 ATTRIB iTCO = fragment.texcoord[0]; ATTRIB iTCl = fragment.texcoord[1]; # нормаль (N) # вектор источника света (L) OUTPUT oPrC = result.color; # выходной цвет TEMP N, L, NdotL; DP3 N.w, iTCO, iTCO; # нормируем нормаль RSQ N.w, N.w; MUL N, iTCO, N.w; DP3 L.w, iTCl, iTCl; # нормируем вектор # источника света RSQ L.w, L.w; MUL L, iTCl, L.w; DP3 NdotL, N, L; # N . L MAX NdotL, NdotL, 0.0; # max(N . L, 0) MUL oPrC.rgb, iPrC, NdotL; # диффузный цвет MOV oPrC.a, iPrC.a; # сохраняем значение альфа END Итак, вначале мы нормируем интерполированную нормаль и векторы источника света Затем — еще одно скалярное произведение, находим максимум, умножаем — и все. Поскольку мы работаем с белым цветом, то можем еще сэкономить на операции умножения на Cli = {1,1,1,1}. Несколько источников отраженного света Мы собираемся рассмотреть отражение света при наличии на сцене нескольких ис- точников света. В связи с этим напомним уравнение отраженного света: Cspec = max{N • Я, 0}Scxp * Cmal * Ct, Помимо вектора нормали для всех трех источников света шейдеры вершин должны сгенерировать интерполируемые значения вектора источника света Вектор биссек- трисы мы будем рассчитывать в шейдере фрагментов, а код шейдеров вершин для трех источников диффузного и отраженного света приводится в листингах 23 17 и 23.18 Листинг 23.17. Высокоуровневый шейдер с тремя источниками света // Slights.vs // установка интерполируемых величин для 3 источников // отражаемого света uniform vec3 lightPosO; uniform vec3 lightPosl; uniform vec3 lightPos2; varying vec3 N, L[3]; void main(void) { // MVP-преобразование вершины gl_Position = gl_ModelViewPro]ectionMatrix * gl_Vertex; vec4 V = gl_ModelViewMatrix * gl_Vertex; // нормаль в пространстве наблюдения
1000 Часть III OpenGL-следующее поколение N = gl_NormalMatnx * gl_Normal; // Векторы источников света L[0] = lightPosO - V.xyz; L[l] = lightPosl - V.xyz; L[2] = lightPos2 - V.xyz; // Copy the primary color gl_FrontColor = gl_Color; Листинг 23.18. Низкоуровневый шейдер вершин стремя источниками света !IARBvpl.0 # Slights.vp # # установка интерполируемых величин для 3 источников # отражаемого света ATTRIB iPos = vertex.position; # входное положение ATTRIB iPrC = vertex.color.primary; # входной первичный цвет ATTRIB iNrm = vertex.normal; # входная нормаль OUTPUT oPos = result.position; # выходное положение OUTPUT oPrC = result.color.primary; # выходной первичный цвет OUTPUT оТСО = result.texcoord[0]; # выходная текстурная координата 0 OUTPUT oTCl = result.texcoord[l]; # выходная текстурная координата 1 OUTPUT oTC2 = result.texcoord[2]; # выходная текстурная координата 2 OUTPUT ОТСЗ = result.texcoord[3]; # выходная текстурная координата 3 PARAM mvp[4] = {state.matrix.mvp); # произведение матрицы наблюдения модели на матрицу проекции PARAM mv[4] = {state.matrix.modelview}; # матрица наблюдения модели # обратная к транспонированной матрице наблюдения модели: PARAM mvIT[4] = {state.matrix.modelview.invtrans}; PARAM lightPosO = program.local[0]; # положение источника света 0 в пространстве наблюдения PARAM lightPosl = program.local[1]; # положение источника света 1 в пространстве наблюдения РАВАМ lightPos2 = program.local[2]; # положение источника света 2 в пространстве наблюдения TEMP V; # временный регистр DP4 oPos.x, iPos, mvp[0]; # преобразовываем входное положение матрицей MVP DP4 oPos.y, iPos, mvp[l]; DP4 oPos z, iPos, mvp[2]; DP4 oPos.w, iPos, mvp[3]; DP4 V.x, iPos, mv[0]; # преобразовываем входное положение матрицей MV DP4 V у, iPos, mv[l]; DP4 V.z, iPos, mv[2]; DP4 V.w, iPos, mv[3];
Глава 23. Затенение фрагментов: поддерживаем обработку пикселей 1001 Рис. 23.11. Расчет диффузного и отраженного света (три источника) по фрагментам DP3 оТСО.х, iNrm, mvIT[0]; # преобразовываем норму в пространство наблюдения DP3 оТСО.у, iNrm, mvIT[l]; DP3 oTCO.z, iNrm, mvIT[2]; # помещаем N в текстурную координату SUB oTCl, lightPosO, V; # помещаем вектор источника света 0 в текстурную координату 1 SUB оТС2, lightPosl, V; # помещаем вектор источника света 1 в текстурную координату 2 SUB оТСЗ, lightPos2, V; # помещаем вектор источника света 2 в текстурную координату 3 MOV oPrC, iPrC; # первичный цвет копируется с входа на выход END Основная нагрузка снова ложится на шейдеры фрагментов. Результат выполнения листингов 23.19 и 23.20 показан на рис. 23.11. Листинг 23.19. Высокоуровневый шейдер фрагментов с тремя источниками диффузного и отраженного света // Slights.fs // //3 источника отражаемого света varying vec3 N, L[3]; void main(void)
1002 Часть III OpenGL-следующее поколение const float specularExp = 128.0; vec3 NN = normalize(N); //Цвета источников света vec3 lighted [ 3 ]; lightCol[0] = vec3(1.0, 0.25, 0.25); lightColfl] = vec3(0.25, 1.0, 0.25); lightCol[2] = vec3(0.25, 0.25, 1.0); gl_FragColor = vec4(0.0); for (int i = 0; i < 3; i++) { vec3 NL = normalize(L[i]); vec3 NH = normalize(NL + vec3(0.0, 0.0, 1.0)); // Накапливаются диффузные вклады gl_FragColor.rgb += gl_Color.rgb * lighted [i] * max(0.0, dot(NN, NL)); 11 Накапливается отраженный свет gl_FragColor.rgb += lighted[i] * pow(max(0.0, dot(NN, NH)), specularExp); ) gl_FragColor.a = gl_Color.a; Листинг 23.20. Низкоуровневый шейдер фрагментов с тремя источниками диффузного и отраженного света !IARBfpl.0 # Slights.fp # # 3 источника отраженного света ATTRIB iPrC = fragment.color.primary; # входной первичный цвет ATTRIB iTCO = fragment.texcoord[0]; # нормаль (N) ATTRIB iTCl = fragment.texcoord[l]; # вектор источника # света (L) 0 ATTRIB iTC2 = fragment.texcoord[2]; # вектор источника # света (L) 1 ATTRIB iTC3 = fragment.texcoord[3]; # вектор источника # света (L) 2 OUTPUT oPrC = result.color; # выходной цвет PARAM lightColO ={1.0,0.25,0.25,1.0); # цвет источника света 0 PARAM lightColl ={0.25,1.0,0.25,1.0}; # цвет источника света 1 PARAM lightCol2 ={0.25,0.25,1.0,1.0); # цвет источника света 2 TEMP N, L, H, NdotL, NdotH, finalcolor; ALIAS diffuse = NdotL; ALIAS specular = NdotH; DP3 N.w, iTCO, 1TC0; # нормируем нормаль RSQ N.w, N.w; MUL N, iTCO, N.w; DP3 L.w, iTCl, 1TC1; # нормируем вектор # источника света 0 RSQ L.w, L.w; MUL L, iTCl, L.w;
Глава 23 Затенение фрагментов поддерживаем обработку пикселей 1003 ADD Н, L, {0, 0, 1}; # вектор биссектрисы DP3 H.w, Н, Н; RSQ H.w, Н w; MUL Н, Н, H.w; # нормируем его DP3 NdotL, N, L; # N . L0 MAX NdotL, NdotL, 0.0; # max(N . L, 0) MUL diffuse, iPrC, NdotL; MUL finalColor, diffuse, lightColO; # диффузный цвет DP3 NdotH, N, H; # N . НО MAX NdotH, NdotH, 0.0; # max(N . H, 0) POW specular, NdotH.x, 128.0.x; # NdotHA128 MAD finalColor, specular, lightColO, finalColor; DP3 L w, 1TC2, 1TC2; RSQ L.w, L.w; MUL L, 1TC2, L.w; # нормируем вектор # источника света 1 ADD H, L, {0, 0, 1}; # вектор биссектрисы DP3 H w, H, H; RSQ H.w, H.w; MUL H, H, H w; # нормируем его DP3 NdotL, N, L; # N L1 MAX NdotL, NdotL, 0.0; # max(N . L, 0) MUL diffuse, iPrC, NdotL; # диффузный цвет MAD finalColor, diffuse, lightColl, finalColor; DP3 NdotH, N, H; # N . Hl MAX NdotH, NdotH, 0.0; # max(N . Н, 0) POW specular, NdotH.x, 128.0 x; # NdotHA128 MAD finalColor, specular, lightColl, finalColor; DP3 L.w, 1TC3, 1TC3; RSQ L.w, L.w; MUL L, 1TC3, L.w; # нормируем вектор # источника света 2 ADD H, L, {0, 0, 1); # вектор биссектрисы DP3 H.w, H, H; RSQ H w, H.w; MUL H, H, H.w; # нормируем его DP3 NdotL, N, L; # N . L2 MAX NdotL, NdotL, 0.0; # max(N . L, 0) MUL diffuse, iPrC, NdotL; # диффузный цвет MAD finalColor, diffuse, lightCol2, finalColor; DP3 NdotH, N, H; # N . Н2 MAX NdotH, NdotH, 0.0; # max(N . Н, 0) POW specular, NdotH.x, 128.0.x; # NdotHA128 MAD oPrC.rgb, specular, lightCol2, finalColor; MOV oPrC.a, END iPrC.a; # сохраняем значение альфа На этот раз мы присвоили всем трем источникам света разный цвет, поэтом} теперь умножать на lightColn (Cli) обязательно Как и ранее, заметна очевидная нехватка циклов в низкоуровневых схемах, из-за чего второй листинг выглядит болс<. чем в три раза длиннее
1004 Часть III OpenGL'следующее поколение Процедурное наложение текстуры Можно ли отобразить текстуру на объект, нс используя текстуру'’ Можно, если ис- по 1ьзовать процедурные карты текстуры Данная зехнология позволяет соотносить с поверхностью объектов цвета или другие свойства точно так же, как при использо- вании обычных карт текстуры В атом случае текстурное изображение загружается в OpenGL с помощью glTexImage, затем в шейдере фрагментов выполняется по- иск текстуры Однако при процедурном наложении текстуры вы пропускаете этапы зат'ру зки и поиска текстуры, вместо этого алгоритмически описывая, как должна вы- глядеть т скстура Процедурное наложение текстуры обладает как достоинствами, так и недостатка- ми Один из его плюсов заключается в том, что вес требования к памяти для хранения 01 раничиваются несколькими командами шейдера, а нс мегабайтами кэша текстуры и/или системной памяти, используемых обычными текстурами Это позволяет осво- бодить память для других нужд, например, задействовав се в обработке буферных объектов (обсуждаются в главе 16, “Буферные объекты это ваша видеопамять, и вы управляете сю1") Другим преимуществом является практически безграничное разрешение Подоб- но векторным рисункам в сравнении с растровыми изображениями, процедурные текстуры масштабируются до любого размера без потери качества Чтобы улучшить качество обычных текстур, требуется увеличивать размеры текстурного изображе- ния В конце концов вы упретесь в аппаратные пределы Единственным же аппарат- ным ограничением, существенным при процедурном наложении текстуры, является точность представления с плавающей запятой процессоров шейдеров — для нужд OpenGL минимальная необходимая точность составляет 24 бит Недостатком процедурных карт текс гуры (а также причиной, объясняющей редкое их использование) является то, что чем сложнее текстура, которую вы желаете пред- ставить, тем сложнее будет и шейдер фрагментов С помощью процедурных текстур из простых форм и цветов вы можете сконструировать все — плазму, огонь, дым, мра- мор, все, что требуется, — ввести в схему затенения немалое число команд Иногда, правда, в сцену нужно ввести очень простую текстуру — логотип компании, снимок со сну шика или чье-то лицо, поэтому не расстраивайтесь - привычным текстурам всегда пандезся применение' Шахматная текстура Все, хватит обсуждений Разберем для разминки процедурную текстуру в виде трех- мерной шахмат ной доски Наш объект будет как бы вырезан из блока чередующихся бе 1ых и черных кубов Звучит достаточно просто, нс правда ли’ Итак д 1я каж юю фрагмента мы, согтасно положению в пространстве объекта, >адаем iibci ( ie ювагечьно, нужен шейдер фрагментов, который помимо обычного преобразования по южения в пространстве объекта в усеченное пространство также копирова । бы эго положение в интерполируемую величину (чтобы оно стало доступ- но 11я шейтсра фрагментов) Если уж на то пошло, можно заодно добавить диффуз- ный и отраженный свет, а значит, шейдер вершин должен еще выдавать нормаль и вектор нс очника света
Глава 23 Затенение фрагментов поддерживаем обработку пикселей 1005 Высоко- и низкоуровневые версии обрисованного выше шейдера вершин пред- ставлены в листингах 23.21 и 23 22, соответственно. Данная схема будет использована во всех трех последующих примерах процедурного наложение текстуры. Листинг 23.21. Высокоуровневый шейдер с процедурным наложением текстуры // checkerboard.vs // // Типичное преобразование вершины, // копирование положение в пространстве объекта //и векторов освещения в интерполируемые величины uniform vec3 lightPos; varying vec3 N, L, V; void main(void) 1 // Типичное MVP-преобразование gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; // Отображаем положение в пространстве объекта //на единичную сферу V = gl_Vertex.xyz; // нормаль в пространстве наблюдения N = gl_NormalMatrix * gl_Normal; // вектор источника света в пространстве наблюдения vec4 Veye = gl_ModelViewMatrix * gl_Vertex; L = lightPos - Veye.xyz; Листинг 23.22. Низкоуровневый шейдер вершин с процедурным наложением текстуры !lARBvpl.O # checkerboard.vp # # Типичное преобразование вершины # копирование положение в пространстве объекта # векторов источников света в интерполируемые величины ATTRIB iPos = vertex.position; # входное положение ATTRIB iNrm = vertex.normal; # входная нормаль OUTPUT oPos = result.position; # выходное положение OUTPUT oTCO = result.texcoord[0]; # выходная текстурная # координата 0: N OUTPUT oTCl = result.texcoordl1]; # выходная текстурная # координата 1: L OUTPUT oTC2 = result.texcoord[2]; # выходная текстурная # координата 2: V PARAM mvp[4] = {state.matrix.mvp); # умножение матрицы наблюдения модели на матрицу проекции PARAM mv[4] = {state.matrix.modelview}; # матрица # наблюдения модели # обратная к транспонированной матрице наблюдения модели: PARAM mvIT[4] = {state.matrix.modelview.invtrans);
1006 Часть III. OpenGL: следующее поколение Рис. 23.12. На приведенной схеме иллюстрируется присвоение блокам фрагментов чередующихся цветов PARAM lightPos = program.local[0] ; # положение источника света в пространстве наблюдения TEMP V; # временный регистр DP4 oPos.x, iPos, mvp[0]; # преобразуем входное положение матрицей MVP DP4 oPos.у, iPos, mvp[1]; DP4 oPos.z, iPos, mvp[2]; DP4 oPos.w, iPos, mvp[3]; DP4 V.x, iPos, mv[0]; # преобразуем входное положение матрицей MV DP4 V.y, iPos, mv[l]; DP4 V.z, iPos, mv[2]; DP4 V.w, iPos, mv[3]; DP3 oTCO.x, iNrm, mvIT[0]; # преобразуем норму в пространство наблюдения DP3 оТСО.у, iNrm, mvIT[1]; DP3 оТСО.z, iNrm, mvIT[2]; # помещаем N в текстурную координату SUB oTCl, lightPos, V; # помещаем вектор источника в текстурную координату 1 MOV оТС2, iPos; # помещаем положение объекта в текстурную координату 2 END Проиллюстрируем процедурное наложение текстуры на примере сферы. Ее размер значения не имеет, поскольку в начале шейдера фрагментов мы нормируем положение в пространстве объекта. Это означает, что все положения, с которыми мы будем работать в шейдере фрагментов, принадлежат диапазону [—1,1]. Для нашей цели в шейдере фрагментов область, по трем осям определяемая интер- валами [—1,1], разбивается вдоль каждой оси на восемь блоков. Блокам поочередно присваиваются значения 0 и 1, как показано на рис. 23.12. Если сумма трех компо- нентов нечетная, кубик закрашивается черным, в противном случае — белым. На рис. 23.13 показан результат выполнения кода из листингов 23.23 и 23.24 — алгоритма процедурного наложения шахматной текстуры.
Глава 23. Затенение фрагментов: поддерживаем обработку пикселей 1007 Рис. 23.13. Трехмерная шахматная доска генерируется без использования текстурных изображений Листинг 23.23. Высокоуровневый шейдер фрагментов с шахматной текстурой // checkerboard.fs // // Трехмерная шахматная сетка varying vec3 V; // положение в пространстве объекта varying vec3 N; // нормаль в пространстве наблюдения varying vec3 L; // вектор источника света // в пространстве наблюдения const vec3 onColor = vec3(1.0, 1.0, 1.0); const vec3 offColor = vec3(0.0, 0.0, 0.0); const float ambientLighting = 0.2; const float specularExp = 60.0; const float specularIntensity = 0.75; const int numSquaresPerSide = 8; void main (void) { // Нормируем векторы vec3 NN = normalize(N); vec3 NL = normalize(L); vec3 NV = normalize(V); vec3 NH = normalize(NL + vec3(0.0, 0.0, 1.0)); // Отображаем -1,1 в 0,numSquaresPerSide vec3 onOrOff = ((NV + 1.0) * float(numSquaresPerSide)) / 2.0; // mod 2 >= 1 onOrOff = step(1.0, mod(onOrOff, 2.0)); // трехходовое применение исключающего ИЛИ onOrOff.х = step(0.5,
1008 Часть III OpenGL следующее поколение mod(onOrOff,х + onOrOff.y + onOrOff.z, 2.0)); // шахматная сетка vec3 surfColor = mix(offColor, onColor, onOrOff.x); // расчет диффузного освещения + 20% рассеянного света surfColor *= (ambientLighting + vec3(max(0.0, dot(NN, NL)))); // расчет отраженного света с 75% интенсивности surfColor += (specularlntensity * vec3(pow(max(0.0, dot(NN, NH)), specularExp))); gl_FragColor = vec4(surfColor, 1.0); Листинг 23.24. Низкоуровневый шейдер фрагментов с шахматной текстурой ''ARBfpl.O # checkerboard.fp # # Трехмерная шахматная сетка ATTRIB N = fragment.texcoord[0]; ATTRIB L = fragment.texcoord[1]; ATTRIB V = fragment.texcoord[2]; # положение в пространстве объекта OUTPUT oPrC = result.color; # выходной цвет PARAM onColor = {1 0, 1 0, 1.0, 1.0}; PARAM offColor = {0.0, 0.0, 0.0, 1.0}; # 0.25 * число квадратов на сторону, окружающий свет # коэффициент отражения, интенсивность отраженного света PARAM misc = (2.0, 0.2, 60.0, 0.75}; TEMP NV, NN, NL, NH, NdotL, NdotH, surfColor, onOrOff; ALIAS specular = NdotH; DP3 NV.w, V, V; # нормируем положение вершины RSQ NV.w, NV w; MUL NV, V, NV w; # Отображаем положение из -1,1 в 0,numSquaresPerSide/2 MAD onOrOff, NV, misc.x, misc.x; # Операция по модулю 2 путем удвоения FRC, # затем из результат вычитается 1 для сравнения >= 1 FRC onOrOff, onOrOff; MAD onOrOff, onOrOff, 2 0, -1.0; CMP onOrOff, onOrOff, 0.0, 1 0; # Применяем операцию исключающего ИЛИ, суммируя значения- # координаты по трем осям # Затем снова применяем операцию по модулю 2 DP3 onOrOff, onOrOff, 1.0; MUL onOrOff, onOrOff, 0.5; FRC onOrOff, onOrOff; MAD onOrOff, onOrOff, 2.0, -1.0; CMP onOrOff, onOrOff, 0.0, 1.0; # шахматная сетка LRP surfColor, onOrOff, onColor, offColor; DP3 NN.w, N, N, # нормируем нормаль RSQ NN w, NN.w;
Глава 23 Затенение фрагментов: поддерживаем обработку пикселей 1009 MUL NN, N, NN.w; DP3 NL.w, L, L; # нормируем вектор источника света RSQ NL.w, NL.w; MUL NL, L, NL.w; ADD NH, NL, {0, 0, 1}; DP3 NH.w, NH, NH; RSQ NH.w, NH.w; MUL NH, NH, NH.w; # диффузное освещение DP3 NdotL, NN, NL; MAX NdotL, NdotL, 0.0; ADD NdotL, NdotL, misc.y; # 20% рассеянного света MUL surfColor, surfColor, NdotL; DP3 NdotH, NN, NH; MAX NdotH, NdotH, 0.0; POW specular, NdotH.x, misc.z; MAD oPrC, misc.w, specular, surfColor; # 75% интенсивности отраженного света END # вектор биссектрисы # нормируем его # N . L # max(N . L, 0) # диффузный цвет # отраженный свет # N . Н # max(N . Н, 0) # NdotHA60 Язык GLSL имеет встроенную функцию “по модулю” (mod), с помощью которой мы и получаем чередующиеся блоки. Однако в низкоуровневом шейдере те же дей- ствия по модулю 2 реализовать немного сложнее. Мы берем значение, делим его на 2, с помощью команды FRC извлекаем дробную часть, после чего умножаем результат на 2. В конечном счете мы получаем значение, принадлежащее диапазону [0,2]. После этого нужно определить, к какому именно промежутку относится значе- ние — [0,1] или [1, 2]. В GLSL для этого применяется функция step, которая возвра- щает 1, если второй аргумент больше или равен первому, и — 0 в противном случае. В низкоуровневом шейдере используется команда СМР, но поскольку она сравнивает первый аргумент с 0, а не с 1, мы предварительно вычитаем 1 из аргумента. Теперь у нас для каждой оси имеется значение 0 или 1. Далее мы суммируем все эти значения (их 3 — по числу осей) и к результату снова применяем операцию по модулю 2 и сравнение. Таким образом, исходя из четности результата, мы можем присваивать фрагментам цвета — черный или белый. В высокоуровневой схеме для этого применяется команда mix, в низкоуровневой — LRP. В приведенных схемах затенения можно легко изменить цвета “шахматного куби- ка” и число блоков, на которые разбивается строка. Экспериментируйте' Текстура “пляжный мяч” В следующем примере мы превратим сферу в пляжный мяч Он будет состоять из восьми продольных полосок чередующихся первичных цветов Северный и южный “полюсы” мяча мы закрасим белым цветом. Вперед' Посмотрим на мяч сверху Разделим его верхнюю часть на три полупростран- ства север-юг, северо-восток-юго-запад и северо-запад-юго-восток (см рис. 23.14).
1010 Часть III. OpenGL: следующее поколение Рис. 23.14. Вид мяча сверху; показано присвоение цветов Вначале северные фрагменты раскрашиваются в насыщенный красный цвет, в юж- ных красный цвет отсутствует. К двум фрагментам, принадлежащим одновременно юго-восточному и северо-восточному полупространствам, прибавляется насыщенный зеленый цвет, все остальные фрагменты зеленого не получают. Обратите внимание на то, что места, где красный цвет перекрывается с зеленым, становятся желтыми. На- конец, все фрагменты, принадлежащие юго-западному полупространству, получают насыщенный синий цвет. На востоке полоски красиво переходят от красной к желтой, зеленой и синей. А что происходит “на западе”? Здесь проще всего скопировать восточные фрагменты, повернув их на 180°. Отметим, что мы смотрим на мяч сверху, по положительному направлению оси у. Если координата х положения в пространстве объекта больше или равна 0, положение используется как есть. Если же координата меньше 0, мы берем положения по оси х и z со знаком “минус”, т.е. зеркально отображаем исходное положение на противоположную сторону пляжного мяча. Белые шапки на полюсах добавляются отдельно. После раскрашивания всего остального мяча, в точках, где модуль координаты у близок к 1, мы заменяем цвет на белый. Шейдеры, при выполнении которых изображается пляжный мяч (рис. 23.15), приведены в листингах 23.25 и 23.26. Листинг 23.25, Высокоуровневый шейдер с реализацией пляжного мяча // beachball.fs // // Продольные полоски и шапки на полюсах varying vec3 V; // положение в пространстве объекта varying vec3 N; // нормаль в пространстве наблюдения varying vec3 L; // вектор источника света в пространстве // наблюдения const vec3 myRed = vec3(1.0, 0.0, 0.0); const vec3 myYellow = vec3(1.0, 1.0, 0.0); const vec3 myGreen = vec3(0.0, 1.0, 0.0); const vec3 myBlue = vec3(0.0, 0.0, 1.0); const vec3 myWhite = vec3(1.0, 1.0, 1.0);
Глава 23. Затенение фрагментов: поддерживаем обработку пикселей 1011 Рис. 23.15. Пляжный мяч, построенный с нуля! const vec3 myBlack = vec3(0.0, 0.0, 0.0); const vec3 northHalfSpace = vec3(0.0, 0.0, 1.0); const vec3 northeastHalfSpace = vec3(0.707, 0.0, 0.707); const vec3 northwestHalfSpace = vec3(-0.707, 0.0, 0.707); const float capsize = 0.03; const float smoothEdgeTol = 0.005; const float ambientLighting = 0.2; const float specularExp = 60.0; const float specularlntensity = 0.75; void main (void) { // Нормируем векторы vec3 NN = normalize(N); vec3 NL = normalize(L); vec3 NH = normalize(NL + vec3(0.0, 0.0, 1.0)); vec3 NV = normalize(V); // Зеркально отображаем половину мяча относительно осей X и Z float mirror = (NV.x >= 0.0) ? 1.0 : -1.0; NV.xz *= mirror; // Проверяем, к какому полупространству относится точка: // С/Ю, В/3, СВ/ЮЗ, СЗ/ЮВ vec4 distance; distance.х = dot(NV, northHalfSpace); distance.у = dot(NV, northeastHalfSpace); distance.z = dot(NV, northwestHalfSpace); // Подготовка белых шапок на полюсах distance.w = abs(NV.y) - 1.0 + capsize;
1012 Часть III OpenGL: следующее поколение distance = smoothstep(vec4(0.0),vec4(smoothEdgeTol),distance); // Полоски: красная, зеленая, красная+зеленая=желтая, синяя vec3 surfColor = mix(myBlack, myRed, distance.x); surfColor += mix(myBlack,myGreen,distance.у*(1.О-distance.z)); surfColor = mix(surfColor, myBlue, 1.0-distance.у); // Белые шапки на полюсах surfColor = mix(surfColor, myWhite, distance.w); // расчет диффузного освещения + 20% рассеянного света surfColor *= (ambientLighting + vec3(max(0.0, dot(NN, NL)))); // расчет отраженного света с 75% интенсивности surfColor += (specularlntensity * vec3(pow(max(0.0, dot(NN, NH)), specularExp))); gl_FragColor = vec4(surfColor, 1.0); Листинг 23.26. Низкоуровневый шейдер с реализацией пляжного мяча !IARBfpl.0 # beachball.fp # # Продольные полоски и шапки на полюсах ATTRIB N = fragment.texcoord[0]; ATTRIB L = fragment.texcoord[l]; ATTRIB V = fragment.texcoord[2]; # положение в пространстве объекта OUTPUT oPrC = result.color; # выходной цвет PARAM myRed = {1.0, 0.0, 0.0, 1.0); PARAM myYellow = {1.0, 1.0, 0.0, 1.0); PARAM myGreen = {0.0, 1.0, 0.0, 1.0}; PARAM myBlue = {0.0, 0.0, 1.0, 1.0}; PARAM myWhite = {1.0, 1.0, 1.0, 1.0}; PARAM myBlack = {0.0, 0.0, 0.0, 1.0); PARAM northHalfSpace = {0.0, 0.0, 1.0}; PARAM northeastHalfSpace = {0.707, 0.0, 0.707}; PARAM northwestHalfSpace = {-0.707, 0.0, 0.707}; # Размер шапки минус один, рассеянный свет, # коэффициент отражения, интенсивность отражения PARAM misc = {-0.97, 0.2, 60.0, 0.75}; TEMP NV, NN, NL, NH, NdotL, NdotH, surfColor, distance, mirror; ALIAS specular = NdotH; ALIAS redColor = NV; DP3 NV.w, V, V; # нормируем положение вершины RSQ NV.w, NV.w; MUL NV, V, NV.w; # Зеркально отображаем половину мяча относительно осей X и Z CMP mirror, NV.x, -1.0, 1.0; MUL NV.xz, NV, mirror; # Проверяем, к какому полупространству относится точка: # С/Ю, В/3, СВ/ЮЗ, СЗ/ЮВ DP3 distance.х, NV, northHalfSpace; DP3 distance.у, NV, northeastHalfSpace; DP3 distance.z, NV, northwestHalfSpace; # Подготовка белых шапок на полюсах
Глава 23 Затенение фрагментов поддерживаем обработку пикселей 1013 ABS distance.w, NV.у; ADD distance.w, distance.w, misc.x; CMP distance, distance, 0.0, 1.0; # Полоски: красная, зеленая, красная+зеленая=желтая, синяя LRP redColor, distance.х, myRed, myBlack; MAD distance.z, -distance.y, distance.z, distance.y; LRP surfColor, distance.z, myGreen, myBlack; ADD surfColor, surfColor, redColor; SUB distance.y, 1.0, distance.y; LRP surfColor, distance.y, myBlue, surfColor; # Белые шапки на полюсах LRP surfColor, distance.w, myWhite, surfColor; DP3 NN.w, N, N; # нормируем нормаль RSQ NN.w, NN.w; MUL NN, N, NN.w; DP3 NL.w, L, L; # нормируем вектор # источника света RSQ NL.w, NL.w; MUL NL, L, NL.w; ADD NH, NL, {0, 0, 1}; # вектор биссектрисы DP3 NH.w, NH, NH; # нормируем его RSQ NH.w, NH.w; MUL NH, NH, NH.w; # диффузное освещение DP3 NdotL, NN, NL; # N . L MAX NdotL, NdotL, 0.0; # max(N . L, 0) ADD NdotL, NdotL, misc.y; # 20% рассеянного света MUL surfColor, surfColor, NdotL; # умножаем на диффузный цвет # отраженный свет DP3 NdotH, NN, NH; # N . Н MAX NdotH, NdotH, 0.0; # max(N . Н, 0) POW specular, NdotH.к, misc.z; # NdotHA60 MAD oPrC, misc.w, specular, surfColor; # 75% интенсивности отраженного света END Отобразив, как описывалось выше, все точки с отрицательной координатой х, с помощью скалярного произведения определяем, к каким полупространствам при- надлежит точка с текущими координатами в пространстве объекта По знаку скаляр- ного произведения мы узнаем, с какой стороны разделяющей плоскости лежит точка В низкоуровневом шейдере для сравнения применяется команда СМР (больше или равно 0). В схеме GLSL мы на этот раз не используем встроенную функцию step. Вместо нее мы представляем новую улучшенную версию — smoothstep Вместо резкого перехода от 0 к 1 на границе полупространств, smoothstep поз- воляет формировать вблизи краев гладкие переходы (значения между 0 и 1) Запуская попеременно высокоуровневую и низкоуровневую версии программы, вы можете убе- диться, насколько лучше smoothstep сглаживает зазубренные границы
1014 Часть III. OpenGL: следующее поколение Рис. 23.16. На схеме показано, как с помощью пятикратного разделения пространства определяется, находится ли фрагмент внутри звезды Текстура “игрушечный мяч” В качестве последнего примера процедурного наложения текстуры преобразуем сфе- ру в детский мячик, не используя традиционные текстурные изображения. Рисунок мячика будет образован красной звездой на желтом фоне, окруженной синей полос- кой. Все соответствующие построения будут рассмотрены в шейдере фрагментов. Очевидно, основную сложность представляет изображение звездочки. Для каж- дого фрагмента шейдер должен определить, принадлежит ли он звездочке (в этом случае фрагмент закрашивается красным) или находится снаружи (в этом случае ис- пользуется желтый цвет). Для принятия решения вначале определяется, к каким пяти полупространствам относится фрагмент (см. рис. 23.16). Любой фрагмент, находящийся внутри по крайней мере четырех из пяти полупро- странств, принадлежит звезде. Вначале мы используем счетчик —3 и увеличиваем его значение на 1 всякий раз, когда фрагмент находится внутри полупространства. Далее мы ограничиваем это значение согласно допустимому диапазону [0,1]. Величина 0 указывает, что фрагмент расположен вне звезды и должен закрашиваться желтым, 1 — что фрагмент попал внутрь звезды и его нужно закрасить красным. Синяя полоска добавляется точно так же, как белые шапки в предыдущем приме- ре — вручную в самом конце. На этот раз мы перерисовываем фрагменты не возле краев мяча, а ближе к центру вдоль оси z. Результат выполнения шейдеров, рисующих игрушечный мяч (листинги 23.27 и 23.28), показан на рис. 23.17.
Глава 23. Затенение фрагментов: поддерживаем обработку пикселей 1015 Рис. 23.17. С помощью шейдеров можно описывать сравнительно сложные формы Листинг 23.27. Высокоуровневый шейдер с реализацией игрушечного мяча // toyball.fs // На основе шейдера Билла Лайси-Кейна (Bill Licea-Kane) varying vec3 V; // положение в пространстве объекта varying vec3 N; // нормаль в пространстве наблюдения varying vec3 L; // вектор источника света // в пространстве наблюдения const vec3 myRed = vec3(0.6, 0.0, 0.0); const vec3 myYellow = vec3(0.6, 0.5, 0.0); const vec3 myBlue = vec3(0.0, 0.3, 0.6); const vec3 myHalfSpaceO = vec3(0.31, 0.95, 0.0); const vec3 myHalfSpacel = vec3(-0.81, 0.59, 0.0); const vec3 myHalfSpace2 = vec3(-0.81, -0.59, 0.0); const vec3 myHalfSpace3 = vec3(0.31, -0.95, 0.0); const vec3 myHalfSpace4 = vec3(1.0, 0.0, 0.0); const float stripeThickness = 0.4; // 0 в 1 const float starSize =0.2; // 0 в 0.3 const float smoothEdgeTol = 0.005; const float ambientLighting = 0.2; const float specularExp = 60.0; const float specularlntensity = 0.5; void main (void) { vec4 distVector; float distScalar;
1016 Часть III OpenGL следующее поколение II Нормируем векторы vec3 NN = normalize(N); vec3 NL = normalize(L); vec3 NH = normalize(NL + vec3(0.0, 0.0, 1.0)); vec3 NV = normalize(V); // Каждая сторона звезды определяет полупространство. //К внутренней части звезды относятся точки, находящиеся // внутри по крайней мере 4 из 5 таких подпространств. // Подсчет начинается со значения -3, чтобы при добавлении // необходимых 4 получалось 1 float mylnOut = -3.0; // Всего нужно найти 5 скалярных произведений - по одному // для каждой стороны звезды // Первые 4 из них заносятся в вектор, пятое - в скаляр. distVector.х = dot(NV, myHalfSpaceO); distVector.у = dot(NV, myHalfSpacel); distVector.z = dot(NV, myHalfSpace2); distVector.w = dot(NV, myHalfSpace3); distScalar = dot(NV, myHalfSpace4); // Все плоскости, разрезающие пространство, пересекаются //в начале координат. Чтобы звезда имела ненулевой размер, // эти плоскости нужно сместить distVector += starsize; distScalar += starSize; distVector = smoothstep(0.0, smoothEdgeTol, distVector); distScalar = smoothstep(0.0, smoothEdgeTol, distScalar); mylnOut += dot(distVector, vec4(1.0)); mylnOut += distScalar; mylnOut = clamp(mylnOut, 0.0, 1.0); 11 Красная звезда на желтом фоне vec3 surfColor = mix(myYellow, myRed, mylnOut); 11 Синяя полоска посредине mylnOut = smoothstep(0.0, smoothEdgeTol, abs(NV.z) - stripeThickness); surfColor = mix(myBlue, surfColor, mylnOut); // расчет диффузного освещения + 20% рассеянного света surfColor *= (ambientLighting + vec3(max(0.0, dot(NN, NL)))); // расчет отраженного света + 50% интенсивности surfColor += (specularIntensity * vec3(pow(max(0.0, dot(NN, NH)), specularExp))); gl_FragColor = vec4(surfColor, 1.0); Листинг 23.28. Низкоуровневый шейдер с реализацией игрушечного мяча !IARBfpl.0 # toyball.fp # # На основе шейдера Билла Лайси-Кейна (Bill Licea-Kane) ATTRIB N = fragment,texcoord[0]; ATTRIB L = fragment.texcoord[1];
Глава 23 Затенение фрагментов- поддерживаем обработку пикселей 1017 ATTRIB V = fragment.texcoord[2]; # положение в пространстве # объекта # выходной цвет OUTPUT oPrC = result.color; PARAM myRed = {0.6, 0.0, 0.0, 1.0}; PARAM myYellow = {0.6, 0.5, 0.0, 1.0}; PARAM myBlue = {0.0, 0.3, 0.6, 1.0}; PARAM myHalfSpaceO = {0.31, 0.95, 0.0}; PARAM myHalfSpacel = {-0.81, 0.59, 0.0}; PARAM myHalfSpace2 = {-0.81, -0.59, 0.0}; PARAM myHalfSpace3 = {0.31, -0.95, 0.0}; PARAM myHalfSpace4 = {1.0, 0.0, 0.0}; # Учитываем толщину полоски, размер звезды и рассеянное освещение # коэффициент отраженного света, интенсивность отраженного света PARAM misc = {0.4, 0.2, 60.0, 0.5}; TEMP NV, NN, NL, NH, NdotL, NdotH, surfColor, distance, mylnOut; ALIAS specular = NdotH; DP3 NV.w, V, V; # нормируем положение вершины RSQ NV.w, NV.w; MUL NV, V, NV.w; # Каждая сторона звезды определяет полупространство. # К внутренней части звезды относятся точки, находящиеся внутри # по крайней мере 4 из 5 таких подпространств. # Подсчет начинается со значения -3, чтобы при добавлении # необходимых 4 получалось 1 MOV mylnOut, -3.0; # Всего нужно найти 5 скалярных произведений - по одному # для каждой стороны звезды # Первые 4 из них заносятся в вектор, пятое - в скаляр # (вместе с синей полоской). DP3 distance.х, NV, myHalfSpaceO; DP3 distance.у, NV, myHalfSpacel; DP3 distance.z, NV, myHalfSpace2; DP3 distance.w, NV, myHalfSpace3; # Все плоскости, разрезающие пространство, пересекаются # в начале координат. Чтобы звезда имела какой-то размер, # нужно эти плоскости сместить. ADD distance, distance, misc.y; CMP distance, distance, 0.0, 1.0; DP4 distance, distance, 1.0; ADD mylnOut, mylnOut, distance; # Задается последний край звезды и синяя полоска DP3 distance.x, NV, myHalfSpace4; ADD distance.x, distance.x, misc.y; ABS distance.у, NV.z;
1018 Часть III OpenGL следующее поколение SUB distance.у, distance.у, misc.x; CMP distance, distance, 0.0, 1.0; ADD_SAT mylnOut, mylnOut, distance.x; # Красная звезда на желтом фоне LRP surfColor, mylnOut, myRed, myYellow; # Синяя полоска посредине LRP surfColor, distance.у, surfColor, myBlue; DP3 NN.w, N, N; # нормируем нормаль RSQ NN.w, NN w; MUL NN, N, NN.w; DP3 NL.w, L, L; # нормируем вектор # источника света RSQ NL.w, NL.w; MUL NL, L, NL.w; ADD NH, NL, {0, 0, 1}; # вектор биссектрисы DP3 NH.w, NH, NH; # нормируем его RSQ NH.w, NH.w; MUL NH, NH, NH.w; # диффузное освещение DP3 NdotL, NN, NL; # N . L MAX NdotL, NdotL, 0.0; max(N . L, 0) ADD NdotL, NdotL, misc.y; # 20% рассеянного света MUL surfColor, surfColor, NdotL; # умножаем на код # диффузного цвета # отраженный свет DP3 NdotH, NN, NH; # N . Н MAX NdotH, NdotH, 0.0; # max(N . H, 0) POW specular, NdotH x, misc.z; # NdotHA60 MAD oPrC, misc.w, specular, surfColor; # 50% интенсивности # отраженного света END Границы разделения полупространств проходят через центр сферы При изобра- жении пляжного мяча это нас устраивало, но сейчас нужно немного сместить их от центра Поэтому к скалярным произведения мы добав 1ясм небольшие величины (чем они больше, тем больше получится звезда) Как и ранее, при проверке “внутри/снаружи” в шейдере GLSL используется коман- да smoothstep, а в низкоуровневой схеме — команда СМР Из соображений эффек- тивности мы помещаем результаты, касающиеся первых четырех полупространств, в четырехкомпонентный вектор Благодаря этому можно просуммировать эти четы- ре компонента, найдя скалярное произведение данного вектора с вектором 1,1,1,1 Пятое значение записывается и прибавляется к остальным четырем отдельно, по- скольку пятикомпонентные векторы у нас не определены (В принципе, такой тип можно создать самостоятельно, но в большинстве реализаций за это придется пла- тить снижением производительности, а нам это не нужно )
Глава 23 Затенение фрагментов поддерживаем обработку пикселей 1019 Если вы хотите поиграть с приведенной схемой затенения, попробуйте решить такую задачу. Преобразуйте звезду в шестиконечную, добавив еще одну разделяю- щую плоскость и соответствующим образом выровняв остальные. Покажите, каким должно быть число полупространств, к которым принадлежит фрагмент, чтобы он находился внутри звезды, а также уточните исходное значение счетчика mylnOut Резюме Возможности шейдеров вершин и фрагментов ограничены только вашим воображе- нием. Мы привели лишь несколько примеров, которые должны подстегнуть ваше творчество и от которых можно отталкиваться при разработке собственных шейде- ров. Вы можете спокойно брать любые программы, разобранные в данной книге и приведенные на компакт-диске, расчленять их и модернизировать, исследовать и находить лучшие пути. Помните: основная цель авторов — “научить делать симпа- тичные картинки”. Поэтому — дерзайте'

ПРИЛОЖЕНИЕ А Что еще почитать? Трехмерная графика реального времени и OpenGL — весьма популярные темы, а огромный объем информации по ним невозможно вместить в одну книгу. По мере развития навыков и увеличения знаний вам могут пригодиться следующие книги. Другие хорошие книги по OpenGL OpenGL Programming Guide, 4th Edition: The Official Guide to Learning OpenGL, Ver- sion 1 4. OpenGL Architecture Review Board, Dave Shreiner, Mason Woo, Jackie Neider, and Tom Davis. Addison-Wesley, 2003.// OpenGL. Официальное руководство програм- миста. By Мейсон, Нейдер Джеки, Девис Том, Шрайнер Дейв. ДиаСофтЮП, 2002. OpenGL Programming for the X Window System. Mark J. Kjlgard. Addison-Wesley, 1996 Interactive Computer Graphics: A Top-Down Approach with OpenGL, 3rd Edition. Ed- ward Angel. Addison-Wesley, 2002./I Интерактивная компьютерная графика Вводный курс на базе OpenGL, 2-е издание. Эдвард Энджел. Вильямс 2001. The OpenGL Extensions Guide. Erie Lengyel Charles River Media, 2003. OpenGL Shading Language. Randi J. Rost. Addison-Wesley, 2004.// OpenGL Трех- мерная графика и язык программирования шейдеров Для профессионалов Рэнди Дж Рост. Питер, 2005 Книги по трехмерной графике 3D Computer Graphics Alan Watt Addison-Wesley, 1993. 3D Math Primer for Graphics and Game Development. Fletcher Dunn and Ian Parbery Wordware Publishing, 2002 Advanced Animation and Rendering Techniques Theory and Practice Alan Watt and Mark Watt (contributor) Addison-Wesley, 1992. Introduction to Computer Graphics. James D. Foley, Andries van Dam, Steven К Feiner, John F. Hughes, and Richard L. Phillips. Addison-Wesley, 1993. Open Geometry OpenGL + Advanced Geometry Georg Glaeser and Hellmuth Stachel. Springer-Verlag, 1999. Mathematics for 3D Game Programming & Computer Graphics. Eric Lengyel. Charles River Media, 2001 Программирование игр для Windows Советы профессионала, 2-е издание + CD- ROM Андре Ламот. Вильямс, 2003.
1022 Приложение А Что еще почитать’’ Програм пирование трехмерны г игр д ix Window s Советы профессиона ia по трех мерной графике и растеризации Андре Ламот Вильямс, 2004 Web-сайты Web-сайт Суперкниги OpenGL http://www.starstonesoftware com/OpenGL Официальный Web-сайт OpcnGL http://www.opengl.org Домашняя страница Khronos Group OpenGL ES http://www.khronos.org/opengles/index.html SGI OpcnGL Extension Registry http://oss.sgi.com/projects/ogl-sample/registry/ Посвященный OpcnGL Web-сайт SGI http://www.sgi.com/software/opengl/tech_info.html Домашняя страница разработчиков, работающих с ATI http://www.ati.com/developer/index html Домашняя страница разработчиков, работающих с NVidia http://developer.nvidia.com/page/home Домашняя страница разработчиков, работающих с 3Diabs http://www.3dlabs.com/support/developer/index.htm Много хороших руководств no OpcnGL (преимущественно нацеленных на разра ботку игр) http://www.gamedev.net/ http://nehe.gamedev.net/ http’//www.xmission.com/ nate/tutors.html http://www.paulsprojects.net/opengl/projectsi.html http://www.gametutorials.com/Tutorials/OpenGL/OpenGL_Pgl.htm http://www.codecolony.de/opengl.htm
ПРИЛОЖЕНИЕ Б Словарь терминов ARB (OpenGL Architecture Review Board). Наблюдательный совет за архитекту- рой OpenGL. Собрания ARB происходят ежеквартально, в них принимают участие производители аппаратного обеспечения с ускорением трехмерной графики. ARB поддерживает Спецификации OpenGL (документ OpenGL Specification) и поощряет использование стандарта OpenGL. NURBS. Сокр. от “Non-Uniform Rational B-Splme” — неравномерный рациональ- ный би-сплайн. Метод задания параметрических кривых и поверхностей. Open Inventor. Библиотека классов и набор инструментов C++ для построения интерактивных трехмерных приложений. Open Inventor построен на OpenGL. Альфа. Четвертый компонент кода цвета, несущий информацию о степени про- зрачности цвета объекта. Значение 0 0 означает полную прозрачность, 1 0 — непро- зрачность Библиотека AUX. Вспомогательная библиотека, независимая от системы окон. Ограниченный, но полезный инструмент, подходящий для быстрых и переносимых демонстрационных программ OpenGL. В настоящее время вытесняется библиоте- кой GLUT. Библиотека GLUT (OpenGL Utility Library). Вспомогательная библиотека, не за- висящая от системы окон Полезна при создании программ-примеров и простых про- грамм с трехмерной визуализацией, не зависящих от операционной системы и си- стемы организации окон. Обычно используется для обеспечения переносимости про- грамм между системами Windows, X Window, Linux и т.д Битовая плоскость. Массив битов, отображаемых непосредственно в пиксели экрана. Буфер. Область памяти, используемая для хранения информации об изображении. Это могут быть данные о цвете, глубине или смешении Буферы красного, зеленого, синего цвета и параметра альфа обычно в сумме называются буферами цвета. Вершина. Точка в пространстве Исключая случаи, когда применяется в прими- тивах “точка” и “линия”, определяет точку встречи двух сторон многоугольника. Визуализация Преобразование примитивов (в координатах объекта) в изображе- ние (в буфере кадров). Конвейером визуализации называется процесс, в ходе которого команды и утверждения OpenGL превращаются в пиксели на экране. Выдавливание (экструзия). Процесс преобразования двухмерной формы или изображения путем равномерного добавления вдоль поверхности третьего измерения. Этот процесс позволяет преобразовать двухмерные шрифты в трехмерные тисненые надписи. Выпуклый. Термин, используемый при описании формы многоугольника. Выпук- лый многоугольник не имеет углублений, и через него нельзя провести прямую ли- нию, пересекающую эту фигуру более двух раз (один раз входит, второй — выходит)
1024 Приложение Б. Словарь терминов Двойная буферизация Техника рисования, используемая OpenGL. Изображение, подготавливаемое к отображению, не строится на экране из отдельных примитивов, а собирается в памяти, после чего целиком помещается на экран при обновлении Двойная буферизация является более быстрой и плавной операцией обновления, кро- ме того; она может использоваться для создания анимации. Добавление псевдослучайного шума Метод, используемый для имитации бо- лее широкого диапазона насыщенности цвета Заключается в помещении пикселей разного цвета в структуры, создающие иллюзию полутонов исходных цветов Зашита от наложения (antialiasing) Метод визуализации, используемый для сгла- живания линий, кривых и сторон многоугольников Действие заключается в усредне- нии цвета пикселей, соседствующих с линией. Визуально это выражается в сглажи- вании переходов от пикселей линии к пикселям, соседствующим с линией Исходный цвет. Цвет поступающего фрагмента в отличие от цвета, уже при- сутствующего в буфере цветов (целевой цвет) Термин обычно используется при описании принципа объединения исходного и целевого цветов в процессе смешения Каркас. Представление сплошного объекта с помощью сетки линий, а не сплош- ных закрашенных многоугольников. Каркасные модели обычно визуализируются быстрее, и их можно использовать, чтобы одновременно увидеть передние и зад- ние элементы объекта. Координаты наблюдения. Система координат, основанная на положении наблю- дателя Наблюдатель располагается перпендикулярно оси z, а его взгляд направлен по отрицательному направлению этой оси. Координаты отсечения. Двухмерные геометрические координаты, полученные после преобразований наблюдения модели и проектирования Кривая Безье. Кривая, форма которой определяется точками, расположенными возле кривой, а не точным набором точек, принадлежащих самой кривой Литерал Значение, а не имя переменной Специфическая строка или численная константа, внедренная непосредственно в исходный код Матрица Двухмерный массив чисел, которым можно оперировать математиче- скими средствами В OpenGL матрицы применяются для преобразований координат Матрица наблюдения модели (modelview matrix). Матрица OpenGL, преобразу- ющая координаты примитивов в системе, связанной с объектом, в координаты на- блюдения Многоугольник Двухмерная фигура, нарисованная с использованием любого числа сторон (не менее трех) Множественное отображение (mipmapping). Технология представления тексту- ры на нескольких уровнях детализации. При наложении текстуры с множественным отображением окончательные фрагменты, используемые в качестве текстуры, выби- раются из изображений текстур нескольких размеров (возможно, объединяются два изображения, ближайших к нужному размеру). Мозаичное представление Разбиение сложного многоугольника или аналитиче- ской поверхности на сетку выпуклых многоугольников Может также применяться для разделения сложной кривой на ряд простых линий Наблюдаемый объем. Область в трехмерном пространстве, которую можно ви- деть в окне. Объекты и точки вне наблюдаемого объема отсекаются (они не видны)
Приложение Б. Словарь терминов 1025 Наложение (aliasing). Потеря информации на изображении, воспроизведенном при некотором разрешении. Чаще всего характеризуется наличием резких зазубрен- ных краев вдоль точек, линий или многоугольников вследствие наличия ограничен- ного числа пикселей фиксированного размера. Наложение (отображение) текстуры. Нанесение текстурного изображения на поверхность Поверхность не обязательно должна быть плоской Наложение текстуры часто используется для оборачивания изображения вокруг криволинейного объекта или получения таких узорных поверхностей, как дерево или мрамор Немедленный режим. Режим визуализации графики, в котором команды и функ- ции немедленно влияют на состояние визуализации Нормаль Направленный вектор, перпендикулярный плоскости или поверхности Если нормали используются, они должны задаваться для всех вершин примитива Нормировка Сокращение нормали до единичной длины. Единичным называется вектора, длина которого равна I О Ортографическая проекция. Режим рисования, при котором перспектива и ра- курс не учитываются Также называется параллельной проекцией. Длины и размеры всех примитивов не искажаются вне зависимости от ориентации и расстояния от наблюдателя Отбор. Устранение графических примитивов, которые не будут видны при визуа- лизации При отборе граней удаляются (т.е. не рисуются) задние или передние грани примитива При пирамидальном отборе удаляются целые объекты, не попадающие в усеченную пирамиду наблюдения Отсечение. Удаление фрагмента отдельного примитива или группы примитивов. Точки, которые при визуализации не попали бы в область отсечения, не рисуются. Обычно объем отсечения задается проекционной матрицей. После отсечения прими- тивы перерисовываются, и никакая часть примитива не выходит за область отсечения. Палитра Набор цветов, доступных операциям рисования. В 8-битовом цветном режиме Windows палитра содержит 256 записей-цветов, и все пиксели на сцене могут закрашиваться только цветами из этого набора. Параметрическая кривая Кривая, форма которой определяется одним (кривая) или двумя (поверхность) поверхности. Эти параметры используются в уравнениях, задающих координаты х, у и z точек кривой. ПДСК (Прямоугольная декартова система координат) Система координат, осно- ванная на трех направленных осях, образующих между собой углы 90°. Три коорди- наты принято обозначать х, у и z. Перспективная проекция Режим рисования, в котором объекты, удаленные от наблюдателя, кажутся меньше близлежащих объектов. Пиксель. Термин образован слиянием слов “picture element” (элемент изображе- ния). Наименьший визуальный элемент, доступный на экране компьютера. Пиксели организованы в строки и столбцы, и при визуализации изображений каждому пиксе- лю экрана присваивается свой цвет. Пиксельный образ (pixmap). Двухмерный массив кодов цвета, образующий цвет- ное изображение Название получил благодаря тому, что каждый элемент изображе- ния соответствует пикселю на экране. Поле просмотра. Область внутри окна, которая используется для отображения изображения OpenGL. Обычно — вся область клиента Вытянутые поля просмотра
1026 Приложение В. Словарь терминов могут давать увеличенные или сморщенные выходные изображения внутри физиче- ского окна. Преобразование Манипуляция с системами координат. Под преобразованием мо- жет подразумеваться поворот, трансляция, масштабирование (как равномерное, так и неравномерное) и перспективное деление. Примитив Двухмерная многоугольная фигура, определенная в OpenGL. Все объ- екты и сцены формируются из различных комбинаций примитивов. Проекция Преобразование линий, точек и многоугольников из координат наблю- дения в координаты отсечения на экране. Просвечивание Степень прозрачности объекта. В OpenGL данное свойство пред- ставляется значением альфа, меняющимся от 1.0 (объект непрозрачен) до 0.0 (объект прозрачен) Рассеянный свет Свет на сцене, не исходящий от точечного источника Равно- мерно освещает все поверхности со всех сторон Растеризация. Процесс преобразования спроектированных примитивов и растро- вых изображении в пиксели буфера кадров. Режим индексирования цвета Цветовой режим, в котором цвета на сцене выби- раются из фиксированного числа цветов, доступных в палитре. Эти позиции иденти- фицируются индексом палитры. Режим используется редко, а ускоряется на аппарат- ном уровне еще реже Сплайн. Общий термин, используемый для кривой, создаваемой путем помеще- ния вблизи нее контрольных точек, притягивающих получающуюся кривую. Подоб- ное поведение похоже на реакцию куска гибкого материала при приложении силы к различным его точкам. Сцинтилляция. Эффект блеска или мерцания, порождаемый на объектах при наложении карты текстуры без множественного отображения на многоугольник, зна- чительно меньший налагаемой текстуры. Таблица отображения. Скомпилированный список функций и команд OpenGL. Вызываемая таблица отображения выполняется быстрее, чем вызываемый вручную список отдельных команд. Тексель. Как пиксель — элемент изображения (picture element), тексель — это элемент текстуры (texture element) Тексель представляет цвет текстуры, который применяется к пиксельному фрагменту в буфере кадров. Текстура Цветной узор-изображение, налагающийся на поверхность примитива. Усеченная пирамида Форма наблюдаемого объема, при которой формируется перспективная проекция (близкие объекты выглядят большими, удаленные — ма- ленькими). Фактура Двоичный “узор”, используемый для маскировки процесса генерации пикселей в буфере кадров Это похоже на монохромное битовое отображение (раст- ровый образ), но одномерные “узоры” используются для линий, а двухмерные — для многоугольников Характеристическое отношение. Отношение ширины окна к его высоте. По сути, — ширина окна в пикселях, деленная на высоту окна в пикселях. Целевой цвет Цвет, записанный в определенном месте буфера цвета. Термин обычно используется при описании операций смешения, чтобы различать цвет, уже присутствующий в буфере цвета, и цвет, поступающий в него (исходный цвет).
ПРИЛОЖЕНИЕ В OpenGL для внедренных систем OpenGL для внедренных систем (OpenGL for Embedded Systems — OpenGL ES) пред- ставляет собой облегченную версию OpenGL, на основе переносимости и стандар- тизации программирования трехмерной графики в широком диапазоне внедренных систем. Внедренной системой называют компьютер, внедренный в некоторое устрой- ство. В отличие от типичного ПК, где основной задачей компьютера являются вычис- ления, внедренная система — это компьютер, облегчающий только операции устрой- ства, в которое он внедрен. В качестве примеров подобных устройств можно привести КПК, сотовые телефоны со сложными графическими дисплеями, медицинские и ди- агностические устройства, а также автомобильные и авиационные дисплеи OpenGL ES поддерживается ассоциацией Khronos Group — консорциумом компаний, про- фильная деятельность которых связана со средствами информации Khronos Group выполняет функции, сходные с функциями Наблюдательного совета по архитектуре OpenGL (Architecture Review Board — ARB). Многие компании входят как в ARB, так и в Khronos Group По сути OpenGL ES определяется как подмножество OpenGL версии 1 3, имею- щее несколько дополнительных расширений. Подмножество делится на два “профи- ля”: “Common” (общий) и “Common-Lite” (общий упрощенный) или “Safety Critical” (с особыми требованиями к безопасности). Профиль Common образован подмноже- ством функциональных возможностей OpenGL, сокращающим основную библиотеку OpenGL с точки зрения числа команд и памяти, требуемой для реализации Другой целью сокращения было устранение функциональных возможностей, не имеющих смысла на внедренном устройстве. Профиль Common-Lite представляет собой еще более сокращенный набор элементов, предназначенный для устройств с еще меньшей памятью или доступными ресурсами. Данный профиль также известен под именем “Safety Critical”; он используется в тех случаях, когда сертификаты безопасности ос- нованы на небольших “отпечатках” API и коротком коде, который нужно тщательно проверить Сокращение количества типов данных OpenGL поддерживает множество типов данных, кроме того существуют функции, позволяющие задавать данные любого удобного типа В связи с этим напрашивается первый шаг на пути сокращения OpenGL — уменьшить число поддерживаемых типов данных и разрешить применение некоторых меньших типов данных в операциях, где они раньше не использовались Первым “отправленным в отставку” типом данных стал GLdouble С потерей этого типа данных отпала необходимость в вариантах
1028 Приложение В. OpenGL для внедренных систем функций, поддерживающих типы данных двойной точности Функции, не удаленные целиком (см. следующий раздел), но использующие только величины двойной точно- сти (например, функция glOrtho) стали теперь принимать параметры с плавающей или фиксированной запятой. Чтобы сократить OpenGL для внедренных систем был добавлен новый тип дан- ных GLFixed — десятичное число с фиксированной запятой (также существует GLclampx — “сжатая” версия GLfixed). Добавление данного типа было необходи- мым, поскольку в профиле Common-Lite был удален тип GLfloat. Во внедренные системы редко включается аппаратное обеспечение, выполняющее математические действия с плавающей запятой и удаление указанного типа данных позволило отка- заться от памяти и служебных издержек, связанных с эмуляцией операций с плаваю- щей запятой Суффиксы функций GLbyte, GLubyte и GLshort также были удалены. В резуль- тате остались только общие суффиксы — i, f и х, причем f отсутствует в функциях профиля Common-Lite. Вы по-прежнему можете задавать вершины и компоненты цвета в виде данных short или byte (но только в массивах вершин). Единственным исключением стало то, что коды цвета можно задавать в виде величин типа ubyte, но никак не short. Используя меньшие типы данных, вы можете уменьшить размер программы и сократить память, требуемую для хранения данных. Ушли совсем Спецификация OpenGL ES (включена в компакт-диск) больше касается элементов, удаленных из OpenGL, чем собственно составляющих OpenGL ES. Это означает, что работу OpenGL ES невозможно понять, не разобравшись предварительно в OpenGL. Отметим, что, по сути, в обеих реализациях (Common и Common-Lite) целиком были удалены следующие функциональные возможности. • Использование Begin/End при задании геометрии (в OpenGL ES применяются только массивы вершин). • Чередующиеся массивы или поддержка glArrayElement. • Таблицы отображения • Схемы оценки. • Режим индексирования цвета. • Определяемые пользователем плоскости отсечения. • Фактура линий или многоугольников • glRect. • Подмножество построения изображений. • Обратная связь. • Выбор • Буфер накопления. • Метки сторон.
Приложение В OpenGL для внедренных систем 1029 • glPolygonMode. • Примитивы GL_QUADS, GL_QUAD_STRIP И GL_POLYGON. • Запись атрибутов glPushAttrib, glPopAttrib, glPushClientAttrib или glPopClientAttrib Сильно сокращенные функциональные возможности Множество других возможностей OpenGL ES были сокращены OpenGL ES по- прежнему обеспечивает ограниченную поддержку задания текущего цвета, норма- ли и текстурных координат (при этом используются варианты команд glColor4, glNormal3 и MultiTexCoord4 с фиксированной или плавающей запятой) Вы мо- жете использовать эти функции, например, когда состояние остается постоянным для всего массива вершин. Конвейер преобразования большей частью не изменился, однако теперь он рабо- тает только с видоизмененным подмножеством типов данных (нет величин двойной точности и т д ) OpenGL ES также не поддерживает транспонирование матрицы, а ми- нимальная глубины стека матриц наблюдения модели была уменьшена с 32 до 16 Наложение текстуры В OpenGL ES поддерживаются только двухмерные текстуры Возможность нало- жения множественной текстуры осталась, а текстурная среда GL_COMBINE — нет Отсутствует поддержка границ текстуры и режимов обертки GL_CLAMP и GL_CLAMP _TO_BORDER Кроме того, отсутствуют заменители текстуры, ограничение согласно допустимому диапазону и параметры смещения Наконец, сжатие текстуры поддер- живается, но вы не сможете считать сжатую текстуру и использовать glTex!mage2D для сжатия изображения Растровые операции Большинство функциональных возможностей, связанных с растровыми операциями, были удалены Частично поддерживается функция glPixelStore, но теперь ее мож- но применять только для упаковки и распаковки текстурных данных Вы можете считывать пиксели с помощью glReadPixels, но функции glDrawPixels, glPix- elTransfer и glPixelZoom нс поддерживаются Хотя функция glReadPixels вес еще существует, вы не можете использовать ее для считывания информации или буфера глубин или трафарета Кроме того, были удалены функции glReadBuffer, glDrawBuffer и glCopyPixels. Смещение многоугольника поддерживается только в режиме заполнения (glPolygonMode уже не поддерживается) Освещение OpcnGL ES поддерживает по крайней мере восемь источников света Все еще существует двухстороннее освещение, хотя обе стороны должны иметь одинако-
1030 Приложение В. OpenGL для внедренных систем вне свойства материалов (свойства материалов уже не отличаются для передних и задних граней). Единственным оставшимся режимом окраски материала является GL_AMBIENT_AND_DIFFUSE, а модели вторичного цвета и освещения для локального наблюдателя отброшены Вывод OpenGL ES — это интерфейс программирования трехмерной графики, предлагаю- щий минимальный набор элементов, удовлетворяющий потребностям программистов трехмерной графики на обширном рынке внедренных систем. Программирование в OpenGL ES требует набора инструментальных средств разработки ПО (software development kit — SDK), предназначенного для целевой платформы и содержащего информацию о создании и использовании трехмерного контекста для экрана этого устройства На момент публикации данной на рынке уже присутствовали эмуляторы ПК для OpenGL, и вы можете обратиться к Web-сайту Khronos (www. khronos. org), где найдете список поставщиков, поддерживающих OpenGL, и ссылки на ресурсы для разработчиков
Предметный указатель А AGL, 710 первая программа, 713 agIChoosePixelFormat, 743 aglCreateContext, 744 aglDestroyContext, 744 aglSetCurrentContext, 745 aglSetDrawable, 745 aglSwapBuffers, 746 aglUseFont, 746 API, 64 ARB, 1023 C Carbon, 710 ChoosePixelFormat, 692 Cocoa, 733 первая программа, 736 D DescribePixelFormat, 694 Direct3D, 66 DirectX, 64 G GetPixelFormat, 696 glAccum, 304 glActiveTexture, 461 glAlphaFunc, 859 glAreTexturesResident, 414 gl Array Element, 577 glAttachObjectARB, 931 glBegin, 161 glBeginQuery, 837 glBindAttnbLocationARB, 932 glBindBuffer, 818 glBindProgramARB, 899 glBindTexture, 415 glBitmap, 355 glBlendColor, 304 glBlendEquation, 305 glBlendFunc, 305 gIBIendFuncSeparate, 306 glBufferData, 819 glBufferSubData, 820 glCallList, 578 glCallLists, 578 glClearAccum, 306 glClearColor, 102 glClearDepth, 162 glClearStencil, 163 glClientActiveTexture, 462 glColor, 275 glColorMask, 277, 307, 860 glColorMaterial, 277 glColorPointer, 579 glColorSubTable, 355 glColorTable, 356 glColorTableParameter, 357 glCompileShaderARB, 932 glCompressedTexImage, 462 glCompressedTexSublmagc, 463 glConvolutionFilterlD, 358 glConvolutionFilter2D, 359 glConvolutionParameter, 359 glCopyColorSubTable, 360 glCopyColorTable, 361 glCopyConvolutionFilterlD, 361 glCopyConvolutionFilter2D, 362 glCopyPixels, 363 glCopyTeximage, 415 glCopyTexSublmage, 416, 860 glCreateProgramObjectARB, 933 glCreateShaderObjectARB, 933 glCullFace, 163 glDeleteBuffers, 821 glDeleteLists, 579 glDeleteObjectARB, 934 glDeleteProgramsARB, 900 glDeleteQueries, 838
1032 Предметный указатель glDeleteTextures, 417 glDepthFunc, 164 glDepthMask, 165 glDepthRange, 165 glDetachObjectARB, 934 glDisable, glEnable, 102 glDisableClientState, 582 glDisableVertexAttnbArrayARB, 900 glDrawArrays, 580 gIDrawBuffer, 166 gIDrawElements, 580 glDrawPixels, 363 glDrawRangeElements, 581 glEdgeFlag, 167 glEdgeFlagPointer, 582 glEnableClientState, 582 glEnableClientState/glDisableClientState, 582 glEnableVertexAttribArrayARB, 901 glEnd, 167 glEndList, 583 glEndQuery, 838 glEvalCoord, 507 glEvalMesh, 508 glEvalPoint, 508 gIFeedbackBuffer, 613 gIFinish, 103 glFlush, 103 glFog, 307 gIFogCoordPointer, 583 glFrontFace, 168 glFrustum, 217 glGenBuffers, 821 glGenLists, 584 glGenProgramsARB, 901 glGenQueries, 839 glGenTextures, 418 glGetActiveAttnbARB, 934 glGetActiveUniformARB, 936 glGetAttachedObjectsARB, 937 glGetAttribLocationARB, 938 glGetBufferParameteriv, 822 glGetBufferPointerv, 822 glGctBuffcrSubData, 823 glGetColorTable, 365 glGetColorTableParameter, 366 glGetCompressedTexImage, 464 gIGetConvolutionFilter, 364 glGetConvolutionParameter, 365 glGetError, 104 glGetHandleARB, 938 glGetHistogram, 366 glGetHistogramParameter, 367 glGetlnfoLogARB, 939 glGetLight, 278 glGetMap, 509 glGetMaterial, 278 glGetMinmax, 368 glGetObjectParameter*vARB, 939 glGetPolygonStipple, 168 glGetProgramEnvParameter*vARB, 904 glGetProgramivARB, 901 glGetProgramLocalParameter*vARB, 904 gIGetProgramStringARB, 905 glGetQueryiv, 839 glGetQueryObject, 840 glGetSeparableFilter, 368 glGetShaderSourceARB, 941 glGetStrmg, 104 glGetTexImage, 421 glGetTexLevelParameter, 419 glGetTexParameter, 419 glGetUniform*vARB, 941 gIGetUniformLocationARB, 942 glGetVertexAttnb*vARB, 906 glGetVertexAttnbPomtervARB, 906 glGetXxxxv, 104 glHint, 105 glHistogram, 369 gllnitNames, 614 gllnterleavedArrays, 584 gllsBuffer, 824 gllsEnabled, 705 gllsList, 585 gllsProgramARB, 907 gllsQuery, 840 gllsTexture, 421 glLight, 280 gILightModel, 281 gILineStipple, 169 gILineWidth, 769
Предметный указатель glLinkProgramARB, 942 gIListBase, 585 glLoadldentity, 218 gILoadMatrix, 218 glLoadName, 614 glLoadTransposeMatrix, 219 glLogicOp, 308 glMap, 510 gIMapBuffer, 824 gIMapGrid, 512 glMaterial, 282 glMatrixMode, 219 glMmmax, 3 70 gIMultiDrawElements, 587 glMultiTexCoord, 465 glMultMatrix, 220 glMultTransposeMatrix, 220 glNewList, 588 gINormal, 283 glNormalPointer, 589 glOrtho, 106 glPassThrough, 614 glPixelMap, 370 glPixelStore, 371 glPixelTransfer, 371 glPixelZoom, 372 glPointSize, 170 glPolygonMode, 171 glPolygonOffset, 172, 861 glPolygonStipple, 172 glPopMatrix, 221 glPopName, 615 gIPrioritizeTextures, 422 glProgramEnvParameter*ARB, 907 glProgramLocalParameter*ARB, 908 glProgramStringARB, 909 glPushAttrib/glPopAttnb, 106 glPushMatrix, 221 glPushName, 615 glRasterPos, 3 73 glReadPixels, 3 74 glRect, 107 glRenderMode, 616 glResetHistogram, 375 glResetMinmax, 375 glRotate, 221 10: glSampleCoverage, 309 gIScale, 222 glScissor, 173 glSecondaryColor, 467 glSecondaryColorPointer, 589 glSelectBuffer, 616 glSeparableFilter2D, 376 glShadeModel, 284 glShaderSourceARB, 943 glStencilFunc, 173 gIStencilMask, 174 gIStencilOp, 174 glTexCoord, 423 glTexCoordPointer, 590 glTexEnv, 424 glTexGen, 468 glTexImage, 426 glTexParameter, 427 glTexSublmage, 429 gITranslate, 222 gluBeginCurve, 513 gluBeginSurface, 513 gluBeginTrim, 513 gluBuildMipmapLevels, 430 gluBuildMipmaps, 431 gluCylinder, 514 gluDeleteNurbsRenderer, 514 gluDeleteQuadric, 515 gluDeleteTess, 515 gluDisk, 515 glu EndCurve, 516 gluEndSurface, 516 gluEndTrim, 517 gluErrorStrmg, 108 gluGetNurbsProperty, 517 gluLoadSamplingMatnces, 518 gluLookAt, 223 gluNewNurbsRenderer, 518 gluNewQuadric, 519 gluNewTess, 519 glUniform*ARB, 943 glUnmapBuffer, 825 gluNurbsCallback, 519 gluNurbsCurve, 521 gluNurbsProperty, 522 gluNurbsSurface, 524
1034 Предметный указатель gluOrtho2D, 223 gluPartialDisk, 525 gluPerspective, 224 gluPickMatrix, 617 gluPwlCurve, 526 gluQuadricCallback, 527 gluQuadncDrawStyle, 528 gluQuadncNormals, 528 gluQuadncOnentation, 529 gluQuadncTexture, 530 glUseProgramObjectARB, 945 gluSphere, 530 GLUT, 77, 710 анимация, 90 использование библиотеки, 749 glutCreateWindow, 108 glutDisplayFunc, 109 gluTessBeginContour, 531 gluTessBeginPolygon, 531 gluTessCallback, 531 gluTessEndContour, 532 gluTessEndPolygon, 532 gluTessProperty, 534 gluTessVertex, 534 glutlnitDisplayMode, 109 glutKeyboardFunc, 110 glutMainLoop, 110 glutMouseFunc, 110 glutPostRedisplay, 111 glutRcshapeFunc, 111 glutSolidTeapot, glutWireTeapot, 111 glutSpecialFunc, 112 glutSwapBuffers, 113 glutTimerFunc, 113 glVahdateProgramARB, 946 glVertex, /76 glVcrtexAttnb*ARB, 910 gIVertexAttribPointerARB, 912 glVertexPointer, 591 glVicwport, 108 glWindowPos, 377 GLX, 770 glXChooseFBConfig, 793 glXChooseVisual, 793 glxCreateContext, 794 glXCrcatcGLXPixmap, 794 glXCreateNewContext, 795 glXCreatePbuffer, 796 gIXDestroyContext, 796 glXDestroyGLXPixmap, 796 glXDestroyPbuffer, 797 glXGetFBConfigs, 797 glXGetVisualFromFBConfig, 798 glXMakeCurrent, 798 glXSwapBuffers, 799 glXUseXFont, 799 I ICD, 623 M MCD, 623 Mesa, 750 Motif, 781 N NURBS, 492, 1023 кривые, 498 свойства, 494 создание поверхности, 493 О Open Inventor, 1023 OpenGL, 67 анимация, 90 будущее, 68 в системе Linux, 750 для игр, 66 использование библиотек, 747 использование контекста визуализации, 641 использование цвета, 233 контекст визуализации, 635 машина состояний, 94 независимость от платформы, 77 общий, 622 ошибки, 96 программный интерфейс, 73 расширение для X Window System, 751 расширения, 100 расширения WGL, 665
Предметный указатель 1035 расширенный, 624 реализации в системе Windows, 622 рисование форм, 83 создание окна, 80; 753 создание палитры, 649 создание растровых шрифтов, 762 управление контекстами, 753 шрифты Windows, 655 OpenGL ARB, 62 Р P-буфер, 776 S SetPixelFormat, 696 SetupRC, 81 superceded, 99 SwapBuffers, 697 W wgICopyContext, 698 wglCreateContext, 697 wgICreateLayerContext, 698 wglDeleteContext, 699 wglDescribeLayerPlane, 699 wglGetCurrentContext, 702 wglGetCurrentDC, 702 wglGetProcAddress, 703 wglMakeCurrent, 703 wglShareLists, 704 wglSwapLayerBuffers, 705 wglUseFontBitmaps, 705 wglUseFontOutlines, 706 Win32, 639 максимальное использование, 671 X X Window System основы, 751 Xll использование библиотек, 747 A Адрес, 892 Альфа, 1023 Альфа-тест, 301 Аппаратная реализация, 71 Атрибуты, 888 вершин, 889 фрагментов, 890 Б Би-сплайн, 492 Битовая плоскость, 1023 Битовый образ, 313 рисование, 317 Буфер, 153, 1023 использование, 152 обратной связи, 605 выбора, 597 глубины, 155 накопления, 298 трафарета, 157 цвета использование, 382 очистка, 82 Буферные объекты, 809 загрузка данных, 811 копирование данных, 811 отображение, 812 управление, 810 В Вееры треугольников, 136 Верификация программ, 917 Вершина, 56, 118, 1023 задание информации, 501 замещение преобразования, 869 преобразование, 864 фиксированная обработка, 864 Вершины смешение, 970 Визуализация, 1023 выбор режима, 751 закадровая, 770 многопоточная, 663 полноэкранная, 660 с двойной буферизацией, 713 с помощью буферных объектов, 811 трехмерного текста, 657 Внутритекстовые константы, 886 Восстановление состояния, 95
1036 Предметный указатель Временные переменные, 885 Встроенные переменные, 922 Встроенные поверхности, 472 Входные атрибуты, 893 Выбор, 594 Выдавливание, 1023 Выпуклый, 1023 Выражение, 922 Высокоуровневые расширения, 875 Г Гистограмма, 351 д Данные, 562 пакетная обработка, 548 Двойная буферизация, 93; 1024 Двухмерная кривая, 483 Двухмерные декартовы координаты, 53 Двухмерные шрифты и текст, 658 Деление, 151 Детализация, 404 Детектирование краев, 993 Диффузный эффект, 243 Дуализм проекции модели, 180 3 Заголовок, 73, 80 Замкнутая линия, 127 Запись состояния, 95 Затенение, 44; 235 выбор модели, 237 Защита от наложения, 47, 1024 И Изображение обработка, 986 растровое, 312 Источник света, 247 установка, 254 Исходный цвет, 1024 К Каркас, 1024 Карта тени, 848; 849 Конвейер, 72 воспроизведения изображений, 343 преобразований, 185 Конструктор, 925 Контекст визуализации инициализация, 643 сворачивание, 644 Контекст устройства, 626 Контрольные точки, 482 Координаты наблюдения, 179, 1024 Координаты отсечения, 1024 Коэффициент зеркального отражения, 259 Кривая Безье, 1024 Кривые, 480 Куб цвета, 233 Кубические карты загрузка, 450 использование, 452 Л Ленты треугольников, 135 Ленты четырехугольников, 145 Линия задание ширины, 129 фактура, 130 Литерал, 1024 Ломаная линия, 127 М Массив, 921, 924 активизация, 562 вершин, 558; 804 активизация, 806 параметров, 888 Масштабирование, 85, 189 Материал добавление света, 242 Свойства, 241 установка свойств, 245, 255 Матрица, 184, 1024 единичная, 190 загрузка, 203 наблюдения модели, 186, 1024 стеки, 192 текстуры, 385
Предметный указатель 1037 цветов, 343 Мини-драйвер, 624 Многоугольник, 1024 заполнение, 146 общего вида, 145 правила построения, 149 режимы, 144 смещение, 857 установка, 255 установка цвета, 139 Множественная выборка, 293 Множественная текстура, 403 Множественное отображение, 1024 Модель сборка, 535 Мозаика, 498 функция, 499 Мозаичное представление, 1024 Н Наблюдаемый объем, 1024 Наборы команд, 881 Наложение текстуры, 1025, 1029 процедурное, 1004 Настройка входа по адресам, 893 Настройка расширений, 880 Насыщенность цвета, 231 Немедленный режим, 52, 1025 Непрерывность, 482 Низкоуровневые расширения, 873 Нормаль, 249; 1025 вектор, 490 единичная, 251 задание, 249 нахождение, 252 усреднение, 260 Нормировка, 1025 О Обратная связь, 604; 607 помечаем объекты, 607 реализация, 610 Обратные вызовы, 782 Обход, 134 Объекты программы, 916 Ограничения, 654 Окно с двойной буферизацией, 754 создание, 637 создание без рамки, 660 создание на весь экран, 661 Определение версии, 97 Ортографическая проекция, 58; 1025 Освещение, 490; 865; 995; 1029 активизация, 243 диффузное, 949; 996 замещение, 869 настройка модели, 244 эффекты, 257 Освобождение очереди, 83 Отбор, 598; 1025 иерархический, 601 Отображение кубическое, 450 линейное относительно точки наблюдения, 447 линейное по объектам, 446 сферическое, 448 Отражательный эффект, 243 Отражение, 955 зеркальное, 258 Отраженные блики, 257 Отраженный свет, 999 Обсечение, 866; 1025 Отсечение координат, 53 П Палитра, 1025 3-3-2, 651 построение, 651 разрешение конфликтов, 647 создание и управление ею, 654 структура, 650 Палитры Windows, 646 Параметрическая кривая, 1025 Параметрическое представление, 480 Параметры, 886 Параметры программы, 887 Параметры, связанные с состоянием, 887 Переменные, 919 Перспективная проекция, 58; 1025
1038 Предметный указатель Пиксель, 3/7; 1025 запись, 325 изменение масштаба, 332 отображение, 33 7 передача, 334 перемещение, 324 Пиксельный образ, 318; 1025 Пиксельный формат, 711 выбор и установка, 634 описание, 629 перечисление, 631 расширенный, 668 с упаковкой,319 Поверхности Безье, 480 Поверхность второго порядка, 474 квадратичная, 478 определение, 494 трехмерная, 488 Поворот, 188 Подрезка, 495 Поле просмотра, 55; 1025 определение,87 установка, 86 Полутона, 978 Потребление ресурсов, 894 Правила именования, 75 Преобразование модели, 180 наблюдения, 180 поля просмотра, 184 проектирования, 182 складывание, 207 Примитив, 144, 594, 1026 Проектирование, 56 Проекция, 1026 использование, 195 ортографическая, 195 перспективная, 196 Прожектор, 263 Прозрачность, 46 Псевдонимы, 892 Р Размер точки, 966 Размывание, 986 Разрешение экрана, 231 Разыменование текстуры, 896 Рамки, 831 Растеризация, 1026 Растровые шрифты использование, 722 Расширение, 98; 991 WGL, 667 проверка, 99 Расширения, 665 настройка, 918 Родные пределы, 896 С Свертка, 347 Свет, 45, 226, 842 диффузный, 239 добавление к сцене, 243 как частица, 226 отраженный, 239, 257, 952 рассеянный, 239; 242 Связывание программ, 917 Сглаживание, 290; 303 Селектор, 926 Система актеров, 208 Система координат, 53 Скрытая поверхность повышение производительности, 141 удаление, 140 Смешение, 46; 285 Смешивание уравнение, 289 Спецификатор, 922 Сплайн, 1026 Сумма цветов, 868 Сфера, 274, 806 Сцинтилляция, 1026 Т Таблица отображения, 547; 551; 1026 Тсксель, 1026 Текстура, 846; 1026 “игрушечный мяч”, 1014 “пляжный мяч”, 1009 зависимый поиск, 983
Предметный указатель 1039 загрхзка, 380 множественная, 452 мультфильмы, 396 наложение, 46; 867 намотка, 395 обновление, 383 отображение на геометрические объекты, 384 параметры, 393 приоритеты, 413 разыменование, 896 резидентная, 412 сжатие, 438-440 сокращенная, 400 шахматная, 1004 Текстурирование, замена шейдером, 872 Текстурная среда, 391, 867 Текстурные координаты генерация,441 генерация и преобразование, 866 замещение обработки, 870 множественные, 454 Текстурные объекты, 405 Текстуры объединение, 459 Тело, 80 Тень, 45; 268; 851 Тепловое излучение, 982 Типографские особенности, 36 Типы данных, 74 Типы переменных, 884 Точка задание размера, 122 Точка входа, 666 Трансляция, 187 Треугольник, 133 Трехмерное пространство рисование линий, 125 рисование точек, 116 рисование треугольников, 133 Трехмерные артефакты, 43 Трехмерные декартовы координаты, 56 Трехмерные шрифты и текст, 655 Трехмерные эффекты обзор, 43 Туман, 46, 295, 962, 984 наложение, 868 У Углы Эйлера, 209 Умножение матриц, 201 Управление камерой, 210 Усеченная пирамида, 1026 Ф Фильтрация, 393 анизотропная, 436 множественной текстуры, 402 ц Цвет, 44, 226 в реальном мире, 238 дополнительный, 433 кодирование, 344 логические операции, 301 маскировка, 301 объединение, 286 преобразование, 978 рисования, 234 согласование, 646 Ш Шейдер вершин, 898 высокоуровневый, 914 загрузка, 878 задание текста, 915 компиляция, 915 низкоуровневый, 878 объекты, 914 опции,898 создание и связывание, 878 удаление, 880 фрагментов, 872, 898, 899 э Экструзия, 1023 ЭЛТ, 40 Эрозия, 991
Научно-популярное издание Ричард С. Райт, мл., Бенджамин Липчак OpenGL. Суперкнига, 3-е издание Литературный редактор Верстка Художественный редактор Корректор ЖЕ Прусакова А В Назаренко С А Чернокозинский А В Назаренко Издательский дом Вильямс 101509, Москва \л Лесная д 4? стр I Подписано в печаль 01.02 2006 Формат 70x100/16 Гарнитура Times Печать офсетная Усл. печ. л S3 85 Уч -изз i 61 91 Тираж 2000 акз Заказ V 427 Отпечатано с зиапозитивов в ФГУП ’Печатный двор" им. А М Горького Федерального агентства по печати и массовым коммуникациям 197110 Санкт Петербчрг Чкиовскии пр 1т