Книга
Книга
Глава 1. Графические системы и модели25
Глава 2. Графическое программирование55
Глава 3. Ввод и взаимодействие с пользователем103
Глава 4. Объекты и геометрические преобразования147
Глава 5. Визуализация205
Глава 6. Закрашивание247
Глава 7. Алгоритмы формирования изображения287
Глава 8. Иерархические графические модели337
Глава 9. Операции с изображением на уровне растрового ПРЕДСТАВЛЕНИЯ375
Глава 10. Кривые и криволинейные поверхности419
Глава 11. Процедурные методы465
Глава 12. Визуализация данных научных исследований493
Приложение А. Демонстрационные программы523
Приложение Б. Абстрактные пространства в компьютерной
ГРАФИКЕ561
Приложение В. Матрицы569
Литература577
Содержание
Предисловие18
Нисходящий подход18
Программирование на языке С сприменением OpenGL19
На кого рассчитана эта книга20
Структура книги20
Изменения, внесенные во второеиздание21
Дополнительные источники23
Благодарности23
Глава 1
Графические системыи модели25
Области применениякомпьютерной графики26
Отображение информации26
Проектирование27
Моделирование28
Интерфейс пользователя28
Графическая система29
Пиксели и буферкадра29
Устройства вывода изображений31
Устройства ввода33
Изображение: физическое и синтезируемое33
Объекты и наблюдатели33
Свет и изображение35
Трассировка лучей36
Глаз человека38
Камера-обскура40
Моделирование камеры41
Интерфейс программиста43
Интерфейс прикладного программирования44
Парадигма “моделирование — тонирование”47
Архитектура графических систем47
Дисплейные процессоры48
Конвейерная архитектура49
Геометрические преобразования50
Отсечение50
Проективное преобразование51
Растровое преобразование51
Производительность работы геометрического конвейера51
Резюме52
Рекомендуемаялитература52
Упражнения53
Глава 2
Графическое программирование55
Узор Серпинского56
Перьевой плоттер57
Системы координат62
Прикладной интерфейс OpenGL63
Графические функции64
Интерфейс OpenGL65
Примитивы и атрибуты66
Многоугольники67
Типы многоугольников в OpenGL69
Текст70
Криволинейные объекты72
Атрибуты72
Цвет73
Цветовая система RGB77
Индексируемый цвет78
Настройка атрибута цвета80
Визуализация80
Визуализация двухмерных объектов81
Ортогональная проекция82
Матричный режим проецирования83
Функции управления84
Взаимодействие с подсистемой окон84
Соотношение сторон и видовые окна85
Функции main(), display() и myinitQ87
Структура программы88
Программа Gasket89
Многоугольники и рекурсия90
Трехмерный узор Серпинского92
Использование трехмерных точек92
Использование многоугольников в трехмерном пространстве94
Удаление невидимых поверхностей95
Резюме96
Рекомендуемая литература97
Упражнения98
Глава 3
Ввод и взаимодействие с пользователем103
Интерактивная компьютерная графика103
Устройства ввода105
Физические устройства ввода105
Логические устройства108
Показания и синхронизация109
Режимы ввода110
Клиенты и серверы112
Дисплейный файл113
Формирование дисплейного списка и преобразование его
в изображение115
Дисплейные списки и формирование текста117
Шрифты библиотеки GLUT120
Программирование ввода, управляемого событиями121
Использование устройств указания121
События окна124
События клавиатуры126
Функции отображения и простоя126
Управление окнами127
Меню127
Указание объектов129
Простая программа рисования130
Интерактивные программы анимации135
Вращающийся квадрат135
Двойная буферизация137
Проблемы с буферизацией138
Разработка интерактивных графических программ139
Инструментальные средства, экранные элементы управления и буфер кадра139
Резюме141
Рекомендуемая литература142
Упражнения142
Глава 4
Объекты и геометрические преобразования147
Скаляры, точки и векторы148
Геометрическое определение базовых типов148
Математическое определение: векторное и аффинное пространства150
Информационное определение150
Геометрические абстрактные типы данных151
Прямые152
Аффинное сложение153
Выпуклость153
Скалярное и векторное произведение154
Плоскости155
Трехмерные примитивы156
Системы координат и фреймы157
Замена систем координат159
Пример изменения представления161
Однородные координаты162
Пример перехода из одного фрейма в другой164
Фреймы и абстрактные типы данных166
Фреймы в OpenGL167
Модель разноцветного куба169
Моделирование куба170
Внешние и внутренние грани170
Структура данных для представления объектов171
Цвет куба172
Билинейная интерполяция173
Массивы вершин174
Аффинные преобразовании176
Поворот, сдвиг и масштабирование178
Плоскопараллельное смещение179
Поворот179
Масштабирование181
Преобразовании в однородных координатах182
Сдвиг182
Масштабирование183
Поворот184
Скос185
Суперпозиций преобразований186
Поворот вокруг произвольной фиксированной точки187
Поворот вокруг произвольной оси188
Преобразование экземпляра189
Поворот вокруг произвольной оси190
Матрицы преобразований в OpenGL193
Текущая матрица преобразования194
Поворот, сдвиг и масштабирование195
Поворот вокруг фиксированной точки средствами OpenGL195
Последовательность выполнения преобразований196
Вращение куба196
Загрузка матриц и использование стека матриц198
Взаимодействие пользователя с трехмерными графическими приложениями198
Использование областей экрана199
Виртуальный трекбол199
Плавное вращение201
Резюме202
Рекомендуемаялитература203
Упражнения203
Глава 5
Визуализация205
Классическая икомпьютерная визуализация205
Визуализация в классической графике207
Ортографические проекции208
Аксонометрические проекции208
Косоугольные проекции209
Визуализация с учетом перспективы210
Размещение камеры211
Настройка положения фрейма камеры212
Задание ориентации камеры216
Проецирование220
Перспективные проекции221
Ортогональная проекция223
Проективные преобразования в OpenGL224
Перспективные преобразования в OpenGL224
Параллельное проецирование в OpenGL226
Удаление невидимых поверхностей227
Путешествие с камерой по сцене228
Матрицы параллельного проецирования230
Нормализация проецирования230
Матрицы ортогонального проективного преобразования231
Косоугольная проекция233
Матрицы перспективного проецирования236
Перспективная нормализация236
Перспективное преобразование в OpenGL238
Проецирование и формирование теней240
Резюме242
Рекомендуемая литература243
Упражнения244
Глава 6
Закрашивание247
Свет и материя248
Источники света250
Цвет излучения251
Фоновое освещение252
■6.2.3. Точечный источник света252
Прожекторы253
Удаленный источник света254
Модель отражения Фонга255
Отражение фонового света257
Диффузное отражение257
Зеркальное отражение258
Вычисление векторов261
Нормаль к поверхности261
Угол отражения_263
Вектор половинного направления264
Преломление света265
Закрашивание многоугольников266
Плоское закрашивание266
Интерполяционное закрашивание и закрашивание по методу Гуро268
Закрашивание по методу Фонга269
Аппроксимация сферической поверхности рекурсивным разбиением 270
Описание источников света в OpenGL273
Спецификация материалов в OpenGL275
Закрашивание модели сферы276
Глобальное тонирование278
Трассировка лучей279
Метод анализа излучательности282
Резюме283
Рекомендуемая литература284
Упражнения284
Глава 7
Алгоритмы формирования изображения287
Четыре основные задачи288
Моделирование288
Геометрическая обработка289
Растровое преобразование289
Отображение290
Базовые стратегии реализации290
Реализация геометрических преобразований292
Отсечение отрезков294
Алгоритм Коэна-Сазерленда294
Алгоритм Лианга-Барского296
Отсечение многоугольников298
Отсечение примитивов других типов301
Прямоугольные оболочки301
Кривые, поверхности и надписи302
Отсечение в буфере кадра302
Отсечение в трехмерном пространстве303
Удаление невидимых поверхностей305
Удаление нелицевых граней307
Алгоритм z-буфера308
Сортировка по глубине310
Алгоритм построчного сканирования312
Растровое преобразование313
Алгоритм Брезенхэма315
Растровое преобразование многоугольников317
Тест принадлежности внутренней области318
Обработка многоугольников общего вида в OpenGL319
Растровое преобразование с использованием z-буфера320
Заполнение внутренней области и сортировка321
Заливка322
Алгоритмы заполнения построчным сканированием322
Особые случаи324
Сглаживание ступенек на изображении линий324
Отображение информации326
Цветовые системы327
Гамма-коррекция330
Формирование полутонов330
Резюме331
Рекомендуемой литература332
Упражнении333
Глава 8
Иерархические графические модели337
Символы и экземпляры338
Иерархические модели339
Модель руки робота341
Обход деревьев344
Алгоритм обхода с использованием стека345
Обход древовидных структур348
Анимация352
Графические объекты353
Методы, атрибуты и сообщения354
Объект cube355
Объекты и иерархия358
Геометрические объекты358
Граф сцены359
Другие типы древовидных структур361
Деревья в конструктивной геометрии тел361
Бинарные деревья разделения пространства364
4-арное и 8-арное деревья366
Графика и Web368
Сети и протоколы368
Гипермедиа и HTML370
Базы данных и VRML371
Java и аплеты372
Резюме372
Рекомендуемая литература373
Упражнения373
Глава 9
Операции с изображением на уровне растрового представления375
Буферы и наложение376
Наложение проективных текстур377
Наложение двухмерных проективных текстур378
Проективное наложение текстуры в системе OpenGL383
Генерация образцов текстур387
Наложение изображения окружающих предметов388
Наложение микрорельефа390
Запись в буферы391
Режимы записи битовых блоков392
Режим записи XOR393
Операции с пикселями в OpenGL394
Буферы OpenGL395
Использование растровых образов символов шрифта396
Пиксели и изображения397
Таблицы соответствия цветов398
Использование буферов в процедуре указания объекта400
Технология комбинирования изображений400
Поглощение света и смешивание изображений400
Смешивание изображений402
Смешивание изображений в OpenGL402
Сглаживание погрешностей дискретизации403
Тонирование сцен со множеством полупрозрачных объектов405
Эффект тумана и создание иллюзии глубины пространства406
Использование буфера-накопителя406
Дискретизация изображения409
Теория квантования по независимым переменным410
Восстановление непрерывной функции по дискретным выборкам413
Квантование по уровню415
Резюме416
Рекомендуемая литература417
Упражнения417
Глава 10
Кривые и криволинейные поверхности419
Представление кривых линий и поверхностей419
Представление в явной форме420
Неявная форма представления421
Параметрическая форма представления422
Параметрические полиномиальные кривые423
Параметрические полиномиальные поверхности423
Общая характеристика полиномиальной параметрической формы представления424
Параметрически заданные кубические кривые426
Интерполяция427
Функции смешивания429
Порция кубической интерполяционнойповерхности430
Эрмитова форма представления кривыхиповерхностей432
Форма Эрмита432
Геометрическая и параметрическая непрерывность434
Кривые и поверхности в форме Безье435
Кривые Безье435
Порции поверхности в форме Безье437
Кубические В-сплайны438
Кубические В-сплайны438
В-сплайны и базисные функции441
Сплайновые поверхности442
Обобщенные В-сплайны442
Рекурсивно определенные В-сплайны443
Равномерные В-сплайны444
Неравномерные В-сплайны445
NURBS — неравномерный рациональный В-сплайн445
Построение кривых и поверхностей446
Методы вычисления полиномов447
Рекурсивное разбиение кривых Безье448
Построение других типов полиномиальных кривых методом разбиения450
Разбиение поверхности Безье451
Пример: формирование изображения чайника452
Алгебраические поверхности454
Квадратичные поверхности454
Вычисление точек на квадратичной поверхности методом приведения лучей455
Кривые и поверхности в OpenGL456
Кривые Безье456
Поверхности Безье457
Отображение чайника Юта458
Функции отображения NURBS-кривых иповерхностей460
Квадратичные поверхности460
Резюме461
Рекомендуемая литература462
Упражнения462
Глава 11
Процедурные методы465
Особенности процедурных моделей465
Физические модели и система частиц467
Ньютоновы частицы468
Несвязанные частицы470
Силы упругости470
Силы взаимного притяжения и отталкивания472
Решение системы уравнений473
Ограничения475
Столкновения475
Частицы внутри сферической оболочки477
Мягкие ограничения479
Языковые модели479
Рекурсивные методы и фракталы483
Масштаб и длина483
Размерность фрактала484
Разбиение в средней точке и броуновское движение .486
Формирование изображения горы с помощью фракталов487
Множество Мандельброта488
Резюме491
Рекомендуемая литература491
Упражнения491
Глава 12
Визуализация данных научных исследований493
Данные+геометрия493
Поля превышений и линии уровня494
Сети495
Вычерчивание линий уровня498
Метод маркированных квадратов498
Визуализация поверхностей и скалярных полей505
Объемное множество данных505
Визуализация функций, заданных в неявной форме506
Изоповерхности и метод маркированных кубиков508
Непосредственное отображение объема510
Управление цветом и коэффициентом прозрачности511
Отображение скалярного поля с помощью отпечатков512
Трассировка лучей в скалярном поле513
Наложение текстуры на объем514
Визуализация векторных полей515
Отрезки переменной длины515
Бусинки516
Цвет516
Треки частиц и линии потока517
Визуализация тензорных полей519
Резюме520
Рекомендуемая литература520
Упражнения521
Приложение А
Демонстрационные программы523
АЛ. Двухмерный узор Серпинского524
А.2. Рекурсивный алгоритм построения узора Серпинского526
А.З. Трехмерный узор Серпинского528
А.4. Рекурсивный алгоритм построения трехмерногоузора Серпинского 529
А.5. Программавычерчивания квадрата532
А.6. Программарисования534
А.7. Программаотображенияс двойной буферизацией541
А.8. Программаотображениявращающегосякуба544
А.9. Вращение куба с использованием массива вершин547
АЛО. Вращающийся куб, управляемый трекболом549
АЛ 1. Изменение положения наблюдателя554
АЛ 2. Построение сферы556
Приложение Б
Абстрактные пространства в компьютерной графике561
Б.1. Скаляры561
Б.2. Векторное пространство562
Б.З. Аффинное пространство564
Б.4. Евклидово пространство566
Б.5. Проекции вектора567
Б.6. Ортогонализация Грама-Шмидта567
Б.7. Рекомендуемая литература568
Упражнения568
Приложение В
Матрицы569
Основные определения569
Операции над матрицами570
Матрицы-строки и матрицы-столбцы571
Ранг матрицы572
Изменение представления573
Векторное произведение574
Рекомендуемая литература575
Упражнения575
Литература577
Предисловие
К
нига, которую вы держите в руках, представляет собой вводный курс компьютерной графики, в котором основной акцент сделан на вопросах прикладного программирования. В первом издании, которое вышло в свет в 1997 году, я отмечал, что за семь лет, прошедших после публикации моей предыдущей книги по компьютерной графике, в этой области произошли разительные изменения — она развивалась со скоростью, превзошедшей самые смелые ожидания специалистов, в том числе и мои собственные. За последние три года эти темпы не только не уменьшились, а даже возросли. Полнометражные кинофильмы, в производстве которых использована компьютерная анимация, имеют не только громадный зрительский, но и коммерческий успех. Включение в кинофильмы эффектных сцен, сделанных с помощью компьютера, стало в последние годы не исключением, а нормой, и подчас такие сцены неотличимы от снятых на “натуре”. Особый интерес к графическим приложениям вызывает их применение в среде Internet.
За последние годы не только возросли функциональные возможности средств компьютерной графики, но и значительно снизилась стоимость графических рабочих станций, причем это характерно для установок всех классов, как простейших, так и профессиональных. В течение нескольких лет стоимость графической станции, способной формировать около одного миллиона трехмерных многоугольников в секунду с учетом эффектов освещения и наложения текстуры, понизилась со $100 000 до нескольких тысяч. Значительно более доступными по цене стали и специализированные графические платы для персональных компьютеров. При цене в несколько сотен долларов эти платы обеспечивают почти такие же возможности, как и графические рабочие станции. В области программного обеспечения также произошли серьезные изменения. OpenGL стала своего рода стандартным интерфейсом для программистов как при написании прикладных программ, так и при разработке программных продуктов высокого класса— от интерактивных игр до систем визуализации результатов научных исследований.
Нисходящий подход
Отмеченные достижения еще более упрочили мою веру в преимущество нисходящего (сверху вниз) подхода при изложении вводного курса компьютерной графики. Хотя на факультетах информатики и вычислительной техники многих вузов читают несколько курсов лекций, имеющих отношение к этой дисциплине, большинство студентов предпочитают прослушать только какой-либо один из них. Обычно такой курс включается в учебную программу после прослушивания курсов по основам программирования, структурам данных, теории алгоритмов, технологии программирования и базовых математических курсов.
Сам предмет компьютерной графики позволяет преподавателю так организовать этот курс, что он будет не только познавательным, но и увлекательным для слушателей. При планировании занятий я стараюсь предоставить студентам возможность как можно раньше приступить к программированию задач трехмерной графики, а анализ алгоритмов нижнего уров-
ня, таких как вычерчивание линий и заливка многоугольников, откладываю на более позднее время. Я возвращаюсь к ним тогда, когда студенты уже могут самостоятельно программировать построение графических изображений.
Джон Кемени (John Kemeny), один из пионеров преподавания этой дисциплины в США, использовал для обоснования такой концепции преподавания понятную многим аналогию с вождением автомобиля. Для того чтобы сесть за руль и стронуться с места, совсем не обязательно знать, что происходит под капотом, но тот, кто не знаком с правилами движения и основными приемами управления автомобилем, обречен сидеть на заднем сиденье, а не на месте водителя. Кемени различает три подхода к обучению. Первый — алгоритмический — обучать сначала тому, как функционирует автомобиль в целом и отдельные его агрегаты: двигатель, трансмиссия, топливная система и т.д. Второй подход — потребительский — нанять шофера, устроиться на заднем сиденье и созерцательно глядеть на мир за окном. Третий подход— это подход программиста, который я и отстаиваю в этой книге,— научиться водить автомобиль и ориентироваться в обстановке на дороге, пренебрегая детальным знанием о том, что происходит под капотом. Овладевший этими знаниями, всегда сможет добраться до места назначения (если, конечно, автомобиль будет исправен — но это уже другая песня). Те, кто давно делает бизнес на прокате автомобилей, выразили эту мысль простой формулой: “Предоставьте нам усадить вас на место водителя”.
Программирование на языке С с применением OpenGL
В прежние времена наиболее серьезные сложности при чтении курса компьютерной графики и подготовке учебников по этой дисциплине были связаны с отсутствием общепринятой графической библиотеки или интерфейса прикладного программирования (API — Application Programming Interface). Из-за этого приходилось приобретать специализированные средства по довольно высокой цене, которые были недоступны большинству студентов вне пределов вуза, страдали отсутствием общности и были довольно сложны в освоении. Появление OpenGL в значительной мере сняло эти вопросы, в чем на собственном опыте убедились те, кто ранее использовал в практике преподавания как другие пакеты API (такие как GKS и PUIGS), так и “доморощенное” программное обеспечение. На сегодняшний день графическая система OpenGL поддерживается большинством производителей рабочих графических станций, а независимые разработчики создали средства ее поддержки для большинства существующих аппаратных и программных платформ. Эта система доступна тем, кто работает в операционной среде Microsoft Windows (как 98, так и NT), а пользователей компьютеров Apple фирма-изготовитель известила о том, что OpenGL будет включена в качестве базового компонента в будущие версии операционной системы. Свободно распространяются исходные программные коды системы Mesa— пакета API на базе OpenGL,— которые можно компилировать в большинстве операционных систем, в том числе и Linux.
Конечно, содержание курса компьютерной графики выходит далеко за пределы простого руководства по применению конкретного пакета API, но наличие хорошего базового пакета значительно упрощает как преподавание, так и усвоение студентами ключевых тем этой дисциплины — создание изображений трехмерных объектов, закрашивание, графические системы с архитектурой “клиент/сервер”, моделирование и создание прикладных графических систем. Я полагаю, что широкие функциональные возможности OpenGL и тщательно продуманная структура этой системы послужат хорошим фундаментом для изложения как теоретических, так и практических аспектов этого предмета, в том числе и новейших идей, таких как наложение текстур и их комбинирование (эти функции не поддерживались прежними пакетами API).
Я перешел на использование OpenGL в своей преподавательской практике примерно 5 лет назад, и уже первые результаты меня изумили — в середине первого семестра каждый сту-
дент мог написать довольно сложную программу построения изображения трехмерных объектов, в которой требуется знание не только математики трехмерного моделирования, но и умение работать с событиями. За предыдущие 15 лет преподавания я и близко никогда не подходил к подобным результатам. Опыт преподавания курса на новой базе подвел меня к мысли полностью переделать написанный ранее учебник.
Настоящая книга представляет собой учебник по компьютерной графике, а не руководство пользователя по работе с OpenGL. Поэтому я не считал необходимым освещать в ней все тонкости использования этого языка, а описывал только то, что имеет непосредственное отношение к теоретическим и практическим задачам, рассматриваемым в книге. Я старался таким образом использовать OpenGL для иллюстрации тех или иных идей, чтобы те читатели, которые пользуются другим пакетом API, могли без особого труда воспринимать излагаемый материал.
Помимо этого я использую в книге язык С. У читателей может возникнуть вполне резонный вопрос: “Почему С, а не C++, Java или любой другой объектно-ориентированный язык программирования?” Мой выбор обоснован двумя соображениями. Во-первых, система OpenGL не относится к объектно-ориентированным системам программирования, а потому использование C++ или Java не внесет ничего особенного в большинство излагаемых тем. Исключение составляет тема структуры библиотеки промежуточных программ между OpenGL и пользователем, но я решил вообще не касаться ее, поскольку это сделает книгу менее доступной для студентов, владеющих навыками программирования, но незнакомых с методикой объектно-ориентированного программирования.
На кого рассчитана эта книга
В первую очередь я адресую эту книгу студентам старших курсов и аспирантам первого года обучения, специализирующимся в области информатики и вычислительной техники, а также студентам других специальностей, имеющим достаточный опыт программирования. Книга будет полезна и многим профессионалам. Я провел около сотни краткосрочных курсов по компьютерной графике для профессионалов, и опыт преподавания для такой аудитории оказал немалое влияние на отбор материала для этой книги.
Необходимым условием для успешного усвоения материала книги является достаточно глубокие знания языка программирования С, знакомство с основными идеями объектноориентированного программирования и хотя бы начальные познания в области линейной алгебры и тригонометрии. Я полагаю, что математическая подготовка студентов и аспирантов, специализирующихся в области информатики и вычислительной техники, играет очень важную роль в изучении большинства дисциплин. Поэтому я постарался включить в материал книги те разделы линейной алгебры и геометрии, которые необходимы для понимания основ компьютерной графики. Этот материал я выделил в два отдельных приложения.
Структура книги
Предлагаемая вашему вниманию книга состоит из 12 глав. В главе 1 представлен обзор методов формирования изображений оптическими приборами, где читатель сразу же познакомится с основными концепциями создания изображений трехмерных объектов. В главе 2 читатель знакомится с методикой программирования с использованием OpenGL. Хотя первая программа, рассматриваемая в этой главе (а в каждой главе рассматривается одна или несколько законченных программ), имеет дело с двухмерными объектами, они “встраиваются” в трехмерную среду. В главе 3 обсуждаются современные тенденции создания интерактивных графических систем типа “клиент/сервер” и методика разработки графических программ, управляемых событиями. В главах 4 и 5 основное внимание уделено концепциям формирова-
ния изображений трехмерных объектов и сцен; в главе 4 речь идет о математическом аппарате описания трехмерных объектов, а в главе 5 рассматриваются методы их отображения. В главе 6 представлен вводный материал о взаимодействии освещения и материалов и о методах закрашивания освещенных поверхностей. Материал первых шести глав следует изучать в той последовательности, в которой он изложен в данной книге. Это должно занять примерно 10 недель при 15-недельном семестре.
Материал следующих пяти глав можно изучать в произвольном порядке. Эти главы не имеют столь жесткой структуры изложения, как предыдущие, и читатель может просмотреть их и получить общее представление или выбрать отдельные темы, и изучить их более детально, чем остальные. В главе 7 представлен обзор методов закрашивания поверхностей, используемых в компьютерной графике. Читатель имеет возможность подробно познакомиться с одним-двумя алгоритмами выполнения каждого из основных этапов процесса построения изображения. Глава 8 включает несколько тем, касающихся иерархической организации графических моделей и использования объектно-ориентированного подхода. Рассмотренные в ней темы охватывают обширный материал от создания моделей, инкапсулирующих отношения между компонентами, до использования графики в Internet. В главе 9 рассматриваются новые возможности современных аппаратных графических средств и методы поддержки этих средств, реализованные в OpenGL. Эти методы предполагают интенсивное использование разнообразных буферов. Заключительные разделы этой главы посвящены обсуждению вопросов квантования графического изображения и минимизации возникающих при этом искажений.
В главе 10 рассматриваются кривые и поверхности. Процедурные методы построения геометрических моделей на базе многоугольников описаны в главе 11. В этой же главе читатель познакомится с языковыми моделями, фракталами и системами частиц. В главе 12 речь идет о применении графических средств при визуализации результатов научных расчетов. Эта область применения машинной графики позволяет продемонстрировать весь рассмотренный в предшествующих главах арсенал методов и средств.
Программы, приведенные в основном тексте книги, собраны отдельно в приложении А. Тексты программ доступны и в электронном виде.
Изменения, внесенные во второе издание
Реакция читателей на появление первого издания этой книги была исключительно позитивной. Особой похвалы удостоились описание методики программирования задач компьютерной графики на OpenGL и принятый в книге нисходящий подход к изложению материала. Но, тем не менее, я включил в настоящее издание много нового материала, а прежний значительно переработал.
Внесенные изменения я бы разделил на три категории. Во-первых, я переработал изложение некоторых математических вопросов (надеюсь, это пошло на пользу полноте и ясности материала) и добавил новые примеры. Во-вторых, я значительно дополнил материал о современных областях применения компьютерной графики, в особенности в научных исследованиях. В-третьих, в книгу включено значительно больше практических программ на базе OpenGL, причем в них использованы расширения, появившиеся в OpenGL 1.1, касающиеся, в частности, массивов вершин. В результате мне пришлось значительно переработать программы, приведенные во второй половине книге.
Я добавил в главу 2 два новых примера. Первый относится к использованию рекурсии при построении узора Серпинского и не только демонстрирует, как организуется рекурсия в OpenGL, но и показывает, какие интересные программы могут создавать студенты, даже не изучив досконально все возможности OpenGL. Второй пример демонстрирует нюансы построения трехмерного узора Серпинского, и при создании этой программы студенты знако-
мятся с элементарными приемами создания изображений трехмерных объектов, не требующими использования сложных преобразований координат. Большая часть материала, относящегося к свойствам многоугольников, была перенесена в главу 7.
В главе 3 существенной переработке подверглась программа вычерчивания. Из нее удалены часы, поскольку для их реализации использовались функции, специфичные для операционной системы UNIX, но вопросы использования временной синхронизации в этой главе по- прежнему обсуждаются. В главу включен новый пример, демонстрирующий вращение квадрата на экране, который иллюстрирует описанные в основном тексте методы анимации и применение двойной буферизации.
В главе 4 более детально описаны абстрактные геометрические объекты и дополнены примерами, в которых показано, как выполняется изменение координат и фреймов. Показано также, как использовать при формировании геометрических моделей массивы вершин — новинку версии OpenGL 1.1. Еще один новый пример демонстрирует использование виртуального трекбола для интерактивной реализации преобразований координат в трехмерном пространстве.
Я надеюсь, что изложение математических основ в главе 5 стало более доступным для студентов по сравнению с первым изданием. Я включил в эту главу также и пример формирования теней, в котором использованы матрицы проецирования. Главаб осталась практически в том же виде, что и в первом издании. Глава 7 также подверглась очень незначительным изменениям — я отредактировал ее название, которое теперь, на мой взгляд, точнее передает смысл представленного материала, и перенес в нее из других глав детальное описание методов манипуляции с многоугольниками.
В главе 8 значительно расширен материал, касающийся древовидных структур. В главу включен второй пример построения изображения робота, в котором использован обобщенный рекурсивный алгоритм обхода дерева. Переработан и материал, касающийся графов сцен. Я рассматриваю эту тему как фундаментальную, имеющую исключительно важное значение для понимания идей объектно-ориентированного подхода в компьютерной графике. В этой главе рассматриваются и побочные применения древовидных структур — бинарные деревья разделения пространства (BSP — binary spatial-partition tree), 4- и 8-арные деревья {quadtree и octree). В заключительную часть этой главы включен материал об использовании компьютерной графики в Internet, о языках VRML и Java (при этом от читателя не требуется знание каждого из этих языков).
Глава 9 включает материал о наложении текстур и использовании буферов, представленный в главе 10 первого издания. В данном издании я поменял местами материал глав 9 и 10, учитывая важность применения методов наложения в современных графических приложениях. В главе 9 читатель найдет и подробное описание средств OpenGL, применяемых для выполнения наложения. Глава 10 настоящего издания представляет собой переработанную главу 9 первого издания, причем в нее добавлены новые примеры формирования кривых и поверхностей с помощью OpenGL.
Глава 11 базируется на тех сведениях о процедурном моделировании, которые изложены в главе 8 первого издания. В нее добавлен более пространный материал о системах моделирования поведения частиц, в частности описание методов решения дифференциальных уравнений движения частиц в силовом поле. Включены также примеры моделирования поведения системы взаимодействующих частиц при наличии между ними пружинных связей отталкивающих сил.
В главе 12 собран материал о визуализации пространственных объектов, разбросанный в первом издании по трем главам. Такая концентрация материала позволяет связно описать методы визуализации различных объектов, начиная с контуров и сеток и заканчивая скалярными, векторными и тензорными полями. Материал о векторных и тензорных полях, представленный в этой главе, а также примеры в прежнем издании отсутствовали.
Дополнительные источники
Текущую информацию о дополнениях к этой книге вы можете получить на Web-сервере издательства Addison-Wesley по адресу http://www.aw.com/cseng.
Материал, доступный в электронном виде, включает решение некоторых упражнений, связи с другими ресурсами и примеры текстов программ. Эту' информацию, а также некоторую другую можно получить на моем Web-сервере по адресу http://www.cs.unm.edu/angel.
Тексты программ из этой книги и другие примеры можно получить по Internet на ftp- сервере по адресу ftp.cs.unm.edu в каталоге pub/angel/BOOK.
Буду рад получить от читателей предложения о любых других дополнительных материалах и замечания по материалу книги. Мой адрес angel@cs.unm.edu.
Благодарности
В течение нескольких последних лет я имел удовольствие работать с прекрасными студентами университета Нью-Мексико. Именно они пробудили во мне интерес к OpenGL, и я многому научился благодаря общению с ними. Это — Хью Бамгарнер- Керби (Hue Bumgarner-Kirby), Пат Кроссно (Pat Crossno), Томми Дэниэл (Tommie Daniel), Лиза Десджарле (Lisa Desjarlais), Ким Эдлунд (Kim Edlund), Ли Анн Фиск (Lee Ann Fisk), Мария Галлегос (Maria Gallegos), Брайан Джонс (Brian Jones), Кристофер Джордан (Christopher Jordan), Макс Хазелриг (Max Hazelrigg), Томас Келлер (Thomas Keller), Пат Мак-Кормик (Pat McCormick), Эл Мак-Ферсон (Al McPherson), Мартин Мюллер (Martin Muller), Джим Пинкертон (Jim Pinkerton), Джим Прейетт (Jim Prewett), Дейв Роджерс (Dave Rogers), Хэл Смайер (Hal Smyer), Дейв Вик (Dave Vick) и Брайан Уайли (Brian Wylie). Именно они разработали большинство примеров и подготовили цветные иллюстрации.
Первое издание этой книги я готовил в период годичного отпуска. Работая над ним, я успел побывать в пяти странах и приобрел громадный опыт работы на портативном компьютере и общения через Internet. Как бы там ни было, но к концу отпуска я справился с задачей, в чем немалую роль сыграла помощь множества людей и организаций. Я в неоплатном долгу перед Джонасом Монтилва (Jonas Montilva) и Крисом Биркбеком (Chris Birkbeck) из университета де Дос-Андес (Венесуэла), Родриго Галлегосом (Rodrigo Gallegos) и Аристид Новоа (Aristides Novoa) из Технологического университета Экиночиал (Эквадор), Лонг Вен Ченем (Long Wen Chang) из Национального университета Цин Хуа (Тайвань), а также Кин Хонг Вонгом (Kin Hong Wong) и Пень Ан Хенгом (Pheng Ann Heng) из Китайского университета в Гонконге. Все эти визиты стали возможными благодаря Рамиро Джордану (Ramiro Jordan) из университета Нью-Мексико. Где бы я ни был, я всегда поддерживал связь с Джоном Брайером (John Brayer) и Джесоном Стюартом (Jason Stewart) из университета Нью-Мексико и Хелен Гольдштейн (Helen Goldstein) из издательства Addison-Wesley, которые очень помогли мне в подготовке материала для этой книги.
Второе издание было целиком подготовлено в университете Нью-Мексико, в чем большую помощь мне оказали сотни читателей, приславших замечания и пожелания к первому изданию.
Я не могу не поблагодарить руководство компаний Silicon Graphics и Apple Computer, которые предоставили в мое распоряжение необходимое оборудование. Я постоянно консультировался с Джоном Шимпфом (John Schimpf) из Silicon Graphics относительно OpenGL. Фирмы Conix Enterprises, Portable Graphics, Template Graphics and Metrowerks благосклонно предоставили мне необходимое программное обеспечение, и я смог протестировать все программы на разных платформах.
Множество других людей оказали мне очень существенную помощь в подготовке этой книги. Я благодарен Гонсало Картагенова (Gonzalo Cartagenova), Тому Коделу (Tom Caudell), Кетти Коллинз (Kathi Collins), Кетлин Дениэлсон (Kathleen Danielson), Роджеру Эриху (Roger Ehrich), Чаку Хансену (Chuck Hansen), Марку Хенне (Mark Henne), Бернарду Море (Bernard Moret), Дику Нордхаусу (Dick Nordhaus), Хелене Соана (Helena Saona), Гвин Силвэн (Gwen Sylvan) и Мэсону Во (Mason Woo). Сообщество пользователей OpenGL должно знать имена Марка Килгарда (Mark Kilgard), Брайана Пола (Brian Paul) и Нейт Робинс (Nate Robins), которые подготовили программы, необходимые для проверки OpenGL-кода на разных платформах. Я особенно благодарен Бену Бедерсону (Ben Bederson) и его студентам, которые на себе “испытали” черновую рукопись этой книги. Цветные иллюстрации на вклейке — дело рук четырех студентов этого курса.
Перед тем как я отдал рукопись в издательство, ее тщательно просмотрели многие специалисты, чтобы оценить полноту и ясность изложения материала для разных категорий читателей. Я благодарен рецензентам, от которых получил множество ценных замечаний, — Хамиду Арабниа (Hamid Arabnia) из университета Джорджия, Уэйену Карлсону (Wayne Carlson) из университета штата Огайо, Норману Чину (Norman Chin) из Silicon Graphics, Скотту Гриссому (Scott Grissom) из университета штата Иллинойс (Спрингфилд), Дику Филлипсу (Dick Phillips), ранее работавшему в Национальной лаборатории в Лос-Аламосе, Тому МакРейнолдсу (Tom McReynolds) из Silicon Graphics, Джейн Вильгельме (Jane Wilhelms) из Калифорнийского университета (Санта-Круз) и Эдварду Вонгу (Edward Wong) из Бруклинского политехнического института. Хотя окончательный вариант может и не отображать всех высказанных точек зрения — которые у рецензентов значительно расходятся, — каждое из них так или иначе повлияло на содержание этой книги.
Я не могу не поблагодарить и всех тех сотрудников издательства Addison-Wesley, благодаря которым эта книга появилась на полках магазинов и библиотек. Мой редактор, Питер Гордон (Peter Gordon),— это человек, работать с которым одно удовольствие, и я, признаюсь, иногда сожалею, что книга уже закончена. Особо благодарен я Лин Дюпре (Lyn Dupre). Я ведь не “литератор от Бога”. Если бы читатели имели возможность взглянуть на первый вариант рукописи, они смогли бы оценить тот вклад, который внесла Лин в эту книгу.
Моя жена, Роз-Мари Мольнар (Rose Mary Molnar), подготовила рисунки к предыдущему изданию, многие из которых остались и в этом издании. Со свойственной ей рассудительностью она не стала возражать против использования для этой работы нашего единственного ноутбука и тем самым сохранила мир и сердечность наших отношений, за что, среди прочего, заслуживает дополнительных тысяч слов благодарности.
ГЛАВА
1
Графические системы и модели
С
овершенно очевидно, что в новом тысячелетии информационные и коммуникационные технологии будут играть важнейшую роль во всех сферах жизни человечества. В таких областях, как кинематография, издательское и банковское дело, в образовательных учреждениях, внедрение этих технологий уже произвело поистине революционный переворот. Интеграция в единой системе компьютерной графики отдельных компьютеров, сетей и систем машинного зрения открыла новые пути отображения информации, “проникновения” в виртуальный мир и организации взаимодействия человека и машины.
Компьютерная графика ^computer graphics) — это область информатики (науки о компьютерах — computer sciences), в сферу интересов которой входят все аспекты формирования изображений с помощью компьютеров. Эта область начала развиваться около 40 лет назад. В те годы удавалось добиться отображения нескольких десятков отрезков на экране электронно-лучевой трубки (ЭЛТ), а современные системы машинной графики позволяют создавать изображения, практически неотличимые по качеству от фотографических снимков. Общепринятой практикой стало сейчас обучение пилотов с помощью систем моделирования реальной ситуации, как она видится из кабины самолета во время пилотирования, создание изображений виртуального динамического мира во всем его многообразии в реальном масштабе времени. На экран выходят полнометражные кинофильмы, в которых нет ни одного кадра, снятого “на натуре” или в павильоне киностудии, а все действие “разворачивается” в памяти компьютера.
В этой главе мы начнем нашу “экскурсию” в мир компьютерной графики с краткого обсуждения областей ее применения. Затем будет дан обзор графических систем и способов формирования изображений. На протяжении всей книги я буду постоянно обращать ваше внимание на тесную связь методов компьютерной графики с “традиционными” способами формирования изображений, такими как рисование “от руки” или фотография. Вы увидите, насколько такие аналогии помогают при создании прикладных графических программ, библиотек и структурном синтезе графических систем.
В процессе изложения материала в данной книге широко используется одна из существующих на сегодняшний день программных графических систем — OpenGL, которая в по-
следнее время рассматривается многими разработчиками как своего рода стандарт для создания графических приложений. Изучение OpenGL не представляет никакой сложности для мало-мальски опытного программиста, и в то же время эта система располагает всем набором средств, характерных для большинства современных графических систем. При изложении материала мы будем использовать нисходящий подход (сверху вниз). Это означает, что я предлагаю вам как можно раньше приступить к разработке практических программ, способных создавать графические картинки, пусть поначалу и несложные. И уже после того как вы добьетесь появления картинки на экране с помощью созданных программ, мы обсудим, как выполняются отдельные графические функции библиотеки и каким образом аппаратно реализуется формирование изображения на экране. Материал этой главы должен дать вам общее представление о том, чего можно достичь, создавая графические программы.
LL Области применения компьютерной графики
Развитие компьютерной графики определяется двумя факторами: реальными потребностями потенциальных пользователей и достижениями в области аппаратного и программного обеспечения. Хотя компьютерная графика используется в самых различных сферах жизни современного общества, можно выделить четыре главные области ее применения.
Отображение информации.
Проектирование.
Моделирование.
Пользовательский интерфейс.
Хотя во многих практических приложениях можно обнаружить характерные признаки двух или более перечисленных групп, развитие каждой из этих групп шло своим путем.
Отображение информации
Классические графические технологии развивались как средство передачи информации в человеческом обществе. Хотя аналогичную роль играет и язык (как в устной, так и в письменной форме), зрительная система человека обладает гораздо большими возможностями (специалист по информатике сказал бы “большей пропускной способностью”), поскольку выполняет функции и обработки данных, и распознавания образов. Еще 4000 лет назад древние вавилоняне использовали графические планы при строительстве каменных сооружений. Древние греки еще в конце первого тысячелетия до н.э. были способны преподносить свои архитектурные идеи в графическом виде, хотя соответствующие математические методы появились только в эпоху Ренессанса. Сегодня подобного рода информация создается архитекторами, конструкторами и чертежниками с помощью компьютерных систем.
На протяжении многих столетий картографы и астрономы вычерчивали карты, чтобы представить информацию о расположении небесных тел и географических областей. Нет смысла говорить о том, какое значение имеют такие карты сегодня не только для навигации на Земле и в Космосе, но и для решения повседневных задач человечества с помощью геоин- формационных систем. Сейчас любую карту можно в считанные минуты получить и обработать с помощью Internet.
За последние 100 лет статистики использовали самые разные технологии для представления в графическом виде первичных данных и результатов их статистической обработки. Такая форма представления множества собранных данных является наиболее информативной. Сегодня и в этой области не обойтись без компьютеров, которые не только обрабатывают собранные данные, но и формируют соответствующие графики, используя самые разнообразные способы их представления, в том числе и с применением цвета. Только такая форма
представления позволяет человеку без труда интерпретировать информацию, содержащуюся в гигабайтах собранных первичных данных.
Множество важных и интересных проблем анализа данных ставит и медицина. Новые технологии визуализации состояния человеческого организма, такие как компьютерная томография, магниторезонансное обследование, ультразвуковое зондирование и позитронноэмиссионная томография, позволяют получать трехмерную информацию, которая может быть впоследствии обработана вычислительными методами. Хотя сами первичные данные формируются специальной медицинской аппаратурой, последующая компьютерная обработка и созданное цветное изображение позволяют специалистам достаточно просто интерпретировать их.
С появлением суперкомпьютеров стало возможным исследовать проблемы, ранее отнесенные к классу неразрешимых. В области визуализации результатов экспериментов и научных расчетов средства компьютерной графики являются мощным инструментом для правильной интерпретации огромных массивов первичных данных. Исследования в таких областях, как течение жидкостей, молекулярная биология и математика, не обходятся без преобразования первичных данных в зримые геометрические образы, что помогает лучше понять суть происходящих процессов. Например, на ил. 9 цветной вклейки представлено изображение динамики движения жидкости в мантии Земли. Исходные данные были получены в результате математического моделирования процесса, а затем использовались различные методы визуализации этих данных, описанные в главах 8, 9 и 12 настоящей книги.
Проектирование
Проектирование является одной из основных стадий создания изделий и сооружений в технике и строительстве. Задавшись спецификацией основных характеристик разрабатываемого изделия, конструктор ищет решение, оптимальное с точки зрения затрат и технических параметров. Процесс проектирования по самой своей природе является итеративным — очень редко бывает так, что заданные характеристики допускают только один вариант решения. Как правило, исходная формулировка задачи проектирования оказывается недоопределенной, т.е. допускает множество решений, или переопределенной, другими словами, в таком виде задача оказывается неразрешимой. В первом случае конструктору или проектировщику нужно перебрать множество вариантов перед тем, как остановиться на одном из них, причем часто получается так, что очередной вариант появляется в результате анализа и устранения недостатков предыдущего. Во втором случае приходится добиваться изменения исходной формулировки задачи, что также включает поиск вариантов, наиболее близких по характеристикам к заданной спецификации.
2727Достоинства парадигмы взаимодействия конструктора с изображением проектируемой конструкции на экране ЭЛТ впервые подметил Айвен Сазерленд (Ivan Sutherland) еще лет сорок назад. Сегодня уже ни у кого не вызывает сомнения прогрессивность применения средств графического взаимодействия конструктора и компьютера в системах автоматизации проектирования (САПР). Такие системы применяются в самых разнообразных отраслях техники — от проектирования микросхем со сверхвысокой степенью интеграции (СБИС) до автомобилей, самолетов и космических аппаратов. Не менее широкое применение такого рода системы нашли в архитектурном проектировании и строительстве. В различных областях проектирования графические средства используются по-разному. Например, при проектировании СБИС интерфейс между конструктором и компьютером (т.е. программами автоматического синтеза электронных схем) реализуется посредством меню и пиктограмм. После того как таким способом будет создан вариант проектируемой конструкции, система анализирует ее параметры и представляет характеристики изделия также в графическом виде. Не менее широкие возможности открывают САПР и перед архитекторами. На ил. 3 и 4 цветной вклейки показаны
27
27
Области применения компьютерной графики
два вида спроектированного здания, созданных с помощью САПР. Эти рисунки демонстрируют. какими возможностями обладают современные САПР при отображении одного и того же объекта на разных стадиях проектирования.
Моделирование
Как только графические системы стали обладать достаточной производительностью для создания сложных динамических изображений, возникла идея применить их в качестве средства моделирования реальной обстановки (симулятора) на разного рода тренажерах. Первыми такие системы освоили авиаторы и использовали для обучения пилотов на земле. Это позволило значительно снизить стоимость обучения, гарантируя при этом его высокое качество и безопасность. Использование в современных системах специальных БИС позволило настолько снизить стоимость подобного рода устройств, что они дошли до уровня детских игрушек. Например, с помощью средств компьютерной графики возможно создагие графической модели робота, которая используется при планировании технологических процессов и подготовке программ управления роботами, работающими в окружения множества других элементов производственной системы.
В телевидении, кинематографии и рекламном деле в последнее время также широко используются средства компьютерной графики, позволяющие создавать динамические изображения, практически неотличимые от снятых “на натуре”. Именно их мы часто видим на экранах телевизоров и в кинотеатрах. Подобного рода изображения (правда, статические) заполонили и страницы популярных периодических изданий. Стоимость создания полнометражного кинофильма с помощью компьютера сравнима со стоимостью съемки такого же фильма на “натуре” и в павильонах студии, но при этом можно создавать такие спецэффекты и трюки, которые недоступны для “живой” съемки. В главах б и 9 будут рассмотрены различные методы моделирования освещения, которые используются при построении изображений, близких по качеству к фотографиям (для краткости будем их впредь именовать фотореалистическими). Ил. 14 на цветной вклейке демонстрирует возможность создания компьютерными средствами изображения, которое невозможно получить иными средствами, хотя в нем полностью соблюдаются все законы оптики. Изображения на ил. 10 и 11 цветной вклейки также созданы компьютером с использованием современных методов тонирования.
В последнее время появилась еще одна область применения средств компьютерной графики, которая получила наименование формирование виртуальной реальности (VR— virtual reality). В VR-системах человек-наблюдатель пользуется специальным шлемом с парой миниатюрных дисплеев, на экранах которых формируются разные изображения для правого и левого глаза. В результате создается стереоэффект. Кроме того, положение и ориентация головы наблюдателя постоянно анализируются и соответственно изменяется изображение на экранах дисплеев. Если добавить к этому еще и средства “влияния” на эту среду вроде перчаток с силомоментными датчиками, а также звуковое сопровождение, то создается полная иллюзия погружения в виртуальную среду. Наблюдатель чувствует себя участником происходящего. Это уже не просто развлечение, а инструмент для практического применения. Например, с помощью такой системы хирург может отработать методику проведения операции, астронавт может подготовиться к выходу в открытый космос и проведению ремонтных работ.
Интерфейс пользователя
В последнее время визуальная парадигма стала доминирующей в сфере взаимодействия пользователя с компьютером. Визуальный метод предполагает использование различного рода окон, пиктограмм, меню и устройств указания, таких как мышь. С точки зрения пользователя оконные операционные системы — X Window, Microsoft Windows и операционная система Macintosh — отличаются только деталями. Сейчас уже миллионы людей пользуются ус-
лугами сети Internet. Доступ к этой сети немыслим без графических программ-броузеров, таких как Netscape и Internet Explorer, которые используют, по сути, одни и те же графические средства интерфейса. Мы настолько к ним привыкли, что часто и не задумываемся, что эти средства также относятся к инструментам компьютерной графики.
Хотя мы уже и привыкли к такому стилю в организации пользовательского интерфейса в большинстве графических рабочих станций, развитие компьютерной графики позволяет использовать и другие формы интерактивного взаимодействия.
Графическая система
Система компьютерной графики является прежде всего вычислительной системой и, как таковая, включает все компоненты вычислительной системы общего назначения. Начнем наш обзор с блок-схемы, представленной на рис. 1.1, на которой показаны основные компоненты системы:
процессор;
память;
буфер кадра;
устройства вывода;
устройства ввода.
Рис. 1.1. Структура графической системы
Эта модель имеет достаточно общий характер и отображает структуру и графической рабочей станции, и персонального компьютера, и графического терминала большой вычислительной системы, работающей в режиме разделения машинного времени, и интеллектуальной системы формирования изображений. Хотя все компоненты представленной блок-схемы присутствуют и в стандартном компьютере (кроме, возможно, буфера кадра), именно специализация каждого компонента в соответствии с требованиями задач компьютерной графики и делает систему графической.
Пиксели и буфер кадра
В настоящее время практически все графические системы используют растровый принцип создания изображения. Суть его заключается в том, что изображение рассматривается как массив — растр — простейших элементов, или пикселей (pixels). Каждый пиксель
имеет четко заданное положение на экране (рис. 1.2). Массив кодов, определяющих засветку пикселей на экране, хранится в отдельной области памяти, которая называется буфером кадра {frame buffer). В системах особо высокого качества для буфера кадра используются специальные типы микросхем — видеопамять с произвольным доступом (VRAM — video random-access memory) или микросхемы динамической памяти с произвольным доступом (DRAM — dynamic random-access memory), которые позволяют быстро вывести содержимое буфера на экран. Глубина {depth) буфера кадра характеризует количество бит информации, определяющих засветку каждого отдельного пикселя, в частности количество цветов, которое может быть представлено на экране данной системы. Например, буфер глубиной 1 бит позволяет выводить только двухградационное изображение, а буфер глубиной 8 бит может выводить изображение, состоящее из элементов 2 = 256 цветов. Современные полноцветные {full-color) системы характеризуются глубиной буфера 24 бита (а иногда и больше). Такие системы способны создавать по-настоящему фотореалистическое изображение. Иногда их называют системами с правильной цветопередачей {true- color). или RGB-системами. поскольку в кодировке засветки каждого пикселя можно выделить отдельные группы битов, характеризующие интенсивность засветки по каждому из основных цветов.— красному (red), зеленому (green) и синему (blue). В более простых системах буфер кадра выделяется в основной памяти компьютера. Размер буфера кадра определяет, в конце концов, одну из главных характеристик графической системы — разрешающую способность (или разрешение).
Рис. 1.2. Пиксели: а — изображение кота Йети; б — деталь изображения (глаз кота), на которой различимы отдельные пиксели
Устройства вывода изображений
Доминирующее положение среди устройств вывода изображений занимают электроннолучевые трубки (ЭЛТ). В упрощенном виде конструкция ЭЛТ представлена на рис. 1.3. При попадании сфокусированного электронного луча на люминофор, покрывающий экран трубки, излучается свет. Направление электронного луча и, следовательно, положение точки засветки экрана управляются двумя парами отклоняющих пластин. Значения координат точки засветки, формируемые компьютером, преобразуются с помощью цифро-аналоговых преобразователей в управляющие напряжения, которые подаются на пластины, отклоняющие луч по осям х и у. В результате на экране появляется засвеченное пятно, положение которого соответствует заданным координатам.
Рис. 1.3. Электронно-лучевая трубка
Если сигналы, подаваемые на отклоняющие пластины, изменяются с постоянной скоростью, то луч вычертит на экране прямую линию— вектор. Такие устройства получили наименования ЭЛТ с произвольным отклонением {random-scan CRT) или векторные ЭЛТ {calligraphic CRT), поскольку в них луч может перемещаться по произвольной траектории. Если электронный луч заперт (его интенсивность равна нулю), то можно переместить его в новое положение, не оставляя следа на экране. Именно по такому принципу работали многие системы отображения, созданные в 60-х годах, которые затем были вытеснены современными растровыми системами.
Сформированное на экране изображение остается видимым в течение очень короткого промежутка времени — это определяется характеристиками люминофора, покрывающего экран, так называемого времени послесвечения. Как правило, время послесвечения не превышает нескольких миллисекунд. Исключение составляют специальные запоминающие ЭЛТ, которые сохраняют на экране сформированное изображение около часа.
При использовании обычных ЭЛТ изображение должно обновляться {регенерироваться) не реже 50 раз в секунду.
В растровых системах луч всегда перемещается по одной и той же траектории, а изображение создается за счет изменения интенсивности луча синхронно со считыванием кодов засветки пикселей из буфера кадра. За время одного цикла регенерации изображения полностью считывается содержимое буфера кадра. Частота регенерации выбирается таким обра-
зом, чтобы человеческий глаз не замечал мелькания изображения вследствие конечного времени послесвечения люминофора. Существуют два способа формирования растра на экране. Первый получил наименование прогрессивной развертки {noninterlaced)— в течение каждого очередного периода кадровой развертки луч последовательно проходит по всем строкам разложения. Частота кадровой развертки соответствует частоте регенерации и составляет 50— 85 Гц. Другой принцип— чересстрочная {interlaced) развертка— предполагает, что в одном кадре луч проходит по всем четным строкам разложения, а в следующем — по всем нечетным. Чересстрочная развертка используется в обычных телевизионных системах. При этом, хотя частота кадровой развертки составляет 60 Гц (в Европе — 50 Гц), изображение на экране полностью обновляется только 30 раз в секунду. Для наблюдателя, который сидит достаточно близко к экрану, разница между прогрессивной и чересстрочной разверткой весьма заметна. Дисплеи с прогрессивной разверткой занимают в настоящее время доминирующее положение, хотя для их нормальной работы и приходится использовать в качестве буфера кадра высокоскоростные модули памяти.
В цветных ЭЛТ экран покрывается точками трех разных типов люминофора— одни под воздействием электронного луча излучают красный свет, другие — зеленый, а третьи — синий. Точки люминофоров разного цвета размещены триадами. Большинство цветных ЭЛТ имеет три электронные пушки (соответственно трем цветам точек люминофора). Между отклоняющей системой и экраном в такой цветной ЭЛТ располагается теневая маска — металлический экран со множеством отверстий соответственно триадам точек люминофора (рис. 1.4). Теневая маска обеспечивает попадание луча, испускаемого определенной пушкой, только на точки люминофора “своего” цвета в соответствующей триаде.
Несмотря на то что ЭЛТ являются наиболее распространенным средством создания изображений в системах компьютерной графики, в последнее время интенсивно разрабатываются приборы, основанные на других физических принципах. Но и в них используется тот же растровый способ создания изображения. В портативных компьютерах используются жидко-кристаллические дисплеи {LEDs — liquid-crystal displays). Такие приборы также требуют регенерации изображения, а устройства создания “твердых” копий на бумажных носителях хотя и не требуют регенерации, но используют все тот же растровый принцип разложения изображения.
Устройства ввода
В большинстве графических систем в качестве хотя бы одного из возможных устройств ввода используется обычная алфавитно-цифровая клавиатура. Но более специфическими устройствами, предназначенными для ввода именно графической информации, являются мышь, джойстик (joystick) и планшет. Каждое из этих устройств способно передавать в систему информацию о положении и каждое оснащено, как минимум, парой кнопок, формирующих управляющие сигналы. По отношению ко всем подобным устройствам применяется термин устройство указания (pointing devices\ Мы рассмотрим подробно их конструкцию и характеристики в главе 3.
Изображение: физическое и синтезируемое
При изучении дисциплины “компьютерная графика” специалисты по педагогике рекомендуют начинать с обсуждения методов формирования растрового изображения простых двухмерных геометрических примитивов (например, точек, отрезков прямых и многоугольников) в буфере кадра. Такой подход хорош, если задаться целью научить студентов формировать простые штриховые изображения, например чертежи. Но для работы с современными графическими системами нужно в первую очередь иметь представление о том, какими возможностями обладают применяемые в них аппаратные и программные средства в части создания реалистических изображений трехмерных объектов. Эта задача предполагает знакомство с физическими принципами формирования изображений, основанными на законах, описывающих взаимодействие света с веществом. Поэтому я предпочитаю начинать изложение курса компьютерной графики именно с таких основополагающих принципов.
Изображение, формируемое компьютером, по самой своей природе является искусственно синтезируемым, поскольку зачастую оно не существует физически. Но при этом используются те же физические законы, которые действуют и в отношении изображений физических объектов, воспринимаемых зрением человека. Поэтому я и начну изложение с рассмотрения принципов создания изображений в оптических системах, таких как фотокамера или глаз человека. Мы сформируем модель процесса формирования изображения, которая в дальнейшем позволит воспроизвести этот процесс программными средствами.
В этой главе я буду использовать математику только там, где без нее совершенно невозможно обойтись. Главное для нас сейчас— познакомиться с принципами создания изображения и представить себе архитектуру вычислительной системы, которая могла бы их реализовать. Все детали процесса, как математические, так и технические, будут изложены в последующих главах.
Объекты и наблюдатели
Нам выпало жить в мире трехмерных объектов. Обычно мы характеризуем положение некоторой точки на объекте в терминах подходящей относительной системы координат. Мы можем измерять расстояние между точками и таким образом расстояние между объектами. Именно желание концептуально систематизировать такие простые идеи, как измерение размеров и расстояний, и привело к развитию многих областей математики, в частности геометрии и тригонометрии. Часто мы стараемся представить свое представление о пространственных отношениях между объектами в форме чертежей или изображений, например карт, технических чертежей, рисунков или фотографий. Точно так же изобретение многих физических приборов, включая фотокамеры, микроскопы и телескопы, было тесно связано с желанием визуализировать пространственные отношения между объектами. Следовательно, существует фундаментальная связь между физикой и математикой описания процесса формирования
изображения. Именно эту связь мы и будем использовать в процессе разработки методов создания изображений с помощью компьютеров.
В любом процессе формирования изображений — физическом или формальном (математическом) — присутствуют две сущности: объект и наблюдатель. Объект существует в пространстве независимо от процесса создания изображения и соответственно от наблюдателя. В компьютерной графике мы будем иметь дело не с реальным физическим объектом, а с воображаемым, синтезированным компьютерной программой. Такой воображаемый объект формируется через спецификацию положения в пространстве разнообразных геометрических примитивов — точек, отрезков прямых или многоугольников. В большинстве графических систем для описания или аппроксимации объектов оказывается достаточно множества описаний точек в пространстве, или вершин (vertices). Например, отрезок прямой характеризуется двумя вершинами, многоугольник — упорядоченным списком вершин, а сфера— двумя вершинами, одна из которых соответствует центру, а другая — любой точке на поверхности сферы. Одна из главных функций САПР — обеспечить пользователю возможность формировать такую синтетическую модель проектируемого изделия и окружающей это изделие среды. В главе 2 будет показано, как формировать модели простых объектов с помощью OpenGL, а в главе 8 — как описать пространственные отношения между объектами.
Любая система отображения должна обладать средствами формирования изображений наблюдаемых объектов. Для этого нам понадобится некто (или нечто), “рассматривающий” эти объекты. Этим некто или нечто может быть человек, фотокамера или дигитайзер. Именно наблюдатель формирует изображение объектов. Если наблюдателем является человек, то изображение формируется на сетчатке глаза, а в фотокамере — на поверхности фотопленки. Часто понятие объекта и его изображения смешиваются. Мы обычно смотрим на объект с определенной точки и забываем, что другие наблюдатели, рассматривая его из другой точки, могут получить совершенно другое изображение того же самого объекта. На рис. 1.5,а показано изображение сцены, как ее видит наблюдатель А. В этой сцене присутствуют два других наблюдателя — В и С. Для наблюдателя А они являются объектами сцены. На рис. 1.5,б,в показано, как увидят эту же сцену наблюдатели В и С.
ВВа)б)в)а)б)в)Рис. 1.5. Как видят изображение одной и той же сцены три разных наблюдателя: а — наблюдатель А; б — наблюдатель В; в — наблюдатель СРис. 1.5. Как видят изображение одной и той же сцены три разных наблюдателя: а — наблюдатель А; б — наблюдатель В; в — наблюдатель С
В
В
а)б)в)
а)б)в)
Рис. 1.5. Как видят изображение одной и той же сцены три разных наблюдателя: а — наблюдатель А; б — наблюдатель В; в — наблюдатель С
Рис. 1.5. Как видят изображение одной и той же сцены три разных наблюдателя: а — наблюдатель А; б — наблюдатель В; в — наблюдатель С
На рис. 1.6 показано, как формируется изображение здания на пленке фотокамеры. Хотя и наблюдатель, и наблюдаемый объект существуют в одном и том же трехмерном мире, созданное на плоскости пленки изображение является двухмерным. Суть процесса формирования изображения и состоит в том, чтобы, зная положение (и характеристики) наблюдателя и положение объекта, описать получаемое при этом двухмерное изображение. Именно это мы и рассмотрим детально в последующих разделах.
Рис. 1.6. Создание изображения в фотокамере
Свет и изображение
В упрощенном описании процесса формирования изображения были опущены многие важные детали. В частности, ничего не было сказано об источниках света и об их влиянии на
создание изображения. Совершенно очевидно, что без источника света объект просто-
напросто погрузится в “тьму кромешную” и ни о каком его изображении просто не может
быть речи (хотя сам объект и будет реально существовать). Точно так же мы ничего не сказа
ли и о цвете или о том, как влияет на изображение объекта текстура его поверхности.
Анализ физики процесса начнем с простейшего случая, представленного схематически на рис. 1.7. На нем показана сцена, в которой присутствуют физический объект, наблюдатель (фотокамера) и источник света. Свет, излучаемый источником, по-разному взаимодействует с
разными участками поверхности объекта, и часть отраженной световой энергии попадает в объектив фотокамеры. Количество энергии, попавшей
в фотокамеру, зависит от характера взаимодействия между падающим светом и поверхностью (точнее,
разными участками поверхности).
Думаю, читателям известно, что свет— это электромагнитное излучение в определенном диапазоне длин волн или частот. В полном спектре электромагнитного излучения (рис. 1.8) диапазон видимого света располагается между диапазонами радиоволн и инфракрасного (теплового) излучения. Видимый свет имеет длину волны в диапазоне от 350 до 780 нанометров (нм). Каждый источник света характеризуется цветам, т.е. спектральным составом излучения. Например, лазер формирует монохроматическое излучение, т.е. излучение одной длины волны, а в излучении, формируемом
лампой накаливания, присутствует множество состав- Рис. 1.7. Фотокамера и источник света ляющих частот. В компьютерной графике, к счастью, практически никогда не приходится иметь дело с волновой природой света.
Вместо этого мы будем следовать традиционному подходу, который вполне корректен при
достаточно высоком уровне интенсивности света, когда его волновая природа не является существенным фактором. В геометрической оптике принято моделировать источники света в пред-
положении, что интенсивность излучаемой ими световой энергии постоянна. Свет распространяется в однородной среде по прямой от источников к тем объектам, с которыми он взаимодействует. Идеальный точечный источник света излучает из заданной точки в пространстве свет равной интенсивности во всех направлениях. Реальные источники имеют конечные геометрические размеры излучающей поверхности и характеризуются пространственной диаграммой интенсивности, поскольку предположение о равномерности излучения по всем направлениям в таком случае уже не отражает физики процесса. Кроме того, диаграмма интенсивности может изменяться для разных длин волн. Пока что мы будем рассматривать только точечные источники света, а моделирование реальных источников отложим до главы 6.
РадиоволныРадиоволныВидимый светВидимый светИнфракрасное (тепловое) излучение
Радиоволны
Радиоволны
Видимый свет
Видимый свет
Другая существенная характеристика источника света — спектр излучения. В этой главе мы рассмотрим только источники .монохроматического излучения, которые имеют единственную спектральную составляющую. Поскольку в отношении разных световых частот можно применять принцип суперпозиции, это предположение не влияет на корректность полученных результатов и позволяет распространить их на полихромные источники. Но такое предположение позволит нам сейчас ограничиться только одной характеристикой источника— его светимостью {brightness), в то время как при работе с полихромными источниками нужно вводить в рассмотрение еще и параметр цветовой тон {hue). Здесь есть определенная аналогия с отличием черно-белого телевидения от цветного.
Трассировка лучей
Начнем создание модели процесса формирования изображения с исследования траектории световых лучей, испускаемых источником. Рассмотрим сцену, .представленную на рис. 1.9, в которой присутствует только один точечный источник света. Наблюдатель в сцене нужен по той простой причине, что нас, в конце концов, интересует свет, который достигнет глаз наблюдателя. Наблюдателем может быть и фотокамера, как показано на рис. 1.10. Луч представляет собой прямую, ограниченную с одной стороны, т.е. исходящую из определенной точки и уходящую в бесконечность. Поскольку свет распространяется по прямой, то его можно анализировать в терминах геометрических лучей, исходящих из точечного источника во всех направлениях. Часть этих лучей и вносит свой вклад в образование изображения на пленке в фотокамере. Например, если источник видим из камеры, то некоторые лучи прямо попадают через объектив на пленку. Большая часть испускаемых источником лучей уходит в бесконечность, не попадая прямо ни в объектив камеры, ни на один из “пассивных” объектов сцены. Эти лучи не вносят никакого вклада в создание изображения, хотя их и могут видеть другие наблюдатели. Оставшиеся лучи падают на объекты сцены и освещают их. Лучи, падающие на объект, могут взаимодействовать с ним по-разному. Например, если поверхность объекта зеркальная, то отраженные лучи могут (в зависимости от ориентации поверхности)
достичь объектива и внести определенный вклад в изображение. Другие поверхности, которые называются рассеивающими, или диффузными {diffuse surfaces'), рассеивают падающий на них свет во всех направлениях. Если поверхность прозрачная, то световой луч от источника может пройти через нее, но при этом возникает его преломление (или рефракция) и частичное отражение. В результате преломленный и отраженный лучи потом могут взаимодействовать с другими объектами и, возможно, попасть в объектив камеры. Как обычно, большая часть этих лучей потом уходит в бесконечность. На рис. 1.10 представлены шесть вариантов взаимодействия лучей, испускаемых источником, с окружающей средой.
Рис. 1.9. Сцена с единственным точечным источником света
Рис 1.10. Трассировка лучей. Луч А попадает непосредственно в объектив камеры. Луч В уходит в бесконечность, не встретив на своем пути никаких препятствий. Луч С отражается от зеркальной поверхности и попадает в объектив камеры. Луч D попадает на диффузную поверхность, и в результате образуется бесконечное множество других лучей, распространяющихся во всех направлениях, часть из которых, вполне вероятно, и попадет в объектив камеры. Луч Е попадает на частично прозрачную поверхность и в результате расщепляется на преломленный луч и отраженный. Луч F, отраженный от зеркальной поверхности, попадает на другой объект, который может его поглотить
Трассировка лучей — это метод моделирования процесса формирования изображения, основанный на анализе описанных оптических явлений. Этот метод в последнее время все шире применяется в компьютерной графике. Метод трассировки световых лучей можно применять для моделирования оптических эффектов в сколь угодно сложной среде — все зависит только от производительности программы и компьютера. Хотя эта модель и достаточно точно описывает реальные физические процессы, соответствующие алгоритмы требуют довольно большого объема вычислений при реализации на компьютере. Можно, однако, в дальнейшем упростить модель, что облегчит ее алгоритмическую реализацию. С физической точки зрения, если наблюдатель улавливает излучение, исходящее от некоторого объекта, для него не имеет значения, каким образом образовалось это излучение — в результате преобразования одного вида энергии в другой (физический источник света) или в результате отражения падающего света. Если, например, зеркало отражает свет от источника в объектив камеры, то для камеры оно является таким же источником. Если мы видим какой-либо объект, то он или освещен светом от какого- либо источника, или сам является источником, или и то и другое. Глядя на объект, мы не можем сказать категорически, какая комбинация этих вариантов имеет место в данной ситуации.
Предположим, что все объекты имеют одинаковую яркость, — сценарий, который трудно реализовать в реальном мире, но который очень часто является вполне резонным при размещении источников в среде. С точки зрения наблюдателя красный треугольник имеет одинаковую яркость красного света в каждой точке, и его нельзя отличить от равномерно излучающего источника того же цвета и формы. Задавшись таким предположением, можно освободиться от собственно физических источников света и с помощью простых тригонометрических методов ‘‘просчитать” изображение. В главе 6 будут рассмотрены модели, включающие сложные источники света и учитывающие свойства материалов реальных объектов.
В следующих разделах мы рассмотрим две физические системы создания изображения — глаз человека и камеру-обскуру (или камеру с точечным отверстием). Глаз человека— это исключительно сложная оптическая система, но она подчиняется тем же физическим законам, что и прочие. Мы рассмотрим ее не только в качестве примера реальной оптической системы, но и потому, что правильное представление о ее характеристиках поможет в дальнейшем анализировать возможности компьютерных графических систем. Камера-обскура — это одна из простейших оптических систем, анализ которой поможет уяснить принципы работы и других, более сложных оптических приборов. Мы будем эмулировать характеристики такой камеры при построении модели процесса формирования изображения.
Глаз человека
Хотя глаза и являются очень сложной биологической системой, они обладают всеми компонентами искусственно созданных оптических систем, таких как фотокамера и микроскоп.
Структура глаза схематически показана на рис. 1.11. Свет попадает в глаз через роговицу и хрусталик (своего рода линзу). Роговица— это прозрачная субстанция, предохраняющая хрусталик. Радужная оболочка глаза играет роль диафрагмы, регулируя количество пропускаемого внутрь света. Хрусталик формирует изображение на двухмерной поверхности сетчатки — внутренней поверхности глазного яблока. Расположенные на сетчатке фоторецепторные клетки — палочки и колбочки — играют роль приемников света, которые воспринимают электромагнитные колебания в диапазоне длин волн от 350 до 780 нм.
_ , ,, _ Палочки являются высокочувствительными приемниками Рис. 1.11. Строение глаза чело-
излучения и работают в условиях слабого освещения
(ночью), а колбочки несут свою “вахту” при обычном, дневном освещении. Размеры палочек и колбочек в совокупности с оптическими свойствами хрусталика и роговицы определяют разрешающую способность глаза. Этот параметр характеризует способность глаза, как, впрочем, и любой другой оптической системы, различать мельчайшие видимые объекты. Если говорить техническим языком, то разрешающая способность характеризует минимальное
расстояние между точками, различимое данной системой.
Чувствительные клетки глаза неодинаково реагируют на электромагнитные колебания разной длины волны. Существуют три типа колбочек и один тип палочек. В то время как интенсив
ность есть мера энергии света, воздействующего на глаз, яркость — это мера восприятия этого
воздействия. Глаз человека по-разному воспринимает монохроматический красный цвет и мо
нохроматический зеленый цвет равной интенсивности, поскольку чувствительность колбочек разного типа отличается. На рис. 1.12 приведена интегральная кривая спектральной чувствительности глаза, известная как стандартная кривая Международной светотехнической комиссии (CIE — Commision Internationale de L'Eclairage). Из нее видно, что глаз наиболее чувствителен к зеленому цвету, а наименее — к чисто красному и синему. Эта кривая очень близка к кривым спектральной чувствительности светоприемников камер черно-белого телевидения, и по ней сверяют свою продукцию изготовители черно-белых фотопленок.
Яркость — это интегральная мера нашего восприятия поступившего света. Возможность цветового восприятия появляется у человека благодаря наличию “узкополосных” приемников излучения — колбочек трех типов. Каждый тип характеризуется своей кривой спектральной чувствительности, причем диапазон длин волн для каждого типа существенно уже полного спектра световых волн (рис. 1.13). Максимум чувствительности на одной из этих кривых приходится на синий цвет, на другой — на зеленый, на третьей — на желтый. Одним из следствий раскрытия механизма цвето- восприятия глаза явилось то, что по этому же принципу выделения основных, или первичных, цветов строятся и технические системы приема цветных изображений — телевизионные и фотографические. Эти первичные цвета можно использовать для приближенного представления любого
Рис. 1.12. Интегральная кривая спектральной чувствительности глаза человека (стандартная кривая CIE)Рис. 1.12. Интегральная кривая спектральной чувствительности глаза человека (стандартная кривая CIE)Рис. 1.13. Кривые чувствительности колбочекРис. 1.13. Кривые чувствительности колбочек“промежуточного” цвета. Обсуждение проблемы цветовосприятия и создания цветных изображений мы отложим до главы 2.
Рис. 1.12. Интегральная кривая спектральной чувствительности глаза человека (стандартная кривая CIE)
Рис. 1.12. Интегральная кривая спектральной чувствительности глаза человека (стандартная кривая CIE)
Рис. 1.13. Кривые чувствительности колбочек
Рис. 1.13. Кривые чувствительности колбочек
Хотя начальная фаза обработки изображения, появившегося на внутренней поверхности сетчатки глаза, во многом походит на то, что происходит в технических системах, далее все идет совершенно по-иному (по крайней мере, по сравнению с существующим на сегодняшний день техническими системами). Нервные клетки связаны с палочками и колбочками исключительно сложным образом, и этот симбиоз в чем-то напоминает чрезвычайно сложный сигнальный процессор (или скорее, наоборот, создатели сигнальных процессоров пытаются воспроизвести то, что происходит в зрительной системе человека). Окончательная обработка происходит в коре головного мозга, где выполняются такие сложные функции, как распознавание образов. Мы не будем вдаваться в обсуждение этих вопросов, поскольку они далеко выходят за рамки проблематики компьютерной графики. В дальнейшем нас будет интересовать только восприятие изображения на уровне первичных сенсоров — палочек и колбочек.
Камера-обскура
УУКамера-обскура, схематически показанная на рис. 1.14, позволяет простейшим образом описать процесс формирования изображения с помощью геометрической модели. Она представляет собой светонепроницаемый ящик, в центре одной из стенок которого “проколото” малюсенькое отверстие (в идеале это должна быть геометрическая точка, имеющая бесконечно малый размер)4. На внутренней стороне противоположной стенки ящика закрепляется фоточувствительная пластина. Совместим с этим отверстием начало прямоугольной системы координат. Ось z этой системы координат направим нормально к плоскости светочувствительной пластины. Обозначим через d расстояние по нормали между отверстием и светочувствительной пластиной. Рассматривая вид камеры сбоку (рис. 1.15), несложно вычислить координаты (л, у, г) точки пересечения светового луча, прошедшего через отверстие, с плоскостью z = -d светочувствительной пластины. Используя подобие двух треугольников на рис. 1.15, получим, что световой луч, исходящий из точки с координатой у на объекте, попадет на световую пластину в точке с координатой^,:
У
У
у,
Анализируя таким же образом вид камеры сверху, получим и значение координаты:
Точка (xp,yp,-d) называется проекцией точки (х, у, z). В нашей идеализированной модели цвет точки на плоскости светочувствительной пластины будет таким же, как и цвет точки (х, у, z). Поле, или угол зрения, камеры — это угол, характеризующий наибольший объект, изображение которого умещается на светочувствительной пластине, полностью занимающей заднюю стенку камеры. Этот параметр можно вычислить на основании геометрической модели, изображенной на рис. 1.16. Если обозначить через h высоту светочувствительной пластины, то угол зрения 6 будет равен
h
6 = 2 arctg—.
2d
Идеальная камера-обскура будет иметь бесконечную глубину резкости, т.е. все видимые точки изображения будут в фокусе. Изображением точки будет опять же точка. Камера- обскура имеет два недостатка. Во-первых, бесконечно малый размер отверстия (в идеале через это отверстие должен пройти только один луч, исходящий из некоторой точки на объекте) приводит к тому, что на светочувствительную пластину попадает очень мало света. Во- вторых, угол зрения камеры определенного размера никак не может быть изменен.
В принципе, современный фотоаппарат отличаегся от камеры-обскуры только тем, что вместо точечного отверстия в нем используется система линз (объектив). Но это позволяет устранить оба отмеченных недостатка. Во-первых, объектив позволяет “собрать” значительно больше света — чем больше апертура объектива, тем больше световой энергии проходит через него. Во-вторых, угол зрения фотокамеры определяется фокусным расстоянием используемого объектива, а его можно изменять, меняя объективы либо применяя объектив с регулируемым фокусным расстоянием. Современные объективы позволяют увеличить угол зрения почти до 180°. Но, к сожалению (а фотохудожники говорят “к счастью”), объективы не обеспечивают бесконечной глубины резкости.
В данной главе мы будем рассматривать только идеальную модель камеры-обскуры, для которой фокусным расстоянием является расстояние d от передней до задней стенки камеры. В компьютерной графике также в большинстве случаев не моделируется эффект ограниченной глубины резкости.
Моделирование камеры
Рассматриваемые модели оптических систем формирования изображения непосредственно подводят нас к фундаментальным концепциям современной трехмерной компьютерной графики. Посмотрим, каким образом можно алгоритмически синтезировать изображение, по-
добное тому, которое реально создается в оптической системе. Эта парадигма получила в литературе название модель синтезированной камеры (synthetic-camera model). Рассмотрим систему формирования изображения, представленную на рис. 1.17. На ней мы снова видим объект и наблюдателя. В данном случае наблюдателем является камера с регулируемым расстоянием между объективом и плоскостью светоприемника (пластины или пленки). Наша цель — эмулировать процесс создания изображения на плоскости светоприемника.
Рис. 1.17. Система формирования изображения
Во-первых, отметим, что спецификация (формальное описание) объекта не зависит от спецификации наблюдателя. Следовательно, при организации графической библиотеки можно отделить функции описания объекта от функций описания наблюдателя.
Во-вторых, параметры изображения можно вычислить, используя простейшие тригонометрические соотношения. Рассмотрим плоскость светоприемника камеры и объект (рис. 1.18). Схема в левой части рисунка абсолютно аналогична той, которую мы рассматривали при анализе камеры-обскуры. В правой части рисунка приведена схема, в которой плоскость изображения передвинута и расположена перед объективом камеры, что соответствует размещению элементов сцены на рис. 1.19. Для построения изображения некоторой точки объекта мы проводим проецирующий луч от этой точки к центру объектива— центру проецирования (center of projection). Обратите внимание на то, что все проецирующие лучи сходятся в центре проецирования. В нашей синтезированной камере плоскость светоприемника, которую мы передвинули и разместили перед объективом, называется картинной плоскостью, или плоскостью проекции (projection plane). Изображение точки размешается в точке пересечения проецирующего луча с картинной плоскостью. В главе 5 этот процесс рассматривается подробно с использованием соответствующего математического аппарата.
Рис. 1.18. Схема формирования изображения: а — изображение формируется на задней поверхности камеры; б — изображение формируется перед объективом камеры
Рис. 1.19. Синтезированная камера
Следует принять во внимание и конечные размеры изображения. Как мы видели, из-за ограниченности угла зрения камеры-обскуры на плоскость светоприемника попадает изображение отнюдь не всех объектов. В нашей синтезированной камере аналогичное ограничение можно смоделировать, разместив на плоскости проекции отсекающий прямоугольник (clipping rectangle}, или отсекающую рамку (clipping window} (рис. 1.20). Этот прямоугольник действительно играет роль рамки окна, через которое наблюдатель, находящийся в центре проекции, смотрит на окружающий мир. Задав положение центра проекции, положение и ориентацию плоскости проекции и размеры отсекающего прямоугольника, можно однозначно определить, какие объекты появятся на сформированном изображении.
а)6)
Рис. 1.20. Отсечение: а — исходное положениерамки; б — рамка смещена
Интерфейс программиста
Существует множество способов взаимодействия пользователя с графической системой. В современных системах автоматизации проектирования пользователь создает изображение. взаимодействуя с системой отображения посредством устройств ввода информации, таких как мышь и клавиатура. В типичной системе построения чертежей (рис. 1.21) для представления возможных операций используются меню и панели инст-
рументов. Щелкая на тех или иных элементах графического интерфейса, пользователь активизирует соответствующие функции и формирует изображение, не прибегая к собственно программированию.
Рис. 1.21. Графический интерфейс программы вычерчивания
Естественно, перед этим кто-то должен разработать программы этого приложения, и многие из тех, кто держит в руках эту книгу, будут самостоятельно создавать такого рода программы (и даже получать от этого удовольствие), несмотря на наличие на рынке огромного количества программных продуктов аналогичного назначения.
Интерфейс прикладного программирования
Интерфейс между прикладной программой и графической системой — это множество функций, которые в совокупности образуют графическую библиотеку. Спецификация этих функций и есть то, что мы называем интерфейсом прикладного программирования (API — application programmer's interface). Модель системы прикладного программирования показана схематически на рис. 1.22. Для программиста, занимающегося разработкой прикладной программы, существует только API, и он избавлен, таким образом, от необходимости вникать в подробности работы аппаратуры и программной реализации функций графической библиотеки. С точки зрения прикладного программиста те функции, к которым он обращается через API, должны соответствовать концептуальной модели описания изображения. Основой для этого часто является описанная выше модель синтезированной камеры. Она используется во множестве разнообразных API, таких как OpenGL, PHIGS, Direct3D, VRML и JAVA-3D.
Следуя концепции модели синтезированной камеры, в составе API должны присутствовать функции, которые позволяли бы описывать:
объекты;
наблюдателя;
источники света;
свойства материалов объектов.
Для описания объектов чаще всего используются массивы вершин. Для простых геометрических объектов — отрезков прямых, прямоугольников и многоугольников — существует достаточно очевидное соответствие между списком вершин и формой объектов, основанное на простых математических соотношениях. Более сложные объекты могут быть по-разному определены с помощью списка вершин. Например, круг можно определить тремя точками (вершинами) на окружности или центром и одной точкой на окружности.
В большинстве API в распоряжение пользователя предоставляется практически один и тот же набор примитивов. Такие примитивы обычно довольно быстро отображаются аппаратными средствами. Типовой набор включает точки, отрезки прямых, многоугольники и иногда текст. В OpenGL примитивы описываются списком вершин. Ниже приведен фрагмент программного кода на OpenGL, в котором с помощью вызова пяти функций API построен многоугольник с тремя вершинами: glBegin(GL_POLYGON);
glVertex3f(0.0, 0.0, 0.0);
glVertex3f(0.0, 1.0, 0.0);
glVertex3f(0.0, 0.0, 1.0);
glEnd( );
Обращаю ваше внимание на то, что, добавив в список дополнительные вершины, можно описать произвольный многоугольник. Изменив параметр типа GL_POLYGON, можно на тех же самых вершинах построить иной примитив. Например, примитив типа GL_LINE_STRIP соответствует двум связанным отрезкам прямых, проходящих через те же точки, а примитив типа GL_POINTS сформирует изображения трех точек.
Некоторые API позволяют пользователю работать напрямую с буфером кадра— считывать и записывать коды засветки отдельных пикселей. Иногда в число примитивов включаются отрезки кривых и участки поверхностей, хотя чаще такие объекты приходится аппроксимировать более простыми примитивами, причем эта задача возлагается на саму прикладную программу. В OpenGL поддерживается доступ к отдельным пикселям буфера кадра, а также создание криволинейных отрезков и участков поверхностей.
Описать наблюдателя или камеру можно разными способами. Доступные на сегодняшний день графические API отличаются как гибкостью, которую они обеспечивают при выборе параметров камеры, так и количеством имеющихся в распоряжении пользователя методов ее описания. Для камеры, представленной на рис. 1.23, существует четыре типа параметров, однозначно определяющих характеристики создаваемого ею изображения.
Положение камеры задается положением центра проекции (ЦП).
Ориентация. Расположив центр проекции в определенной точке пространства, можно совместить с ним начало локальной системы координат камеры и вращать ее относительно осей этой системы координат, изменяя таким образом ориентацию объектива.
Фокусное расстояние объектива камеры фактически определяет размер изображения на плоскости проекции.
Плоскость светоприемника. Задняя стенка камеры имеет конечные размеры в высоту и ширину. Некоторые API позволяют настраивать ориентацию плоскости светоприемника независимо от ориентации объектива.
Такую спецификацию можно сформировать разными способами. Один из них состоит в том, что положение и ориентация камеры задаются набором координатных преобразований. Эти координатные преобразования используются для вычисления положения точек объектов, заданных вершинами, в системе координат камеры. Такой подход очень удобен и с точки зрения его программной реализации, и с точки зрения гибкости графической системы, поскольку позволяет довольно просто формировать различные виды объекта, “перемещая” камеру. Именно такой подход мы и будем использовать в дальнейшем, начиная с главы 5.
Необходимость настройки значений множества параметров также представляет определенную сложность. Частично причины этой проблемы кроются в самой модели синтезированной камеры. В классических методах визуализации, применяемых, в частности, в архитектурном проектировании, акцент делается именно на взаимосвязи наблюдателя и среды, в то время как модель предполагает независимость наблюдателя и наблюдаемых объектов. Так, классическая двухточечная перспектива куба на рис. 1.24 формируется именно вследствие учета взаимосвязи наблюдателя и плоскостей граней куба (см. упр. 1.6). API OpenGL позволяет создавать преобразования самого разного вида и обеспечивает программисту возможность использовать некоторые нетрадиционные функции. Рассмотрим, например, обращение к двум функциям: gluLookAt(cop_x, сор_у, cop_z, at_x, at_y, at_z,...);
glPerspective(field_of_view, . . .);
Вызов первой функции ориентирует камеру в направлении указанной точки пространства, а вызов второй — устанавливает параметры объектива для формирования изображения с учетом перспективы. Но ни одна из существующих графических библиотек, построенных на основе концепции синтезированной камеры — OpenGL, PH1GS, VRML, — не имеет в своем составе функций для задания взаимосвязей между камерой и объектом.
Рис. 1.23. Параметры сне- Рис. 1.24. Двухточечная перспектива куба цификации камерыРис. 1.23. Параметры сне- Рис. 1.24. Двухточечная перспектива куба цификации камеры
Рис. 1.23. Параметры сне- Рис. 1.24. Двухточечная перспектива куба цификации камеры
Рис. 1.23. Параметры сне- Рис. 1.24. Двухточечная перспектива куба цификации камеры
Источник света характеризуется положением, интенсивностью и цветом излучения и его направленностью. В составе большинства API имеются функции для задания такого рода параметров, причем в сцене может присутствовать несколько источников света с разными характеристиками. Существуют и функции спецификации оптических свойств материалов поверхностей объектов. Эти функции вызываются на этапе создания моделей объектов. Набор программируемых свойств источников света и материалов определяется принятой в данном API моделью взаимодействия света с окружающей средой. Подробно такие модели рассматриваются в главе 6.
Парадигма "моделирование-тонирование"
В процессе формирования изображения можно следовать и другому подходу: во многих САПР-приложениях и программах создания фотореалистических изображений, например для включения в кинофильм, моделирование сцены и ее тонирование (rendering) представляют собой две последовательные стадии процесса (рис. 1.25). Хотя решаемые задачи и не отличаются от тех, которые мы рассматривали выше, такая организация позволяет разделить программные и аппаратные средства реализации этих двух стадий. Например, рассмотрим процесс создания одного кадра мультипликационного фильма. Сначала определяются компоновка кадра и расположение в нем персонажей и объектов фона. Этот процесс требует активного участия художника- мультипликатора, но на первом этапе воспроизводить все детали изображения, связанные с оптическими эффектами, нет необходимости. Следовательно, этот этап предпочтительнее выполнять на специализированной рабочей станции, располагаюшей средствами активного диалога с пользователем. После того как сцена будет скомпонована, можно приступать к ее раскрашиванию. На этой стадии используется все многообразие средств моделирования оптических эффектов — освещения, фактуры поверхностей объектов и т.п. Этот этап значительно меньше связан с вмешательством пользователя, но требует больших объемов вычислений, а потому для его реализации имеет смысл использовать высокопроизводительные компьютеры. Не только аппаратные, но и программные средства реализации этих этапов разительно отличаются, а потому грамотное разделение процесса может существенно повысить его эффективность.
Рис. 1.25. Двухэтапный процесс “моделирование-тонирование”
Связь между компонентами системы, которые отвечают за моделирование и тонирование, осуществляется с помощью промежуточного файла. В структуре такого файла предусматривается возможность хранения как описания объектов сцены, созданных в результате моделирования, так и источников света, информации о положении наблюдателя, свойствах материалов и т.п. Этот подход реализован в формате Renderman Interface, который позволяет передать информацию о модели в текстовом формате. У этого подхода есть и другое немалое достоинство — он позволяет оптимизировать структуру и функциональные возможности того компонента системы, который отвечает за моделирование, в соответствии со спецификой конкретного приложения. При этом второй компонент может быть использован без изменений для совершенно разных графических приложений. Возможен и другой вариант, когда промежуточный файл может обрабатываться разными программами тонирования, которые имеют разные функциональные возможности и работают на разных моделях компьютеров. В принципе, по крайней мере теоретически, при таком подходе можно вообще не использовать программу моделирования, а описать всю сцену в текстовом виде с помощью обычного текстового редактора. Но, конечно, на практике удобнее использовать интерактивные программы моделирования, в которых применяется все та же модель синтезированной камеры для создания изображений, хоть и не раскрашенных.
Архитектура графических систем
В схеме, приведенной на рис. 1.22, по одну сторону API располагается прикладная программа, а по другую — комбинация программных и аппаратных средств, которая реализует функции, специфицированные в API, и формирует изображение на экране. В течение послед-
них 40 лет было предложено множество вариантов структурной организации средств, реализации функций графического API.
В первых графических системах использовались вычислительные машины общего назначения со стандартной архитектурой, предложенной еще фон Нейманом. В таких вычислительных машинах был один процессор, который в каждый момент времени “исполнял” одну инструкцию. В простейшем виде такая структура представлена на рис. 1.26. В качестве устройства отображения использовалась ЭЛТ с необходимым аппаратным обрамлением — все вместе это именовалось дисплеем. Дисплей формировал на экране отрезки прямых между двумя заданными точками или дуги по трем точкам. Главный (и единственный в такой системе) компьютер выполнял прикладную программу и вычислял координаты характеристических точек примитивов в системе координат экрана. Эта информация передавалась в дисплей с достаточно высокой скоростью, чтобы избежать мелькания изображения на экране. В те не столь уж далекие времена скорость вычислений была настолько низкой, что вывод на экран даже пары сотен отрезков требовал использования практически всех вычислительных ресурсов машины.
ГлавныйкомпьютерГлавныйкомпьютерЦифро-аналоговыйпреобразователь
Главныйкомпьютер
Главныйкомпьютер
Рис. 1.26. Структура ранних графических систем
Дисплейные процессоры
Первые попытки создания специализированных графических систем в первую очередь преследовали цель освободить главный компьютер от рутинных операций регенерации изображения на экране дисплея. В состав дисплея включили “собственный” дисплейный процессор (рис. 1.27), который взял на себя задачу регенерации. Дисплейный процессор чаще всего имел ту же архитектуру, что и процессор общего назначения, но дополнительно в его систему команд были включены и инструкции формирования графических примитивов. Главное преимущество такой структурной организации всей системы состояло в том, что от главного компьютера требовалось только сформировать описание изображения и передать его в виде дисплейного файла в дисплейный процессор. Последний сохранял дисплейный файл в собственной памяти и считывал его с частотой регенерации, освобождая таким образом главный компьютер от рутинной работы. Этот вариант архитектуры в общих чертах воспроизведен в современных графических системах со звучным названием “клиент/сервер”, которые рассмотрены в главе 3.
ГлавныйкомпьютерГлавныйкомпьютерРис. 1.27. Структура графической системы с дисплейным процессоромРис. 1.27. Структура графической системы с дисплейным процессором
Главныйкомпьютер
Главныйкомпьютер
Рис. 1.27. Структура графической системы с дисплейным процессором
Рис. 1.27. Структура графической системы с дисплейным процессором
Конвейерная архитектура
Совершенствование архитектуры графических систем шло параллельно с совершенствованием аппаратного обеспечения рабочих станций. В обеих областях существенный прогресс наметился после появления на рынке специализированных интегральных микросхем сверхбольшой степени интеграции (СБИС). Кроме того, радикальное снижение стоимости интегральных запоминающих схем привело к повсеместному переходу на растровый принцип создания изображения в системах компьютерной графики.
С точки зрения задач компьютерной графики наибольшее значение имеет возможность реализовать в специализированных СБИС конвейерный принцип обработки информации. Схема на рис. 1.28 иллюстрирует этот принцип на примере простейших арифметических вычислений.
Рис. 1.28. Арифметический конвейер
Эта простейшая конвейерная структура состоит из сумматора и умножителя. Пусть необходимо вычислить значения выражений вида а+(6хс), что требует одной операции сложения и одной операции умножения на каждое очередное выражение. Если вычисляется одно выражение, то время вычислений в такой структуре будет таким же, как и в единственном процессоре, использующем электронику с теми же динамическими характеристиками. Но если нужно выполнить подряд несколько однотипных операций с разными значениям операндов а, b и с, то умножитель, выполнив умножение пары операндов первого набора, передаст результат на сумматор, и пока последний будет выполнять сложение, успеет выполнить умножение второй пары операндов. В результате время выполнения всей операции сократится вдвое (если, конечно, умножитель и сумматор выполняют свою работу с одинаковой скоростью). Таким образом, интегральная пропускная способность системы возрастает (в данном случае удваивается).
По этому же принципу можно создать и конвейеры с более сложной структурой, которые позволяют еще больше повысить пропускную способность специализированного вычислителя. Естественно, применять подобный конвейер имеет смысл только в том случае, если необходимо многократно выполнять вычисления по одним и тем же формулам с разными данными. Но в задачах компьютерной графики мы имеем именно такой случай — нужно многократно обрабатывать по одним и тем же формулам список вершин, характеризующих отображаемые объекты.
Предположим, что имеется множество вершин, определяющих графические примитивы, из которых формируется изображение. Поскольку все объекты представлены в терминах координат положения точек в пространстве, можно рассматривать множество типов примитивов и вершин как геометрические данные. Сложная сцена описывается тысячами, если не миллионами, вершин. Все их нужно обработать по одному алгоритму и в результате сформировать в буфере кадра описание растра. Если рассматривать этот процесс в терминах геометрических операций с исходными данными, то можно представить его в виде схемы рис. 1.29. На этой схеме показаны четыре основных этапа процесса:
геометрическое преобразование;
отсечение;
проективное преобразование;
растровое преобразование.
Вепшины► Геометрическое | Птгечение I^ Прое!ОИ6Ное|^^► Пиксели Рпреобразование|I ^ преобразование! преобразование I ^
Рис. 1.29. Структура геометрического конвейера
В последующих разделах мы обсудим детали реализации каждого из этих этапов. Но главный вывод, который следует из всего изложенного, состоит в том, что если для всех объектов сцены выполняются единообразные преобразования, то быстрее всего можно реализовать этот процесс с помощью специализированного процессора с конвейерной архитектурой.
Геометрические преобразования
Большинство этапов обработки графической информации можно описать в форме геометрических преобразований представления объектов сцены в разных системах координат. Например, рассматривая модель синтезированной камеры, приходим к выводу, что основная часть процесса визуализации представляет собой преобразование представления объектов из базовой (мировой) системы координат в систему координат камеры. Другой пример — стадия размещения изображения на поле экрана ЭЛТ или другого устройства вывода. Внутреннее представление геометрических объектов — будь то в системе координат камеры или в любой другой подходящей системе координат, используемой в графическом API,— должно быть преобразовано на этой стадии в представление в системе координат устройства отображения. Каждое такое преобразование можно представить в матричной форме, причем последовательные преобразования выражаются перемножением (конкатенацией — concatenating) соответствующих матриц элементарных преобразований. В результате формируется матрица комплексного преобразования. В главе 4 будет подробно рассмотрен математический аппарат описания геометрических преобразований. Поскольку произведение двух матриц есть также матрица, этот математический метод является прекрасным кандидатом для реализации в форме конвейерных вычислений. Кроме того, поскольку матрицы преобразований в компьютерной графике имеют небольшие размеры (4x4), сам процесс умножения матриц также можно распараллелить в блоке, который выполняет эту операцию.
Отсечение
Вторая важная операция в графическом конвейере — отсечение (clipping). Необходимость в ней возникает по той простой причине, что имеющиеся в нашем распоряжении средства отображения сами по себе имеют конечные размеры. Сетчатка глаза человека имеет ограниченный угол зрения примерно 90°, а наша синтезированная камера (как, впрочем, и реальный фотоаппарат) — ограниченное поле “фотоприемника". В реальной фотокамере поле (угол) зрения можно настраивать, подбирая объективы с разным фокусным расстоянием (или перестраивая фокусное расстояние объектива-трансфокатора). Этот процесс в синтезируемой камере моделируется изменением размеров (а иногда и положения) отсекающей прямоугольной рамки (clipping rectangle) на плоскости проекции (см. рис. 1.20). Объекты, проекция которых попадает во внутреннюю область отсекающей рамки, “участвуют" в формировании изображения. Те объекты, проекции которых пересекают отсекающую рамку, будут частично видимы.
Отсечение выполняется на разных этапах формирования изображения. Отсечение геометрических примитивов можно выполнить, анализируя только координаты, и, следовательно, этот процесс несложно встроить в геометрический конвейер. Сам процесс отсечения можно детализировать и разбить на последовательность элементарных операций, также поддающихся распараллеливанию, а следовательно, совместимых с принципом конвейерной обработки (см. упр. 1.4 и 1.5). Эффективные алгоритмы отсечения будут рассмотрены в главе 7.
Проективное преобразование
Как правило, при обработке геометрической информации трехмерное описание объектов стараются сохранить как можно дольше по мере продвижения “по конвейеру”. Но после стадий геометрических преобразований и отсечения неизбежно наступает момент, когда те объекты, которые попадают в поле видимости, нужно преобразовать из трехмерной формы в двухмерную. Существует множество видов проективного преобразования, которые мы рассмотрим в главе 5. Некоторые из них позволяют использовать математический аппарат операций с матрицами размером 4x4 и, следовательно, могут быть реализованы в том же самом конвейере.
Растровое преобразование
Последний этап процесса— преобразование описания двухмерных объектов в коды засветки пикселей в буфере кадра. Как выполняется эта операция для стандартных примитивов — линий и многоугольников, — будет подробно рассмотрено в главе 7. Поскольку регенерация изображения выполняется аппаратно, этот процесс практически скрыт от прикладного программиста и можно считать, что последняя операция геометрического конвейера — это растровое преобразование.
Производительность работы геометрического конвейера
В рассматриваемой структуре обработки геометрической информации используются операции двух типов. На начальных стадиях выполняются операции со значениями координат вершин, представленными в форме чисел с плавающей точкой. Эти операции идеально подходят для реализации в конвейерной вычислительной структуре. Разработанный фирмой Silicon Graphics геометрический процессор на базе СБИС стал базовым элементом большинства графических рабочих станций. Для операций с 4х4-матрицами преобразования в нем используются специальные микросхемы арифметических расширителей, подобные Intel i860. В результате программа перемножения двух матриц сводится к единственной инструкции. В графических рабочих станциях и графических платах для профессиональных ПК используются специализированные СБИС, которые берут на себя выполнение большинства необходимых преобразований и выполняют их аппаратно с огромной скоростью.
Хотя в подавляющем большинстве профессиональных графических рабочих станций используется конвейерный принцип обработки, следует учитывать, что по мере добавления в конвейер новых операций время реакции системы увеличивается, и нужно найти оптимальное сочетание времени реакции и функциональных возможностей системы.
Все стадии обработки после растрового преобразования требуют выполнения побитовых операций на уровне содержимого буфера кадра. Характер этих операций существенно отличается от характера вычислений с матрицами, выполняемых при геометрических и проективных преобразованиях. Чаще всего при работе с буфером кадра приходится выполнять перемещение содержимого участка памяти. Общая производительность системы определяется скоростью “перемещения” геометрических примитивов по конвейеру и количеством пикселей, которые могут быть изменены в буфере кадра за определенное время. Поэтому наиболее производительные графические рабочие станции используют СБИС с конвейерной архитектурой для выполнения преобразований трехмерных и двухмерных геометрических объектов и параллельные битовые процессоры на конечной стадии обработки изображения в растровой форме.
Конвейерная архитектура занимает доминирующее положение среди существующих на сегодняшний день структур аппаратных средств графических систем, в особенности тех из них, которые должны формировать динамические изображения в реальном масштабе времени. Но следует отметить, что сам по себе принцип распараллеливания вычислений годится и для организации программного обеспечения при реализации высокопроизводительных графических пакетов API. Причем в обоих случаях — аппаратной и программной реализации — используются возможности, предоставляемые все той же моделью синтезированной камеры.
Резюме
В этой главе описаны основные стадии разработки графических приложений, как они представляются при использовании нисходящего подхода к проектированию и обучению. Имея перед собой общую картину, читатель сможет приступить к разработке простейших графических программ уже при изучении материала следующей главы.
Мы делаем акцент на том, что в компьютерной графике главную роль играют методы, основанные на тех же физических принципах формирования изображения, которые действуют и в реальном мире, в частности в оптических системах, таких как фотокамера или глаз человека. В главе 2 будут рассмотрены вопросы ввода цвета в изображение, и вы увидите, что понимание основных принципов функционирования зрительной системы человека позволяет искусственно создавать изображения, воспринимаемые наблюдателем как реальные.
В этой главе мы рассмотрели в общих чертах три различные парадигмы формирования изображения с помощью компьютера. Описанная модель синтезируемой камеры имеет два очень важных для практики следствия. Во-первых, она предполагает независимость изображаемых объектов и наблюдателя, что влияет на способ организации библиотеки графических функций. Во-вторых, описанная модель открывает путь к использованию конвейерных принципов построения системы компьютерной графики.
В этой главе мы также познакомили читателей с идеей трассировки лучей при построении изображения сцены. Эта парадигма играет существенную роль в понимании процессов взаимодействия света и материала, на которой основан процесс формирования физического изображения. Метод трассировки лучей требует альтернативного варианта организации системы компьютерной графики, и мы попытались сделать набросок такого варианта системы. Однако уже поверхностный анализ показывает, что на этом пути нас ожидают весьма серьезные трудности, поскольку реально только малая часть всех лучей участвует в формировании изображений, а время, ушедшее на анализ большей их части, оказывается потраченным впустую. В главе 6 мы вернемся к этому методу и расскажем о способах повышения его эффективности.
Далее в этой главе было показано, какое важное значение имеет парадигма “моделирование-тонирование”. В современной графической системе с конвейерной организацией в 1 секунду формируются миллионы отрезков прямых или многоугольников с разрешением до 1280x1024 пикселей. Такая система успевает выполнять в реальном масштабе времени удаление невидимых поверхностей и тонирование примитивов, но в последней процедуре приходится использовать упрощенные алгоритмы, поскольку на большее не хватает производительности аппаратных средств. Если стоит цель сформировать картинку, не уступающую по качеству той, которая получается при съемке кинофильма “на натуре”, необходимо добиться разрешения порядка 4000x6000 пикселей и учитывать характеристики источников света и материалов отображаемых объектов, причем все расчеты должны выполняться с такой скоростью, чтобы воспроизводить динамическую сцену в реальном масштабе времени. Несмотря на непрерывное возрастание производительности аппаратных и программных средств, используемых в системах компьютерной графики, принцип разделения фаз моделирования и тонирования будет и в будущем сохранять свою актуальность.
В этой же главе мы затронули и аспекты реализации прикладной части системы. Было показано, что API OpenGL, поддерживаемый на большинстве существующих аппаратных и программных платформ, является мощным средством изучения принципов формирования изображения и построения прикладных систем.
Рекомендуемая литература
На сегодняшний день опубликовано множество прекрасных книг по компьютерной графике. Первое место в этом ряду занимает книга Ньюмена (Newman) и Спрулла (Sproull) [Nexv73], в которой впервые с современных позиций была описана модель синтезированной
камеры. В следующем десятилетии ни одна работа по компьютерной графике не обходилась без ссылки на книги Фоли (Foley) и соавторов [Fol90, Fol94J. Среди других книг, на которые я хотел бы обратить внимание читателей, работы Херна (Hearn) и Бейкера (Baker) [Неа94] и Хилла (Hill) [НП90]. В своей работе Фоли, так же, как, впрочем, и Херн с Бейкером, использовали API PH1GS, а материал книги Анджела [Ang90] базируется на API GKS.
Я рекомендую всем читателям регулярно просматривать журналы Computer Graphics, который ежеквартально выпускается SIGGRAPH (Группа по компьютерной графике в составе Ассоциации по вычислительной технике —Association for Computing Machinery's Special Interest Group on Graphics), IEEE Computer Graphics and Applications и Visual Computer. Летний выпуск Computer Graphics, как правило, представляет собой сборник трудов ежегодной конференции SIGGRAPH и содержит сообщения о результатах самых последних исследований в области компьютерной графики. Особый интерес для новичков представляют выпускаемые SIGGRAPH учебные видеофильмы и заметки о специальных учебных курсах, рекомендуемых на этой конференции. Последние распространяются на компакт-дисках.
Докторская диссертация А. Сазерленда (I. Sutherland) “Project Sketchpad”, вышедшая затем отдельной книгой Sketchpad: A Man-Machine Graphical Communication System [Sut63], явилась, возможно, тем ростком, из которого выросло дерево современной компьютерной графики. Именно Сазерленд первым осознал, какие широкие перспективы открывает возможность взаимодействия пользователя с компьютером через систему отображения на основе ЭЛТ. Видеофильм о его оригинальной работе до сих пор пользуется популярностью.
В книгах Тафта (Tufte) [Tuf83, Tuj90, Tuf97] описана история развития компьютерной графики. В статье Карлбома (Carlbom) и Пасьорека (Paciorek) [Саг78] обсуждается связь между классической теорией зрения, которая используется испокон веков в архитектуре, и методами визуализации, используемыми в компьютерных приложениях.
Существует огромное множество книг, описывающих зрительную систему человека. Книга Пратта (Pratt) [Рга78] посвящена принципам создания растровых изображений. Я бы советовал также познакомиться с работами Гласснера (Glassner) [Gla95], Визецкого (Wyszecki) и Стайлса (Stiles) [Wys82] и Холла (Hall) [На189].
Упражнения
Фокусное расстояние линзы в фотокамере— это расстояние между центром линзы и той точкой, в которой сходятся параллельные лучи после их преломления линзой. В камере-обскуре фокусное расстояние измеряется как расстояние между передней и задней стенками камеры. Размер кадра на 35-миллиметровой пленке примерно равен 24x36 мм. Будем считать, что глаз человека характеризуется углом зрения 90°. Каково должно быть фокусное расстояние линзы в фотокамере, использующей 35-миллиметровую пленку, чтобы изображение на ней было подобно тому, которое видит глаз человека?
В компьютерной графике криволинейные поверхности, например сферическая, аппроксимируются объектами, состоящими из множества плоских многоугольников (многогранниками). Первым объектом такого рода являет тетраэдр, четыре грани которого являются треугольниками. Отыщите координаты всех его вершин, полагая, что начало системы координат находится в центре аппроксимируемой сферы, а одна из вершин располагается на оси у. Разработайте алгоритм, позволяющий получать все более точную аппроксимацию сферической поверхности правильными многогранниками, используя последовательное деление граней тетраэдра.
5353Рассмотрите отсечение линейного отрезка, заданного крайними точками. Покажите, что, для того чтобы определить, рассекается ли отрезок прямоугольной рамкой, весь он видим или весь невидим, достаточно знать только положение его крайних точек.
53
53
Упражнения
Покажите, что отсечение прямолинейного отрезка верхней стороной отсекающей рамки может быть выполнено независимо от отсечения прочими сторонами рамки. На основании полученных результатов покажите, как можно распараллелить процесс отсечения в системе с конвейерной архитектурой, имеющей четыре независимо работающих блока.
Распространите методы, использованные при выполнении упр. 1.3 и 1.4, на трехмерный случай.
Рассмотрите перспективные изображения куба, представленные на рис. 1.30. Изображение, представленное в левой части рисунка, называется одноточечной перспективой (one-point perspective), поскольку параллельные линии — ребра верхней грани — на изображении пересекаются в одной точке схода (vanishing point). В правой части рисунка показана двухточечная перспектива (two-point perspective) того же куба. Подумайте над тем, какие отношения существуют между наблюдателем (например, камерой) и объектом в обоих случаях.
Рис. 1.30. Перспективные изображения куба
Память, используемая для хранения растрового представления изображения, должна иметь быстродействие, достаточное для того, чтобы выведенное на экран изображение не мелькало. В типовой графической рабочей станции разрешение экрана составляет 1280x1024 пикселей. Если изображение регенерируется 72 раза в секунду, каково должно быть быстродействие памяти буфера кадра? Иными словами, сколько времени должно занимать считывание из буфера кода светимости одного пикселя? Каково будет значение этого параметра для дисплея с разрешением 480x640 пикселей при частоте регенерации 60 Гц и чересстрочной развертке.
В течение одной секунды кинозрителю последовательно “предъявляется” 24 кадра кинофильма. В принципе, эта частота не позволяет избежать мелькания изображения на экране. Каким образом удается избежать мелькания в кинопроекторе? (Подсказка: зрителю кажется, что он видит 48 кадров в секунду.)
Подумайте над тем, как организовать двухмерный графический пакет API для определенного класса приложений, например проектирования СБИС. Перечислите все примитивы и атрибуты, которые необходимо включить в состав такого API.
Можно придумать такую конструкцию цветной ЭЛТ, в которой будет только одна электронная пушка и соответственно отпадет необходимость в теневой маске. Единственный электронный луч будет включаться и выключаться в нужные моменты времени и таким образом возбуждать свечение того люминофора, который нужен. Почему управлять такой ЭЛТ сложнее, чем ЭЛТ с теневой маской?
В типовой ЭЛТ с теневой маской для получения изображения с плавными цветопе- реходами размер пикселя должен быть примерно втрое больше размера триады. Предположим, что монитор должен иметь разрешение 1280x1024 пикселей, диаметр экрана ЭЛТ равен 50 см, а глубина ЭЛТ — 25 см. Рассчитайте расстояние между отверстиями в теневой маске.
ГЛАВА
2
Графическое программирование
И
спользуемый в этой книге подход к изложению проблематики компьютерной графики ориентирован на программирование, и, следуя ему, я стараюсь вовлечь читателя в процесс программирования как можно раньше. Поэтому в данной главе будет описан минимальный набор функций в составе графического API, который достаточен для программирования довольно широкого круга задач двух- и трехмерной графики и поможет читателям усвоить основные концепции построения графических программ.
Двухмерная графика в данной книге рассматривается как частный случай трехмерной графики. Это позволяет нам начать практическую работу еще до детального знакомства с концепциями построения изображений трехмерных объектов. Те программы работы с двухмерными объектами, которые читатели создадут с нашей помощью в процессе изучения материала этой главы, затем без всяких изменений можно будет включать и в трехмерную графическую систему.
Изучая материал данной главы, мы будем анализировать разные аспекты достаточно простой и в то же время весьма информативной задачи — построения так называемого узора Серпинского (Sierpinski gasket). На примере этой задачи будет показано, как с помощью довольно небольшого набора функций можно строить сложные узоры. Как и было обещано ранее, в качестве графического API будет использоваться OpenGL, но обсуждение основных концепций выходит далеко за рамки этого конкретного пакета и приложимо к большинству существующих графических систем, в том числе PHIGS (Programmer's Hierarchical Graphics System) и GKS (Graphical Kernel System). Те функциональные возможности, с которыми читатель познакомится в этой главе, вполне достаточны для разработки довольно сложных программ построения изображения двухмерных объектов, которые, правда, не оснащены средствами взаимодействия с пользователем. В конце этой главы читатели познакомятся с простой программой построения изображения трехмерных объектов.
Узор Серпинского
Для демонстрации принципов программирования задач двухмерной графики мы будем использовать в качестве примера узор Серпинского. История исследования этого узора достаточно длинная, а сам узор представляет особый интерес в такой области, как геометрия фракталов. Узор Серпинского является случайным объектом, определенным рекурсивно, но в пределе его форма стремится к детерминированному объекту.
Предположим, что в исходном состоянии у нас есть три точки на плоскости, причем их положение описано координатами в некоторой подходящей системе координат: (х1( ух), (^2, Хг) и U3. Уз\ Далее выполняется следующая процедура.
Случайно выбирается некоторая точка внутри треугольника, образованного заданными вершинами.
Случайно выбирается одна из трех вершин.
Выбирается точка, равноотстоящая от первой выбранной точки и от выбранной вершины.
Новая точка включается в изображение и помечается маркером, например кружком.
Исходная точка заменяется этой новой точкой.
Повторяется вся процедура, начиная с шага 2.
'2'2Рис. 2.1. Формирование узора СерпинскогоРис. 2.1. Формирование узора СерпинскогоТаким образом, каждая новая точка, созданная на шаге 3, включается в изображение и выводится на устройство отображения. Весь описанный процесс проиллюстрирован на рис. 2.1, где р0— исходная точка (см. шаг 1), a р, и р2— две точки, сформированные в процессе двух последовательных циклов описанного алгоритма.
'2
'2
Рис. 2.1. Формирование узора Серпинского
Рис. 2.1. Формирование узора Серпинского
Прежде чем приступить к разработке программы, весьма желательно представить себе, какой вид будет иметь изображение, полученное в результате применения этого алгоритма. Попробуйте нарисовать его вручную на листе бумаге — результат вас скорее всего удивит.
Ниже представлен возможный вариант графической программы построения узора.
main( ) {
initialize_the_system();
for(some_number_of_points)
{
pt = generate_a_point();
display_the_point(pt);
}
cleanup();
}
Хотя в окончательном виде наша программа на OpenGL будет иметь несколько другой вид, но останется такой же простой. Процесс разработки программы будет прослежен поэтапно. Сначала сконцентрируем внимание на двух основных этапах — формирование точки и вывод ее на экран. Нужно ответить на два вопроса: “Как представить точки в пространстве?” и “Следует ли использовать двух-, трехмерное или другое представление?”
Перьевой плоттер
Большинство графических систем первого поколения были двухмерными. В них использовалась концепция .модели перьевого плоттера (pen-plotter model). Такое устройство формирует изображение на бумаге за счет перемещения пера, закрепленного на двух подвижных направляющих (рис. 2.2). Одна направляющая перемещается в продольном направлении (вдоль оси у), а другая — в поперечном (вдоль оси х). Специальный механизм поднимает и опускает перо. В опущенном состоянии перо при перемеше-«
нии оставляет след на бумаге — этот след и формирует изо- - —^w
бражение. Такого рода устройства и поныне используются в Рмс 22 Перьевой плоттер графических системах при построении технических чертежей. Для программного управления графопостроителями применяются разнообразные пакеты API — такие как LOGO, GKS и PostScript. Хотя эти пакеты и отличаются друг от друга функциональными возможностями, но во всех используется та же самая идея формирования на носителе следа подвижного пера, т.е. фактически воспроизведения процесса ручного вычерчивания с помощью карандаша. Пользователь при этом
имеет дело с участком плоскости, ограниченным размерами планшета.
Поведение такой графической системы описывается двумя основными графическими функциями:
moveto(х, у); lineto(x,y);
При выполнении функции moveto () перо приподымается и затем перемещается в точку с координатами (х, у), не оставляя следа на носителе (при этом траектория перемещения не имеет значения — главное, что заканчиваться она должна в точке с заданными координатами). При выполнении функции lineto() перо опускается и затем перемещается по прямой из текущего положения в точку с заданными координатами (х, у), оставляя след на носителе. Если добавить к этим функциям еще и функции настройки (выбор пера подходящего цвета и толщины), то получим законченную двухмерную графическую систему. Ниже приведен фрагмент программы вычерчивания несложного изображения с помощью такой системы:
moveto(0, 0);
lineto(l, 0);
lineto(l, 1);
lineto(0, 1);
lineto(0, 0);
Эта программа формирует изображение, представленное на рис. 2.3,а. Добавив в программу еще несколько операторов, получим при ее выполнении изображение куба в изометрической проекции (рис. 2.3,6).
moveto(0, 1);
lineto(0.5, 1.866);
lineto(1.5, 1.866);
lineto(1.5, 0.866);
lineto(l, 0);
moveto(l, 1);
lineto(l,5, 1.866);
Рис. 2.3. Результат выполнения простой программы вычерчивания: а — квадрат; б — изометрическая проекция кубаРис. 2.3. Результат выполнения простой программы вычерчивания: а — квадрат; б — изометрическая проекция кубаПо этому принципу построены некоторые приложения, например системы компоновки страниц в полиграфии. Язык описания страниц PostScript является стандартным средством управления принтерами, в котором реализована эта же идея.
Рис. 2.3. Результат выполнения простой программы вычерчивания: а — квадрат; б — изометрическая проекция куба
Рис. 2.3. Результат выполнения простой программы вычерчивания: а — квадрат; б — изометрическая проекция куба
Но нас значительно больше интересует изображение трехмерных объектов. Модель перьевого плоттера не очень “вписывается” в процесс формирования изображений трехмерных объектов. Если, например, использовать эту модель для построения изображения трехмерного объекта на двухмерном планшете (то ли вручную, то ли с помощью компьютера), то в первую очередь нужно
решить, где на планшете должна располагаться двухмерная точка, соответствующая определенной точке на изображаемом трехмерном объекте. Такие двухмерные точки являются, как было показано в главе 1, проекциями точек трехмерного пространства. Математически процесс формирования проекций описывается тригонометрическими соотношениями. Мы проанализируем эти соотношения в главе 5, но предпочтительнее использовать такой пакет API, который позволял бы пользователю формулировать свою задачу в терминах трехмерного пространства и не выполнять в прикладной программе никаких тригонометрических преобразований. Все операции, связанные с проективным преобразованием, должны взять на себя функции API. Такой подход очень удобен для пользователей, поскольку избавляет их от необходимости создавать доморощенные программы выполнения таких преобразований в составе своей прикладной системы. Пакет API, созданный профессионалами, решит эту задачу лучше и эффективнее.
Решение двухмерных задач, таких как построение узора Серпинского, можно рассматривать как частный случай работы в трехмерном пространстве. С точки зрения математики двухмерную плоскость или простую двухмерную криволинейную поверхность можно считать подпространством трехмерного пространства. Следовательно, выражения, справедливые для трехмерного пространства, будут справедливы и для его подпространства.
Для простоты будем считать планшет бесконечным и совместим его поверхность с плоскостью z=0. В таком случае точки р=(х, у, 0) на планшете можно рассматривать или как точки трехмерного пространства, или как точки р=(х, у) двухмерного подпространства — плоскости планшета. OpenGL, как и большинство других трехмерных графических систем, позволяет пользователю использовать любое из описанных представлений, причем внутреннее представление не зависит от той формы “внешнего” представления, которую выбрал для себя пользователь. Точку можно представить разными способами, но самый простой — рассматривать ее как триаду в трехмерном пространстве:
Р =Р =х
Р =
Р =
У г
Сейчас мы не будем обращать внимание на то, в какой системе координат задан компонент р.
В этой книге мы чаще всего будем использовать термин вершина (vertex), а не точка (point). Вершина характеризует определенное положение в пространстве, причем в компьютерной графике используются двух-, трех- и четырехмерные пространства. С точки зрения графической системы вершина представляет собой атомарный графический объект, который соответствует простейшему геометрическому объекту — точке. Двумя вершинами определя-
ется отрезок прямой — второй по простоте графический объект, тремя — треугольник и окружность, четырьмя — четырехугольник и т.д.
В OpenGL имеется множество форм представления объектов, из которых пользователь может выбирать ту, которая наилучшим образом соответствует специфике определенной задачи. Обобщенная форма представления вершины в OpenGL имеет вид
gIVertex*
Здесь * можно заменить двумя или тремя символами в формате nt или ntv, где п задает количество размерностей (2, 3 или 4), t описывает тип данных: целый (integer) — i, с плавающей точкой одинарной точности (float) —f или с плавающей точкой удвоенной точности (double) — d. Третий символ v, если он присутствует в определении вершины, означает, что переменные (значения координат) задаются указателем на массив, а не списком аргументов. В дальнейшем в каждом частном случае мы будем выбирать именно ту форму представления вершины, которая наиболее подходит для данного случая. Необходимую дополнительную информацию о формах представления объектов в OpenGL читатель найдет в руководстве пользователя OpenGL Reference Manual [Ope97b]. Еще раз обращаю ваше внимание на то, что при любом внешнем представлении объекта (зададите вы вершину как двухмерный объект или как трехмерный) его внутреннее представление будет одним и тем же. В главе 4 мы рассмотрим четырехмерное внутреннее представление, но сейчас вам нет нужды беспокоиться об этих деталях реализации.
При программировании на языке OpenGL мы часто будем пользоваться базовыми типами переменных — GLf loat и GLint, — а не основными типами переменных языка С, такими как float и int. Эти типы определены в соответствующем заголовочном файле с помощью директивы #def ine, например, так:
fdefine GLfloat float
Использование специализированных типов переменных в OpenGL обеспечивает дополнительную гибкость, поскольку позволяет переопределять эти типы (например, заменить формат числа с одинарной точностью на формат с удвоенной точностью) и изменять таким образом точность вычислений, не изменяя текст прикладной программы.
Вернемся к функции определения вершины. Форма
glVertex2i(GLint xi, GLint yi)
подходит для представления вершины на двухмерной плоскости, причем координатами являются целые числа. Представление
glVertex3f(GLfloat х, GLfloat у, GLfloat z)
задает вершину в трехмерном пространстве, причем значения координат будут представлены вещественными числами одинарной точности с плавающей точкой. Можно использовать для задания координат и массив. Сначала нужно определить массив значений координат:
GLfloat vertex[3];
а затем задать вершину в приведенном ниже формате:
glVertex3fv(vertex);
Набор вершин позволяет описать разнообразные геометрические объекты, причем для каждого вида объектов требуется определенное количество вершин. Язык OpenGL позволяет сгруппировать любое количество вершин в описании объекта с помощью пары связанных функций glBegin() и glEnd(). Аргумент функции glBegin() задает тип геометрического объекта, который определяется следующим далее набором вершин. Например, отрезок прямой задается следующим фрагментом программы:
glBegin(GL_LINES);
glVertex2f(xl,yl);
glVertex2f(x2,y2);
glEnd();
Те же вершины позволяют определить и пару геометрических объектов — точек: glBegin(GL_POINTS);
glVertex2f(xl,yl);
glVertex2f(х2,у2);
glEnd();
Теперь можно приступить и к разработке программы, формирующей узор Серпинского. Будем считать, что все точки узора должны располагаться внутри единичного квадрата, левый нижний угол которого находится в точке (0, 0), — это обычный вариант, который при желании легко можно изменить.
Сначала задумайтесь над тем, как представлять геометрические данные в программе. При работе в трехмерном пространстве можно использовать базовое представление в форме трех раздельных переменных х, у, z, но многие программисты предпочитают подход, более близкий в объектно-ориентированному, например использовать структурный тип point3 для трехмерных вершин и point2 — для двухмерных. Тогда в объектно-ориентированной программе можно было бы определить операции с переменными такого структурного типа вроде приведенной ниже:
new_point = old_point + random_number
Но ни язык С, ни язык OpenGL не располагают такими возможностями (по крайней мере на сегодняшний день). Для представления двухмерных точек в качестве компромисса между низкоуровневым представлением и абстракцией на высоком уровне будем использовать массив из двух элементов:
typedef GLfloat point2[2);
Ниже приведен текст функции display(), которая формирует 5000 точек узора после каждого вызова. Массив вершин базового треугольника узора vertices[3] определен в самой функции display () как массив элементов типа point2.
void display(void) {
point2 vertices[3] = {{0.0, 0.0}, {250.0. 500.0},
{500.0, 0.0}};
/* Произвольный треугольник */
static point2 p = {75.0 ,50.0};
/* Установка исходной точки */
int j, k;
int rand(); /* Стандартный генератор случайных чисел */
for(k=0;k<5000;k++)
{
/* Случайным образом выбрать индекс вершины из множества 0,1,2 */ j=rand()%3;
/* Вычисление новой точки */
р[0] = (р[0] + triangle[j][0])/2;
P[l] = (РШ + triangle[j][l])/2;
/* Вывод на экран новой точки */
glBegin(GL_POINTS);
glVertex2fv(p);
glEnd();
}
glFlush();
}
Функция rand() в этой программе— стандартный генератор случайных чисел. Для формирования случайной последовательности, элементами которой могут быть только три целых числа 0, 1 и 2, используется операция вычисления остатка при целочисленном делении. При небольшом количестве итераций статистические характеристики генератора случайных чисел большого значения не имеют, а потому в программе можно использовать любой другой генератор, а не только тот, который вызывается стандартной функцией rand().
Функция glFlush() обеспечивает вывод формируемых точек на экран сразу же по окончании вычислений их координат. Если не обращаться к этой функции, точки все равно появятся на экране, но при этом будет ощутима задержка вывода, особенно при наличии сетевого окружения. Эту программу в таком виде еще нельзя считать завершенной. На рис. 2.4 показано, как будет выглядеть сформированный ею узор.
Рис. 2.4. Узор Серпинского, сформированный из5000 случайных точекРис. 2.4. Узор Серпинского, сформированный из5000 случайных точек
Рис. 2.4. Узор Серпинского, сформированный из
5000 случайных точек
Рис. 2.4. Узор Серпинского, сформированный из
5000 случайных точек
Нам осталось разобраться с некоторыми нюансами формируемого узора и, соответственно, программы:
■ Как задать цвет узора?
■ В какой части экрана появится изображение?
■ Как управлять размером изображения?
Как выделить область экрана — видовое окно — для формируемого изображения?
Какая часть нашего “бесконечного” планшета появится на экране?
Как долго созданное изображение будет оставаться на экране?
Хотя получить ответы на перечисленные вопросы и весьма важно, на первый взгляд кажется, что они стоят как-то в стороне от рассматриваемых сейчас базовых концепций. Как вы увидите далее, основной текст тех фрагментов программы, которые мы разработаем в процессе поиска ответов на эти вопросы, практически без изменений войдет и в другие программы формирования изображений. Следовательно, затратив сейчас некоторое время на поиск ответов, мы будем вознаграждены в дальнейшем.
Системы координат
Сейчас вы скорее всего гадаете, каким образом можно интерпретировать сформированные программой значения координат— переменных х, у и z— в спецификации вершин. В каких линейных единицах они выражены — футах, метрах, микронах? Где находится точка отсчета, от которой откладываются соответствующие смещения вдоль осей? В любом случае простейший ответ будет таков — все зависит от программиста, т.е. от вас.
При работе с ранними системами компьютерной графики требовалось, чтобы в прикладной программе вся геометрическая информация задавалась в терминах системы координат экрана используемого устройства отображения. Если бы такой способ описания распространялся и на современные прикладные программы высокого уровня, нам пришлось бы говорить о точках в терминах пикселей экрана или сантиметров от угла экрана. Но этот метод кажется совершенно абсурдным для описания изображений, в которых расстояния измеряются в световых годах (при отображении данных, с которыми имеет дело астрономия), или таких, где расстояния измеряются микронами (при выводе изображений топологии интегральных микросхем). Одно из достоинств современных графических программ — возможность использования в них любых единиц измерения, которые сочтет необходимым выбрать пользователь, руководствуясь только спецификой решаемой задачи. Концепция графики, инвариантной к устройствам (device-independent graphics), освободила прикладных программистов от необходимости учета деталей конструкции устройств ввода-вывода графической информации. По отношению к применяемой пользователем системе координат используется термин мировая система координат (world coordinate system) или прикладная система координат (problem coordinate system). Пользователь может использовать любой диапазон значений координат, причем единственным (и очень незначительным) ограничением является диапазон представления вещественных чисел в современных компьютерах в формате с плавающей точкой.
Система отсчета положения на экране конкретного устройства отображения ранее называлась координатами физического устройства (physical-device coordinates), или просто координатами устройства (device coordinates). Для растровых устройств, каковыми являются подавляющее большинство современных дисплеев, мы используем термин координаты растра (raster coordinates), или координаты экрана (screen coordinates). Координаты растра всегда представляются целыми числами, поскольку пиксели занимают совершенно определенное положение в узлах фиксированной сетки (или, если выразиться более профессиональным языком, пиксели по самой своей природе являются дискретными объектами и, следовательно, адресуются целыми числами).
Между координатами некоторой точки в системе координат прикладной программы и в системе координат растра существует определенное соответствие, как показано на рис. 2.5. Выполнение преобразования из системы координат прикладной программы в координаты растра выполняется графической системой, и программист беспокоиться об этом не должен.
Как будет показано в последующих разделах, в прикладной программе следует специфицировать только несколько параметров, необходимых для выполнения такого преобразования, — область отображаемого мирового пространства и размер поля экрана.
Координаты прикладной Координаты растра программы
Рис. 2.5. Отношение между координатами прикладной программы и координатами растра
Прикладной интерфейс OpenGL
Поскольку функцию, выполняющую основной алгоритм построения узора, мы уже разработали, то теперь рассмотрим вспомогательные операции, которые позволяют управлять видом объектов на экране. Нам понадобится также управлять ходом выполнения программы и взаимодействовать с операционной системой. Сначала более детально рассмотрим API OpenGL — программный инструмент, которым нам предстоит пользоваться. Поскольку внутреннее представление вершин не зависит от того, определили мы их как двух- или трехмерные объекты, то все, что будет изложено в этом разделе, в равной степени относится и к трехмерным задачам графики. Естественно, работа в трехмерном пространстве предоставляет прикладному программисту гораздо более широкие возможности, но ведь мы находимся только в самом начале пути, и у нас еще все впереди. Основная задача этой главы — познакомить читателей с методами спецификации графических примитивов, а о методах взаимодействия пользователя с системой мы поговорим позже, в главе 3.
Структура OpenGL аналогична структуре большинства других графических API, в том числе PHIGS и GKS. Следовательно, все усилия, которые придется приложить при изучении OpenGL, будут оплачены сторицей, так как одновременно вы постигаете и возможности других аналогичных систем. По сравнению с другими API изучать OpenGL гораздо проще, но это не означает, что функциональные возможности его менее широкие, чем у “конкурентов”. OpenGL поддерживает работу как с двухмерными, так и с трехмерными примитивами, что вы увидите в программах, представленных в главах 2-6. В этом пакете реализованы и современные технологии тонирования, что будет продемонстрировано в программах, описанных в главах 8-12.
Поскольку основная цель этой книги — изучение методов решения задач, применяемых в компьютерной графике, то и OpenGL мы будем использовать именно в качестве вспомогательного средства для достижения этой цели. Следовательно, вы не найдете в этой книге исчерпывающего описания всех доступных функций OpenGL и многих деталей, но, тем не менее, все представленные программы носят законченный вид. Более детальные сведения об источниках дополнительной информации, касающейся как OpenGL, так и других графических API, вы найдете в обзоре рекомендуемой литературы в конце главы.
Графические функции
При знакомстве читателей с графическими пакетами мы будем использовать подход, ставший уже классическим в кибернетике, — концепцию черного ящика (black box). Этим термином в технике принято называть устройства и системы, функциональные возможности которых описываются только соответствием между определенной входной и выходной информацией, а о внутреннем механизме работы никакой информации не имеется. Таким образом, мы будем считать графическую систему подобным черным ящиком, входом которого являются различные функции, вызываемые из прикладной программы, информация, передаваемая с помощью устройств ввода, таких как мышь или клавиатура, и сообщения, передаваемые операционной системой. Выходом системы являются в первую очередь графические образы, формируемые на экране. На данном этапе входом для черного ящика будут только вызовы функций, а выходом — примитивы, отображаемые на экране ЭЛТ (рис. 2.6). Аналогичный подход можно использовать и при изучении других графических API.
Вызовы функцийВыход
Прикладная f~ Графическая IУстройства I
программа система ввода-вывода I
Рис. 2.6. Графическая система как черный ящик
Описывать возможности API мы будем через функции его библиотеки. В состав мощного пакета API может входить несколько сотен функций, а потому желательно сразу же разделить их на категории. Мы рассмотрим функции шести категорий.
Функции описания примитивов (primitive functions') определяют объекты нижнего уровня иерархии — примитивы, — которые способна отображать графическая система. В большинстве графических API имеются такие примитивы, как точки, отрезки прямых линий, многоугольники, пиксели, текст и различного рода криволинейные отрезки и участки криволинейных поверхностей.
Если с помощью примитивов определяется, что появится на экране, то с помощью атрибутов определяется, как будут выглядеть отображаемые объекты, т.е. атрибуты определяют способ вывода объектов на экран. Функции задания атрибутов (Attribute functions) позволяют прикладному программисту выполнять широкий круг операций настройки изображения — от выбора цвета до указания образца заливки внутренней области многоугольника или шрифта для надписей на графике.
Нужно задать параметры используемой модели синтезированной камеры, с помощью которой создается изображение. Как было сказано в главе 1, от прикладного программиста требуется выбрать положение и ориентацию камеры в мировой системе координат и параметры объектива, в частности фокусное расстояние. Зная эти параметры системы, он сможет не только правильно построить изображение, но и отсечь те объекты, которые оказываются вне поля зрения. Последнее позволяет не тратить времени впустую. Функции визуализации (viewing functions) позволяют задать разнообразные виды, хотя разные типы API существенно отличаются возможностями манипулирования видами.
Одна из наиболее интересных возможностей хорошего графического пакета — набор функций геометрических преобразований (transformation functions), которые позволяют пользователю выполнять различные преобразования объектов — поворот, плоскопараллельный перенос, масштабирование и т.п. Такого рода операции мы часто будем использовать при описании методов визуализации (глава 5) и моделирования (глава 8).
Для создания интерактивных приложений нужно, чтобы в составе API имелись и функции ввода графической информации {input functions). Эти функции играют роль промежуточного звена между устройствами ввода, такими как клавиатура, мышь, планшеты разного рода, дигитайзеры, и прикладной программой. В главе 3 читатели смогут познакомиться с функциями, использующими три разных режима ввода, и с различными устройствами ввода.
В реальных приложениях программисту приходится думать и об управлении средой функционирования программы, особенно в случае, когда речь идет о многопроцессорной системе, многооконной операционной среде или сетевой среде со множеством пользователей. Для управления процессом выполнения программы в состав API, как правило, включаются специальные управляющие функции {control functions). Они позволяют прикладной программе взаимодействовать с операционной системой, инициализировать приложение и обрабатывать ошибки других графических функций.
Интерфейс OpenGL
Имена функций OpenGL начинаются с букв gl , а сами функции хранятся в библиотеке, которую мы будем обозначать аббревиатурой GL. Помимо основной, существует и несколько дополнительных библиотек, которые мы также будем использовать в своей работе. Первая из них— библиотека графических утилит {GLU— graphics utility library). Функции этой библиотеки обращаются только к функциям из GL и в ее состав входят функции формирования часто встречающихся сложных объектов вроде сферических поверхностей, которые пользователю не имеет смысл “изобретать” самостоятельно. Эта библиотека входит практически во все версии пакета OpenGL. Вторая библиотека отвечает за взаимодействие с системой окон. В качестве таковой мы будем использовать библиотеку GLUT (GL Utility Toolkit), которая содержит функции, обеспечивающие пользователя основными возможностями, характерными для большинства современных многооконных систем. С некоторыми функциями из состава GLUT читатель познакомится уже в этой главе, а более подробная информация о них содержится в главе 3, где будут описаны возможности ввода информации и взаимодействия с пользователем. На рис. 2.7 схематически представлена организация системы библиотек в той версии OpenGL, которая работает под управлением операционной системы X Window. Обратите внимание на то, что из библиотек OpenGL вызываются функции, которые входят в состав других библиотек, но прикладная программа с ними напрямую не работает. Аналогичная организация используется и в версиях OpenGL, работающих под управлением других операционных систем, в частности Microsoft Windows.
Рис. 2.7. Организация библиотеки OpenGL
Для улучшения “читабельности” текста программы в OpenGL предлагается использовать макросы. В частности, в заголовочных файлах (h-файлах) определены макросы GL_FILL и GL POINTS. В большинстве версий для включения заголовочных файлов glut.h, gl.h и glu.h достаточно вставить в текст программы директиву iinclude <GL/glut.h>
или
Iinclude <glut.h>
Примитивы и атрибуты
Среди специалистов в области компьютерной графики не прекращаются дебаты вокруг вопроса, носящего, казалось бы, совершенно схоластический характер: “Что считать примитивом?” и “Какие примитивы следует поддерживать в составе API?” Скорее всего спорщики никогда не придут к единому мнению. Минималисты считают, что примитивами должны быть только такие объекты, которые в большинстве существующих графических систем формируются аппаратно. Кроме того, набор примитивов должен обладать свойством ортогональности, т.е. ни один из элементов этого набора нельзя было бы получить из других. Минимальные системы обычно поддерживают набор, состоящий из отрезка прямой, многоугольника и некоторой формы текста (последовательности символов). Все элементы набора могут быть эффективно сформированы аппаратно. В другом лагере— максималистов — придерживаются мнения, что в набор следует включить и такие объекты, как окружности, кривые разного рода, поверхности и сплошные тела. В качестве основного аргумента при этом выдвигается забота о пользователе, которого следует избавить от самостоятельного формирования таких объектов, ставших неотъемлемой частью большинства развитых приложений. Но поскольку возможностью поддерживать на аппаратном уровне работу с большим набором довольно сложных примитивов обладают только немногие модели графических станций, программу, использующую такой большой набор, нельзя будет переносить с одной платформы на другую.
В этом споре разработчики OpenGL заняли промежуточную позицию (как правило, самую удобную в споре). В базовой библиотеке используется небольшой набор примитивов, а в дополнительной библиотеке GLU содержатся функции построения более сложных объектов, которые с точки зрения прикладного программиста также являются примитивами.
Базовые примитивы OpenGL специфицируются набором точек в пространстве (вершин). Формат определения объектов представлен ниже.
glBegin(<тип>);
glVertex*(...);
glVertex*(...);
glEnd();
Значение <тип> определяет вид объекта и несет OpenGL информацию о том, каким образом нужно интерпретировать следующий далее список вершин. Между вызовами функций glBegin () и glEnd() могут быть включены операторы вызовов других функций OpenGL. Например, здесь могут стоять операторы вызова функций задания атрибутов или вычисления значений координат вершин. Основное концептуальное отличие примитивов разного типа состоит в том, имеют ли они внутреннюю область. Все примитивы, кроме точек, определяются либо в терминах набора вершин, либо в терминах отрезков прямых линий (не следует путать отрезок прямой линии, который имеет ограниченные размеры, и прямую, которая представляет собой геометрический объект бесконечной протяженности). Естественно, что отдельный
отрезок задается парой вершин, но отрезок играет настолько большую роль в описании прочих объектов, что его можно рассматривать как базовый примитив. Отрезки можно использовать для аппроксимации кривых линий или с помощью отрезков соединять соседние точки на графике функции. Отрезками отображаются и ребра замкнутых геометрических объектов, которые имеют внутреннюю область, например многоугольников.
В OpenGL есть несколько способов формирования отрезков (рис. 2.8).
GLJ.INESGLJ.INESGL_LINE_STRIPGL_LINE_LOOPGL_LINE_STRIPGL_LINE_LOOPРзPie * еРз
GLJ.INES
GLJ.INES
GL_LINE_STRIPGL_LINE_LOOP
GL_LINE_STRIPGL_LINE_LOOP
Рое еР4
Р7* е *Р5РбGL_POINTS
Рис. 2.8. Типы точек и отрезков
Отрезки (GL_LINES)
Тип GL LINES задает формирование отрезка по двум вершинам, которые рассматриваются как его начальная и конечная точки. Обратите внимание на то, что построенные таким образом отрезки, как правило, не стыкуются друг с другом в крайних точках.
Ломаные линии (GL_LINE_STRIP)
Если необходимо сформировать ломаную линию (polyline) — последовательность стыкующихся отрезков, — то используется тип GL LINE_STRIP. Чаще всего ломаные линии используются для аппроксимации кривых. Если желательно получить замкнутый контур, то можно либо указать в качестве конечной и начальной в списке одну и ту же вершину, либо использовать специальный тип, определяющий именно замкнутую ломаную, — GL_LINE_LOOP. В этом случае OpenGL самостоятельно свяжет отрезком последнюю вершину в списке с первой.
Многоугольники
Отрезки и ломаные можно использовать для моделирования ребер более сложных объектов, но примитив типа замкнутая ломаная не имеет одного важного свойства — внутренней области. Подобным свойством в OpenGL обладают примитивы другого типа— многоугольники (polygon) (рис. 2.9). ухитя по форме замкнутая ломаная ничем не отличается от многоугольника с совпадающими вершинами, она не имеет внутренней области. Многоугольники играют особую роль в компьютерной графике, поскольку в растровых системах изображение таких примитивов формируется очень быстро. С помощью многоугольников аппроксимируют криволинейные поверхности. Производительность графических систем принято сейчас оценивать в количестве многоугольников, выводимых на экран в течение одной секунды. Изображение прямоугольника на экране может выглядеть по-разному. Можно выводить только контур, заливать внутреннюю область одним цветом или заполнять определенным рисунком (образцом — pattern), причем в
залитом многоугольнике ребра можно и не отображать (рис. 2.10). Хотя контур многоугольника определяется очень легко упорядоченным списком его вершин, неправильное (неоднозначное) определение его внутренней области может привести к некорректному ее заполнению. Для корректного отображения внутренней области многоугольника он должен обладать тремя свойствами — быть простым, выпуклым и плоским.
Рис. 2.9. Примитивы, имеющие внутреннюю областьРис. 2.9. Примитивы, имеющие внутреннюю областьРис. 2.10. Способы отображения .многоугольниковРис. 2.10. Способы отображения .многоугольников
Рис. 2.9. Примитивы, имеющие внутреннюю область
Рис. 2.9. Примитивы, имеющие внутреннюю область
Рис. 2.10. Способы отображения .многоугольников
Рис. 2.10. Способы отображения .многоугольников
б)Рис. 2.11. Многоб)Рис. 2.11. Многоугольники: а — простой; б — непростойугольники: а — простой; б — непростой
б)
Рис. 2.11. Много
б)
Рис. 2.11. Много
угольники: а — простой; б — непростой
угольники: а — простой; б — непростой
Если в двухмерном многоугольнике нет пересекающихся ребер, то он называется простым. Как видно на рис. 2.11, простой многоугольник имеет однозначно определенную внутреннюю область. Выяснить, является ли многоугольник простым, можно по положению его вершин, но алгоритм проверки потребует множества вычислений (см. упр. 2.14). По- N. этому в большинстве графических API предполагается, что такая проверка >, выполняется в прикладной программе. Вопрос, как должна поступить гра- / Iфическая система в случае, если заданный для отображения многоугольник
Iне является простым, будет рассмотрен в главе 7.
4С точки зрения алгоритмов заливки внутренней области недостаточ-
°)но, чтобы многоугольник был простым. В некоторых API гарантируется
правильное выполнение заливки только в случае, если многоугольник является выпуклым. Говорят, что геометрический объект является выпуклым (convex), если все точки отрезка, соединяющего две любые точки внутренней области объекта, также принадлежат внутренней области (рис. 2.12). Хотя в этой главе мы будем иметь дело только с двухмерными (плоскими) объектами, это определение справедливо и для трехмерных объектов. В число выпуклых объектов входят треугольники, тетраэдры, прямоугольники, круги, сферы и параллелеп''"'цы (рис. 2.12). Существует множество алгоритмов проверки выпуклости объектов (см. упр. 2.20). Но следует отметить, что проверка выпуклости, как и проверка простоты многоугольника, требует достаточно сложных вычислений и, как правило, возлагается на прикладную программу.
Рис 2.12. К определению понятия “выпуклости”Рис 2.12. К определению понятия “выпуклости”Рис. 2.13. Выпуклые геометрические объекты
Рис 2.12. К определению понятия “выпуклости”
Рис 2.12. К определению понятия “выпуклости”
При работе с многоугольниками в трехмерном пространстве появляются дополнительные сложности. В этом случае многоугольник не обязательно является плоским, т.е. все его вершины не обязательно лежат в одной плоскости. Одна из геометрических теорем утверждает, что любые три неколлинеарные точки (т.е. точки, не лежащие на одной прямой) однозначно определяют плоскость, в которой лежит треугольник, построенный на этих трех вершинах. Этот вывод используется в большинстве графических систем. Следовательно, если использовать для аппроксимации криволинейных поверхностей только треугольники, то графическая система будет иметь дело исключительно с плоскими многоугольниками, которые всегда тонируются корректно. Кроме того, заливка треугольных областей выполняется аппаратно или программно значительно быстрее, чем заливка многоугольников другой формы.
Типы многоугольников в OpenGL
Теперь вернемся к типам объектов в OpenGL. Существует несколько типов плоских объектов, имеющих внутреннюю область (рис. 2.14).
Pz Р5 Рб GL_TRIANGLESPz Р5 Рб GL_TRIANGLESРаPie * еРз
Pz Р5 Рб GL_TRIANGLES
Pz Р5 Рб GL_TRIANGLES
Ро* еР4
Р7е е *р5РбGL_POINTS
Рис. 2.14. Типы многоугольников в OpenGL
Многоугольники (GL_POLYGON)
Ребра объектов этого типа совпадают с отрезками замкнутой ломаной, построенной на том же наборе вершин. Внутренняя область объекта заполняется в соответствии с заданными значениями атрибутов заливки. Сами по себе ребра имеют конечную толщину. В большинстве графических систем можно либо заливать внутреннюю область определенным цветом (заполнять определенным образцом), либо вычерчивать ребра линиями определенной толщины, но нельзя совмещать эти два способа отображения. В OpenGL можно использовать функцию glPolygonMode() для отображения ребер и тем самым отменять используемый по умолчанию режим заливки. Если же желательно сформировать совмещенное изображение многоугольника в обоих режимах, придется фактически создать два объекта на одном и том же наборе вершин. Первый объект— это стандартный многоугольник, а второй — либо многоугольник, сформированный функцией glPolygonMode(), либо замкнутая ломаная.
Треугольники и четырехугольники (GL_TRIANGLES, GL_QUADS)
Для формирования треугольников и четырехугольников в OpenGL используются специальные примитивы. Треугольник определяется тремя вершинами, а четырехугольник— четырьмя. Эти объекты тонируются быстрее, чем такие же по форме объекты типа GL_POLYGON.
Полосы (GL_TRIANGLE_STRIP, GL_QUAD_STRIP, GL_TRIANGLE_FAN)
Объекты этих типов представляют собой группу треугольников или четырехугольников, в которых отдельные фигуры совместно используют некоторые вершины. В треугольной полосе — объекте типа GL_TRIANGLE_STRIP — каждая последующая вершина в списке комбинируется с двумя предыдущими и определяет таким образом очередную треугольную ячейку полосы (рис. 2.15). В полосе, состоящей из четырехугольных ячеек — объекте типа
GL_QUAD_STRIP, — каждая очередная пара вершин в списке комбинируется с предыдущей парой и задает очередную четырехугольную ячейку. Объект типа GL TRIANGLE_FAN — розетка— строится на основе одной фиксированной вершины (центра), которая идет первой в списке вершин. Следующие две вершины в списке определяют первый треугольный “лепесток”, а затем каждая очередная вершина вместе с предыдущей в списке и центральной определяет следующий “лепесток”.
Р1 РЗ Р5 Р7Р1 РЗ Р5 Р7GL_QUAD_STRIPGL_QUAD_STRIPPlРЗ₽5Р7
Р1 РЗ Р5 Р7
Р1 РЗ Р5 Р7
GL_QUAD_STRIP
GL_QUAD_STRIP
•••
•••
РОР2Р4Рб
GLPOINTS
Рис. 2.15. Полосы из треугольников и четырехугольников
Текст
Очень редко в графическое изображение не включаются поясняющие надписи. Хотя в неграфических приложениях вывод информации в текстовом виде используется повсеместно, в графических вывод текста вызывает определенные сложности. В неграфических приложениях мы обычно имеем дело с небольшим набором символов, отображаемых единообразно. В графических приложениях текст, как правило, формируется из символов разного размера, начертания, цвета и т.д. В распоряжение прикладного программиста также предоставляется множество шрифтов, определяющих форму символов на экране, таких как Times, Computer Modern или Helvetica.
Существуют два типа шрифтов — штриховой и растровый. Штриховой шрифт формируется по тому же принципу, что и прочие графические примитивы (рис. 2.16). Начертание символа определяется вершинами соответствующих прямолинейных и криволинейных отрезков. Если символ формируется из замкнутых контуров, то его внутреннюю область можно залить. Преимущество использования штриховых шрифтов заключается в том, что с ними можно обращаться в графической системе точно так же, как с любыми другими графическими объектами. Несомненным достоинством штриховых шрифтов является и сохранение начертания во всех деталях при выполнении геометрических преобразований — масштабировании или повороте. Поэтому определение символов набора выполняется только для одного, базового, размера, а все прочие получаются после выполнения стандартных геометрических преобразований — масштабирования и поворота.
Computer GraphicsРис. 2.16. ШтриховойшрифтComputer GraphicsРис. 2.16. ШтриховойшрифтНо следует отметить, что исходное описание шрифта из 128 или 256 символов может быть довольно сложным и занимать много места в памяти. Стандартные шрифты PostScript создаются из отрезков полиномиальных кривых и демонстрируют все достоинства и недостатки штриховых шрифтов. Существуют различные модификации шрифтов PostScript для приложений с высоким и низким разрешением. Довольно часто разработчики пытаются справиться с проблемой медленной обработки таких объектов в процессе тонирования изображения, возлагая значительную часть связанных с этим операций
Computer Graphics
Рис. 2.16. Штриховой
шрифт
Computer Graphics
Рис. 2.16. Штриховой
шрифт
на аппаратное обеспечение принтеров. Эта стратегия тесно связана с принципами организации систем “клиент/сервер” и будет рассмотрена в главе 3.
Растровый шрифт определяется значительно проще, а отображается быстрее (рис. 2.17). Символы шрифта определяются на прямоугольной области и представляют собой блоки битов (bit blocks). Каждый блок задает определенный символ в виде образа из нулей и единиц, которые соответствуют засвеченным и незасвеченным точкам растра. При отображении определенный таким способом символ помещается в буфер кадра с помощью операции побитового переноса (bitbit), которая выполняется очень быстро. Мы рассмотрим эту операцию подробно в главе 9. В составе OpenGL есть функции, которые позволяют прикладной программе напрямую манипулировать содержимым буфера кадра.
Увеличить размер символов растрового шрифта можно только дублированием пикселей, что при большом увеличении приводит к формированию символов довольно “грубой” формы (рис. 2.18). Выполнять какие-либо геометрические преобразования растрового шрифта бессмысленно. Кроме того, поскольку растровые шрифты, как правило, хранятся в постоянной памяти, они не переносятся с компьютера на компьютер.
Рис. 2.17. Растровый шрифтРис. 2.17. Растровый шрифтРис. 2.18. Изменения размера растрового шрифтаРис. 2.18. Изменения размера растрового шрифта
Рис. 2.17. Растровый шрифт
Рис. 2.17. Растровый шрифт
Рис. 2.18. Изменения размера растрового шрифта
Рис. 2.18. Изменения размера растрового шрифта
Символы как штриховых, так и растровых шрифтов формируются из других примитивов, а потому в составе основной библиотеки OpenGL нет специального примитива для формирования текста. Но в составе дополнительной библиотеки GLUT имеется несколько наборов символов (как штриховых, так и растровых), которые определены программно, а следовательно, являются переносимыми. Например, для включения в изображение символа растрового шрифта размером 8x13 пикселей нужно вызвать функцию glutBitmapCharacter():
glutBitmapCharacter(GLUT_BITMAP_8_BY_13, с)
Второй аргумент этой функции, с, представляет собой ASCII-код выводимого символа. Символ размещается в текущей позиции растра на поле экрана — это один из параметров, характеризующих текущее состояние графической системы. Изменение значения текущей позиции растра выполняется с помощью разных модификаций функции glRasterPos*. Мы еще вернемся к методам вывода текста на экран в главе 3, где речь пойдет об использовании дисплейных списков.
Криволинейные объекты
Все ранее рассмотренные примитивы полностью определялись набором вершин. Все они, за исключением точки, состояли из прямолинейных отрезков или использовали такие отрезки для определения границ некоторой области, которая заливалась определенным цветом или заполнялась образцом. В этом разделе будут рассмотрены два подхода к формированию расширенного набора примитивов.
Первый подход предполагает использование ранее определенных примитивов для аппроксимации кривых и поверхностей. Если, например, нам нужен круг, то его можно аппроксимировать правильным многоугольником с и вершинами. Точно так же сфера аппроксимируется правильным многогранником. В общем случае любую криволинейную поверхность можно аппроксимировать сеткой из выпуклых многоугольников (мозаикой— tessellation), которая формируется либо на стадии тонирования, либо в прикладной программе.
Другой подход, который мы подробно рассмотрим в главе 10, состоит в том, что создается математическое описание криволинейного объекта, а затем разрабатываются графические функции реализации этого математического описания. Математический аппарат описания таких объектов, как квадратичные поверхности и параметрические полиномиальные кривые, достаточно хорошо разработан, и их можно строить, базируясь на наборе характеристических точек. Например, сферу можно построить, зная всего две точки — центр сферы и произвольную точку на ее поверхности. Кубическую полиномиальную кривую можно построить по четырем точкам, находящимся на этой кривой.
В большинстве графических систем используются оба подхода. В OpenGL можно применять функции аппроксимации наиболее часто используемых криволинейных поверхностей из библиотеки утилит GLU, но можно разработать и собственные функции построения разного рода специфических криволинейных объектов. Мы будем использовать эти возможности OpenGL при работе с параметрическими полиномиальными кривыми и поверхностями.
Атрибуты
В современных системах компьютерной графики принято делить используемую информацию на две категории: информация, определяющая форму объекта, и информация, характеризующая вид объекта на экране. Красная сплошная линия и зеленая штриховая линия формируются на основании одного и того же типа примитива, но выглядят совершенно по- разному. Атрибут — это любое свойство, которое определяет способ отображения заданного графического примитива. Чаше всего используются такие атрибуты, как цвет, толщина линий и образец заливки многоугольников. Рис. 2.19 поясняет физический смысл некоторых атрибутов линий и многоугольников.
Рис. 2.19. Атрибуты линий и многоугольников
Атрибуты ассоциируются или связываются с примитивами на разных этапах конвейерного процесса моделирования и тонирования. Связывание может не быть постоянным. В этой главе мы будем рассматривать только режим немедленного отображения {immediate-mode), в котором примитивы не сохраняются в памяти системы, а сразу же после формирования передаются на отображение. Значения многих атрибутов входят в состав параметров, характеризующих текущее состояние всей системы. При формировании любого примитива в программе к нему применяются текущие значения атрибутов, приложимых к данному типу объектов, и соответственно формируется изображение объекта на экране. Поскольку примитив не сохраняется в памяти системы, то после очистки экрана он утрачивается. В главе 3 мы рассмотрим дисплейный список (или дисплейный файл), в котором сохраняется информация о сформированных графических объектах. Такой режим работы системы позволяет повторно использовать однажды сформированные объекты при создании нового изображения8.
К каждому типу объектов приложим свой набор атрибутов. Например, на внешний вид точки влияют только два атрибута — цвет и размер точки. На внешний вид отрезка влияют цвет,
толщина линии и тип линии (сплошная, штриховая или пунктирная). Внешний вид объектов,
имеющих внутреннюю область, определяется более широким набором атрибутов, поскольку требуется специфицировать множество параметров заливки. Можно заливать внутреннюю область одним цветом или определенным образцом, можно задать режим отображения многоугольника— с заполнением или контурный. Контур многоугольника может выводиться одним цветом, а внутренняя область заливаться другим.
В графических системах, поддерживающих работу со штриховыми наборами символов, существуют еще и атрибуты, определяющие начертание таких символов. Как проявляются некоторые из них, продемонстрировано на рис. 2.20. Атрибуты этой группы позволяют регулировать ориентацию текстовой строки, соотношение между высотой и шириной символов, наклон символов, вид начертания (полужирный, курсив, подчеркнутый) и т.д.
со U
Computer Graphics
Computer Graphics о
Computer Graphics "^
CouubniGi Qiabpic2 о
sdii|cIdjq JЭ4ndшo^
Puc. 2.20. Атрибуты текста, использующего штриховой шрифт
2.4. Цвет
Способность воспринимать цвет является одним из наиболее интересных свойств рецепторной системы человека, а возможность воспроизводить цвет — одним из главных достоинств современных систем компьютерной графики. Исчерпывающий анализ возможности человека воспринимать цвет требует значительно более глубокого изложения анатомии, физиологии и психологии, чем тот, который мы можем себе позволить в данной книге. Но, тем не менее, я постараюсь расширить представленную в главе 1 модель зрительной системы человека и получить на ее основе приемлемую для использования в компьютерной графике модель цветовосприятия.
8 В OpenGL упор сделан именно на режиме немедленного отображения и использовании конвейерной организации процесса, которая позволяет создавать динамические графические приложения, активно работающие с пользователем. В этом состоит одно из основных отличий OpenGL от таких графических систем, как PH1GS, в которых используется парадигма баз данных. В базе данных сохраняется информация о геометрических объектах, которую при желании .можно использовать повторно.
Рис. 2.21. Функция спектрального распределения светового излученияРис. 2.21. Функция спектрального распределения светового излучениязадач использования цветазадач использования цветаввРис. 2.22. Наложение первичных цветовРис. 2.22. Наложение первичных цветов
Рис. 2.21. Функция спектрального распределения светового излучения
Рис. 2.21. Функция спектрального распределения светового излучения
задач использования цвета
задач использования цвета
в
в
Рис. 2.22. Наложение первичных цветов
Рис. 2.22. Наложение первичных цветов
Как уже говорилось, видимый свет представляет собой электромагнитные колебания в диапазоне длин волн между 350 и 780 нм. Колебания с более короткой длиной волны воспринимаются как более близкие к синему цвету, а по мере увеличения длины цвет воспринимается как
все более близкий к красному. Промежуточное положение между синей и красной частями спектра занимает зеленый цвет. Практически все источники света излучают колебания в достаточно широком диапазоне частот. Исключение составляют источники когерентных колебаний — лазеры. Каждый источник света характеризуется функцией спектрального распределения С(Л), которая в графическом виде представлена на рис. 2.21. Аргументом функции является длина волны Л, а значением функции — мощность излучения соответствующей частоты.
Функция С(Л) учитывает объективные физические свойства излучения, но никак не отражает способность зрительной системы человека воспринимать цвет этого излучения. Для того чтобы это различие стало более понятным, рассмотрим одну из ключевых компьютерной графике — задачу соответствия цветов. Предполо
жим, что нам нужно воспроизвести на экране цветовой эффект, создаваемый в реальном мире излучением, имеющим функцию распределения С(л). Должны ли мы для этого воспроизвести относительную мощность С для каждого значения Л? Монитор, который обладал бы подобной возможностью во всем диапазоне видимых цветов, пока что не создан. Но, к счастью, нам и не нужен такой монитор, поскольку зрительная система человека вполне удовлетворительно описывается моделью, основанной на трех первичных цветах.
Представление цвета в компьютерной графике основано на теории трех первичных составляющих {three-color theory^. Аддитивная цветовая модель {additive color model) базируется на предположении, что любой цвет можно рассматривать как взвешенную сумму трех основных цве-
9Исключение составляют приложения для полиграфии, в которых используется четырехцветная обработка. Но это связано не столько с фундаментальными концепциями теории, сколько с практическими аспектами использования полиграфического оборудования.
тов. Достаточно близкая и понятная аналогия — использование трех прожекторов основных цветов с регулируемой интенсивностью излучения каждого (рис. 2.22). Все прожекторы направлены в одну и ту же область темного экрана. Наши глаза воспринимают отраженный световой поток, который представляет собой наложение трех первичных, но видит его как один смешанный цвет.
При использовании такой модели можно, варьируя интенсивность излучения каждого из первичных прожекторов, добиться желаемого цвета. Хотя с помощью этой модели нельзя добиться абсолютно точного воспроизведения всех цветов, но если в качестве первичных использовать красный, зеленый и синий, такое воспроизведение будет довольно близким. Формально этот процесс можно описать следующим образом. Пусть С — это тот цвет, который требуется воспроизвести, a R, G и В представляют наши три прожектора. Цветовое соответствие при этом выражается соотношением
C=TxR^TtG*T3B,
где Тх, 72 и 73 представляют интенсивность излучения соответствующих прожекторов. Синтезируемый цвет можно охарактеризовать триадой (7Ь Т2, T3Y
Естественно, что точного соответствия между непрерывной функцией С(Л) и наборами триад (Гь 72, Г3) добиться не удастся, но именно так действует зрительная система человека. В ней существуют три типа цветовых рецепторов — колбочек. Колбочки типа i, i е {1,2, 3}, имеют кривую чувствительности 5,(Л) и, когда на них попадает световой поток с функцией распределения С(Л), посылают в мозг сигнал
A, = ^S.(X)C(X)dk,
где интегрирование выполняется по всему диапазону длин волн видимого света. Таким образом мозг получает информацию о цвете в виде триад (Аь А2, А3), а не в виде отсчетов непрерывной функции С(Л).
Подводя итог сказанному, получим основополагающий принцип трехкомпонентной теории цветовосприятия:
Если два цвета характеризуются одним и тем же набором первичных составляющих, они визуально неразличимы.
Два цвета, которые воспринимаются глазом одинаково, образуют метаметрическую пару (metameric pairs'). Такие цвета имеют те же самые значения интенсивности первичных составляющих, хотя их функции распределения С(Х) могут и не совпадать. Для построения соответствующих цветов достаточно воспроизвести в системе компьютерной графики интенсивности первичных составляющих.
Существует множество нюансов теории цветовосприятия, на которых мы в данной книге останавливаться не будем. Наибольшее внимание обычно уделяется выбору набора первичных составляющих и ограничениям, проистекающим из физических принципов работы реальных приборов. Первичные цвета, воспринимаемые рецепторами глаза, не совпадают ни с теми, которые используются в цветных ЭЛТ или принтерах, ни с теми, которые используются в цветных фото- и кинопленках. Кроме того, интенсивности первичных составляющих, которые используются для получения соответствующих цветов на экране ЭЛТ, должны быть обязательно положительны, поскольку по самой своей физической природе этот прибор позволяет только добавлять какой-либо цвет, но никак не может “вычесть” его. Существует и предел интенсивности свечения как излучаемого данным физическим прибором, так и воспринимаемого рецепторами зрительной системы. Следовательно, хотя математически мы и можем соотнести интенсивности первичных компонентов одного набора с аналогичными значениями другого набора, физически воспроизвести их не всегда возможно. Диапазон цве-
Рис. 2.23. Цветовой кубРис. 2.23. Цветовой кубтов, который можно воспроизвести в данной системе при заданном наборе первичных цветов, называется цветовой гаммой (color gamut).
Рис. 2.23. Цветовой куб
Рис. 2.23. Цветовой куб
Аддитивная модель подходит для таких средств формирования цветного изображения, как ЭЛТ, прозрачные экраны и позитивные (слайдовые) фотопленки. В них в качестве первичных используются красный, зеленый и синий цвета. Цвет отдельной точки изображения можно охарактеризовать положением в цветовом кубе, который изображен на рис. 2.23 и на ил. 6 цветной вклейки. Вдоль осей координатной системы такого цветового куба откладываются интенсивности первичных компонентов. В нормализованном цветовом кубе любой цвет можно представить определенной точкой внутри такого единичного куба. Вершины куба соответствуют черному цвету (интенсивность всех первичных компонентов равна 0), красному, зеленому и синему цветам (два других первичных компонента имеют интенсивность 0), бирюзовому (сумма зеленого и синего), фиолетовому (сумма красного и синего) и желтому (сумма красного и зеленого) цветам, а вершина, которая отстоит от начала системы координат куба по главной диагонали, соответствует белому цвету. Все цвета, располагающиеся вдоль главной диагонали, имеют равные соотношения первичных компонентов и проявляются в изображении в виде оттенков серого. С помощью такого цветового куба можно сравнивать системы воспроизведения цветов, построенные на разных наборах первичных компонентов.
СинийСинийБирюзовыйБелыйБирюзовыйБелыйЗеленыйЗеленыйЖелтыйЖелтыйКрасныйЧерныйКрасныйЧерныйФиолетовыйФиолетовыйб)б)Рис. 2.24. Формирование цвета: а — наложение первичных цветов; б — вычитание первичных цветовРис. 2.24. Формирование цвета: а — наложение первичных цветов; б — вычитание первичных цветовПри работе с ЭЛТ цвет формируется в результате добавления первичных компонентов. В издательских системах используется иная модель, основанная на вычитании первичных компонентов. В этом случае носитель изображения в исходном состоянии является белым (например, чистый лист бумаги). Окрашенные пигменты как бы удаляют определенную составляющую из белого цвета, отражаемого поверхностью носителя. Если считать, что поверхность засвечивается чисто белым цветом, то некоторая точка этой поверхности будет восприниматься красной в том случае, если все компоненты, кроме красного, будут поглощены наложенными на эту точку пигментами. В таких системах первичные пигменты имеют дополняющий цвет. Это так называемая система CMY (по первым буквам английских названий дополняющих цветов — cyan, magenta, yellow (рис. 2.24)). В данной книге мы не будем в дальнейшем использовать систему дополняющих цветов и сконцентрируемся только на аддитивном методе формирования цвета.
Синий
Синий
Бирюзовый
Белый
Бирюзовый
Белый
Зеленый
Зеленый
Желтый
Желтый
Красный
Черный
Красный
Черный
Фиолетовый
Фиолетовый
б)
б)
Рис. 2.24. Формирование цвета: а — наложение первичных цветов; б — вычитание первичных цветов
Рис. 2.24. Формирование цвета: а — наложение первичных цветов; б — вычитание первичных цветов
Цветовая система RGB
В этом разделе мы рассмотрим, как выполняются манипуляции с цветом в графических системах с точки зрения программиста, т.е. какие функции для этого имеются в составе API. Существуют два довольно сильно отличающихся подхода к программным манипуляциям с цветом. Мы будем рассматривать только цветовую модель RGB, поскольку она играет решающую роль в дальнейшем обсуждении вопросов моделирования освещения и реализации тонирования объектов сцены. По сравнению с моделью цветовых индексов, которая будет рассмотрена в следующем разделе, RGB-модель более сложная в аппаратной реализации, поскольку требует большего объема памяти, но в связи с тем, что стоимость памяти непрерывно снижается, это уже можно не считать ее существенным недостатком. В современных графических API стало возможным обеспечить правильную цветопередачу, причем аппаратно удается использовать алгоритмы аппроксимации, позволяющие достаточно приблизиться к полноценной RGB-модели отображения.
Концептуально в RGB-системе наложения цветов должны существовать три раздельных буфера кадра— по одному на каждый из первичных цветов. В каждом буфере за определенным пикселем на экране закреплен фиксированный адрес в памяти (рис. 2.25). В типовой системе, имеющей разрешение 1280x1024 пикселей, каждый из них описывается тремя байтами (24 битами): по одному байту соответственно на красный, зеленый и синий цвета. В результате буфер кадра должен иметь объем порядка 3 Мбайт, причем весь буфер должен считываться с частотой регенерации. До последнего времени необходимость в таком буфере являлась одним из наиболее существенных препятствий во внедрении графических систем с правильной цветопередачей.
Рис. 2.25. Цветовая система RGB
Программисту желательно было бы иметь возможность задавать цвет в виде определенного числа и не думать о том, каким образом соответствующая информация будет храниться в буфере кадра. Если считать, что на каждый цвет отводится по 1 байту, то в распоряжении программиста есть 224 возможных цветов (иногда говорят, что имеется 16 М цветов, понимая под М число 10242). В других графических системах используется 12 и более битов на каждый цвет, но существуют и системы с 4 битами на цвет. Поскольку все графические API желательно делать аппаратно-независимыми, то программисту' нужно позволить специфицировать цвет независимо от объема памяти, отводимой в буфере кадра на каждый пиксель. Все остальные операции, связанные с приведением кода цвета к формату конкретной системы отображения, должны взять на себя драйверы и аппаратные средства. Наиболее естественным было бы воспользоваться моделью цветового куба и специфицировать цвет в виде трех действительных чисел в диапазоне от 0 до 1.0. В OpenGL эта идея реализуется следующим образом. Для того чтобы задать красный цвет, вызывается функция glColor3f ():
glColor3f(1.0, 0.0, 0.0);
В результате выполнения этой функции устанавливается значение текущего атрибута цвета, соответствующее красному цвету. Поскольку атрибут цвета является одним из параметров текущего состояния системы, все последующие графические объекты будут отображаться красным цветом до тех пор, пока значение атрибута не изменится. Суффикс 3f в имени функции играет ту же роль, что и суффиксы в именах функций семейства gIVertex*, и означает, что используется трехцветная (RGB) модель формирования цвета, а значения компонентов— аргументов функции— это вещественные числа в формате float, как он определен в языке С. Если для задания компонентов будут использованы числа в формате integer или byte, то максимальное значение выбранного типа будет соответствовать максимальному значению интенсивности первичного компонента, а минимальное — полностью отключенному первичному компоненту.
В дальнейшем мы рассмотрим четырехкомпонентную (RGBA) систему представления цвета. Четвертый компонент в такой системе (A-компонент) называется альфа-каналом {alpha channel), но сохраняется в буфере кадра, как и значения RGB-компонентов. В главе 9 будут рассмотрены различные варианты использования альфа-канала, которые позволяют, например, создать эффект тумана или комбинированного (смешанного) изображения. Значение альфа-составляющей в OpenGL трактуется как значение параметров прозрачности {transparency) или поглощения {opacity). Прозрачность и поглощение — это два взаимно дополняющих параметра, характеризующих оптические свойства среды. Поглощающий объект не пропускает свет через себя, а прозрачный объект пропускает весь падающий на него световой поток. При определенных значениях этого параметра объект может быть абсолютно черным телом, т.е. полностью поглощать световой поток, либо абсолютно прозрачным.
Одна из первых операций, которые выполнятся в графической программе, — это очистка экрана, точнее той его части, в которой будет формироваться графическое изображение, — видового окна. При использовании четырехкомпонентной (RGBA) цветовой системы можно создать эффект взаимного наложения текущего видового окна с тем, которое “расположено” под ним. Это выполняется с помощью параметра поглощения окна при выполнении операции его очистки. Вызов функции
glClearColor(1.0, 1.0, 1.0, 1.0);
настраивает белый цвет заполнения, поскольку первые три аргумента — цветовые компоненты RGB — равны 1.0, и режим непрозрачности, поскольку альфа-компонент также равен 1.0. Теперь после вызова функции glClear() окно окрасится в белый цвет и станет непрозрачным.
Индексируемый цвет
Во множестве графических систем используется буфер кадра с ограниченной глубиной. Например, в системе может использоваться буфер, рассчитанный на разрешение 1280x1024 пикселей, но каждый пиксель имеет только 8-битовый код засветки. Можно было бы разделить эти 8 бит на более мелкие группы и назначить по одной такой группе на каждый из первичных цветов — красный, зеленый и синий. Но такая технология, во-первых, слишком ограничивает возможности цветопередачи в системе, а во-вторых, разбиение на очень мелкие группы битов очень негативно сказывается на скорости работы буфера кадра.
Вместо этого можно последовать аналогии с художником, который пишет картину. Он может воспроизвести на картине сколько угодно цветов, смешивая на палитре краски всего из нескольких тюбиков. Будем говорить, что художник имеет потенциально неограниченную палитру цветов, но в каждый момент времени он работает только с некоторыми из них, которые уже смешаны (подготовлены) на палитре.
Возвращаясь к нашей компьютерной модели, покажем, что, выбирая для определенного приложения ограниченный набор цветов из большого множества (нашу палитру), можно в подавляющем большинстве случаев создавать изображение достаточно высокого качества.
Код пикселя при этом интерпретируется не как абсолютный код цвета, а как индекс в списке
или таблице. Предположим, что каждый пиксель характеризуется кодом из к бит, т.е. имеет зна
чение индекса в диапазоне от 0 до 2*'1. Будем также считать, что аппаратные средства отображения позволяют задавать каждый из основных цветов с точностью т бит, т.е. можно получить 2” градаций интенсивности любого из основных цветов. Следовательно, потенциально наш дисплей может воспроизвести любой из 23/” цветов, но в буфере кадра можно задать только 2* из них. Эти значения можно “пропустить” через определенную пользователем таблицу соответствия цветов (color-lookup table), которая имеет размер 2^x3 m (рис. 2.26). В прикладной программе заполняется 2* строк табли
ИндексКрасныйЗеленыйСиний00 .0012т- 10002т- 1021- 1ИндексКрасныйЗеленыйСиний00 .0012т- 10002т- 1021- 1ние. 2.26. Таблица соответствия цветовние. 2.26. Таблица соответствия цветовцы желаемыми кодами цветов, которые представляют собой набор из трех чисел, соответствующих интенсивности первичных компонентов (каждое имеет размер т бит). Создав такую таблицу, пользователь может специфицировать любой из перечисленных в ней цветов его индексом в таблице (рис. 2.27). При к=т=й (это стандартный вариант системы отображения) пользователь имеет в своем распоряжении набор, включающий любые 256 цветов из общего числа 16 М. Таблица из 256 элементов и образует палитру пользователя данного приложения.
Индекс
Красный
Зеленый
Синий
0
0 .
0
0
1
2т- 1
0
0
0
2т- 1
0
21- 1
Индекс
Красный
Зеленый
Синий
0
0 .
0
0
1
2т- 1
0
0
0
2т- 1
0
21- 1
ние. 2.26. Таблица соответствия цветов
ние. 2.26. Таблица соответствия цветов
В режиме индексированного цвета текущий цвет выбирается специальной функцией, аргументом которой является значение индекса. В OpenGL эта операция выполняется функцией gllndexi(). Заполнение и модификация строк в таблице соответствия цветов требует взаимодействия с подсистемой управления окнами, которая в каждой операционной системе может быть реализована по-своему. (Этот вопрос обсуждается в главе 3.) Основная сложность состоит в том, что в некоторых операционных системах и комплектах аппаратуры системы отображения поддерживается ограниченное количество цветов и подсистема управления окнами располагает единственной таблицей соответствия цветов для всех окон. В других операционных системах для каждого окна можно сформировать отдельную таблицу. Библиотека GLUT позволяет прикладному программисту организовать отдельную таблицу для каждого окна и заполнять ее, вызывая функцию glutSetColor():
void glutSetColor(int, GLfloat red, GLfloat green, GLfloat blue);
Управление цветом с помощью индексации в таблице соответствия представляет в настоящее время интерес разве что с точки зрения исторической, поскольку снижение стоимости памяти свело на нет основное преимущество этого метода. В то же время ограниченное количество цветов в одном приложении не позволяет создавать изображения высокого качества, особенно динамические. Поэтому в дальнейшем будем полагать, если не оговорено противное, что используется полноцветная RGB-система.
Настройка атрибута цвета
В программе формирования узора Серпинского, которую мы будем в разных вариантах рассматривать далее в этой главе, используется полноцветная RGB-система управления цветом. В этой программе необходимо установить значения трех атрибутов. Сначала настраивается цвет чистого экрана {clear color), что выполняется вызовом функции glClearColor():
glClearColor(1.0, 1.0, 1.0, 1.0);
Этот атрибут играет в OpenGL ту же роль, что и знакомый большинству программистов цвет фона {background color). Настройка цвета отображения точек осуществляется функцией glColor3f (). Приведенный ниже оператор устанавливает текущим красный цвет:
glColor3f(1.0, 0.0, 0.0);
Можно настроить и размер примитива “точка” при отображении его на экране. Для этого в OpenGL имеется функция glPointSize(). Приведенный ниже оператор устанавливает размер примитива “точка” на экране равным двум пикселям:
glPointSize(2.0);
Обратите внимание на то, что атрибуты, определяющие линейные размеры графических элементов на экране, например размер точки или толщина линии, задаются в пикселях. Следовательно, при выполнении одной и той же программы на компьютерах, оборудованных мониторами с разными размерами экрана и разным разрешением, получится несколько отличающееся в деталях изображение. В некоторых графических API все размеры задаются в единицах, независимых от используемой аппаратуры, но и в этом случае не всегда удается получить изображение одинакового качества на разных платформах. Разработчики OpenGL выбрали компромиссный подход, более практический с их точки зрения.
Визуализация
Итак, в предыдущих разделах вы познакомились с теми графическими примитивами, из которых может состоять изображение, и с тем, как с помощью атрибутов задать их внешний вид на экране. Но до сих пор мы не рассматривали методы, позволяющие точно задать, какие из этих объектов должны появиться на экране. Точно так же, как фотограф по-разному компонует кадр, изменяя объектив и точку съемки, программист должен настроить параметры визуализации в своей прикладной программе.
В основе описанной в главе 1 модели синтезированной камеры лежит концепция независимости параметров отображаемых объектов и параметров камеры. После того как в программе будут построены модели сцены и камеры, можно приступать к формированию изображения этих объектов с помощью этой камеры. В фотографии камера формирует изображение, экспонируя фотопленку. Компьютерная система формирует изображение, выполняя последовательность операций, заложенную в конвейер визуализации {viewing pipeline). Прикладная программа должна заботиться только о спецификации параметров объектов и камеры, так же, как и фотограф-любитель не думает о том, как работает затвор фотоаппарата или как изменяется химический состав фотоэмульсии под воздействием света.
Существуют установленные по умолчанию параметры формирования изображения в компьютере, что аналогично установленной на штативе фотокамере со штатным объективом. Но такая фиксированная камера вынуждает фотографа компоновать кадр, переставляя объекты съемки. Если нужно снять слона, то его придется втащить в павильон и заставить стоять на приличном удалении от камеры. А какого-нибудь паучка вообще не удастся снять, поскольку это не позволит сделать штатный объектив. Поэтому профессиональные фотографы пользу-
ются портативными камерами с набором сменных объективов или специальным объективом с переменным фокусным расстоянием — трансфокатором. Тот же подход реализуется и в системах компьютерной графики.
Визуализация двухмерных объектов
Визуализация двухмерных объектов сводится к выбору прямоугольной области двухмерного пространства и переносу ее содержимого на экран, как показано на рис. 2.28. Отображаемая область пространства называется прямоугольником видимости (viewing rectangle), или прямоугольником отсечения (clipping rectangle). Объекты, попавшие внутрь этого прямоугольника, переносятся на экран, а не попавшие в него — отсекаются и не отображаются. Объекты, которые пересекаются контуром прямоугольника отсечения, будут частично видимыми. Какими должны быть размеры окна и где его расположить на экране — это отдельный разговор, который мы продолжим в разделе 2.6.
б)
Рис. 2.28. Визуализация двухмерных объектов: а — объекты до отсечения; б — изображение после выполнения отсечения
Не забывайте, что двухмерная графика является частным случаем трехмерной, а следовательно, прямоугольник видимости лежит на плоскости z = 0 в трехмерной зоне видимости (viewing volume), как показано на рис. 2.29. Если зона видимости не задана явно, то в OpenGL используется установленная по умолчанию зона в виде куба видимости 2x2x2 с началом координат в центре куба. В терминах двухмерной плоскости левый нижний угол прямоугольника видимости имеет координаты (-1.0, -1.0), а правый верхний — координаты (1.0, 1.0).
Ортогональная проекция
Описанный двухмерный вид является частным случаем ортогональной проекции (orthographic projection), которую мы подробно рассмотрим в главе 5. При простом ортогональном проецировании точка (х,у, z) на объекте проецируется в точку (х,у, 0) на плоскости проекции (рис. 2.30). Поскольку двухмерное пространство есть плоскость z = 0, то никакого эффекта проецирования мы не заметим. Однако, что не менее важно, при формировании ортогональной проекции двухмерных объектов можно использовать тот же механизм, что и при проецировании трехмерных объектов. В OpenGL ортогональная проекция, характеризуемая параллелепипедом видимости, задается функцией glOrtho(), объявленной следующим образом:
void glOrtho(GLdouble left, GLdouble right, GLdouble bottom,
GLdouble top, GLdouble near, GLdouble far);
Puc. 2.30. Ортогональная проекция
При настройке по умолчанию камера направлена в сторону, противоположную направлению оси z на рис. 2.31. При ортогональном проецировании видны только объекты, попавшие внутрь параллелепипеда видимости. В отличие от реальной камеры, видимыми полагаются и объекты, расположенные позади нее. Таким образом, до тех пор, пока плоскость z = 0 находится между ближней и дальней гранями параллелепипеда видимости, все объекты, попавшие в прямоугольник видимости, будут включены в изображение. Если прикладной программист испытывает определенный дискомфорт от необходимости использовать трехмерную зону видимости при работе с двухмерными объектами, то можно воспользоваться специальной функцией gluOrtho2D(), которая специфицирует не параллелепипед видимости, а именно прямоугольник. Определение функции имеет вид
void gluOrtho2D(GLdouble left, GLdouble right,
GLdouble bottom, GLdouble top)
Результат выполнения этой функции будет таким же, как и после вызова функции glOrtho(), где для аргументов near и far установлены значения -1.0 и 1.0 соответственно. В главах 4 и 5 будет рассмотрено, как организовать в программе перемещение камеры и формирование более сложных видов.
Рис. 2.31. Размещение камеры при настройке по умолчанию параметров параллелепипеда видимости при ортогональном проецировании. Показаны соответствие аргументов вызова функции glOrtho() и положения вершин параллелепипеда
Матричный режим проецирования
В системах компьютерной графики, использующих конвейерный принцип обработки, желательно, чтобы все операции формирования изображения выполнялись единообразно — в форме перемножения матриц. Ряд матриц преобразования являются переменными состояния системы OpenGL и действуют до тех пор, пока их принудительно не изменят. Из них наибольшее значение для нас сейчас имеют матрицы вида {model-view) и проецирования (или проективного преобразования). В начальном состоянии обе эти матрицы имеют вид единичных. В главе 4 читатели смогут познакомиться с имеющимися в составе OpenGL средствами выполнения операций с матрицами. Обычно этим матрицам присваиваются значения, которые отличают их от единичных. Специальные функции позволяют манипулировать любыми типами матриц. Выбор конкретного типа матрицы осуществляется настройкой переменной режима работы с матрицами {matrix mode). Эта переменная, как и ряд других, характеризует текущее состояние графической системы. По умолчанию переменной присваивается значение, соответствующее работе с матрицей вида (матрицы model-view). Поэтому для настройки типа проецирования нужно первым делом изменить состояние переменной режима работы с матрицами. Ниже приведен типовой фрагмент программы, в котором выполняется настройка двухмерного прямоугольника видимости.
glMatrixMode(GL_PROJECTION);
glLoadldentityQ;
gluOrtho2D(0.0, 500.0, 0.0, 500.0);
glMatrixMode(GL_MODELVIEW);
Значения аргументов функции gluOrtho2D() задают прямоугольник видимости размером 500x500, левый нижний угол которого находится в начале двухмерной системы координат. Последний оператор фрагмента восстанавливается режим работы с матрицей вида. В сложной программе считается хорошим тоном восстанавливать значения параметров текущего со-
стояния после выполнения каких-либо манипуляций. Если следовать этому правилу, то при разработке любой подпрограммы всегда можно будет считать, что текущим является режим работы с матрицей вида.
Функции управления
Мы рассмотрели практически все вопросы, необходимые для разработки программы построения на экране узора Серпинского. Осталось невыясненным единственное — как графическая система взаимодействует с подсистемой окон и операционной системой. Не углубляясь в детали определенных платформ, таких как система X Window под UNIX или Microsoft Windows, сразу же отметим, что интерфейс между графической системой и конкретной операционной системой может быть достаточно сложным. В деталях этот интерфейс очень тесно связан со спецификой определенной операционной системы, и их обсуждение далеко уведет нас от главной задачи — исследования методов компьютерной графики.
Поэтому мы ограничимся минимальным набором операций, которые необходимы для нормальной работы прикладной графической системы. Большинство из них располагает комплектом версий библиотек, предназначенных для работы с определенными операционными системами. В OpenGL эта роль принадлежит библиотеке GLUT (GL Utility Toolkit). Именно ее функции учитывают все особенности определенной операционной системы. Таким образом, основной набор функций API можно считать независящим от используемой платформы. Практически вся настройка сводится к добавлению нового пути в набор путей поиска библиотек. В данной главе и в главе 3 при описании средств взаимодействия современных графических систем с операционной средой мы будем опираться на функции библиотеки GLUT.
Взаимодействие с подсистемой окон
Термин окно (window) используется в информатике и программировании очень часто и имеет множество значений. В компьютерной графике термин окно, или окно экрана (screen window), означает прямоугольную область, на которой формируется изображение. Чаще всего это экран ЭЛТ, хотя в данном случае это и непринципиально. Окно характеризуется своими размерами (высотой и шириной), и поскольку в пределах окна выводится содержимое буфера кадра, положение на поле окна измеряется в оконных (window coordinates) или экранных координатах (screen coordinates), причем в качестве единиц измерения используются пиксели.
Современная операционная среда позволяет выводить на один и тот же экран множество окон. Каждое из них может принадлежать разным приложениям или даже в пределах одного приложения выполнять совершенно разные функции. Мы в дальнейшем будем использовать термин подсистема окон, подразумевая под ним многооконную среду, поддерживаемую в таких операционных системах, как X Window или Microsoft Windows. Положение внутри этого окна отсчитывается от его базовой точки — обычно один из углов прямоугольной рамки окна. В большинстве случаев им является левый нижний угол, который имеет оконные координаты (0, 0), но учтите, что в некоторых операционных системах это соглашение не выполняется. Поскольку в растровом дисплее развертка выполняется по той же траектории, что и в коммерческом телевидении (слева направо и сверху вниз), то с этой точки зрения желательно было бы выбирать в качестве начала координат левый верхний угол. В OpenGL команды подразумевают, что начало оконной системы координат находится в левом нижнем углу, хотя информация, получаемая от подсистемы окон, в частности о положении указателя мыши, часто выражается в иной системе координат, которая имеет начало отсчета в левом верхнем
углу. Таким образом, приходится выполнять дополнительное преобразование данных, поступающих от операционной системы.
Обращаю ваше внимание и еще на одно обстоятельство. Хотя весь экран дисплея имеет определенное стандартное разрешение, скажем 1280x1024 пикселей, окно приложения может иметь любые размеры (но, конечно, не больше, чем размер полного экрана). Таким образом, буфер кадра приложения должен иметь разрешение соответственно размеру окна. Если используется окно размером 300x400 пикселей, то нужно считать, что и буфер кадра имеет разрешение 300x400, хотя при этом используется только часть памяти, отведенной для буфера.
Перед тем как окно приложения будет открыто на экране, должен состояться сеанс связи между подсистемой окон и OpenGL. В составе библиотеки GLUT эта операция возлагается на функцию glutlnit():
void glutlnit(int *argcp, char **argv);
Функция имеет два аргумента, через которые можно передать аргументы командной строки, как в стандартной С-функции main(). После выполнения glutlnit() можно открыть окно OpenGL-программы, вызвав функцию glutCreateWindow():
int glutCreateWindow(char *title);
Аргумент title этой функции — надпись, которая будет выведена в строке заголовка окна.
При создании окна устанавливаются его параметры, назначенные графической системой по умолчанию, — размер, положение на экране и режим RGB управления цветом. Для настройки иных значений параметров нужно перед вызовом glutCreateWindow() обратиться к соответствующим функциям их установки. Например, в следующем фрагменте программы устанавливается размер окна 480x640 пикселей, а само окно размещается в левом верхнем углу экрана".
glut!nitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE);
glut!nitWindowSize(480, 640);
glut!nitWindowPosition(0,0);
Обратите внимание на то, что константы задания отдельных режимов объединяются в аргументе функции glut!nitDisplayMode() операцией побитового ИЛИ. Константа GLUT_RGB задает RGB-режим. Если предполагается использовать режим индексированных цветов, то вместо константы GLUT_RGB следует передать GLUT_INDEX. Вторая константа GLUT_DEPTH задает использование буфера глубины при выполнении удаления невидимых поверхностей. Третья константа GLUT_DOUBLE задает двойную буферизацию (одиночная буферизация задается константой GLUT_SINGLE).
Режим, заданный по умолчанию, который вполне достаточен для той задачи, которую мы будем решать в этой главе, — цвет RGB, отсутствие удаления невидимых поверхностей и одиночная буферизация. Эти настройки можно и не устанавливать в явном виде, но включение их в программу делает ее текст более понятным для тех, кто только осваивает методику программирования на OpenGL.
Соотношение сторон и видовые окно
Сам по себе параметр соотношение сторон (aspect ratio) применим к любому прямоугольнику и означает отношение его ширины к высоте. Существующая в графической системе независимость между объектами, визуализацией и настройкой окна приложения
м В таких системах, как X Window, запрашивается формирование окна с определенными свойствами. Но переданные значения свойств могут быть переопределены операционной системой, если они не соответствуют функциональным возможностям установленной версии.
может привести к нежелательным побочным эффектам, в частности искажению пропорций между продольными и поперечными размерами объектов на экране. Соотношение сторон прямоугольника видимости задается аргументами функции glOrtho() и может отличаться от соотношения сторон окна, которое задается аргументами функции glut!nitWindowSize(). Именно такой случай продемонстрирован на рис. 2.32, и вы видите, к каким искажениям пропорций в изображении объектов на экране это привело. Следует оговориться, что “тайной пружиной” такого искажения явилось не только несоответствие между соотношениями сторон, но и назначенный по умолчанию режим отображения, при котором все содержимое прямоугольника видимости должно быть перенесено в заданное окно на экране. Чтобы избежать таких искажений, следует отменить этот режим — использовать концепцию видового окна. Видовое окно (viewport) — это прямоугольная область в пределах окна на экране (окно в окне). По умолчанию видовое окно занимает всю область экрана, запрошенную у операционной системы, но можно установить и другие его размеры, вызывав функцию glViewport():
void glViewport(GLint х, GLint у, GLsizei w, GLsizei h);
Первые два аргумента этой функции — х и у — положение левого нижнего угла прямоугольника видового окна относительно левого нижнего угла выделенной области экрана. Следующие аргументы — w и h — задают высоту и ширину видового окна. Все параметры задаются как целые числа и измеряются в пикселях. Соответствие между отсекающей рамкой (прямоугольником видимости) и видовым окном на экране иллюстрируется на рис. 2.33. При заданных размерах окна приложения таким способом можно в прикладной программе устранить искажение пропорций изображения.
а)б)
Рис. 2.32. Искажение пропорций при несоответствии соотношения сторон прямоугольника видимости и окна приложения на экране: а — прямоугольник видимости;
б — окно на экране
Параметры видового окна также характеризуют текущее состояние графической системы. Если изменить их между последовательными циклами тонирования изображения или циклами повторения вывода изображений одних и тех же объектов, то можно получить эффект появления нескольких изображений на поле окна, причем управлять можно не только положением видового окна, но и соотношением его сторон (результаты очень впечатляют). В главе 3 мы еще вернемся к тому, как можно изменять размеры и форму окна приложения на экране.
Рис. 2.33. Соответствие между отсекающей рамкой и видовым окном на экране
Функции main(), display () и myinit()
В принципе, можно было бы скомбинировать приведенный выше фрагмент инициализации окна и текст программы, приведенный в разделе 2.1, и получить завершенную OpenGL- программу формирования узора Серпинского. Но, к сожалению, не так просто все делается в современных операционных системах. Нам предстоит решить еще две проблемы: первая из них характерна для всех графических систем, а вторая касается еше одного аспекта взаимодействия с операционной системой.
В режиме немедленного графического вывода примитив выводится на экран, как только он формируется графической системой. При этом графическая система использует текущие значения атрибутов, характеризующие состояние системы. При выполнении нашей простой программы, в которой отсутствует взаимодействие с пользователем, изображение выводится на экран, и на этом процесс завершается. После завершения работы программы операционная система сразу же закрывает окно приложения, и пользователь вряд ли успеет заметить, что же там нарисовала программа. Самое простое решение этой проблемы — вставить в программу выдержку времени с помощью стандартной функции sleep(). Но даже в тривиальном приложении следует поискать более интересное решение. В главе 3 будет рассмотрен механизм обработки событий, который предоставляет прикладному программисту значительно более широкие возможности контролировать процесс выполнения программы. А сейчас мы воспользуемся функцией glutMainLoop() из библиотеки GLUT. Определена эта функция следующим образом:
void glutMainLoop(void)
Обращение к этой функции приведет к тому, что программа “войдет” в цикл обработки событий. Пока в очереди событий не будет заявки на обработку, программа будет находиться в состоянии ожидания и пользователь сможет рассматривать созданное изображение. Так будет продолжаться до тех пор, пока пользователь не нажмет какую-либо клавишу.
Собственно вывод графической информации выполняется с помощью функции отображения с обратным вызовом {display callback). Эта функция задается еще одной функцией из библиотеки GLUT, определение которой имеет вид
void glutDisplayFunc(void (*func)(void));
Здесь аргумент func задает имя функции, которая будет вызываться операционной системой, как только она придет к выводу, что окно приложения OpenGL должно быть обновлено. Одно из таких событий — открытие окна. Поскольку разрабатываемая нами программа не
реагирует на действия пользователя, то все процедуры формирования изображения должны быть сосредоточены в теле функции, имя которой передается в качестве аргумента glutDisplayFunc(). Эта функция будет выполнена однократно сразу же после открытия окна и выведет изображение узора.
Но функция формирования изображения может вызываться не только при открытии окна, но и при его перемещении или закрытии окна другого приложения, частично или полностью закрывающего окно OpenGL-программы. с
Для большинства неинтерактивных графических приложений вполне подходит та программа main(), которая приведена в листинге 2.1.
Листинг 2.1. Функция main()
finclude <GL/glut.h>
void main(int argc, char **argv) {
glutlnit(&argc,argv);
glutlnitDisplayMode (GLUT_SINGLE | GLUT_RGB);
glutInitWindowSize(500,500);
glut!nitWindowPosition(0,0);
glutCreateWindow("Simple OpenGL example");
glutDisplayFunc(display);
myinit();
glutMainLoop();
Для начальной установки переменных текущего состояния, отвечающих за настройку параметров визуализации и атрибутов, используется функция myinit( ). Эти параметры предпочтительнее устанавливать один раз, независимо от функции формирования изображения. Стандартный заголовочный файл библиотеки GLUT включается в текст программы перед определениями любых функций. В большинстве систем программирования на языке С для этого используется директива компилятора
^include <GL/glut.h>
При этом одновременно в текст программы включаются и заголовочные файлы стандартной библиотеки gl.h и библиотеки утилит OpenGL glu.h. В этих файлах находятся и макроопределения таких констант, как GL_LINES и GL_RGB.
Структура программы
Все программы, которые мы будем рассматривать в данной книге, имеют такую же структуру, что и программа построения узора Серпинского. Во всех этих программах будет использоваться библиотека GLUT. Функция main() включает вызовы функций из библиотеки GLUT, которые устанавливают параметры окна (или окон) приложения и обеспечивают поддержку со стороны операционной системы процесса отображения. В функции main() также объявляется имя функции отображения с обратным вызовом, которая, собственно, и формирует изображение. Помимо нее, в программе могут существовать и другие функции с обратным вызовом, которые необходимы для организации взаимодействия с пользователем. В
функции myinit() настраиваются значения атрибутов и опций прикладной программы, для чего используются функции из библиотек GL и GLU. Хотя эти настройки можно выполнять и в функции main(), текст программы будет понятнее и его будет легче сопровождать, если обращения к функциям из библиотеки GLUT вынести в отдельный блок (в данном случае — функцию main()). Как правило, в программах OpenGL практически вся процедура формирования изображения выполняется в функции отображения с обратным вызовом, которую чаще всего именуют display ().
Программа Gasket
В дополнение к функции main(), представленной в листинге 2.1, в программу Gasket формирования узора Серпинского нужно включить функции myinit() (листинг 2.2) и display () (листинг 2.3). Эта программа будет выводить точки красного цвета на белом фоне. При формировании изображения используются двухмерная система координат и прямоугольник (квадрат) видимости размером 500x500, причем начало координат располагается в левом нижнем углу этого квадрата.
Листинг 2.2. Функция инициализации myinit()
void myinit(void)
{
/* Атрибуты */
glClearColor(1.0, 1.0, 1.0, 0.0); /* белый фон */
glColor3f(1.0, 0.0, 0.0); /* вывод красным цветом */
/* Настройка параметров визуализации */
glMatrixMode(GL_PROJECTION);
gluLoad!dentity();
gluOrtho2D(0.0, 500.0, 0.0, 500.0);
glMatrixMode(GL_MODELVIEW);
Листинг 2.3. Функция формирования изображения display ()
void display( void )
{
/* Определение типа данных для точек */
typedef GLfloat point2[2];
point2 vertices[3]={{0.0,0.0},{250.0,500.0},{500.0,0.0}};
/* Треугольник */
int i, j, k;
int rand(); /* Стандартный генератор случайных чисел */
point2 р ={75.0,50.0}; /* Произвольная точка
внутри треугольника */
glClear(GL_COLOR_BUFFER_BIT); /* Очистка окна */
/* Вычисление и вывод на экран 5000 новых точек */ for( k=0; k<5000; k++)
{
j=rand()%3; /* Выбрать вершину случайным образом */
/* Вычислить координаты точки, лежащей посередине между вершиной и предыдущей точкой */ р[0] = (р[0]+vertices[j][0])/2.0;
Р[1] = (р[1]+vertices[j][1])/2.0;
/* Вывести точку на экран */ glBegin(GL_POINTS);
glVertex2fv(p);
glEnd();
}
glFlush();
В теле функции формирования изображения определены произвольный треугольник (массив из трех вершин) и произвольная точка внутри этого треугольника. Далее следует цикл, в котором формируется фиксированное количество (в данном случае 5000) точек. В конце процедуры вызывается OpenGL-функция glFlush(); тем самым система принуждается выводить сформированные точки на экран как можно скорее.
Полный листинг этой программы читатель найдет в приложении А.
Многоугольники и рекурсия
Результат работы описанной выше программы (см. рис. 2.4) демонстрирует весьма интересную графическую структуру. Чем больше точек будет формировать эта программа, тем менее случайный характер будет носить изображение. Анализируя эту структуру, приходим к выводу, что независимо от количества сформированных точек, они никогда не появляются в середине некоторых треугольных областей. Если провести отрезки между средними точками прилежащих сторон исходного треугольника, то он будет разбит на четыре треугольника, причем в средний точки узора никогда не попадут (рис. 2.34).
В других трех треугольниках (исключая центральный) можно повторить описанную процедуру разбиения. Возврата \щаясь к узору Серпинского (см. рис. 2.4), вновь отметим, что
/\и в средних треугольниках второго уровня разбиения точки
узора не появляются.
/ \/ \Эти рассуждения лежат в основе второго варианта алго-
/\/\ ритма построения узора Серпинского — варианта рекурсив/\/\ ного разбиения многоугольников. Этот алгоритм не требует
генератора случайных чисел. Одно из достоинств второго ваРис. 2.34, Один цикл разбиения рИанта алгоритма состоит в том, что он позволяет использовать заливку многоугольников на экране. Стратегия построения узора довольно очевидна. Исходный треугольник делится на четыре, соединяя попарно середины прилежащих сторон, а затем средний треугольник изымается из дальнейших операций. Цикл повторяется с каждым из оставшихся трех треугольников до тех пор, пока размеры сформированных треугольников не станут достаточно малы — сравнимы с размерами пикселя.
Этот процесс реализуется с помощью рекурсивно вызываемой функции, т.е. функции, вызывающей саму себя. Начинаем с построения исходного треугольника по трем заданным вершинам: void triangle( point2 a, point2 b, point2 c) {
glBegin(GL_TRIANGLES);
glVertex2fv(a);
glVertex2fv(b);
glVertex2fv(c);
glEnd();
}
Пусть вершины треугольника будут представлены элементами массива
point2 v[3];
тогда средние точки треугольника также образуют массив т[3], элементы которого вычисляются в приведенном ниже фрагменте программы:
for(j=0; j<2; j++) m[0][j]=(v[0][j]+v[l][j])/2.0;
for(j=0; j<2; j++) m[l][j]=(v[0][j]+v[2][j])/2.0;
for(j=0; j<2; j++) m[2][j]=(v[1][j]+v[2][j])/2.0;
Имея в своем распоряжении эти шесть точек, можно использовать функцию triangle () для формирования трех треугольников: вершины первого— (v[0], m[0], m[ 1 ]), второго — (v[2], m[l], m[2]), третьего—(v[l], m[2], m[0]). Нам не нужно выводить изображения этих треугольников на экран — они потребуются для выполнения дальнейшего деления. Таким образом, процесс становится рекурсивным. Определим рекурсивную функцию di- vide_triangle(point2 a, point2 b, point2 c, int k), которая будет выводить изображение треугольника на экран только тогда, когда аргумент к будет равен нулю. В противном случае функция будет выполнять разбиение треугольника, заданного аргументами а, b и с, и уменьшать значение к. Ниже приведен текст этой функции: void divide_triangle(point2 a, point2 b, point2 c, int k) {
point2 ab. ac, be;
int j;
if(k>0)
{
/* вычисление координат средних точек сторон */
for(j=0; j<2; j++) ab[j]=(a[j]+b[j])/2; for(j=0; j<2; j++) ac [ j ] = (a [ j ]+c [ j ])/2; for(j=0; j<2; j++) bc[j]=(b[j]+c[j])/2;
/* Разбиение всех треугольников, кроме среднего */
divide_triangle(a, ab, ас, k-1);
divide_triangle(c, ac, be, k-1); divide_triangle(b, be, ab, k-1);
}
else(triangle(a,b,c)); /* Вывод треугольника на экран и завершение работы */
}
При такой организации программы функция display() становится тривиальной. В ней используется объявленное глобальным в функции main() значение переменной п, которое задает количество циклов рекурсии, и вызывается функция divide_triangle().
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT);
divide_triangle(v[O], v[l], v[2], n); glFlush();
}
Оставшаяся часть программы та же, что и в предыдущем варианте, за исключением того, что в ней считывается значение переменной п. Изображение, которое сформируется после пяти циклов выполнения рекурсивной процедуры, представлено на рис. 2.35.
Полный текст программы приведен в приложении А.
Рис. 2.35. Изображение, которое сформируется после пяти циклов рекурсивного разбиения треугольников
Трехмерный узор Серпинского
Я неоднократно подчеркивал, что двухмерная графика— это только частный случай трехмерной, но мы до сих пор не рассматривали програм- / мы, в которых использовалось бы представление трехмерных объектов. В / этом разделе будет показано, как преобразовать рассмотренную выше про- /' ■ ы Ч1ИУ грамму построения узора Серпинского в трехмерную. В этом разделе мы L:—___у
воспользуемся обоими методами формирования узора— и точечным, и ме- рис 2.36. Тетраэдр тодом рекурсивного разбиения. В обоих случаях все начинается с пространственного аналога треугольника — тетраэдра (рис. 2.36).
Использование трехмерных точек
Поскольку тетраэдр является выпуклым геометрическим телом, то средняя точка отрезка, проведенного между некоторой вершиной и любой точкой внутри тетраэдра, также лежит внутри тетраэдра. Следовательно, можно использовать процедуру, подобную описанной ранее, но на сей раз вместо трех вершин треугольника включить в рассмотрение четыре вершины тетраэдра. Обратите внимание на то, что, до тех пор пока любые три вершины не будут коллинеарны (т.е. лежать на одной прямой), можно случайным образом выбирать любую из четырех вершин тетраэдра и это не повлияет на характер сформированного в результате изображения.
Первым делом нужно внести определенную коррекцию в функцию display (). Определим в ней тип данных для представления трехмерной точки:
typedef GLfloat point3[3];
Затем определим вершины исходного тетраэдра:
/* Вершины произвольного тетраэдра */
point3 vertices[4]={{0.0, 0.0, 0.0},{250.0, 500.0, 100.0}, {500.0, 250.0, 250.0},{250.0, 100.0, 250.0}};
/* Произвольная исходная точка */
point3 р={250.0,100.0,250.0};
Для формирования точек в программе будем использовать функцию glPoint3fv(). Одна из проблем, которая специфична именно для трехмерного случая, состоит в том, что формируемые точки не лежат в одной плоскости, а потому на двухмерном изображении трудно будет рассматривать трехмерную структуру. Чтобы “обойти” ее, не используя геометрические построения, о которых речь еще впереди, воспользуемся цветовым кодированием — цвет каждой формируемой точки будет нести информацию о ее положении в глубину. Это несколько облегчит восприятие изображения, хотя до полной иллюзии пространства при этом, конечно же, очень далеко. Ниже приведен текст функции формирования изображения display ().
void display()
{
/* Вычислить и вывести на экран единственную новую точку */
int rand();
int i;
j=rand()%4; /* Случайным образом выбрать индекс вершины */
/* Вычислить координаты точки, расположенной на полпути между выбранной вершиной и точкой, сформированной в предыдущем цикле */
р[0] = (р[0]+vertices[j][0])/2.0;
р[1] = (p[l]+vertices[j][l])/2.0;
Р[2] = (p[2]+vertices[j][2])/2.0;
/* Вывести точку на экран */
glBegin(GL_POINTS);
glColor3f(p[0]/250.0,р[1]/250.О,р[2]/250.0);
glVertex3fv( р );
glEnd();
/* Заменить координаты прежней точки */
old.x=new.x;
old.y=new.y;
old.z=new.z;
glFlush();
}
Поскольку мы теперь работаем в трехмерном пространстве, то в файле main.с нужно определить не прямоугольник, а параллелепипед видимости:
glOrtho(-500.0 , 500.0, -500.0, 500.0 ,-500.0, 500.0);
На рис. 2.37 и ил. 1 на цветной вклейки показано, что если сформировать достаточно большое количество точек, то изображение будет походить на исходный тетраэдр, из которого “вынут” меньший тетраэдр в центре.
Рис. 2.37. Трехмерный узор Серпинского
Использование многоугольников в трехмерном пространстве
Теперь рассмотрим, каким образом в трехмерном пространстве можно реализовать второй подход к построению узора. Гранями тетраэдра являются четыре треугольника, заданные четырьмя вершинами. Для каждой из четырех граней можно применить алгоритм разбиения, точно такой же, как тот, который применялся к единственному треугольнику при работе в двухмерном пространстве. В результате текст новой программы в значительной мере воспроизводит текст программы, формирующей двухмерный узор по тому же принципу. Единственное отличие в базовой функции разбиения — использование трехмерных точек вместо двухмерных:
void triangle( points a, points b, points c) {
glBegin(GL_POLYGON);
glVertex3fv(a);
glVertexSfv(b);
glVertexSfv(c);
glEnd();
}
Функция divide_triangle() выполняет те же операции, но над трехмерными точками: void divide_triangle(points a, points b, points c, int k) {
points ab, ac, be;
int j;
if(k>0)
{
for(j=0; j<3; j++) ab[j]=(a[j]+b[j])/2;
for(j=0; j<3; j++) ac[j]=(a[j]+c[j])/2;
for(j=0; j<3; j++) bc[j]=(b[j]+c[j])/2;
divide_triangle(a, ab, ac, k-1);
divide_triangle(c, ac, be, k-1);
divide_triangle(b, be, ab, k-1);
}
else(triangle(a,b,c)); /* Вывод треугольника на экран и завершение работы */
}
Для разделения четырех граней тетраэдра нужно применить эту функцию к каждой из них:
void tetrahedron( int n ) {
glColor3f(l.0,0.0,0.0);
divide triangle(v[0], v[l], v[2], k);
glColor3f(0.0,1.0,0.0);
divide triangle(v[3], v[2], v[l], k);
glColor3f(0.0,0.0,1.0);
divide_triangle(v[0], v[3], v[l], k);
glColor3f(0.0,0.0,0.0);
divide_triangle(v[0], v[2], v[3], k);
Здесь переменная n по-прежнему представляет количество циклов выполнения рекурсивной процедуры. При выводе на экран элементарных треугольников каждой грани им следует задать свое значение атрибута цвета, тогда изображение трехмерной структуры будет лучше восприниматься пользователем. Но перед нами стоит еще одна проблема, которую следует решить, если уж мы хотим получить нормальное изображение.
Удаление невидимых поверхностей
При выполнении программы, рассмотренной в предыдущем разделе, результат не очень вас впечатлит. Программа будет выводить на экран треугольники в том порядке, в каком они формируются в процессе выполнения деления граней. Этот порядок не имеет отношения к расположению граней в пространстве, каждый треугольник будет заливаться своим цветом, причем те треугольники, которые формируются позже, перекроют нарисованные раньше.
Совсем другая картинка открылась бы нашему взору, если бы узор создавался из реальных маленьких непрозрачных треугольников. В этом случае были бы видны только те из них, которые ближе к наблюдателю. На рис. 2.38 схематически показано, в чем состоит суть проблемы невидимых поверхностей. Наблюдатель видит прежде всего ближайший к нему участок А, а что касается остальных, то треугольник В оказывается вообще невидим, а в треугольнике С видна только та часть, которая не перекрывается участком А. Даже не вдаваясь в
детали алгоритмов анализа невидимых поверхностей, можно сразу заметить, что решающее
Рис. 2.38. Проблема удаления невидимых поверхностейРис. 2.38. Проблема удаления невидимых поверхностейзначение имеет взаимное положение наблюдателя и отображаемых участков поверхностей, в данном случае— треугольников. Алгоритмы упорядочения объектов в порядке их близости к наблюдателю называются алгоритмами анализа видимости поверхностей (visible-surface algorithms) или алгоритмами удаления невидимых поверхностей (hidden- surface-removal algorithms) — все зависит от точки зрения. Эти алгоритмы мы подробно рассмотрим в главах 4 и 7.
Рис. 2.38. Проблема удаления невидимых поверхностей
Рис. 2.38. Проблема удаления невидимых поверхностей
В данной программе будет использован алгоритм удаления невидимых поверхностей, получивший в литературе название алгоритма z-буфера (z-buffer algorithm), который поддерживается в OpenGL. Существуют средства управления этим алго-
ритмом, позволяющие легко отключать или включать его. В функции main() нужно запросить дополнительную память для работы этого алгоритма — z-буфер или буфер глубины. Это выполняется спецификацией константы GLUT_DEPTH при вызове функции glut!nitDisplayMode(): glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);
Разрешается выполнение алгоритма вызовом функции glEnable() с константой GL_DEPTH_TEST в качестве аргумента:
glEnable(GL_DEPTH_TEST)
Фрагмент с операторами настройки включается либо в файл main.с, либо в функцию инициализации (файл myinit.c). Поскольку алгоритм хранит информацию в буфере глубины, его требуется очищать перед формированием каждого нового изображения. Очистка выполняется в той части функции отображения, где инициализируются параметры процесса: glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
Функция отображения display () теперь примет вид
void display()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
tetrahedron(n);
glFlush();
Результат ее выполнения после пяти циклов показан на рис. 2.39 и на ил. 2 цветной вклейки. Полный текст программы приведен в приложении А.
Рис. 2.39. Трехмерный узор Серпинского после выполнения пяти циклов рекурсивной процедуры
Резюме
В этой главе читатели познакомились с основными характеристиками API OpenGL и с тем, как в этой системе реализуются базовые концепции, представленные в главе 1. Хотя первое из рассмотренных в этой главе приложений и является двухмерным, мы все время обращали ваше внимание на то, что двухмерная графика в OpenGL — это только частный случай трехмерной. Было показано, как с минимальными усилиями распространить результаты, полученные для двухмерного случая, на трехмерный вариант постановки задачи.
В качестве учебной задачи была рассмотрена нетривиальная задача построения узора Серпинского с помощью алгоритмов двух типов. Некоторые математические вопросы, связанные с этой задачей, мы предлагаем вам проанализировать самостоятельно в упражнениях
после основного текста главы. Более пространное вступление в теорию фракталов читатель найдет в главе 11, где эта теория будет использована для формирования реалистически выглядящих объектов случайной формы. Литературные источники, которые упоминаются в заключительном разделе главы, содержат много других примеров интересных кривых и поверхностей, которые можно реализовать в довольно простых программах.
Обзор развития графических API показал, насколько важно как можно раньше начинать работать с задачами компьютерной графики в трехмерной постановке. Много лет концептуальная модель перьевого плоттера использовалась в качестве базовой при создании графических API, в частности Postscript. Разработка международных стандартов для графических API началась в 1970-х годах, и первым результатом в этом направлении было принятие в 1984 году Международной организацией по стандартизации (International Standards Organization — ISO) стандарта GKS. Однако основанный на модели перьевого плоттера стандарт GKS распространялся только на двухмерные графические приложения и имел ограниченное применение в индустрии автоматизированного проектирования. Хотя позднее он был расширен и охватывал трехмерные приложения (стандарт GKS-3D), ограничения, связанные с ориентацией на указанную концептуальную модель, все же остались. API PHIGS и PH1GS+, поначалу применявшиеся только в САПР, изначально создавались как трехмерные на основе модели синтезированной камеры.
Парадигма “моделирование-тонирование” реализована в интерфейсе Renderman Interface. Хотя его спецификация охватывает только интерфейс между средствами моделирования и тонирования, этот программный продукт использует высококачественный алгоритм тонирования на основе трассировки лучей и наряду с другими широко используется при создании мультипликационных фильмов.
Графическая система OpenGL появилась как развитие GL API, основанного на модели синтезированной камеры в приложении к конвейерной архитектуре. Пакет GL был разработан фирмой Silicon Graphics для выпускаемых ею рабочих графических станций, в которых реализована конвейерная архитектура обработки на базе специализированных СБИС. Поэтому, хотя между PHIGS и GL есть много общего, пакет GL был разработан специально для получения с высокой скоростью тонированных изображений, близких к реальным. Система OpenGL появилась в ответ на спрос со стороны прикладных программистов, убедившихся в достоинствах программирования с использованием GL и пожелавших реализовать эти принципы на других платформах. При этом оказалось необходимым изъять из пакета функции, входящие в компетенцию операционных систем, и сосредоточить основное внимание на эффективных методах моделирования и тонирования.
Приведенные в этой главе примеры и программы касаются прежде всего описания и отображения геометрических объектов. Если рассматривать материал данной главы с точки зрения парадигмы “моделирование-тонирование”, то основное внимание в нем было уделено вопросам моделирования. Но созданные простейшие модели абсолютно неструктурированы. Объекты представлены только набором вершин и атрибутов. В главе 8 будет рассмотрен иерархический подход к моделированию, который позволяет представить в модели отношения между объектами. Но, тем не менее, и на этой базе можно создавать довольно впечатляющие графические программы.
Рекомендуемая литература
Узор Серпинского является отличным примером для ввода читателя в мистический мир геометрии фракталов, которая в разных аспектах обсуждается в работах [Bar93, НП90, Мап82, Рги90].
Пакеты API, построенные на базе модели перьевого плоттера, используются в Postscript [Ado85] и LOGO [Pap81J. LOGO поддерживает так называемую “черепашью”, или “паучью”
графику (turtle graphics). Этот пакет несложен в изучении и пригоден для построения разного рода двухмерных математических кривых. О нем еще пойдет речь в главе 8 (см. упр. 2.4).
Международные и национальные графические стандарты описаны в целом ряде технических документов: [ANS185] — GKS, [1SO88] — GKS-3D, [ANS188] —PHIGS, [PH1G89] — PHIGS+. Соответствующую документацию можно получить в Американском национальном институте стандартов (ANSI — American National Standards Institute) и Международной организации по стандартизации (ISO— International Standards Organization). Существует и множество книг, посвященных описанию этих API, — [Ang90, End84, Fol94, Hea94, Hop83, Нор91].
Операционная система X Window [Sch88] стала в последнее время фактическим стандартом для рабочих станций на платформе UNIX. Ее структура и возможности оказали большое влияние на развитие оконных систем на других платформах. Последние версии X Window включают и специальное расширение PHIGS — РЕХ.
Интерфейс Renderman описан в работе [Ups89].
Два наиболее популярных источника информации о системе OpenGL — это руководство программиста OpenGL Programmer's Guide [Оре97,а] и справочник OpenGL Reference Manual [Оре97,Ь]. Существует и формальная спецификация OpenGL— [Seg92], Во втором издании руководства программиста Programmer's Guide описана библиотека GLUT, разработанная Марком Килгардом (Mark Kilgard) [КИ94,Ь]. В это руководство включено множество примеров использования функций OpenGL. Библиотека GLUT была разработана для операционной системы X Window [KU96], но ее новые версии применимы и в операционных системах Windows 98 и NT. В первой редакции руководства для интерфейса с X Window использован более простой инструментальный пакет aux. Конвертирование программ, написанных с ориентацией на aux в программы, работающие с библиотекой GLUT, не представляет труда. Много информации об OpenGL и примеры программ можно найти и в Internet. Достаточно много адресов соответствующих страниц вы найдете в самом начале приложения А.
Упражнения
Небольшая модификация алгоритма формирования узора Серпинского с помощью рекурсивного разбиения треугольников позволяет создавать с помощью треугольников изображения фрактальных гор {fractal mountains') в компьютерных мультфильмах. После определения средних точек на каждой стороне треугольника нужно слегка исказить эти значения случайным образом, а затем выполнять разбиение. Разработайте программу, которая будет формировать такие треугольники без заливки. Позже можно будет распространить этот алгоритм и на трехмерный случай и добавить тонирование. После нескольких циклов разбиения получится достаточно детальное изображение пирамидальной горы с неровным рельефом.
Узор Серпинского, сгенерированный в предыдущем упражнении, демонстрирует геометрические объекты, которые изучаются в геометрии фракталов (подробно она будет рассмотрена в главе 8). Предположим, что формируется узор из идеальных математических линий, т.е. двухмерных многообразий, имеющих длину, но не имеющих толщину. Какая часть области исходного треугольника окажется в пределе не заполнена этими линиями после того, как центральный треугольник при каждом разбиении изымается из дальнейшего процесса? Рассмотрите также значение периметра треугольников, оставшихся после удаления центрального. К какому значению в пределе стремится суммарный периметр всех оставшихся треугольников?
На самом нижнем уровне обработки мы манипулируем с битами в буфере кадра. В составе OpenGL имеются команды работы с пикселями, позволяющие напрямую обращаться к буферу кадра. Вы можете поэкспериментировать с простыми растро-
выми алгоритмами формирования отрезков или окружностей, используя в качестве базовой OpenGL-функцию glPoint(). Разработайте библиотеку подобных функций формирования примитивов, которая позволит создать буфер кадра в памяти. Ядро такой библиотеки должны составить функции WritePixel() и ReadPixel(). В составе библиотеки также должны быть функции заполнения и отображения буфера кадра и запуска программы пользователя, которая будет записывать и считывать коды засветки отдельных пикселей.
“ Черепашья ” графика (turtle graphics) — это метод построения штриховых изо
бражений, альтернативный позиционному. В основе этого метода лежит идея воспроизведения следа черепахи, ползущей по экрану с закрепленным в ее центре пером (перо способно подниматься и опускаться). Текущее состояние черепахи описывается триадой (х,^, 0)— положением центра и углом ориентации. В типовом API, поддерживающем такой режим, есть следующие функции: init(x,y,theta); /* Установка положения и ориентации черепахи */ forward(distance);
right(angle);
left(angle);
pen(up_down);
Реализуйте библиотеку функций черепашьей графики на базе OpenGL.
Используя библиотеку, разработанную при выполнении предыдущего упражнения, напишите программу построения узора Серпинского и фрактального изображения горы (алгоритмы построения такого изображения рассматриваются в упр. 2.1 и 2.2).
Уже не одно столетие математиков интересуют свойства и методы построения кривых заполнения пространства (space-filling curves). В пределе такие кривые бесконечны, но, во-первых, пространство заполнения ограничено конечным прямоугольником, а во-вторых, кривая не должна быть самопересекаюшейся. Многие из таких кривых могут быть сгенерированы с помощью итеративной процедуры. Рассмотрите “правило”, представленное на рис. 2.40, которое заменяет одиночный отрезок четырьмя более короткими. Разработайте программу, которая начинает работу- с треугольника, а затем итерационно применяет сформулированное правило замены ко всем отрезкам. Объект, который сформирует подобная программа, называется снежинкой Коха (Koch snowfake). Другие типы кривых заполнения пространства описаны в книгах [HU90, Ваг93].
Рис. 2.40. Формирование снежинки Коха
1Л. Изображение простейшего лабиринта можно построить, начав с прямоугольного массива ячеек. Каждая ячейка имеет четыре стороны. В каждом цикле итерационной процедуры удаляется одна из сторон ячейки, но только не та. что лежит на периметре ограничивающего прямоугольника. Процедура продолжается до тех пор, пока все ячейки не станут связаны “проемами” в стенах. Затем создается вход и выход из лабиринта, для чего нужно удалить “наружную” стену в двух ячейках, примыкающих к ограничивающему прямоугольнику. Простой лабиринт такого типа показан на рис. 2.41. Разработайте OpenGL-программу, которая принимает в качестве аргументов построения лабиринта размером КхМ значения двух целых чисел Л и М.
2.8.2.9.2.10.2.8.2.9.2.10.2.11.2.12.2.13.2.14.2.15.2.11.2.12.2.13.2.14.2.15.Подумайте, как адаптировать цветовую модель RGB, используемую в OpenGL, для представления модели цветов с вычитанием основных компонентов.
2.8.
2.9.
2.10.
2.8.
2.9.
2.10.
2.11.
2.12.
2.13.
2.14.
2.15.
2.11.
2.12.
2.13.
2.14.
2.15.
В этой главе было показано, что базовой операцией графической системы является отображение точки (х,у\ которая лежит во внутренней области отсекающего прямоугольника, в точку (х.„Ю, которая принадлежит видовому окну на экране. Предположим, что отсекающий прямоугольник и рамка видового окна определены вызовами функций OpenGL: glViewport(u, v, w , h);
glu0rtho2D(x_min, x_max, y_min, y_max);
Puc. 2.41. Простой лабиринтPuc. 2.41. Простой лабиринтОтыщите математическое соотношение, связывающее пары координат (х, у) с парами (х„ у,).
Puc. 2.41. Простой лабиринт
Puc. 2.41. Простой лабиринт
Во многих графических API используется относительный способ задания положения. В подобных API для вычерчивания отрезков и многоугольников имеются функции move_rel(x,y); line_rel(x,y);
Функция move_rel() перемешает курсор на заданное расстояние в новое положение. Текущая позиция курсора является внутренней переменной состояния системы. Функция line_rel() не только перемещает курсор, но и вычерчивает отрезок с заданными проекциями на оси координат, начиная с предыдущей позиции курсора. Каковы преимущества и недостатки относительного задания параметров графических элементов по сравнению с заданием в абсолютных значениях, как это делается в OpenGL? Каким образом реализовать относительный способ в рамках API OpenGL?
В разделе 2.3 при анализе положения точки относительно контура многоугольника были проигнорированы проблемы, которые могут возникнуть, если одна или несколько сторон многоугольника окажутся параллельными линиям растра. В чем, по- вашему, суть этих проблем? Как с ними справиться?
С практической точки зрения анализ каждой точки некоторой области на принадлежность внутренней или внешней области многоугольника крайне неэффективен. Разработайте общую стратегию, которой нужно следовать, чтобы избежать анализа всего массива точек.
В разделе 2.3 было сказано, что в OpenGL многоугольник задается списком его вершин. Как можно в рамках системы OpenGL задать многоугольник его ребрами?
Разработайте тест простоты многоугольника.
Рис. 2.42. Полигональная сетьРис. 2.42. Полигональная сетьНа рис. 2.42 показано множество многоугольников, образующее полигональную сеть (polygonal mesh\ Эти многоугольники имеют совместные грани и вершины. Разработайте один или несколько вариантов структур данных, описывающих такую сеть. “Хорошая” структура данных должна включать информацию о совместном использовании вершин и ребер. В рамках системы OpenGL отыщите эффективный метод отображения сети, представленной такой структурой.
Рис. 2.42. Полигональная сеть
Рис. 2.42. Полигональная сеть
В системе OpenGL с каждой вершиной можно связать некоторый цвет. Если вершины отрезка имеют разные цвета, OpenGL при вычерчивании отрезка плавно изменяет (интерполирует) цвет линии по мере перемещения от начальной вершины к конечной. Точно так же вычерчиваются и ребра многоугольников. Воспользуйтесь этим свойством OpenGL и разработайте программу вычерчивания треугольника Максвелла {Maxwell triangle^— равностороннего треугольника, вершины которого имеют красный, зеленый и синий цвета. Как соотносятся треугольник Максвелла и цветовой куб?
Включив в модель описание некоторых физических явлений, можно получить довольно интересные изобразительные эффекты. Смоделируйте прыгающий шарик в двухмерном пространстве, используя явления гравитации и упругого столкновения с поверхностью. Сам шарик можно представить в виде замкнутого многоугольника с достаточно большим количеством сторон, чтобы он выглядел на экране как гладкий.
В развитие предыдущего упражнения подумайте о том, как смоделировать игру в бильярд (это задача посложнее, чем простой “попрыгунчик”). Придется просчитывать взаимодействие множества шаров со столом и друг с другом.
Подсказка. Начните с двух шаров и подумайте над тем, как выяснить, столкнутся ли они.
В сообщениях о некоторых дисплеях на ЭЛТ анонсируется, что они способны воспроизводить любые четыре из 64 цветов. Какую информацию о буфере кадра и качестве дисплея несет такое сообщение?
Разработайте программу анализа выпуклости двухмерного многоугольника.
Для многих новичков, только приступивших к работе с OpenGL, большую сложность представляет множество вариантов базовых функций, как, например, glVer- tex*(). В таких языках, как C++, можно использовать одно и то же имя функции при работе с разными типами аргументов — заботу о выборе подходящего варианта реализации функции берет на себя компилятор. Разработайте библиотеку функций C++, которые должны выполнять роль посредника между прикладной программой и OpenGL и таким образом облегчить жизнь прикладному программисту — позволить ему пользоваться единственным именем функции и неявно специфицировать ее вариант через типы аргументов.
ГЛАВА
3
Ввод и взаимодействие с пользователем
В
данной главе мы рассмотрим интерактивные графические программы. В этот класс входит огромное количество самых разнообразных приложений — от систем проектирования до программ управления большими системами, в которых используется графический интерфейс, систем виртуальной реальности и компьютерных игр.
Обсуждение этой темы я разделил на три части. Сначала я познакомлю читателей с устройствами ввода, которые используются одной из взаимодействующих “сторон”, — пользователем графической программы. Устройства ввода мы рассмотрим с двух точек зрения: как способ описания физических устройств их реальными характеристиками и как способ представления этих устройств в прикладной программе. Затем мы рассмотрим сетевую среду и графические приложения типа “клиент/сервер”, работающие в такой среде. Идеи организации подобных приложений мы будем использовать для создания подсистемы ввода информации, управляемой событиями. В последней части главы будет рассмотрена интерактивная графическая программа, изменяющая изображение в соответствии с командами пользователя. Эта программа продемонстрирует возможности организации эффективного взаимодействия человека и компьютера.
Интерактивная компьютерная графика
Одно из основных достоинств компьютерной технологии состоит в том, что она позволяет пользователю оперативно взаимодействовать с компьютером в процессе решения определенной проблемы. При этом со стороны компьютера роль средства “общения” предоставляется дисплею, на экране которого компьютер демонстрирует результат своей работы. Отсчет современной эры интерактивной компьютерной графики следует вести от проекта Sketchpad, выполненного под руководством Айвена Сазерленда (Ivan Sutherland). В основе проекта лежала на удивление простая и эффективная идея — пользователь видит на экране дисплея изображение, сформированное компьютером, и реагирует на него, передавая компьютеру информацию через различные устройства ввода. В ответ на поступившую информацию — команды и данные — компьютер изменяет изображение, и цикл повторяется вновь. Эта идея используется повсеместно практически во всех современных программах — от программ,
создающих среду для разработки программ, до интерактивных экспонатов в музеях, отвечающих на вопросы посетителей или демонстрирующих нечто в ответ на запрос посетителя.
За те 35 лет, которые прошли со времени появления работы Сазерленда, и аппаратные, и программные средства подобных систем значительно усовершенствовались, но фундаментальные идеи интерактивной компьютерной графики, введенные им в научный оборот, остались прежними. Эти идеи охватывают широкий круг вопросов, начиная от концепции взаимодействия человека с компьютером и заканчивая методами структурирования графических данных, обеспечивающими эффективную реализацию таких систем.
В этой главе я использую подход, слегка отличный от подхода к изложению материала в других главах. Хотя в большинстве современных графических API, a OpenGL в этом смысле не исключение, основное внимание уделяется методам реалистического тонирования изображения, возможность взаимодействия с пользователем является одной из важнейших функций большинства приложений. Но должен отметить, что OpenGL не располагает средствами непосредственной поддержки этого режима. Главная причина подобной “забывчивости” разработчиков OpenGL кроется в том, что они стремились обеспечить максимальную “переносимость” API с одной платформы на другую, т.е. позволить системе работать в самых различных операционных средах. Поэтому функции создания окон и ввода информации от пользователя не включены в состав API, а отданы “на откуп” операционной системе. Хотя такое решение и обеспечило переносимость самых сложных алгоритмов формирования изображения с одной платформы на другую, оно в то же время затрудняет обсуждение вопросов взаимодействия с пользователем, так как при этом приходится учитывать специфику каждой отдельной операционной системы. Кроме того, поскольку большинство программ должно располагать хотя бы минимальным интерфейсом с подсистемой окон, нам не удастся избежать обсуждения специфики операционных систем, если, конечно, наша цель — создание нетривиальных законченных программных продуктов. Если средства взаимодействия вынесены за пределы графического API, то прикладному программисту волей-неволей приходится самостоятельно постигать тайные пружины конкретной операционной системы.
Этих потенциальных сложностей можно избежать, если использовать простую инструментальную библиотеку, как мы делали это при обсуждении некоторых вопросов в главе 2. Такая инструментальная библиотека должна поддерживать выполнение базовых функций, которые существуют в том или ином виде в любой операционной системе, — открытие окна приложения, работа с клавиатурой и мышью, создание меню. В данной главе мы воспользуемся именно таким подходом, хотя он и не позволяет использовать с максимальной эффективностью весь спектр функциональных возможностей каждой отдельной операционной среды.
Мы будем по-прежнему использовать термин система окон (windowing system) в том же смысле, что и в главе 2, подразумевая под ним какую-либо операционную среду, способную формировать окна приложений, например среду X Window, Microsoft Windows или операционную систему компьютеров Macintosh. Те графические программы, которые будут рассмотрены в этой главе, заполняют область окна, выделенного приложению, действуя в среде одной из перечисленных операционных систем. Та терминология, которая уже устоялась в литературе по операционным системам, может иногда приводить к путанице, особенно при использовании термина “окно”. Но вы всегда можете рассматривать окно OpenGL как частный случай окна операционной системы, например X Window. Использование инструментальной библиотеки GLUT позволит нам избежать сложностей, связанных с взаимодействием системы окон, менеджера окон и графической системы. Как и те программы, которые мы рассматривали в главе 2, новые графические программы также не будут зависеть от конкретной платформы, если только на ней функционирует своя версия библиотеки GLUT.
Начнем изложение материала этой главы с описания нескольких типов устройств ввода и способов организации взаимодействия с пользователем на их базе. Затем будет рассказано о том, как эти устройства можно применять в сетевых системах класса “клиент/сервер”. После
этого будут описаны пакет API, обеспечивающий минимально необходимый набор функций взаимодействия, и простая графическая программа на его основе.
Устройства ввода
Устройства ввода можно рассматривать с двух точек зрения. Прежде всего, их можно анализировать как физические устройства, например клавиатуру или мышь, и рассматривать физические принципы работы. Конечно же, программист должен иметь определенное представление о том, как работают устройства, с которыми он сталкивается практически ежеминутно. Однако с точки зрения прикладного программирования нежелательно использовать в программе конкретные характеристики физического устройства определенного типа. Следует скорее рассматривать такое устройство как логическое, свойства которого можно специфицировать в терминах функций, которые оно выполняет в прикладной программе. Логическое устройство характеризуется интерфейсом достаточно высокого уровня с программой пользователя, а не физическими параметрами. Термин “логическое устройство” знаком всем программистам, работающим с языками высокого уровня. Например, в языке С ввод и вывод данных осуществляется с помощью функций printf (), scanf (), getchar() и putchar(), аргументами которых являются переменные стандартных типов языка. При выводе строки с помощью функции printf () в качестве физического устройства используется дисплей терминала, принтер или файл на диске. Выводимые данные могут быть использованы как входные для другой программы. Нюансы форматирования, характерные для конкретного устройства, при этом никак не заботят прикладного программиста.
В компьютерной графике работа с логическими устройствами ввода организуется несколько сложнее, поскольку формат входной информации может сильно отличаться от простой последовательности битов или символов, которой вполне достаточно для неграфических данных. Например, можно использовать мышь — физическое устройство — либо для указания позиции на экране ЭЛТ, либо для выбора определенного пункта меню. .В первом случае в прикладную программу возвращается пара значений (х, у) в некоторой системе координат, а во втором — программа получает целое число, которое однозначно идентифицирует выбранный пункт меню. Такое отделение физического устройства от логического позволяет программе использовать одно и то же физическое устройство совершенно по-разному. Кроме того, такая программа способна нормально работать без всякой коррекции даже в том случае, когда мышь заменена другим физическим устройством, например планшетом или трекболом.
Физические устройства ввода
Особенности конструкции каждого устройства позволяют ему специализироваться на выполнении определенного круга задач. Мы будем придерживаться того же подхода, который использован в большинстве литературных источников по машинной графике, — разделение на две большие группы: устройства указания и клавиатуры. С помощью устройства указания (pointing device) пользователь может указать позицию на экране. Практически все устройства этой группы оснащены парой или несколькими кнопками, которые позволяют сформировать и передать в компьютер какие-либо сигналы или прерывания. Клавиатура (keyboard device) почти всегда представляет собой панель, на которой установлено множество (как правило, свыше сотни) кнопок. Различные варианты устройств этой группы объединяет то, что все они передают в компьютер коды символов1.
'Как правило, при этом используется формат кодов символов ASCII (Американский стандартный код для обмена информацией — American Standard Code for Information interchange), рекомендованный Американским институтом стандартов. Но ничто не мешает использовать и любую другую кодировку, если того требует специфика приложения.
Мышь (рис. 3.1) и трекбол (рис. 3.2) похожи не только по назначению, но часто и по конструкции. Если перевернуть типичную механическую мышь кверху “брюшком”, то она будет очень похожа на трекбол. В обоих устройствах вращение шарика преобразуется с помощью пары преобразователей в сигналы, передаваемые в компьютер. Преобразователи измеряют вращение относительно двух взаимно перпендикулярных осей.
Рис. 3.1. Мышь Рис. 3.2. Трекбол
Существует очень много модификаций устройств этих групп. В некоторых их них используются оптические, а не механические чувствительные элементы для измерения перемещения. Оптическая мышь измеряет расстояние, подсчитывая штрихи на специальной подложке. Маленькие трекболы (trackball) широко используются в портативных компьютерах, где их встраивают прямо в клавиатуру.
В некоторые клавиатуры встраиваются приборы, чувствительные к давлению, которые выполняют те же функции, что и мышь или трекбол, но при этом в них отсутствуют подвижные элементы. Преобразователи в таких устройствах измеряют величину давления на небольшой выпуклый набалдашник, размещенный между двумя кнопками в средней части клавиатуры.
Выходные сигналы мыши или трекбола можно рассматривать как две независимые величины и преобразовать их в координаты положения на двухмерной- плоскости экрана или в какой-либо другой системе координат. Считанные с устройства значения можно сразу же использовать для управления специальной отметкой (маркером, курсором) на экране, но в таком режиме подобные устройства используются очень редко.
Совсем не обязательно, чтобы формируемые мышью или трекболом сигналы интерпретировались как расстояния. Драйвер устройства и прикладная программа могут трактовать их и как значения двух независимых скоростей (см. упр. 3.4). Затем программа может интегрировать последовательность этих значений и получить абсолютные координаты в двухмерной системе. Таким образом, по мере движения мыши по какой-либо поверхности интеграл от скорости дает значения (х, у), которые служат для отображения маркера на экране (рис. 3.3). Интегрируя смещения трекбола (рассматривая их как значения скоростей), можно использовать его как устройство ввода с переменной чувствительностью. Небольшие отклонения шара трекбола от заданного “нулевого” положения приводят к медленному смещению маркера на экране, а большие— к его быстрому движению. При использовании мыши или трекбола в обычном режиме мы имеем дело фактически с относительным положением устройства. Если переместить указатель каким-либо способом в другое место, не вращая при этом шарик мыши или трекбола, то дальнейшие сигналы будут смещать указатель относительно новой позиции. Абсолютные координаты устройства не считываются прикладной программой.
При решении некоторых задач, например при вводе в компьютер графиков, прикладная программа нуждается не в относительных, а в абсолютных координатах устройства ввода. Такую возможность обеспечивают разного рода планшеты (Data tablets). В типичном планшете применяется ортогональная сетка проводов, расположенных под его поверхностью
Рис. 3.3. Управление положением указателя мышиРис. 3.3. Управление положением указателя мыши(рис. 3.4). Положение щупа {stylus) определяется посредством электромагнитного взаимодействия сигналов, проходящих от проводов к щупу. Иногда в качестве планшета используются чувствительные к прикосновению прозрачные экраны, которые наносятся на поверхность ЭЛТ. Небольшие экраны такого типа размешаются иногда на клавиатуре портативных компьютеров. Чувствительные к прикосновению панели можно использовать в режимах абсолютных и относительных отсчетов.
Рис. 3.3. Управление положением указателя мыши
Рис. 3.3. Управление положением указателя мыши
Рис. 3.4. Планшет
Пожалуй, самую длинную историю имеет использование в компьютерной графике устройства, получившего при рождении имя световое перо {lightpen). Впервые оно появилось еще в проекте Sketchpad. Световое перо содержит фоточувствительный элемент (рис. 3.5), который при приближении к экрану “чувствует” излучение, порождаемое при столкновении электронов с люминофорным покрытием экрана. Если мощность светового импульса превышает определенный порог, фоточувствительный элемент формирует импульс, который передается в компьютер. Анализируя смещение этого импульса относительно начала цикла регенерации, компьютер может четко определить координаты той точки экрана, возбуждение которой “засветило” фотоэлемент (см упр. 3.19). Таким образом, в распоряжении пользователя оказывается устройство непосредственного указания, работающее напрямую с изображением на экране. В те далекие времена еще не была изобретена мышь, но сейчас эти простые и надежные устройства повсеместно вытеснили экзотические световые перья. Кроме всего прочего, в графической системе, работающей со световым пером, нужно предпринимать специальные меры, чтобы иметь возможность считывать координаты точек на темных участках экрана.
Рис. 3.6. ДжойстикРис. 3.6. Джойстик
Рис. 3.6. Джойстик
Рис. 3.6. Джойстик
Еще одно устройство, которое заслуживает упоминания, — это джойстик {joystick) (рис. 3.6). Перемещение джойстика в двух взаимно перпендикулярных направлениях воспринимается преобразователями, интерпретируется как составляющие вектора скорости, интегрируется, а полученные значения используются для управления положением маркера на экране. Интегрирование выполняется таким образом, что неподвижный джойстик в каком-либо промежуточном положении не изменяет положение маркера, а чем дальше джойстик отклонен от начального положения, тем быстрее маркер перемещается по экрану. Таким образом.
джойстик играет роль устройства ввода с переменной чувствительностью. Другое преимущество джойстика— возможность имитации силовой обратной связи с помощью разного рода пружин. При этом пользователь чувствует, что чем дальше отклонен джойстик, тем большее усилие требуется для его дальнейшего движения. Это очень помогает в работе с разного рода симуляторами и играми.
В трехмерной графической системе весьма соблазнительно использовать и трехмерные устройства ввода. Хотя и существуют различные конструкции таких устройств, они все еще не получили широкого распространения, поскольку проигрывают популярным двухмерным устройствам как по стоимости, так и по техническим характеристикам. Пространственный шар (спейсбол — spacebar очень похож на джойстик, но отличается от него тем, что на рукоятке закреплен шар (рис. 3.7). причем рукоятка в этой конструкции неподвижна. Шар имеет датчики давления, которые измеряют усилие, прикладываемое пользователем. Шар может измерять не только составляющие усилия в трех основных направлениях (сверху вниз, от себя или на себя, влево — вправо), но и вращение относительно трех осей. Таким образом, это устройство способно передавать в компьютер шесть независимых параметров (говорят, что устройство имеет шесть степеней свободы), характеризующих как плоскопараллельное смещение, так и вращение. Такого рода устройство можно использовать, например, при настройке положения и ориентации камеры в нашей модели получения изображения.
Существуют и другие трехмерные системы измерения и ввода, использующие самые современные технологии, например лазерные. В системах виртуальной реальности используются еще более экзотические устройства, позволяющие динамически отслеживать положение и ориентацию пользователя. Для приложений, связанных с современной робототехникой и моделированием виртуальной реальности, иногда требуются устройства, обладающие еще большим
Рис. 3.7. Пространственный числом степеней свободы, чем упомянутый спейсбол. В по- шар (спейсбол)следнее время появилось множество сообщений о новых раз
работках в этом направлении, в частности об изобретении “очувствленных” перчаток, которые способны улавливать движения отдельных органов подвижности человека.
В тех программах, которые будут рассматриваться в этой книге, мы не будем использовать никакие экзотические устройства, а ограничимся известными всем мышью и клавиатурой. Но это отнюдь не значит, что пакет API ограничивает возможность ввода графической информации только двухмерными данными.
Логические устройства
Теперь вернемся к рассмотрению устройств ввода с точки зрения возможности их связи с прикладной программой, т.е. будем рассматривать их как логические устройства. Функционирование устройства ввода можно описать двумя характеристиками:
какую измерительную информацию передает устройство в прикладную программу;
когда устройство посылает эту информацию.
В некоторых API, таких как PHIGS и GKS, рассматривается шесть классов логических устройств ввода. Поскольку в современных операционных системах функцию ввода можно полностью отделить от конструктивных особенностей того или иного прибора, в OpenGL такой подход не применяется. Тем не менее мы кратко познакомим читателей с этими шестью классами, поскольку они демонстрируют, какой широкий спектр форм ввода информации имеется в распоряжении разработчика прикладной графической системы. Кроме то-
го, этот перечень поможет нам в дальнейшем проанализировать, как аналогичные функции реализуются в OpenGL.
Строковое устройство. Это логическое устройство ввода, способное передавать в прикладную программу текстовую (символьную) информацию в виде строк (последовательностей символов) в определенном формате — чаше всего в виде кодов ASCII. Обычно логические устройства этого типа конструктивно оформляются в виде клавиатуры. В таком случае используемая терминология полностью совместима с применяемой в описании большинства существующих операционных систем. В OpenGL не делается различия между логическими строковыми устройствами и физическими устройствами типа клавиатуры.
Локатор. Устройство типа локатор передает в прикладную программу информацию о положении в мировой системе координат. Обычно такое логическое устройство конструктивно оформляется в виде устройства указания — мыши или трекбола. В OpenGL мы будем использовать устройства указания именно таким образом, хотя для этого и придется выполнять в прикладной программе промежуточные преобразования из системы координат экрана в мировую систему координат.
Указатель. Указатель возвращает в прикладную программу идентификатор указанного объекта. Обычно в качестве логического устройства этого типа используется та же самая мышь или трекбол, но оно имеет другой интерфейс с прикладной программой. При описании OpenGL будем именовать процессом выбора (selection') процедуру указания какого-либо объекта на экране.
Устройство выбора. Устройство такого типа позволяет пользователю выбрать одну из дискретных (т.е. перечислимых) опций. В OpenGL в качестве логического устройства такого типа используются элементы графического интерфейса, поддерживаемые операционной системой, — меню, полосы прокрутки, экранные кнопки и т.д. Например, меню, включающее п пунктов, играет роль устройства выбора, позволяющего пользователю выбрать одну из п альтернатив.
Циферблат. Это устройство позволяет пользователю вводить значения непрерывных величин. Роль циферблата опять же выполняют различные элементы графического интерфейса, поддерживаемые операционной системой, — шкалы, регуляторы с движками и т.п.
Росчерк. Логическое устройство, которое получило столь экзотическое название (в оригинале — stroke), возвращает массив позиций. Фактически его представляет некоторая процедура— например, нажать кнопку мыши, переместить мышь, отпустить кнопку мыши. В результате в прикладную программу должен быть передан массив координат траектории перемещения мыши.
Показания и синхронизация
Информацию, передаваемую от физического или логического устройства в прикладную программу, можно условно разделить на две категории: показания устройства и сигнал синхронизации от устройства. Показание устройства — это то значение, которое устройство передает в прикладную программу. Сигнал синхронизации — это физический сигнал от устройства, которым пользователь извещает программу о завершении некоторого этапа процесса ввода (или всего процесса). Например, показания клавиатуры — это строка символов, соответствующая нажатым алфавитно-цифровым клавишам, а сигнал синхронизации формируется после нажатия клавиши <Enter> или <Return>. Для устройства типа локатор показаниями являются данные о положении устройства, а сигнал синхронизации формируется после нажатия определенной кнопки на устройстве указания.
Помимо основной информации, которую пользователь вводит явно, показания могут содержать и дополнительную информацию, например о текущем состоянии устройства. Например, устройство типа указатель возвращает в качестве основного компонента показаний идентификатор того объекта, на который указывает пользователь. Если физическим устройством этого типа является мышь, то сигнал синхронизации вырабатывается после нажатия кнопки мыши (как правило, левой). При разработке прикладной программы следует предусматривать и такой вариант, когда пользователь нажал кнопку мыши, не указывая при этом ни на какой объект. Если показания будут состоять только из идентификатора объекта, то программист в этом случае столкнется с довольно сложной проблемой анализа некорректности показаний. Разрешить ее будет значительно проще, если в показания включить и еще один компонент, который будет нести информацию о том, указывает ли устройство в момент формирования сигнала синхронизации на какой-либо объект или нет, не вышел ли маркер устройства за пределы выделенного приложению окна и т.п.
Режимы ввода
Помимо существующего множества типов логических устройств ввода, показания от этих устройств могут передаваться тремя разными способами. Эти способы (или режимы) отражают характер взаимодействия процесса съема показаний (или применительно к работе пользователя — ввода показаний) и формирования сигнала синхронизации. Обычно инициализация устройства ввода запускает и процесс съема показаний. Для инициализации может потребоваться вызов в явном виде определенной функции API или она может выполняться автоматически. В любом случае, как только будет запущен процесс съема показаний, соответствующая информация начинает поступать в буфер, хотя последний в это время и недоступен для прикладной программы. Например, положение мыши отслеживается непрерывно соответствующими средствами операционной системы, независимо от того, нуждается ли прикладная программа в этой информации или нет.
В режиме работы по запросу с синхронизацией от пользователя {request mode) показания устройства ввода не передаются в прикладную программу до тех пор, пока устройство не сформирует сигнал синхронизации. Этот режим ввода является стандартным для неграфических приложений, например типичной программы на С, которая требует ввода символов. В таких приложениях, встретив вызов функции ввода символов (например, scanf ()), программа приостанавливает выполнение и ожидает, пока пользователь не введет последовательность символов, пользуясь клавиатурой терминала. При этом в последовательности могут быть и символы забоя, а сама последовательность может иметь произвольную длину. По мере ввода данные помещаются в буфер клавиатуры, но содержимое последнего передается прикладной программе только после того, как будет нажата определенная клавиша (чаще всего <Enter>), которая сформирует сигнал синхронизации. Логическое устройство, например локатор, можно перемещать в требуемую позицию, а затем, нажав кнопку на этом устройстве, сформировать сигнал синхронизации. В результате в прикладную программу поступят координаты положения устройства именно в тот момент, когда была нажата кнопка. Схематически связь между сигналом синхронизации и показаниями в режиме работы по запросу показана на рис. 3.8.
' Процесс '■ синхронизаций■' Процесс '■ синхронизаций■..Запрос■
' Процесс '■ синхронизаций■
' Процесс '■ синхронизаций■
Процесс съема«;
показаний I^ ПрограммаСигнал ' ммйашмйммимЛ Показания "Швееййе^
синхронизации
Рис 3.8. Режим работы по запросу с синхронизацией от пользователя
В режиме выборок {sample-mode) ввод выполняется немедленно после поступления запроса со стороны прикладной программы. Как только в программе будет вызвана соответствующая функция, показания от устройства ввода передаются в программу. Таким образом, от устройства не требуется формирования какого-либо сигнала синхронизации (рис. 3.9). При
, -,. ВыборкаПроцесс съема,показаний I^ ПрограммаWiieieiAlfcmimI Показания ww*w™*»^^, -,. ВыборкаПроцесс съема,показаний I^ ПрограммаWiieieiAlfcmimI Показания ww*w™*»^^Рис. 3.9. Режим выборокРис. 3.9. Режим выборокработе в режиме выборок в программу поступают показания, введенные пользователем как раз перед вызовом функции считывания показаний, — код последней клавиши, нажатой на клавиатуре, или положение указателя локатора.
, -,. Выборка
Процесс съема,
показаний I^ Программа
WiieieiAlfcmimI Показания ww*w™*»^^
, -,. Выборка
Процесс съема,
показаний I^ Программа
WiieieiAlfcmimI Показания ww*w™*»^^
Рис. 3.9. Режим выборок
Рис. 3.9. Режим выборок
При работе через функции API с устройствами ввода в обоих описанных режимах нужно заранее
указать в программе, от какого именно устройства следует ввести информацию. Для работы с устройствами используются функции API, подобные приведенным ниже:
request_locator(device_id, Smeasure);
sample_locator(device_id, Smeasure);
В качестве аргументов функций используются идентификатор конкретного устройства и адрес буфера приема информации. Совершенно очевидно, что в обоих режимах игнорируется информация от всех прочих устройств, кроме специфицированного в вызове функции. Такая идеология организации свойственна тем приложениям, в которых ведущую роль играет программа, — именно программа определяет, с каким устройством ввода на данном этапе должен работать пользователь. Но если мы хотим отдать “пальму первенства” пользователю, оба эти режима ввода не подходят. Например, симулятор полета оснащен несколькими устройствами ввода—джойстиком, шкалами, кнопками и переключателями, причем именно пользователь-пилот выбирает, какое из устройств нужно использовать в данной ситуации. Разработать программу, которая обращалась бы ко всем этим устройствам в режиме запроса или выборки, практически невозможно, поскольку заранее неизвестно, какое из них понадобится пилоту в той или иной ситуации. Рассуждая на обобщенном уровне, приходим к заключению, что эти режимы не удовлетворяют требованиям современных систем активного взаимодействия пользователя с компьютером.
Третий режим — режим ввода, синхронизируемый событиями (или короче, режим обработки событий — event mode), позволяет реализовать обслуживание множества одновременно работающих устройств ввода. Описание этого режима я разбил на три стадии. Сначала я покажу, как режим обработки событий можно описать в терминах других режимов в пределах парадигмы “показания-синхронизация”. Затем будут рассмотрены основные концепции архитектуры “клиент/сервер”, в которой режим обработки событий играет главенствующую роль. Третья стадия— описание методики использования библиотеки GLUT при создании программ на OpenGL, активно взаимодействующих с пользователем. На этой стадии мы проанализируем демонстрационную программу, в которой применяется подобный интерфейс.
Предположим, что наше приложение работает в операционной среде, располагающей множеством устройств ввода, каждое из которых имеет свои средства формирования сигналов синхронизации и съема показаний. Каждый раз, когда устройство формирует сигнал синхронизации, возникает (иногда говорят возбуждается) событие {event). При этом показания устройства вместе с его идентификатором помещаются в очередь событий {event queue). Очередь событий заполняется операционной системой без всякого вмешательства со стороны прикладных программ и независимо от того, как прикладная программа собирается реагировать на возникшие события. Один из способов обработки событий в прикладной программе схематически показан на рис. 3.10. Прикладная программа может проанализировать информацию о событии, которое стоит в очереди первым на обработку, или, если очередь пуста,
перейти в состояние ожидания возникновения события. После извлечения из очереди информации о событии прикладная программа определяет его тип и решает, что делать дальше. Именно такой метод используется в API графических систем GKS и PHIGS.
, Ожидание
Процесс IПроцесс съема |^ ОчередьI
синхронизации Iпоказаний |событий IПрограмма I
■ ■■ ■ Сигнал |Показания I Событие - *
синхронизации
Рис. 3.10. Режим ввода с обработкой событий
Другой подход— связать с каждым типом событий функцию с обратным вызовом {callback). Мы будем следовать именно такому подходу, поскольку он используется в подавляющем большинстве современных операционных систем и доказал свою эффективность в системах с архитектурой “клиент/сервер”.
Клиенты и серверы
До сих пор, рассматривая процесс ввода информации, мы отстранялись от всех прочих процессов, происходящих в операционной среде. Графическая система представлялась этаким монолитным блоком, который имеет ограниченные возможности коммуникации с внешним миром, помимо тех устройств ввода, управление которыми тщательно спланировано в прикладной программе, и, конечно же, графического дисплея. Эта картина разительно изменилась с появлением сетевых многопользовательских систем. Теперь даже автономная система, рассчитанная на работу с отдельным пользователем, организуется таким образом, что ее программное обеспечение имеет ярко выраженное деление на компоненты клиентов и серверов, как в сетевой среде.
Чтобы графические приложения могли получить широкое распространение в современной компьютерной индустрии, они должны обладать способностью эффективно работать в распределенной вычислительной среде, организуемой на базе локальных (а теперь уже и глобальных) вычислительных сетей. В такой среде основными структурными компонентами системы — ее строительными блоками — являются серверы и клиенты. Серверы и клиенты могут быть распределены между разными компонентами аппаратной части системы, как это показано схематически на рис. 3.11, а могут “сосуществовать” и на одном и том же компьютере. Многим хорошо знаком сервер печати, который позволяет всем пользователям сети пользоваться единственным устройством высококачественной печати. Другие, может быть, менее распространенные примеры — вычислительный сервер, реализованный на суперкомпьютере, доступный прикладным программам, выполняемым на отдельных, менее мощных компьютерах рабочих станций, файл-сервер, позволяющий множеству пользователей совместно использовать (а иногда и пополнять) информацию из определенных файлов, и терминальные серверы, которые заняты обслуживанием запросов, поступающих по телефонным каналам. Те пользователи и прикладные программы, которые пользуются услугами таких серверов, называются клиентами или клиентскими программами.
В этой ясной и четкой структуре рабочим станциям трудно сразу отвести определенную роль. В принципе, рабочая станция может быть и клиентом, и сервером, и даже более, параллельно выполнять и клиентские программы, и серверные.
Представленная здесь модель реализована операционной системой X Window. В дальнейшем мы будем широко пользоваться терминологией, заимствованной из документации этой системы, которая сейчас повсеместно используется и в других операционных системах и хорошо согласуется с графическими приложениями.
Рабочая станция, оснащенная растровым дисплеем, клавиатурой, устройством указания, таким как мышь, является графическим сервером. Такой сервер может взять на себя
оказание “услуг” по выводу информации на экран дисплея или по вводу данных с помощью клавиатуры и устройства указания. На эти услуги может рассчитывать любой клиент, подключенный к сети.
Рис. 3.11. Сетевая вычислительная среда
Те прикладные программы на OpenGL, которые мы рассматривали ранее (и будем рассматривать впредь), являются, по сути, клиентскими программами, пользующимися “услугами” графического сервера. Внутри автономно функционирующего компьютера это разделение между прикладной программой и специальными графическими средствами кажется не столь уж и существенным, но в сетевой среде прикладная клиентская программа (в том числе и графическая) может выполняться на одном компьютере, а пользователь будет сидеть за экраном совершенно другого компьютера — графического сервера.
Дисплейный файл
С помощью дисплейных файлов (или дисплейных списков) организуется эффективное взаимодействие между графическими клиентами и серверами в сетевой среде. История появления дисплейных файлов уходит корнями в “седую древность” (конечно, по масштабам компьютерной эры). Как уже упоминалось в главе 1, в структуре ранних графических систем использовался компьютер общего назначения (сейчас для такого компьютера используется термин главный компьютер, или хост, а тогда подавляющее большинство компьютеров были “главными”), связанный через блоки цифро-аналоговых преобразователей с ЭЛТ (рис. 3.12). Компьютер передавал на преобразователи графические данные (координаты последовательных точек вычерчиваемых на экране линий) с частотой регенерации изображения. В те времена (начало 1960-х годов) вычислительная мощность компьютеров была невелика (по современным меркам), а сами компьютеры дороги, а потому использовать графические приложения могли позволить себе только весьма состоятельные клиенты.
Рис. 3.12. Простейший вариант архитектуры графической системыРис. 3.12. Простейший вариант архитектуры графической системыГлавный компьютерГлавный компьютерРешение этой проблемы нашли, воспользовавшись советом Юлия Цезаря, — “Разделяй и властвуй" (в этом мире новое — это хорошо забытое старое). Были разработаны специализированные компьютеры — дисплейные процессоры, — организация которых схематически представлена на рис. 3.13. Набор команд дисплейного процессора был ограничен, причем большинство команд было ориентировано на вычерчивание графических примитивов на экране ЭЛТ. Прикладная программа выполнялась на главном компьютере и результатом ее выполнения был дис
Рис. 3.12. Простейший вариант архитектуры графической системы
Рис. 3.12. Простейший вариант архитектуры графической системы
Главный компьютер
Главный компьютер
Рис. 3.13. Архитектура графической системы с использованием дисплейного процессораРис. 3.13. Архитектура графической системы с использованием дисплейного процессораплейный файл — программа построения изображения в терминах команд дисплейного процессора. Этот дисплейный файл сохранялся в дисплейной памяти (функционально — это аналог современного буфера кадра, хотя в нем хранились не коды засветки пикселей, а команды, но описывали они единственный кадр изображения). Если в приложении не использовался активный диалог с пользователем, то, сформировав дисплейный файл, главный компьютер мог заняться чем-нибудь более важным (на то он и главный), а черную работу по регенерации изображения брал на себя дисплейный процессор. Поначалу дисплейный процессор был довольно примитивным, но потом в его состав начали включать специализированные аппаратные блоки выполнения процедур, требовавших большого объема вычислений, например тонирования. В результате через десяток лет вычислительная мощность специализированного дисплейного процессора превысила вычислительную мощность того компьютера, который когда-то “гордился” тем, что он главный.
Рис. 3.13. Архитектура графической системы с использованием дисплейного процессора
Рис. 3.13. Архитектура графической системы с использованием дисплейного процессора
Теперь то, что раньше именовалось дисплейным процессором, называется графическим сервером, а прикладная программа, ранее выполнявшаяся на главном компьютере, стала клиентской программой. Основной проблемой теперь является не обеспечение регенерации изображения с необходимой частотой, а перегрузка сети при передаче информации между клиентами и серверами. Попутно отмечу, что в высококачественных графических серверах используются специализированные аппаратные средства (как для формирования изображения, так и для взаимодействия с пользователем).
Организовать работу с дисплейным файлом можно двумя способами. Можно переслать на графический сервер полное описание изображения — координаты вершин, атрибуты, типы примитивов и дополнительную информацию о визуализации сцены. Напомню, что мы договорились использовать в работе режим немедленного отображения (immediate mode), при котором как только программа выполняет оператор формирования примитива, последний передается на сервер для отображения и в памяти о нем не остается никакой информации. Для
повторного отображения примитива (того же самого, в том же месте экрана) после очистки поля изображения или после внесения некоторых корректив в модель сцены придется заново его сформировать и переслать описание на сервер. Если приложение предназначено для работы со сложными графическими объектами (например, моделью здания), причем пользователь активно “вмешивается” в процесс, объем информации, передаваемой от клиента серверу, может оказаться достаточно большим и привести к “заторам” в сети.
Второй способ предполагает использование дисплейного файла (теперь чаще используется термин дисплейный список, поскольку этот файл на диске не сохраняется). Такой способ часто называют режимом отображения с сохранением (retained-mode), поскольку при работе в этом режиме объект определяется однократно, а его описание помешается в дисплейный список, который сохраняется на сервере. Вывести его на экран можно вызовом специальной функции со стороны программы-клиента. В результате, во-первых, снижается объем информации, передаваемой от клиента серверу, во-вторых, в дисплейном списке описания объектов представлены таким образом, что появляется возможность использовать для их отображения специализированные аппаратные средства, которыми оснащен графический сервер. Оказывается, что оптимальная структура графической системы должна включать высокопроизводительный компьютер общего назначения, способный быстро выполнять обработку числовой информации (на него возлагается выполнение прикладной программы), и специализированный графический компьютер, оснащенный аппаратными средствами формирования изображений, в том числе и средствами тонирования с учетом освещения и т.д. Короче говоря, от чего в начале 1970-х годов начали уходить, к тому сейчас опять вернулись, правда, в новом исполнении.
В концепции использования дисплейного списка имеются и определенные недостатки. Для хранения списка требуется память на сервере и, кроме того, на его формирование и обработку тратится определенное время. Но эти дополнительные затраты окупаются высокой эффективностью специализированной аппаратуры преобразования дисплейного списка в изображение.
Формирование дисплейного списка и преобразование его в изображение
Для работы с дисплейными списками необходимо иметь в своем распоряжении средства создания списка и включения в него нужной информации. Механизм доступа к элементам списка должен быть достаточно гибким и обеспечивать прикладной программе значительную свободу. В составе OpenGL имеется небольшой набор функций манипулирования содержимым дисплейного списка, и ниже на примерах будет показано, как ими пользоваться.
Дисплейные списки формируются по той же схеме, что и графические примитивы. Список открывается функцией glNewList(), а закрывается функцией glEndList(). Операторы между этими двумя вызовами формируют содержимое списка. Каждый дисплейный список имеет уникальный идентификатор — целое число, которое определяется с помощью директивы макроопределения Idefine языка С. Например, приведенный ниже фрагмент программы формирует дисплейный список, в котором имеется один объект— красный квадрат. Этот фрагмент практически идентичен фрагменту, приведенному в главе 2, но на сей раз информация не отправляется сразу же на отображение, а помешается в список.
Idefine BOX 1 /* Или любое другое ранее не
использованное целое число */
glNewList(BOX, GL_COMPILE);
glBegin(GL_POLYGON);
glColor3f(1.0, 0.0, 0.0);
glVertex2f(-1.0, -1.0);
glVertex2f( 1.0, -1.0);
glVertex2f( 1.0, 1.0);
glVertex2f(-1.0, 1.0);
glEnd();
glEndList();
Флаг GL_COMPILE настраивает такой режим работы графической системы, что список пересылается на сервер, но его содержимое на экран не выводится. Если желательно сразу же вывести изображение на экран, то вторым аргументом функции glNewList() должен быть флагGL_COMPILE_AND_EXECUTE.
Как только возникнет необходимость вывести изображение этого квадрата на экран, нужно вызвать функцию glCallList (), передав ей в качестве аргумента идентификатор списка, в данном случае — BOX:
glCallList(BOX);
Как и по отношению к другим функциям построения примитивов на экране, к дисплейному списку применяются переменные текущего состояния исполнительной системы OpenGL, которые задают необходимые преобразования. Если,' например, изменить матрицы проективного преобразования и/или вида между двумя последовательными вызовами функции glCallList(), то изображение объекта появится в другом месте экрана (а может, и вообще исчезнет). Как выполняется управление матрицей проективного преобразования, показано в приведенном ниже фрагменте программы.
glMatrixMode(GL_PROJECTION); for(i= 1 ; i<5; i++) {
glLoad!dentity();
gluOrtho2D(-2.0*i, 2.0*i, -2.0*i, 2.0*i);
glCallList(BOX);
}
При каждом вызове функции glCallList () квадрат будет повторно выводиться на экран с учетом новых параметров отсекающего прямоугольника.
В последующих главах я познакомлю вас с матрицами преобразований, которые позволяют использовать дисплейный список для моделирования динамических сцен. Но учтите, поскольку имеется возможность манипулировать дисплейным списком, нужно быть предельно внимательным при программировании такого рода процедур, иначе потом могут появиться совершенно нежелательные и часто непредсказуемые эффекты. Например, в приведенном выше дисплейном списке текущий цвет изменяется на красный. После этого все объекты будут выводиться на экран красным цветом до тех пор, пока в каком-либо другом списке не встретится функция, устанавливающая другой текущий цвет. Проше всего “предохраниться” от подобного эффекта с помощью стека матриц и атрибутов, который поддерживается пакетом OpenGL. Стек— это структура данных, в которой элемент, включенный в структуру последним, извлекается из нее первым. Таким образом, можно поместить в вершину соответствующего стека (иногда говорят “затолкать в стек”) текущее значение используемого атрибута или матрицы, а затем восстановить его, извлекая из стека это значение (одновременно оно удаляется из стека). Поэтому стандартная последовательность операций, обеспечивающая сохранение-восстановление текущих параметров состояния, предусматривает сначала сохранение их в соответствующих стеках, за-
тем изменение текущего значения, заполнение дисплейного списка примитивами, а затем восстановление значений параметров— извлечение их из стеков. Поэтому в начале процедуры формирования стека вы часто увидите вызовы функций
glPushAttrib(GL_ALL_ATTRIB_BITS); glPushMatrix();
В конце процедуры в таком случае должны присутствовать вызовы функций glPopAttrib(); glPopMatrix();
При описании методов построения иерархических графических моделей в главе 8 мы будем очень часто обращаться к стекам матриц и атрибутов.
В составе OpenGL есть еще несколько функций, облегчающих работу с дисплейным списком. Когда приходится работать со множеством дисплейных списков, то для формирования последовательных значений идентификаторов списков можно воспользоваться функцией glGenLists^количеством, которая возвращает первое целочисленное значение в последовательности из <количество>, еще не использованное в программе в качестве идентификатора. Функция glCallLists () позволяет запустить процесс отображения сразу нескольких дисплейных списков. Очень хорошим примером использования возможностей, которые открывают дисплейные списки, является формирование текста. В следующем разделе будет подробно описана технология формирования текста с помощью средств OpenGL, но вы можете при чтении этой главы и опустить этот материал, а вернуться к нему позже. Однако хочу обратить ваше внимание на то, что описанная методика демонстрирует, какими гибкими возможностями обладает API и как это облегчает прикладному программисту решение множества проблем. К теме дисплейных списков мы вернемся еще раз в главе 8, когда будем обсуждать использование графических приложений в сети World Wide Web.
Дисплейные списки и формирование текста
В главе 2 были описаны два способа формирования текстовых символов — растровый и штриховой. Независимо от того, какой из этих методов выбран, нам понадобится определенная программа, описывающая весь набор символов определенного шрифта. Предположим, что мы остановились на растровом шрифте, в котором каждый символ должен быть описан
матрицей битов размером 12x10. В результате для хранения каждого символа нужно выделить память объемом 15 байт. Проще всего формировать изображение строки, отсылая на
а) Input Outputа) Input OutputРис. 3.14. Штриховые символы: а — .многоугольники с заливкой; б —укрупненное изображение контуровРис. 3.14. Штриховые символы: а — .многоугольники с заливкой; б —укрупненное изображение контуровсервер матрицу битов каждого очередного символа в порядке их появления в строке. Таким образом, придется отсылать по 15 байт на каждый символ строки. Если же сформировать штриховой шрифт, состоящий из отрезков, то на каждый символ “уйдет” разное количество отрезков и соответственно потребуется разный объем памяти. Можно пойти дальше и сформировать шрифт из многоугольников с заливкой, как показано на рис. 3.14. Для буквы “1” много труда не потребуется, но чтобы получить плавное изображение буквы “О”, придется аппроксимировать ее контур множеством коротких отрезков. В среднем на полный комплект букв такого шрифта потребуется гораздо больше памяти, чем 15 байт на символ. Если приложение должно выводить на экран достаточно длинный текст, то на сервер придется пересылать очень много информации, что приведет к зна-
а) Input Output
а) Input Output
Рис. 3.14. Штриховые символы: а — .многоугольники с заливкой; б —укрупненное изображение контуров
Рис. 3.14. Штриховые символы: а — .многоугольники с заливкой; б —укрупненное изображение контуров
чительной загрузке сети.
что такая технолочто такая технолоВсе, что говорилось выше, должно подвести вас к заключению о том,
что такая техноло
что такая техноло
гия вывода текста в приложении “клиент/сервер” неэффективна. Более рационально исполь-
зовать дисплейные списки (по одному на каждый символ) и сохранить таким образом описание шрифта на сервере. Это очень похоже на использование стандартных растровых матриц символов в алфавитно-цифровых терминалах. Весь набор символов сохраняется в ПЗУ терминала, а затем каждый символ выводится на экран при получении его ASCII-кода— одного байта. Отличие заключается только в качестве получаемого изображения и количестве шрифтов. Ничто не мешает нам определить столько шрифтов, сколько сможет хранить память сервера, а затем использовать описание каждого символа, составленного из отрезков, как любой другой графический объект, и применять по отношению к нему любые преобразования — масштабировать, поворачивать или смещать.
Таким образом, основная идея — передать на сервер описание всех символов шрифта, а затем запрашивать отображение нужных символов, передавая всего один байт, — демонстрирует, насколько эффективным может оказаться использование дисплейных списков в OpenGL. Процедура, по сути, та же, что и при работе с растровыми шрифтами.
Сначала нужно сформировать множество из 96 воспроизводимых ASCII-символов или расширенный набор из 256 символов, если предполагается использовать приложение не только в англоязычных странах.
Определим функцию OurFont(char с), которая будет вычерчивать любой ASCII-символ с. Эта функция в общих чертах имеет следующий вид:
void OurFont(char с)
{
switch(c)
{ case 'а':
break;
case 'A':
break;
}
}
В каждом блоке case нужно позаботиться о размещении символа на экране — каждый очередной символ должен выводиться на определенном расстоянии справа (а иногда и слева) от предыдущего символа. Для перемещения изображения можно воспользоваться функцией glTranslate(). Пусть, например, у нас есть описание буквы “О” в виде дисплейного списка и желательно вписать ее изображение в единичный квадрат. Соответствующий фрагмент функции OurFont() будет выглядеть следующим образом:
case 'О':
glTranslatef(0.5, 0.5, 0.0); /* Передвинуть в центр */ glBegin(GL_QUAD_STRIP);
for (i=0; i<=12; i++)/* 12 вершин */
{
angle = 3.14159/6.0*i;/* 30 градусов в радианах */
glVertex2f(0.4*cos(angle), 0.4*sin(angle));
glVertex2f(0.5*cos(angle), 0.5*sin(angle));
}
glEnd();
glTranslatef(0.5, -0.5, 0.0); /* Передвинуть в правый нижний угол */
break;
В этом фрагменте окружность (точнее, две концентрические окружности) аппроксимируется 12 четырехугольниками (полосой из 12 участков). Каждый прямоугольник будет залит в соответствии с текущими значениями атрибутов. Поскольку мы еще не рассматривали методы выполнения геометрических преобразований — это материал главы 4, — придется объяснить суть используемых в этом фрагменте формул. Все преобразования выполняются с двухмерными объектами. Следовательно, каждый из них определен на плоскости z=0 и можно использовать любую систему координат для его описания. Предполагается, что каждый объект (символ шрифта, включая и межсимвольный интервал) вписан в прямоугольник. Обычно отображение строки символов начинается с левого нижнего угла первого из них. Следующий символ размешается таким образом, что его левый нижний угол совпадает с правым нижним углом предыдущего символа.
Первый вызов функции glTranslatef () в приведенном фрагменте задает смещение в
центр прямоугольника, который в данном случае совпадает с центром окружности. Можно
Рис. 3.15. Вычерчивание буквы “О”'Рис. 3.15. Вычерчивание буквы “О”'считать, что функция glTranslatef () сдвигает начало текущей системы координат, т.е. системы, в которой будут отсчитываться координаты всех последующих графических объектов. Затем формируются вершины, расположенные вдоль двух концентрических окружностей, одна из которых имеет радиус 0.5, а вторая— 0.4 (рис. 3.15). После того как таким способом будут сформированы 12 четырехугольников, начало системы координат переносится в правый нижний угол. В результате все готово к вычерчиванию следующего символа, начиная с правого нижнего угла только что вычерченного. Обратите внимание— в этом примере нам не пришлось обращаться к стекам матриц и атрибутов. Другие символы формируются по этой же схеме.
Рис. 3.15. Вычерчивание буквы “О”'
Рис. 3.15. Вычерчивание буквы “О”'
Хотя приведенный фрагмент программы выглядит и не очень элегантно, его эффективность не должна нас особенно заботить,
поскольку символы формируются только один раз в течение сеанса работы приложения. Сформи
рованные символы пересылаются на сервер в виде скомпилированного дисплейного списка.
Предположим, что нам необходимо сформировать расширенный шрифт из 256 символов. Соответствующая программа, в которой используется функция OurFont(), будет выглядеть
следующим образом:
base = glGenLists(256); /* Возвращает индекс первого из
256 доступных идентификаторов */ for(i=0; i<256; i++)
{
glNewList(base + i, GL_COMPILE);
OurFont(i);
glEndList();
Когда понадобится использовать этот список для вычерчивания отдельных символов, можно будет не смешать каждый последующий вызов относительно base, а просто установить базу смещения:
glListBase(base);
Собственно вывод некоторой текстовой строки производится самим сервером при вызове функции glCallLists ():
char *text_string;
glCallLists( (GLint) strlen(text_string), GL_BYTE, text_string);
В этом вызове используется функция strlen() из стандартной библиотеки языка С, которая определяет длину строки text_string. Первый аргумент функции glCallLists () задает количество отображаемых дисплейных списков. Третий аргумент — указатель на массив, элементы которого имеют тип, заданный вторым аргументом. Идентификатор к-го дисплейного списка, который подлежит отображению, формируется как сумма базы списка, установленной функцией glListBase() и значением Л-го символа в строке (массиве символов text string).
Шрифты библиотеки GLUT
Конечно, любой программист предпочитает использовать уже разработанные ранее шрифты, а не заниматься созданием собственных, если в этом нет особой необходимости. В состав библиотеки GLUT входит несколько растровых и штриховых шрифтов. Для того чтобы вывести с их помощью текст на экран, не обязательно использовать механизм работы с дисплейными списками, но в последнем примере этой главы мы все-таки создадим дисплейный список, который будет содержать один из шрифтов библиотеки GLUT. Для вывода на экран отдельного символа этого моноширинного шрифта нужно вызвать функцию glutStrokeCharacter():
glutStrokeCharacter(GLUT_STROKE_MONO_ROMAN, int character);
Константа GLUT_STROKE ROMAN задает режим вывода символов пропорционального шрифта. Процедуры манипулирования такими шрифтами нужно тщательно продумывать. Размер символов (не более 120 единиц) может не соответствовать размерам остальных элементов изображения, а потому при выводе текста нужно использовать масштабирование. Обычно управление положением символов выполняется смещением перед вызовом функции вывода символов на экран. Кроме того, каждый повторный вызов функции glutStrokeCharacter () приводит к дополнительному сдвигу по горизонтали на ширину символа и таким образом подготавливает позицию для вывода следующего символа надписи. Параметры масштабирования и смешения являются составляющими текущего состояния исполнительной системы OpenGL, поэтому при их изменении нужно не забывать о сохранении и восстановлении текущего состояния с помощью стека матриц (функции glPushMatrix() и glPopMatrix()). В противном случае могут возникнуть совершенно нежелательные побочные эффекты.
Символы растровых шрифтов выводятся с помощью функций библиотеки GLUT аналогичным способом. Например, для вывода на экран одиночного символа размером 8x13 нужно вызвать функцию glutBitmapCharacter():
glutBitmapCharacter(GLUT_BITMAP_8_BY_13, int character);
Растровые символы позиционируются на экране значительно проще, чем штриховые, поскольку “образ” такого символа прямо переносится в буфер кадра и, в отличие от штрихового символа, не подвергается никаким геометрическим преобразованиям. Исполнительная система OpenGL в качестве одного из параметров текущего состояния хранит и позицию растра (raster position). Этот параметр указывает позицию вывода образа очередного растрового примитива. Его значение устанавливается функцией glRasterPos*(). Как правило, после вы-
вода образа очередного символа функцией glutBitmapCharacter() прикладная программа смещает позицию растра на один символ вправо. Это изменение не влияет на последующее тонирование геометрических примитивов. Если символы имеют разную ширину, нужно воспользоваться функцией glutBitmapWidth(font, char), которая возвращает ширину заданного символа указанного шрифта. Таким образом, фрагмент прикладной программы, который организует вывод символа некоторой надписи, обычно имеет вид
glRasterPos2i(rx, гу);
glutBitmapCharacter(GLUT BITMAP_8_BY_13, k);
rx+=glutBitmapWidth(GLUT~BITMAP_8_BY_13, k);
Далее в этой главе будет рассмотрена программа, в которой надпись выводится растровым шрифтом с помощью дисплейного списка.
Организация прикладной программы на базе использования дисплейных списков требует особого внимания при программировании, и мы еще вернемся к этому вопросу в главе 8, когда будем рассматривать иерархические графические модели. Но сейчас нас больше интересует ясность и наглядность программы, хотя это и может пойти в ущерб ее производительности. Поэтому в большинстве примеров в последующих главах дисплейные списки применяться не будут. В принципе, большую часть приведенных текстов программ достаточно легко использовать и применительно к дисплейным спискам. Для этого требуется заключить фрагменты, формирующие изображение, в “процедурные скобки” — вызовы функций glNewList() иglEndList().
Программирование ввода, управляемого событиями
В этом разделе на множестве несложных примеров мы рассмотрим механизм обработки событий при вводе информации пользователем в прикладную программу. В основу программной реализации этого механизма положены функции с обратным вызовом, упомянутые в разделе 3.2. Будут рассмотрены типы событий, распознаваемые операционной системой, и для тех из них, которые представляют интерес для графических приложений, разработаны функции с обратным вызовом, отвечающие за реакцию приложения на эти события.
Использование устройств указания
Начнем обсуждение механизма обработки событий с модификации функции main() в программе построения узора Серпинского, рассмотренной в главе 2. В первой ее версии для вывода окна приложения на экран были использованы функции из библиотеки GLUT, а затем с помощью функции glutMainLoop() организован цикл ожидания событий. В теле цикла не было запрограммировано никаких операций. Даже прекращение работы программы выполнялось внешним по отношению к этому циклу механизмом. Этот недостаток первой версии программы был преодолен за счет использования устройства указания, которое позволяло прекратить выполнение программы, вызвав стандартную функцию прерывания выполнения exit() при нажатии определенной кнопки мыши.
Мы будем рассматривать только те события, которые распознаются библиотекой GLUT, хотя операционные системы, подобные X Window, распознают гораздо более широкий класс событий. Но в библиотеку GLUT включены средства распознавания только тех событий, которые являются общими для большинства существующих операционных систем и представляют интерес для разработчиков графических приложений. С устройством указания, в качестве которого чаще всего используется мышь, ассоциируются события двух типов. Событие типа перемещение возбуждается в том случае, если мышь перемещается при нажатой кнопке (любой из имеющихся на устройстве). Если мышь перемещается, но при этом никакая кнопка
не удерживается в нажатом состоянии, это событие рассматривается как пассивное перемещение. Положение мыши — показание этого устройства ввода — становится доступным прикладной программе после возникновения события типа перемещение. Другой тип события — событие мыши — возбуждается в случае, если пользователь нажал или отпустил любую из кнопок. Пока кнопка удерживается нажатой, никакие события не возбуждаются (если, конечно, мышь не перемешается) до тех пор, пока кнопка не будет отпущена. Передаваемая при этом информация — показания устройства — включает идентификатор кнопки, послужившей причиной возникновения события, ее состояние после возникновения события (нажата или отпущена) и положение маркера мыши в координатах экрана. Регистрация в операционной системе функции мыши с обратным вызовом обычно выполняется в теле функции main() через вызов функции библиотеки GLUT:
glutMouseFunc(mouse_callback_func)
Функция с обратным вызовом для мыши должна иметь формат
void mouse_callback_func(int button, int state, int x, int y);
В теле функции с обратным вызовом должны быть запрограммированы операции, которые необходимо выполнить в ответ на возникшее событие. Вариантов таких операций может быть достаточно много, в зависимости от того, какая кнопка вызвала появление события и в каком состоянии она оказалась после этого (нажата или отпущена). В данном простом примере нас интересует нажатие левой кнопки мыши, которое должно привести к прекращению выполнения прикладной программы. Таким образом, тело функции с обратным вызовом будет включать всего один оператор:
void mouse_callback_function(int button, int state, . int x, int y)
{
if(button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) exit();
}
Любое другое сочетание показаний события — отпускание левой кнопки или любые манипуляции с правой кнопкой — останутся без внимания со стороны прикладной программы, поскольку в функции с обратным вызовом никаких действий на этот случай не предусмотрено.
Следующий пример продемонстрирует достоинство той структуры программы, которая была описана в предыдущей главе. Разработаем программу, которая будет вычерчивать небольшой квадратик на экране в том месте, где установлен маркер мыши в момент нажатия левой кнопки. Нажатие средней кнопки мыши должно привести к завершению работы выполнения программы.
Функция main() новой программы будет выглядеть практически так же, как и аналогичная функция в предыдущем примере.
int main(int argc, char **argv) {
glutlnit(6argc,argv);
glut!nitDisplayMode(GLUT_SINGLE | GLUT_RGB);
glutCreateWindow("square");
myinit();
glutReshapeFunc(myReshape);
glutMouseFunc(mouse);
glutDisplayFunc(display);
glutMainLoop();
}
Событие перерисовки возбуждается операционной системой при любом изменении размеров или положения окна приложения, которое может возникнуть по требованию пользователя. Реакцию прикладной программы на это событие мы рассмотрим ниже. В этом примере нет нужды использовать функцию отображения с обратным вызовом, поскольку графические примитивы формируются только в ответ на щелчок кнопкой мыши. Но библиотека GLUT требует, чтобы каждое приложение содержало функцию отображения с обратным вызовом, а потому нам придется ее определить как пустую.
void display(){ }
На события мыши будет реагировать функция с обратным вызовом mouse(): void mouse(int btn, int state, int x, int y) {
if(btn==GLUT_LEFT_BUTTON &6 state==GLUT_DOWN) drawSquare(x,y);
if(btn==GLUT_MIDDLE_BUTTON && state==GLUT_DOWN) exit();
}
Поскольку графические примитивы формируются только в функции drawSquare(), значения атрибутов можно устанавливать в любом месте программы. Мы для этого будем использовать функцию myinit().
Нам потребуются три глобальные переменные. Размеры окна могут изменяться в процессе работы программы, а потому текущие значения соответствующих параметров должны быть доступны всем компонентам программы. В частности, они понадобятся функции перерисовки и функции вычерчивания квадратиков drawSquare (). Если бы нужно было вычерчивать квадратики переменного размера, то соответствующий параметр также следовало бы сделать глобально доступным. Программа инициализации устанавливает размеры отсекающего прямоугольника равными размерам окна приложения, которое будет сформировано в main(), а размеры видового окна задает таким образом, чтобы оно занимало всю область окна приложения. Цвет фона окна— черный. Обращаю ваше внимание на то, что установку параметров окна приложения и видового окна в данном случае можно и не включать в программу, поскольку мы фактически дублируем установки по умолчанию. Но я специально включаю в программу соответствующие операторы, чтобы потом можно было сравнить этот вариант программы с тем, который будет создан в следующем разделе.
/* Глобальные переменные */
GLsizei wh = 500, ww = 500; /* Исходные размеры окна */
GLfloat size =3.0;/* Половина длины стороны квадратика */
void myinit(void)
/* Установка параметров визуализации */
glviewport(0,0,ww,wh);
glMatrixMode(GL_PROJECTION);
glLoad!dentity();
gluOrtho2D(0.0, (GLdouble)ww, 0.0, (GLdouble)wh);
glMatrixMode(GL_MODELVIEW);
/* Очистить окно, цвет фона - черный */
glClearColor (0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
glFlush();
}
При разработке программы вычерчивания квадратиков нужно учитывать, что те координаты положения маркера мыши, которые передаются операционной системой при возбуждении события мыши, определены в системе координат окна приложения, начало которой находится в его верхнем левом углу. Следовательно, придется сначала скорректировать значение координаты у, используя для этого текущее значение высоты окна (глобальную переменную wh). Для вычерчивания будем использовать случайно выбранный цвет, воспользовавшись для этого стандартным генератором случайных чисел — функцией rand().
void drawSquare(int x, int y) {
y=wh-y;
glColor3ub((char)rand()%256, (char)rand()%256,
(char)rand()% 256);
glBegin(GL_POLYGON);
glVertex2f(x+size, y+size);
glVertex2f(x-size, y+size);
glVertex2f(x-size, y-size);
glVertex2f(x+size, y-size);
glEnd();
glFlush();
}
Остается только вставить в текст программы необходимые директивы #include, и получим программу, которая будет вполне удовлетворительно работать до тех пор, пока пользователь не попытается изменить размеры окна приложения.
События окна
Большинство современных операционных систем позволяет пользователю изменять размеры окон приложений и перемещать их по поверхности экрана. Эта процедура, как правило, выполняется с помощью мыши путем перетаскивания какого-либо из углов окна. При этом операционная система возбуждает событие, которое относится к группе события окна (window event). В прикладной программе должна присутствовать функция, которая будет реагировать на это событие. Продумывая реакцию приложения на изменение размеров окна, следует обратить внимание на следующие вопросы.
Следует ли перерисовать все объекты, которые были вычерчены в области окна перед тем, как его размеры изменились?
Как следует отреагировать на изменение соотношения сторон окна приложения?
Нужно ли после этого изменять размеры или атрибуты новых примитивов?
Однозначных ответов на эти вопросы не существует — здесь все зависит от специфики конкретного приложения. Если на экране изображена реальная сцена, то, скорее всего, следует сохранить нормальное соотношение сторон, чтобы изображение не оказалось искаженным. Но при этом может оказаться так, что часть области окна будет не использована или часть сцены не “впишется” в окно измененного размера. Если при изменении размеров окна нужно воспроизвести в нем все ранее вычерченные объекты, то необходим какой-то механизм сохранения информации об этих объектах и их повторной перерисовки. Чаще всего это выполняется в теле единственной функции, например функции display (), которую мы уже использовали в программе из главы 2. Эта функция регистрируется в качестве функции отображения с обратным вызовом. Но в данном примере этот механизм не подходит, поскольку было решено, что программа будет вычерчивать квадратики только в ответ на действия пользователя в интерактивном режиме.
В этой программе все квадратики должны иметь один и тот же размер, который не зависит от размеров и формы (соотношения сторон) окна приложения. При каждом изменении размеров окна приложения оно очищается, и мы фактически начинаем “с чистого листа”. В качестве показаний событие перерисовки передает в прикладную программу высоту и ширину нового окна. Эти параметры используются для переопределения прямоугольника отсечения с помощью функции gluOrtho2D() и нового видового окна с тем же соотношением сторон. Затем окно очищается, причем в качестве цвета фона устанавливается черный цвет. Таким образом, функция перерисовки с обратным вызовом будет иметь вид void myReshape(GLsizei w, GLsizei h) {
/* Настроить прямоугольник отсечения */
glMatrixMode(GL_PROJECTION);
glLoad!dentity();
gluOrtho2D(0.0, (GLdouble)w, 0.0, (GLdouble)h);
glMatrixMode(GL_MODELVIEW);
glLoad!dentity();
/* Настроить параметры видового окна и очистить его */
glviewport(0,0,w,h);
glClearColor (0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
glFlush();
}
Полный текст программы читатель найдет в приложении А.
Но предлагаемый вариант функции перерисовки не единственно возможный. Можно, например, изменять размеры вычерчиваемых в дальнейшем квадратиков в соответствии с новыми размерами окна приложения. Помимо события изменения размеров окна приложения, существуют и другие, связанные с перекомпоновкой окон на экране, — перемещение окна без изменения его размеров, перевод окна “на передний план” и т.д. Для всех таких событий, если в приложении планируется реагировать на них каким-то образом, нужно предусмотреть свои функции обработки с обратным вызовом, аналогичные myReshape(), или можно “положиться” на те функции по умолчанию, которыми обеспечивает приложение библиотека GLUT.
В разработанную программу можно ввести и еще одно усовершенствование — пусть она вычерчивает квадратики, пока пользователь будет перемешать мышь, удерживая кнопку нажатой. В этом случае необходимо определить в программе соответствующую функ-
цию с обратным вызовом и зарегистрировать ее в операционной системе с помощью функции glutMotionFunc(). В нашей программе уже есть такая функция — drawSquare(), а потому остается просто зарегистрировать ее в качестве функции с обратным вызовом: glutMotionFunc(drawSquare);
Теперь можно отслеживать на экране траекторию движения мыши, которая будет обрисовываться прямоугольной кистью.
События клавиатуры
В качестве одного из устройств ввода очень часто в графических приложениях используется и клавиатура. События клавиатуры возбуждаются в том случае, если на клавиатуре нажата одна из клавиш в тот момент, когда маркер мыши на экране находится в области окна приложения. В некоторых операционных системах событие клавиатуры возбуждается и при отпускании клавиши, но в библиотеке GLUT на этот случай функция регистрации обратного вызова не предусмотрена. При возбуждении события операционная система передает в прикладную программу ASCII-код нажатой клавиши и координату положения маркера мыши в окне приложения. Регистрация функции обработки события клавиатуры выполняется функцией glutKeyboardFunc():
glutKeyboardFunc(keyboard);
Если, например, в приложении с помощью клавиатуры планируется только сигнализировать программе о завершении работы, то функция обработки события клавиатуры будет иметь вид
void keyboard(unsigned char key, int x, int y) {
if(key=='q' || key == 'Q') exit();
}
Функции отображения и простоя
Из еще не рассмотренных функций с обратным вызовом две заслуживают особого внимания — это функция отображения и функция простоя {idleY Функция отображения упоминалась в главе 2. Она регистрируется в операционной системе с помощью функции glutDisplayFunc() из библиотеки типов GLUT:
glutDisplayFunc(display);
Функция, указанная в glutDisplayFunc(), будет вызываться каждый раз, когда потребуется перерисовка изображения. Одна из таких ситуаций— инициализация окна приложения. Поскольку заранее известно, что событие отображения возбуждается, когда открывается окно приложения, то в эту функцию желательно включить все операции вывода, не связанные с взаимодействием с пользователем. Исполнительная система GLUT требует, чтобы в каждом графическом приложении была определена функция отображения с обратным вызовом.
Функцию отображения можно использовать и в другом контексте, например при анимации, когда некоторые параметры, определяемые в приложении, изменяются. Библиотеку GLUT можно использовать для того, чтобы открыть несколько окон. В таком случае в число компонентов текущего состояния приложения включается и идентификатор текущего окна, и можно выводить объекты в разные окна, изменяя этот параметр. Можно также “свернуть” окно в пиктограмму (iconify). Следовательно, интерактивные приложения и программы анимации могут вызывать функцию отображения в разных ситуациях. Удобно выполнять эту процедуру косвенно через функцию glutPostDisplay() из библиотеки GLUT. Эта функция
включает определенную логику и предотвращает попытку вывода на экран, который не может быть реализован, например, если окно свернуто в пиктограмму.
Функция простоя с обратным вызовом (idle callback) вызывается в том случае, если программа не получает сообщений о каких-либо событиях. По умолчанию эта функция ничего не выполняет — ее тело не содержит никаких операторов, но программист может реализовать в этой функции какие-либо полезные для приложения операции. Типичный пример использования такой функции — формирование графических примитивов, которые выводятся на экран, пока в окружающем мире ничего не меняется (см. упр. 3.2). Функции отображения и простоя будут использованы в программе, которую мы рассмотрим в последних разделах этой главы.
В процессе выполнения можно переназначать функции с обратным выводом для обработки определенных событий. Можно вообще заблокировать обработку какого-либо события, передав в качестве аргумента соответствующей функции регистрации NULL.
Управление окнами
Библиотека GLUT поддерживает как работу с несколькими окнами в одном приложении, так и с дочерними окнами в пределах одного главного окна приложения. В приложении можно создать второе окно верхнего уровня (с заголовком second window) следующим оператором: id=glutCreateWindow("second window");
Значение переменной id можно в дальнейшем использовать для установки этого окна в качестве текущего и перенаправления в него всех последующих операторов графического вывода: glutSetWindow(id);
Во втором окне можно установить другие настройки параметров, обратившись к функции glutInitDisplayMode() перед вызовом glutCreateWindow(). Более того, каждое окно может иметь свой набор функций с обратным вызовом, поскольку функции регистрации имеют силу только по отношению к текущему окну.
Меню
В прикладной программе с помощью графических примитивов и функций обработки событий мыши можно создать собственные средства графического интерфейса. Например, можно сформировать ползунковый регулятор, изображенный на рис. 3.16. Для этого потребу
ется сформировать на экране изображения прямоугольников и вывести текст надписей шка
лы, а затем, обрабатывая события мыши, получать информацию о желаемом перемещении
ползунка регулятора. Конечно, программирование такого элемента управления — задача до
вольно утомительная, особенно если попытаться воспроизводить разного рода объемные эффекты изображения отдельных деталей механизма (если хочется, чтобы регулятор выглядел
Рис. 3.16. Ползунковый регуляторРис. 3.16. Ползунковый регуляторна экране, “как живой”). В большинстве операционных систем имеется свой набор элементов управления для графического интерфейса, но поскольку в этой книге мы не “привязываемся” к определенной операционной системе, то обсуждать специфику их использования не будем. Но, к счастью, в библиотеке GLUT имеются функции поддержки работы с всплывающими меню (pop-up menus), которые можно
Рис. 3.16. Ползунковый регулятор
Рис. 3.16. Ползунковый регулятор
использовать в приложениях, нуждающихся в интенсивном
диалоге с пользователем.
Работа с меню в прикладной программе включает несколько достаточно простых операций. Сначала нужно определить в программе пункты меню. Нужно связать меню с определенной кнопкой мыши. И последнее — нужно определить функцию с обратным вызовом для
обработки выбора каждого пункта меню. Ниже мы продемонстрируем, как это делается, на примере меню из трех пунктов. Выбор первого пункта будет завершать выполнение приложения, в выбор второго и третьего — изменять размеры квадратиков, вычерчиваемых функцией drawSquare(). Функцию обработки меню с обратным вызовом назовем demo_menu(). В функцию main() поместим вызовы функций, которые формируют меню и связывают его с правой кнопкой мыши:
glutCreateMenu(demo_menu);
glutAddMenuEntry("quit", 1);
glutAddMenuEntry("increase square size", 2);
glutAddMenuEntry("decrease square size", 3);
glutAttachMenu(GLUT_RIGHT_BUTTON);
Функция glutAddMenuEntry () формирует очередной пункт меню и ее второй аргумент — идентификатор, который будет передаваться функции обработки с обратным вызовом при выборе этого пункта. Текст функции обработки меню с обратным вызовом приведен ниже, void demo_menu(int id)'
{
if(id == 1) exit( );
else if (id == 2) size = 2 * size;
else if (size > 1) size = size/2; glutPostRedisplay( );
}
Обращение к функции glutPostResdisplay() запрашивает перерисовку изображения, которое выполняется той функцией отображения, которая зарегистрирована функцией glutDis- playFunc(). В результате после завершения выполнения demo_menu() на экране восстановится картинка без изображения меню.
Библиотека GLUT поддерживает работу и с иерархическими меню (рис. 3.17). Пусть, например, главное меню состоит только из двух пунктов. При выборе первого пункта выполнение программы завершается, а при выборе второго на экран выводится подменю. Это подменю содержит два пункта, позволяющих увеличивать или уменьшать размеры квадратиков, формируемых функцией drawSquare (). Для организации такого иерархического меню в функцию main() следует включить следующий фрагмент:
sub_menu = glutCreateMenu(size_menu);
glutAddMenuEntry("increase square size", 2);
glutAddMenuEntry("decrease square size", 3);
glutCreateMenu(top_menu);
glutAddMenuEntry("quit",1);
glutAddSubMenu("Resize", sub_menu);
glutAttachMenu(GLUT_RIGHT_BUTTON);
quitresizequitresizeФункции обработки size_menu() и top_menu() я предлагаю читателям разработать самостоятельно (см. упр. 3.5).
quit
resize
quit
resize
increase square size
Puc. 3.17. Иерархическое менюPuc. 3.17. Иерархическое менюdecrease square size
Puc. 3.17. Иерархическое меню
Puc. 3.17. Иерархическое меню
Указание объектов
Указание объектов (picking) — это одна из операций ввода, которая позволяет пользователю передать прикладной программе информацию о выборе того или иного из изображенных на экране объектов. Хотя при реализации этой операции и используется то же самое физическое устройство, что и для операции локализации позиции на экране, в информационном плане они значительно отличаются. В графических системах с растровым способом создания отображения реализация операции указания объектов связана со значительными сложностями.
Но так было не всегда. В системах с векторным способом вычерчивания дисплейный процессор получал сигнал указания от светового пера в тот момент, когда на экране физически вычерчивался выбранный объект, а потому селекция выполнялась по времени — достаточно было зафиксировать адрес объекта в дисплейном файле в момент получения прерывания от светового пера.
Одна из причин, усложняющих выполнение операции указания в современных системах, — конвейерный принцип обработки информации. Примитивы, определенные в пользовательской программе, затем проходят через несколько стадий преобразования, пока не будет сформировано их растровое разложение в буфере кадра. Хотя практически все преобразования обратимы в математическом смысле, эту обратимость сложно реализовать, поскольку преобразования выполняются аппаратно. Поэтому преобразование информации о положении на экране в информацию о графическом примитиве не может быть сведена к простейшим вычислениям. Кроме того, существует и проблема потенциальной неоднозначности такого преобразования (см. упр. 3.11 и 3.12).
Существуют два главных способа преодоления этих сложностей. Первый способ — се- лектирование объектов— предполагает настройку прямоугольника отсечения небольшого размера и видового окна так. что с их помощью можно отобрать то подмножество объектов, которые попадают в окрестность маркера устройства указания. Эти отобранные примитивы включаются в специальный список селекции (hit list), который далее анализируется прикладной программой.
Альтернативный подход — использовать при анализе прямоугольные оболочки объектов (bounding rectangles или extents). Прямоугольная оболочка (или просто оболочка)— это наименьший прямоугольник со сторонами, параллельными осям координат, который может быть описан вокруг объекта. Отобрать среди оболочек всех объектов те, которые содержат точку, указанную пользователем, не так сложно. Если в прикладной программе используется достаточно простая структура данных для сопоставления объектов в локальных или мировых системах координат с их оболочками в системе координат окна, то выбор соответствующих объектов можно возложить на прикладную программу. Мы продемонстрируем этот подход на практике в программе, которая будет рассмотрена в следующем разделе.
Значительно сложнее реализовать операцию указания объектов в том случае, если в графической системе используется иерархическая организация отображаемых объектов. В этом случае каждый отдельный объект является членом какого-либо подмножества объектов. Когда такой объект “захватывается” устройством, в прикладную программу нужно переслать список всех объектов этого подмножества.
Как правило, в графических API не специфицируется, как близко должна находиться указанная на экране точка к объекту, чтобы его можно было считать выбранным. Одна из причин такого “упущения” — стремление предоставить программистам самостоятельно реализовать подходящий способ отбора. Если, например, использовать подход, основанный на применении оболочек, то можно указывать любую точку на поле объекта. Следует учитывать, что и пользователи при выполнении операции указания также далеко не всегда располагают маркер устройства точно на объекте, особенно если это контурный объект.
Простая программа рисования
Все, что было сказано выше о функциях обработки с обратным вызовом, дисплейных списках и разработке интерактивных графических программ, мы продемонстрируем теперь на примере реальной программы. Программы рисования обладают множеством функциональных возможностей развитых программ автоматизированного проектирования. Практически всякая программа рисования должна иметь следующий набор функций.
Работа с геометрическими объектами, такими как отрезки прямых и многоугольники. Полагая, что объекты описываются множеством вершин, программа должна позволять вводить эти вершины в интерактивном режиме.
Возможность манипулировать пикселями и таким образом формировать изображение непосредственно в буфере кадра.
Управление атрибутами — цветом, типом линий и образцом заполнения.
Поддержка операций с меню для управления режимом работы приложения.
Корректное отслеживание перемещения окна приложения и изменение его размеров.
Всеми этими возможностями обладает программа, которую мы сейчас рассмотрим.
На рис. 3.18 показано, как выглядит окно программы сразу после начала сеанса. Пять прямоугольников в верхней части окна — это кнопки выбора одного из режимов рисования: Отрезок, Прямоугольник, Треугольник, Пиксель и Текст. Режим выбирается щелчком на поле соответствующего прямоугольника левой кнопкой мыши. После того как будет выбран режим Отрезок, следующими двумя щелчками левой кнопкой мыши пользователь может указать положение конечных точек отрезка. При этом указатель мыши должен находиться в рабочей области окна — вне зоны кнопок режима. После задания второй крайней точки программа выводит соответствующее изображение отрезка, причем он вычерчивается текущим цветом. По этой же схеме формируются изображения прямоугольников и треугольников. При вычерчивании прямоугольника задаются две точки на противоположных углах по диагонали, а при вычерчивании треугольника — три точки вершин. Новый режим можно выбрать после вычерчивания любого очередного объекта. В режиме Пиксель последовательные щелчки ле-
Рис. 3.18. Окно программы рисования в начале сеансаРис. 3.18. Окно программы рисования в начале сеансавой кнопкой мыши задают положение маленьких квадратиков, размер которых регулируется, начиная с двух пикселей, причем квадратики заливаются случайно выбранным цветом. В режиме Текст пользователь вводит текст с клавиатуры, который отображается, начиная с последней указанной в рабочем поле точки.
Рис. 3.18. Окно программы рисования в начале сеанса
Рис. 3.18. Окно программы рисования в начале сеанса
Два меню вызываются на экран после щелчка средней и правой кнопками мыши (рис. 3.19). То меню, которое вызывается правой кнопкой, позволяет пользователю очистить экран (команда Clear) или прекратить выполнение программы (команда Exit). Меню, которое вызывается средней кнопкой, имеет иерархическую структуру и включает три подменю. При выборе пункта Color в подменю основного меню можно указать новый текущий цвет. Выбор пункта Fill в основном меню позволяет задать в подменю режим заполнения (пункт Fill on) или контурного изоб-
ражения (пункт Fill off) прямоугольников и треугольников. Последний пункт основного меню, Pixel size, позволяет указать в подменю увеличение размеров формируемых квадратиков (пункт increase pixel size) или их уменьшение (пункт decrease pixel size).
Правая кнопкаПравая кнопкаСредняя кнопкаСредняя кнопкаРис. 3.19. Структура меню программы рисованияРис. 3.19. Структура меню программы рисования
Правая кнопка
Правая кнопка
Средняя кнопка
Средняя кнопка
Рис. 3.19. Структура меню программы рисования
Рис. 3.19. Структура меню программы рисования
Самое сложное при разработке программы — решить, как распределить задачи между отдельными компонентами (функциями). То решение, которое нашло отражение в приведенном ниже программном коде, согласуется с ранее рассмотренными примерами. Многие из функций дублируют рассмотренные ранее примеры. В программе определены следующие функции:
/* Обработка событий мыши */
void mouse(int btn, int state, int x, int y);
/* Обработка событий клавиатуры */ void key( unsigned char c, int x, int y);
void display(void);/* Отображение */
/* Формирование к»=црата co случайным цветом заливки */ void drawSquare(int x, int y);
/* Обработка изменения размеров окна */ void myReshape(GLsizei, GLsizei);
void myinit(void);/* Инициализация */
/* Построение квадрата заданного размера
в системе координат окна */ void screen_box(int х, int у, int s);
/* Обработка команд меню */
void right menu(int id);
void middle_menu(int id);
void color_menu(int id);
void pixel_menu(int id);
void fill_menu(int id);
/* Выбор режима рисования */ int pick(int x, int y);
Функция main() в этой программе также аналогична функциям main() в рассмотренных ранее примерах:
int main(int argc, char **argv) {
int c_menu, p_menu, f_menu;
glutlnit(6argc,argv);
glutlnitDisplayMode (GLUT_SINGLE | GLUT_RGB);
glutCreateWindow("paint");
glutDisplayFunc(display);
c_menu = glutCreateMenu(color_menu);
glutAddMenuEntry("Red", 1);
glutAddMenuEntry("Green", 2);
glutAddMenuEntry("Blue",3);
glutAddMenuEntry("Cyan", 4);
glutAddMenuEntry("Magenta",5);
glutAddMenuEntry("Yellow", 6);
glutAddMenuEntry("White", 7);
glutAddMenuEntry("Black", 8);
p_menu = glutCreateMenu(pixel_menu);
glutAddMenuEntry("increase pixel size", 1);
glutAddMenuEntry("decrease pixel size", 2);
f_menu = glutCreateMenu(fill_menu);
glutAddMenuEntry("fill on", 1);
glutAddMenuEntry("fill off", 2);
glutCreateMenu(right_menu);
glutAddMenuEntry("quit",1);
glutAddMenuEntry("clear",2);
glutAttachMenu(GLUT_RIGHT_BUTTON);
glutCreateMenu(middle_menu);
glutAddSubMenu("Colors", c_menu);
glutAddSubMenu("Pixel Size", p_menu);
glutAddSubMenu("Fill", f menu);
glutAttachMenu(GLUT_MIDDLE_BUTTON);
myinit ();
glutReshapeFunc (myReshape);
glutMouseFunc (mouse);
glutKeyboardFunc(key);
glutMainLoop();
}
Функция myinit () очищает экран, устанавливает значения глобальных переменных и формирует дисплейных список для отображения 128 символов.
void myinit(void) {
/* Установка шрифта в дисплейном списке */
int i;
base = glGenLists(128);
for(i=0;i<l28;i++) {
glNewList(base+i, GL_COMPILE);
glutBitmapCharacter(GLUT_BITMAP_9_BY_15, i);
/* Для установки штрихового шрифта раскомментируйте следующий оператор */
/*glutStrokeCharacter(GLUT_STROKE_ROMAN, 1); */
glEndList();
}
glListBase(base);
glViewport(0,0, ww, wh);
/* Настройка параметров отсекающего прямоугольника в
соответствии с размерами окна операционной системы X window.
Такая настройка позволяет избежать масштабирования
координат объектов при изменении размеров окна приложения. */
glMatrixMode(GL_PROJECTION);
glLoad!dentity();
glOrtho(0.0, (GLdouble)ww , 0.0,
(GLdouble)wh , -1.0, 1.0);
/* Настройка цвета фона окна и очистка окна приложения */
glClearColor (0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glFlush();
}
Функция display () очищает окно, устанавливает цвет фона и выводит изображения кнопок. Кнопки представляют собой прямоугольники со сторонами, соответствующими примерно 10% высоты и ширины окна. Такой способ вычерчивания обеспечивает изменение размеров кнопок при изменении размеров окна. На поле каждого прямоугольника затем вычерчиваются условные обозначения соответствующих функций в виде простейших фигур.
Функция myReShape() в этом примере дублирует рассмотренную в разделе 3.5.2 одноименную функцию с обратным вызовом.
Функция pick() выясняет, какой объект на экране выбран с помощью мыши, и работает в сочетании с функцией обработки события мыши mouse(). В OpenGL поддерживается метод выбора обобщенных объектов, который довольно сложен в реализации. Но если в некоторой задаче нас интересует только определенная прямоугольная область экрана, например занятая кнопкой, то проще разработать собственный метод выбора. Та процедура, которая представлена в данной программе, работает следующим образом. Как только возбуждается событие мыши, функция mouse () вызывает функцию pick() и с помощью последней выясняет, в какой части экрана расположен маркер мыши. Поскольку кнопки занимают на экране прямоугольные области, то не представляет особого труда выяснить, где находится маркер мы-
ши — на одной из кнопок или на рабочем поле окна. Эта информация возвращается функции mouse(), которая и берет на себя остальную работу. Поскольку область экрана, на которой выполнен щелчок мышью, определяется функцией pick(), то mouse(), анализируя текущий режим, либо выводит на экран соответствующий примитив, либо запоминает текущие координаты маркера мыши. Например, если пользователь выбрал режим построения треугольников, щелкнув перед этим на кнопке с изображением треугольника, программа должна запомнить положение первых двух вершин, а после указания третьей вершины построить треугольник. Соответствующий фрагмент текста функции mouse () представлен ниже.
case (TRIANGLE): /* Функция pick() определила ранее, что был выполнен щелчок на кнопке с изображением треугольника */ switch(count) /* Счетчик количества вершин */ {
case(O): /* Сохранить координаты первой вершины */
count++;
хр[0] = х;
ур[0] = у;
break;
case(l): /* Сохранить координаты второй вершины */
count++;
хр[1] = х;
ур[1] = у;
break;
case(2): /* Третья вершина. Вычертить треугольник */
if(fill) glBegin(GL_POLYGON);
else glBegin(GL LINE LOOP);.
glVertex2i(xp[0],wh-yp[0]);
glVertex2i(xp[l],wh-yp[l]);
glVertex2i(x,wh-y);
glEnd();
draw_mode=0; /* Сбросить переменную режима */
count=0;
Обратите внимание на то, что первым щелчком кнопкой мыши был выбран режим вычерчивания треугольников, а последующие щелчки подсчитываются и соответствующие координаты вершин сохраняются. Если же пользователь щелкнет на другой кнопке до того, как введет положение третьей вершины треугольника, выбирается новый режим и данные о предыдущих вершинах утрачиваются.
Функция key () заполняет надпись символами, которые пользователь вводит с клавиатуры, и выводит эту надпись на экран.
void key(unsigned char k, int xx, int yy)
{
if(draw_mode!=TEXT) return; /* Символы выводятся на экран до тех пор, пока не изменится режим. */
glRasterPos2i(rx,ry);
glCallList(k); /* Вывести на экран дисплейный список, соответствующий коду введенного символа к. */
rx+=glutBitmapWidth (GLUT_BITMAP_9_BY_15, к);
}
Типичный результат работы программы представлен на рис. 3.20. Хотя тот вариант программы рисования, который мы рассмотрели выше, достаточно прост и его возможности далеки от совершенства, функции программы при желании можно расширить, причем это выполняется без особого труда. Например, можно заменить растровый шрифт на штриховой, добавить возможность выбора шрифтов, настройку атрибутов (ширины и типа), предоставить пользователю возможность выбирать режим вычерчивания фигур (с заливкой или контурный), включить другие типы фигур — произвольные многоугольники, аппроксимированные круги, кривые и т.д. Однако обеспечить построение гладких кривых в программе рисования, подобно тому, как это
Рис. 3.20. Результат работы программы рисо- делается в коммерческих программных про- ваниядуктах, довольно сложно. Почему это так, вы
узнаете в следующем разделе.
Полный текст рассмотренной программы представлен в Приложении А.
Интерактивные программы анимации
Программы, которые мы рассматривали до сих пор, формировали статическое изображение, т.е. выведенные на экран объекты нельзя было изменять. Но для пользователя гораздо больший интерес представляют динамические картинки, в которых отдельные объекты могут двигаться или изменяться в ответ на команды пользователя (или по ходу какого-нибудь внешнего по отношению к программе процесса). Например, можно “оживить” символы надписи и заставить их двигаться по экрану или показать, как меняется изображение некоторой сцены при перемещении наблюдателя. Ниже мы рассмотрим, как организовать в программе анимацию на примере простого вращающегося объекта.
Вращающийся квадрат
Рассмотрим двухмерную точку, описываемую парой координат:
X = COS0, у = sin0.
Эта точка при изменении значения угла 0 будет перемешаться вдоль окружности единичного радиуса. Три точки (-sin0, cos0), (-cos0, -sin0) и (sin0, -cos0) также лежат на окружности единичного радиуса и вместе с первой образуют вершины квадрата, вписанного в эту окружность (рис. 3.21). Стороны этого квадрата имеют длину V2 .
Рис. 3.21. Квадрат, вписанный в окружность единичного радиусаРис. 3.21. Квадрат, вписанный в окружность единичного радиусаПолагая, что значение 0 является глобальной переменной, можно вычертить этот квадрат с помощью следующей функции:
Рис. 3.21. Квадрат, вписанный в окружность единичного радиуса
Рис. 3.21. Квадрат, вписанный в окружность единичного радиуса
void display()
{
glClear();
glBegin(GL_POLYGON);
/* Преобразование градусов в радианы */
thetar = theta/((2*3.14159)7360.0);
glVertex2f(cos(thetar), sin(thetar));
glVertex2f(-sin(thetar), cos(thetar));
glVertex2f(-cos(thetar), -sin(thetar));
glVertex2f(sin(thetar), -cos(thetar));
glEnd();
}
Приведенная функция display() будет формировать изображение квадрата при любом заданном значении 0, но при этом предполагается, что значение 9 изменяется по ходу выполнения программы и в результате на экране появляется изображение вращающегося квадрата. Для этого нужно время от времени вызывать с помощью функции glutPostRedisplay(), которая входит в библиотеку GLUT, функцию display ().
Теперь организуем приращение угла 9 на фиксированное значение в то время, когда программа не получает сообщения ни о каких других событиях (находится в состоянии “простоя”). Это выполняется с помощью функции обработки простоя, которая регистрируется функцией glut!dleFunc() из библиотеки GLUT:
glutldleFunc(idle);
Функция idle () имеет следующий вид:
void idle()
{
theta+=2; /* или какое-либо другое приращение */
if(theta >= 360.0) theta-=360.0;
glutPostRedisplay();
}
Такая программа будет работать, но желательно было бы хоть как-то управлять этим процессом. Самый простой вариант— предоставить пользователю возможность “включать” и “выключать” вращение. Для этого потребуется включить в программу функцию обработки события мыши, в которой будет выполняться изменение режима работы функции обработки простоя. Функция обработки события мыши регистрируется следующим образом: glutMouseFunc(mouse);
Текст самой функции mouse () представлен ниже:
void mouse(int button, int state, int x, int y) {
if(button==GLUT_LEFT && state==GLUT_DOWN) glutldleFunc(idle);
if(button==GLUT_MIDDLE && state=GLUT_DOWN) glutldleFunc(NULL);
}
Таким образом, щелчок левой кнопкой мыши запускает вращение квадрата, а щелчок средней кнопкой— прекращает его. Если запустить программу на выполнение, то, скорее всего, выводимое изображение не создаст у вас иллюзию вращающегося квадрата, а вы уви-
дите фрагменты квадрата, изменяющиеся с течением времени. Что же в этой программе сделано неправильно? Этим вопросом мы и займемся в дальнейшем.
Двойная буферизация
При выводе изображения нужно, чтобы частота обновления была достаточно высокой и зритель не смог заметить, как экран очищается и как на нем формируется новое изображение. Эта частота должна превышать 50 Гц, тогда зритель не заметит мелькания изображения на экране. В графических компьютерных системах это требование касается прежде всего частоты регенерации изображения, т.е. быстродействия буфера кадра. Если содержимое буфера не изменяется и обеспечена частота регенерации свыше 50 Гц (а лучше где-то около 85 Гц), для зрителя будут созданы необходимые комфортные условия восприятия изображения. Но если содержимое буфера изменяется, то зритель может увидеть совершенно нежелательные эффекты, например заметить “дерганье” последовательных фаз изменяющейся картинки.
Это явление наблюдается особенно отчетливо, если изображение достаточно сложное и очередную его фазу не удается сформировать за один период регенерации. В этом случае зритель увидит половину кадра с новой фазой движения, а половину — с прежней. Изображение перемещающегося объекта будет при этом искажено на экране. То же самое происходит и с последовательными процессами очистки экрана и обрисовки нового изображения, как это имеет место в нашем примере с вращающимся квадратом. Хотя квадрат — это довольно простой объект, который формируется за время, меньшее периода регенерации, в нашей программе отсутствует связь между процессом формирования объекта в буфере и выводом содержимого буфера на экран аппаратными средствами. Таким образом, вполне вероятно, что на экран будет отправлено содержимое буфера, в котором новый квадрат сформирован только частично.
Эту проблему можно решить с помощью двойной буферизации (double buffering). Предположим, что в нашем распоряжении есть два буфера кадра, которые принято называть рабочим (front buffer) и фоновым (back buffer). Рабочий буфер — это тот, из которого выполняется регенерация изображения на экране, а в фоновом изображение формируется программой. По командам от прикладной программы можно переключать функции буферов: сделать рабочим тот, который ранее был фоновым, а фоновым — тот, который ранее был рабочим. При переключении вызывается функция display (), в результате чего на экран начинает автоматически выводиться изображение, подготовленное ранее в фоновом буфере, а новое изображение можно формировать в том буфере, который теперь стал фоновым. Механизм двойной буферизации устанавливается в режиме инициализации аргументом функции glut!nitDisplayMode(). Вместо константы GLUT_SINGLE нужно задать в дизъюнкции флагов константу GLUT_DOUBLE. Переключение функций буферов выполняется функцией glutSwapBuffers(), которая входит в состав библиотеки GLUT. Если формируется довольно сложное изображение, требующее много времени, программа может спокойно работать с фоновым буфером и это никак не скажется на регенерации ранее созданного изображения из рабочего буфера. После завершения формирования очередной фазы динамической картинки выполняется переключение буферов.
Двойная буферизация — это стандартная технология организации компьютерной анимации. Все операторы формирования изображения включаются в функцию display(), но при использовании двойной буферизации в этой функции нужно сначала очистить рабочий буфер, вызвав glClear(), а последним оператором вызвать функцию переключения буферов glutSwapBuffers().
Теперь вернемся к нашему вращающемуся квадрату. В рассмотренную ранее программу нужно внести два изменения. Во-первых, в функции main() нужно заказать режим двойной буферизации:
glut!nitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
Во-вторых, нужно скорректировать функцию display (), добавив в самый ее конец оператор glutSwapBuffers();
Учтите, что двойная буферизация не решает проблемы скорости формирования изображения. Фактически эта технология позволяет только избежать наложения отображения и формирования новой фазы картинки — новое изображение выводится на экран только после завершения его формирования. Но возможно получить вполне приемлемое качество динамического изображения при частоте формирования новых фаз порядка 10-20 Гц.
Проблемы с буферизацией
При создании интерактивных графических программ формирования динамического изображения программисту приходится сталкиваться с целым рядом проблем, которые не могут быть решены только за счет двойной буферизации. Возвращаясь к рассмотренному выше
примеру — программе рисования по командам пользователя, — предположим, что нам понадобилось добавить в картинку изображение часов, которые показывали бы текущее время
выполнения программы (рис. 3.22).выполнения программы (рис. 3.22).Рис. 3.22. Окно программы рисования, в которой есть часы текущего времени выполненияРис. 3.22. Окно программы рисования, в которой есть часы текущего времени выполненияВ большинстве операционных систем существуют функции, возвращающие текущее астрономическое время в виде строки символов в коде ASCII, либо функции, возвращающие целое число, равное времени, прошедшему после некоторого события (в секундах). Периодически вызывая эти функции (например, в программе обработки простоя), можно затем преобразовать полученное значение в приемлемый формат, например часы „минуты: секунды. Последняя стадия процесса — вывести эти меняющиеся данные на экран в виде текста.
выполнения программы (рис. 3.22).
выполнения программы (рис. 3.22).
Рис. 3.22. Окно программы рисования, в которой есть часы текущего времени выполнения
Рис. 3.22. Окно программы рисования, в которой есть часы текущего времени выполнения
Посмотрим, что случится, если добавить соответствующий фрагмент в первый вариант программы построения вращающегося квадрата, в котором не использовалась двойная буферизация. Вы, скорее всего, сразу же заметите, что показания часов мигают при выполнении очистки экрана функцией display(). Попробуем справиться с этой проблемой, воспользовавшись технологией двойной буферизации.
При переходе в режим двойной буфериза
ции мигание исчезнет, но вместо этой проблемы появится другая, еще более серьезная, — станут мерцать основные объекты на рабочем поле окна. Причина этого неприятного эффекта
заключается в том, что используется режим немедленного вывода изображения. Объекты, которые формируются программой по командам пользователя, передаются в буфер кадра единственный раз. Поэтому каждый объект оказывается только в одном из двух буферов и не мо
жет присутствовать одновременно в двух. Теперь, справившись с миганием показаний часов, мы видим, что начали мерцать объекты в графической зоне экрана.
Решение этой проблемы тоже можно отыскать. В нашей программе рисования для вывода объектов на экран можно использовать дисплейный список, в который следует вносить объекты по мере обработки команд пользователя. После этого можно при каждом переключении очищать рабочий буфер и формировать в нем изображения всех объектов, уже включенных к этому времени в дисплейный список. Правда, для этого программу придется значительно усложнить, особенно если для вывода символов также используются свои дисплейные списки.
Но можно поступить и проще, сделав так, чтобы изображение каждого объекта оказалось сразу в обоих буферах— и в экранном, и в рабочем. OpenGL и некоторые другие графические системы позволяют таким образом настроить режим работы системы, чтобы можно было одновременно формировать изображение во множестве буферов. Мы рассмотрим подробно эту технологию позже, в главе 9, когда будем обсуждать и другие проблемы использования буферов.
Разработка интерактивных графических программ
Формально определить перечень характеристик хорошей интерактивной графической программы довольно сложно, но отличить хорошую программу этого типа от плохой, поработав с обеими, можно достаточно быстро. Хорошая программа, как правило, располагает следующими функциональными возможностями.
Плавное изображение динамических сцен без видимого ‘‘дерганья’* объектов на экране.
Наличие нескольких устройств ввода, с помощью которых можно управлять характеристиками объектов на экране, в том числе и их динамикой.
Разнообразие методов ввода и отображения информации.
Доступный и интуитивно понятный интерфейс пользователя.
Обратная связь с пользователем.
“Лояльное” отношение к возможным ошибкам пользователя.
В программе должны учитываться как зрительные, так и моторные свойства человека- оператора, т.е. его способность воспринимать зрительную информацию и реагировать на нее, выполняя какие-либо действия с устройствами ввода.
Важность перечисленных функциональных возможностей графической программы и сложность их реализации на практике нельзя недооценивать. Тема взаимодействия человека с машиной (HCI— human-computer interaction)— одна из тех, которые привлекают внимание множества исследователей во всем мире, и ее нельзя изложить на нескольких страницах. В данной книге нас в этой теме больше всего будет интересовать “компьютерная” сторона, которая обеспечивает формирование графического изображения в такой комплексной системе. Но есть несколько вопросов, общих для компьютерной графики и теории человекомашинных систем, которые нужно учитывать при разработке интерактивных программ.
10.1. Инструментальные средства, экранные элементы управления и буфер кадра
В рассмотренной выше программе рисования мы использовали графические средства интерфейса, в частности всплывающие меню, которые поддерживаются библиотекой GLUT, и графические кнопки, которые разработали самостоятельно. Спектр экранных элементов интерфейса, доступных в современных программах, значительно шире. Он включает и различные линейные регуляторы, шкалы, “горячие” области экрана, звук, пиктограммы и т.п. Обычно прикладной программист рассматривает эти элементы как стандартные и рассчитывает в
этом деле на библиотеки инструментальных программ, хотя некоторые из специалистов считают, что свой продукт всегда лучше, чем заимствованный. В общем случае, почти все такие инструментальные пакеты используют для связи с прикладной программой механизм функций с обратным вызовом. Значительно облегчит разработку собственных средств графического интерфейса описание методики работы с внутренними буферами графической системы, которое представлено в главе 9.
Ниже на двух примерах будет показано, что использование графических построений на геометрическом уровне недостаточно эффективно при работе с элементами экранного интерфейса. Гораздо эффективнее в этом случае работать напрямую с буфером кадра. Сначала рассмотрим, как выполняются операции построения всплывающего меню. При обращении к функции работы с меню на экране должно появиться его изображение, причем примерно в
том месте, где находится указатель мыши в момент, когда пользователь щелкнул соответствующей кнопкой. После того как будет выбран один из пунктов меню, его изображение должно исчезнуть с экрана, а на его месте восстановлены ранее вычерченные объекты. Если
использовать только операции геометрических построений, описанные ранее, выполнить такую операцию быстро не удастся. Наиболее целесообразно запомнить изображение,
“перекрытое” меню, а затем, после завершения работы с меню, вернуть его на экран. Таким образом, нам придется работать на уровне пикселей поля экрана, а не геометрических объектов и, следовательно, иметь дело с буфером кадра. При этом интенсивно используются операции пересылки битового блока памяти (операции группы bitbit}.
Второй пример— построение "резиновой” линии {rubberbanding}— метод формирования графических изображений в интерактивном режиме. В рассмотренной выше программе
Рис. 3.23. Построение “резиновой ”линииРис. 3.23. Построение “резиновой ”линиирисования для задания вершин формируемого графического объекта пользователь должен был дважды (или трижды) указать позиции в графической зоне окна, щелкнув кнопкой мыши. При этом он только мысленно мог себе представить, как именно пройдет формируемый отрезок и как он соотносится с ранее вычерченными объектами. Новый метод позволяет пользователю постоянной видеть “эскиз” формируемого отрезка, который будет следить за текущим положением мыши, связывая его с ранее введенной точкой. Поведение этого “эскиза” нового отрезка очень напоминает поведение резиновой нити, закрепленной одним концом на каком-либо гвоздике, вбитом в чертежную доску, а другим концом на игле циркуля или “чертилки”, — куда бы ни двигалась “чертилка”, между нею и вбитым гвоздиком всегда видна прямая линия, образованная натянутой нитью. Точно такую же роль играет в самом примитивном виде и искусственная “резиновая” нить в графической системе. Обычно изображение нити возникает, когда пользователь нажимает кнопку мыши, и отслеживает
Рис. 3.23. Построение “резиновой ”линии
Рис. 3.23. Построение “резиновой ”линии
текущее положение указателя мыши до тех пор, пока пользователь не отпустит кнопку (рис. 3.23). Но в некоторых графических системах применяется и другая последовательность
операций— нить возникает после первого щелчка, а после второго и всех последующих “перепрыгивает” в новую позицию, пока не будет сформирована команда прекращения последовательности отрезков. При формировании изображения “резиновой” нити на каждом цикле прежнее изображение отрезка (т.е. отрезка, связывавшего предыдущее положение указателя мыши с начальной точкой) стирается, а вычерчивается новое изображение, связанное с
текущим положением указателя мыши.
При построении изображения “резиновой” нити не обойтись без операций на уровне буфера кадра или без специальных аппаратных средств. Эта проблема во многом сходна с проблемой построения изображения всплывающего меню. В каждом цикле отслеживания текущего положения мыши нужно сначала вернуть изображению “первозданный” вид, а потом вычертить новый отрезок. С помощью аналогичной технологии вычерчиваются не только “резиновые” отрезки, но и “резиновые” прямоугольники и окружности. Такие инструментальные пакеты, как GLUT, предоставляют программисту широкие возможности для выполнения операций пересылки блоков буфера кадра. В состав пакетов практически всегда включаются средства для формирования всплывающих меню и построения изображения “резиновой” нити, что избавляет прикладного программиста от необходимости самостоятельно разрабатывать их, если в приложении нужно реализовать подобные функции.
Резюме
В этой главе мы лишь слегка “прикоснулись” ко многим темам, связанным с взаимодействием пользователя и графической программы. Организация в программе реакции на действия пользователя с выводом динамического изображения на экран придает приложению совершенно новые возможности. Мы показали, что хотя состав функций API OpenGL и не зависит от конкретной операционной системы, в любой графической программе не обойтись без минимального набора средств взаимодействия с операционной системой. Роль переходного элемента между независящим от платформы API и операционной системой играет набор инструментальных программ, который, с одной стороны (со стороны графической системы), имеет единообразный интерфейс, а с другой стороны, имеет множество вариантов реализации, специфических для каждой из имеющихся операционных систем. При работе с OpenGL роль такого инструментального пакета отводится библиотеке GLUT.
Было рассмотрено, как отражается парадигма “клиент/сервер” на структуре графической системы и характеристиках ее отдельных компонентов. Эта парадигма не только способствует рациональной организации графических приложений в сетевой среде, но позволяет создавать графические программы, переносимые с одной аппаратной или программной платформы на другую. Эта концепция нашла свое воплощение в объектно-ориентированном подходе к программированию задач компьютерной графики, а также во внедрении графических приложений в сетевую среду World Wide Web (эти темы будут подробно рассмотрены в главе 8).
С точки зрения прикладного программиста идеология создания интерактивных графических приложений практически единая во всех системах. Графическая часть сервера включает растровый дисплей, клавиатуру и устройство указания. Почти на всех рабочих станциях пользователь работает в сетевой операционной среде, поддерживающей многооконный интерфейс. Как правило, в такой среде параллельно решается множество задач, но операционная система создает у пользователя иллюзию, что он является безраздельным хозяином компьютера.
Избыточность в программе, настроенной на такой режим работы, минимальна, а использование логических внешних устройств избавляет прикладного программиста от заботы об организации обслуживания конкретной модели физического устройства.
Стандартным методом взаимодействия с внешними устройствами в такой многопользовательской операционной среде является механизм реакции на события, хотя при желании можно использовать и другие методы. Например, ввод-вывод по запросу и обработка событий предоставляют наиболее широкие возможности в организации гибкой реакции программы на все разнообразие ситуаций, возникающих во внешнем по отношению к программе мире.
Интерактивная компьютерная графика представляет собой удивительно мощный инструмент с практически неограниченными возможностями. Пользуясь различными методиками организации взаимодействия с пользователем, программист может создавать программные продукты, рассчитанные на любую категорию пользователей. Возможно, вы убедитесь в этом на собственном опыте, выполнив упражнения, представленные в конце главы.
Рекомендуемая литература
Проект Sketchpad Сазерленда (Sutherland) описан в работе [Sut63].
Большинство концепций, положенных в основу оконного графического интерфейса, разработано в научно-исследовательском центре фирмы Xerox в Пало-Альто (Xerox Palo Alto Research Center— PARC) в 1970-х годах (см. [Sch87]}. Здесь же появилась на свет и мышь [Eng68]. Разработки PARC лежат и в основе популярных сегодня операционных систем — Macintosh Operating System, X Window и Microsoft Windows.
Книга Фоли (Foley) и его соавторов [Fol94] содержит подробное описание процесса разработки интерфейса пользователя с акцентом на аспекты использования графических средств. Книги Шнайдермана (Schneiderman) [Sch97] и Нильсона (Nielson) [Nie94] могут послужить хорошим вводным курсом в проблематику человеко-машинных систем.
Операционная система X Window [Sch88] разработана в Массачусетсском технологическом институте (Massachusetts Institute of Technology) и в настоящее время стала стандартом de facto для всех приверженцев UNIX. Завоевывающая все большую популярность среди пользователей ПК версия UNIX под названием Linux также поддерживает работу X Window.
Режимы ввода информации и взаимодействия с пользователем, которые мы обсуждали на страницах этой книги, основываются на стандартах, реализованных в GKS [ANSI85] и PHIGS [ANSI88], Эти стандарты разрабатывались как универсальные, т.е. пригодные и для векторных, и для растровых дисплеев, а потому в них не учитывались уникальные возможности растровых графических систем (см. [Pik84, Gol83]\
Хотя при изложении материала в этой главе мы опирались на инструментальную библиотеку GLUT [Kil94,b], программисты на практике используют и непосредственный доступ к операционной системе X Window, и различные версии других инструментальных библиотек, позволяющих работать как с X Window [Kil94,a, OSF89], так и с Microsoft Windows. Интерес представляет и использование при разработке пользовательского интерфейса в OpenGL- программах языков написания сценариев, таких как tcl/tk [Ous94].
Упражнения
Объясните, в чем суть проблем, с которыми сталкивается программист при определении штриховых шрифтов. Разработайте простую структуру данных, которая позволила бы определить набор символов штрихового шрифта, оградившись только отрезками прямых.
Разработайте новую версию программы отображения узора Серпинского из главы 2, в которой щелчок левой кнопкой мыши запускал бы процесс формирования точек узора на экране, щелчок правой кнопкой мыши останавливал бы процесс формирования новых точек, а щелчок средней кнопкой прекращал выполнение приложения и закрывал его окно. Включите в программу обработку события изменения размеров окна приложения.
Разработайте линейный регулятор, который позволял бы пользователю задавать цвет вычерчивания фигур в программе рисования. С помощью этого интерфейса пользователь сможет сначала визуально оценить новый цвет, а уже затем назначить его для формирования очередных фигур.
Виртуальный трекбол можно разработать на основе обычной мыши, “отобразив” ее на поверхность шара. Основное назначение такого устройства — позволить пользователю задавать с помощью мыши скорость, вращая шар вокруг вертикальной оси (имитируя “спин”). Разработайте такое виртуальное устройство и графическую программу, которая будет демонстрировать его возможности.
Разработайте новую версию программы вычерчивания квадратов (раздел 3.5), в которой использовалось бы всплывающее меню, аналогичное описанному в разделе 3.6.
Добавьте в программу рисования (раздел 3.8) индикатор времени работы, используя собственную программу считывания показаний системных часов.
Хорошую практику при освоении методов программирования взаимодействия компьютера и пользователя дает разработка игровых программ. Разработайте программу игры в шашки. Каждую клетку на доске следует рассматривать как объект, который может выбрать пользователь. Чтобы не связывать себя тайнами шашечной стратегии, разработайте программу, в которой оба участника — люди.
Разработайте простую программу раскладки пасьянса. Сначала разработайте простую колоду карт, используя только те примитивы, которые мы уже рассмотрели в этой книге. Программу следует свести к выбору прямоугольных объектов на экране.
Моделирование разных видов игры в бильярд — это тоже довольно интересная задача для тех, кто увлекается игровыми программами. В такой программе, как и в упр. 2.18, придется вычислять траектории и определять столкновение шаров. Интерактивный аспект новой программы — задание направления движения шаров с помощью этакого компьютерного кия и обеспечение достаточно плавной имитации движения шара на экране. Разработайте программу для двух игроков.
Вместо того чтобы использовать для ввода команд меню или кнопки, можно просто разбить все поле графического окна на зоны и анализировать, в какой из них пользователь щелкнул кнопкой мыши. Используйте этот принцип в программе рисования (раздел 3.8).
Пересчет координат точек из мировой системы координат в систему координат экрана обеспечивает однозначное соответствие, но оно не является обратимым, поскольку происходит переход от трехмерной системы координат к двухмерной. Предположим, однако, что мы имеем дело с двухмерным приложением. Является ли в нем такой пересчет обратимым? Какие проблемы возникают при определении значения координат в мировой системе по положению точки в системе координат экрана?
Как применить результат упр. 3.11 к организации процедуры указания в программе?
При разработке прикладной программы программисту приходится решать, следует ли использовать в ней дисплейные списки. Рассмотрите хотя бы два разных практических приложения и составьте для каждого из них список доводов за и против применения дисплейных списков. Используя шрифты, имеющиеся в составе библиотеки GLUT, проверьте на практике, правильно ли были сформулированные доводы.
Разработайте интерактивную программу, которая позволит провести “виртуальную” крысу через лабиринт, который был сформирован при выполнении упр. 2.7. Можно для управления движением крысы использовать кнопки мыши: правую и левую — для поворота соответственно вправо и влево, а среднюю — для движения вперед.
В разделе 3.9 было показано, что даже двойная буферизация в программе рисования не гарантирует прекращение мигания изображения. Можно ли справиться с ним, используя только те программы формирования изображения, которые вам известны на данной стадии изучения? Обоснуйте свой ответ.
Недорогие устройства ввода типа джойстик, как те, что часто используются в игровых приставках, не имеют преобразователей, вместо которых используются трехпозиционные переключатели. Как, по-вашему, такие устройства “общаются” с программой?
Ориентация летательного аппарата описывается ориентацией осей системы координат, показанной на рис. 3.24. Движение джойстика вперед-назад контролирует поворот вверх-вниз продольной оси аппарата {тангаж аппарата — pitch). Движение джойстика влево-вправо контролирует вращение вокруг этой оси {крен — roll). Разработайте программу, которая будет использовать в качестве устройства ввода мышь и по ее смещению управлять аппаратом по тангажу и крену, изменяя параметры визуализации для пилота, который сидит в кабине такого аппарата. Это упражнение можно выполнить для двухмерного случая, рассматривая набор объектов, видимых из кабины аппарата и пренебрегая эффектами перспективы.
Рассмотрим стол, на котором смонтирован плоский рычажный механизм (манипулятор), на конце которого имеется сенсор (рис. 3.25). Предположим, что длины обоих звеньев манипулятора фиксированы, а звенья связаны с основанием и друг с другом простыми поворотными сочленениями (сочленениями с одной степенью свободы). Найдите соотношения, связывающие углы поворота в сочленениях 0 и ф и положе
ние сенсора, закрепленного на последнем звене.
Пусть имеется ЭЛТ-монитор с размерами экрана 40x40 сантиметров. В мониторе используется прогрессивная развертка частотой 60 Гц, причем 10% периода кадровой развертки уходит на обратный ход луча из правого нижнего угла экрана в левый верхний. Будем считать, что разрешение монитора 1024x1024 пикселей. Найдите соотношение между временем появления сигнала на выходе светового пера и положением светового пера на поле экрана. Представьте результат в метрических единицах измерения и в относительных (пикселях).
Программа вычерчивания электронных схем является
Рис. 3.24. Система координат летательного аппаратаРис. 3.24. Система координат летательного аппаратаодним из вариантов программы рисования, рассмот- „, ^, „-
Рис. 3.24. Система координат летательного аппарата
Рис. 3.24. Система координат летательного аппарата
г3.25. Плоский манипуля-
ренной в этой главе. Будем считать, что схема состо- тОр
ит из логических элементов типа И (AND), ИЛИ (OR)
и НЕ (NOT), символические обозначения которых
представлены на рис. 3.26. Разработайте программу, которая позволяет пользователю вычерчивать схему, пользуясь меню для выбора типа элемента и задавая его положение на экране с помощью мыши. Подумайте над тем, как автоматически совмещать
выход одного элемента с входом другого.
Дополните программу, разработанную в предыдущем упражнении, возможностью графически формировать последовательность входных сигналов для разработанной схемы. Программа должна в ответ выводить графики сигналов в указанных пользо
вателем узлах схемы.
ORANDNOT
Puc. 3.26. Символы логических элементов электронной схемы
Дополните программу, разработанную в упр. 3.20, таким образом, чтобы у пользователя была возможность вводить логические выражения. В ответ программа должна формировать электронную схему, реализующую введенное выражение.
Распространите методы, положенные в основу разработки программы упр. 3.20, на формирование схем алгоритмов программ или отображение графов, которые изучаются в курсах структур данных.
Программные пакеты вычерчивания графических документов, как правило, предоставляют в распоряжение пользователей множество методов формирования изображения. Разработайте интерактивную программу вычерчивания двухмерных кривых. Это приложение должно предоставлять пользователю возможность выбрать режим отображения графиков (в виде ломаной линии, столбиковой или сегментной диаграммы), цвета и типа линии.
Требования к частоте регенерации дисплеев на ЭЛТ (50-85 Гц) сформулированы применительно к люминофорам с малым послесвечением. Но существуют и люминофоры с длительным послесвечением. Почему, по вашему мнению, ЭЛТ с такими люминофорами не используются в большинстве дисплеев компьютерных рабочих станций? Где имеет смысл использовать подобные дисплеи?
ГЛАВА
4
Объекты и геометрические преобразования
И
так, мы готовы обстоятельно рассмотреть трехмерную графику. Большая часть этой главы посвящена описанию различных способов представления базовых геометрических типов, преобразований одного представления в другое и свойств геометрических объектов, независимых от конкретного представления.
Начнем с анализа математических соотношений, положенных в основу компьютерной графики. Такой подход поможет избежать в дальнейшем путаницы, которая порождается из- за отсутствия четкого представления об отличиях между геометрическими сущностями, способами их описания в конкретной системе координат и математическими абстракциями.
Мы будем использовать понятия аффинного и Евклидова векторных пространств, которые являются математическим фундаментом для дальнейшей работы. Одна из основных целей, которую я преследую в этой главе, познакомить читателя с методами решения геометрических проблем, не зависящими от выбранных систем координат. Такой подход является достаточно надежной основой для всего последующего анализа, распространяется на любые частные виды представления геометрических объектов и логически приводит к идее использования обобщенных однородных координат, которая не только помогает понять суть проблемы, но и является основополагающей для эффективной реализации методов ее решения на практике.
Мы используем терминологию абстрактных типов данных для того, чтобы подчеркнуть отличие между объектом как таковым и его частным представлением. Дальнейшее обсуждение покажет, что математические соотношения естественным образом вытекают из нашего желания манипулировать немногими базовыми геометрическими типами данных. Математический аппарат, который будет представлен в этой главе, относится к таким областям математики, как векторные пространства, геометрия и линейная алгебра. Формальное изложение математики векторных пространств и матричной алгебры читатель найдет в приложениях Б и В соответственно.
Следуя подходу, декларированному в начале этой книги и уже реализованному в предыдущих главах, для иллюстрации основных теоретических положений будет разработана несложная программа, в которой мы постараемся продемонстрировать методику реализации
средствами графического API основных концепций геометрических преобразований. Пример, предложенный вашему вниманию в этой главе, имеет дело с представлением и преобразованиями простого геометрического объекта— куба. Мы также продемонстрируем, как задавать параметры преобразований в интерактивном режиме.
Скаляры, точки и векторы
Компьютерная графика имеет дело со множеством геометрических объектов, таких как точки, многоугольники и многогранники. Как уже было показано в предыдущих главах на примере двухмерных приложений, все разнообразие геометрических объектов можно свести к ограниченному множеству простейших сущностей. Нам понадобятся для этого три базовых типа — скаляры, точки и векторы. Существуют, по крайней мере, три варианта определения этих сущностей, в зависимости от того, с какой точки зрения их рассматривать — чисто математической (формальной), геометрической или с точки зрения программной реализации. В конечном счете нам придется иметь дело со всеми тремя вариантами, хотя на первый взгляд и кажется, что они мало связаны друг с другом.
Несмотря на то что существует огромное множество примеров каждого из трех базовых типов, в этой главе мы рассмотрим только по одному представителю каждого типа. Как бы там ни было, но для нас наибольшую важность представляет вопрос об отличиях между математическим определением каждого типа и его частной реализацией, что требует основательного знакомства с математикой, лежащей в основе дальнейших рассуждений.
Геометрическое определение базовых типов
Роль фундаментального геометрического объекта в компьютерной графике играет точка. В трехмерной геометрической системе точка — это положение в трехмерном пространстве. Единственным атрибутом точки является ее положение. Математическая точка даже не имеет размера. Точки существуют в пространстве независимо от какой-либо системы координат. Конечно, это может показаться неудобным, поскольку на определенную точку приходится ссылаться как “точка вот там” или “синяя точка рядом с красной”. Проблема ссылок решается с помощью систем координат или фреймов (раздел 4.3), но сейчас нас интересует, как далеко можно продвинуться, не обращаясь к конкретной системе отсчета.
Скаляры — это всегда действительные числа. Хотя скаляры и не имеют геометрических свойств, они понадобятся нам в качестве единиц измерения. Например, длина отрезка есть скаляр. Скаляром является и угол вращения объекта.
В компьютерной графике точки часто связываются направленными отрезками, как показано на рис. 4.1. Такие отрезки прямых и есть векторы. В более общем смысле физики используют термин вектор для обозначения любой величины, характеризуемой направлением и значением. Физические величины, такие как скорость и сила, являются векторами. Однако вектор в этом смысле не имеет фиксированной точки приложения (позиции). Следовательно, направленные отрезки, показанные на рис. 4.2, являются одинаковыми векторами, поскольку имеют одно и то же направление и одинаковую длину, хотя и разные точки приложения. Мы часто будем использовать в этой книге термины вектор и направленный отрезок как синонимы.
Длина и направление вектора характеризуются вещественными числами. Вектор А на рис. 4.3,а имеет то же направление, что и вектор В, но В имеет длину в два раза большую, чем А, в потому можно записать В = 2.4. Вектор С имеет ту же длину, что и А, но противоположное направление, а потому соотношение между ними выразится формулой С = -А. Объединять векторы можно с помощью правила сочленения начала с концом (head-to-tail rule}, схематически представленного на рис. 4.3,6. На этом рисунке конец вектора А сочленен
с началом вектора В и сформирован новый вектор С, величина и направление которого определяются отрезком, соединяющим начало вектора А и конец вектора В. Этот новый вектор будем называть суммой векторов А и В и записывать соотношение между ними в виде С=А+В. Обратите внимание на то, что поскольку векторы не имеют фиксированной точки приложения, то для геометрического сложения векторов их можно переносить параллельно самим себе куда угодно.
Рис. 4.1. Соединение двух точек направленным отрезкомРис. 4.1. Соединение двух точек направленным отрезкомРис. 4.3. Соотношение между векторами: а — выраженное с помощью вещественных чисел; б — правило сочленения начала с концомРис. 4.3. Соотношение между векторами: а — выраженное с помощью вещественных чисел; б — правило сочленения начала с концом
Рис. 4.1. Соединение двух точек направленным отрезком
Рис. 4.1. Соединение двух точек направленным отрезком
Рис. 4.3. Соотношение между векторами: а — выраженное с помощью вещественных чисел; б — правило сочленения начала с концом
Рис. 4.3. Соотношение между векторами: а — выраженное с помощью вещественных чисел; б — правило сочленения начала с концом
Рис. 4.4. Сомнительное представление вектораРис. 4.4. Сомнительное представление вектораказано дальше, мы должны будем работать с их представлениями в определенной систеказано дальше, мы должны будем работать с их представлениями в определенной систе
Рис. 4.4. Сомнительное представление вектора
Рис. 4.4. Сомнительное представление вектора
казано дальше, мы должны будем работать с их представлениями в определенной систе
казано дальше, мы должны будем работать с их представлениями в определенной систе
Точки и векторы — это разные геометрические типы. Геометрическое представление точки в виде направленного отрезка, соединяющего некоторую опорную точку с заданной (рис. 4.4), следует рассматривать как сомнительное. Однако существует операция, в которой связываются точки и направленный отрезок (см. рис. 4.1.) Можно использовать направленный отрезок для перемещения от одной точки к другой. Точно так же две точки определяют отрезок, проведенный между ними, а точка и вектор определяют вторую точку. Таким образом, правильная интерпретация рис. 4.4 состоит в том, что данный вектор можно определить как исходящий из фиксированной опорной точки (начала системы координат) и приходящий в определенную точку пространства. Обратите внимание на то, что вектор, как и точка, существует независимо от опорной системы, но, как будет по-
ме координат.
Математическое определение: векторное и аффинное пространства
Скаляры, точки и векторы можно рассматривать как элементы математических множеств. Поэтому мы будем использовать разного рода абстрактные пространства для представления и манипулирования этими множествами объектов. В математике принято использовать такие абстрактные пространства для решения разного рода прикладных проблем — от решения дифференциальных уравнений до аппроксимации математических функций. Формальное определение тех пространств, которые представляют для нас интерес — векторного, аффинного и Евклидова, — приведено в приложении Б. Нас интересуют только такие пространства, элементами которых являются геометрические типы.
Возможно, наиболее важным математическим пространством является векторное (лийейное) пространство. Векторное пространство содержит две различные сущности — векторы и скаляры. В этом пространстве существуют правила объединения скаляров с помощью двух операций (сложения и перемножения), и, таким образом, определено скалярное поле. Примерами скаляров могут быть вещественные и комплексные числа, а также рациональные функции. В векторном пространстве можно комбинировать скаляры и векторы и создавать новые векторы с помощью операции умножения скаляра на вектор. Можно также комбинировать и векторы с помощью операции сложения векторов. Примерами математических векторных пространств являются «-группы действительных чисел, решение однородных линейных дифференциальных уравнений и геометрические операции над направленными линейными отрезками.
Аффинное пространство — это расширение векторного пространства, в которое включен дополнительный тип объектов — точка. В аффинном пространстве определена операция сложения точки и вектора, результатом которой является новая точка. Обратной ей является операция вычитания двух точек, результатом которой будет вектор.
В линейном векторном пространстве отсутствует способ измерения скалярных величин. Евклидово пространство — это расширение векторного пространства, в которое введена мера для измерения расстояния (размера).
В этих абстрактных пространствах объекты могут быть определены независимо от конкретного представления — они просто являются элементами некоторых множеств. Одна из основных концепций векторного пространства состоит в представлении любого вектора в терминах одного или нескольких множеств базисных векторов. Представление обеспечивает связь между абстрактными объектами и их реализацией, а взаимные преобразования одних представлений в другие приводят нас к геометрическим преобразованиям.
Информационное определение
Хотя математики предпочитают иметь дело со скалярами, точками и векторами как членами множеств, которые можно комбинировать в соответствии с определенными аксиомами, для специалистов по информатике привычнее и ближе рассматривать их как абстрактные типы данных — АТД (ADT — abstract data types). Абстрактный тип данных — есть множество операций над некоторыми данными, причем операции определяются независимо от внутреннего представления данных или от способа их реализации в конкретной системе. Понятие абстрагирование от данных (data abstraction) является фундаментальным в современной науке о компьютерной обработке информации. Например, операция включения нового элемента в список или перемножения двух многочленов может быть определена независимо от того, как именно хранится список или как представлены действительные числа в данном компьютере. Те, для кого эта концепция стала привычной, безо всякого труда воспринимают разницу между понятием объекта (или операции над объектами) и представлением объектов (или их реализацией) в конкретной системе. С точки зрения информатики нам требуется
средство, позволяющее объявлять геометрические объекты в тексте программы независимо от их представления или реализации в данном компьютере. Выглядеть такое объявление должно примерно таким образом:
vector u,v;
point p,q;
scalar a,b;
Для определения абстрактных типов в объектно-ориентированных языках программирования, подобных C++, можно применить такие средства, как классы и перегрузку операторов. Это позволяет записать в программе операцию над данными некоторого геометрического типа в следующем виде:
q = p+a*v;
Но сначала нужно определить функции, которые будут выполнять операции над данными новых типов, а для этого понадобится рассмотреть те математические соотношения, которые требуется реализовать в этих функциях.
Геометрические абстрактные типы данных
Рассмотрев понятия скаляра, точки и вектора с трех точек зрения, мы получили математическую и информационную “оболочку”, в которой будем в дальнейшем работать с геометрическими объектами. Скаляры— это действительные числа, по отношению к которым можно применять обычные операции сложения и умножения. Геометрические точки— это положения в пространстве, а векторы — направленные отрезки в пространстве. На эти объекты распространяются правила, определенные в абстрактном аффинном пространстве. В программе можно определить абстрактные типы данных для представления базовых типов объектов и специфицировать операции, подчиняющиеся этим правилам.
Теперь рассмотрим, как можно использовать базовые типы для выполнения геометрических операций и формирования новых объектов. Будем обозначать в дальнейшем скаляры буквами греческого алфавита а, Р, у,. ., точки— прописными буквами латинского алфавита P,Q,R,..., а векторы— строчными буквами латинского алфавита и, г, и\ .... Позже, в разделе 4.3, мы введем в рассмотрение и другие абстрактные математические объекты, которые будем обозначать полужирными буквами латинского алфавита. До сих пор мы никак не затрагивали вопрос о системе отсчета, например системе координат, а потому векторы и точки являются для нас пока что абстрактными объектами, а не объектами, представленными в определенной системе отсчета. Модуль вектора г (или его длина, абсолютная величина — magnitude)— это действительное число, которое обозначим |г|. Операция умножения скаляра на вектор обладает следующим свойством (см. приложение Б):
|а r| = |a||v|,
причем при положительном а направление вектора av то же, что и направление вектора v. Обращаю ваше внимание на то, что в дальнейшем нам понадобится специальный вектор нулевой длины (нуль-вектор — zero vector), который будем обозначать полужирной цифрой 0.
Существуют две эквивалентные операции, связывающие две точки и вектор. Первая — это вычитание точек, которая формирует из двух точек Р и Q вектор г (рис. 4.5). Эту операцию запишем следующим образом:
v = P-Q.
Следствием такого определения операции вычитания точек является то, что для заданной точки Q и вектора v существует единственная точка Р, которая удовлетворяет приведенному выше соотношению. Это утверждение можно формально выразить следующим образом: пусть заданы точка Q и вектор г, тогда существует точка Р, такая, что
Р - v^Q.
Таким образом, Р формируется с помощью операции сложения вектора с точкой. Правило сочленения начала вектора с концом другого вектора позволяет очень наглядно представить операцию сложения векторов (рис. 4.6,а). Из правила сложения векторов следует и отношение, справедливое для любых трех точек Р, Q и R (рис. 4.6,6):
(/>-0 + kQ-R) - P-R.
Рис. 4.6. Правило сочленения начала одного вектора с концом другого: а — сложение векторов;б — соотношения между точкамиРис. 4.6. Правило сочленения начала одного вектора с концом другого: а — сложение векторов;б — соотношения между точками6)6)
Рис. 4.6. Правило сочленения начала одного вектора с концом другого: а — сложение векторов;
б — соотношения между точками
Рис. 4.6. Правило сочленения начала одного вектора с концом другого: а — сложение векторов;
б — соотношения между точками
6)
6)
Прямые
Логическим следствием операции сложения точки и вектора (или, если вам это нравится больше, операции вычитания точек) является понятие прямой в аффинном пространстве. Рассмотрим все точки, удовлетворяющие соотношению
Ла) = Pq + ad,
где Ро— произвольная точка, d— произвольный вектор, а а— некоторый скаляр. В соответствии с правилами объединения точек, векторов и скаляров, определенными в аффинном пространстве, Ла) является точкой при любом заданном значении а. Все эти точки будут лежать на прямой, как показано на рис. 4.7. Такую форму представления прямой часто называют параметрической, поскольку совокупность точек, представляющих прямую, формируется в результате изменения значения параметра а. При а = О точка на прямой совпадает с Ро, и по мере увеличения а точки располагаются все дальше от исходной в направлении вектора d. Если ограничить значения параметра а только неотрицательными числами, то получим луч, исходящий из точки Ро в направлении вектора d.
Рис. 4.7. Определение прямой в аффинном пространстве
Аффинное сложение
В аффинном пространстве определены операции сложения двух векторов, умножения вектора на скаляр и сложения вектора и точки, а операции сложения произвольных точек и умножения точки на скаляр отсутствуют. Но в этом пространстве существует также операция аффинного сложения (affine addition), в которой имеются некоторые элементы двух последних операций. Для любой точки Q, вектора v и положительного скаляра а результат операции
Р = Qf-av
описывает все точки на прямой, проходящей через точку Q в направлении вектора v (рис. 4.8). Всегда можно найти такую точку R, что
v = R-Q.
Следовательно,
Р = Q + a(R-Q) = aR + (l-a)£Z
Эта операция выглядит так же, как и сложение двух точек, и, следовательно, имеет эквивалентную форму:
Р = а,/? + а20,
где
а, + а2 = 1.
Выпуклость
Выпуклым (convex) является такой объект, для которого соблюдается следующее правило: все точки, лежащие на отрезке, соединяющем две любые точки, принадлежащие объекту, также принадлежат этому объекту. Мы уже видели, на примере многоугольников (см. главу 2), насколько важна выпуклость объекта. Для более формального анализа выпуклости мы теперь можем воспользоваться операцией аффинного сложения. При 0<а<1 операция аффинного сложения определяет отрезок, связывающий точки R и Q, как показано на рис. 4.9. Следовательно, отрезок прямой является выпуклым объектом. Расширим операцию аффинного сложения и на объекты, заданные п точками Рх, Р2, ..., Р„. Рассмотрим выражение
F = a1F1 + а2Д2 + ... + а,Л-
Рис. 4.8. Аффинное сложениеРис. 4.8. Аффинное сложениеРис. 4.9. Отрезок прямой, соединяющий две точкиРис. 4.9. Отрезок прямой, соединяющий две точки
Рис. 4.8. Аффинное сложение
Рис. 4.8. Аффинное сложение
Рис. 4.9. Отрезок прямой, соединяющий две точки
Рис. 4.9. Отрезок прямой, соединяющий две точки
Можно доказать по индукции, что эта сумма определена тогда и только тогда, когда а| + а2 + ... + а„= 1.
Множество точек, образованных аффинным сложением п точек, на которое распространяется дополнительное ограничение
а,> О,а,> О,/ = 1,2,..., и,/ = 1,2,..., и,
а,> О,
а,> О,
/ = 1,2,..., и,
/ = 1,2,..., и,
называется выпуклой оболочкой (convex hud) исходного набора точек (рис. 4.10). Несложно
доказать по индукции, что выпуклая оболочка включает все отрезки прямых, связывающих
любую пару точек из набора {/’ь Р2,..., Рп). В геометрической трактовке выпуклая оболочка— это множество точек, принадлежащих поверхности минимальной площади, “натянутой” на заданное множество исходных точек. Понятие выпуклости играет особенно важную роль при конструировании кривых и поверхностей, и мы еще не раз вернемся к нему, в частности в главе 10.
Рис. 4.10. Выпуклая оболочка
Скалярное и векторное произведение
Многие геометрические процедуры, связанные с определением углов между векторами, реализуются с помощью элементарных операций скалярного (dot, inner) и векторного (cross) произведений двух векторов. Скалярное произведение векторов и и v обозначается и • у. Если и • у = 0, то и и у являются ортогональными. В Евклидовом пространстве квадрат модуля вектора есть скалярное произведение вектора на самого себя:
|и|2= и • и.
Угол между двумя векторами определяется по формуле л uv c°s6 = nn-
ИМ
Кроме того, выражение
Ivlcos0 = ^-j^
И
описывает ортогональную проекцию вектора v на и, как показано на рис. 4.11.
Если в трехмерном пространстве заданы три линейно-независимых вектора, то можно с помощью операции скалярного произведения построить три вектора, каждый из которых будет ортогонален двум другим. Этот процесс рассмотрен в приложении Б. Можно также использовать два непараллельных вектора, и и у, для того чтобы определить третий вектор п, ортогональный по отношению к первым двум (рис. 4.12). Этот вектор задается с помощью операции векторного произведения (cross product), формальное определение которой приведено в Приложении В:
п = и х у.
Рис 4.11. Скалярное произведениеРис. 4.12. Векторное произве-
и ортогональная проекциядение
Модуль векторного произведения позволяет определить синус угла 0 между векторами- сомножителями и и V.
Обратите внимание на то, что векторы и, v и п образуют правостороннюю систему координат (right-handed coordinate system), т.е. если вектор и направлен в ту же сторону, что и большой палец правой руки, а вектор v будет направлен вдоль указательного пальца, то вектор п будет параллелен среднему пальцу.
Плоскости
Плоскость (plane) в аффинном пространстве определяется прямым расширением параметрического представления прямой. Из курса элементарной геометрии вам должно быть известно, что три точки, не лежащие на одной прямой, однозначно определяют плоскость. Предположим, что P,Q и R — это именно такие три точки в аффинном пространстве. Отрезок, который соединяет Р и Q, есть множество точек, описываемое соотношением
5(a) = аР +(1-а)0,0 < a < 1.
этом отрезке, можно описать,этом отрезке, можно описать,Предположим, что на этом отрезке выбрана произвольная точка и из нее проведен отрезок до точки R, как показано на рис. 4.13. Точки, расположенные на воспользовавшись вторым параметром, Р:
этом отрезке, можно описать,
этом отрезке, можно описать,
RRРис. 4.13. Формирование плоскостиРис. 4.13. Формирование плоскостиДР) = Р5 + (1-Р)/?,0<р<1.
R
R
Рис. 4.13. Формирование плоскости
Рис. 4.13. Формирование плоскости
Эти точки определяются уже парой параметров — а и Р, а их совокупность образует плоскость, заданную точками Р, Q и R. Объединив два предыдущих уравнения, получим уравнение плоскости в параметрическом виде:
Т(а, Р) = P[aP + (l-a)5J + (1-Р)/?.
Полученное уравнение можно переписать в другом виде:
Да, Р) = Р + РО-аХ^-Л + (1-РХЯ-/Д
Учитывая, что Q-Р и R-Р представляют собой векторы, мож
но показать, что плоскость также определяется точкой Ро и парой непараллельных векторов и и v следующим образом:
Т(а, Р) = Ро + оси + Pv.
Несложно прийти к заключению, что при 0 < a, Р < 1 все точки Да, Р) лежат внутри треугольника, образованного вершинами P,Q и R. Если точка Р принадлежит плоскости, то
P-Pq = аи + Pv.
Можно определить вектор п, который будет ортогонален и и, и v. Воспользовавшись векторным произведением
п = и х V,
получим уравнение плоскости в таком виде:
п • (Р-Ро) = 0.
Вектор п перпендикулярен (ортогонален) плоскости; его принято называть нормалью к плоскости. Уравнения прямых, в левой части которых стоит член Р(а), и плоскостей, в левой части которых стоит член Т(а, Р), принято называть уравнениями в параметрической форме, поскольку они определяют точки на прямой или плоскости как функции параметров аир.
Трехмерные примитивы
В трехмерном пространстве значительно возрастает разнообразие геометрических объектов, доступных компьютерной графике. При работе на двухмерной плоскости в примерах, рассмотренных в главе 2, мы имели дело только с отрезками, плоскими кривыми и объектами, которые имели внутреннюю область, в частности многоугольниками. В трехмерном пространстве все эти типы объектов сохраняются, но, во-первых, они могут лежать в разных плоскостях, а во-вторых, к ним добавляются пространственные кривые (рис. 4.14). Помимо плоских объектов с внутренней областью, существуют и аналогичные пространственные объекты — участки криволинейных поверхностей (рис. 4.15). В трехмерном пространстве появляется совершенно новый класс объектов — объемные тела, такие как параллелепипеды и эллипсоиды (рис. 4.16).
Рис. 4.14. ТрехмерныеРис. 4.14. Трехмерные'Рис. 4.15. Трехмерные'Рис. 4.15. ТрехмерныекривыекривыеповерхностиповерхностителателаРис. 4.16. Объемные
Рис. 4.14. Трехмерные
Рис. 4.14. Трехмерные
'Рис. 4.15. Трехмерные
'Рис. 4.15. Трехмерные
кривые
кривые
поверхности
поверхности
тела
тела
При создании графической компьютерной системы, способной работать со всем разнообразием трехмерных объектов, программист неизбежно сталкивается со множеством проблем, из которых наиболее существенны две. Во-первых, математическое описание пространственных объектов оказывается значительно более сложным, чем их двухмерных аналогов (если таковые вообще существуют). Во-вторых, из всего разнообразия трехмерных объектов приходится отбирать такие, которые могут быть эффективно реализованы методами компьютерной графики (имеется в виду как процесс отображения, так и манипулирование объектами). Те типы объектов, которые не попали в это множество, нужно аппроксимировать — приближенно представить объектами отобранных типов.
Трехмерные объекты, работа с которыми может быть реализована существующими аппаратными и программными средствами компьютерной графики, должны обладать следующими свойствами.
Объекты описываются поверхностями и могут рассматриваться как полые.
Количественно объекты полностью характеризуются множеством трехмерных вершин.
Объекты либо состоят из плоских выпуклых многоугольников, либо могут быть аппроксимированы плоскими выпуклыми многоугольниками.
Понять, почему мы ограничили множество реализуемых в графических системах объектов таким образом, можно, если принять во внимание, что эффективнее всего в современных графических системах выполняется отображение треугольников, т.е. простейших плоских многогранников. Графические рабочие станции высокого класса способны выводить на экран до 5 миллионов маленьких тонированных треугольников в секунду. Специализированные платы управления монитором для персональных компьютеров обеспечивают вывод до 1 миллиона тонированных треугольников в секунду. Из первого условия следует, что для
моделирования трехмерных объектов вполне достаточно двухмерных примитивов (плоский многоугольник, даже расположенный в плоскости, произвольно ориентированной в трехмерном пространстве, является все же двухмерным примитивом). Второе условие является, по сути, расширением выводов, сделанных в главах 1 и 2. Если объект количественно полностью характеризуется множеством вершин, он наилучшим образом подходит для реализации в системе с конвейерной архитектурой. Последнее из сформулированных условий следует из обсуждения свойств двухмерных многоугольников. Большинство графических систем оптимизировано с учетом потребностей обработки и отображения точек и многоугольников. В трехмерном пространстве многоугольник определяется упорядоченным множеством вершин, но если таких вершин больше трех, то многоугольник не обязательно является плоским, т.е. все его вершины не всегда лежат на одной плоскости, а следовательно, значительно сложнее определить внутреннюю область этого объекта. Поэтому в подавляющем большинстве графических систем на прикладную программу возлагается ответственность за то, что отображаемые многоугольники являются плоскими. В противном случае не гарантируется корректный результат растрового преобразования. Поскольку треугольник по самой своей природе является плоским многоугольником, то все поверхности в графической системе желательно представлять множеством таких треугольников, причем это может выполняться как на стадии моделирования объектов отображаемой сцены, так и на стадии подготовки объектов к отображению. В последнем случае именно графическая система производит разбиение {tessellation) произвольных многоугольников на треугольные компоненты. Эти же аргументы сохраняются и в отношении криволинейных поверхностей, например сферических. Такие криволинейные поверхности нужно аппроксимировать плоскими многоугольниками, причем проще всего выполняется аппроксимация треугольниками. Следовательно, даже если система моделирования и использует при построении модели криволинейные поверхности, подразумевается, что на одном из следующих этапов обработки выполняется их полигональная аппроксимация.
Исключением из этого подхода является новое направление в компьютерной графике, которое получило название конструктивная геометрия тел — КП" {CSG — constructive solid geometry). В таких системах объект строится из небольшого множества монолитных (объемных) примитивов с помощью операций теории множеств, таких как объединение и пересечение. Мы вернемся к моделированию на базе конструктивной геометрии тел в главе 8. Хотя для решения задач моделирования использование концепции КГТ очень эффективно, отображать созданные модели значительно труднее, чем модели, построенные на базе полигонального разбиения поверхностей. Вполне возможно, что в будущем ситуация изменится, но сейчас мы будем уделять основное внимание методам отображения полигональных моделей объектов.
Все примитивы, с которыми приходится работать в таких моделях, описываются множествами вершин. Если перейти от абстрактных объектов к реальным, нужно сначала определиться с методами представления в графической системе вершин — точек в пространстве.
Системы координат и фреймы
В предыдущих разделах мы рассматривали точки и векторы как абстрактные объекты, не привязываясь ни к какой системе отсчета. В трехмерном векторном пространстве можно однозначно представить любой вектор w в виде линейной комбинации любых трех линейнонезависимых векторов vb v2 и г3 (см. приложение Б):
и- = ар-] + a2v2 + a3v3.
Скаляры ah a2 и a3— это компоненты (или координаты) вектора и- в базисе гь г2, г3. Это отношение показано на рис. 4.17. Можно записать представление вектора и- в некотором базисе в виде матрицы, состоящей из одного столбца:
а = а.
Здесь и далее буквы, набранные полужирным шрифтом, обозначают представление в определенном базисе, в отличие от абстрактного вектора w, который обозначается обычным шрифтом. Это же соотношение можно записать и в виде
w = a
Векторы vb v2, v3, образующие базис, определяют конкретную систему координат. Но
при рассмотрении проблем, в которых принимают участие точки, векторы и скаляры, нам потребуется более общий метод определения системы координат. Суть проблемы
Рис. 4.17. Разложение вектора по векторам базисаРис. 4.17. Разложение вектора по векторам базисаРис. 4.18. Системы координат: а — векторы, исходящие из одной и той же позиции; б — свободные векторыРис. 4.18. Системы координат: а — векторы, исходящие из одной и той же позиции; б — свободные векторыпоясняет рис. 4.18. Систему координат, представленную на рис. 4.18,а, образуют три вектора, причем все они исходят из одной и той же точки. Именно так мы и привыкли рисовать систему координат на бумаге. Эти три вектора можно использовать в качестве базиса для представления любого вектора в трехмерном пространстве. Однако выше уже отмечалось, что векторы как геометрические объекты не имеют точки приложения, а характеризуются только направлением и модулем. Следовательно, векторы на рис. 4.18,а,б эквивалентны. Большинство студентов весьма смущает базис, представленный на правой картинке, хотя с точки зрения чистой математики обе картинки имеют один и тот же смысл. Но у нас по- прежнему остается нерешенной проблема представления точки — геометрического объекта, который имеет фиксированное положение в пространстве.
Рис. 4.17. Разложение вектора по векторам базиса
Рис. 4.17. Разложение вектора по векторам базиса
Рис. 4.18. Системы координат: а — векторы, исходящие из одной и той же позиции; б — свободные векторы
Рис. 4.18. Системы координат: а — векторы, исходящие из одной и той же позиции; б — свободные векторы
Поскольку аффинное пространство включает и точки, то, как только мы зафиксируем определенную точку отсчета — начало координат, в нем можно будет однозначно предста-
вить любую точку. Общепринято изображать оси системы координат как исходящие из точки начала координат (см. рис. 4.18,а). Это имеет смысл в аффинном пространстве, в котором можно представлять как векторы, так и точки. Однако такое представление требует использования точки отсчета и векторов базиса, которые в совокупности называются фреймом (frame). Если не требовать особой строгости в формулировках, то можно считать, что базисные векторы исходят из некоторой точки Ро. В конкретном фрейме любой вектор можно однозначно записать в виде
w = alv1 + a2v, + a3v3,
точно так же, как и в векторном пространстве. Кроме того, для любой точки можно написать соотношение
Р = Ро+П1^1 + Л 2^2 + Пз^.
Таким образом, любой вектор описывается в фрейме тремя скалярами, а описание точки включает три скаляра и данные о точке отсчета. Как будет показано в разделе 4.3.3, отказ от знакомого со школьной скамьи понятия системы координат в пользу менее привычного понятия фрейма позволит избежать трудностей при работе с векторами, которые имеют направление и величину, но не имеют фиксированной точки приложения. Кроме того, концепция фрейма позволяет представлять точки и векторы таким образом, что для манипуляций с ними можно использовать единый математический аппарат матричной алгебры, сохраняя, тем не менее, различие между этими двумя типами геометрических объектов.
Замена систем координат
Очень часто требуется выяснить, как изменится представление вектора при изменении базиса. Предположим, что {vb v2, v3} и {мь иъ м3} — два базиса. Каждый вектор второго базиса можно представить в первом базисе (и наоборот). Следовательно, существует девять скалярных компонентов {уу}, таких, что
«1 =Yhvi+Y12V2+Y13V3,
«2 = Y2IV1 + Y22V2 +Y23V3.
«3 =Y31v, +Y32V2 +Y33V3.
Эти скаляры можно “уложить” в матрицу 3x3:
М =М =Yuy2i.Y3IYuy2i.Y3IY,2 Y13
М =
М =
Yu
y2i
.Y3I
Yu
y2i
.Y3I
Y22 Y23
Y32 Y33
а соотношение между компонентами представить в матричной форме:
Матрица М содержит информацию, необходимую для преобразования представления вектора в одном базисе в представление в другом. Обращение матрицы М дает матрицу, которая позволяет преобразовать представление в базисе {мь иъ w3} в представление в базисе {rb v2, v3}. Пусть вектор зг имеет в базисе {vb v2, v3} представление {ab a2, a3}, т.е.
w = a^i + a2v2 + a3v3.
Это же соотношение можно записать и в матричной форме:
w = а
где
а = а2
Предположим, что b является представлением w в базисе {мь м2, м3}: w = Р^! + Р2и2 + Р3«3
или
w = Ь7w = Ь7“з“з
w = Ь7
w = Ь7
“з
“з
“з
где
Ь =Ь ='IVРзРз
Ь =
Ь =
Таким образом, используя представление во втором базисе в терминах первого, получим:
Следовательно,
а = МГЬ.
Матрица (М7) 1 позволяет перейти от а к Ь:
b = Аа = (МУ'а.
Важность этого результата заключается в том, что он позволяет перейти от рассмотрения абстрактных векторов к операциям с представлениями векторов — матрицами-столбцами, состоящими из скаляров. Но при работе с такими матрицами всегда нужно помнить о том, что за ними “скрывается” определенный базис, иначе мы рискуем оказаться совсем в другой системе координат.
Изменение базиса не затрагивает положение точки начала координат. Такое изменение можно использовать для представления поворота или масштаба набора векторов базиса (рис. 4.19). Однако плоскопараллельное смещение фрейма или перенос начала координат (рис. 4.20) таким способом представить нельзя. После того как мы рассмотрим простой пример, будет введено понятие однородных координат, пользуясь которыми можно с помощью операций над матрицами выполнять комплексное преобразование фреймов.
Рис. 4.19. Поворот и масштабирование базисаРис. 4.19. Поворот и масштабирование базисаПример изменения представления
Рис. 4.19. Поворот и масштабирование базиса
Рис. 4.19. Поворот и масштабирование базиса
Предположим, что имеется вектор м\ представление которого в некотором базисе имеет вид
1
а =а =2
а =
а =
3
Обозначим три вектора базиса соответственно vb v2, v3. Тогда
w = V] + 2v2 + 3v3.
Теперь предположим, что возникла необходимость сформировать новый базис на основе прежнего:
W1 = V),
Z/2 = V] + у2,
U3 = V| + V2 + у3.
Матрица М в этом случае имеет следующий вид:
11М= 11М= 11О о
1
1
М= 1
1
М= 1
1
1 о
1 1
Матрица преобразования представления из базиса {vb v2, v3} в базис {z/b иъ w3} имеет вид
А=(МГ)‘ =А=(МГ)‘ ="1 1 1Т*"1 1 1Т*О 1 1О О 1О 1 1О О 11 -1 1 о 1 -i о о 11 -1 1 о 1 -i о о 1
А=(МГ)‘ =
А=(МГ)‘ =
"1 1 1Т*
"1 1 1Т*
О 1 1
О О 1
О 1 1
О О 1
1 -1 1 о 1 -i о о 1
1 -1 1 о 1 -i о о 1
В новой системе координат представление iv будет иметь вид
-1
Ь = Аа =Ь = Аа =-1
Ь = Аа =
Ь = Аа =
3
Отсюда следует, что w = -их -и2 + 3z/3 .
Однородные координаты
Когда речь заходит о представлении точки Р, имеющей положение (х, у, z), с помощью трехмерного фрейма с началом в точке Ро и базисом {vh v2, v3}, то первой приходит в голову мысль использовать матрицу-столбец
Р =
где х, у, z — компоненты базисных векторов в этой точке, т.е. коэффициенты в соотношении
Р = Ро + XV) + yv2 + zv3.
Но в этом случае точка будет представлена так же, как и вектор
IV = 5|V) + 52V2 + 83v3,
поскольку w представляется столбцом
"5, 8, 5з
В литературе очень часто точка (х,у, z) ассоциируется с вектором, проведенным в эту точку из начала координат. Эта ассоциация может привести к недоразумениям. Например, вектор из точки (1, 1, 1) в точку (2, 3,4) равен вектору, проведенному из точки (0, 0, 0) в точку (1, 2, 3), поскольку оба вектора имеют одинаковый модуль и направление. Но первый вектор нельзя ассоциировать с точкой (1, 2, 3) до тех пор, пока его точка приложения не будет совмещена с началом координат — точкой (0, 0,0).
Пренебрежение различием в представлении точек и векторов может серьезно усложнить реализацию системы, поскольку после этого не удастся представить смещение фрейма как все другие виды трансформаций — с помощью операции перемножения матриц. Поэтому предпочтительнее выбрать такой способ представления, который позволит легко отличать точки от векторов.
Для этого мы воспользуемся так называемыми однородными координатами (homogeneous coordinates). В системе однородных координат для представления точек и векторов трехмерного пространства используются четырехмерные матрицы-столбцы. Во фрейме, заданном набором параметров (vb v2, v3, Ро), любую точку Р можно однозначно представить соотношением
Р = Ро+ ct|V) + a2v2 + a3v3.
Если на множестве точек операцию умножения на скаляры 0 и 1 определить следующим образом:
О • Р = О,
• Р = Р,
то приведенное выше соотношение можно записать в матричной форме с помощью перемножения матриц:
Р = [а, а2 а3 1] Vz .
Строго говоря, это выражение не является скалярным произведением, поскольку элементы матрицы разнородны, но в компьютере такое выражение реализуется той же подпрограммой, что и скалярное произведение. Четырехмерная матрица-строка в правой части уравнения — это представление в однородных координатах точки Р во фрейме, определенном параметрами (vb v2, v3, Ро)- Точку Р можно представить и матрицей-столбцом
г«.1
1
В том же самом фрейме любой вектор w можно представить в виде
w = 5,у,+5,Vj+8,v, = [5j 52 53 о]г
Следовательно, вектор w можно представить матрицей-столбцом
5.
52 5,
О
Существует множество способов геометрической интерпретации этих формул. Для нас же самое важное состоит в том, что, пользуясь формальным аппаратом однородных координат, можно выполнять операции над точками и векторами с помощью обычных операций матричной алгебры. Рассмотрим, например, изменение параметров фрейма — проблему, которую не удается просто решить при использовании обычного трехмерного представления. Если ( vi, v2, v3,Pq) и (z/b и2, z/3, 0 — два фрейма, то векторы базиса второго фрейма и его начало координат можно выразить в терминах первого фрейма следующим образом:
”i =Yiiv1+Y,2V2+YI3v3,
«2 =У21^+У22^+У^3«
^3 — Y 31Vl + Y 32V2 + Y 33V3 ’
С) =Y4,V> + Y42V2 +Y43V3 ^
Эти уравнения можно записать и в матричной форме:
где на сей раз М обозначает матрицу размером 4x4:
"Yu Yi2 Y13 О"
М= Y21 Y22 Y23 ° .
Y3I Y32 Y33 0 ‘
_Y4i Y42 Y43 L
Матрица M называется матрицей представления изменения фреймов.
Матрицу М можно использовать и для непосредственного вычисления изменения представления. Пусть а и b — представления в однородных координатах двух точек (или двух векторов) в разных фреймах. Тогда
Следовательно,
а = М7Ь.
Использование аппарата однородных координат имеет и множество других достоинств, которые мы будем повсеместно использовать по ходу изложения всего материала этой книги. Возможно, наиболее важным из них является то, что все аффинные преобразования (т.е. сохраняющие линейные свойства геометрических объектов) при использовании представления в однородных координатах выполняются единообразно — с помощью перемножения матриц. Хотя для решения задач трехмерного пространства при этом используется четыре измерения, количество арифметических операций в результате даже снижается. Единообразное представление всех аффинных преобразований позволяет, во-первых, довольно просто организовать последовательное выполнение сложных преобразований (конкатенацию преобразований), а во-вторых, реализовать большинство этих операций аппаратно, что значительно повышает скорость обработки.
Пример перехода из одного фрейма в другой
Вернемся к примеру из раздела 4.3.2. Опять начинаем с базиса {vb v2, v3} и преобразуем его в базис {wb w2, и3^ три уравнения для векторов которого имеют вид
«I = Vb
U2 = V| + у2,
U3 = V| + v2 + v3.
Точка отсчета фрейма не меняет своего положения, а потому к этим трем уравнениям добавим четвертое:
Qt = Р».
Нас интересуют матрица преобразования М, обратная ей матрица М 1 и транспонированная матрица М7. Матрица М имеет вид
М =М =ОООООООООО
М =
М =
О
О
О
О
О
О
О
О
О
О
Предположим, что, помимо изменения базиса, требуется изменить и точку отсчета в новом фрейме. Ее положение в прежнем фрейме имеет представление (1, 2, 3, 1). Вектор смещения v=V|+2v2+3v3 сместит Ро в Q^. Ненулевой четвертый компонент означает, что объект является точкой. Таким образом, к трем уравнениям предыдущего примера добавляется четвертое:
Со = ^о+ v,+ 2v2+ 3v3, а матрица М7 примет вид
мгмгО О оО О оо оо о123123
мг
мг
О О о
О О о
о о
о о
1
2
3
1
2
3
Обратная матрица имеет вид
А=(М7) ’ =А=(М7) ’ =ООООООООООО-1О-12-32-3
А=(М7) ’ =
А=(М7) ’ =
О
О
О
О
О
О
О
О
О
О
О
-1
О
-1
2
-3
2
-3
Эти матрицы позволяют преобразовывать представления в разных фреймах в обе стороны. Обратите внимание на то, что матрица А преобразует точку, имеющую в исходном фрейме представление (1,2, 3) или в однородных координатах представление
1
Р =Р =2
Р =
Р =
3
в начало координат нового фрейма:
О'
Р' =Р' =О О
Р' =
Р' =
Но вектор (1,2, 3), который имел в однородных координатах исходного фрейма представление
1
2
3
О
преобразуется в новом фрейме в
О
Этот результат полностью согласуется с тем, который мы получили в примере с изменением систем координат. Он также демонстрирует, как важно отличать точки от векторов при выполнении операций.
Фреймы и абстрактные типы данных
До сих пор мы рассматривали сугубо математические вопросы представления геометрических объектов. Теперь наступила пора “спуститься на грешную землю” и задуматься над тем, как вся эта математика может быть реализована в компьютерной программе. Начнем с понятия об абстрактных типах данных.
В предыдущих разделах мы рассмотрели несколько типов геометрических объектов, в частности точки, прямые и плоскости, исполняющих в графических системах роль примитивов — “кирпичиков”, из которых строятся другие объекты. Поэтому желательно, чтобы графическая система позволяла включить в программу определение переменных в таком, например, виде: point3 p,q;
vector3 v,u;
а затем разрабатывать программу в терминах этих типов данных. В языках программирования, подобных C++, которые поддерживают переопределение (перегрузку) операторов, можно использовать такие выражения:‘
q=p+v;
u=p-q;
Но включение в программу выражений
q=v;
u=p+q;
приведет к появлению сообщения об ошибке. Приведенные выше типы можно реализовать в программе разными способами. В C++ и других объектно-ориентированных языках программирования можно использовать такие средства, как конструкторы, деструкторы и перегрузку операторов, что позволяет скрыть детали реализации от прикладного программиста. Те же, кто работает на языке С, могут определить в программе тип данных point3 следующим образом: typedef float point3[4);
Таким образом, с помощью четырехмерного массива можно ввести в программу представление в однородных координатах. Другой тип typedef float point2[4];
позволяет рассматривать двухмерные графические объекты как частный случай трехмерных. Стандартные операции над геометрическими примитивами можно реализовать в виде набора функций. Такой подход используется в большинстве графических систем (в частности, GKS, PHIGS и OpenGL), однако он не позволяет избежать и определенных трудностей. Рассмотрим, например, проблему инициализации точки. В стандартной программе на языке С напрашивается воспользоваться для этого таким фрагментом кода:
point3 р={1.0,2.0,3.0};
Но это повлечет за собой два нежелательных следствия. Во-первых, это приведет к смешиванию конкретной реализации и абстрактного типа данных. В результате программный код не будет переносим с одной графической системы на другую, т.е. будет зависеть от конкретной реализации. Во-вторых, при такой инициализации “за кадром” остается вопрос, представление в какой системе координат или в каком фрейме формируется этим кодом.
Первую проблему можно решить, разработав спецификацию набора функций, реализующих стандартные абстрактные операции, что избавит прикладного программиста от необходимости разбираться в деталях конкретной реализации. Например, инициализация точки может выполняться вызовом функции р = new_point3(1.0, 2.0, 3.0);
и все операции также выполняются через вызовы специальных функций, как в приведенном ниже фрагменте:
point3 p,q; vector3 v;
v = point_sub(p,q);
Вторая проблема на практике решается значительно сложнее, чем в теории. Как было показано раньше, представление некоторой точки имеет смысл только в том случае, если оно привязано к спецификации фрейма. Следовательно, один из возможных подходов — создать специальный тип данных frame. Графическая система должна позволить программисту либо использовать при определении точки фрейм, специфицированный по умолчанию, либо создать свой и указать, что новая точка “привязывается” к этому фрейму. Функции выполнения стандартных операций могут включать в качестве аргумента явную ссылку на определенный фрейм: point3 p,q;
vector3 v;
frame f;
v = point_sub(p,q,f);
или использовать текущий фрейм, который становится в этом случае таким же параметром текущего состояния системы, как и прочие атрибуты (тип линии, цвет и т.п.).
Фреймы в OpenGL
При работе с OpenGL мы пользуемся двумя фреймами— фреймом камеры и мировым фреймом. Фрейм камеры можно рассматривать как фиксированный. Матрица вида задает положение мирового фрейма относительно фрейма камеры. Таким образом, матрица вида (modelview matrix) преобразует представление точек и векторов в однородных координатах в мировом фрейме в представление в фрейме камеры. Поскольку матрица вида является одним из компонентов текущего состояния графической системы, то, следовательно, в любой момент в системе специфицированы фреймы камеры и мировой. OpenGL поддерживает стек матриц, в котором можно сохранять текущую матрицу вида, или, что то же самое, два фрейма..
Как мы уже говорили в главе 2, камера всегда размещается в точке начала координат своего фрейма. Векторы базиса этого фрейма направлены следующим образом: один, у, вверх по отношению к камере, второй, z, в направлении, обратном направлению визирования камеры, а третий, х, таким образом, чтобы вместе с двумя первыми образовать правостороннюю ортогональную систему координат. Другие фреймы, которые понадобятся нам в процессе размещения объектов сцены, формируются с помощью однородных преобразований относительно фрейма камеры. В разделе 4.5 будет описано, каким образом можно задавать такие преобразования. В главе 5 мы рассмотрим использование этих преобразований при размещении камеры относи-
тельно объектов сцены. Поскольку изменение фрейма представляется матрицей вида и система позволяет сохранять текущее значение матрицы, у прикладного программиста имеется возможность переключаться между фреймами, изменяя текущее значение матрицы вида.
Прежде чем перейти к подробному анализу методов выполнения преобразований и реализации этих методов в OpenGL, рассмотрим еще один простой пример. По умолчанию фрейм камеры совмещен с мировым фреймом, причем ось визирования камеры направлена вдоль оси с, но в обратную сторону (рис. 4.21,а). В большинстве приложений объекты сцены, как правило, размещаются вблизи начала координат. Например, квадрат строится относительно своего центра, а группа объектов — вокруг центра масс. Естественно таким же образом настраивать параметры визуализации, чтобы в поле зрения камеры попадали только объекты, расположенные перед ней. Следовательно, для того чтобы сформировать изображение, в котором будут присутствовать все сформированные объекты, нужно либо отодвинуть камеру от объектов, либо отодвинуть объекты от камеры. Это все равно, что сместить фрейм камеры относительно мирового фрейма. Если же рассматривать фрейм камеры как фиксированный, а матрицу вида — как представляющую положение мирового фрейма относительно фрейма камеры, то матрица вида
10 0 0 0 10 0 0 0 1 -d
0 0 0 1
преобразует точку, имеющую в мировом фрейме координаты (х, у, z), в точку, которая во фрейме камеры имеет представление (х, у, z-d). Если задать d достаточно большое положительное значение, то можно “передвинуть” объекты, чтобы они оказались перед камерой (рис. 4.21,6). Учтите, что пользователь — а именно он имеет дело с мировой системой координат — размещает объекты, как и раньше, недалеко от начала координат. Матрица вида определяет только взаимное положение фреймов. Использовать такой подход гораздо удобнее и логичнее, чем изменять параметры вершин объектов, чтобы разместить их перед камерой.
Рис. 4.21. Фреймы камеры и мировой: а — исходное положение по умолчанию; б—размещение после задания матрицы видаРис. 4.21. Фреймы камеры и мировой: а — исходное положение по умолчанию; б—размещение после задания матрицы вида
Рис. 4.21. Фреймы камеры и мировой: а — исходное положение по умолчанию; б—размещение после задания матрицы вида
Рис. 4.21. Фреймы камеры и мировой: а — исходное положение по умолчанию; б—размещение после задания матрицы вида
В OpenGL матрицу вида можно задать поэлементно, передав 16 чисел в качестве аргументов функции glLoadMatrix(). Однако не ясно, как определить нужные значения элементов. При решении геометрических задач мы всегда мыслим категориями последовательных преобразований (элементарных переходов), таких как плоскопараллельные смешения, повороты и изменения масштаба. Этому подходу мы и последуем в дальнейшем.
Модель разноцветного куба
Выше вы познакомились с инструментарием — математическим аппаратом и средствами его программной реализации, — необходимым для создания трехмерных графических приложений. Как и в рассмотренных примерах двухмерных приложений, мы будем следовать подходу, ориентированному на конвейерную архитектуру системы. Объекты снова определяются набором вершин, которые подвергаются последовательным преобразованиям до того, как соответствующие примитивы будут преобразованы в растр в буфере кадра. В основе всех преобразований лежит математический аппарат однородных координат, который позволяет эффективно реализовать весь процесс обработки графической модели.
В этом разделе будет рассмотрена задача формирования на экране изображения вращающегося куба. (Один кадр такого “мультфильма” представлен на рис. 4.22.) Всю задачу можно разбить на ряд подзадач:
моделирование;
преобразование во фрейм камеры;
отсечение;
проецирование;
удаление невидимых поверхностей;
преобразование в растр.
Ниже мы по очереди рассмотрим каждую из подзадач. Начнем с того, каким образом можно представить трехмерный объект, опираясь на множество его вершин, аналогично тому, как это делалось с двухмерными объектами. Для этого нам потребуется разработать структуру данных, учитывающую связи между вершинами, ребрами и гранями геометрического трехмерного объекта. Такая структура данных в OpenGL поддерживается .массивами вершин (vertex arrays^, о которых речь пойдет в конце данного раздела.
Рис. 4.22. Один кадр анимации вращающегося куба
Разобравшись с методами моделирования куба, мы перейдем к его анимации, используя для этого математический аппарат аффинных преобразований в однородных координатах (подробно они будут рассмотрены в разделе 4.5). С помощью этих преобразований будет изменяться матрица вида OpenGL. В главе 5 мы опять будем использовать эти преобразования в качестве одного из этапов конвейерного процесса визуализации. Описания вершин будут подвергаться последовательным преобразованиям по мере “перемещения по конвейеру”, причем на каждой стадии будет применяться все то же представление вершин в однородных координатах. На выходе конвейера описания объектов, опирающиеся на множество трансформированных вершин, подвергаются растровому преобразованию. На этой стадии можно смело полагать, что все будет выполнено корректно, если предыдущие преобразования были сделаны правильно.
Моделирование куба
Пожалуй, трудно подобрать для первых экспериментов более простой трехмерный объект, чем куб. Но даже такой простой объект можно моделировать в компьютере по- разному. В системе CSG он рассматривается как единый примитив. Но, с другой стороны, куб обрабатывается аппаратными средствами как объект, состоящий из восьми вершин. Поскольку мы повсеместно будем использовать модели, основанные на ограничивающих поверхностях, то куб следует рассматривать либо как совокупность шести пересекающихся плоских поверхностей, либо как шесть многоугольников, которые определяют его грани (facets). Начнем с определения массива вершин, который должен быть доступен всем компонентам программы:
GLfloat vertices[8][3] = {{-1.0,-1.0,-1.0}, {1.0,-1.0,-1.0},
{1.0,1.0,-1.0}, {-1.0,1.0,-1.0}, {-1.0,-1.0,1.0},
{1.0,-1.0,1.0}, {1.0,1.0,1.0}, {-1.0,1.0,1.0}};
Если следовать объектно-ориентированному подходу, то нужно ввести новый тип данных: typedef GLfloat point3[3];
В таком случае вершины куба будут определены в программе следующим образом:
point3 vertices[8] ={{-1.0,-1.0,-1.0},{1.0,-1.0,-1.0},
{1.0,1.0,-1.0}, {-1.0,1.0,-1.0}, {-1.0,-1.0,1.0}, {1.0,-1.0,1.0}, {1.0,1.0,1.0}, {-1.0,1.0,1.0}};
Использование typedef — это вопрос стиля программирования. OpenGL в любом случае реализует каждую вершину в виде четырехмерного массива однородных координат. При вызове функций, для которых задан трехмерный тип данных (таких как glVertex3fv()), значения преобразуются в четырехмерную форму внутри графической системы.
Для определения граней куба можно использовать список точек— элементов массива вершин. Например, одна грань в тексте программы определяется следующим образом: glBegin{GL_POLYGON};
glVertex3fv(vertices[ 0 ]);
glVertex3fv(vertices[3]);
glVertex3fv(vertices[2]);
glVertex3fv(vertices[1]);
glEnd();
Другие пять граней определяются аналогично. Обратите внимание на то, что трехмерный многоугольник в программе определяется точно так же, как и двухмерные в рассмотренных выше примерах.
Внешние и внутренние грани
При определении трехмерных многогранников порядок перечисления вершин имеет очень существенное значение. Во фрагменте программы, приведенном выше, мы перечислили вершины первой грани в таком порядке: 0, 3, 2, 1. Перечисление в порядке 1, 0, 3, 2 приведет к тому же результату, поскольку последняя вершина в списке автоматически соединяется с первой. Однако порядок 0, 1, 2, 3 даст совсем иной результат. Хотя таким образом формируется тот же самый контур многоугольника, ребра будут “обходиться” в обратном направлении, как показано на рис. 4.23. Следует учитывать, что многоугольник (точнее, его внутренняя область) имеет две стороны — внешнюю и внутреннюю. Можно отображать любую из них или обе, но в любом случае нужно располагать каким-то методом идентификации сторон.
Рис. 4.23. Обход вершин многоугольникаРис. 4.23. Обход вершин многоугольникаБудем называть грань смотрящей наружу, или внешней (outward facing), если при взгляде с внешней стороны объекта на эту грань ее вершины, расположенные в том порядке, который задан при определении грани, “обходятся” против часовой стрелки. Этот метод определения порядка вершин некоторого контура известен как правило правой руки (right-hand rule), поскольку, если расположить четыре согнутых пальца правой руки вдоль направления обхода контура, большой палец будет указывать наружную сторону грани.
Рис. 4.23. Обход вершин многоугольника
Рис. 4.23. Обход вершин многоугольника
В нашем примере очень важно перечислить при определении первой грани вершины именно в порядке 0, 3, 2, 1, а не 0, 1,2,3, поскольку в этом случае внешняя сторона многоугольника будет и внешней по отношению к кубу.
Структура данных для представления объектов
Теперь рассмотрим, каким образом описать с помощью множества вершин объемный объект — куб. Можно, например, вызывать функцию формирования многогранника
glBegin(GL_POLYGON)
шесть раз, передавая ей каждый раз свой набор из четырех вершин (посредством glVertex()) и завершая список вызовом glEnd(). Можно использовать другой способ — вызвать glBegin(GL QUADS), а затем передать список из 24 вершин, который завершается glEnd(). Оба эти способа будут восприняты графической системой, но при этом, хотя геометрия куба и будет воспроизведена, информация о его топологии как составного объекта будет утеряна. Если рассматривать куб как многогранник, то он представляет собой совокупность шести граней, которые являются многоугольниками с совпадающими вершинами, — каждая вершина принадлежит трем граням. Каждая пара соседних вершин определяет ребро, которое принадлежит двум граням. Приведенные утверждения и описывают топологию шестигранника. Они правомерны независимо от положения конкретных вершин, т.е. от геометрии объекта.
В дальнейшем мы увидим, насколько существенно формировать структуру данных описания объекта таким образом, чтобы геометрия объекта в ней была отделена от его топологии.
В данном примере для описания геометрии объекта используется список, или массив вершин— vertices[8]. Объектом верхнего уровня иерархии является куб. Мы рассматриваем его как совокупность шести граней, а каждая грань, в свою очередь, описана четырьмя упорядоченными вершинами. Доступ к определенной вершине из списка вершин осуществляется по ее индексу в этом списке. Такая структура данных схематически представлена на рис. 4.24. Одно из ее достоинств состоит в том, что каждая геометрическая позиция определяется только в одном месте и не повторяется в спецификации каждой из трех граней, пересекающихся в соответствующей точке. Таким образом, если по ходу выполнения программы потребуется изменить значения координат точки, это придется сделать только в одном месте, не отслеживая все грани, пересекающиеся в ней.
Многогранник Грани Списки вершинВершиныМногогранник Грани Списки вершинВершины
Многогранник Грани Списки вершинВершины
Многогранник Грани Списки вершинВершины
Рис 4.24. Структура данных описания куба
Цвет куба
Список вершин можно использовать и для того, чтобы хранить информацию, необходимую для раскрашивания куба. С вершинами в данном примере будут ассоциированы чистые цвета вершин цветового куба, о котором шла речь в главе 2 (черный, белый, красный, зеленый, синий, голубой, фиолетовый, желтый). Функция quad() вычерчивает четырехугольник, заданный точками в его списке вершин, а функция colorcube () задает шесть граней таким образом, чтобы все они были внешними.
typedef GLfloat point3[3];
point3 vertices[8] ={{-1.0,-1.0,1.0},(1.0,-1.0,1.0},
{1.0,1.0,1.0}, {-1.0,1.0,1.0}, {-1.0,-1.0,-1.0},
{1.0,-1.0,-1.0}, {1.0,1.0,-1.0}, {-1.0,1.0,-1.0}};
GLfloat colors[8][3] = {{0.0,0.0,0.0},{1.0,0.0,0.0},
{1.0,1.0,0.0}, {0.0,1.0,0.0}, {0.0,0.0,1.0},
{1.0,0.0,1.0}, {1.0,1.0,1.0}, {0.0,1.0,1.0}};
void quad(int a, int b, int c , int d)
{
glBegin(GL_QUADS);
glColor3fv(colors[a]);
glVertex3fv(vertices[a]);
glColor3fv(colors[b]);
glVertex3fv(vertices[b]);
glColor3fv(colors[ c ]);
glVertex3fv(vertices[c]);
glColor3fv(colors[d]);
glVertex3fv(vertices[d]);
glEnd();
void colorcube()
{
quad(0,3,2,l); quad(2,3,7,6); quad(0,4,7,3); quad(l,2,6,5); quad(4,5,6,7); quad(0,1,5,4);
}
Билинейная интерполяция
Недостаточно только указать, каким цветом выводить на экран вершины объекта. Графическая система должна каким-то образом по этим данным раскрасить области, занимаемые на поле изображения гранями объекта, — внутренние области многоугольников. Существует довольно много способов, как “распределить” некоторый атрибут (в данном случае цвет), значение которого фиксировано в вершинах, по внутренней области, ограниченной контуром, построенным на этих вершинах, — интерполировать (interpolate) атрибут. Возможно, наиболее распространенным является метод билинейной интерполяции (bilinear interpolation). Рассмотрим, как этот метод прилагается к задаче интерполяции цвета. Пусть задан четырехугольник, с каждой из вершин которого связан определенный цвет, — Со, Сь С2, С3 (рис. 4.25). Для определения распределения цвета вдоль любого ребра (линии, связывающей соседние вершины) можно воспользоваться линейной интерполяцией:
С0|(а) = (1-а)С0 + аС,, С23(а) = (1-а)С2 + аС3.
Согласно этим уравнениям, при изменении а от 0 до 1 формируются значения цвета С0|(а) и С23(а) точек, расположенных на ребрах, соединяющих вершины 0 и 1 и 2 и 3 соответственно. При данном значении а получаем два значения цвета, С4 и С5, для соответствующих точек на двух этих ребрах. По этим значениям можно выполнить еще одну процедуру интерполяции — на сей раз вдоль линии, соединяющей соответствующие точки на двух противолежащих ребрах:
С45(Р) = (1-Р)С4+РС5.
Если четырехугольник плоский, то каждое значение цвета, сформированное по этим уравнениям, будет соответствовать определенной точке внутренней области многоугольника. Но ес
Рис. 4.25. Билинейная интерполяцияРис. 4.25. Билинейная интерполяцияли четырехугольник не плоский, т.е. четыре его вершины не принадлежат одной плоскости, то, хотя значения цвета и сформируются, положение соответствующих точек на криволинейной поверхности определить так просто не удастся.
Рис. 4.25. Билинейная интерполяция
Рис. 4.25. Билинейная интерполяция
Существует другой алгоритм, интерполяции вдоль строк растра (scan-line interpolation), который позволяет обойти сложности, связанные с формой поверхности, “натянутой” на контур четырехугольника. Этот алгоритм можно включить в процесс растрового преобразования. Многоугольник заполняется цветом только при выводе на экран. Но еще до выполнения растрового преобразования многоугольник проецируется
на двухмерную картинную плоскость (рис. 4.26). Если затем многоугольник закрашивается по
строкам растра, строка за строкой, как показано на рис. 4.27, то для распределения цвета
Рис. 4.27. Интерполяция вдоль строк растраРис. 4.27. Интерполяция вдоль строк растравдоль строки растра используются только два противолежащих ребра изображения. OpenGL поддерживает этот метод не только для цветов, но и для других атрибутов, которые можно ассоциировать с вершинами. Подробнее об этом — в главе 6.
Рис. 4.27. Интерполяция вдоль строк растра
Рис. 4.27. Интерполяция вдоль строк растра
Центр проецирования
Рис. 4.26. Проецирование многоугольника
Итак, на этой стадии у нас есть объект, который можно отобразить на экране тем же способом, который был использован для вывода трехмерного узора Серпинского в главе 2. Как вы, надеюсь, помните, тогда использовалась ортографическая проекция, которая формируется функцией glOrtho(). В разделе 4.5 будут рассмотрены геометрические преобразования, после знакомства с которыми можно будет “оживить” куб на экране, а также создавать более сложные объекты. Но сначала я познакомлю вас с новым средством поддержки операций с вершинами, которое появилось в версии OpenGL 1.1. Оно позволяет не только сократить объем программного кода, но и предоставляет в распоряжение прикладного программиста методы высокого уровня для работы с многогранниками.
Массивы вершин
При вычерчивании куба, в модели которого используются списки вершин, приходится довольно много раз вызывать различные функции OpenGL. Если с каждой вершиной ассоциируется определенный цвет, то нужно выполнить 60 вызовов функций OpenGL: 6 граней, формирование каждой из которых требует вызовов glBegin() и glEnd(), 4 вызовов glColor() и 4 вызовов glVertex(). Каждый вызов функции влечет за собой накладные расходы и пересылку данных. Как будет показано в главе б, при вычерчивании объекта также необходимо задавать векторы нормалей для каждой вершины, и в результате количество вызовов при формировании изображения куба даже превышает указанную цифру. Следовательно, хотя описанная структура данных и позволяет инкапсулировать информацию о геометрии и топологии куба как трехмерного объекта, соответствующий программный код будет выполняться не так быстро, как нам хотелось бы.
Массивы вершин (vertex arrays') в OpenGL обеспечивают значительное повышение скорости вычерчивания трехмерных объектов, состоящих из многоугольников, сохраняя при этом возможность инкапсуляции их внутренней топологической структуры. Использование такой структуры данных предусматривает три этапа. Первый — разрешение работы с механизмом обслуживания массивов векторов. Второй — передача исполнительной системе OpenGL информации о том, где и в каком формате содержатся в программе массивы вершин. Третий — формирование объектов с использованием этих массивов. Две первые операции выполняются в процессе инициализации программы, а третья, как правило, реализуется с помощью функций с обратным вызовом. Ниже будет показано, как это делается на примере формирования куба.
Пакет OpenGL позволяет работать с шестью специальными видами массивов — вершин, цветов, индексов цветов, координат текстур и флагов ребер, — соответствующих шести операторам между glBegin() и glEnd(). Но, как правило, в отдельном приложении все эти типы не используются. В программе формирования цветового куба нам потребуются только массивы цветов и вершин. Механизм поддержки работы с этими типами массивов инициализируется следующими вызовами функций OpenGL:
glEnableClientState(GL_COLOR_ARRAY);
glEnableClientState(GL_VERTEX_ARRAY);
Сами по себе массивы те же, что и раньше, причем объявляются они в статусе глобальных:
GLfloat vertices[] = {{-1.0,-1.0,-1.0}, {1.0,-1.0,-1.0},
{1.0,1.0,-1.0}, {-1.0,1.0,-1.0}, {-1.0,-1.0,1.0},
{1.0,-1.0,1.0}, {1.0,1.0,1.0}, {-1.0,1.0,1.0}};
GLfloat colors[] = {{0.0,0.0,0.0}, {1.0,0.0,0.0}, {1.0,1.0,0.0}, {0.0,1.0,0.0}, {0.0,0.0,1.0}, {1.0,0.0,1.0}, {1.0,1.0,1.0}, {0.0,1.0,1.0}};
Следующая операция — передать OpenGL информацию о том, где находятся массивы. Это выполняется следующим образом:
glVertexPointer(3, GL_FLOAT, 0, vertices);
glColorPointer{3, GL_FLOAT, 0, colors);
Первые три аргумента несут информацию о том, что элементы массивов являются соответственно трехмерными цветами и вершинами, которые сохраняются в формате float, и что элементы образуют непрерывный массив. Четвертый аргумент — указатель на определенный массив.
Теперь нужно включить в структуру данных информацию об отношениях между вершинами и гранями в кубе. Для этого организуется массив из 24 упорядоченных индексов вершин для шести граней:
GLubyte cubeindices[24]={0,3,2,1,
,3,7,6,
0,4,7,3,
,2,6,5,
,5,б,7, 0,1,5,4};
Первая грань имеет вершины с индексами (0, 3, 2,1), вторая — (2, 3, 7,6) и т.д. Обратите внимание на то, что при упорядочении индексов вершин нужно учитывать порядок их обхода в многоугольнике при взгляде с внешней стороны куба. Массив индексов вершин также должен иметь статус глобального.
Теперь можно приступить к формированию трехмерного объекта — в данном случае куба — на основе информации из этих массивов. При формировании изображения обрабатываются все элементы объявленных массивов (в данном случае — цветов и вершин). Имеется несколько опций настройки, касающихся режима обработки массивов при отображении. Для настройки используется функция
glDrawElements(type, n, format, pointer)
Аргументы функции имеют следующий смысл:
type — тип элемента, например линия или многоугольник, который определяется массивом;
п — количество элементов, которые планируется отобразить;
format — описывает формат данных в индексном массиве;
pointer — указатель на первый используемый индекс.
При формировании изображения куба в функции отображения display () нужно шесть раз вызвать функцию glDrawelements(), по одному разу на каждую грань:
for(i=0;i<6;i++)
glDrawElements(GL_POLYGON, 4, GL_UNSIGNED_BYTE, &cubelndex[4*i]);
Таким образом, после формирования и инициализации массивов в процессе отображения придется выполнить только шесть вызовов функций OpenGL вместо пятидесяти с лишним. Поворачивать изображение куба можно тем же способом, что и раньше, поскольку функция glDrawElements() использует при формировании изображения параметры текущего состояния, в том числе и матрицу вида.
Можно еще больше упростить процесс отображения, если учесть, что каждая грань куба является именно четырехугольником, а не многоугольником общего вида. Тогда можно использовать в функции glDrawelements()тип GL_QUADS вместо GL_POLYGON и вызывать эту функцию в display () только один раз вместо шести вызовов при вычерчивании многоугольников общего вида:
glDrawElements(GL_QUADS, 24, GL_UNSIGNED_BYTE, cubeindices);
Если в glDrawelements () используется тип GL_QUADS, то вычерчивание следующего четырехугольника начинается автоматически после завершения обработки каждой очередной четверки вершин.
Аффинные преобразования
Под преобразованием {трансформацией — transformation) в дальнейшем будем понимать функцию, которая принимает (точку или вектор) и отображает ее на другую точку (или вектор). Графически смысл преобразования представлен на рис. 4.28. Для точек преобразование формально записывается в виде
Q=T{P),
а для векторов — в виде
v = R{u).
При использовании однородных координат и векторы, и точки представляются в виде четырехмерных матриц-столбцов. В таком случае преобразование имеет единообразный вид и для точек, и для векторов одного и того же фрейма:
q =Лр),
v =Л«).
Эта формулировка носит слишком общий характер, чтобы ее можно было использовать на практике, поскольку она распространяется на любые однозначные отображения точек и векторов. На практике, даже располагая удобным описанием функции Д), нужно применять преобразование ко всем точкам кривой. Если, например, трансформируется отрезок прямой, обобщенное преобразование придется применить к каждой точке между двумя заданными конечными.
Рис. 4.28. ПреобразованиеРис. 4.28. ПреобразованиеМы будем рассматривать ограниченный класс преобразований в четырехмерном пространстве однородных координат. В этом пространстве и точки, и векторы представляются как вершины, т.е. как наборы из четырех чисел. Этот класс можно определить формально, наложив определенные ограничения на вид функции Д). Наиболее важное ограничение — линейность преобразования. Функция^) является линейной тогда и только тогда, когда для любых скаляров а и 0 и для любых вершинpnq выполняется соотношение
Рис. 4.28. Преобразование
Рис. 4.28. Преобразование
Дар + р^) = аДр) + ^Дд).
Особое значение такого свойства преобразований состоит в том, что если известно преобразование вершин, то всегда можно получить и преобразование линейной комбинации вершин, используя для этого линейную комбинацию преобразований. В результате отпадает необходимость в повторном вычислении преобразований вершин при определении преобразования их линейной комбинации.
В четырехмерном пространстве мы работаем с представлениями точек и векторов. Линейное преобразование, которое трансформирует представление точки (или вектора) в другое представление, всегда может быть записано в терминах двух представлений и и v с помощью перемножения матриц:
v = Au,
где А — квадратная матрица. Сравнивая это выражение с полученным в разделе 4.3 для операции изменения фрейма, приходим к выводу, что, если А невырождена, любое линейное преобразование соответствует изменению фрейма. Следовательно, линейное преобразование можно рассматривать с двух точек зрения: как изменение представления или фрейма, которое приводит к новому представлению вершин, или как преобразование вершин в рамках того же фрейма.
При выполнении операций в однородных координатах А представляет собой матрицу 4x4, которая оставляет неизменным четвертый компонент представления (и ). Матрица А имеет вид
«и «t2(Х130С|4
Д _ «21«22«23«24
«31«32«33«34
0001
Двенадцать элементов матрицы могут изменяться произвольным образом, и мы говорим о таком преобразовании, что оно имеет 12 степеней свободы (degrees of freedom). Но точки и векторы в аффинном пространстве имеют немного другое представление. Любой вектор можно представить в виде
а, а2 а3
0
а любую точку — в виде
ГМ
_ 1
Если рассмотреть применение произвольного преобразования А по отношению к вектору
v = Au,
то придем к выводу, что на результат влияют только 9 элементов матрицы А, т.е. преобразование векторов обладает только девятью степенями свободы. Аффинные преобразования точек обладают всеми двенадцатью степенями свободы.
Несложно показать, что аффинное преобразование преобразует прямую линию в прямую линию. Предположим, что прямая линия записана в виде
Р(а) = Pq + ad,
где Ро — точка, ad — вектор. В некотором фрейме прямая выражается с помощью представления ро и d соответственно, точки Ро и вектора d в этом фрейме:
р(а) = ро + ad.
Для любой матрицы аффинного преобразования А справедливо соотношение
Ар(а) = Ар0 + a Ad.
Таким образом, можно построить преобразование прямой, выполнив сначала преобразование Ро и d, а затем воспользоваться любым алгоритмом формирования прямой. Если используется форма представления отрезка двумя точками
p(a) = ap0 + (l-<x)pi,
получим аналогичный результат— нужно сначала выполнить преобразование р0 и рь а затем по ним построить преобразованный отрезок. Поскольку в матрице А имеется 12 элементов, которые можно выбирать произвольно, то аффинное преобразование прямой или прямолинейного отрезка имеет 12 степеней свободы.
До сих пор мы рассуждали в терминах абстрактного математического пространства. Однако эти рассуждения имеют совершенно конкретное приложение к практическим проблемам компьютерной графики. Из них следует, что для построения в системе компьютерной графики изображения преобразованного отрезка необходимо выполнить только преобразование представлений в однородных координатах конечных точек этого отрезка, а, следовательно, графическую систему можно реализовать в виде конвейера аффинных преобразований представлений отдельных точек в однородных координатах.
К счастью, подавляющее большинство преобразований, которые используются в компьютерной графике, можно свести в конечному множеству аффинных преобразований. Это множество включает вращение, плоскопараллельное смещение (сдвиг) и масштабирование. Внеся небольшие изменения, таким же образом можно выразить и проективные преобразования — параллельное и перспективное.
Поворот, сдвиг и масштабирование
Рассматривая преобразования, нам придется все время переключаться от геометрических объектов как абстрактных сущностей к их конкретным представлениям в определенных фреймах. В прикладной программе мы имеем дело с представлениями объектов. В этом раз-
деле сначала будет показано, что наиболее важные аффинные преобразования можно сформулировать независимо от конкретного представления. Затем будут выведены матрицы этих преобразований, которые позволяют работать с представлениями точек и векторов. О том, как эти преобразования реализованы в OpenGL, рассказывается в разделе 4.8.
Мы рассматриваем преобразования как способ перемещения группы точек, описывающих один или несколько геометрических объектов, в новую позицию. Хотя существует множество способов выполнить перемещение отдельной точки, оказывается, что имеется только один способ однообразно выполнить такое преобразование по отношению к множеству точек, сохранив при этом отношения между вершинами в объекте.
Плоскопараллельное смещение
Плоскопараллельное смещение, или сдвиг (translation),— это операция, которая смещает точки на фиксированное расстояние вдоль заданного направления (рис. 4.29). Сдвиг задается только вектором смещения d, поскольку для всех точек Р на объекте справедливо соотношение
Р'=РЧ.
Обратите внимание на то, что такое определение операции никак не связано с системой отсчета или фреймом представления. Сдвиг имеет 3 степени свободы, поскольку можно произвольно задать три компонента вектора смешения. - - -
б — сдвинутый объектб — сдвинутый объект
б — сдвинутый объект
б — сдвинутый объект
Рис. 4.30. Поворот в двухмерной системе координатРис. 4.30. Поворот в двухмерной системе координат
Рис. 4.30. Поворот в двухмерной системе координат
Рис. 4.30. Поворот в двухмерной системе координат
Поворот
Задать поворот (rotation) несколько сложнее, чем сдвиг, так как требуется специфицировать больше параметров. Начнем с простого примера поворота точци вокруг начала координат в двухмерной плоскости, как показано на рис. 4.30. Поскольку задана определенная точка — начало координат, — мы имеем дело с определенным фреймом. Двухмерная точка (х, у) в этом фрейме поворачивается вокруг начала координат на угол 6 и занимает после поворота позицию (х', уЗ- Формулы описания поворота несложно получить, воспользовавшись полярным представлением точек (х, у) и (х', у*):
х = р созф, у = р зтф, х'= р cos(0 + ф), у* = р sin(0 + ф).
Воспользовавшись формулами приведения синуса и косинуса суммы углов, получим
х' = р созф cos0 - р зтф sin0 = х cos0-у sin0, у' = р созф sin0 + р $тф COS0 = х sinO + у соз0.
Эти же уравнения можно записать и в матричной форме:
ШШCOS0 sin 6COS0 sin 6-sin 6 хcos 6 у-sin 6 хcos 6 у
Ш
Ш
COS0 sin 6
COS0 sin 6
-sin 6 хcos 6 у
-sin 6 хcos 6 у
О том, как можно расширить эти формулы на трехмерный случай, вы узнаете в разделе 4.7.
Обратите внимание на три свойства этого преобразования, которые сохраняются и в других вариантах операции поворота.
Существует некоторая точка, она называется фиксированной точкой (fixed point) преобразования (в приведенном выше примере— начало координат), которая остается неподвижной при применении к ней этого преобразования. На рис. 4.31 показано, какой результат получится при повороте вокруг фиксированной точки, отличной от начала координат.
Учитывая, что двухмерное пространство является частным случаем трехмерного, можно распространить сделанные выводы и на три измерения. В правосторонней системе координат, когда оси х и у выглядят привычным для всех нас образом, положительное направление оси z трехмерной системы координат будет направлено на наблюдателя (т.е. ось z “исходит” из листа бумаг, на котором начерчены оси х и у). Положительное направление поворота — это направление против часовой стрелки, если смотреть со стороны положительной полуоси z, исходящей из фиксированной точки. Это же определение положительного направления поворота мы будем использовать и для трехмерного случая при повороте вокруг произвольной оси.
Двухмерное вращение на плоскости эквивалентно трехмерному вращению вокруг оси z. Все точки на плоскости, параллельной ху (т.е. имеющие одинаковое значение компоненты с), поворачиваются одинаково, сохраняя значение компонента z.
Рис. 4.31. Поворот вокруг фиксированной точкиРис. 4.31. Поворот вокруг фиксированной точки
Рис. 4.31. Поворот вокруг фиксированной точки
Рис. 4.31. Поворот вокруг фиксированной точки
Эти свойства мы используем при выводе обобщенного преобразования поворота в трехмерном пространстве, не зависящего от конкретного фрейма. Для такого преобразования (рис. 4.32) нужно специфицировать три параметра — фиксированную точку (Pj), угол поворота (0) и прямую или вектор, вокруг которого выполняется поворот (ось вращения). Если фиксированная точка задана, то в нашем распоряжении имеются 3 степени свободы: два угла необходимы для задания ориентации оси вращения и один угол задает сам поворот.
Поворот и сдвиг относятся к группе так называемых изометрических преобразований (или преобразований твердого тела — rigid-body transformations). Комбинация этих преобразований не может изменить формы объекта, а изменяет только его положение в пространстве — позицию и ориентацию. Следовательно, сами по себе повороты и смещения не дают
полного набора аффинных преобразований. Преобразования, представленные на рис. 4.33, также являются аффинными, но не относятся к группе изометрических преобразований.
Рис. 4.32. Трехмерный поворотРис. 4.32. Трехмерный поворотРис. 4.33. Анизометрические преобразованияРис. 4.33. Анизометрические преобразования
Рис. 4.32. Трехмерный поворот
Рис. 4.32. Трехмерный поворот
Рис. 4.33. Анизометрические преобразования
Рис. 4.33. Анизометрические преобразования
Рис. 4.34. Пропорциональное и неравномерное масштабированиеРис. 4.34. Пропорциональное и неравномерное масштабированиеРис. 4.35. Параметры масшта-Рис. 4.35. Параметры масшта-бированиябирования
Рис. 4.34. Пропорциональное и неравномерное масштабирование
Рис. 4.34. Пропорциональное и неравномерное масштабирование
Рис. 4.35. Параметры масшта-
Рис. 4.35. Параметры масшта-
бирования
бирования
Масштабирование
Масштабирование (scaling) представляет собой анизометрическое аффинное преобразование. Практически любое аффинное преобразование можно свести к суперпозиции преобра
зований масштабирования, поворота и сдвига. Преобразование масштабирования увеличивает или уменьшает размеры объекта. На рис. 4.34 показаны два варианта масштабирования — пропорциональное (равномерное во всех направлениях или преобразование подобия) и растяжение только в одном направлении. Последний вариант также понадобится включить в набор элементарных преобразований, которыми мы будем пользоваться в дальнейшем при моделировании и визуализации объектов.
Как видно на рис. 4.35, преобразование масштабирования также имеет фиксированную точку. Таким образом, набор параметров этого преобразования включает фиксированную точку, направление, вдоль которого изменяется масштаб, и масштабный множи
тель (а). При а > 1 объект растягивается в заданном направлении, а при 0 < а <1 объект сжимается. При отрицательном значении а происходит отражение (reflection) объекта относительно заданной фиксированной точки в указанном направлении (рис. 4.36).
Рис. 4.36. Отражение
Преобразования в однородных координатах
В большинстве графических API приходится работать в определенной системе отсчета (системе координат). Хотя программист и может изменить эту систему координат (как правило, фрейм), он не может работать с представлениями на высоком уровне, например с выражениями вида
Q = Р + av.
Вместо этого программист имеет дело с представлениями в однородных координатах и выражениями вида
q = р + цу.
4x4 вида"«и«12д= «21«22«31«32О О4x4 вида"«и«12д= «21«22«31«32О ОВ некотором фрейме любое аффинное преобразование может быть представлено матрицей
4x4 вида
"«и«12
д= «21«22
«31«32
О О
4x4 вида
"«и«12
д= «21«22
«31«32
О О
«•з «и'
«23«24
«33«34'
О 1
Сдвиг
Преобразование сдвига смещает точки в новые позиции в соответствии с заданным вектором смещения. Если точка р сдвигается в р' на расстояние d, то такое преобразование можно записать в виде
Р* = Р + d
В однородных координатах р, р' и d выражаются следующим образом:
Тогда эти уравнения можно записать в виде отдельных выражений для каждого компонента:
х' = х+ Оу,
У* = У^^
c' = z + a_.
Метод представления преобразования сдвига посредством суммирования матриц-столбцов плохо сочетается с другими аффинными преобразованиями. Существует и другой метод представления сдвига — с помощью перемножения матриц:
Р' = Тр,
где
'10 0 а/
0 10а Т=.
0 0 1 аг
.0 0 0 1
Матрица Т называется матрицей сдвига (translation matrix). Иногда ее записывают в виде Т(а„ ос,, а.), чтобы подчеркнуть три независимых параметра преобразования.
На первый взгляд может показаться, что четвертый элемент в матрицах-столбцах излишний, но оказывается, что при использовании трехмерной версии выражения р' = Тр тот же результат не получается. Поэтому иногда применение однородных координат для выполнения преобразования сдвига рассматривается как своего рода трюк, позволяющий заменить суммирование трехмерных матриц-столбцов перемножением четырехмерных матриц.
Матрицу обратного преобразования можно сформировать либо с помощью алгоритма обращения матриц, либо приняв во внимание, что обратное преобразование — это сдвиг на расстояние -d. В любом случае получим
1 О1 ОТ"|(ах,а>.,а.) = Т(-аЛ,-ау,-а.) =Т"|(ах,а>.,а.) = Т(-аЛ,-ау,-а.) =ОО1О О1О ОО -а, О -ау 1 -а:О 1О -а, О -ау 1 -а:О 1
1 О
1 О
Т"|(ах,а>.,а.) = Т(-аЛ,-ау,-а.) =
Т"|(ах,а>.,а.) = Т(-аЛ,-ау,-а.) =
О
О
1
О О
1
О О
О -а, О -ау 1 -а:
О 1
О -а, О -ау 1 -а:
О 1
Масштабирование
Как уже говорилось выше, преобразования поворота и масштабирования характеризуются фиксированной точкой, которая остается неподвижной при выполнении этих преобразований. Сейчас будем считать, что фиксированной точкой является начало координат, а в дальнейшем покажем, как с помощью суперпозиции базовых преобразований можно сформировать преобразование масштабирования относительно произвольной фиксированной точки.
Матрица преобразования масштабирования, имеющего фиксированную точку в начале координат, позволяет задавать масштабные коэффициенты по каждой из координатных осей независимо друг от друга. Имеем три уравнения:
х' =
У =
которые можно выразить в однородных координатах матричным уравнением р* = Sp, где
$=5(р,.р,д)=$=5(р,.р,д)=О оО оор,оор,оо оо оо о о о Р; ОО 1о о о о Р; ОО 1
$=5(р,.р,д)=
$=5(р,.р,д)=
О о
О о
ор,о
ор,о
о о
о о
о о о о Р; О
О 1
о о о о Р; О
О 1
В этой матрице, а также в матрице сдвига и других матрицах преобразований однородных координат четвертая строка не зависит от характера преобразования, а служит для того, чтобы после выполнения перемножения сохранялось значение 1 в четвертом компоненте точки.
Для обращения матрицы масштабирования необходимо использовать обратные значения масштабных коэффициентов по осям:
^^М-И^- 57
Поворот
Сначала рассмотрим поворот вокруг начала координат, т.е. будем считать, что фиксированная точка преобразования поворота находится в начале координат. Поскольку поворот можно выполнять независимо вокруг каждой из осей координат, это преобразование имеет три степени свободы. Но нужно учитывать, что перемножение матриц не является коммутативной операцией (см. приложение В). Если за поворотом вокруг оси х на угол 0 следует поворот вокруг оси у на угол ф, то результат будет отличаться от полученного при выполнении этих же преобразований в другой последовательности.
Чтобы найти матрицу поворота вокруг отдельной оси системы координат, можно использовать результат, полученный при анализе поворота в двухмерной системе (см. раздел 4.6). Мы уже видели, что поворот в двухмерной системе фактически является поворотом вокруг оси z трехмерной системы координат и что после выполнения такого преобразования точки остаются на той же плоскости, т.е. компонент z точки не изменяется. Следовательно, уравнения поворота вокруг оси z на угол 0 в трехмерном пространстве можно записать следующим образом:
x' = xcos0-ysin0,
y' = xsin0 + ycos0,
или в матричном виде:
Р=КР,
где
COS0
-sin©
0
0‘
R; = R;(e)=
sin0
COS0
0
0
0
0
1
0
0
0
0
1
Рассуждая аналогично, можно сформировать и матрицы поворота вокруг осей х и у. При повороте вокруг оси х значения компонентов х точек остаются неизменными, и получаем двухмерный поворот в плоскости х = const. При повороте вокруг оси у сохраняются значения компоненту точек. Матрицы этих поворотов имеют вид
10
0
0'
R =Rx(0) =
0 cos0
-sin0
0
0 sin0
COS0
0
.° 0
0
1
cosO 0
sin0
0"
R =R(Q) =
01
0
0
-sin© 0
COS0
0
. 0 0
0
1
Знак элементов матриц, содержащих sin, соответствует принятому определению положительного направления поворота в правосторонней системе координат.
Обозначим через R любую из трех приведенных выше матриц поворотов вокруг осей системы координат. После поворота на угол 0 преобразованные точки всегда можно вернуть в исходное состояние, выполнив поворот вокруг той же оси на угол -0. Следовательно,
R '(0) = R(-0).
Обратите внимание и на то, что элементы, содержащие cos, всегда размешаются на главной диагонали матрицы, а пара элементов, содержащих sin, — по разные стороны от главной диагонали.
Воспользовавшись тригонометрическими тождествами cos(-0) = cosO и sin(—G) = -sinO, получим, что
R '(0) = Rr(0).
В разделе 4.8 будет показано, как сформировать матрицу поворота вокруг произвольной оси с фиксированной точкой в начале координат, используя перемножение матриц поворота вокруг отдельных осей системы координат:
R = R^R. R •
Учитывая, что преобразование произведения есть произведение преобразований в обратном порядке, можно показать, что для любой матрицы поворота выполняется соотношение
R 1 = R7.
Матрицы, для которых операция транспонирования дает тот же результат, что и обращение, называются ортогональными {orthogonal matrix); любая ортогональная матрица описывает некоторое преобразование поворота с фиксированной точкой в начале координат.
Скос
Хотя любое аффинное преобразование можно свести к последовательности поворотов, сдвигов и изменений масштаба, существует один вид преобразования, который настолько важен в компьютерной графике, что мы будем рассматривать его также как базовый тип. Это преобразование скоса {shear). Рассмотрим куб, центр которого находится в начале системы координат, а ребра параллельны осям координат (рис. 4.37). Если сместить верхнюю грань куба вправо, а нижнюю — влево, объект будет скошен в направлении оси х. Учтите, что при этом компоненты у и z всех точек на объекте остаются неизменными. Мы будем называть показанное на рисунке преобразование х-скосом, чтобы отличать его от скоса в другом направлении. Воспользовавшись простыми тригонометрическими соотношениями, которые поясняются на рис. 4.38, можно охарактеризовать преобразование такого типа единственным параметром — углом скоса 0. Уравнения преобразования имеют вид
х' = х + yctg0,
У' = У,
откуда следует вид матрицы скоса:
ctgO 0 0
000 1
Матрицу обратного преобразования несложно получить, если учесть, что следует выполнить скос в обратном направлении; следовательно,
Нх '(0) = Н/(-0).
Рис. 4.37. Преобразование скосаРис. 4.37. Преобразование скосаРис. 4.38. К определению матрицы скосаРис. 4.38. К определению матрицы скоса
Рис. 4.37. Преобразование скоса
Рис. 4.37. Преобразование скоса
Рис. 4.38. К определению матрицы скоса
Рис. 4.38. К определению матрицы скоса
Суперпозиция преобразований
В этом разделе продемонстрировано, как формировать матрицу сложного аффинного преобразования, перемножая матрицы отдельных базовых (канонических) преобразований. Выражаясь языком математики, можно сказать, что сложное преобразование есть суперпозиция базовых преобразований (concatenating), которые были рассмотрены в предыдущем разделе. Использованию такой стратегии формирования матрицы сложного преобразования следует отдать предпочтение перед непосредственным вычислением элементов матрицы. Этот подход хорошо “вписывается” в конвейерную архитектуру графической системы.
Предположим, что выполняются три последовательных преобразования точки р и при этом формируется новая точка q. Поскольку произведение матриц ассоциативно, можно записать такую последовательность преобразований в виде
q = СВАр,
без использования скобок. Однако порядок выполнения операций существенно влияет на производительность процесса вычисления. Например, можно сначала умножить матрицу А, затем на полученный результат умножить матрицу В, а потом умножить матрицу С (этот порядок соответствует группировке, представленной на рис. 4.39):
q = (С(В(Ар))).
Рис. 4.39. Последовательное выполнение преобразованийРис. 4.39. Последовательное выполнение преобразований
Рис. 4.39. Последовательное выполнение преобразований
Рис. 4.39. Последовательное выполнение преобразований
Если выполняется преобразование единственной точки, то этот порядок наиболее эффективен, поскольку на каждом шаге квадратная матрица умножается на матрицу-столбец. Но если необходимо преобразовать множество точек, то операцию следует реализовать в два этапа. Сначала вычислять произведение квадратных матриц отдельных преобразований:
М = СВА,
а затем использовать полученную матрицу для обработки всего множества точек:
q = Мр.
Этот порядок реализуется в графическом конвейере, как показано на рис. 4.40. Сначала вычисляется матрица произведения М, затем она загружается в ту секцию конвейера, ко-
Рис. 4.40. Выполнение преобразований в графическом конвейереРис. 4.40. Выполнение преобразований в графическом конвейереторая выполняет преобразование. Если посчитать количество операций, то окажется, что вычисление матрицы М требует несколько большего числа операций, чем (С(В(Ар))), но затем при обработке каждой из сотен или тысяч точек требуется выполнить только одну операцию умножения квадратной матрицы на матрицу-столбец. Ниже на примерах будет показано, как вычислять матрицу М.
Рис. 4.40. Выполнение преобразований в графическом конвейере
Рис. 4.40. Выполнение преобразований в графическом конвейере
Поворот вокруг произвольной фиксированной точки
В первом примере будет показано, как формировать матрицу преобразований поворота вокруг произвольной фиксированной точки, используя канонические матрицы поворота вокруг начала координат. Направление оси поворота в примере совпадает с направлением координатной оси z, но этот же метод можно использовать и при другом направлении оси.
Рассмотрим куб, центр которого находится в точке ру, а ребра параллельны осям координат. Требуется повернуть куб вокруг оси г, но сохранить неизменным положение центра (рис. 4.41). Если бы точка ру совпадала с началом координат, мы могли бы воспользоваться уже сформированной матрицей R.(0). Отсюда следует, что сначала нужно сдвинуть куб таким образом, чтобы его центр оказался в начале координат, а затем воспользоваться матрицей R;(0). Последняя операция — сдвинуть куб так, чтобы его центр занял прежнее положение ру. Эта последовательность преобразований представлена на рис. 4.42. Используя введенные раньше обозначения, нужно сначала выполнить преобразование Т(-ру), далее — R.(0), а последним выполнить Т(ру). В результате суперпозиции этих преобразований получим матрицу
м = т(ру) r.(0) т(-ру). .
Рис. 4.41. Поворот куба вокруг собственного центраРис. 4.41. Поворот куба вокруг собственного центра
Рис. 4.41. Поворот куба вокруг собственного центра
Рис. 4.41. Поворот куба вокруг собственного центра
После перемножения матриц получим:
cos0 -sinO 0 Ху-XyCosO + yySinO sin0 cos0 0 уу-Ху sin0-yzcos0
0010
ООО1
Рис. 4.42. Последовательность канонических преобразованийРис. 4.42. Последовательность канонических преобразований
Рис. 4.42. Последовательность канонических преобразований
Рис. 4.42. Последовательность канонических преобразований
Поворот вокруг произвольной оси
Теперь покажем, как можно представить поворот вокруг произвольной оси в виде суперпозиции трех поворотов вокруг осей координат. Порядок выполнения канонических преобразований в этом случае не однозначен (см. упр. 4.10), хотя результат будет однозначным. В примере начало координат выбрано в качестве фиксированной точки преобразования. Первой операцией будет поворот вокруг оси z, затем поворот вокруг оси у и последним — поворот вокруг ОСИ X.
Рассмотрим куб, центр которого находится в начале координат, а ребра параллельны осям системы координат, как показано на рис. 4.43,а. Сначала повернем его вокруг оси z на угол а таким образом, чтобы ориентация граней куба стала такой, как на рис. 4.43,6. Затем повернем куб на угол Р вокруг оси у, как показано на рис. 4.44. Последний поворот — на угол у вокруг оси х, как показано на рис. 4.45.
У У
Рис. 4.43. Поворот куба вокруг оси г: а — до поворота; б — после поворотаРис. 4.43. Поворот куба вокруг оси г: а — до поворота; б — после поворота
Рис. 4.43. Поворот куба вокруг оси г: а — до поворота; б — после поворота
Рис. 4.43. Поворот куба вокруг оси г: а — до поворота; б — после поворота
Таким образом, матрица поворота вокруг произвольной оси получается в результате перемножения трех матриц:
R = R^R^R-.
Несложный эксперимент должен убедить вас в том, что правильно подобрав значения углов а, Р и у, можно таким способом обеспечить поворот вокруг оси с любой ориентацией, хотя выбор углов — задача непростая, о чем будет рассказано в разделе 4.8.4.
Рис. 4.45. Поворот куба вокруг оси хРис. 4.45. Поворот куба вокруг оси хПреобразование экземпляра
Рис. 4.45. Поворот куба вокруг оси х
Рис. 4.45. Поворот куба вокруг оси х
Рассмотренный пример с кубом, который можно ориентировать как угодно, подводит нас к обобщенному подходу, который можно использовать при моделировании объектов. Рассмотрим
сцену, состоящую из множества простых объектов (рис. 4.46). Один из возможных вариантов формирования описания такой сцены— определить каждый объект сцены множеством вершин, учитывая его положение, размеры и ориентацию. Другой подход — сначала определить набор прототипов объектов сцены определенного фиксированного размера, положения и ориентации, причем прототипами могут быть как стандартные объекты (параллелепипед или сфера), так и более сложные — стол, стул и т.п. Каждый конкретный объект сцены — это экземпляр прототипа (instance) со своими размерами, положением и ориентацией в пространстве сцены. Размеры, положение и ориентация экземпляра задаются каноническими преобразованиями, которые в данном случае будем называть преобразованиями эк
земпляра (instance transformation). В результате описание сцены ^мс* ^"^ Сцена из простых с-объектов
примет вид базы данных, опирающейся на список идентификаторов прототипов (например, 1 — куб, 2 — сфера и т.д.).
Преобразование экземпляра применяется в порядке, представленном на рис. 4.47. Объект обычно определяется в собственном фрейме, начало координат которого находится в центре масс объекта, а оси координат параллельны характерным ребрам объекта или его оси симметрии. Сначала устанавливаются размеры экземпляра, затем его сдвигают и поворачивают. Следовательно, преобразование экземпляра имеет вид
М = TRS.
Моделирование на основе преобразований экземпляров эффективно реализуется не только в системах с конвейерной архитектурой, но и в системах, работающих с дисплейными списками, о которых шла речь в главе 3. Описания сложных прототипов, которые используются в модели многократно, передаются на сервер только один раз в виде дисплейного списка. Отображение каждого экземпляра потребует только пересылки на сервер соответствующей матрицы преобразования экземпляра.
Поворот вокруг произвольной оси
Пример выполнения поворота, который мы рассмотрим в данном разделе, продемонстрирует, как использовать углы поворотов вокруг осей координат для задания поворота вокруг произвольного вектора. Рассмотрим поворот куба, показанного на рис. 4.48. Для определения произвольного поворота в пространстве требуются три параметра: фиксированная точка преобразования (в качестве таковой выберем центр куба — точку р0), вектор, вокруг которого выполняется поворот, и угол поворота. Обратите внимание на то, что ни один из них не зависит от конкретного фрейма, а следовательно, мы специфицируем преобразование, не связываясь ни с какой системой координат. Но при формировании матриц аффинных преобразований нам придется привязаться к определенному фрейму.
Рис. 4.47. Преобразование экземпляраРис. 4.47. Преобразование экземпляраРис. 4.48. Поворот куба вокруг произвольной осиРис. 4.48. Поворот куба вокруг произвольной оси
Рис. 4.47. Преобразование экземпляра
Рис. 4.47. Преобразование экземпляра
Рис. 4.48. Поворот куба вокруг произвольной оси
Рис. 4.48. Поворот куба вокруг произвольной оси
Вектор, вокруг которого поворачивается куб, можно специфицировать разными способами, например, можно задать его двумя точками р( и р2:
и = р2-р).
Обратите внимание на то, что порядок использования точек задает положительное направление вращения, и хотя на рисунке вектор и проходит через точку р0, имеет значение только его направление. Выполнение дальнейших операций облегчит нормализация вектора оси поворота— замена и вектором единичной длины, имеющим то же направление:
а, at a.
Выше уже было показано, что преобразование упрощается, если перенести фиксированную точку в начало координат. Таким образом, первый сдвиг — это Т(-р0), а последний — Т(ро). После начального сдвига задача поворота приобретает вид, как на рис. 4.49.
В предыдущем примере (см. раздел 4.8.2) было показано, что поворот вокруг произвольной оси можно свести к последовательности поворотов вокруг отдельных осей координат. Основная загвоздка при этом — определить, на какой угол нужно повернуть вокруг каждой оси. Поэтому применим другую стратегию — выполним два первых поворота таким образом, чтобы ось поворота v совместилась с координатной осью z. Затем повернем вокруг z на заданный угол О, после чего выполним первые два поворота в обратном порядке и обратном направлении. Таким образом, матрица комплексного преобразования имеет вид произведения:
Рис. 4.49. Перенос фиксированной точки в начало координатРис. 4.49. Перенос фиксированной точки в начало координатR = R/-6J R^-e^) RM) R/0J RM)-
Рис. 4.49. Перенос фиксированной точки в начало координат
Рис. 4.49. Перенос фиксированной точки в начало координат
Эта последовательность поворотов показана на рис. 4.50. Наиболее сложная часть процедуры — определение 0, и 9Г
Рассмотрим компоненты вектора v. Поскольку v является вектором единичной длины, то а/ + а>2 + а? = 1.
Проведем отрезок от начала координат в точку М, ос,, а.). Этот отрезок имеет единичную длину и ориентирован в направлении вектора v. Опустим перпендикуляры из точки (а,, а,, а.) на каждую координатную ось, как показано на рис. 4.51. Три направляющих угла (direction angles) — фу, фр ф. — это углы между отрезком (или вектором v) и координатными осями. Между направляющими косинусами (direction cosines) и компонентами v существует очевидное соотношение:
созфу = а„
со$ф} = Ор созф. = а.-.
Независимы только два направляющих угла, поскольку
СО32фу + СОЗ^. + С03~ф- = 1.
Зная значения направляющих косинусов, можно вычислить значения углов 0Т и 0Г Рассмотрим рис. 4.52. На нем видно, что поворот точки (а„ Оу, ос) приводит к такому повороту отрезка, что он оказывается в плоскости у = 0. Длина проекции отрезка (до выполнения поворота) на плоскость х = 0 равна d. Можно и по-другому трактовать этот рисунок. Считайте, что плоскость х = 0 — это стена, и рассмотрите удаленный источник света, расположенный где- то на положительной полуоси х. Отрезок, который вы увидите на стене, — это тень, отбрасываемая отрезком, проведенным из начала координат в точку (а„ Оу, а.). Учтите, что длина тени меньше длины отрезка. Можно сказать, что отрезок сжат до величины d = ^а?у + а^ . Искомый угол поворота — это угол между этой тенью и осью z. Однако в матрице поворота присутствуют не углы, а их синусы и косинусы, следовательно, нам не потребуется вычислять само значение 0т, а только
10 О О'
О a. Id -av Id О О av/J a.Id О
0001
Рис. 4.51. Направляющие углы оси поворотаРис. 4.51. Направляющие углы оси поворотаРис. 4.52. Вычисление угла поворота вокруг оси хРис. 4.52. Вычисление угла поворота вокруг оси х
Рис. 4.51. Направляющие углы оси поворота
Рис. 4.51. Направляющие углы оси поворота
Рис. 4.52. Вычисление угла поворота вокруг оси х
Рис. 4.52. Вычисление угла поворота вокруг оси х
Рис. 4.53. Вычисление угла поворота вокруг оси уРис. 4.53. Вычисление угла поворота вокруг оси у
Рис. 4.53. Вычисление угла поворота вокруг оси у
Рис. 4.53. Вычисление угла поворота вокруг оси у
Элементы матрицы R^ вычисляются исходя из аналогичных рассуждений (рис. 4.53). Обратите внимание на то, что поворот вокруг оси у выполняется по часовой стрелке, поэтому нужно
внимательно следить за знаками тех элементов матрицы, которые содержат функцию синуса. Матрица Ry будет иметь вид
" d 0 -ax О'
ММ-
0 100
ах 0 d0
.0 001
Последний шаг— перемножить матрицы отдельных преобразований:
м = Т(ро) RX-0x) i^W R-(O^) R>W r/oj T(-Po).
Теперь рассмотрим численный пример. Пусть требуется повернуть объект на 45° вокруг прямой, которая проходит через начало координат и точку (1,2,3). Оставим фиксированную точку преобразования в начале координат. Первый шаг— оп-
ределение точки на прямой, которая отстоит от начала координат на единичное расстояние. Эту точку получим после нормализации тройки (1, 2, 3).
Получим (1/V14, 2/V14, 3/V14) или в однородных координатах — (1/V14, 2/V14, 3/V14. 1). После выполнения первой фазы поворота эта точка перейдет в положение (0, 0, 1, 1). Сначала будет выполнен поворот вокруг оси х на угол, равный arccos-y= . В результате точка (1/VT4, 2/VL4, 3/V14, 1) преобразуется в точку (1/V14, 0. V13/14, 1), которая принадлежит плоскости у = 0. Вокруг оси}' объект поворачивается на угол, равный arccosV13/14 . В результате прямая окажется вытянутой вдоль оси с, и можно будет выполнить поворот вокруг этой оси на 45°. Последний этап — выполнить первые два поворота в обратном порядке и в обратном направлении. Перемножив матрицы этих преобразований, получим матрицу R:
R.(45) R,R.(45) R,г arccosкг arccosк
R.(45) R,
R.(45) R,
г arccos
к
г arccos
к
2 + 13^2282-V2+3V?146-3V2-4V72802 + 13^2282-V2+3V?146-3V2-4V72802-V2-3V7144 + 5^2146-3V2+V71402-V2-3V7144 + 5^2146-3V2+V71406-3V2+4V7 0 286-3V2-V? 01418 + 5V228016-3V2+4V7 0 286-3V2-V? 01418 + 5V22801
2 + 13^2282-V2+3V?
146-3V2-4V7280
2 + 13^2282-V2+3V?
146-3V2-4V7280
2-V2-3V7
14
4 + 5^2
146-3V2+V7
14
0
2-V2-3V7
14
4 + 5^2
146-3V2+V7
14
0
6-3V2+4V7 0 28
6-3V2-V? 014
18 + 5V2
28
01
6-3V2+4V7 0 28
6-3V2-V? 014
18 + 5V2
28
01
Эта матрица не изменяет положение точек, которые находятся на прямой, проходящей через начало координат и точку (1,2, 3). Если потребуется выполнить такой же поворот, но фиксированная точка располагается не в начале координат, а в некоторой позиции ру, то нужно скорректировать матрицу преобразования:
M = T(P/)RT(-P/).
Этот пример показывает, как использовать множество простейших преобразований для выполнения сложного преобразования. Задача поворота вокруг произвольной оси и произвольной фиксированной точки возникает в множестве приложений. Варианты ее решения зависят в основном от того, каким образом задана ось поворота. Однако почти всегда можно воспользоваться описанной выше методикой для определения направляющих углов или направляющих косинусов.
Матрицы преобразований в OpenGL
В этом разделе речь пойдет о реализации механизма преобразований в однородных координатах в виде программного пакета и об интерфейсе между этим пакетом и прикладной программой. В OpenGL существуют три матрицы, которые входят в состав параметров, характеризующих текущее состояние графической системы. В этой главе мы рассмотрим только матрицу вида (model-view matrix}. Всеми тремя матрицами можно манипулировать с помощью одного и того же набора функций, а для выбора, с какой именно матрицей выполняются операции, используется функция glMatrixMode().
Текущая матрица преобразования
В большинстве графических систем используется текущая матрица преобразования — ТМП (СТМ — current transformation matrix). Эта матрица применяется для преобразования всех вершин. Если изменяется ТМП, изменяется текущее состояние системы. Умножение на ТМП является одной из стадий конвейерного процесса обработки информации в графической системе (рис. 4.54). Обозначим матрицу ТМП через С. Тогда, если р — это вершина, то при “перемещении” ее по конвейеру формируется произведение Ср. Матрица ТМП имеет размер 4x4 и может быть изменена функциями, которые входят в состав графического пакета.
Рис. 4.54. Текущая матрица преобразования
В исходном состоянии ТМП является единичной матрицей размера 4x4; при необходимости в любой момент прикладная программа может ее реинициализировать. Будем использовать символ <— для обозначения процедуры замены содержимого матрицы. Операция инициализации в нашей системе обозначений будет выражаться следующим образом:
С<-1.
Функции изменения С разделены на две группы: функции присвоения новых значений элементам матрицы и функции преобразования матрицы умножением ее на другую матрицу справа или слева. В подавляющем большинстве графических систем поддерживаются три вида преобразований: сдвиг, масштабирование с фиксированной точкой в начале координат и поворот с фиксированной точкой в начале координат. В принятой системе символических обозначений операции умножения матрицы ТМП справа на матрицы преобразований выражаются следующим образом:
с<-ст,
c<-cs, С<—CR.
Операции установки элементов ТМП обозначаются так:
С<-Т,
С <—S, С <—R.
В большинстве графических систем можно присвоить элементам ТМП значения из любой другой матрицы или умножить ее справа на произвольную матрицу М:
С<-М, с<-см.
Поворот, сдвиг и масштабирование
В OpenGL матрица ТМП, которая преобразует координатные описания всех примитивов, является произведением двух матриц — матрицы вида GL_MODELVIEW и матрицы проецирования GL_PROJECTION (рис. 4.55). Программист имеет возможность манипулировать каждой из них по отдельности, выбирая нужную с помощью функции glMatrixMode(). Выбранная матрица загружается вызовом функции glLoadMatrixf(<указатель_на_матрицу>^ ;
или приравнивается к единичной матрице (инициализируется) вызовом функции glLoad!dentity();
ВершиныВершины(Вершины
Вершины
Вершины
—
ТМПТМПРис. 4.55. Матрицы вида и проецированияРис. 4.55. Матрицы вида и проецирования
ТМП
ТМП
Рис. 4.55. Матрицы вида и проецирования
Рис. 4.55. Матрицы вида и проецирования
Произвольную матрицу размера 4x4 можно задать указателем на массив из 16 чисел, в котором элементы матрицы перечислены по столбцам. Изменить выбранную матрицу можно с помощью функции умножения справа glMultMatrixf (<указатель_на_матрицу>). Для выполнения поворота, сдвига и масштабирования служат три специальные функции:
glRotatef(angle, vx, vy, vz);
glTranslatef(dx, dy, dz);
glScalef(sx, sy, sz);
Каждая из этих функций изменяет выбранную с помощью glMatrixMode() матрицу текущего состояния, домножая ее справа. В функции поворота glRotatef (angle, vx, vy, vz) первый аргумент angle задает угол поворота в градусах, а три последующих — vx, vy и vz — компоненты вектора оси поворота. Аргументы функции сдвига glTranslatef ()— компоненты вектора смещения. Аргументы функции масштабирования glScalef () — масштабные коэффициенты по координатным осям.
Поворот вокруг фиксированной точки средствами OpenGL
В разделе 4.8 было показано, что для поворота вокруг произвольной фиксированной точки (т.е. точки, отличной от начала координат) сначала нужно выполнить преобразование сдвига, совмещающее заданную фиксированную точку с началом координат, потом преобразование поворота вокруг начала координат, а затем обратное преобразование сдвига, “возвращающее” фиксированную точку в прежнюю позицию. В приведенной ниже последовательности вызовов функций OpenGL сначала устанавливается режим работы с матрицей вида, затем выполняются описанные выше преобразования для следующих исходных данных: угол поворота 45°, ось поворота параллельна прямой, проведенной через начало координат и точку (1, 2,3), а фиксированная точка имеет координаты (4, 5, 6):
glMatrixMode(GL_MODELVIEW) glLoadldentityO;
glTranslatef(4.0, 5.0, 6.0);
glRotatef(45.0, 1.0, 2.0, 3.0); glTranslatef(-4.0, -5.0, -6.0);
Обратите внимание на то, что работая с пакетом OpenGL, не нужно формировать матрицу поворота вокруг произвольной оси так, как мы это делали в примере из раздела 4.8.4, хотя в качестве упражнения попробуйте запрограммировать этот метод с помощью имеющихся в составе OpenGL функций умножения матриц.
Последовательность выполнения преобразований
В приведенной выше последовательности обращения к функциям OpenGL внимательный читатель наверняка отметил, что преобразования задаются “в обратном порядке”, т.е. первое преобразование сдвига задается пятым оператором, а последнее — третьим. Дело в том, что в OpenGL действует следующее правило:
Преобразование, которое должно быть в цепочке последним, передается первым вызовом соответствующей функции.
Несложный анализ убедит вас в справедливости этого правила, которое учитывает особенности выполнения операций над текущей матрицей преобразования в исполнительной системе OpenGL. Дело в том, что ТМП всегда домножается справа на новую матрицу преобразования. Поэтому приведенную выше последовательность вызовов функций можно выразить с помощью введенной раньше системы обозначений следующим образом:
С<-1,
С <—СТ(4.0, 5.0, 6.0),
С<—CR(45.0, 1.0, 2.0, 3.0),
С <—СТ(-4.0, -5.0,-6.0).
На каждом шаге домножаем ТМП справа на новую матрицу преобразования, в результате чего вся процедура будет иметь результат
С = Т(4.0, 5.0, 6.0) R(45.0, 1.0, 2.0, 3.0) Т(-4.0, -5.0, -6.0),
который полностью соответствует формуле, приведенной в разделе 4.8.1. Каждая вершина р, которая задается после того, как будет сформирована такая матрица вида, умножается справа на матрицу С, в результате чего формируется новая вершина
q = Cp.
Можно и по-другому рассматривать такой порядок операций в OpenGL, например воспользовавшись концепцией стека. Изменение матрицы ТМП имеет аналогию с занесением матриц в стек. После завершения цепочки матрицы извлекаются из стека в обратном порядке и образуют представленную выше последовательность сомножителей. Аналогия эта только концептуальная, поскольку в исполнительной системе OpenGL стек используется совсем иначе, а матрица, сформированная функцией преобразования, немедленно умножается справа на матрицу ТМП. Что касается стека матриц, то он нам часто понадобится при выполнении моделирования, о чем будет подробно рассказано в главе 8. В составе OpenGL имеются функции записи матрицы в стек glPushMatrix() и извлечения матрицы из стека glPopMatrix(), которые будут использоваться при построении иерархии объектов сцены.
Вращение куба
В описанной ниже программе мы воспользуемся кубом, определенным в разделе 4.4, и будем вращать его по командам, формируемым при нажатии кнопок мыши. Нам потребуются три функции с обратным вызовом, которые регистрируются следующим образом:
glutDisplayFunc(display);
glut!dleFunc(spincube);
glutMouseFunc(mouse);
В функции отображения display () сначала устанавливается матрица вида, для чего используются три угла, заданных в функции обработки событий мыши mouse(). Затем с помощью функции colorcube(), которую мы рассматривали в разделе 4.4, вычерчивается куб. В этом примере будет использоваться двойная буферизация. При каждом вызове display() очищаются буфер кадра и буфер глубины. Последний используется для удаления невидимых поверхностей. Выполнение display () завершается процедурой переключения буферов: void display(void) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoad!dentity();
glRotatef(theta[0], 1.0, 0.0, 0.0);
glRotatef(theta[l], 0.0, 1.0, 0.0);
glRotatef(theta[2], 0.0, 0.0, 1.0);
colorcube();
glutSwapBuffers();
}
В функции обработки событий мыши mouse () выбирается ось вращения: void mouse(int btn, int state, int x, int y) {
if(btn==GLUT_LEFT_BUTTON && state == GLUT_DOWN) axis = 0;
if(btn==GLUT_MIDDLE_BUTTON && state == GLUT_DOWN) axis = 1;
if(btn==GLUT_RIGHT_BUTTON && state == GLUT_DOWN) axis = 2;
}
В функции обработки простоя выполняется приращение на 2° угла поворота, связанного с текущей осью:
void spinCube()
{
theta[axis] += 2.0;
if( theta[axis] > 360.0 ) theta[axis] -= 360.0;
display();
}
Завершается выполнение программы нажатием клавиши на клавиатуре. Это событие обрабатывается функцией mykey():
void mykey(char key, int mousex, int mousey) {
if(key=='q'||key=='Q') exit();
}
Обсуждение алгоритмов удаления невидимых поверхностей мы отложим до главы 5, но сейчас нужно отметить, что с точки зрения прикладного программиста эта задача решается в программах OpenGL очень просто. От него требуется только очистить буфер глубины {depth buffer) и разрешить работу алгоритма вызовом glEnable(GL_DEPTH_TEST). Полный текст программы вы найдете в приложении А.
Загрузка матриц и использование стека матриц
Хотя в большинстве случаев для формирования матрицы преобразования вполне достаточно функций поворота, сдвига и масштабирования, в некоторых ситуациях приходится формировать матрицу поэлементно. В частности, это приходится делать при формировании матрицы скоса. Точно так же, как матрицу преобразования можно приравнять к единичной матрице, в нее можно загрузить и произвольную 4х4-матрицу однородных координат. Для этого в составе OpenGL имеется функция
glLoadMatrixf(myarray)
Аргументом функции glLoadMatrixf () является указатель на загружаемый массив. Текущую матрицу, выбранную с помощью glMatrixMode(), можно домножить справа на произвольную матрицу, определенную в прикладной программе. Это выполняется с помощью функции gMultMatrixf (myarray). Аргумент myarray представляет собой одномерный массив из 16 чисел, в котором элементы матрицы представлены по столбцам. Так, если необходимо сформировать в массиве myarray элементы матрицы М (речь идет только о матрицах размером 4x4), то это делается следующим образом:
GLfloat myarray[16];
for(i=0;i<3;I++)
for(j=0;j=3;j++)
myarray[4*j+i]= m[i)[j];
Иногда в программе нужно выполнить некоторое преобразование, а затем восстановить предыдущее состояние. В частности, это необходимо делать при организации преобразований экземпляров. Такие преобразования не должны распространяться на все объекты, а только на выбранные. Сохранение-восстановление матриц текущего состояния выполняется с помощью стека матриц. Для записи матрицы в стек служит функция glPushMatrix(), а для извлечения из стека — функция glPopMatrix(). Поэтому в графических программах вы можете часто встретить примерно такой фрагмент: glPushMatrix();
glTranslatef(....);
glRotatef ();
glScalef ();
/* Здесь вычерчиваются объекты */
glPopMatrix();
Взаимодействие пользователя с трехмерными графическими приложениями
В рассмотренном выше примере для управления направлением вращения куба на экране использовалась трехкнопочная мышь. Но возможности такого интерфейса пользователя с приложением слишком ограничены. Вместо того чтобы три кнопки мыши использовать для задания ориентации, их можно было бы применить для управления программой, например ее завершением.
Как уже отмечалось в разделе 4.8, существует множество способов выполнения поворота вокруг произвольной оси. Мы использовали последовательное вращение вокруг осей коорди-
нат х, у и z, но можно организовать этот процесс и по-другому: сначала повернуть вокруг оси х, затем вокруг оси у, а закончить опять поворотом вокруг оси х. В таком случае можно добиться любой ориентации объекта, используя только две кнопки мыши. Однако по-прежнему остается проблема с направлением вращения — в нашей программе поворот всегда выполняется только в одном направлении. Для пользователя же желательно иметь возможность поворачивать в обе стороны — и вперед, и назад — и останавливать процесс, когда объект будет сориентирован, как задумано.
Библиотека GLUT позволяет использовать для управления процессом кнопки мыши в сочетании с управляющими клавишами клавиатуры. Например, левой кнопкой мыши можно задать вращение вокруг оси х по часовой стрелке, а нажав одновременно еще и клавишу <Ctrl> — против часовой стрелки.
Но есть и другие способы, которые позволяют организовать более гибкий и удобный интерфейс пользователя с программой. Ниже мы рассмотрим два таких способа.
Использование областей экрана
Предположим, что решено использовать одну кнопку мыши для управления ориентацией объекта, другую — для сдвига его ближе к наблюдателю или дальше от него, а третью — для смещения объекта вправо-влево или вверх-вниз. Для организации таких процедур можно воспользоваться функцией с обратным вызовом motion(), которая возвращает информацию о том, какая кнопка мыши нажата и где находится указатель мыши на экране. Информацию о положении указателя мыши можно использовать для управления скоростью вращения или смещения и их направлением.
Как уже отмечалось, можно добиться произвольной ориентации, выполняя поворот только вокруг двух осей координат. Следовательно, для управления ориентацией нам достаточно одной кнопки мыши и информации о текущем положении указателя мыши на экране. Если при нажатии левой кнопки мыши указатель находится в центре экрана, поворот не выполняется. Если в этот момент указатель находится вверху экрана, то объект поворачивается вокруг оси х по часовой стрелке, а если внизу экрана — против часовой стрелки. Аналогично организуется и вращение вокруг оси у, но указатель должен находиться справа или слева от центра. Когда указатель находится в углу экрана, можно одновременно поворачивать объект и вокруг оси х, и вокруг оси у.
Сдвиг объекта вправо-влево или вверх-вниз можно выполнять по этой же схеме, но использовать правую кнопку мыши. Для управления движением объекта к наблюдателю или от него (вдоль оси z) будем использовать среднюю кнопку. При этом можно принимать во внимание только положение указателя по вертикали относительно центра экрана.
Мы предоставляем читателям возможность самостоятельно реализовать описанный алгоритм в виде программы (см. упр. 4.19).
Виртуальный трекбол
Идея использовать текущее положение указателя мыши для управления поворотом вокруг двух осей координат очень близка к тем методам, которые используются при работе с трекболом (см. главе 3). Ниже мы рассмотрим, как развить эти идеи и создать виртуальный трекбол, используя только мышь. Одно из достоинств этого “устройства” состоит в том, что задав с его помощью параметры, можно организовать непрерывное вращение, которое прекратится только по команде пользователя. Кроме того, оно позволяет оперативно, “на ходу”, изменять скорость вращения и его направление. Этот же принцип можно использовать и для управления смещением объекта.
Начнем с того, что определим соответствие между положением точки на поверхности шарика трекбола и позицией указателя мыши. Будем рассматривать трекбол, представленный на рис. 4.56, и предположим, что радиус его шарика равен одной единице. Тогда можно поста-
вить в соответствие точкам поверхности шарика точки на плоскости у = 0 (рис. 4.57). Точка (х, у, z) на поверхности шарика проецируется в точку (х, 0, z) на плоскости. Такая проекция обратима, поскольку известно, что трехмерная точка, спроецированная в определенную точку на плоскости, должна удовлетворять соотношению, описывающему сферу:
X +у +z = 1.
Следовательно, данной точке (х, 0, z) на плоскости должна соответствовать точка (х, у, z) на верхней полусфере, где
у = Vl-x2 -z2 .
Рис. 4.56. Фрейм трекболаРис. 4.57. Проекция на плоскость точки на поверхности шарика трекболаРис. 4.56. Фрейм трекболаРис. 4.57. Проекция на плоскость точки на поверхности шарика трекбола
Рис. 4.56. Фрейм трекболаРис. 4.57. Проекция на плоскость точки на по
верхности шарика трекбола
Рис. 4.56. Фрейм трекболаРис. 4.57. Проекция на плоскость точки на по
верхности шарика трекбола
Рис. 4.58. Определение плоскости поворотаРис. 4.58. Определение плоскости поворота
Рис. 4.58. Определение плоскости поворота
Рис. 4.58. Определение плоскости поворота
Таким образом, по положению указателя мыши на экране можно восстанавливать трехмерную точку на поверхности шарика трекбола. Рассмотрим теперь две точки, р! и р2, на верхней полусфере шарика. Векторы, проведенные из центра шарика к этим точкам, определяют ориентацию плоскости, нормаль к которой можно вычислить как векторное произведение этих векторов (рис. 4.58):
п = р, х р2.
Поворачивая трекбол вокруг оси п, можно совместить вектор р, с р2. Угол поворота— это угол между векторами р( и р2, который можно вычислить по значению модуля векторного произведения. Поскольку pi и р2 являются единичными векторами, угол определяется соотношением
|sinO| = |п|.
Если с большой скоростью отслеживать траекторию указателя мыши, так что каждое элементарное смешение ее позиции на экране будет достаточно мало, то можно воспользоваться аппроксимацией
угла 0, не прибегая к обратным тригонометрическим функциям:
sin0 = 0.
Реализовать обработку положения трекбола можно с помощью функций обработки событий простоя, движения мыши и нажатия кнопок мыши из библиотеки GLUT. Этот процесс можно рассматривать в терминах трех логических переменных или флагов, которые контролируют движение мыши и перерисовку изображения. При инициализации программы этим логическим переменным присваиваются такие значения:
bool trackingMouse = false;
bool trackballMove = false;
bool redrawContinue = false;
Если значение флага redrawContinue равно true, то функция обработки простоя вызывает функцию перерисовки изображения. Если значение флага trackingMouse равно true, то обновляется позиция трекбола в функции обработки движения. Если значение флага trackballMove равно true, то обновляется матрица вращения, которая используется в функции display ().
Значения флагов изменяются в функции обработки событий мыши mouse(). Когда пользователь нажимает кнопку мыши (определенную или любую — это определяет разработчик программы), запускается процесс обновления позиции трекбола в функции обработки события движения мыши, причем, если пользователь передвигает мышь, вызывается функция перерисовки объектов на экране. Когда пользователь отпускает кнопку мыши, отслеживание прекращается. Два последних положения мыши используются для определения вектора скорости, что позволяет организовать непрерывное обновление матрицы поворота и таким образом — непрерывное вращение объектов на экране. В этом случае объект будет вращаться и после того, как пользователь отпустит кнопку мыши.
Текст программы, реализующей описанный алгоритм, находится в файле cube2.c. Учтите, что в этой программе используется некоторое упрощение при определении скорости вращения, но в ней также обрабатываются ситуации, не рассмотренные в данном описании. Например, что делать, если указатель мыши находится в углу экрана и соответствующая точка на плоскости не может быть спроектирована на верхнюю полусферу поверхности шарика трекбола.
Плавное вращение
Описанный пример демонстрирует, с какими сложностями встречается программист при использовании рассмотренных выше методов определения параметров поворота. Этот подход основан на углах Эйлера, которые описывают повороты вокруг осей системы координат. Он позволяет сформировать матрицу произвольного поворота, используя суперпозицию простых поворотов вокруг осей системы координат х, у и г. Хотя OpenGL и позволяет самостоятельно сформировать любую матрицу поворота, но удобнее использовать принцип суперпозиции для определения как оси вращения, так и соответствующих углов.
Рассмотрим, что получится в программе анимации, если попытаться организовать движение объекта при переходе от одной ориентации к другой. Подходящую матрицу поворота можно определить в виде произведения матриц поворота вокруг трех осей:
R(6) = RV(0X) Rv(0r) R.(6.-).
Если цель разработчика— организовать на экране плавное вращение объекта между заданными крайними положениями, то нужно на каждом шаге использовать малые приращения углов. Но такая последовательность не будет восприниматься наблюдателем как плавное вращение — он будет видеть вращения вокруг каждой из трех осей.
При описании работы с трекболом было показано, как можно достаточно плавно поворачивать куб из одного положения в другое. Для этого использовалось соответствие между двумя ориентациями и двумя точками на окружности единичного радиуса. Плавный переход от одной ориентации к другой соответствует движению по большой окружности на поверхности сферы. При этом ось вращения — это нормаль к плоскости окружности, которая определяется двумя точками на окружности и центром сферы. Если плавно увеличивать этот угол, наблюдатель увидит плавное вращение.
Сложности при реализации этого метода проистекают из использованного нами математического аппарата, который “привязан'’ к некоторой системе координат. Кватернионы (quaternions) — это математический аппарат, связанный с комплексными числами, который позволяет использовать альтернативные методы описания вращения. Хотя этот аппарат и не так интуитивно понятен, как тот, который использовали мы, он имеет существенные преимущества в приложениях, где требуется анимация изображения. Другие примеры организации плавного вращения будут рассмотрены в главе 8.
Резюме
В этой главе мы представили две различные, но дополняющие друг друга точки зрения на математический аппарат, пригодный для решения задач компьютерной графики. Одна из них состоит в том, что для глубокого понимания сути выполняемых в графической системе операций необходимо использовать математические абстракции объектов. Другая же состоит в том, что базовым для реализации графической системы должен быть аппарат преобразований, в частности преобразований в однородных координатах.
Используемый математический аппарат заимствован из векторного анализа и линейной алгебры. Однако последовательность описания этого аппарата, которая определяется задачами компьютерной графики, весьма непривычна для большинства студентов. Как правило, сначала студенты знакомятся с линейной алгеброй, а затем с векторными пространствами (как приложение к изучению «-мерных пространств R"). Мы же, наоборот, сначала рассмотрели представление объектов в математических пространствах, которое привело нас к использованию линейной алгебры в качестве инструмента для реализации абстрактных типов.
При изложении мы старались следовать подходу, независящему от координат. Сделано это было по двум причинам. Во-первых, желательно показать, что все базовые концепции геометрических объектов и преобразований не зависят от способа их представления. Во- вторых, все более популярными становятся языки объектно-ориентированного программирования, и большинство прикладных программистов будут работать непосредственно с объектами, а не с их представлениями. В литературе, которая упоминается в следующем разделе, имеется множество примеров геометрических систем программирования, демонстрирующих потенциальные возможности такого подхода.
Однородные координаты — это один из прекрасных примеров огромных возможностей математической абстракции. Используя абстрактное математическое пространство — аффинное пространство, — оказалось возможным создать исключительно эффективный программный инструмент для манипулирования графическими объектами.
В последних разделах главы описаны те преобразования, которые поддерживаются средствами OpenGL, и обсуждается методика их суперпозиции для получения произвольных аффинных преобразований. Принцип комбинированного использования небольшого набора базовых преобразований для определения преобразования любой сложности лежит в основе подавляющего большинства графических систем. На его использование ориентированы все упражнения в конце настоящей главы. В главе 5, взяв за основу эту же методику, мы рассмотрим процесс визуализации трехмерных сцен, а в главе 8 покажем, как она используется при иерархическом моделировании объектов.
Рекомендуемая литература
Векторному анализу и линейной алгебре посвящено огромное множество книг, хотя практически во всех эти два вопроса рассматриваются по отдельности. В среде тех, кто занимается компьютерной графикой, подход к описанию кривых и поверхностей, основанный на использовании векторных пространств, довольно популярен и представлен, например, в книге Фокса (Faux) и Пратта (Pratt) [Fau80]. Введению в геометрическое программирование посвящены книги Де Розе (DeRose) [DeR89, DeR90). Однородные координаты были придуманы геометрами [Мах51] и только значительно позже их открыли для себя специалисты по компьютерной графике [Rie81]. Аппаратно методы преобразований в однородных координатах впервые были реализованы в Silicon Graphics Geometry Engine [С1а82]. В современных графических станциях используются специализированные БИС (ASIC — Application Specific Integrated Circuits), которые среди прочих функций реализуют и преобразования в однородных координатах.
Использование кватернионов в графических системах при анимации изображения описано Шомейкером (Shoemaker) в работе [Sho85].
Для изучения методов работы с матрицами геометрических преобразований прекрасно подходят современные программные продукты Mathematica [Wol91] и Matlab [Mat95].
Упражнения
Рассмотрите решение любого линейно-дифференциального уравнения с постоянными коэффициентами или аналогичного разностного уравнения. Докажите, что решения однородных уравнений образуют векторное пространство. Покажите связь между решением определенного неоднородного уравнения и аффинным пространством.
Покажите, что следующие последовательности коммутативны:
поворот и равномерное масштабирование;
два поворота вокруг одной и той же оси;
два смещения.
Разработайте библиотеку функций, которая позволит реализовать геометрическое программирование. Библиотека должна содержать функции манипулирования базовыми геометрическими типами (точками, прямыми, векторами) и операциями над этими типами, включая скалярное и векторное произведение. Функции должны допускать изменение фрейма. Включите в библиотеку и функции взаимодействия с OpenGL, что позволит просматривать на экране результаты геометрических вычислений.
При работе исключительно с двухмерными объектами можно использовать трехмерные однородные координаты для представления точки в виде р = [х^ I]7 и векторов в виде v = [а b О]7. Какой вид будут иметь в этом случае ЗхЗ-матрицы преобразований поворота, сдвига, масштабирования и скоса. Сколько степеней свободы у каждого из этих преобразований?
Параметры аффинного преобразования можно однозначно определить, сравнивая положение нескольких точек до и после выполнения преобразования. Сколько таких точек нужно использовать для однозначного описания аффинного преобразования в трехмерном пространстве? Сколько их требуется для решения этой же задачи в двухмерном пространстве?
203203Упражнения
203
203
Какой вид примет матрица поворота при работе в левосторонней системе координат и как нужно переопределить положительное направление вращения?
Покажите, что любая последовательность поворотов и сдвигов может быть заменена единственным поворотом с фиксированной точкой в начале координат и сдвигом.
Выведите матрицу преобразования скоса в виде суперпозиции матриц поворота, сдвига и масштабирования.
В двухмерном пространстве прямую можно задать уравнением у = mx+h. Найдите аффинное преобразование, которое сформирует отражение точки относительно этой прямой. Обобщите полученный результат на отражение точки относительно плоскости в трехмерном пространстве.
В разделе 4.8 было показано, что матрица поворота вокруг произвольной оси может быть получена в результате суперпозиции трех матриц поворотов вокруг координатных осей. Сколько способов подобного разложения существует? Всегда ли нужно использовать все три оси координат?
Добавьте в набор преобразований экземпляра преобразование скоса. Покажите, как использовать такой расширенный набор для формирования параллелепипеда из единичного куба.
Отыщите представление плоскости в однородных координатах.
Какой вид имеет матрица поворота, сформированная функцией glRotate(). Элементы матрицы должны зависеть от аргументов вызова glRotate().
Напишите программу, которая будет формировать узор Серпинского следующим образом. Сначала строится белый треугольник. На каждом очередном шаге используются преобразования, формирующие три подобных треугольника поверх исходного, которые вычерчиваются черным цветом.
Пусть в исходном состоянии имеется куб, центр которого совмещен с началом координат, а ребра ориентированы вдоль осей системы координат. Какой вид имеет матрица поворота, которая придаст кубу симметричную ориентацию, как показано на рис. 4.59.
Для описания таких объектов, как трехмерные многоугольники, используются трехмерные вершины. По заданному множеству вершин определите, является ли определенный ими многоугольник плоским.
Три вершины определяют треугольник в том случае, если они не лежат на одной прямой (неколлинеарны). Разработайте тест коллинеарности трех точек.
Рис 4.59. Симметричная ориентация кубаРис 4.59. Симметричная ориентация кубаМы определили преобразование экземпляра как суперпозицию сдвига, поворота и масштабирования. Можно ли получить тот же самый объект, если применить преобразования этих типов в другом порядке?
Рис 4.59. Симметричная ориентация куба
Рис 4.59. Симметричная ориентация куба
Напишите программу, которая позволит ориентировать куб по
командам от одной кнопки мыши, сдвигать по командам от второй кнопки, а растя- гивать-сжимать — по командам от третьей.
Заданы два непараллельных трехмерных вектора и и v. Как сформировать ортогональную систему координат, в которой вектор и будет одним из векторов базиса?
ГЛАВА
5
Визуализация
В
предыдущей главе мы рассмотрели одну часть модели синтезированной камеры — методику описания трехмерных объектов и манипулирования ими. В этой главе будет рассмотрена другая часть — способы описания виртуальной камеры. В процессе обсуждения мы затронем и темы, тесно связанные с этими способами, в частники сопоставим классические методы визуализации с теми, которые используются в компьютерной графике.
Обсуждение условно делится на три части. Сначала мы обсудим, какие типы видов можно формировать в графической системе и почему недостаточно располагать видами только одного типа. Затем рассмотрим, какие средства предоставляет OpenGL в распоряжение прикладного программиста для формирования конкретного вида. Будет показано, что процесс визуализации распадается на две стадии. На первой стадии используется .матрица вида, которая задает преобразование между мировым фреймом и фреймом камеры. Напомним, что модели объектов сцены представляются в мировом фрейме. Представление объектов во фрейме камеры позволяет на второй стадии использовать канонические процедуры визуализации. Вторая часть процесса имеет отношение к преобразованию проецирования. Прикладной программист, а значит, и пользователь могут выбирать тип проекции (параллельная или перспективная) и ту часть мирового пространства, которая преобразуется в изображение. По этим параметрам формируется .матрица проецирования (или проективного преобразования), которая объединяется с матрицей вида. Весь процесс визуализации будет проиллюстрирован на примере с использованием соответствующих функций OpenGL. Третья часть обсуждения в этой главе — вывод матриц проецирования для наиболее распространенных параллельных и перспективных видов.
Классическая и компьютерная визуализация
Прежде чем рассматривать интерфейс между системой компьютерной графики и прикладной программой, необходимый для визуализации трехмерных объектов, совершим небольшой экскурс в область классических методов визуализации. Это нужно сделать по двум причинам. Во-первых, множество видов чертежных работ, выполнявшихся раньше вручную — в архитектурном проектировании, конструировании и мультипликации, — сейчас перекладывается на системы компьютерной графики. При этом с помощью компьютерной системы нужно воспроизводить привычные для специалистов виды — изометрические, в плане и
разного рода перспективные. Во-вторых, сравнение классических методов визуализации и
тех, которые доступны системам компьютерной графики, позволит выяснить достоинства и
недостатки подходов, использованных в конкретных графических API.
При описании модели синтезированной камеры (см. главу 1) мы уже обращали ваше внимание на сходство классических и компьютерных методов визуализации. Базовые элементы в
цпцпРис. 5.1. ВизуализацияРис. 5.1. Визуализацияобоих случаях одни и те же: объекты, которые мы “просматриваем”, проецирующие лучи и картинная плоскость (плоскость проекции) (рис. 5.1). Проецирующие лучи пересекаются в точке, которая называется центром проецирования (ЦП). Точка ЦП соответствует фокусу объектива или глаза и в системе компьютерной графики выбирается в качестве центра фрейма камеры (camera frame). Описанная в главе 1 модель синтезированной камеры, которая, в свою очередь, базируется на геометрической оптике, положена в основу всех стандартных графических систем. Но следует учитывать, что использование в качестве поверхности, на которую проецируется изображение, плоскости, не является
цп
цп
Рис. 5.1. Визуализация
Рис. 5.1. Визуализация
единственно возможным вариантом. Иногда изображение проецируется на цилиндрическую или сферическую поверхность, но в стандартных графических системах такие варианты не
используются, по крайней мере в настоящее время.
Рис. 5.2. Удаление ЦП в бесконечностьРис. 5.2. Удаление ЦП в бесконечностьИ в классической, и в компьютерной графике ЦП может быть расположен как угодно далеко от картинной плоскости и от объектов, что делает проецирующие лучи параллельными. В результате центр проецирования в модели заменяется направлением проецирования (DOP— direction of projection), как показано на рис. 5.2. Виды, которые создаются при расходящихся проецирующих лучах, т.е. когда ЦП находится не слишком далеко от картинной плоскости, называются перспективными (perspective views), а при параллельных проецирующих лучах — параллельными видами (parallel views).
Рис. 5.2. Удаление ЦП в бесконечность
Рис. 5.2. Удаление ЦП в бесконечность
На ил. 3 и 4 цветной вклейки показаны соответственно параллельный и перспективный виды спроектированного здания, созданные с помощью САПР. Эти иллюстрации показывают, насколько важно иметь в составе графической системы средства для формирования видов обоих типов. В большинстве современных графических API такие средства имеются. Класс проекций, формируемых такими системами, называется плоскими геометрическими проекциями (planar geometric projections), поскольку поверхность, на которую проецируется изображение, является плоскостью, а линии проецирования — лучами. И перспективная, и параллельная проекции преобразуют прямую в прямую, но, в общем случае, не сохраняют углы
между прямыми. Хотя параллельные виды можно считать частным случаем перспективных, и в классической, и в компьютерной графике их рассматривают как отдельные классы видов. В классической (ручной) графике для построения параллельных и перспективных видов используются совершенно различные методики, что известно всем, кто профессионально занимался рисованием или техническим черчением. С точки зрения компьютерных графических систем они отличаются набором характеристических параметров. Кроме того, до последнего времени средства для формирования параллельных видов реализовать было значительно проще, чем средства построения перспективных изображений. В современных системах с конвейером, реализованным аппаратно, это различие практически исчезло, но при программной реализации построение параллельных проекций все же требует меньших вычислительных затрат, чем построение перспективных.
В классической графике, помимо рассмотренных параллельной и перспективной проекций, используется, хотя и значительно реже, и множество других способов проецирования, начиная от классической системы трех ортогональных видов и заканчивая двух- и трехточечными перспективными видами. Такие виды позволяют передать отношения между объектом, наблюдателем и картинной плоскостью, что противоречит принятому в компьютерной графике подходу, ориентированному на независимость этих трех “субъектов” процесса визуализации.
Визуализация в классической графике
Когда архитектор вычерчивает на листе ватмана вид проектируемого здания, он заранее планирует, какая сторона здания должна быть изображена, и соответственно “размещает” мнимого наблюдателя по отношению к зданию. Каждый классический вид определяется специфическими отношениями между объектами и наблюдателем.
В классической графике существует понятие главной грани (principal face). Чертежи реальных объектов, например зданий в архитектуре или машин в технике, компонуются из видов на картинные плоскости, параллельные главным граням. Для объектов, имеющих прямоугольную форму, выбор главных граней очевиден — это фасад, тыльная сторона, верхняя и нижняя грани и две боковые.
На рис. 5.3 показаны основные типы видов, используемых в архитектурном черчении. Мы начнем их обсуждение с наиболее простых, а затем перейдем к тем, которые характеризуются меньшими ограничениями.
ФронтальныйФронтальная косоугольная проекцияИзометрияОдноточечная перспективаРис. 5.3. Виды в архитектурном черченииВерхняя косоугольная проекцияТрехточечная перспективаФронтальныйФронтальная косоугольная проекцияИзометрияОдноточечная перспективаРис. 5.3. Виды в архитектурном черченииВерхняя косоугольная проекцияТрехточечная перспектива
ФронтальныйФронтальная косоугольная проекция
ИзометрияОдноточечная перспектива
Рис. 5.3. Виды в архитектурном черчении
Верхняя косоугольная проекция
Трехточечная перспектива
ФронтальныйФронтальная косоугольная проекция
ИзометрияОдноточечная перспектива
Рис. 5.3. Виды в архитектурном черчении
Верхняя косоугольная проекция
Трехточечная перспектива
Ортографические проекции
Первый классический вид — это ортогональная, или ортографическая проекция {orthographic projection), представленная на рис. 5.4. При построении всех ортогональных видов проецирующие лучи перпендикулярны картинной плоскости. В техническом и архитектурном черчении часто на одном чертеже совмещают три ортогональные проекции — фронтальную, в плане и боковую, для каждой из которых картинная плоскость расположена параллельно соответствующей главной грани объекта (или, точнее, прямоугольной оболочки объекта) (рис. 5.5). Но использование только ортогональных проекций требует от наблюдателя определенного искусства, умения восстановить пространственную форму объекта по этим проекциям. В технике даже имеется для этого специальный термин — “умение читать чертежи”. Такие проекции обладают весьма важным свойством — они сохраняют как пропорции размеров элементов, так и углы между ними.
Рис. 5.4. Ортографическая проекцияРис. 5.5. Здание и три его ортогональных вида
Аксонометрические проекции
Если желательно иметь на чертеже изображение не одной главной грани, а двух-трех, нужно устранить одно из ограничений, характеризующих ортографические проекции. В аксонометрических (axonometric) проекциях проецирующие лучи по-прежнему ортогональны картинной плоскости, как показано на рис. 5.6, но сама картинная плоскость может иметь любую ориентацию относительно объекта. Если картинная плоскость ориентирована симметрично по отношению к трем главным граням, пересекающимся в одном углу прямоугольного объекта, то образуется изометрическая (isometric) проекция. Если картинная плоскость ориентирована симметрично по отношению к двум главным граням, то образуется диметри- ческая (dimetric) проекция. Общий случай — триметрическая (trimetric) проекция. Все три вида аксонометрических проекций показаны на рис. 5.7. Обратите внимание на то, что на изометрическом виде длины отрезков меньше, чем на исходном объекте. В изометрической проекции коэффициент искажения (foreshortening) длин одинаков по всем трем главным осям, а потому такой вид можно использовать для измерения длин. В диметрической проекции коэффициент искажения длин по двум осям одинаков, а по третьей отличается, т.е. мы имеем дело с парой коэффициентов. В триметрической проекции коэффициенты искажения по всем трем осям различны. В аксонометрических проекциях сохраняется параллельность прямых, но углы между ними искажаются. Окружность в аксонометрической проекции пре-
образуется в эллипс. Эти искажения — плата за возможность видеть на одной плоской проекции больше, чем одну главную грань объекта, при том что методы построения аксонометрических проекций достаточно просты и давно освоены в техническом черчении. Аксонометрические проекции широко применяются в архитектурном планировании и техническом конструировании.
а)б)в)а)б)в)Рис. 5.6. Аксонометрическое проецирование: а — формирование триметрической проекции; б — вид сверху; в — вид сбокуРис. 5.6. Аксонометрическое проецирование: а — формирование триметрической проекции; б — вид сверху; в — вид сбокуДиметрическаяДиметрическаяТриметрическаяТриметрическаяРис. 5.7. Виды аксонометрических проекцийРис. 5.7. Виды аксонометрических проекцийИзометрическаяИзометрическая
а)б)в)
а)б)в)
Рис. 5.6. Аксонометрическое проецирование: а — формирование триметрической проекции; б — вид сверху; в — вид сбоку
Рис. 5.6. Аксонометрическое проецирование: а — формирование триметрической проекции; б — вид сверху; в — вид сбоку
Диметрическая
Диметрическая
Триметрическая
Триметрическая
Рис. 5.7. Виды аксонометрических проекций
Рис. 5.7. Виды аксонометрических проекций
Изометрическая
Изометрическая
Косоугольные проекции
Косоугольная {oblique} проекция является параллельной проекцией общего вида. При построении косоугольной проекции не накладываются никакие ограничения на угол между проецирующими лучами и картинной плоскостью (рис. 5.8). В косоугольной проекции сохраняются углы между прямыми на объекте, расположенными в плоскости, параллельной картинной. Окружность в такой плоскости также проецируется в окружность и при этом на изображении присутствует более чем одна главная грань объекта. Строить косоугольные проекции вручную — задача довольно сложная. Кроме того, они выглядят все-таки как-то ненатурально. В большинстве физических систем формирования изображения — в фотоаппаратах или в глазах человека— плоскость объектива параллельна картинной плоскости. Хотя в таких приборах фактически формируется перспективная проекция, но при разглядывании достаточно удаленных объектов получается почти параллельная проекция, но именно ортогонально параллельная, поскольку картинная плоскость параллельна плоскости объектива. Не-
которые фотокамеры с растягивающимся мехом (их теперь можно увидеть только в музее или лавке антиквара) оснащены механизмом, который позволяет поворачивать объектив, сохраняя неизменной ориентацию фотопластинки. Это один из немногих физических приборов, в котором можно получить проекцию, близкую к параллельно косоугольной.
Рис. 5.8. Косоугольная проекция: а — формирование; б — вид сверху; в — вид сбоку
С точки зрения прикладного программиста особой разницы между разными видами параллельных проекций нет. От него требуется только указать тип проекции — параллельная или перспективная — и набор параметров, специфицирующих положение и характеристики камеры. Единственная проблема — как задать эти параметры, если требуется получить определенный “классический” вид или наилучшим образом представить определенный объект наблюдателю.
Визуализация с учетом перспективы
Все перспективные виды характеризуются наличием перспективных сокращений (diminution). Чем дальше отстоит объект от наблюдателя, тем меньше размер его изображения. Это искажение размеров и придает перспективному изображению натуральность, но оно же не позволяет использовать такое изображение для оценки действительных размеров изображенных объектов. Поэтому перспективные проекции применяются в основном в тех областях приложения компьютерной графики, где важна именно натуральность изображения с точки зрения наблюдателя, — в архитектуре и кинематографии.
При построении перспективной проекции классическими методами предполагается, что наблюдатель расположен симметрично относительно картинной плоскости (точнее, сторон ограничивающего прямоугольника в картинной плоскости), как показано на рис. 5.9. Таким образом, пирамида, основанием которой является ограничивающий прямоугольник в картинной плоскости, а вершиной — центр проецирования, является симметричной, или правильной пирамидой. Эта симметрия порождается геометрией конструкции большинства оптических приборов, в том числе и глаза человека. Но в некоторых фотокамерах, в частности фотокамерах с мехом, плоскость фотопластины можно поворачивать относительно плоскости объектива. В результате можно формировать перспективную проекцию общего вида. Те модели построения перспективы, которые используются в компьютерной графике, распространяются и на такой случай.
В классической графике известны одно-, двух- и трехточечная перспективные проекции. Разница между этими тремя видами заключается в том, сколько направлений сохраняет параллельность при проецировании. Рассмотрим три разные перспективные проекции здания на рис. 5.10. К каждому углу здания подходят линии, параллельные его главным осям. В наиболее общем случае — трехточечной перспективной проекции — линии, направленные вдоль всех трех осей, которые на объекте параллельны, становятся на проекции непараллельными и сходятся в тех разных точках схода (vanishing pointsXpwc. 5.10,а). При двухточечной перспективе линии, проходящие вдоль одной из главных
Рис. 5.9. Перспективная проекцияРис. 5.9. Перспективная проекцияосей, сохраняют параллельность и на изображе
Рис. 5.9. Перспективная проекция
Рис. 5.9. Перспективная проекция
нии (рис. 5.10,6), а при одноточечной перспективе сохраняют параллельность линии, проходящие вдоль двух главных осей (рис. 5.10,в). В двухточечной перспективе имеются две точки
Рис. 5.10. Классические перспективные проекции: а— трехточечная; б — двухточечная; в — одноточечнаяРис. 5.10. Классические перспективные проекции: а— трехточечная; б — двухточечная; в — одноточечнаясхода, а в одноточечной — одна. С точки зрения прикладного программиста все описанные варианты являются частным случаем обобщенной перспективной проекции, математическое описание которой будет детально рассмотрено в разделе 5.3.
Рис. 5.10. Классические перспективные проекции: а— трехточечная; б — двухточечная; в — одноточечная
Рис. 5.10. Классические перспективные проекции: а— трехточечная; б — двухточечная; в — одноточечная
Размещение камеры
Теперь вернемся к трехмерной системе компьютерной графики. Рассмотрим, какими функциями располагает OpenGL для поддержки отображения трехмерных объектов. Мы также увидим, насколько эти средства отличаются от тех, которые имеются в других графических системах, — GKS-3D и PHIGS. В этом разделе речь пойдет о средствах моделирования положения камеры, а в разделе 5.4 — о проективных преобразованиях.
В OpenGL матрицы вида и проецирования перемножаются, и формируется единая матрица преобразования, которая применяется ко всем вершинам всех геометрических объектов. Мы уже рассматривали использование матрицы вида для размещения объектов в пространстве. Другой способ ее применения — преобразование из опорного фрейма модели во фрейм камеры.
Настройка положения фрейма камеры
Как было показано в главе 4, координаты вершин можно задавать в любых приемлемых единицах, а матрицу вида определять с помощью суперпозиции аффинных преобразований, которые задают изменение положения этих вершин. Операции формирования матрицы вида можно рассматривать и как изменение фрейма, а результат применения преобразования вида— как задание положения вершин в другом фрейме. Другими словами, мы определяем объекты в некотором подходящем локальном фрейме, а затем с помощью матрицы вида переводим их из локального фрейма в мировой или фрейм пользователя. В некоторых API этот процесс трактуется таким образом: объекты моделируются в собственной системе координат модели, а преобразование экземпляра переносит их в мировой фрейм.
Объекты моделируются независимо от того, как они потом будут отображаться, а следовательно, и независимо от положения камеры, через которую на них будет “смотреть” пользователь. По умолчанию OpenGL размещает камеру в начале координат мирового фрейма, причем ось проецирования направляется в сторону, противоположную оси z мирового фрейма (рис. 5.11). Следовательно, если матрица вида единичная, то фрейм камеры и мировой фрейм совпадают.
Рис. 5.11. Исходное положение камерыРис. 5.11. Исходное положение камерыВ большинстве приложений в ходе моделирования объекты размешаются в окрестности начала координат, а значит, камера при ее расположении по умолчанию не видит всех объектов сцены. Таким образом, либо нужно сместить камеру так, чтобы в поле ее зрения попали все необходимые объекты, либо нужно “оттащить” объекты так, чтобы они оказались перед камерой. В программном смысле это эквивалентные операции.
Рис. 5.11. Исходное положение камеры
Рис. 5.11. Исходное положение камеры
Можно считать, что сцена, сформированная в процессе моделирования, имеет единичную матрицу вида, и задавать все вершины с помощью функции glVertex(). Последующие изменения матрицы вида сместят мировой фрейм по отношению к камере и таким образом повлияют на то, как они, в конце концов, будут видны в камере, поскольку вершины заданы относительно преобразованного мирового фрейма. В терминах процесса выполнения программы матрицы проецирования и вида являются компонентами текущего состояния. При формировании примитивов система использует текущее состояние и именно его применяет к вершинам, заданным в программе.
Рассмотрим последовательность операций, представленную на рис. 5.12. Исходная конфигурация показана на рис. 5.12,а. Вершина, заданная набором координат р, имеет одинаковое представление в обоих фреймах. На рис. 5.12,6 показана измененная конфигурация фреймов — матрица вида в результате последовательности преобразований получила значение С. Теперь два фрейма не совпадают, а матрица С содержит информацию о переходе от фрейма камеры к мировому фрейму, или, что то же самое, содержит информацию о том, как камера была сдвинута из исходной позиции, совпадавшей с началом координат мирового фрейма. Вершина, которая была сформирована в q с помощью gIVertex (), после изменения матрицы вида будет по-прежнему занимать позицию q в мировом фрейме. Однако ее позиция во фрейме камеры теперь будет Cq, и это будет известно исполняющей системе OpenGL. OpenGL преобразует координаты позиций в систему координат камеры в процессе выполнения операций конвейера визуализации. Можно и по-иному трактовать этот процесс: камера по-прежнему остается в начале координат собственного фрейма, а матрица вида применяется ко всем примитивам, описанным в данной системе. Вы можете использовать ту трактовку, которая для вас более понятна. Главное— внимательно следить за тем, каково состояние матрицы вида в момент формирования примитивов в программе.
У'УсУ'УсУУ
У'Ус
У'Ус
У
У
а) 6)
Рис. 5.12. Взаимное перемещение фрейма камеры и мирового фрейма: а — исходная конфигурация; б — конфигурация после изменения матрицы вида
В любой текущий момент состояние матрицы вида задает отношение между фреймом камеры и мировым фреймом. Хотя объединение в одной матрице преобразований моделирования и формирования вида поначалу может сбивать с толку, но после внимательного анализа оказывается, что такой подход довольно эффективен. Если считать камеру объектом с определенными геометрическими свойствами, то преобразования, которые изменяют положение и ориентацию каких-то объектов, будут также изменять и положение, и ориентацию камеры относительно этих объектов.
После знакомства с концепцией взаимного размещения фреймов невольно возникает вопрос, а как все это реализовано в OpenGL. В дальнейшем для определенности будем считать, что камера перемещается относительно мирового фрейма. В общих чертах мы рассмотрим три подхода: один — в этом разделе, а два других — в разделе 5.2.2. Еще два подхода я предлагаю читателям проанализировать самостоятельно при выполнении упр. 5.2 и 5.3.
Сначала зададим положение камеры косвенно посредством последовательности поворотов и сдвигов в матрице вида. Этот подход является, по сути, реализацией преобразований экземпляра, описанных в главе 4. При этом нужно обратить особое внимание на два обстоятельства. Во-первых, как правило, в программе стремятся определиться с положением камеры еще до того, как будут созданы отображаемые объекты. Во-вторых, применение преобразований по отношению к камере может привести к эффекту, противоположному ожидаемому.
Рассмотрим объект, центр которого находится в начале координат. Камера находится в начальном положении — в начале координат, а ее оптическая ось направлена вдоль отрицательной полуоси z. Пусть нам требуется “посмотреть” на ту грань объекта, которая видна со стороны положительной полуоси х. Следовательно, нужно отодвинуть камеру от начала координат. Если при этом не изменять ориентацию камеры, то смещать ее нужно вдоль положительной полуоси z. Соответствующее преобразование реализуется функцией
glTranslatef(0.0, 0.0, -d);
где d — положительное число.
Для многих удобнее интерпретировать эту операцию как сдвиг фрейма камеры относительно мирового фрейма. Такая точка зрения базируется на классической графике. В компьютерной графике распространен другой подход — считается, что объект установлен в фиксированном фрейме, а наблюдатель “бегает” вокруг него в поисках подходящего ракурса.В классической графике наблюдатель занимает доминирующую позицию. Концептуально процесс визуализации выполняется таким образом: мы берем объект, ориентируем его так, как считаем нужным, и переносим в нужное место. Одно из следствий классического подхода состоит в том, что расстояние измеряется от наблюдателя до объекта, а не от объекта до наблюдателя, как это делается в большинстве физических приборов. В результате классический подход приводит к левостороннему фрейму камеры. Во многих графических системах, в том числе и OpenGL, следуют этому подходу, моделируя в правосторонней системе координат, а визуализируя — в левосторонней. Это решение, хотя и корректное с технической точки зрения, кажется весьма странным для большинства пользователей. В OpenGL расстояния, характеризующие параллелепипед видимости (те, которые используются в качестве аргументов вызова glOrtho()), отсчитываются от камеры, что эквивалентно использованию левостороннего фрейма камеры.
Теперь рассмотрим, как посмотреть на этот объект со стороны положительной полуоси х. Для этого потребуется не только отодвинуться от объекта, но и повернуть камеру вокруг оси у, как показано на рис. 5.13. Сдвиг нужно выполнить после поворота на 90° вокруг оси у. В тексте программы вызовы соответствующих функций должны идти в обратном порядке, как на том акцентировалось ваше внимание в главе 4. Поэтому соответствующий фрагмент программы будет выглядеть следующим образом: glMatrixMode(GL_MODELVIEW);
glLoad!dentity();
glTranslatef(0.0, 0.0, -d); glRotatef(-90.0, 0.0, 1.0, 0.0);
В терминах двух фреймов мировой фрейм сначала поворачивается относительно фрейма камеры, а затем фреймы отодвигаются друг от друга.
Рис. 5.13. Позиционирование камерыРис. 5.13. Позиционирование камерыВ некоторых примерах в главах 2 и 4 была использована устанавливаемая по умолчанию матрица проецирования, подробнее о ней мы поговорим в разделе 5.3. Сейчас же воспользуемся тем, что по умолчанию она формирует ортогональную проекцию, причем камера размещается в начале координат мирового фрейма, а ее ось проецирования направляется в сторону, противоположную оси z мирового фрейма. В примере с вращением куба (см. главу 4) мы поворачивали куб, чтобы увидеть нужную грань. Но как уже отмечалось, тот же эффект дает и поворот фрейма куба относительно фрейма камеры (такое же изображение можно получить, поворачивая камеру относительно куба).
Рис. 5.13. Позиционирование камеры
Рис. 5.13. Позиционирование камеры
Рассмотрим, как можно сформировать изометрический вид куба. Предположим, что в исходном положении центр ку-
ба находится в начале координат, а его ребра ориентированы вдоль осей координат. Так как камера оказывается при этом внутри куба, нам нужно отодвинуть ее с помощью преобразования сдвига. Изометрический вид получится в том случае, если камера будет установлена симметрично трем граням, пересекающимся в некоторой вершине куба. Поэтому сначала нужно соответствующим образом повернуть куб, а потом отодвинуть от него камеру.
Хотя нам еще предстоит отодвинуть камеру, предположим, что мы смотрим на куб откуда-то со стороны положительной полуоси z. Можно будет получить один из восьми изометрических видов (соответствующих восьми вершинам куба), повернув его сначала вокруг оси у, пока не увидим симметричные изображения двух граней (рис. 5.14,а). Очевидно, что для этого нужно повернуть куб на 45°. Второй поворот— вокруг оси х. Будем поворачивать куб вниз, пока не получим требуемый изометрический вид. Для этого потребуется повернуть его вокруг оси х на-35.26°. Вычислить значение второго угла можно следующим образом.
Рассмотрим, какое положение занял куб после первого поворота. Поскольку мы смотрим на него со стороны положительной полуоси с, то куб будет выглядеть, как на рис. 5.14,а. Вершина (-1, 1, 1) теперь займет положение (0, 1, V2 ). Если теперь посмотреть на куб со стороны оси х, как на рис. 5.14,6, то очевидно, что нужно поворачивать его до тех пор, пока правая верхняя вершина не совместится с осью у. Прямоугольный треугольник, который содержит этот угол, имеет катеты 1 и V2 , что соответствует углу 35.26°. Поворот должен быть выполнен по часовой стрелке, а значит, нужно взять этот угол со знаком “минус”. Последний шаг — отодвинуть камеру от начала координат. Следовательно, наша стратегия — сначала повернуть фрейм камеры относительно фрейма объекта, а затем разделить два фрейма. Соответствующая матрица вида определяется следующим образом:
М = TRXR>
Рис. 5.14. Куб после поворота вокруг оси у: а — вид со стороны положительной полуоси z; б — вид со стороны положительной полуоси хРис. 5.14. Куб после поворота вокруг оси у: а — вид со стороны положительной полуоси z; б — вид со стороны положительной полуоси х
Рис. 5.14. Куб после поворота вокруг оси у: а — вид со стороны положительной полуоси z; б — вид со стороны положительной полуоси х
Рис. 5.14. Куб после поворота вокруг оси у: а — вид со стороны положительной полуоси z; б — вид со стороны положительной полуоси х
Элементы этой матрицы вычисляются суперпозицией матриц элементарных преобразований в однородных координатах. Перемножение матриц поворотов дает следующее:
'1
0
0
0'
V2/2
0
42/2
0
R = RxRl =
0
7б/з
7з/з
-Vs/3
0
0
1
0
0
0
7б/з
0
— V
^/2
0
42/2
0
0
0
0
1
0
0
0
1
/2/2
0Л
/2/2
0
/б / 6
Уб/з -
Тб/6
0
-
7з/з
Уз / з л
/з / з
0
0
0
0
1
Несложно проверить, что исходная вершина (-1, 1, 1) корректно преобразуется этой матрицей в (О, О, 7з ). Теперь добавим к повороту преобразование сдвига на (0, 0, -</). Получим:
V2/2 О V2/2 О 7б/б 7б/з -7б/б о -7з/з 7з/з 7з/з -а
0 0 0 1
Фрагмент программы, в котором формируется эта матрица вида, имеет следующий вид: glMatrixMode(GL_MODELVIEW);
glLoad!dentity();
gllranslatef(0.0, 0.0, -d);
glRotatef(-35.26, 1.0, 0.0, 0.0);
glRotatef(45.0, 0.0, 1.0, 0.0);
Обратите внимание на то, что функция glOrtho() устанавливает параллелепипед видимости относительно фрейма камеры. Таким образом, при ортогональном проецировании сдвиг камеры не влияет на размер изображения, но определяет, попадет ли объект “в кадр” или нет.
Задание ориентации камеры
Я подозреваю, что предыдущий пример с вычислением параметров преобразования вида, необходимых для получения изометрии объекта, вас, мягко говоря, озадачил. Неужели придется вручную вычислять эти параметры, а затем задавать их в программе? Должен вас успокоить — продемонстрированный подход далеко не единственный. Ниже мы рассмотрим другой подход, который реализован в PHIGS и GKS-3D. Как и раньше, начнем с мирового фрейма. Опишем в нем положение и ориентацию камеры. Тип проецирования — параллельное или перспективное — определяется отдельно. Эта процедура эквивалентна спецификации матрицы проецирования в OpenGL. Первую часть процесса визуализации часто называют нормализацией {normalization transformation). Как и раньше, в исходном состоянии камера находится в начале координат мирового фрейма, и ее оптическая ось направлена вдоль отрицательной полуоси z мирового фрейма. Требуется установить камеру таким образом, чтобы она находилась в точке, которую будем называть точкой привязки вида — ТПВ {view-reference point) (рис. 5.15). Положение ТПВ задается в мировом фрейме. Для установки камеры в эту точку в прикладной программе вызывается функция set_view_reference_point(x, у, z);
Далее нужно задать ориентацию камеры. Эту задачу можно
Р ,~ разделить на две подзадачи: задание ориентации картинной Т/плоскости посредством вектора нормали к картинной плоско-
I/ vemu {view-plane normal) и задание вектора вертикали вида
{view-up vector). Вектор нормали к картинной плоскости (п на Ч. ' рис. 5.15) задает ориентацию картинной плоскости или “фотопластинки” в камере. Ориентация любой плоскости всегда определяется вектором нормали к ней, соответствующая функция входит в состав API:
Рис. 5.15. Фрейм камеры.,,
set_view_plane_normal(nx, ny, nz);
Но для однозначного определения ориентации камеры недостаточно знать ориентацию картинной плоскости, поскольку остается еще одна степень свободы — поворот камеры вокруг
нормали к картинной плоскости. Поэтому потребуется определить еще и положение вектора вертикали вида (w на рис. 5.15), что зафиксирует однозначную ориентацию камеры. Это выполняется вызовом еще одной функции API:
set_view_up(w_x, w_y, w_z);
Проекция вектора вертикали вида w на картинную плоскость дает вектор вертикали картинной плоскости v (рис. 5.16). Таким образом, в качестве вектора вертикали вида w можно задать любой вектор, непараллельный п, что избавляет пользователя от необходимости вычислять вектор, лежащий обязательно в картинной плоскости. Вектор v ортогонален вектору п. Третий вектор, и, образующий с первыми двумя правостороннюю тройку, есть векторное произведение v х п. Полученную таким образом новую систему координат принято называть либо системой координат вида (viewing-coordinate system), либо u-v-n системой. Вместе с ТПВ эта тройка векторов образует фрейм камеры. Матрица, которая выполняет соответствующее преобразование фрейма, называется матрицей ориентации вида (view-orientation matrix).
Рис. 5.16. Определение вектора вертикали картинной плоскостиРис. 5.16. Определение вектора вертикали картинной плоскостиЭта матрица формируется в результате суперпозиции матриц поворота и сдвига в однородных координатах. Начнем с определения точки привязки вида
Рис. 5.16. Определение вектора вертикали картинной плоскости
Рис. 5.16. Определение вектора вертикали картинной плоскости
нормали к картинной плоскости
П$
п.
О
и вектора вертикали вида
Wv W. о
Начало координат нового фрейма устанавливается в точке привязки вида, вектор нормали к картинной плоскости задает направление одной координатной оси, а две другие задаются векторами и и v. По умолчанию считаем, что тройке осей х, у, z соответствуют и, v, п. Такой выбор используется и в установленной по умолчанию матрице вида в OpenGL. Перенос начала фрейма в точку привязки вида выполняется посредством матрицы сдвига Т(-х, -у, -z). Далее выполняется поворот, и матрица вида V образуется как произведение
V = TR.
Вектор v должен быть ортогонален вектору п, следовательно, п • v = 0.
На рис. 5.16 видно, что v является проекцией w на картинную плоскость, заданную своей нормалью п. Следовательно, v есть линейная комбинация п и w:
v = an + Pw.
Если пренебречь длинами векторов, то можно положить 0=1 и получить
W п . а = п п
и
wn
V = Wп . п п
Теперь можно определить третий вектор правой тройки как векторное произведение:
U = V X п.
В общем случае эти векторы не являются единичными, поэтому их нужно нормализовать и получить три единичных вектора (орта) осей координат фрейма камеры u', у' и п'. Матрица поворота
их у' пх О
и' у' п' О
м =1
м' у' п' О
0 0 0 1
позволяет получить ориентацию вектора, заданного в системе u', у', п', по отношению к осям координат исходной системы. Но нам нужно решать обратную задачу — получить представление вектора, заданного в исходной системе координат,, в системе u', v', п'. Поэтому нужно обратить матрицу М, но поскольку это матрица “чистого” поворота, то обращение можно заменить транспонированием:
R = М1 = Мг.
Последняя операция — умножение на матрицу сдвига Т:
V = RT =V = RT =<у'<у'0000< - хи'х -yu'v - ги'г v' -xv'x-yv'v-zv', n' - xnx - уn'v - zn'0 1< - хи'х -yu'v - ги'г v' -xv'x-yv'v-zv', n' - xnx - уn'v - zn'0 1
V = RT =
V = RT =
<
у'
<
у'
00
00
< - хи'х -yu'v - ги'г v' -xv'x-yv'v-zv', n' - xnx - уn'v - zn'
0 1
< - хи'х -yu'v - ги'г v' -xv'x-yv'v-zv', n' - xnx - уn'v - zn'
0 1
Обратите внимание на то, что в данном случае матрица сдвига стоит в произведении справа, в то время как раньше мы умножали ее слева. Интерпретировать это отличие можно следующим образом: в первом случае мы сначала поворачивали фрейм, а затем отодвигали его в направлении, которое было представлено в координатах фрейма камеры. В данном же случае позиция камеры (а следовательно, и требуемое ее смещение из исходного положения) представлена в координатах мирового фрейма.
Можно и по-другому разобраться в этих отличиях. Обратите внимание на то, что матрицы RT и TR имеют аналогичную структуру. Ориентирующие части обоих произведений матриц — верхние левые подматрицы 3x3 — идентичны, а три первых элемента четвертого столбца отличаются по той простой причине, что компоненты смещения выражаются в них в разных фреймах. В произведении TR — это компоненты в неповернутом фрейме, а в произ-
ведении RT — в повернутом. Для примера с получением изометрического вида, который был рассмотрен в предыдущем разделе, имеем:
п =п =11 О11 ОО 1 О 0.
п =
п =
1
1 О
1
1 О
Положение камеры следует выбрать на диагонали исходного фрейма, т.е.
-ddd
В результате получим ту же матрицу вида, что и в разделе 5.2.1.
Описанный выше метод задания положения камеры является только одним из многих. В некоторых ситуациях удобнее задавать ориентацию камеры косвенно с помощью точки визирования на рассматриваемом объекте. Рассмотрим компоновку камеры и объекта, представленную на рис. 5.17. Камера находится в точке е, которая задана в координатах мирового фрейма, а ее оптическая ось направлена таким образом, что проходит через вторую точку а, которая называется точкой визирования (at point). Этими двумя точками определяется вектор нормали к картинной плоскости:
п = е-а.
После этого остается только определиться с направлением вектора вертикали картинной плоскости, который, как и раньше, можно задать вектором вертикали вида. В составе OpenGL имеется функция gluLookAt(), которая позволяет изменить матрицу вида в соответствии с заданной точкой визирования и заданным вектором вертикали вида:
gluLookAt(eyex, eyey, eyez, atx, aty, atx, upx, upy, upz);
Аргументы функции имеют следующий смысл:
eyex, eyey, eyez — компоненты точки привязки вида;
atx, aty, atx — компоненты точки визирования;
Рис. 5.17. Задание положения камеры с помощью точки визированияРис. 5.17. Задание положения камеры с помощью точки визированияupx, upy, upz—компоненты точки, которая задает вектор вертикали вида.
Рис. 5.17. Задание положения камеры с помощью точки визирования
Рис. 5.17. Задание положения камеры с помощью точки визирования
В приложениях, имеющих дело с различными тренажерами, ни один из описанных способов задания положения камеры не подходит. В симуляторе полета пилот управляет программой отображения, задавая три угла ориентации воображаемого летательного аппарата: крена (roll), тангажа (pitch) и рыскания (yaw). Эти углы задаются относительно осей системы координат аппарата, центр которой находится в его центре масс (рис. 5.18). Следовательно, и изображение, которое видит пилот, должно формироваться в зависимости от этих углов и расстояния между
аппаратом и объектами. Исходя из этого, матрица вида может формироваться из матрицы сдвига и трех канонических матриц поворота вокруг осей координат (см. упр. 5.2).
КренКренТангажТангажРысканиеРысканиеРис. 5.18. Углы ориентации летательного аппаратаРис. 5.18. Углы ориентации летательного аппарата
Крен
Крен
Тангаж
Тангаж
Рыскание
Рыскание
Рис. 5.18. Углы ориентации летательного аппарата
Рис. 5.18. Углы ориентации летательного аппарата
В некоторых других приложениях ориентацию камеры удобно описывать в полярных, а не прямоугольных координатах. В частности, это приходится делать в приложениях, где
Рис. 5.19. Возвышение и азимутРис. 5.19. Возвышение и азимутодни объекты вращаются вокруг других. Рассмотрим, например, каким образом задается положение светила на небосводе. Направление взгляда наблюдателя на светило задается углами возвышения {elevation) и азимута {azimuth) (рис. 5.19). Угол возвышения отсчитывается от горизонтальной плоскости. Азимут — это угол, который отсчитывается от координатной оси, лежащей в горизонтальной плоскости, до проекции линии визирования на эту плоскость. При таком способе задания ориентации камеры, ось проекции которой совмещается с линией визирования, остается еще одна степень свободы — возможность поворачивать камеру вокруг оси проекции, задавая угол кручения {twist angle).
Рис. 5.19. Возвышение и азимут
Рис. 5.19. Возвышение и азимут
Проецирование
Те, кто увлекаются фотографией, знают, что после установки камеры нужно выбрать подходящий объектив или настроить фокусное расстояние трансфокатора. Как было показано в главе 1, зона захвата камеры зависит от размеров пленки и характеристик объектива. В компьютерной графике имеет место аналогичная ситуация — какие объекты будут включены в изображение, зависит от выбора типа проецирования и параметров визуализации.
В фотографии широкоугольный объектив дает наиболее ощутимую перспективу — объекты, расположенные близко к камере, имеют на отпечатке гораздо большие размеры, чем объекты, несколько отстоящие от камеры. И наоборот, длиннофокусные объективы (их часто называют телеобъективами) скрадывают перспективу, уравнивая размеры объектов. Говорят, что такое изображение “плоское”, приближающееся по характеру к тому, что создается при параллельном проецировании. В большинстве графических API параллельное и перспективное проецирования рассматриваются как два разных типа, причем каждому из них соответствует свой набор функций обработки. Такой же подход реализован и в OpenGL, хотя и для того, и для другого используется тот же конвейер обработки, как мы увидим в разделах 5.8 и 5.9.
При работе с OpenGL матрица проецирования может быть загружена с помощью функции glLoadMatrix(), как и матрица вида. Для большинства стандартных условий визуализации в составе OpenGL имеются соответствующие функции. Но сначала мы рассмотрим математический аппарат описания проективных преобразований на основе однородных координат. Этот аппарат позволяет описать практически любое проективное преобразование с помощью матриц размера 4x4.
Перспективные проекции
Будем считать, что камера находится в начале координат и ее ось направлена вдоль отрицательной полуоси z. На рис. 5.20 показаны два варианта компоновки объектива и картинной плоскости камеры. На рис. 5.20,а картинная оси объектива. Этот вариант характерен для большинства реальных оптических приборов, в том числе и для глаза человека. На рис. 5.20,6 показан более общий случай, когда ориентация картинной плоскости не совпадает с оптической осью объектива. Мы детально рассмотрим первый вариант, поскольку он проще. Однако вывод соотношений для общего случая выполняется по той же методике, и мы предоставляем читателям сделать его самостоятельно (см. упр. 5.6).
6)6)Как было сказано в главе 2, плоскость проекции можно поместить перед центром проецирования. Если картинная плоскость перпендикулярна оси проекции, то сформируются виды, схематически представленные на рис. 5.21. Точка, имеющая в мировом пространстве координаты (х, у, г), проецируется в точку (ху„ ур, 2Р). Все проецирующие лучи сходятся в начале координат, и, поскольку картинная плоскость перпендикулярна оси л, 2Р = d. Так как камера “смотрит” в направлении отрицательной полуоси 2. картинная плоскость находится в отрицательном полупространстве л < 0 и значение с/также отрицательно.
6)
6)
Рис. 5.21. Перспективная проекция: а — вид в пространстве; б — вид сверху; в — вид сбоку
На рис. 5.21,6 показаны два подобных треугольника, для которых справедливы соотношения
Анализируя вид сбоку, получим аналогичный результат для ур:
У,
Эти уравнения нелинейны. Деление на 2 реализует перспективное сокращение — чем дальше отстоит объект от центра проецирования, тем меньше размер его изображения.
Процесс проецирования можно рассматривать как преобразование точки (х, у, л) в другую точку (хр, ур, 2р\ Это перспективное преобразование {perspective transformation)
сохраняет линейность, но не является аффинным. Кроме того, оно необратимо. Поскольку все точки, расположенные на проецирующем луче, преобразуются в одну и ту же точку, восстановить их исходное положение нельзя. Для работы с проективными преобразованиями нам придется слегка модифицировать используемый аппарат однородных координат.
При описании однородных координат мы представляли трехмерную точку (х, у, z) в виде четырехмерной (х, у, z, 1). Вместо этого будем представлять точку (х, у, z) в таком виде:
wx
wy
WZ
W
Если iv * 0, то трехмерное представление точки можно однозначно восстановить из четырехмерного, разделив три первых компонента на четвертый iv. В этой более общей форме однородных координат трехмерные точки отображаются в прямые четырехмерного пространства. Преобразования по-прежнему выражаются матрицами размера 4x4, но теперь элементы последней строки можно изменять, и, таким образом, преобразование распространяется и на четвертый компонент ж
Естественно, мы будем стараться работать с w = 1, поскольку это позволяет избежать дополнительных операций деления при восстановлении трехмерных координат. Однако, изменяя tv, можно представить в матричном виде более широкий класс преобразований, в том числе и перспективное. Рассмотрим матрицу
1 О1 ОО оО оО 0‘
1 О
1 О
О о
О о
о о
1 о
\/d О
Матрица М преобразует точку
На первый взгляд в q мало что изменилось по сравнению с р, но не нужно забывать, что при переходе в трехмерное пространство первые три компонента необходимо разделить на четвертый. Следовательно, получится результат
хуz ,
х =, уz == d.
р zldр z/dр zld
Приведенные уравнения описывают простое перспективное преобразование. В однородных координатах деление компонентов точки q на четвертый компонент w заменяет ее эквивалентной точкой:
Итак, вы убедились, что, по крайней мере, простые перспективные преобразования можно реализовать в матричной форме, определив соответствующую 4х4-матрицу. Это преобразование выполняется после преобразования вида, заданного матрицей вида. Но в конце процесса нужно выполнить перспективное деление (perspective division), которое можно также реализовать в виде одной из стадий конвейерного процесса обработки (рис. 5.22).
• Вид• ВидРис. 5.22. Конвейерная организация визуализацииРис. 5.22. Конвейерная организация визуализацииПерспективное ■ делениеПерспективное ■ деление
• Вид
• Вид
Рис. 5.22. Конвейерная организация визуализации
Рис. 5.22. Конвейерная организация визуализации
Перспективное ■ деление
Перспективное ■ деление
Ортогональная проекция
Ортогональная проекция — это частный случай параллельной проекции, при которой проецирующие лучи ортогональны картинной плоскости. Можно с определенной точностью
Рис. 5.23. Ортогональное проецированиеРис. 5.23. Ортогональное проецированиесчитать, что ортогональная проекция получается в камере, имеющей объектив с очень большим фокусным расстоянием, причем картинная плоскость камеры ортогональна оптической оси ее объектива. Однако уравнения ортогонального проецирования можно вывести и непосредственно, не прибегая к “переносу” в бесконечность центра проецирования математической модели. На рис. 5.23 показана ортогональная проекция на картинную плоскость г = 0. При проецировании точки на эту плоскость сохраняются значения ее компонентов х и у, а уравнения проецирования будут иметь вид
Рис. 5.23. Ортогональное проецирование
Рис. 5.23. Ортогональное проецирование
хР=х, ур=у, zp=0.
Запишем результат в виде матрицы преобразования однородных координат:
При выполнении ортогонального проективного преобразования отпадает необходимость в делении, хотя для обоих видов проективного преобразования можно использовать одну и ту же аппаратную реализацию конвейерной обработки.
Результаты, полученные для простых случаев, можно расширить на перспективное и параллельное проецирование общего вида, дополнив их последовательностью преобразований сдвига и поворота. Но сначала рассмотрим, как реализуется описанный математический аппарат в OpenGL.
Проективные преобразования в OpenGL
Рассматривая в разделе 5.3 процесс проецирования, мы не принимали во внимание характеристики камеры — фокусное расстояние объектива и размеры пленки (окна на картинной плоскости). На рис. 5.24 показан геометрический смысл угла зрения (angle of view) для каме
ры-обскуры, которую мы рассматривали в главе 1. На изображении появляются только те объекты, которые попали внутрь угла зрения. Если окно на картинной плоскости имеет вид прямоугольника, то на изображении появятся только те объекты, которые окажутся внутри бесконечной пирамиды с вершиной в центре проецирования. Эта пирамида ограничивает зону видимости (view volume}. Объекты, не попавшие в эту зону, отсекаются (clipped) и не включаются в отображаемую сцену. Таким образом, приведенное выше математическое описание процесса визуализации является неполным, поскольку мы не включили в него отсечение.
В большинстве графических API параметры отсе-
Рис. 5.24. Геометрический смысл углачения определяются вместе с параметрами проеци-
зрениярования. В системах компьютерной графики зона ви
Рис. 5.25. Передняя и задняя отсекающие плоскостиРис. 5.25. Передняя и задняя отсекающие плоскостидимости ограничивается не только с боков, но и вдоль оси проецирования — так называемыми передней и задней отсекакпцими плоскостями (рис. 5.25). В результате зона приобретает вид усеченной пирамиды видимости (frustum). В ней есть только один жестко зафиксированный параметр — центр проецирования, который находится в начале координат фрейма камеры. С помощью остальных шести параметров можно, в принципе, добиться любой ориентации и формы пирамиды, но такая гибкость используется в очень редких случаях. Ниже мы рассмотрим, как задаются параметры проецирования и пирамиды видимости в OpenGL. В других графических системах набор функций может отличаться, но сохраняются практически те же ограничения.
Рис. 5.25. Передняя и задняя отсекающие плоскости
Рис. 5.25. Передняя и задняя отсекающие плоскости
Перспективные преобразования в OpenGL
В составе OpenGL имеются две функции для задания перспективных проекций и одна — для задания параллельных проекций. Но, кроме того, можно формировать матрицы проективного преобразования и поэлементно или использовать преобразования поворота, сдвига и
масштабирования исходной единичной матрицы. Параметры пирамиды видимости задаются функцией, смысл аргументов которой поясняет рис. 5.26:
glFrustum(xmin, xmax, ymin, ушах, near, far)
Значения аргументов near и far, задающих положения передней и задней отсекающих плоскостей, должны быть положительными и отсчитываться от центра проецирования вдоль оси проецирования. Плоскости отсечения параллельны плоскости с = 0 фрейма камеры. Обратите внимание на то, что поскольку камера “смотрит” в сторону отрицательной полуоси г, то передняя отсекающая плоскость описывается уравнением г rmin = -/,„ где /„ — значение аргумента near, а задняя (дальняя — far) отсекающая плоскость описывается уравнением " =-max =-/л гДе //— значение аргумента far3. Поскольку матрица проецирования, определенная таким набором параметров, умножается на текущую матрицу, сначала нужно задать режим работы с этой матрицей. Типичная последовательность операций представлена ниже: glMatrixMode(GL_PROJECTION);
glLoad!dentity();
glFrustum(xmin, xmax, ymin, ушах, near, far)
Puc. 5.26. Параметры пирамиды видимости
Совсем не обязательно, чтобы параметры, задающие левую (xmin), правую (xmax). верхнюю (углах) и нижнюю (ymin) грани пирамиды были симметричны относительно оси 2. Точно также не требуется, чтобы формируемая в результате пирамида была симметричной (правильной) усеченной пирамидой. В разделе 5.8 будет показано, как в этом случае из матрицы простого перспективного проецирования, выведенной в разделе 5.3, выводится матрица проективного преобразования.
Во многих приложениях предпочтительнее задавать не линейные параметры, характеризующие положение углов усеченной пирамиды видимости, а угол или поле зрения. Однако если окно картинной плоскости является прямоугольником, а не квадратом, то нужно задавать пару углов зрения: один — в вертикальной плоскости, а другой — в горизонтальной (рис. 5.27). Угол в вертикальной плоскости fovy измеряется между плоскостями верхней и нижней граней пирамиды, а угол в горизонтальной плоскости — между правой и левой гранями. Эти параметры задаются в OpenGL функцией gluPerspective(fovy, aspect, near, far). Аргументы этой функции имеют следующий геометрический смысл:
fovy — угол зрения в вертикальной плоскости;
aspect — отношение ширины окна картинной плоскости к его высоте;
5Измерение расстояний от камеры эквивалентно переходу от правосторонней системы координат мирового фрейма, в котором задаются объекты сиены, к левосторонней системе координат фрейма камеры
near и far — расстояния от центра проецирования до передней и задней отсекающих плоскостей.
Параметры near и far задаются исходя из тех же соображений, что и при вызове функции glFrustum(). Сформированная по этим параметрам матрица заменяет текущую, поэтому перед вызовом функции следует выбрать режим работы с матрицами (вызвать glMatrixMode(GL PROJECTION)) и загрузить сначала единичную матрицу, а уже потом вызывать gluPerspective().
Рис. 5.27. Задание пирамиды видимости с помощью углов зрения
Параллельное проецирование в OpenGL
В составе OpenGL имеется только одна функция для задания параметров параллельного проецирования, которая формирует ортогональную проекцию: glOrtho(xmin, xmax, ymin, ушах, near, far)
Рис 5.28. Параметры ортогональной проекцииРис 5.28. Параметры ортогональной проекцииАргументы вызова этой функции имеют тот же геометрический смысл, что и одноименные аргументы функции glFrustum(). Зона видимости при этом превращается в параллелепипед видимости (рис. 5.28.). Как и в случае перспективной проекции, передняя отсекающая плоскость описывается уравнением z = cmin = -/,„ где /„ — значение аргумента near, а задняя отсекающая плоскость — уравнением z = cmax = -I/, где //— значение аргумента far.
Рис 5.28. Параметры ортогональной проекции
Рис 5.28. Параметры ортогональной проекции
При формировании перспективной проекции расстояния до передней и задней плоскостей отсечения должны были быть положительными, поскольку все проецирующие лучи должны были пересекаться в центре проецирования. Поэтому объекты, расположенные “за” центром проецирования, будут изображаться “вверх ногами”. Точки на плоскости z = 0 вообще не могут быть спроецированы, поскольку это приведет к делению на нуль. При параллельном проецировании все эти проблемы отпадают, а потому ограничение на знаки расстояний до плоскостей отсечения при вызове функции glOrtho() снимаются. Остается единственное ограничение —far > near.
Удаление невидимых поверхностей
Теперь можно вернуться к программе вращения куба, которую мы рассматривали в главе 4, и добавить в нее перспективную визуализацию и перемещение камеры. Но сначала выясним, как выполняется удаление невидимых поверхностей, — этот процесс мы использовали в первой версии программы без объяснения. Когда мы смотрим на объект, в данном случае куб, грани которого непрозрачны, то видим только три ближайшие к нам грани. Принимая во внимание используемую модель визуализации, это можно объяснить тем, что проецирующие лучи, исходящие из центра проецирования, блокируются этими гранями и не могут достичь точек на других гранях.
Но в системе компьютерной графики описания всех граней, т.е. все вершины модели, “проходят” через единую систему конвейерной обработки, следовательно, графическая система каким-то образом должна определить, какие же грани, в конце концов, выводить на экран. Таким образом, нужно изобрести алгоритм, который либо удалил бы из процесса обработки те грани, которые закрываются другими, — алгоритм удаления невидимых поверхностей (hidden-surface—removal algorithm), либо выделил те грани, которые видимы, — алгоритм выделения видимых поверхностей (visible-surface algorithm). Существует множество методов решения этой проблемы, некоторые из которых будут подробно рассмотрены в главе 7. В OpenGL используется один из них — алгоритм с-буфера (z-buffer algorithm). Для обращения к этому алгоритму в OpenGL требуется вызвать три функции. Ниже мы кратко опишем суть этого алгоритма, а к его подробному анализу вернемся в главе 7.
Все алгоритмы удаления невидимых поверхностей можно разделить на два больших класса. В алгоритмах объектного пространства (object-space algorithms) сначала выполняется упорядочение поверхностей объектов сцены. Объекты сортируются таким образом, что более дальние стоят в списке раньше тех, что расположены ближе к наблюдателю. Если затем вы
водить на экран поверхности в таком порядке, то в результате сформируется корректное изображение сцены. Применительно к нашему кубу процесс формирования изображения будет выглядеть так: сначала на экран выводятся изображения граней, ориентированных прочь от наблюдателя, а затем их перекрывают изображения граней, смотрящих на наблюдателя. В итоге на экране появляется корректное изображение куба4.
Алгоритмы пространства изображения (image-space algorithms) включаются в процесс проективного прес^^^зования и анализируют отношения между всеми точками объектов сцены, лежащими на одном и том же луче проецирования. Алгоритм z-буфера относится именно к этому классу и очень хорошо сочетается с конвейерной архитектурой формирования изобра
жения в большинстве графических систем.
Основная идея этого алгоритма поясняется на рис. 5.29. Проецирующий луч, исходящий из центра проецирования ЦП, пересекает две поверхности. Если в процессе растрового преобразования многоугольников мы будем отслеживать расстояние от ЦП до ближайшего многоугольника вдоль каждого луча проецирования, то сможем обновлять эту информацию по мере проективного преобразования всех многоугольников, описывающих объекты сцены. В изображение от каждого луча проецирования включается только точка многоугольника, ближайшего к ЦП.
TinTin4Если речь идет о выпуклых объектах, таких как куб, то грани, ориентированные прочь от наблюдателя, можно просто исключить из дальнейшего процесса. Частные случаи будут рассмотрены в главе 7.
Tin
Tin
Удаление невидимых поверхностей
Основное достоинство этого алгоритма заключается в том, что его сложность в худшем случае пропорциональна количеству многоугольников и что для его реализации требуется очень мало вычислительных операций в дополнение к тем, которые в любом случае требуется выполнять при формировании изображения. Для реализации алгоритма в системе должен быть организован буфер глубины (или >буфер\ в котором по мере выполнения обработки очередных многоугольников сохраняется информация о расстоянии между ЦП и точками на объекте. В некоторых графических системах такой буфер организуется в основной памяти компьютера, а в некоторых добавляется специальный модуль памяти, с которым работает аппаратно реализованный конвейер обработки.
От прикладного программиста при работе с этим алгоритмом требуется выполнить только две операции: инициализировать буфер глубины и разрешить режим удаления невидимых поверхностей. В OpenGL это выполняется следующим образом: glut!nitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glEnable(GL_DEPTH_TEST);
В этом фрагменте для инициализации используется функция из библиотеки GLUT, в которой флаг работы с буфером глубины добавляется к уже известным вам флагам работы с RGB-цветом и флагом двойной буферизации. Перед формированием нового изображения программист может очистить буфер глубины, вызвав функцию
glClear(GL_DEPTH_BUFFER_BIT);
Путешествие с камерой по сцене
В программе, рассмотренной в главе 4, куб вращался относительно начала координат. Использовалось ортогональное проецирование, и нам не нужно было думать о том, как размешается камера, — положение и ориентация, устанавливаемые системой по умолчанию, нас вполне устраивали.
В новой версии программы будет использовано перспективное проецирование, и мы позволим наблюдателю “ходить" с камерой по сцене, управляя его движением с помощью клавиш <х>, <Х>, <у>, <Y>, <z> и <Z>. Для задания положения и ориентации камеры воспользуемся функцией gluLookAt().
Для того чтобы разработанная раньше программа соответствовала новой спецификации, в нее придется внести минимум изменений. Определим массив viewer[3], в котором будем хранить информацию о положении камеры. Содержимое этого массива будет изменяться программой обработки событий клавиатуры keys():
void keys(unsigned char key, int x, int y) {
if(key == 'x') viewer!0]-= 1.0;
if(key == 'X') viewer[0]+= 1.0;
if(key == 'y') viewer!l]-= 1.0;
if(key == 'Y') viewer!l]+= 1.0;
if(key == 'z') viewer[2]-= 1.0;
if(key == ' Z') viewer!2]+= 1.0;
display!);
}
В функции отображения display() вызывается функция LookAt(), которая использует массив viewer для задания параметров камеры, а в качестве точки визирования используется начало координат. Тот фрагмент программы, который организует вращение куба, остается в новой версии без
изменений. Управление вращением по-прежнему реализуется с помощью мыши. Обратите внимание на очередность вызовов функций, изменяющих матрицу вида в функции display ():
void display(void) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoad!dentity();
gluLookAt(viewer[0],viewer[l],viewer[2], 0.0, 0.0, 0.0,
0.0, 1.0, 0.0);
glRotatef(theta[0], 1.0, 0.0, 0.0);
glRotatef(theta[1], 0.0, 1.0, 0.0);
glRotatef(theta[2], 0.0, 0.0, 1.0);
colorcube();
glFlush();
glutSwapBuffers();
}
Функция glFrustum() вызывается из функции перерисовки myReshape () и задает положение и ориентацию камеры:
void myReshape(int w, int h) {
glviewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoad!dentity();
if(w<=h) glFrustum(-2.0, 2.0, -2.0 *(GLfloat)h/(GLfloat)w,
2.0*(GLfloat)h/(GLfloat)w, 2.0, 20.0);
else glFrustum(-2.0, 2.0, -2.0*(GLfloat)w/(GLfloat)h,
2.0* (GLfloat)w/(GLfloat)h, 2.0, 20.0);
glMatrixMode(GL_MODELVIEW);
}
Обращаю ваше внимание на то, что аргументы функции glFrustun() задаются с помощью соотношения сторон окна приложения w и h. Теперь остается только добавить в программу main() регистрацию функции с обратным вызовом для обработки событий клавиатуры keys(), и новая версия программы будет готова. Полный текст программы читатель найдет в приложении А. После запуска программы на выполнение вы, несомненно, отметите про себя эффект камеры, движущейся под “чутким руководством” пользователя, более близкое к фотографическому перспективное изображение куба и эффект отсечения, который вносится заданием передней и задней плоскостей отсечения. Обратите внимание на то, что как бы вы ни “ходили” по сцене, куб всегда оказывается в центре кадра.
Существует множество способов организовать движение камеры в пространстве сцены с помощью различных устройств ввода. Если в программе мышь не используется для управления объектами сцены (в нашем примере — вращением куба), то, манипулируя кнопками мыши, можно выполнять, как говорят кинематографисты, “наезд” камеры, поворачивать ее вправо или влево. Для “привязки” камеры обычно используется функция gluLookAt(), но этот эффект можно организовать и другими способами. Один из них — воспользоваться матрицами поворота и сдвига и с их помощью менять в цикле матрицу вида. Если в процессе перемещения камеры не нужно держать в поле зрения определенный объект, этот способ может
оказаться более предпочтительным. Можно также организовать в программе специальную переменную для хранения положения камеры и изменять ее по командам пользователя. В таком случае матрицу вида нужно будет вычислять в каждом цикле, начиная с единичной, а не накапливать приращения. В конце концов, способ, который выбирает программист, зависит от специфики приложения. Иногда приходится обращать внимание на эффект накопления ошибки в вычислении матрицы вида при длительном цикле работы с приращениями.
Матрицы параллельного проецирования
Те матрицы проективного преобразования, которые формируются и используются в OpenGL, не так просты, как рассмотренные в разделе 5.3. В этом и следующем разделах мы подробно рассмотрим особенности формирования матриц проективного преобразования в OpenGL. Поскольку моделирование проецирования является ключевой проблемой в трехмерной компьютерной графике, без глубокого понимания используемых при этом математических методов нельзя ни написать хорошую прикладную программу, ни разработать новую графическую систему. Более того, хотя функции работы с проективными преобразованиями, которые имеются в OpenGL, и дают вполне удовлетворительные результаты в большинстве ситуаций, возникающих в практике разработки графических приложений, некоторые виды проекций, в частности косоугольные, непосредственно в OpenGL не поддерживаются. Если в приложении нужно организовать именно такую проекцию, приходится прибегать к косвенным методам, а для этого необходимо глубокое понимание механизмов работы системы. Ниже будет показано, как это знание может быть использовано на практике.
Нормализация проецирования
Используемый в OpenGL подход базируется на методике нормализации проецирования projection normalization}, которая предусматривает сведение всех типов проекций к ортогональной, для чего выполняется предварительное искажение исходных объектов (рис. 5.30). Поскольку такое искажение может быть представлено в виде преобразования в однородных координатах, можно не выполнять фактическое искажение формы объектов, а добавить соответствующие матрицы преобразований к матрице обычного ортогонального проецирования и получить матрицу требуемого проективного преобразования, как показано схематически на рис. 5.31.
Рис 5.30. Предварительное искажение объекта:а — перспективная проекция; б — ортогональ-ная проекция искаженного объекта
Предыскажение (нормализация)Предыскажение (нормализация)Ортогональное проецированиеОртогональное проецирование
Предыскажение (нормализация)
Предыскажение (нормализация)
Ортогональное проецирование
Ортогональное проецирование
Рис. 5.31. Нормализация преобразования
Матрицы ортогонального проективного преобразования
Хотя параллельная проекция является частным случаем перспективной проекции, мы начнем с параллельной ортогональной проекции, а затем распространим методику нормализации на перспективную. Выше было показано, что проективное преобразование отображает точки трехмерного пространства на точки картинной плоскости и что такое преобразование является вырожденным (т.е. определитель матрицы преобразования равен 0). Все точки, расположенные на одном проецирующем луче, преобразуются в единственную точку на картинной плоскости.
Анализ процесса проецирования разделим на две части. Сначала с помощью невырожденного преобразования в однородных координатах преобразуем заданную зону видимости в стандартную. Будем использовать преобразование, которое выполняет эту операцию по отношению ко всем объектам, комбинируя матрицу преобразования с матрицей вида. Объекты искажаются таким образом, что после выполнения на втором этапе проективного преобразования выбранного типа (мы сейчас выбрали параллельное ортогональное проективное преобразование) по отношению к этим объектам и зоне видимости получим
Хр =х, Ур = у, =р = 0.
Обратите внимание на то, что, по сути, ортогональное проективное преобразование сводится только к приравниванию компонента : к 0 или его простому игнорированию, поскольку при отображении он нам просто не нужен. Таким образом, вся вычислительная нагрузка переносится на первую стадию процесса. Разделение процесса проецирования на две стадии в значительной мере связано с выполнением других задач в ходе конвейерной обработки. В частности, в главе 7 будет показано, что отсечение должно выполняться при работе с трехмерным представлением объектов и что использование невырожденных матриц преобразования позволяет сохранять информацию о глубине расположения объектов вдоль проецирующего луча, которая необходима для удаления невидимых поверхностей и закрашивания (глава 6). На первой стадии процесса формируется матрица проецирования (projection matrix) (в большинстве графических систем, в том числе и в OpenGL, она называется именно так). В OpenGL также выполняется разделение между координатами экрана (screen coordinates), которые являются двухмерными и не содержат информации о глубине, и координатами окна (window coordinates), которые являются трехмерными и сохраняют информацию о глубине. Матрица проецирования и последующее перспективное деление преобразуют вершины в координаты окна.
При ортогональном проецировании простейшей зоной видимости является куб, центр которого находится в начале координат, а грани образуются плоскостями х = ±1, у = ±1, z = ±1.
В OpenGL такой куб видимости устанавливается системой по умолчанию. Для его принудительной установки нужно включить в программу такую последовательность операторов: glMatrixMode(GL_PROJECTION);
glLoadldentityO;
glOrtho(-1.О, 1.0, -1.0, 1.0, -1.0, 1.0);
В дальнейшем этот куб будем называть канонической зоной видимости (canonical view volume). Два последних аргумента в вызове glOrtho() — это расстояния до передней и задней отсекающих плоскостей, измеренные от позиции камеры в направлении отрицательной полу-
оси 2. Передняя отсекающая плоскость - = 1.0 находится за камерой, задняя отсекающая плоскость 2 = -1.0 — перед камерой. Хотя проецирующие лучи параллельны и ортогональная проекция по своим свойствам аналогична использованию телеобъектива с очень большим фокусным расстоянием, задание расстояний до передней и задней отсекающих плоскостей позволяет селектировать отображаемые объекты сцены.
Предположим, что при вызове функции glOrtho() заданы произвольные значения аргументов:
glOrtho(xmin, xmax, ушах, ymin, Inear, Ifar);
Такой набор аргументов задает правильный параллелепипед видимости, правая сторона которого, если смотреть от камеры, — это плоскость х = xmax, левая сторона — плоскость х хт1П, верхняя грань— плоскость^ =^тах, а нижняя грань— плоскость у = ^min- Передняя и задняя отсекающие плоскости — это 2 = -/„ и г = -lf. Матрица проецирования, которая устанавливается при этом в OpenGL. преобразует этот параллелепипед в куб, центр которого расположен в начале координат, а ребра имеют длину 2 единицы (рис. 5.32). На этом рисунке -max— это взятое с обратным знаком значение аргумента Ifar, a rmin— взятое с обратным знаком значение аргумента Inear вызова функции glOrtho().
Полученная таким способом матрица преобразует координаты вершин, описывающих объекты, которые были заданы при вызове giVertex(), в вершины внутри канонической зоны видимости, для чего выполняются преобразования масштабирования и сдвига. В результате те вершины, которые находились внутри заданного параллелепипеда видимости, преобразуются в вершины, находящиеся внутри канонической зоны видимости, а те вершины, которые были снаружи от заданного параллелепипеда видимости, окажутся и снаружи канонической зоны видимости. Итак, мы видим, что матрица проецирования определяется заданным типом проекции и параметрами зоны видимости, переданными в качестве аргументов функции glOrtho(), причем они специфицируются относительно камеры. Положение и ориентация камеры задаются матрицей вида. Эти две матрицы — вида и проецирования — перемножаются, и координаты объектов умножаются справа на результирующую матрицу.
Рис. 5.32. Соответствие между заданным параллелепипедом видимости и канонической зоной видимости
Теперь, используя наши познания в аффинных преобразованиях, выведем уравнение матрицы проецирования, формируемой в OpenGL. Нам предстоит для этого решить две задачи. Сначала с помощью преобразования сдвига передвинем центр заданного параллелепипеда в центр канонической зоны видимости — начало координат. Затем изменим масштабные коэффициенты таким образом, чтобы каждое ребро параллелепипеда имело длину 2 единицы (рис. 5.33). Следовательно, имеем два преобразования:
Т( (^max J" -^min)'^ (Утах Утт)'^ (-max -min)^)
S(2/(.Xmax Xmjn), 2/(ymax J^min), 2/(-max -min)),
после суперпозиции которых сформируется матрица проецирования:
Рис. 5.33. Аффинные преобразования в процессе нормализацииРис. 5.33. Аффинные преобразования в процессе нормализации
Рис. 5.33. Аффинные преобразования в процессе нормализации
Рис. 5.33. Аффинные преобразования в процессе нормализации
2
0
0
X + X maxnun
х - X maxnun
'^max *^nun
0
2
0
V + У
Z лих Z nun
P = ST =
У maxУ nun
Л пых У пип
0
0
2
ПЫХ "^ — пип
0
0
— них ~ пип
0
— пых ~ пип
1
Рис. 5.34. Косоугольная проекция точкиРис. 5.34. Косоугольная проекция точки
Рис. 5.34. Косоугольная проекция точки
Рис. 5.34. Косоугольная проекция точки
Поскольку ось проецирования камеры направлена вдоль отрицательной полуоси г, проецирующие лучи направлены из бесконечности отрицательного полупространства в начало координат. В терминах расстояния до передней и задней отсекающих плоскостей — параметрах Inear и If аг вызова функции glOrtho() — матрица проецирования примет следующий вид:
2
0
0
+ Л‘тт
х - X
- *„„„
0
2
0
пип
р =
0
0
2
L
/, -1п
L
0
0
0
Косоугольная проекция
Использование функции glOrtho() позволяет создавать в OpenGL ограниченный класс параллельных проекций — только такие, в которых проецирующие лучи ортогональны картинной плоскости. Но во многих приложениях желательно иметь и косоугольные проекции . Можно, конечно же, самостоятельно сформировать соответствующую матрицу поэлементно. но попробуем воспользоваться той же идеей, которую разработчики OpenGL применили при формировании матрицы ортогонального проецирования, — найдем преобразование желаемой проекции в каноническую проекцию предварительно искаженных объектов.
Косоугольная проекция характеризуется углом между проецирующими лучами и картинной плоскостью (рис. 5.34). В графической системе, которая поддерживает параллельное проецирование в общем виде, зона видимости для косоуголь-
'Обращаю ваше внимание на то. что без косоугольного проецирования не у бал ось бы вычерчивать оси координат в рисунках этой книги (см упр. 5.15 >.
ной проекции имеет переднюю и заднюю грани, параллельные картинной плоскости, а боковые— верхнюю и нижнюю, параллельные проецирующим лучам (рис. 5.35). Анализируя вид сверху и сбоку на рис. 5.36, выведем уравнения косоугольной проекции.
Проекция характеризуется углами 0 и ф. В техническом черчении широко используются частные виды косоугольных проекций — кавальерная и кабинетная, — которые характеризуются конкретными значениями этих углов. Однако эти углы не являются единственно возможным набором параметров, характеризующих косоугольную проекцию (см. упр. 5.9 и 5.10).
Рис. 5.35. Зона видимости при косоугольном проецированииРис. 5.35. Зона видимости при косоугольном проецировании
Рис. 5.35. Зона видимости при косоугольном проецировании
Рис. 5.35. Зона видимости при косоугольном проецировании
а)б)
Рис. 5.36. Косоугольное проецирование: а — вид сверху; б — вид сбоку
Из анализа вида сверху очевидно следует, что хр находится из соотношения
tg0 =—-—.
Следовательно, хр = х - zctg0. Аналогично получим и соотношение ур = у - zctg0.
Используя уравнение картинной плоскости 2р = 0, запишем результат в виде матрицы преобразования однородных координат:
1 0 -ctg0 О О 1 -ctgф О ОООО ООО 1
Следуя принятой стратегии, представим матрицу Р в виде произведения:
11Р = МопН(0,ф) =Р = МопН(0,ф) =ООООООО О 1 О о о о оО О 1 О о о о оо о о1о о о11 о о о1 о о оо 1 о оо 1 о о-CtgO—ctgф1о-CtgO—ctgф1оо о о1о о о1
1
1
Р = МопН(0,ф) =
Р = МопН(0,ф) =
О
О
О
О
О
О
О О 1 О о о о о
О О 1 О о о о о
о о о
1
о о о
1
1 о о о
1 о о о
о 1 о о
о 1 о о
-CtgO
—ctgф1
о
-CtgO
—ctgф1
о
о о о
1
о о о
1
где Н(0, ф) является матрицей преобразования скоса. Таким образом, косоугольное проецирование можно представить как первоначальный скос объектов посредством Н(0, ф), а затем выполнение обычного ортогонального проецирования. На рис. 5.37 показано, как матрица Н(0, ф) преобразует объект (куб) внутри зоны видимости. Боковые грани зоны видимости становятся перпендикулярными картинной плоскости, а боковые грани куба—наклонными по отношению к ней, поскольку на оба объекта воздействует то же самое преобразование скоса. Следовательно, ортогональная проекция искаженного куба будет выглядеть точно так же, как и косоугольная проекция неискаженного.
Рис. 5.37. Эффект от преобразования скосаРис. 5.37. Эффект от преобразования скоса
Рис. 5.37. Эффект от преобразования скоса
Рис. 5.37. Эффект от преобразования скоса
Но мы еще не закончили, поскольку созданная после скоса зона видимости еще не является канонической. Теперь воспользуемся преобразованиями масштабирования и сдвига, выведенными в предыдущем разделе:
"2
q0 ^max + "^"nun
max ^ nun
^max "^"nun
ST =0
03 max 3 nun У max У nunУ max A nun
22+1
0
022maxmin
** max “ nun^ max ^ nun
0
00I
Матрицу этого преобразования нужно вставить между матрицами скоса и конечного ортогонального преобразования:
Р = MorlSTH.
Значения ,rmax, xmin, ^тах, _)тт— это параметры правильного параллелепипеда видимости, который получится после преобразования скоса. Эти значения должна вычислить прикладная программа, взяв за основу исходную зону видимости — косоугольную призму.
Матрицы перспективного проецирования
Принцип, положенный в основу реализации перспективного проецирования в OpenGL, тот же, что и в организации параллельного, — формируется преобразование, которое приводит к искажению объектов, такому, что после их проецирования стандартным способом создается нужное изображение. Для вывода вида такого преобразования сначала нужно решить, какую форму в случае перспективного проецирования должна иметь каноническая зона видимости. Затем следует сформировать преобразование перспективной нормализации ^perspective-normalization transformation}, которое преобразует перспективную проекцию в ортогональную. Последний этап — вывод матрицы перспективного проецирования, которая и используется в OpenGL.
Перспективная нормализация
В разделе 5.3 была выведена матрица простого перспективного преобразования. Если уравнение картинной плоскости - = — 1, а центр проецирования размещен в начале координат, то эта матрица имеет вид
1 О О1 О ОО о о
1 О О
1 О О
0 0-10
Но для формирования изображения нам нужно знать параметры зоны видимости. Предположим. что выбран угол зрения 90° и, таким образом, боковые грани зоны видимости на
клонены по отношению к картинной плоскости на 45°. Следовательно, зона видимости имеет вид пирамиды бесконечной в одном направлении, грани которой — плоскости х = ±z, у = iz (рис. 5.38).
Рис. 5.38. Простая перспективная проекция
Эту зону можно ограничить, задав переднюю и заднюю отсекающие плоскости z = cmin и
-ma\, где оба значения отрицательны, причем zmax
Рассмотрим матрицу '10 00"
0 100
N =л ,
0 0 а3
0 0-10
которая аналогична М, но не является вырожденной. Пока что не будем рассматривать, каковы должны быть значения а и 3, — главное, что они должны быть отличны от 0. Если применить преобразование N к точке в однородных координатах р = [х у z 1 ]7, то получим новую точку q [х' у* z' ir^7, где
х' = х, у* = у, z’ = az + 3, и’' =
После деления на ir' получим координаты точки в трехмерном пространстве
, X , V , f 3)
х =—, у =-—, z. = - а + — .
Если применить по отношению к N преобразование ортогонального проецирования вдоль оси z, получим в результате матрицу
"10 00"
0 100
М N =,
оп 0 0 0 0
0 0-10
которая и является матрицей простого перспективного преобразования. Проекция произвольной точкиримеет вид
р = MonNp =р = MonNp =хУохУо
р = MonNp =
р = MonNp =
х
У
о
х
У
о
После выполнения перспективного деления получим искомые значения хр и ур:
Итак, мы показали, что если применить к произвольной точке преобразование N, то ее ортогональная проекция будет иметь такой же вид, как и перспективная проекция исходной точки. Здесь есть аналогия с применением преобразования скоса на первом этапе приведения косоугольной проекции к ортогональной.
Матрица N является невырожденной и преобразует исходную зону видимости в новую. Параметры а и 3 можно выбрать таким образом, чтобы новая зона имела вид канонической. Рассмотрим уравнения ограничивающих плоскостей х = ±г. Эти плоскости преобразуются посредством х" = -V" в х" = ±1. Аналогичным образом плоскости y = ±z преобразуются в у" = ±1. Передняя отсекающая плоскость зоны видимости z = cmin преобразуется в плоскость
И, наконец, задняя отсекающая плоскость зоны видимости г = zmax преобразуется в плоскость
z = - а + -£— .
Если выбрать значения этих параметров в соответствии с соотношениями
q ^max ~ *'min ^ _ ^**гпах ^лип
^тах ^min^тах ^min
то отображением плоскости г = -min будет плоскость z" = -1, плоскости г = rmax — плоскость z" = 1, т.е. мы получим искомую каноническую зону видимости. На рис. 5.39 показано, как выглядит это преобразование и искажение куба внутри зоны видимости. Таким образом, N трансформирует усеченную пирамиду видимости в правильный параллелепипед, а ортогональное преобразование трансформирует параллелепипед в канонический куб видимости. В результате ортогональная проекция искаженного объекта будет иметь тот же вид, что и перспективная проекция исходного. Матрицу N называют .матрицей перспективной нормализации (perspective-normalization matrix). Отображение
z =-а + —
является нелинейным, но сохраняет порядок расположения точек по глубине. Так, если г, и г2 — значения глубины расположения двух точек до преобразования и г, > г2, то после преобразования выполняется соотношение г"] >г"2.
Следовательно, алгоритм удаления невидимых поверхностей, основанный на анализе глубины, будет успешно работать и после выполнения перспективной нормализации, хотя нелинейность и может привести к некоторым проблемам с точностью вычислений, так как буфер глубины имеет ограниченную разрядность (обычно — 24 или 32 бита).
Рис. 5.39. Перспективная нормализация зоны видимости
Итак, мы показали, как с помощью предварительного искажения можно свести и перспективное, и параллельное проективные преобразования к ортогональному. Такая замена позволяет включить любой тип проективного преобразования в стандартный процесс конвейерной обработки. Все детали реализации конвейера мы рассмотрим в главе 7, где будет показано, что возможность привести зону видимости любого типа к параллелепипеду также упрощает и выполнение отсечения, и удаление невидимых поверхностей.
Перспективное преобразование в OpenGL
При использовании функции glFrustum() зона видимости не обязательно должна иметь форму симметричной (правильной) усеченной пирамиды. Геометрический смысл аргументов вызова этой функции поясняет рис. 5.40. Матрица перспективного преобразования OpenGL формируется таким образом, что сначала заданная усеченная пирамида трансформируется в симметричную с углом на-
клона боковых граней 45° (см. рис. 5.38). Этот процесс аналогичен трансформации косоугольной призмы видимости в ортогональный параллелепипед. Первый этап трансформации — преобразование скоса, превращающее асимметричную пирамиду в симметричную. Угол скоса должен быть таким, чтобы точка ((xmin + хтах)<2, (у^ + УттУЗ. -min) заняла положение (0, 0, х^). Таким образом, матрица скоса определяется в соответствии с соотношением
Н(0,ф) = Н arCCtg '^t^x iarcctg ^min + Уп»х
Рис. 5.40. Перспективное преобразование в OpenGLРис. 5.40. Перспективное преобразование в OpenGL У max У min2^ У max У min2^Полученная в результате усеченная пирамида видимости имеет следующие плоскости граней:
Рис. 5.40. Перспективное преобразование в OpenGL
Рис. 5.40. Перспективное преобразование в OpenGL
У max У min
2^
У max У min
2^
2 = 2
Следующий этап — масштабирование боковых граней пирамиды таким образом, чтобы выполнялись соотношения
X = ±2, у = ±2.
При этом положение передней и задней отсекающих плоскостей не изменяется. Такое преобразование выполняется матрицей масштабирования S(2rma/(xmax-xmin), 2zma/(ymax-y™^^ Обратите внимание на то, что матрица масштабирования не зависит от положения задней отсекающей плоскости 2 = zmin, поскольку в трехмерном пространстве аффинное преобразование однозначно определяется соответствием положения четырех точек. В данном случае таковыми являются четыре вершины, в которых боковые грани пирамиды пересекаются с передней отсекающей плоскостью.
Для того чтобы после выполнения нормализации проецирования задняя плоскость отсечения стала плоскостью z = -l, а передняя— плоскостью 2= 1, матрица нормализации N должна иметь вид
10 0 0
0 10 0
0 0 а р
0 0 -1 0
причем значения а и Р выбираются в соответствии с результатами, полученными в разделе 5.8.1. С учетом всего сказанного, получим вид матрицы проективного преобразования:
2zmn
0
X 4- X ^max Лтт
0
■^пых ^min
X — X maxnun
0
nun
У max .У nun
0
Р = NSH =
У maxУ min
У maxУ mm
0
0
z + z maxmin
2^ max
0
0
-1
0
В терминах расстояний по передней (/„) и задней (/у) отсекающих плоскостей получим матрицу Р в виде
2 сх + х
nun0ПЫХпип0
Р = NSH =
’^пих '^‘пип*^ma\ *^пип
0^'"пип У max ~^~ A mm0
Т maxnun A max У пип
00—^—-—
iz-i„lf~ln
00-10
Рис. 5.41. Тень от отдельного многоугольникаРис. 5.41. Тень от отдельного многоугольникаПроецирование и формирование теней
Рис. 5.41. Тень от отдельного многоугольника
Рис. 5.41. Тень от отдельного многоугольника
Одна из интересных задач, при решении которой используются матрицы проективного преобразования, — формирование теней в изображении. Хотя в структурном смысле тени и не являются геометрическими объектами, без них невозможно представить себе, как передать в плоском изображении трехмерную форму объектов и отношения между ними в пространстве сцены. Появляются тени только в том случае, если имеется хотя бы один источник света. Некоторая точка на объекте оказывается в тени, если на нее не попадает свет ни от одного из имеющихся источников света. Но если источник находится в центре проецирования, то мы не увидим теней, поскольку все точки, находящиеся в тени, закрыты видимыми поверхностями объектов. Такая модель освещения называется моделью “плоской засветки’’ (“flashlight in the eye” model), и именно ее мы фактически использовали до сих пор, не акцентируя на этом ваше внимание.
Для того чтобы сформировать в изображении тени (или, как сказал бы художник, наложить тени), нужно представлять себе физику процесса взаимодействия света и материалов реальных объектов. Об этом мы подробно поговорим в главе 6. А в этом разделе только покажем, насколько сложна подобная задача из-за объема вычислений, которые при отображении динамических объектов должны выполняться в реальном масштабе времени.
Но как бы там ни было, без формирования теней нельзя обойтись при воссоздании в компьютере изображения, близкого к реальному, а потому в тех системах, где требуется еще и высокая динамика изображения, приходится идти на различные хитрости. Рассмотрим, как образуется тень при использовании единственного точечного источника света (рис. 5.41). Для простоты предположим, что тень падает на плоскую поверхность у = 0. При этом на поверхности образуется многоугольник тени (shadow polygon), который является ничем иным, как проекцией объекта- многоугольника на заданную поверхность, причем центр проецирования располагается в той точке, где находится источник света. Таким образом, если удастся сформировать проекцию исходного объекта- многоугольника во фрейме, начало координат которо- получим вершины многоугольника тени. Далее нужно
го совпадает с источником света, того совпадает с источником света, тосформировать представление полученных вершин в мировой системе координат. Эту задачу
го совпадает с источником света, то
го совпадает с источником света, то
можно решать в прикладной программе, но гораздо рациональнее подобрать подходящую матрицу проективного преобразования и передать дальнейшие заботы о формировании многоугольника тени графической системе, в данном случае —OpenGL. Предположим, что источник света находится в точке (хь Уь "/), как показано на рис. 5.41.
Если с помощью матрицы сдвига Т(-х/, -ур -z^ изменить положение объектов на рисунке таким образом, чтобы источник света оказался в центре координат (рис. 5.42,6), то получим обычную перспективную проекцию с центром проецирования в начале координат. Матрица проективного преобразования при этом имеет вид
100 0
М =М =оо1о1"У,1о1"У,о о1 о о оо о1 о о о
М =
М =
о
о
1
о
1
"У,
1
о
1
"У,
о о
1 о о о
о о
1 о о о
Последний этап — сдвинуть все обратно с помощью матрицы сдвига Т(хЛ yh z;). Суперпозиция двух сдвигов и проективного преобразования проецирует вершину (х, у, z) в точку кхр. Ур, 2р\ где
х-х.
X = X,,
Р(y-yj/y,
Ур=0.
Zp(у-у,)/у,
а)а)Рис. 5.42. Проекция многоугольника тени: а — при взгляде от источника света; б — источник сдвинут в начало координатРис. 5.42. Проекция многоугольника тени: а — при взгляде от источника света; б — источник сдвинут в начало координат
а)
а)
Рис. 5.42. Проекция многоугольника тени: а — при взгляде от источника света; б — источник сдвинут в начало координат
Рис. 5.42. Проекция многоугольника тени: а — при взгляде от источника света; б — источник сдвинут в начало координат
Для формирования такого многоугольника в программе OpenGL можно изменить матрицу вида:
GLfloat m[16); /* Матрица проективного преобразования тени */ for(i=0;i<15;i++) m[i]=0.0;
m[0]=m[5]=m[10]=1.О;
m[7] = -1.0/yl;
glColor3fv(polygon_color)
glBegin(GL_POLYGON)
/* Вычертить многоугольник как обычно. */
glEnd()
glMatrixMode(GL_MODELVIEW);
glPushMatrix() /* сохранить состояние */
glTranslatef(xl,yl,zl); /* сдвинуть */
glMultMatrixf(m);/* проекция */
glTranslate(-xl,-yl,-zl); /* перенести источник света в начало координат */
glColor3fv(shadow_color);
glBegin(GL_POLYGON);
/* Снова вычертить многоугольник. */
glEnd();
glPopMatrix(); /* восстановить состояние */
Обратите внимание на то, что, хотя выполняется проективное преобразование относительно точки положения источника света, матрица, которую мы при этом используем, является “системной” матрицей вида. Тот же самый многоугольник вычерчивается дважды: первый раз — как обычно, а второй раз — с использованием измененной матрицы вида, которая преобразует вершины многоугольника. И для исходного многоугольника, и для его тени используются одни и те же параметры визуализации. Результат вычисления многоугольника тени для изображения цветового куба показан на ил. 5 цветной вклейки, а программу формирования тени вы найдете в файле cubes.с.
В простых программах, например в программе, отображающей тень, которую отбрасывает на землю летящий самолет, описанный прием работает достаточно эффективно. Его можно использовать и при работе не с точечным, а с удаленным (параллельным) источником света (см. упр. 5.17). Но если объект отбрасывает тень на поверхность другого объекта, то такая методика не годится. В главе 9 будет описан другой, более общий метод формирования теней, который, правда, требует гораздо более сложных вычислений.
Резюме
Итак, подведем первые итоги. Закончив изучение материала этой главы, вы уже способны самостоятельно полностью разработать нетривиальную программу формирования изображений трехмерных объектов. Возможно, самое полезное, что следует сейчас предпринять, — это попробовать написать одну-две такие программы. Работая над программой и отлаживая
ее, вы приобретете определенные практические навыки работы с матрицами вида и проективного преобразования, “прочувствуете”, как они влияют на формируемое изображение.
В этой главе вы познакомились с математическим описанием стандартных проективных преобразований. Хотя большинство графических API избавляет прикладного программиста от забот, связанных с созданием собственных средств выполнения проективных преобразований, знание основ этого процесса помогает понять, как в графической системе работает конвейер обработки информации, базирующийся на операциях с 4х4-матрицами. В современных графических программах организация проективных преобразований и подготовка всех необходимых исходных данных для них возлагается на прикладную программу.
В оставшейся части этой книги мы рассмотрим три большие темы. Во-первых, мы проанализируем возможности моделирования сложных объектов из примитивов. В главе 8 будет представлена методика иерархического моделирования сложных объектов. В главе 10 будут рассмотрены криволинейные графические объекты и методы их аппроксимации плоскими объектами. Такая аппроксимация позволяет использовать для отображения криволинейных объектов уже знакомые вам конвейерные средства. В главе 11 будет описан объектно-ориентированный подход к моделированию. Этот подход позволяет моделировать реальные объекты с той степенью детализации, которая диктуется спецификой конкретного приложения, включать в модель физические законы, лежащие в основе тех или иных моделируемых явлений. Будет показано, как в результате такого подхода удается визуализировать реальные физические процессы, которые невозможно описать, ограничиваясь только многоугольниками.
Вторая большая тема— формирование изображения, близкого к реалистическому, т.е. не уступающему по качеству тому, которое формируется современными приборами оптического наблюдения и регистрации. В главе 6 мы рассмотрим физику взаимодействия света с материалом поверхности реальных объектов; алгоритмы удаления невидимых поверхностей; модели закрашивания объектов с учетом формы поверхности, а в главе 9 сконцентрируемся на современной технологии наложения текстур, которая находит все более широкое применение при формировании сложных тонированных изображений.
Детальное знакомство с реализацией современных графических систем — третья тема, которую мы обсудим в главе 7. Будут рассмотрены все компоненты графического конвейера и алгоритмы выполнения операций на каждом из этапов. Мы представим вам также дополнительную информацию о работе напрямую с буфером кадра.
Закончив изучение материала, изложенного в главе 6, остальные главы можно читать в произвольном порядке.
Рекомендуемая литература
В книге Карлбома (Carlbom) и Пасьорека (Paciorek) [Саг78] обсуждаются сходства и различия классических и компьютерных методов визуализации. Роджерс (Rogers) и Адамс (Adams) [Rog90] приводят множество примеров матриц проективного преобразования применительно к стандартным видам, используемым в техническом черчении. В книгах Фоли [Fol90], Уатта (Watt) [Wat93], Херна (Hearn) и Бейкера (Baker) [Неа94] выведены канонические проективные преобразования. Все эти книги ориентированы на пользователей графической системы PH1GS, API которой несколько отличается от API системы OpenGL. Но следует отметить, что в книге Фоли рассматривается наиболее общий случай. В разных книгах выведенные соотношения не совпадают, поскольку одни авторы используют для представления точки центра проецирования матрицу-строку, а другие — матрицу-столбец. Кроме того, по- разному определено положительное направление оси z. Подробную информацию о матрицах вида и проективного преобразования в OpenGL читатель сможет найти в руководстве программиста OpenGL Programmer's Guide [Оре97,а].
Упражнения
Не все проекции относятся к классу плоских геометрических. Приведите примеры проекций, где поверхность, на которую выполняется проецирование, является криволинейной. Придумайте примеры, в которых линии проецирования не являются прямыми.
Представьте себе летательный аппарат, состояние которого в пространстве задается углами крена, тангажа и рыскания и расстоянием от некоторого другого объекта. Какова будет матрица вида, выраженная этими параметрами.
Пусть имеется спутник, вращающийся вокруг Земли. Его положение относительно Земли задается в полярных координатах. Какова будет матрица вида для наблюдателя, который, сидя в движущемся спутнике, пытается все время держать Землю в поле зрения.
Покажите, как вычислить с помощью операции векторного произведения направления и и г по заданным вектору нормали к картинной плоскости, точке привязки вида и вектору вертикали вида.
Можно ли сформировать изометрический вид куба, используя только один поворот вокруг некоторой оси? Поясните свой ответ.
Выведите матрицу перспективного проективного преобразования для случая, когда центр проецирования является произвольной точкой в пространстве, а картинная плоскость имеет произвольную ориентацию.
Докажите, что перспективное проективное преобразование сохраняет прямолинейность линий.
Любая попытка проецировать точку, которая принадлежит плоскости, параллельной картинной и проходящей через центр проецирования, приведет к делению на нуль. Какой вид будет иметь проекция прямолинейного отрезка, одна из конечных точек которого лежит на такой плоскости?
Разработайте один или несколько вариантов API для задания косоугольной проекции. Выполнение этого упражнения не потребует от вас разработки соответствующих функций — нужно только определить набор параметров, передаваемых этим функциям.
Выведите матрицу косоугольного проективного преобразования, для которой исходными являются параметры передней и задней отсекающих плоскостей, правой верхней и левой нижней точек пересечения боковых граней зоны видимости с передней отсекающей плоскостью.
Подход, основанный на выполнении нормализации для любого типа проецирования, предполагает предварительное искажение всех объектов отображаемой сцены и выполнение для любого типа проекции на втором этапе только ортогонального проецирования. Какие проблемы, по-вашему, могут возникнуть при использовании этого подхода для создания графической системы?
Как изменится матрица проективного преобразования OpenGL, если центр проецирования не совпадает с началом координат? Положите, что центр проецирования находится в точке (0, 0, сГ), а картинная плоскость описана уравнением д = 0.
Один из классов трехмерных объектов формируется вытягиванием в направлении третьей оси соответствующих двухмерных объектов. Например, в результате такой операции из окружности формируется цилиндр, а из плоского четырехугольника — параллелепипед. С помощью этой методики преобразуйте программу формирова-
ния двухмерного лабиринта (см. упр. 2.7) таким образом, чтобы с ее помощью можно было сформировать трехмерный лабиринт.
Расширьте возможности программы, разработанной при выполнении предыдущего упражнения, таким образом, чтобы пользователь мог “пройтись” по сформированному трехмерному лабиринту. Щелчок средней кнопкой мыши должен задавать движение вперед, а щелчки правой и левой кнопками — поворачивать направление движения на 90° вправо или влево.
Если для вычерчивания осей использовать ортогональное проецирование, то оси х иу будут лежать в плоскости чертежа, а ось z преобразуется в точку. Можно так организовать ортогональное проецирование, что проекции осей х и у будут по- прежнему пересекаться под углом 90°, а проекция оси z будет проходить под углом -135° по отношению к оси х. Отыщите соответствующую матрицу проективного преобразования.
Напишите программу отображения вращающегося куба внутри ящика с тремя источниками света. Тень от куба, создаваемая каждым источником, должна быть видна на одной из видимых граней этого ящика.
Отыщите тень от точки на плоскость ах + by + cz + d = 0. Тень должна создаваться в результате освещения удаленным источником света, лучи которого направлены вдоль вектора <dx, d^ d:\
ГЛАВА
6
Закрашивание
И
зучив материал предыдущих глав, вы уже можете формировать графические модели трехмерных объектов и выводить на экран их изображения. Но вряд ли вас устроит качество этого изображения — все объекты на нем кажутся плоскими, картинка не передает пространственного характера смоделированных объектов. Такое качество изображения объясняется тем, что при его формировании мы заливали каждую поверхность равномерно одним и тем же цветом. В результате ортогональная проекция сферы выглядит на экране как равномерно закрашенный круг, а куб — как плоский шестиугольник. Фотография той же сферы выглядит совсем по-другому — на ней внутренняя область окружности, в которую проектировалась сфера, окрашена неравномерно, и в результате явно чувствуется выпуклая поверхность криволинейного геометрического тела. О таком изображении говорят, что цвет в нем имеет множество градаций или оттенков {shades). Именно таким способом передается объемность, пространственность объекта на плоском изображении.
До сих пор в книге не затрагивался вопрос о том, как отражается реально существующее в природе взаимодействие света и материала на моделях, сформированных компьютером. В этой главе мы попытаемся восполнить этот пробел в ваших познациях. Будут рассмотрены по отдельности модели разных источников света и модели наиболее распространенных физических явлений, возникающих при взаимодействии света и материальной среды. Нашей целью является показать, как в уже описанную выше конвейерную архитектуру графической системы включить еще одну очень важную функцию — закрашивание отображаемой сцены с учетом характеристик источников света и материала моделируемых объектов. Поэтому мы сосредоточим внимание только на локальных моделях распределения света. Такие модели, в противоположность глобальным моделям освещения, позволяют вычислить оттенок отдельной точки на некоторой поверхности, независимо от других поверхностей как этого, так и других объектов сцены. В процессе вычислений учитываются только характеристики материала, ассоциированного с данной поверхностью, локальная геометрия этой поверхности, расположение и свойства источников света.
В предыдущей главе говорилось о том, как определить форму тени, отбрасываемой многоугольником. Теперь мы разовьем эту идею и выясним, как в общем случае реализовать наложение теней на изображение, созданное на основе полигональной модели. В качестве тестового примера рассмотрим программу рекурсивной аппроксимации сферы. Затем перейдем
к практическим вопросам реализации средств закрашивания в OpenGL— спецификации источников света и свойств материалов — и включим их в созданную программу.
В заключение мы кратко остановимся на двух наиболее интересных методах реализации глобальной модели распределения света — методе трассировки лучей {ray tracing и методе излучательности {radiosityY
Свет и материя
Познакомив вас в главах 1 и 2 с основами физиологии органов зрения человека, мы отложили на будущее обсуждение темы взаимодействия света и материи — поверхностей материальных объектов. Для того чтобы понять, как программно реализовать закрашивание объектов сцены, нужно разобраться в физике реального процесса распространения световой энер
гии в материальной среде.
С точки зрения физики поверхность материального тела может либо излучать световую энергию, например поверхность электрической лампочки, либо отражать свет, падающий на нее от внешнего источника. Некоторые тела одновременно и отражают свет, и излучают его вследствие внутренних физических процессов, происходящих в материале. Когда мы смотрим на некоторую точку материального объекта, то ее цвет определяется множеством элементарных актов взаимодействия со светом, падающим на объект как непосредственно от ис
точников света, так и от других отражающих поверхностей. Последовательность этих элементарных актов можно представить в виде рекурсивного процесса. Рассмотрим простую
сцену, представленную на рис. 6.1. Часть света от источника освещения, которая попадает на поверхность объекта А, отражается. Часть этого отраженного света попадает на поверхность объекта Б, причем часть этого отраженного света отражается и попадает вновь на объект А. Далее процесс повторяется. Такое рекурсивное отражение света от двух поверхностей приводит к определенным цветовым эффектам, в частности появлению на поверхностях дополнительных окрашенных бликов. Математически этот рекурсивный процесс описывается инте
гральными уравнениями, которые называются глобальными уравнениями заполнения {rendering equationY В принципе, их можно было бы использовать для определения распределения цвета по всем поверхностям объектов сиены, но, к сожалению, такие интегральные
уравнения в общем случае не поддаются решению даже с использованием численных методов. Существует множество подходов, основанных на разных вариантах их аппроксимации
Рис. 6.1. Отражающие поверхностиРис. 6.1. Отражающие поверхности(например, метод трассировки лучей или метод излучательности), каждый из которых позволяет найти решение уравнений для определенного типа объектов. Но и здесь имеется одно существенное препятствие — и метод трассировки лучей, и метод излучательности требуют большого объема вычислений, которые на существующей технологической базе не удается выполнить в реальном масштабе времени для сцен даже средней сложности. Поэтому основное внимание мы уделим более простым локальным моделям заполнения, основанным на модели отражения Фонга (Phong), которая на сегодняшний день представляет собой вполне приемлемый компромисс между физической корректностью и объемом необходимых вычислений.
Рис. 6.1. Отражающие поверхности
Рис. 6.1. Отражающие поверхности
Вместо того чтобы рассматривать уравнения общего баланса световой энергии в сцене,
эта модель анализирует световые лучи, испускаемые светоизлучающими поверхностями —
источниками света {light sources'), — и их взаимодействие с отражающими поверхностями объектов сцены. В чем-то такой подход сходен с трассировкой лучей, но в отличие от последнего метода он анализирует только один акт отражения от отдельной поверхности, а не рекурсивный процесс. Предлагается разделить проблему на две независимые части. Во- первых, в модель сцены нужно включить описания источников света. Во-вторых, сформировать модель процесса отражения, которая будет адекватно передавать взаимодействие материала поверхностей объектов сцены и света.
Для того чтобы представить себе этот процесс, попробуем проследить, как распространяются лучи от точечного источника света к сцене, представленной на рис. 6.2. Как отмечалось в главе 1, наблюдатель видит только те лучи, которые отразились от поверхностей объектов и достигли его глаз, “совершив”, возможно, довольно сложное “путешествие” в пространстве сцены и “зацепив” по пути не один объект. Если луч попал в глаз непосредственно от источника, то наблюдатель будет воспринимать цвет света, испускаемого этим источником. Если луч по пути отразился от поверхности какого-либо объекта, то наблюдатель видит цвет, зависящий от характера взаимодействия света, падающего на поверхность, и материала поверхности.
В компьютерной графической модели в роли глаза наблюдателя выступает картинная плоскость (рис. 6.3) и изображение формируют только те лучи, которые достигли центра проецирования, пройдя внутри зоны, ограниченной рамкой отсечения картинной плоскости. Обратите внимание на то, что большинство лучей, испускаемых источником, не попадает на картинную плоскость, а потому не представляет для нас никакого интереса. Мы воспользуемся этим выводом позже, в разделе 6.10. Будем считать, что картинная плоскость разделена на прямоугольники, каждому из которых соответствует отдельный пиксель экрана, причем цвет пикселя зависит от цвета луча, попавшего на этот пиксель.
Рис. 6.2. Свет и поверхностиРис. 6.2. Свет и поверхностиЦПЦПРис. 6.3. Световые лучи, поверхности объектов и изображение на экране компьютераРис. 6.3. Световые лучи, поверхности объектов и изображение на экране компьютера
Рис. 6.2. Свет и поверхности
Рис. 6.2. Свет и поверхности
ЦП
ЦП
Рис. 6.3. Световые лучи, поверхности объектов и изображение на экране компьютера
Рис. 6.3. Световые лучи, поверхности объектов и изображение на экране компьютера
На рис. 6.2 показаны варианты и однократного, и многократного взаимодействия луча с объектами. Именно характер этого взаимодействия и определяет, что же увидит наблюдатель— красный объект или коричневый, темный или светлый, матовый или блестящий.
Когда луч попадает на поверхность, часть его энергии поглощается, а часть — отражается. Если поверхность непрозрачна, то вся энергия луча распределяется между процессами отражения и поглощения. Если же поверхность прозрачна, то часть энергии луча проходит через эту поверхность и может взаимодействовать с другими объектами. Характер взаимодействия зависит от длины световой волны. Объект, облученный белым светом, кажется наблюдателю красным, потому что большая часть энергии падающего света поглощена материалом объекта, а та, что отразилась, имеет максимум интенсивности в красной части спектра. Объект кажется наблюдателю блестящим, если его поверхность гладкая. И наоборот, объект, поверхность которого шероховата, кажется наблюдателю матовым. Распределение теней на поверхности объекта зависит также от ориентации отдельных участков его поверхности, т.е. от направления вектора нормали в каждой точке этой поверхности. Можно выделить три основных типа характера взаимодействия света и материала поверхности (рис. 6.4).
Зеркальное отражение. Поверхности, таким образом взаимодействующие с падающим на них светом, выглядят блестящими, поскольку большая часть световой энергии отражается или рассеивается в узком диапазоне углов, близких к углу отражения. Зеркало — это идеально отражающая поверхность. Хотя небольшая часть энергии падающего луча и поглощается, остальной свет отражается под одним углом, причем этот угол равен углу падения луча.
Диффузное отражение. При диффузном отражении падающий свет рассеивается в разных направлениях. Такой тип взаимодействия характерен для поверхности равномерно окрашенной стены или поверхности Земли, как она представляется пилоту самолета или космического летательного аппарата.
Преломление. В этом случае луч света, падающий на поверхность, преломляется и проникает в среду объекта под другим углом. Этот процесс рефракции характерен для стекла и воды. Как правило, при этом отражается часть падающего света.
а)6)в)
Рис. 6.4. Взаимодействие света и материала: а — зеркальное отражение; б — диффузное отражение; в — преломление
В разделе 6.3 будет показано, как моделируются все эти виды взаимодействия и соответствующие поверхности в системах компьютерной графики. Но сначала мы займемся характеристиками источников света.
Источники света
Свет может исходить от поверхности объекта в двух случаях: либо объект является излучающим, либо объект отражает свет, падающий на него извне. Как правило, источником света мы считаем излучающие объекты, хотя, строго говоря, это и не так. Например, в ночных сценах источником может быть Луна, которая сама свет не излучает. Кроме того, некоторые объекты, которые излучают свет, одновременно и отражают свет, падающий на них от других
источников, например колба электрической лампочки. Но в нашей упрошенной модели, не претендующей на абсолютную адекватность реальному физическому миру, мы не будем учитывать эти нюансы. Когда мы будем рассматривать средства моделирования света в OpenGL (раздел 6.7), вы увидите, насколько просто в этой графической системе сформировать источник света.
Рассматривая источник света, такой, как показан на рис. 6.5, мы не должны забывать о том, что этот объект имеет определенную поверхность. Каждая точка этой поверхности (х,у, z) может испускать световой луч, который характеризуется направлением эмиссии (0, ф) и распределением световой энергии по длинам волн Л. Таким образом, элементарный источник света в общем случае характеризуется функцией излучения (illumination function), 1(х, у, z, 0, ф, Л), зависящей от шести параметров. Обратите внимание на то, что направление излучения описывается двумя углами и что физический источник полихромного излучения представляется как множество независимых элементарных источников монохроматического света. Рассматривая освещаемую таким источником поверхность, можно получить освещенность каждой ее точки, интегрируя функцию излучения по поверхности источника света (рис. 6.6). При интегрировании нужно учитывать, во-первых, только те углы излучения, которые обеспечивают попадание лучей на анализируемую точку освещаемой поверхности, а во- вторых, расстояние между элементарным источником и этой точкой. Для распределенного источника света, каковым является электрическая лампа, вычислить такой интеграл довольно сложно как аналитически, так и численно. Для упрощения подобный источник часто моделируется многоугольниками, каждый из которых представляет собой элементарный источник, или аппроксимацией реального источника множеством точечных.
Рис. 6.5. Источник светаРис. 6.5. Источник светаМы рассмотрим четыре основных типа источников света: фонового освещения (ambient lighting), точечные источники (point sources), прожекторы (spotlights) и удаченные источники света (distant light). Этих четырех типов вполне достаточно для моделирования освещения в большинстве простых сцен.
Рис. 6.5. Источник света
Рис. 6.5. Источник света
Рис. 6.6. Суммирование света от элементарных источников
Цвет излучения
Создать адекватную модель источника довольно сложно, поскольку в ней нужно учитывать, что от длины волны зависит не только интенсивность, но и направление излучения. Однако мы с самого начала остановились на трехцветной модели зрения человека, которая предполагает, что для получения всей цветовой гаммы достаточно учитывать при анализе три основные (первичные) цветовые составляющие — красного, зеленого и синего цветов.
Поэтому в дальнейшем мы будем рассматривать любой источник как состоящий из трех независимых источников первичных цветов и соответственно описывать его трехкомпонентной функцией излучения:
1 =
Следовательно, для вычисления распределения красного цвета в изображении будет использоваться “красный” компонент функции излучения 1Г. Поскольку вычисления для всех трех цветов выполняются по единой схеме, мы в дальнейшем для краткости будем рассматривать только по одному скалярному уравнению каждого типа и будем считать, что его можно применять к любому из первичных цветов.
Фоновое освещение
В некоторых помещениях, например учебных аудиториях или в кухне, система освещения организуется таким образом, чтобы обеспечить равномерный свет по всему пространству. Чаще всего для этого используются источники большого размера с рассеивателями. Смоделировать такую систему освещения можно, по крайней мере, в принципе, сформировав большое множество маленьких источников, направление излучения которых выбирается случайно, а затем проинтегрировав освещение некоторой точки от всех этих источников. Но при реализации такого подхода на практике придется выполнять массу вычислений, что не позволит графической системе формировать изображение в реальном масштабе времени. Можно поступить и по-другому — подобрать такие характеристики источника, которые обеспечили бы равномерное освещение по всему пространству сцены. Такой источник принято называть источником фонового света {ambient light). Следуя второму подходу, мы можем при моделировании просто считать, что каждая точка на поверхности объектов этой сцены освещена одинаково. Таким образом, функция освещенности каждой точки поверхности характеризуется только заданной интенсивностью 1а.
Как уже отмечалось выше, будем считать, что источник фонового света состоит из трех источников первичных цветов, а его функция интенсивности — трехкомпонентная:
А
Л
Любой из компонентов функции 1а — красный, зеленый или синий — является скаляром /а, но в дальнейшем нужно учитывать, что хотя каждая точка на поверхности любого из объектов сцены получает от источника свет одинаковой интенсивности, отражают они его по-разному.
Точечный источник света
Идеальный точечный источник света (point source) излучает свет одинаково во всех направлениях. Такой источник, размещенный в точке р0, характеризуется трехкомпонентным вектором цвета:
к (Ро)
1(Ро)= /,(Ро) /ДРо)
Освещенность некоторой точки поверхности светом от этого источника обратно пропорциональна квадрату расстояния между этой точкой и источником. Следовательно, освещенность в точке р (рис. 6.7) светом от некоторого точечного источника может быть представлена матрицей-столбцом:
1(Р'Ро) = ;—ЦтЦРо)-
|Р-Ро|
Как и при моделировании фонового света, обозначим через /(р0) любой компонент !(р0).
Использование точечных источников в большинстве приложений определяется скорее простотой работы с ними, чем желанием точно передать характеристики реальных физических осветительных приборов. Изображение сцены, сформированное с учетом только точечных источников, получается очень контрастным (как говорят фотографы, жестким), все объекты оказываются либо очень яркими, либо слишком темными. Реально каждый физический осветительный прибор имеет конечные размеры, что приводит к более плавному переходу от полностью светлых участков к полностью затененным (рис. 6.8). Эта зона перехода оказывается частично затененной — находится в полутени. В графической программе можно сымитировать снижение контраста от точечного источника, добавив источник фонового света (фотографы называют его источником заполняющего света).
Рис. 6.7. Освещение поверхности точечным источникомРис. 6.7. Освещение поверхности точечным источникомРис. 6.8. Формирование теневого перехода источником света, имеющим конечные размерыРис. 6.8. Формирование теневого перехода источником света, имеющим конечные размеры
Рис. 6.7. Освещение поверхности точечным источником
Рис. 6.7. Освещение поверхности точечным источником
Рис. 6.8. Формирование теневого перехода источником света, имеющим конечные размеры
Рис. 6.8. Формирование теневого перехода источником света, имеющим конечные размеры
Учет расстояния от конкретной точки до точечного источника света также вносит свою долю в излишнее “ужесточение” контраста. Хотя с чисто физической точки зрения образно пропорциональная зависимость между освещенностью и расстоянием до источника вполне корректна, в компьютерной графике нередко используется модифицированная зависимость (а - bd * ссЬ\ где d-— расстояние между р и р0. Константы а. b и с выбираются из условия “смягчения” светотеневого перехода. Учтите, если точечный источник находится достаточно далеко от всех объектов сцены, то можно считать, что отличием в расстоянии до разных точек сцены можно смело пренебречь и что этот источник освещает их совершенно одинаково.
Прожекторы
Источники света типа прожектор (spotlights) отличаются тем, что испускают свет направленным пучком, т.е. каждая точка излучающей поверхности посылает свет в одном и том же направлении. Проще всего смоделировать прожектор с помощью точечного источника света, ограничив для него направление, в котором распространяются световые лучи. Таким
образом, формируется конус с вершиной в точке р„ ось которого направлена вдоль вектора 1„ а угол наклона образующей к оси равен 0 (рис. 6.9). Обратите внимание на то, что при 9 = 180° такой квазипрожектор превращается в точечный источник.
Модель прожектора, более близкая к реальному физическому прибору, характеризуется функцией распределения интенсивности в конусе излучения. Естественно, что наибольшей интенсивностью обладают лучи, направленные вдоль оси конуса. Интенсивность излучения прожектора является функцией от угла ф между осью конуса излучения прожектора и вектором s, направленным на определенную точку освещаемой поверхности, если этот угол меньше 9 (рис. 6.10). Хотя функцию распределения интенсивности можно аппроксимировать по- разному, чаще всего ее задают в виде соз'ф, где показатель е определяет, насколько быстро убывает интенсивность по мере увеличения углаф (рис. 6.11). Как вы увидите в дальнейшем в этой главе, тригонометрическая функция косинус очень часто используется при математическом моделировании освещения. Если и s. и I являются векторами единичной длины (ортами), то значение косинуса можно вычислить с помощью скалярного произведения этих векторов:
созф = s • I.
Рис. 6.11. Экспоненциальное распределение интенсивностиРис. 6.11. Экспоненциальное распределение интенсивностиРис. 6.9. Прожек-торРис. 6.9. Прожек-торВычисление косинуса этим методом не требует никаких таблиц или разложения в ряд — достаточно выполнить три умножения и два сложения.
Рис. 6.11. Экспоненциальное распределение интенсивности
Рис. 6.11. Экспоненциальное распределение интенсивности
Рис. 6.9. Прожек-тор
Рис. 6.9. Прожек-тор
Рис. 6.10. Распределение ин-тенсивности в конусеизлучения прожектора
Удаленный источник света
Характерной особенностью удаленного источника света является то, что все испускаемые им лучи можно считать параллельными. Использование такого источника в сцене избавляет от необходимости рассчитывать направления лучей, освещающих разные точки отображаемой поверхности, а значит, существенно повышает скорость формирования изображения. Прекрасным примером такого источника является солнце. На рис. 6.12 показано, что в этом случае можно заменить точечный источник света параллельным пучком лучей. На практике обработка удаленного источника выполняется почти так же, как параллельное проецирование, — вместо положения источника света учитывается направление его лучей. Следовательно, если использовать математический аппарат однородных координат, точечный источник света ро можно представить четырехмерной матрицей-столбцом
Ро
Рис. 6.12. ПараллельРис. 6.12. Параллельные лучи от удаленного источника светаные лучи от удаленного источника светаа удаленный источник света — вектором направления в виде
Рис. 6.12. Параллель
Рис. 6.12. Параллель
ные лучи от удаленного источника света
ные лучи от удаленного источника света
В графической системе вычисления, связанные с обработкой удаленного источника света, выполняются значительно быстрее, чем обработка близко расположенных точечных источников и прожекторов. Естественно, что одна и та же сцена, освещенная точечным источником или удаленным, выглядит совершенно по-разному. В составе OpenGL имеются средства представления и манипулирования и близко расположенными точечными источниками, и прожекторами, и удаленными источниками.
Модель отражения Фонга
Итак, перед нами стоит довольно сложная задача: с одной стороны, нам нужна модель взаимодействия света и материала, которая бы соответствовала реальным физическим процессам, а с другой стороны, реализация этой модели в графической системе должна хорошо встраиваться в конвейерную архитектуру и не слишком перегружать компьютер. Ниже будет рассмотрена модель процесса отражения света объектами сцены, предложенная Фонгом (Phong). Практика использования этой модели во множестве графических систем доказала ее эффективность и достаточно близкое приближение к реальным физическим процессам, что позволяет формировать полутоновые изображения высокого качества при самых различных условиях освещения и свойствах материалов.
Для вычисления цвета произвольной точки р на поверхности объекта в модели используется четыре вектора (рис. 6.13). Если поверхность криволинейная (а это наиболее распространенный случай в реальном мире вещей), все четыре вектора могут изменяться при переходе от одной точки к другой. Вектор п является нормалью к поверхности в точке р; методику вычисления компонентов этого вектора мы обсудим в разделе 6.4. Вектор v направлен от точки р к наблюдателю или центру проецирования, а вектор 1 задает направление от точки р к произвольной точке источника распределенного света или в нашем случае — к точечному источнику света. И наконец, вектор г— это направление идеального отражения луча, падающего на поверхность вдоль вектора 1. Учтите, что вектор г однозначно определяется векторам п и I. Методику расчета компонентов этого вектора мы рассмотрим в разделе 6.4.
Модель Фонга адекватно передает три из четырех описанных выше типов взаимодействия света и материала — зеркальное и диффузное отражение и фоновое освещение. Предположим, что имеется множество точечных источников света. Можно считать, что для каждого из трех основных цветов свет от этого источника, влияющий на формируемое изображение, имеет три составляющие, которые назовем компонентами фонового (ambient), зеркального (specular) и диффузного (diffuse)
Рис. 6.13. Векторы, исполь-света. (Соответственно в математических выражениях компо-
зуемые в модели Фонганент фонового цвета будет иметь индекс “а”, зеркального —
индекс “г”, а диффузного — “б/”.) С физической точки зрения такое предположение выглядит несколько странным, но наша цель состоит в том, чтобы, в конце концов, сформировать в графической системе световые эффекты, близкие к реальным,
уложившись при этом в достаточно жесткие временные рамки. Поэтому для имитации эффектов, которые по самой своей природе носят глобальный характер, мы пытаемся использовать локальную модель. Итак, модель источников освещения включает компоненты фонового, диффузного и зеркального типов, и для каждой точки р отображаемой поверхности можно вычислить матрицу освещенности размером 3x3 для z-го источника света:
Ura Uri Ur.Ura Uri Ur.^iga ^/gd^iga ^/gdЛь» ^bd UbsЛь» ^bd Ubs
Ura Uri Ur.
Ura Uri Ur.
^iga ^/gd
^iga ^/gd
Ль» ^bd Ubs
Ль» ^bd Ubs
Элементы первой строки матрицы представляют интенсивности фонового света соответственно для красного, зеленого и синего цветов, поступающего от z-го источника. Элементы второй строки представляют цветовые компоненты интенсивности для диффузного света, а третьей— для зеркального. Предполагается, что в этих элементах не учитываются эффекты ослабления интенсивности освещения, связанные с расстоянием до источника.
Модель формируется в предположении, что существуют методы вычисления интенсивности света, отражаемого любой точкой поверхности, на основе значений элементов матрицы L, для этой точки. Например, зная значение диффузной составляющей Lird красного цвета, поступающего от z-го источника, и определив каким-то способом коэффициент отражения RITd для соответствующего компонента, можно вычислить значение составляющей интенсивности света, отраженного точкой р, в виде произведения R,tdLnd. Значение коэффициента отражения Rird зависит от свойств материала поверхности, ее ориентации, направления на источник света и расстояния между источником и освещаемой точкой. Для каждой точки можно вычислить свой набор коэффициентов и объединить их в матрицу:
R,= R,R,= R,Rfga ^zgdRfga ^zgdRibaRiba
R,= R,
R,= R,
Rfga ^zgd
Rfga ^zgd
Riba
Riba
Для каждой цветовой составляющей света, приходящего от определенного источника, можно по этой же схеме вычислить и интенсивность отраженного света для фонового и зеркального компонентов, а затем все их сложить. Например, для /-го источника интенсивность составляющей красного цвета, отраженной от поверхности в точке р, равна
Ur RiraUra + RirdUrd + ^zrs^zrs Ura Urd Urs*
Интегральную интенсивность отраженного света несложно найти, просуммировав составляющие от всех источников, имеющихся в сцене, и, в общем случае, глобального фонового освещения:
U = ^(^ + Urd + Ur. ) + ^ar >
где /аг обозначает красную составляющую глобального фонового освещения.
Мы упростим в дальнейшем вид выражений, опустив индекс цвета, поскольку все сформулированные соотношения в равной степени справедливы для каждого из трех основных цветов. Но вычисление коэффициентов отражения для составляющих фонового, диффузного и зеркального света будет существенно отличаться. Отбросив индексы цвета, получим приведенное выше выражение в более компактном виде:
I = /а + /d + /s = RaLa + RdLd + RSLS.
Вычисления по приведенной формуле нужно выполнить для каждого основного цвета и каждого из имеющихся в сцене источников, затем просуммировать результаты по всем источникам и прибавить к ним член, зависящий от глобального фонового освещения.
Отражение фонового света
Интенсивность падающего на поверхность фонового света La одинакова во всех точках этой поверхности. Частично энергия этого света поглощается материалом поверхности, а частично — отражается. Отражение фонового света характеризуется коэффициентом Ra = ка. Во внимание нужно принимать только положительные значения интенсивности отраженного света, т.е. О < ка < 1. Следовательно, получим
/а - ка La.
Здесь La может принимать индивидуальное значение для каждого из источников или быть единым для всех источников в сцене.
Опять напомню, что поверхность характеризуется тремя такими коэффициентами для каждого из основных цветов — kar. kag, kab, причем их значения могут отличаться. В результате изображение сферы будет выглядеть желтым, если коэффициент kah для синего цвета мал, а коэффициенты ка1 и kag для красного и зеленого цветов — велики.
Диффузное отражение
Поверхность с идеальным диффузным отражением отражает падающий на нее свет одинаково во всех направлениях. Следовательно, такая поверхность выглядит одинаково для всех наблюдателей. Однако количество отраженного света зависит от материала поверхности, поскольку часть энергии падающего светового потока поглощается. Способность поверхности к диффузному отражению характеризуется ее шероховатостью. Если рассмотреть профиль такой поверхности при сильном увеличении, то, скорее всего, вы увидите картинку, подобную приведенной на рис. 6.14. Лучи света, падающие на поверх
ность под практически одинаковыми углами, чаще всего отражаются в совершенно различных направлениях. Поверхность с идеальным диффузным отражением настолько шероховата, что не существует какого-либо предпочтительного направления отраженных лучей и они с равной вероятностью могут отразиться в любом направлении в верхнем по отношению к поверхности полупространстве. Такие поверхности иногда называют Ламбертовыми поверхностями {Lambertian surfaces) и математически характер отражения описывается законом Ламберта.
Рассмотрим плоскую шероховатую поверхность (рис. 6.15), ко
торая освещается солнечными лучами. Самой яркой поверхность х. \/
будет в полдень, а затем, ближе к вечеру, будет казаться все более ~\'\\/
темной, поскольку, в соответствии с законом Ламберта, для на-/
блюдателя яркость поверхности определяется только вертикальной ш|||е|1^ш||ж№ составляющей отраженного от нее света. Это явление можно пред
ставить себе следующим образом. Рассмотрим, как отражаются от ^^ 6.14. Шероховатая такой поверхности лучи от небольшого источника параллельного поверхность
света (рис. 6.16). По мере того как источник света будет двигаться
по воображаемому небосводу, наклоняясь все ниже и ниже, то же количество световой энергии будет распределяться по все большей площади и поверхность будет восприниматься как более темная. Возвращаясь к точечному источнику на рис. 6.15, можно сформулировать математическую модель диффузного отражения. Закон Ламберта утверждает, что
Rj~ cosQ,
где знаком ~ обозначается пропорциональность, а 0 — это угол между нормалью п к поверхности в анализируемой точке и направлением на источник света I. Если I и п являются единичными векторами1, то
cos0 = I • п.
Введем в рассмотрение коэффициент пропорциональности kd, который определяет, какая часть падающего на поверхность света отражается, и получим соотношение для составляющей диффузного отражения в общем отраженном световом потоке:
/d = £d(l • n) U.
В это соотношение можно ввести и множитель, зависящий от расстояния, который позволит учесть ослабление светового потока по мере удаления источника от анализируемой точки поверхности. Мы будем использовать множитель, имеющий в знаменателе квадратный трехчлен от расстояния d:
L =•
Рис. 6.15. Освещение диффузной поверхности: а — в полдень; б — после полудняРис. 6.15. Освещение диффузной поверхности: а — в полдень; б — после полудняa + bd + cd~
Рис. 6.15. Освещение диффузной поверхности: а — в полдень; б — после полудня
Рис. 6.15. Освещение диффузной поверхности: а — в полдень; б — после полудня
Рис. 6.16. Графическая интерпретация закона Ламберта: а — свет падает по нормали; б — свет падает под углом
Зеркальное отражение
Включив в модель только фоновое освещение и диффузное отражение, мы получим изображение, на котором уже будут видны тени и, таким образом, передана трехмерная форма объектов, но все поверхности будут выглядеть матовыми, как будто на них лежит слой пудры. Ни на одном объекте вы не увидите яркого пятна, которое в природе бывает на блестящих поверхностях. Обычно такое яркое пятно на объекте имеет цвет, отличный от того, который порождается фоновым светом или диффузным отражением. Например, на поверхности красного шара, освещенного источником белого света, вы увидите яркое белое пятно, которое появилось в результате зеркального отражения части светового потока источника от поверхности шара в сторону наблюдателя (рис. 6.17).
'Векторы направлений, подобные I и п, широко используются в операциях вычисления скалярного произведения при тонировании сцен в компьютерной графике. В программах, реализующих соответствующие алгоритмы, следует как можно раньше нормализовать эти векторы, что позволит избежать возможных ошибок.
В то время как диффузное отражение свойственно шероховатым поверхностям, зеркальное отражение появляется на гладких поверхностях. Чем более гладкой является поверхность, тем больше она напоминает по своим оптическим свойствам зеркало. На рис. 6.18 показано, почему по мере сглаживания поверхности все большая часть отраженного от нее светового потока концентрируется в определенном направлении, т.е. разброс углов отражения от различных точек поверхности все более сужается. Точное моделирование явления зеркального отражения — задача не простая, поскольку рассеивание света носит несимметричный характер и зависит от длины волны падающего света, а также меняется при изменении луча отражения.
Фонг предложил приближенную модель, в которой учет зер- очень незначительно увеличивает объем
Рис. 6.17. Пятно, появившееся в результате зеркального отраженияРис. 6.17. Пятно, появившееся в результате зеркального отражениякальною отражениякальною отраженияРис. 6.18. Зеркальная поверхностьРис. 6.18. Зеркальная поверхностьвычислений по сравнению с моделью, учитывающей только диффузное отражение. В этой модели вводится дополнительный аддитивный член, учитывающий зеркальное отражение. Таким образом, поверхность представляется шероховатой для составляющей диффузного отражения и гладкой — для составляющей зеркального отражения. Интенсивность светового потока, который достигает наблюдателя, зависит от угла ф между вектором г, характеризующим направление идеального зеркального отражения, и вектором v, направленным к наблюдателю. В модели Фонга используется уравнение.
Рис. 6.17. Пятно, появившееся в результате зеркального отражения
Рис. 6.17. Пятно, появившееся в результате зеркального отражения
кальною отражения
кальною отражения
Рис. 6.18. Зеркальная поверхность
Рис. 6.18. Зеркальная поверхность
/$ = АДсо$“ф.
Рис. 6.19. Эффект изменения коэффициента резкости бликовРис. 6.19. Эффект изменения коэффициента резкости бликовКоэффициент к% (0 < к^ < 1) определяет, какая часть светового потока отражается. Показатель степени а — это коэффициент резкости бликов (shininess coefficient). На рис. 6.19 показано, как при увеличении значения а отраженный свет концентрируется в зоне, близкой к углу идеального зеркального отражения. В пределе, при возрастании а до бесконечности, получим идеальное зеркало; значения в диапазоне от 100 до 500 соответствуют характеру отражения от большинства металлических поверхностей, а малые значения (<100) соответствуют наиболее распространенным материалам с обычными оптическими свойствами.
Рис. 6.19. Эффект изменения коэффициента резкости бликов
Рис. 6.19. Эффект изменения коэффициента резкости бликов
Располагая данными о нормализованных векторах г и п, можно использовать в модели Фонга операцию скалярного произведения и таким образом несколько сократить объем вычислений. В этом случае член, учитывающий зеркальное отражение, принимает вид
/$ = А$Л$(г-v)“.
В это выражение можно добавить множитель, учитывающий расстояние до источника света, который имеет такой же вид, как и в выражении для диффузного отражения. В результате получим выражение для .модели Фонга {Phong model), учитывающей это расстояние:
/ =Ak.LX n + k.L (г • v)" ) + X L .
a+bd + cd^ d d ' Л ; /" °
Вычисления по этой формуле выполняются для каждого источника света, причем для всех трех основных цветов раздельно.
На первый взгляд кажется нецелесообразным связывать с каждым источнике*' _юЮ интенсивность фонового света или раздельно рассматривать составляющие диффузного и зеркального отражения. Но дело в том, что мы не можем позволить себе роскошь использовать в графической системе полную интегральную модель распределения освещения, а потому вынуждены прибегать к различным искусственным приемам, пытаясь получить результат, близкий к реальному. Рассмотрим, например, сцену, в которой имеется множество объектов. Если включить в нее и источники света, то некоторые объекты будут освещены прямым светом от источников. Изображение таких объектов можно сформировать в графической системе, пользуясь составляющими зеркального и диффузного отражения. Но большая часть светового потока от источников достигнет объектов в результате многократного отражения. Этот свет можно смоделировать в графической системе о помощью составляющей фонового освещения, специфической для каждого источника, причем цветовая характеристика этого фонового освещения должна учитывать как цветовую характеристику источника, так и цветовые характеристики множества объектов сцены. В этом и состоит особенность приближенной модели, в которой мы вынуждены усреднять реальные свойства компонентов. В несколько расширенном виде такой же анализ можно провести и для составляющей диффузного отражения. Эта составляющая должна неявно учитывать тот факт, что в реальной обстановке рассеянный свет многократно отражается от разных объектов и в результате его цвет может существенно измениться. В модели это можно отобразить, варьируя коэффициенты по основным цветам в составляющих диффузного и зеркального отражения, и таким образом свести все к локальному анализу отдельных поверхностей.
На ил. 11 цветной вклейки показано изображение чайников разною цвета, сформированное OpenGL-программой на основе модели Фонга (подробно об этом— в главе 9). Обратите внимание на то, что изображения разных чайников отличаются друг от друга только свойствами материала поверхности. Как видите, модель Фонга достаточно близко передает световые эффекты на поверхностях разной фактуры — от матовых до почти зеркальных.
Мы рассматривали модель Фонга в пространстве объектов, но в графической системе тонирование изображения выполняется после того, как описания объектов будут преобразованы посредством матриц вида и проецирования. Эти преобразования могут изменить члены, включающие функцию косинуса (см. упр. 6.22). Следовательно, чтобы получить правильный результат, придется либо каким-то образом сохранить используемые пространственные соотношения после выполнения геометрических преобразований объектов, либо в процессе тонирования выполнять необходимые обратные преобразования.
Вычисление векторов
Описанная модель отражения света объектами сцены никак не связана с видом проецирования (параллельным или перспективным) и является достаточно общей в том смысле, что может применяться как к плоским, так и к криволинейным поверхностям, причем расстояние между поверхностью и наблюдателем не имеет значения. Большая часть вычислений в процессе тонирования изображения пространственной сцены приходится на определение компонентов векторов, используемых в модели Фонга, и их скалярных произведений. В процессе выполнения этих вычислений часто используются разнообразные упрощения, учитывающие специфику конкретной ситуации. Например, если анализируемая поверхность — плоский многоугольник, то для всех ее точек вектор нормали будет одним и тем же. Если источник света удален от поверхности достаточно далеко, то для всех точек этой поверхности вектор I. характеризующий направление падения лучей от этого источника, будет одним и тем же.
В этом разделе мы рассмотрим методы вычисления компонентов векторов для общего случая, а в разделе 6.5 проанализируем возможные упрощения в частных случаях, когда поверхности отображаемых объектов можно представить совокупностью плоских многоугольников. Этот частный случай представляет для нас особый интерес, поскольку в большинстве графических систем, в том числе и в OpenGL, при выполнении тонирования криволинейные поверхности аппроксимируются множеством малых плоских многоугольников.
Нормаль к поверхности
Для гладкой поверхности вектор нормали существует в каждой ее точке и определяет локальную ориентацию участка поверхности в окрестности этой точки. Метод вычисления компонентов вектора нормали зависит от принятого способа математического описания поверхности. На двух примерах — плоскости и сферы — мы продемонстрируем, как определять нормаль и в чем состоят основные сложности вычислительного характера.
Плоскость описывается уравнением
ах + by -г cz -г d = 0.
Как было показано в главе 4, уравнение плоскости можно записать и в терминах нормали п в точке ро, которая принадлежит этой плоскости:
П • (Р-Ро) = 0,
где р— любая точка (х, у, с) на рассматриваемой плоскости. Сравнивая эти два уравнения, получим выражение для вектора нормали:
а
n= bn= bили, в однородных координатах, п =
n= b
n= b
О
Плоскость может быть также однозначно задана совокупностью трех точек р0, р,, Р;, не лежащих на одной прямой (неколлинеарных). Векторы р2-ро и Р|-р0 параллельны плоскости, и для определения нормали можно использовать их векторное произведение:
П = (Р2-Р0) X (Р1-Ро).
При определении нормали таким способом нужно внимательно отнестись к порядку сомножителей в векторном произведении. Как уже говорилось в главе 4, для любой поверхности в системе компьютерной графики различают внешнюю и внутреннюю стороны. Изменение порядка сомножителей в векторном произведении приводит к инвертированию направления
вектора нормали, а следовательно, внешняя сторона поверхности становится внутренней, а внутренняя — внешней. Это может сушественно сказаться на дальнейших операциях, связанных с моделированием световых эффектов. В некоторых графических системах первые три вершины в определении многоугольника автоматически используются для формирования нормали к поверхности этого многоугольника, полагая его плоским. В OpenGL этого не происходит, но, как будет показано в разделе 6.5, возможность самостоятельно организовать в программе определение нормали позволяет прикладному программисту гибко управлять свойствами модели освещения.
При работе с криволинейными поверхностями способ вычисления компонентов вектора нормали существенно зависит от принятого способа описания такой поверхности. В главе 10 будут проанализированы три разных метода представления кривых и криволинейных поверхностей. Здесь же мы покажем несколько способов определения нормали к сферической поверхности единичного радиуса, центр которой совпадает с началом координат. Обычно такая сфера описывается уравнением в неявной форме (implicit equation)'.
fx,y, z) = х1 +/ + ? - 1 = 0,
или в векторной форме:
Лр) = р р- 1 =о.
Нормаль определяется вектором градиента (gradient vector), который обычно представляется в виде матрицы-столбца:
2х
2у2z2у2z= 2р.= 2р.
2у
2z
2у
2z
= 2р.
= 2р.
Сфера может быть описана и уравнением в параметрической форме (parametric form). При этом координаты х, у и z любой точки на сферической поверхности представляют собой независимые уравнения от двух параметров, и и у:
х = х(и, v), у =у(и, v), z = z(u, v).
Как будет показано в главе 10, в системах компьютерной графики предпочтение следует отдать параметрической форме, особенно при описании кривых и поверхностей, хотя некоторые виды поверхностей имеют несколько вариантов параметрического представления. К ним принадлежит и сфера. Один из вариантов параметрической системы уравнений сферы имеет вид
х(и, v) = cosh sinv, у(и, v) = cosh cost, z(u, v) = sinzz.
Изменяя м и v в диапазоне -л/2 < и < л/2, —тг/2 < v < л/2, получим все точки на сфере. Используя параметрическое представление поверхности, нормаль можно вычислить как характеристику касательной плоскости (tangent plane) в точке р(м, v) = [x(zz, v) у(и, v) z(u, г)]г (рис. 6.20). Получить уравнение касательной плоскости можно на базе линейных членов разложения параметрических функций х(и, v), у(и, v), z(u, v) в ряд Тейлора в окрестности др Эр
точки р. Прямые, параллельные векторам и -1- и проходящие через точку р, лежат в кади ov
XГ»др Эрд
сательной плоскости. Векторы и — определяются следующим образом:
ои Эр
dx ди dp _ ду ди ди
dz ди
Рис. 6.20. Касательная плоскость к сферической поверхностиРис. 6.20. Касательная плоскость к сферической поверхностидхdv др _ ду dv dvdz dvдхdv др _ ду dv dvdz dvВектор нормали формируется как векторное произведение:
Рис. 6.20. Касательная плоскость к сферической поверхности
Рис. 6.20. Касательная плоскость к сферической поверхности
дх
dv др _ ду dv dv
dz dv
дх
dv др _ ду dv dv
dz dv
др др п =—^х—.
du dv
Подставляя в это уравнение параметрические функции сферической поверхности, получим:
П = COSHП = COSHCOSH COSVsin иCOSH COSVsin и= (cosn)p= (cosn)pcos и sinv
П = COSH
П = COSH
COSH COSVsin и
COSH COSVsin и
= (cosn)p
= (cosn)p
Поскольку нас интересует только направление вектора нормали, то для сферы единичного радиуса получим
п = р.
В разделе 6.9 этот результат будет использован при закрашивании сферы, аппроксимированной многоугольниками.
В графической системе основным типом примитива, с которым выполняются все вычисления, являются вершины. Таким образом, и вектор нормали следует формировать на основании набора вершин, достаточно близких к той точке, нормаль в которой нас интересует. В рамках конвейерной архитектуры графических систем организовать такое вычисление довольно сложно, поскольку по “конвейеру” точки следуют последовательно одна за другой. Поэтому в большинстве графических систем программисту приходится самостоятельно организовывать вычисление векторов нормалей в прикладной программе.
Работая с системой OpenGL, можно связать нормаль с некоторой вершиной с помощью пары функций:
glNormal3f(nx, ny, nz);
glNormal3fv(pointer_to_normal);
Аргументы функции glNormal3f () — компоненты вычисленного раньше вектора нормали, а функция glNormal3fv() использует в качестве аргумента указатель на массив компонентов уже сформированного вектора.
Нормали в OpenGL являются переменными режима. Если определить нормаль перед тем, как задавать новые вершины с помощью giVertex(), то эта нормаль будет связана со всеми вершинами, сформированными далее по ходу выполнения программы. Но напомню еше раз, проблему вычисления нормали должен решать прикладной программист.
Угол отражения
Определив нормаль к поверхности в анализируемой точке и зная положение источника света, можно вычислить и направление идеально отраженного луча. В идеальном зеркале выполняется закон: угол падения равен углу отражения. Угол падения (angle of incidence) измеряется между нормалью и направлением на источник света (предполагается точечный источник, хотя для отдельного луча это и не принципиально), а угол отражения (angle of reflection) измеряется между нормалью и отраженным лучом (рис. 6.21).
Рис. 6.21. Отражение от идеального зеркалаРис. 6.21. Отражение от идеального зеркалаВ двухмерном пространстве направление отраженного луча задается этим законом однозначно, а в трехмерном пространстве для вычисления направления соответствующего вектора нужно ввести дополнительное условие — в точке р поверхности падающий и отраженный лучи, а также нормаль к поверхности должны лежать в одной плоскости, те. быть компланарными векторами. В совокупности эти два условия позволяют однозначно определить направление отраженного луча г по заданным вектору нормали п и орту падающего луча I. Поскольку нас интересует только направление отраженного луча г, то в дальнейшем будем предполагать, что все интересующие нас векторы являются ортами — векторами единичной длины, т.е. |1| = |n| = 1. На искомый вектор также накладывается условие |r| = 1 .Если 0, = 0„ то cosO, = cosOr.
Рис. 6.21. Отражение от идеального зеркала
Рис. 6.21. Отражение от идеального зеркала
Используя скалярное произведение, получим соотношение, связывающее углы падения и отражения:
cosO, - I • п = cosO^ = п г.
Условие компланарности трех векторов с точки зрения математики означает, что г можно выразить линейной комбинацией 1 и п:
г = al + Рп.
Для скалярных произведений на п правой и левой частей этого уравнения получим
п • r=al • n + Р = I • п.
Второе соотношение, связывающее углы а и Р, можно найти из условия, что вектор г должен быть ортом:
1 = г • г = а2 + 2«pi • п + р2.
Решив это уравнение, получим, что
г = 2(1 n)n -1.
Вектор половинного направления
модели Фонга скалярные произведения г • v нужно вычислять для каждой точки поверхно-модели Фонга скалярные произведения г • v нужно вычислять для каждой точки поверхно-При воспроизведении эффекта зеркального отражения в графической системе на основе
модели Фонга скалярные произведения г • v нужно вычислять для каждой точки поверхно-
модели Фонга скалярные произведения г • v нужно вычислять для каждой точки поверхно-
Рис. 6.22. Определение вектора половинного направленияРис. 6.22. Определение вектора половинного направлениястей объектов сцены. Можно несколько упростить этот процесс, включив в рассмотрение промежуточный вектор единичной длины, направленный между векторами положения наблюдателя v относительно точки и положения источника света I:
Рис. 6.22. Определение вектора половинного направления
Рис. 6.22. Определение вектора половинного направления
|l + v|
На рис. 6.22 представлены все пять векторов, которые понадобятся нам для выполнения тонирования изображения поверхно
сти. На этом чертеже угол \|/ между векторами п и h называется углом половинного направления (half-angle). Если вектор v лежит в той же плоскости, что и 1, п и г, то выполняется соотно-
шение (см. упр. 6.7) 2у = ф.
г • v произведением п • h,г • v произведением п • h,Если удастся заменить вычисление скалярного произведения
г • v произведением п • h,
г • v произведением п • h,
можно избавиться от вычисления вектора г. Однако угол у меньше угла ф, а следовательно,
если использовать тот же показатель степени е в выражении (п • h)1, который использовался в члене (г • v/, то интенсивность зеркальной составляющей уменьшится. Справиться с этой проблемой можно, используя вместо исходного значения е скорректированное значение с', такое, что (п • h)1' будет достаточно близким к (г • v)1'. Думаю, не стоит доказывать вам, насколько желательно уменьшить объем вычислений, но при этом нужно принимать во внимание все последствия такого упрощения для отображения при разном относительном положении источников света как плоских, так и криволинейных поверхностей (см. упр. 6.8).
Преломление света
Аналогичная методика используется и при тонировании сцен, в которых нужно учитывать не только отражение света от границы двух сред, но и явление пропускания света из одной среды в другую (такая задача решается в простой программе трассировки лучей, которую мы рассмотрим в разделе 6.10). Рассмотрим поверхность, через которую пропускается весь падающий на нее световой поток (рис. 6.23). Если скорость распространения света в двух средах отличается, то на границе этих сред происходит преломление падающего светового луча. Пусть т|/ и Т), — показатели преломления (indices of refraction) — параметры, характеризующие относительную скорость распространения света в средах по обе стороны от поверхности их раздела. Закон Спелля (Snell’z law) (см. упр. 6.13) утверждает, что
sin 0, _ Г|,
sin 0,Г],
Puc. 6.23. Идеальное преломление светового лучаPuc. 6.23. Идеальное преломление светового лучаНаправление преломленного луча t находится следующим образом. Значение cos0z определяется векторами п и I; если они нормализованы, то cos0z равно скалярному произведению этих векторов. По заданному значению cos0, можно найти sin0„ а далее, зная характеристики обеих сред, — и sin0z. Обозначив т] = т],/т|/, получим
Puc. 6.23. Идеальное преломление светового луча
Puc. 6.23. Идеальное преломление светового луча
Как и при вычислении компонентов отраженного луча, в случае преломления векторы t, п и 1 должны быть компланарны, следовательно,
t = an + pi.
Учитывая, что вектор t должен иметь единичную длину, получим
t = - —1 +| COS0 -—cosO, n n I nJ
Знак “минус” в этом уравнении появился вследствие того, что вектор t направлен в сторону внутреннего полупространства поверхности раздела сред. Угол, при котором подкоренное выражение для cos0, становится равным нулю, т.е. выполняется условие sin0/ = Т], называется критическим углом (critical angle). Если на поверхность раздела сред свет падает под этим углом, возникает явление полного внутреннего отражения, т.е. преломленный луч направляется вдоль поверхности раздела. При дальнейшем увеличении 0/ весь падающий световой поток будет отражаться от поверхности раздела сред.
Более общий случай того, что происходит на границе раздела двух сред, представлен на рис. 6.24: часть падающего светового потока преломляется, часть отражается как при зеркальном отражении, а остальная часть поглощается. Распределение по направлениям той части светового потока, которая прошла во вторую среду, имеет такой же характер, как и в слу-
чае зеркального отражения, но лучи концентрируются в окрестности вектора L Следовательно, модель преломления для наблюдателя, расположенного со стороны внутреннего полупространства поверхности раздела, может включать член, пропорциональный скалярному произведению t • v. При моделировании преломления можно воспользоваться описанной выше методикой использования вектора половинного направления, что позволит несколько упростить вычисления (см. упр. 6.16).
Рис 6.24. Реальная картина взаимодействия света с границей раздела двух сред
Закрашивание многоугольников
Располагая средствами вычисления векторов нормали, можно при заданном расположении источников света и наблюдателя применить рассмотренные модели ко всем точкам поверхностей объектов сцены. Но, к сожалению, использование уравнений вычисления нормали, которые мы рассмотрели в разделе 6.4 применительно к сферической поверхности, приводит к неприемлемо большим вычислительным затратам. Раньше мы неоднократно подчеркивали достоинства полигональной модели для представления объектов отображаемой сцены. Использование этой модели для выполнения тонирования изображения сцены, состоящей из множества криволинейных объектов, существенно уменьшает объем вычислений. Именно такая модель, предпола- гаюшая аппроксимацию криволинейных поверхностей множеством маленьких плоских многоугольников, и используется в большинстве графических систем, в том числе и в OpenGL.
Рассмотрим полигональную сеть, подобную пред- Рис. 6.25. Полигональная сеть ставленной на рис. 6.25. Каждый многоугольник в такой сети — плоский, и вычислить компоненты вектора нормали к нему не представляет особого труда. Ниже мы рассмотрим три метода закрашивания многоугольников: плоское, интерполяционное, или закрашивание по методу Гуро (Gouraud), и закрашивание по методу Фонга.
Плоское закрашивание
При перемещении от одной точки на поверхности к другой в общем случае могут изменяться три вектора — I, п и v. Однако, если поверхность плоская, вектор п остается постоянным для всех точек этой поверхности. Если наблюдатель расположен достаточно далеко от этой поверхности, то изменением вектора v при переходе от точки к точке на поверхности плоского многоугольника небольшого размера также можно пренебречь и считать его постоянным. И, наконец, если в сцене используется удаленный источник света, то вектор I также считается постоянным для всех точек поверхности, ограниченной закрашиваемым многоугольником. В данном случае термин “удаленный” имеет прямой смысл, т.е. считается, что источник бесконечно далеко удален от освещаемой поверхности. Для реализации алгоритма закрашивания нужно в этом случае вместо расположения источника задавать направление на
источник. Но термин “удаленный” можно рассматривать и в относительном смысле, сравнивая размеры закрашиваемого многоугольника с расстоянием до наблюдателя или до источника (рис. 6.26). Именно такая интерпретация этого термина используется в большинстве графических систем.
Рис. 6.26. Удаленные источник света и наблюдатель
Если три указанных вектора постоянны для всех точек многоугольника, то все необходимые вычисления для его закрашивания можно выполнить только один раз и применить результаты ко всем точкам этого многоугольника. Этот метод получил название плоского (flat}, или равномерного закрашивания (constant shading}. В OpenGL режим плоского закрашивания задается аргументом GL_FLAT при вызове функции glShadeModel(): glShadeModel(GL_FLAT);
В режиме плоского закрашивания OpenGL использует вектор нормали, ассоциированный с первой вершиной каждого очередного закрашиваемого многоугольника. Для примитивов типа полосы из треугольников (triangle strip} OpenGL для первого треугольника полосы использует нормаль к третьей вершине, для второго треугольника — нормаль к четвертой вершине и т.д. Аналогичная последовательность используется и при закрашивании полос из четырехугольников (quadrilateral strips}.
На изображении, сформированном алгоритмом плоского закрашивания, четко видна разница в оттенках цвета отдельных многоугольников сети, причем если источник света или наблюдатель расположены достаточно близко к объектам сцены, то это отличие порождается не только разной ориентацией многоугольников (значениями векторов нормалей п), но и разным направлением векторов I и v. Но при аппроксимации таким способом гладкой поверхности качество изображения может удовлетворить только самого непритязательного пользователя, поскольку переход между отдельными многоугольниками сети на изображении кажется очень резким (рис. 6.27). Одна из особенностей зрительной системы человека заключается в том, что она очень чувствительна к малым изменениям оттенка соседних участков изображения вследствие так называемой вторичной задержки (lateral inhibition}. Если рассматривать шкалу с дискретно изменяющейся интенсивностью участков (рис. 6.28), то каждый переход будет восприниматься таким образом, что с одной стороны перехода кажется, будто интенсивность участка понизилась по сравнению с номинальной, а с другой стороны перехода участок вначале кажется более темным, чем номинальный (рис. 6.29). Таким образом, в области светотеневого перехода глаз воспринимает полосы Маха (Mach bands}. Это явление объясняется характером связи между колбочками и оптическим нервом, и единственное, что может сделать разработчик компьютерной графической системы для устранения такого неприятного эффекта — постараться по возможности сгладить контраст между оттенками соседних участков на границе многоугольников.
Рис. 6.27. Плоская закраска полигональной сетиРис. 6.27. Плоская закраска полигональной сетиРис. 6.28. Шкала интенсивностейРис. 6.28. Шкала интенсивностейВоспринимаемая интенсивность-Воспринимаемая интенсивность-'Действительная интенсивность'Действительная интенсивность
Рис. 6.27. Плоская закраска полигональной сети
Рис. 6.27. Плоская закраска полигональной сети
Рис. 6.28. Шкала интенсивностей
Рис. 6.28. Шкала интенсивностей
Воспринимаемая интенсивность-
Воспринимаемая интенсивность-
'Действительная интенсивность
'Действительная интенсивность
Рис. 6.29. Воспринимаемая и действительная интенсивности в области перепада
Интерполяционное закрашивание и закрашивание по методу Гуро
В программе отображения вращающегося куба (см. главу 4) мы уже использовали средства интерполяции цвета, ассоциированного с отдельными вершинами многоугольника, которыми располагает система OpenGL. Если установить режим сглаживания при закрашивании, передав в качестве аргумента функции glShadeModel() значение GL_SMOOTH, то OpenGL будет интерполировать цвет вдоль отображаемого примитива, например прямой. Предположим, что в программе установлены режимы сглаживания закрашивания и учета освещения и что с каждой вершиной ассоциирован вектор нормали соответствующего многоугольника. При вычислении освещения каждой вершины определяется ее цвет, который зависит от свойств материала и векторов v и 1, вычисленных раньше для этой вершины. Обратите внимание на то, что при использовании удаленных источников света и отсутствии зеркальной составляющей алгоритм интерполяционного закрашивания сформирует одинаковый цвет для всей внутренней области многоугольника.
Возвращаясь к полигональной сети, отметим, что идея использования в вычислениях нормалей, ассоциированных с вершинами такой сети, должна вызвать у любого математика возражения, поскольку с математической точки зрения она абсолютно некорректна. Поскольку вершина является точкой пересечения, как минимум, двух по-разному ориентированных многоугольников, то в ней происходит разрыв непрерывности функции вектора нормали. Хотя такая ситуация и может серьезно осложнить математику алгоритма. Гуро (Gouraud) пришел к выводу, что нормаль в точке вершины может быть определена таким способом, который позволит сгладить закрашивание. Рассмотрим одну из вершин в середине сети, в которой пересекаются четыре многоугольника (рис. 6.30). Каждый многоугольник имеет свой вектор нормали. Метод закрашивания Гуро состоит в том, что с вершинами связываются нормали, которые получаются в результате усреднения нормалей многоугольников, пересе-
кающихся в этой вершине. Для примера, представленного на рис. 6.30, с выделенной вершиной ассоциируется нормаль, вычисленная в соответствии с соотношением
п( +п, +п, + п4|п( + п, +п3 +п4|
Метод Гуро довольно просто реализуется в программе, использующей OpenGL. От прикладного программиста требуется только правильно вычислить нормали, ассоциируемые с вер
Рис. 6.30. Определение нормали, ассоциируемой с вершиной полигональной сетиРис. 6.30. Определение нормали, ассоциируемой с вершиной полигональной сетишинами полигональной сети. В литературе часто смешиваются метод закрашивания Гуро и интерполяционный метод закрашивания. Иногда это вызывает определенные проблемы. Как определить, какие нормали следует усреднять для вычисления нормали, ассоциируемой с определенной вершиной? Если структура данных в программе линейна, т.е. все вершины перечислены в обычном линейном списке, мы не располагаем информацией о том, какие именно многоугольники пересекаются в определенной вершине. Сам собой напрашивается вывод, что требуется такая структура данных, которая отражала бы
Рис. 6.30. Определение нормали, ассоциируемой с вершиной полигональной сети
Рис. 6.30. Определение нормали, ассоциируемой с вершиной полигональной сети
Рис. 6.31. Структура данных полигональной сетиРис. 6.31. Структура данных полигональной сетисвязи между многоугольниками в сети. Просматривая такую структуру данных, можно определить те вершины, в которых следует усреднять векторы нормалей. Такого рода структура данных должна
Рис. 6.31. Структура данных полигональной сети
Рис. 6.31. Структура данных полигональной сети
связывать, как минимум, многоугольники, вершины и свойства материалов. Один из возможных вариантов ее организации представлен схематически на рис. 6.31. Нас в ней интересует информация о том, какие именно многоугольники пересекаются в каждой вершине.
Пример изображения со сглаженным закрашиванием представлен на ил. 6 цветной вклейки. Этот цветовой куб уже использовался в примерах, описанных в главах 2 и 3. Текст программы формирования этого куба читатель найдет в приложении А. Восьми вершинам куба присвоены разные цвета спектра — черный, белый, красный, зеленый, синий, бирюзовый, фиолетовый и желтый. В программе
установлен режим гладкого закрашивания, и OpenGL автоматически выполняет интерполяцию цвета по мере перехода от одной вершины к другой.
Закрашивание по методу Фонга
Но даже при использовании метода Гуро в ряде случаев не удается избежать появления на изображении полос Маха. Фонг предложил интерполировать не цвет точек от вершины к вершине, а направление нормалей последовательных точек на ребрах каждого многогранника. Рассмотрим отдельный многоугольник полигональной сети, представленный на рис. 6.33. С каждой вершиной можно связать нормаль, которая формируется усреднением нормалей к граням, пересекающимся в этой вершине. Далее с помощью билинейной интерполяции, которую мы уже рассматривали в главе 4, можно интерполировать нормали по всей внутренней области многоугольника. Рассмотрим рис. 6.33. Векторы нормалей в вершинах А и В используются для интерполяции вдоль ребра, связывающего эти две вершины:
n(a) = (l-a)nj + апд.
Рис. 6.32. Интерполяция нормалей вдоль Рис. 6.33. Интрополирова- ребра полигональной сетиние нормалей при закрашивании по методу ФонгаРис. 6.32. Интерполяция нормалей вдоль Рис. 6.33. Интрополирова- ребра полигональной сетиние нормалей при закрашивании по методу Фонга
Рис. 6.32. Интерполяция нормалей вдоль Рис. 6.33. Интрополирова- ребра полигональной сетиние нормалей при за
крашивании по методу Фонга
Рис. 6.32. Интерполяция нормалей вдоль Рис. 6.33. Интрополирова- ребра полигональной сетиние нормалей при за
крашивании по методу Фонга
Аналогичную процедуру можно выполнить и для других ребер многоугольника. Затем можно вычислить нормаль в любой точке внутренней области этого многоугольника, зная распределение нормалей на его ребрах:
n(a, P) = (l-p)nr + pn/J.
Получив вектор нормали в определенной точке, можно выполнить все необходимые вычисления, определяемые используемой моделью отражения. Обычно этот процесс реализуется вместе с растровым преобразованием многоугольника, в результате чего отрезок между точками С и D проецируется на участок строки развертки в буфере кадра. Мы рассмотрим эту тему подробнее в главе 7.
Метод Фонга позволяет получить более гладкое тонирование изображения, но за это приходится платить весьма изрядную цену — объем вычислений резко возрастает (см. упр. 6.23). Существует множество вариантов аппаратной реализации метода Гуро, которые позволяют получать изображение приемлемого качества, практически не увеличивая время закрашивания, чего не скажешь о методе Фонга. В результате в настоящее время метод Фонга используется только в тех системах, где не требуется формировать изображение в реальном масштабе времени.
Аппроксимация сферической поверхности рекурсивным разбиением
Для демонстрации того, как на практике реализуются различные методы закрашивания криволинейных поверхностей в графической системе, следовало бы использовать сферу. Однако объект этого типа в OpenGL непосредственно не поддерживается. В библиотеках утилит GL (GLU) и инструментальных средств GL (GLUT) имеются различные варианты аппроксимации сферы. В первой используется квадратичная аппроксимация (эту тему мы обсудим в главе 10), а во второй — полигональная.
В этом разделе будет рассмотрен упрощенный алгоритм полигональной аппроксимации, на основе которого мы разработаем программу и продемонстрируем, как параметры закрашивания связываются в программе с полигональной аппроксимацией криволинейных поверхностей. Вы познакомитесь с методикой рекурсивного разбиения, которая позволяет аппроксимировать кривые и криволинейные поверхности с любой заданной точностью.
Начнем мы с тетраэдра, хотя можно было бы начать с любого правильного многогранника, гранями которого являются треугольники3. Правильный тетраэдр имеет четыре грани, каждая из которых — равносторонний треугольник, и определяется четырьмя вершинами. Начнем со следующего массива вершин: (0, 0, 1), (0, 2^2/3, -1/3), (->/б/3, ->/2/3, -1/3), (V6/3, -V2/3, -1/3). Все четыре вершины лежат на сферической поверхности единичного радиуса, центр которой находится в начале координат. В упр. 6.6 вам предлагается самостоятельно разработать метод определения координат этих вершин.
Начальное приближение вычертим в режиме построения проволочного изображения. Определим в программе глобально четыре вершины, используя тип переменных point3, который мы рассматривали в главе 4:
point3 v[4]={{0.0, 0.0, 1.0}, {0.0, 0.942809, -0.333333},
{-0.816497, -0.471405, -0.333333}, {0.816497, -0.471405, -0.333333}};
Для вычерчивания треугольника воспользуемся функцией triangle(), текст которой представлен ниже:
void triangle( point3 a, point3 b, point3 c) {
glBegin(GL_LINE_LOOP);
glVertex3fv(a);
glVertex3fv(b);
glVertex3fv(c);
glEnd();
}
Отдельную функцию разработаем для вычерчивания тетраэдра:
void tetrahedron() {
triangle(v[0], v[l], v[2] );
triangle(v[3], v[2], v[l] );
triangle(v[0], v[3], v[l] );
triangle(v[0], v[2], v[3] );
}
При перечислении вершин в треугольниках мы придерживались правила правой руки, так что несложно будет преобразовать эту программу для формирования не проволочного, а закрашенного изображения. После того как в программу будут добавлены вызовы функций инициализации, она сможет вывести на экран изображение, подобное представленному на рис. 6.34. Это простой правильный многогранник, в котором только при наличии большой фантазии можно угадать будущую сферу.
Можно приблизить его к сфере, разделив каждую грань на правильные треугольники меньшего размера. Разбиение на треугольники гарантирует, что все грани нового объекта фигуры будут плоскими. Существуют, по меньшей мере, три способа такого разбиения, которые представлены на рис. 6.35. Можно выполнить разбиение биссектрисами каждого внутреннего угла, которые, поскольку треугольник равносторонний, пересекутся в одной точке. Можно вычислить центр масс треугольника и соединить его с каждой вершиной. Но оба эти метода нам не подходят, поскольку в результате получаются треугольники с разными длинами сторон, что не позволит сформировать на их основе правильный многогранник. Поэтому мы
^Правильный икосаэдр имеет 12 граней, каждая из которых является равносторонним треугольником. Такой .многогранник прекрасно подходит для аппроксимации сферы; см [Оре97,а].
б. Аппроксимация сферической поверхности рекурсивным разбиением 271
воспользуемся методом, который применялся при формировании узора Серпинского в главе 2, — разделим каждую сторону треугольника пополам и соединим средние точки, как показано на рис. 6.35, в.
Рис. 6.34. ТетраэдрРис. 6.34. Тетраэдр6)в)6)в)Рис. 6.35. Разбиение треугольника: а — биссектрисами углов; б — линиями, сходящимися в центре тяжести; в — делением сторон пополамРис. 6.35. Разбиение треугольника: а — биссектрисами углов; б — линиями, сходящимися в центре тяжести; в — делением сторон пополам
Рис. 6.34. Тетраэдр
Рис. 6.34. Тетраэдр
6)в)
6)в)
Рис. 6.35. Разбиение треугольника: а — биссектрисами углов; б — линиями, сходящимися в центре тяжести; в — делением сторон пополам
Рис. 6.35. Разбиение треугольника: а — биссектрисами углов; б — линиями, сходящимися в центре тяжести; в — делением сторон пополам
После того как каждая грань будет разделена выбранным способом, четыре новых треугольника будут лежать в плоскости прежней грани. Поэтому нам придется сдвинуть новые вершины на поверхность аппроксимируемой сферы, выполнив нормализацию их координат. Эта операция реализуется приведенной ниже программой: void normal(point3 р) {
double d=0.0;
int i;
for(i=0; i<3; i++) d+=p[i]*p[i];
d=sqrt(d);
if(d > 0.0) for(i=0; i<2; i++) p[i]/=d;
}
Таким образом, разбиение треугольника, заданного вершинами с индексами а, b и с, может быть выполнено с помощью следующей программы:
point3 vl, v2, v3;
int j;
for(j=0; j<3; j++) vl[j]=v[a][j]+v[b][j];
normal(vl);
for(j=0; j<3; j++) v2[j]=v[a][j]+v[c][j];
normal(v2);
for(j=0; j<3; j++) v3[j]=v[c][j]+v[b][j ];
normal(v3);
triangle(v[a], v2, vl);
triangle(v[c], v3, v2);
triangle(v[b], vl, v3);
triangle(vl, v2, v3);
Этот код можно использовать в подпрограмме формирования тетраэдра и с его помощью сформировать 16 треугольных граней вместо прежних четырех. Но желательно повторить подобную процедуру п раз, получая после каждого цикла все более близкое приближение многогранника к сфере. Рекурсивно вызывая подпрограмму разбиения, мы можем контролировать количество циклов, а через него — точность аппроксимации.
Первое, что нам придется для этого сделать, — модифицировать подпрограмму формирования тетраэдра и ввести в нее аргумент номера цикла п, который задает глубину рекурсии: void tetrahedron(int n) {
divide_triangle(v[O],v[l],v[2],n);
divide_triangle(v[3],v[2],v[l],n);
divide_triangle(v[O],v[3],v[l],n);
divide_triangle(v[O],v[2],v[3],n);
Функция divide_triangle() вызывает себя в процессе рекурсивного формирования многогранника до тех пор, пока переменная п остается больше нуля, а в противном случае формирует треугольник. Ниже приведен программный код этой функции:
divide_triangle(point3 a, point3 b, point3 c, int n) {
point3 vl, v2, v3;
int j;
if(n>0)
{
for(j=0; j<3; j++) vl[j]=a[j]+b[j];
normal(vl);
for(j=0; j<3; j++) v2[j]=a[j]+c[j];
normal(v2);
Рис. 6.36. Аппроксимация сферы методом последовательного разбиенияРис. 6.36. Аппроксимация сферы методом последовательного разбиенияfor(j=0; j<3; j++) v3[j]=c[j]+b[j];
Рис. 6.36. Аппроксимация сферы методом последовательного разбиения
Рис. 6.36. Аппроксимация сферы методом последовательного разбиения
normal(v3);
divide_triangle(a ,v2, vl, n-1);
divide_triangle(c ,v3, v2, n-1);
divide_triangle(b ,vl, v3, n-1);
divide_triangle(vl ,v2, v3, n-1);
}
else triangle(a, b, c);
}
яснить, какими средствами поддержки модели освещения и тонирования располагает графическая система OpenGL.яснить, какими средствами поддержки модели освещения и тонирования располагает графическая система OpenGL.На рис. 6.36 показан многоугольник, аппроксимирующий сферу, который сформирован описанной программой. Сформировав приближенное представление криволинейной поверхности объекта, можно приступать к включению в программу источников света и тонированию изображения этого объекта. Но сначала нужно вы-
яснить, какими средствами поддержки модели освещения и тонирования располагает графическая система OpenGL.
яснить, какими средствами поддержки модели освещения и тонирования располагает графическая система OpenGL.
Описание источников света в OpenGL
В системе OpenGL поддерживаются источники света четырех типов, описанных выше, причем в одной программе может использоваться до восьми источников света. Каждый источник света имеет свой набор параметров, в том числе и программный флаг включе- ния/выключения. Параметры, описывающие источник света, соответствуют параметрам модели Фонга. Для установки векторных параметров используется функция glLightfv(), которая имеет следующий формат обращения:
glLightfv(source, parameter, pointer_to_array);
Существует четыре векторных параметра, которые определяют положение или направление лучей источника и цветовой состав его составляющих, — фоновой, диффузной и зеркальной.
Для установки скалярных параметров в OpenGL служит функция glLightf ():
glLightf(source, parameter, value);
Пусть, например, требуется включить в сцену источник GL_LIGHT0, который должен находиться в точке (1.0, 2.0, 3.0). Положение источника сохраняется в программе в виде точки в однородных координатах:
GLfloat lightO_pos[]={1.0, 2.0, 3.0, 1.0};
Если четвертый компонент этой точки равен нулю, то точечный источник превращается в удаленный, для которого существенно только направление лучей:
GLfloat lightO_dir[]={1.0, 2.0, 3.0, 0.0};
Далее определяется цветовой состав фоновой, диффузной и зеркальной составляющих источника. Если в рассматриваемом примере источник имеет белую зеркальную составляющую, а фоновая и диффузная должны быть красными, то фрагмент программы, формирующий источник, выглядит следующим образом:
GLfloat diffuse0[]={1.0, 0.0, 0.0, 1.0};
GLfloat ambient0[]={1.0, 0.0, 0.0, 1.0};
GLfloat specular0[]={1.0, 1.0, 1.0, 1.0};
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glLightfv(GL_LIGHT0, GL_POSITION, light0_pos);
glLightfv(GL_LIGHT0, GL_AMBIENT, ambientO);
glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseO);
glLightfv(GL_LIGHT0, GL_SPECULAR, specular0);
Обратите внимание на то, что в программе функция glEnable() вызывается дважды: сначала для включения режима анализа освещения, а затем для включения в сцену конкретного источника света.
В сцену можно включить и глобальное фоновое освещение, которое не связано ни с каким отдельным источником. Если, например требуется слабо подсветить все объекты сцены белым цветом, в программу следует включить такой фрагмент кода:
GLfloat global_ambient[]={0.1, 0.1, 0.1, 1.0};
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, global_ambient);
В модели освещения член, учитывающий расстояние до источника, имеет вид
a+bd + cd2
и постоянную, линейную и квадратичную составляющие. Соответствующие коэффициенты для каждого источника задаются индивидуально с помощью функции установки скалярных параметров glLightf (), например:
glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, а);
Для преобразования точечного источника в прожектор нужно задать направление луча прожектора (GL_SPOT_DIRECTION), показатель функции распределения интенсивности (GL_SPOT_EXPONENT) и угол рассеяния луча (GL SPOT CUTOFF). Эти параметры устанавливаются с помощью функций glLightf () и glLightfv().
В OpenGL существуют еще два параметра, имеющих отношение к моделированию освещения, — GL_LIGHT_MODEL_LOCAL_VIEWER и GL_LIGHT_MODEL_TWO_SIDE. С помощью параметра GL LIGHT MODEL LOCAL VIEWER в графической системе устанавливается режим “близкого”
наблюдателя. Дело тут вот в чем. Если наблюдатель расположен достаточно далеко от рассматриваемой сцены, то можно считать, что вектор, задающий направление на наблюдателя, для всех объектов сцены один и тот же. Это довольно существенно сокращает объем вычислений. Если же желательно воспроизвести в изображении все эффекты, связанные с положением наблюдателя относительно отдельных объектов сцены, то следует установить режим “близкого” наблюдателя, воспользовавшись функцией glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);
Второй из упомянутых параметров— GL_LIGHT_MODEL_TWO_SIDE— имеет отношение к отображению внутренней и внешней сторон поверхностей. В главе 4 отмечалось, что мы различаем у каждого элементарного многоугольника две стороны — внутреннюю и внешнюю. Какая из сторон будет внутренней, а какая — внешней, определяется порядком задания вершин при формировании многоугольника— правилом правой руки. Для большинства случаев в изображение включается только внешняя сторона поверхности, а потому нас не интересует, как OpenGL выполняет закрашивание внутренней стороны. Например, при отображении таких выпуклых тел, как сфера или параллелепипед, наблюдатель никогда не видит внутренние грани поверхностей, независимо от того, с какой стороны он рассматривает объект (рис. 6.37). Но если удалить одну из граней куба (например, на сцене появилась открытая коробка) или срезать часть сферы, как показано на рис. 6.38, наблюдатель с определенной позиции может “заглянуть” внутрь объекта и увидеть как внешние, так и внутренние его грани. В таком случае от графической системы требуется корректно закрашивать внутренние грани всех поверхностей. Именно такой режим и задается передачей в качестве аргумента функции glLightModel() параметра GL_LIGHT_MODEL_TWO_SIDED: glLightModel(GL_LIGHT_MODEL_TWO_SIDED, GL_TRUE);
В OpenGL источники света являются объектами, во многом такими же, как многоугольники или точки. В частности, геометрические параметры источников света преобразуются матрицей вида, поэтому можно задавать их положение, используя привычные средства преобразования. На них распространяется основное правило обработки координат в OpenGL — параметры, характеризующие положение, преобразуются текущей матрицей вида в момент формирования объекта. Таким образом, формируя источник света одновременно с остальными объектами сцены, его можно “привязать” к этим объектам и затем совместно перемещать в пространстве сцены. Можно поступить и по-другому— сформировать стационарный источник света, который будет оставаться на месте, пока другие объекты перемещаются.
Рис. 6.37. Закрашивание выпуклых объектовРис. 6.37. Закрашивание выпуклых объектовРис. 6.38. Объекты с видимыми внутренними гранямиРис. 6.38. Объекты с видимыми внутренними гранями
Рис. 6.37. Закрашивание выпуклых объектов
Рис. 6.37. Закрашивание выпуклых объектов
Рис. 6.38. Объекты с видимыми внутренними гранями
Рис. 6.38. Объекты с видимыми внутренними гранями
Спецификация материалов в OpenGL
В OpenGL свойства материалов соответствуют поддерживаемым параметрам источников света и модели отражения Фонга. Программист имеет возможность связывать разные материалы с внутренней и внешней сторонами одной и той же поверхности. Все параметры, обрабатываемые в модели отражения, задаются вызовом двух функций: glMaterialfv(face, type, pointer_to_array);
glMaterialf(face, type, value);
Для определения коэффициентов отражения для фоновой, диффузной и зеркальной составляющих (£„, kj, к.) по каждому из первичных цветов в программу нужно включить определение трех массивов:
GLfloat ambient[] = {0.2, 0.2, 0.2, 1.0};
GLfloat diffuse[] = {1.0, 0.8, 0.0, 1.0};
GLfloat specular[]= {1.0, 1.0, 1.0, 1.0};
Первый массив задает небольшое значение коэффициента отражения фоновой составляющей, причем коэффициент одинаков для всех первичных цветов, что эквивалентно отражению белого света. Для диффузной составляющей набор коэффициентов по отдельным цветам задает в результате отражение желтого цвета, а для зеркальной составляющей коэффициенты по всем первичным цветам опять одинаковы. Если внутренние и внешние стороны поверхностей имеют одинаковые параметры материала, то при вызове функции glMaterialfv() ей в качестве аргумента face передается константа GL_FRONT_AND_BACK: glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, ambient); glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse);
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular);
Учтите, что если параметры для зеркальной и диффузной составляющих одинаковы, то можно задавать их одним вызовом функции glMaterialfv(), передав ей в качестве параметра type константу GL_DIFFUSE_AND_SPECULAR. При индивидуальном определении параметров материала для внешней и внутренней сторон в качестве аргумента face используются соответственно константы GL_FRONT и GL_BACK. Коэффтщиент резкости бликов — показатель степени в члене зеркального отражения модели Фонга — задается вызовом функции glMaterialfv(), которой в качестве параметра type передается константа GL_SHININESS: glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 100.0);
Свойства материала являются параметрами режима— их текущие значения ассоциируются со всеми объектами, формируемыми в программе, до тех пор, пока не будут изменены с помощью glMaterialfv() или glMaterialf ().
В системе OpenGL можно включить в сцену излучающую поверхность, которая сама по себе является источником света. Этот метод используется в тех случаях, когда необходимо, чтобы на изображении был виден источник света. OpenGL не анализирует освещение светящейся поверхности другими источниками света, а также влияние испускаемого ею света на другие объекты сцены. Со всей такой поверхностью ассоциируется постоянный цвет, который задается так же, как и другие свойства материалов. Например, для придания такой поверхности сине-зеленого (бирюзового) цвета нужно включить в программу представленный ниже фрагмент:
GLfloat emission[]={0.0, 0.3, 0.3, 1.0};
glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, emission);
Закрашивание модели сферы
Теперь можно вернуться к уже разработанной модели сферы и организовать в программе ее тонирование. Мы не будем останавливаться на стандартных процедурах инициализации OpenGL — полный текст программы читатель найдет в приложении А. Использование модели Фонга требует прежде всего позаботиться о связывании вектора нормали с каждым пло-
ским многогранником аппроксимированной поверхности. В первом варианте будем использовать самый простой алгоритм, обеспечивающий плоское закрашивание, — определим нормаль по трем вершинам многоугольника и свяжем этот вектор с первой вершиной. Следуя подходу, описанному в разделе б.б, воспользуемся для этого операцией векторного произведения, а затем нормализуем результат. Программный код функции формирования векторного произведения представлен ниже:
cross(point3 a, points b, points с, points d);
{
d[0]=(b[l]-a[l])*(c[2]-a[2])-(b[2]-a[2])*(c[l]-a[l]); d [ 1 ] = (b [ 2 ]-а [ 2 ]) * (с [ 0 ]-а [ 0 ]) - (b [ 0 ]-а [ 0 ]) * (с [ 2 ]-а [ 2 ]);
d[2]=(b[0]-a[0])*(c[l]-a[ll)-(b[l]-a[l])*(c[0]-a[0]);
normal(d);
Полагая, что источники света уже сформированы в программе и что работа с ними разрешена, внесем изменения в функцию triangle(), которые позволят сформировать изображение закрашенного треугольника:
void triangle( points a, points b, points c) {
points n;
cross(a, b, c, n);
glBegin(GL_POLYGON);
glNormalSfv(n);
glVertexSfv(a);
glVertexSfv(b);
glVertexSfv(c);
glEnd();
}
В результате программа сформирует изображение аппроксимированной сферы, представленное на рис. 6.39. Обратите внимание на то, что на контуре изображения сферы отчетливо виден излом между соседними многоугольниками аппроксимирующего многогранника.
Перейти от этой программы к варианту, использующему интерполяционное закрашивание, можно довольно просто, поскольку для сферы известно направление нормали в каждой точке р — нормаль должна быть направлена вдоль радиуса, а в нашем частном случае — от начала координат к точке р. После этого с каждой вершиной аппроксимирующего многогранника можно связать нормаль именно к поверхности сферы, a OpenGL будет в процессе закрашивания выполнять интерполяцию между вершинами каждого треугольника. Таким образом, нам понадобится скорректировать текст функции triangle () следующим образом:
void triangle( points a, points b, points c) {
points n;
int i;
glBegin(GL_POLYGON);
for(i=0;i<3;i++) n[i]=a[i];
normal(n);
glNormal3fv(n);
glVertex3fv(a);
for(i=0;i<3;i++) n[i]=b[i];
normal(n);
glNormal3fv(n);
glVertex3fv(b);
for(1=0;i<3;1++) n[i]=c[i];
normal(n);
glNormal3fv(n);
glVertex3fv(c);
glEnd();
}
выполнении этого варианта программы, пока-выполнении этого варианта программы, пока-Результат закрашивания, полученный при зан на рис. 6.40.
выполнении этого варианта программы, пока-
выполнении этого варианта программы, пока-
Рис. 6.39. Изображение сферы, сформированное методом плоского закрашиванияРис. 6.39. Изображение сферы, сформированное методом плоского закрашиванияРис. 6.40. Изображение сферы, сформированное методом интерполяционного закрашиванияРис. 6.40. Изображение сферы, сформированное методом интерполяционного закрашивания
Рис. 6.39. Изображение сферы, сформированное методом плоского закрашивания
Рис. 6.39. Изображение сферы, сформированное методом плоского закрашивания
Рис. 6.40. Изображение сферы, сформированное методом интерполяционного закрашивания
Рис. 6.40. Изображение сферы, сформированное методом интерполяционного закрашивания
Но, к сожалению, такой метод определения нормали можно применять только при работе с поверхностями, которые описываются довольно простыми аналитическими зависимостями. При формировании изображения в этом варианте программы не использовался метод Гуро, иначе пришлось бы при формировании каждой вершины вычислять нормали всех пересекающихся в ней граней. В программе не создается структура данных, отражающая топологию аппроксимирующего многогранника, а потому в ней отсутствует необходимая для метода Гуро информация. Попробуйте выполнить упр. 6.9 и 6.10, в которых предлагается сформировать необходимую структуру данных. Обратите внимание на то, что в аппроксимирующем многограннике, полученном по методу половинного деления, в каждой вершине пересекаются шесть граней, в то время как в исходном тетраэдре — три.
Глобальное тонирование
Естественно, что локальная модель освещенности имеет определенные ограничения. Рассмотрим, например, группу шаров (сфер), освещенных солнечным светом (удаленным источником). как показано на рис. 6.41, а. Шар, расположенный ближе всех к солнцу, будет закрывать другие шары. т.е. на них появится тень от первого шара. При использовании локальной модели поверхность каждого объекта анализируется отдельно, а значит, сформировать тень от одного объекта на другом не представляется возможным, что и показано на рис. 6.41,6. Если к тому же шары отполированы, то в реальной сцене вы увидите на соседних шарах блики, вызванные зеркальным отражением света от одного шара в направлении другого. И этот эффект не может быть учтен при использовании локальной модели.
a)6)
Рис. 6.41. Изображение группы шаров: а— изображение создано методом глобального тонирования; б — изображение создано на основе локальной модели освещенности
Если требуется создать изображение, в котором присутствовали бы все описанные оптические эффекты, нужно использовать более сложную, а значит, и более медленную модель глобального распределения света, из которых на сегодняшний день наиболее популярными являются модели трассировки лучей и анализа излучательности. В данном разделе мы кратко опишем эти две дополняющие друг друга модели. Модель трассировки лучей адекватно передает оптические эффекты в сценах, насыщенных зеркальными поверхностями, если, например, значительная часть объектов сделана из стекла, полированного металла и других аналогичных материалов. Модель анализа излучательности лучше передает оптические эффекты в сценах с рассеивающими поверхностями, например в интерьере зданий.
Трассировка лучей
Модель трассировка лучей является в значительной степени расширением уже рассмотренной выше локальной модели распространения света. Она основана на том факте, что из всех лучей, испускаемых источником, на формируемое изображение влияют только те, которые, в конце концов, попадают в глаз наблюдателя или в объектив камеры и проходят через центр проецирования. На рис. 6.42 показаны варианты распространения лучей, испускаемых точечным источником, в сцене, включающей несколько зеркально отражающих поверхностей. Луч может попасть в объектив камеры прямо от источника, после однократного отражения от поверхности, видимой объективом камеры, после многократного отражения от разных объектов или после преломления.
Большинство лучей, испускаемых источником, не попадают в камеру, а значит, и не влияют на формируемое ею изображение. Следовательно, отслеживать все лучи не имеет никакого смысла — это значит впустую тратить время. Однако если изменить направление распространения лучей на противоположное, то совершенно очевидно, что в создании изображения “участвуют” только те из них, которые “испускает” центр проецирования с учетом рамки отсечения в картинной плоскости (рис. 6.43). Эти-то лучи и имеет смысл анализировать— их принято называть приведенными лучами (cast rays). При реализации метода трассировки рассматриваются лучи, проходящие через отдельные элементарные участки картинной плоскости, — чаше всего, отдельные пиксели. Каждый приведенный луч либо попадает на поверхность какого-нибудь объекта сцены, в том числе и источника света, либо уходит в бесконечность и теряется там. Пикселям, через которые проходят такие “потерянные” лучи, нужно присвоить цвет заднего плана. Для тех приведенных лучей, которые попали на какую- либо поверхность (будем пока что считать, все поверхности непрозрачны), нужно вычислить цвет точки пересечения с объектом. Если применить для этого модель Фонга, то сформируется точно такое же изображение, как и при использовании локальной модели. Но можно поступить и по-другому.
Рис. 6.42. Лучи, испускаемые источ- Рис. 6.43. Модель приведения лучей никомРис. 6.42. Лучи, испускаемые источ- Рис. 6.43. Модель приведения лучей никомОбратите внимание на то, что процессы, которые требуются для трассировки (моделирование объектов, проецирование и определение видимых поверхностей), — это отдельные стадии уже не раз упоминавшегося конвейера обработки информации в графической системе. Но порядок выполнения вычислений совершенно другой. При выполнении тонирования
Рис. 6.42. Лучи, испускаемые источ- Рис. 6.43. Модель приведения лучей ником
Рис. 6.42. Лучи, испускаемые источ- Рис. 6.43. Модель приведения лучей ником
в конвейере последовательность обработки задается последовательностью вершин, а при трассировке лучей — последовательностью пикселей. В главе 7 мы рассмотрим, как эти отличия сказываются на реализации графической системы.
При трассировке лучей первым делом нужно проанализировать, освещена ли точка пересечения луча с поверхностью светом от какого-либо источника. Для этого вычисляются закрашивающие (shadow) или зондирующие лучи (feeler rays) (рис. 6.44), “исходящие” из этой точки поверхности в сторону каждого источника света. Если зондирующий луч по дороге “упирается” в какой-либо объект, свет от соответствующего источника не достигает этой точки поверхности и она оказывается в тени по отношению к этому источнику. Поэтому можно считать, что никаких вычислений, связанных с этим источником, для определения освещенности анализируемой точки выполнять не нужно. Если все объекты непро-
„, .. „зрачны и в процессе анализа не рассматривается
Рис. 6.44. Лучи от затененных поверхно- г_
стей "влияние отражения от поверхностей других объ
ектов, то получится изображение, в котором в дополнение к тому, что сформировано по модели Фонга, появились еще и тени. Цена, которую придется заплатить за такое дополнение, — выполнение некой модификации алгоритма анализа невидимых поверхностей для каждой точки пересечения приведенных лучей с какими-либо поверхностями.
Теперь предположим, что некоторые поверхности обладают свойствами зеркального отражения (рис. 6.45). Тогда можно проследить дальнейший путь зондирующего луча после отражения от промежуточной поверхности и продолжать эту процедуру до тех пор, пока не выяснится, что луч
ушел в бесконечность или попал в источник. Такую процедуру, очевидно, можно реализовать в виде рекурсивной и принимать во внимание поглощение света при каждом очередном отражении.
Рис. 6.45. Трассировка лучей с учетом отраженияРис. 6.45. Трассировка лучей с учетом отраженияРис. 6.46. Трассировка лучей при наличии отражения и преломленияРис. 6.46. Трассировка лучей при наличии отражения и преломления
Рис. 6.45. Трассировка лучей с учетом отражения
Рис. 6.45. Трассировка лучей с учетом отражения
Рис. 6.46. Трассировка лучей при наличии отражения и преломления
Рис. 6.46. Трассировка лучей при наличии отражения и преломления
Особенно хорошие результаты дает метод трассировки лучей при формировании изображения сцен, насыщенных отражающими и преломляющими поверхностями. Используя базовую парадигму этого метода, будем отслеживать дальнейшую траекторию луча, который после “столкновения” с поверхностью частично поглощается, частично диффузно рассеивается, а оставшаяся часть энергии луча делится на две составляющие, из которых одна зеркально отражается, а вторая — преломляется. Если источник света видим из точки пересечения, то сначала нужно вычислить “вклад” от этого источника в освещенность анализируемой точки, используя для этого стандартную модель отражения. Далее нужно разделить трассируемый луч на два: один приводится к направлению идеального отражения, а второй — к направлению идеального преломления. Эти два луча в дальнейшем будут анализироваться как и начальные приведенные лучи, т.е. они могут пересекаться с другими поверхностями, заканчиваться в каком-либо из источников или навсегда покидать сцену. При столкновении такого вторичного приведенного луча с поверхностью какого- либо объекта могут возникать новые вторичные лучи. На рис. 6.47 показана траектория прохождения единственного луча от источника к наблюдателю в довольно простой сцене, а на рис. 6.48 — дерево лучей, сформированное для этого случая. Из этого дерева, которое динамически формируется в процессе трассировки, видно, какие лучи следует прослеживать.
Рис. 6.47. Схема среды, анализируемой методом трассировки лучейРис. 6.47. Схема среды, анализируемой методом трассировки лучейРис. 6.48. Дерево лучей, соответствующее схеме на рис. 6.47Рис. 6.48. Дерево лучей, соответствующее схеме на рис. 6.47
Рис. 6.47. Схема среды, анализируемой методом трассировки лучей
Рис. 6.47. Схема среды, анализируемой методом трассировки лучей
Рис. 6.48. Дерево лучей, соответствующее схеме на рис. 6.47
Рис. 6.48. Дерево лучей, соответствующее схеме на рис. 6.47
Сама собой напрашивается идея организовать трассировку в виде рекурсивной процедуры, которая будет сама вызывать себя, как только выяснит, что анализируемый луч отражается или преломляется (см. упр. 6.16 и 6.17). Значительная часть вычислений при реализации трассировки лучей приходится на определение пересечения лучей с объектами сцены. По этой причине такой метод очень сложно реализовать при отображении сцен, насыщенных множеством криволинейных объектов. Поэтому большинство систем, в которых используется эта технология, работает исключительно с плоскими или квадратичными поверхностями. В главе 10 будет рассмотрена только часть сложностей, возникающих при решении задачи пересечения лучей с криволинейными поверхностями.
Хотя рассмотренный алгоритм трассировки лучей и учитывает диффузное рассеяние света в точке пересечения луча с поверхностью, засветка других поверхностей рассеянным в результате такого отражения светом игнорируется. Если попытаться проследить и траекторию этих лучей, задача неимоверно усложнится и вряд ли какой-нибудь компьютер справится с ней за приемлемое время. Поэтому этот алгоритм имеет смысл применять только при отображении сцен, в которых большинство объектов обладает зеркальными свойствами. На ил. 10 цветной вклейки показано изображение сцены, сформированное с использованием метода трассировки лучей. Хотя сцена включает всего несколько предметов, но никакой другой метод не смог бы так передать в изображении их оптические свойства— прозрачность и зеркальность. Обратите внимание также и на нюансировку теней на рифленых поверхностях ручек — это еще один из эффектов, которые доступны методу трассировки лучей.
Метод анализа излучательности
Метод анализа излучательности очень хорошо подходит для тонирования сцен, в которых подавляющее большинство объектов имеет поверхности с диффузным рассеиванием. В этом случае можно сформировать глобальное уравнение энергетического баланса, решение которого позволит получить цвет каждой из полигональных поверхностей. Этот метод до
вольно сложен, и даже его чрезвычайно упрошенное описание не укладывается в рамки этой книги, но основные идеи метода мы все же попробуем объяснить на простом примере.
Рассмотрим сцену, которая состоит только из поверхностей, характеризуемых идеальным диффузным рассеиванием (рис. 6.49). Если тонировать такую сцену в графической системе,
Рис. 6.49. Сцена с диффузно рассеивающими поверхностямиРис. 6.49. Сцена с диффузно рассеивающими поверхностямииспользуя удаленные источники света, то каждый из многоугольников поверхностей будет равномерно окрашен определенным цветом. Но в реальной обстановке часть света, отраженного красной стеной, попадет на белую стену и придаст ей слабый красный оттенок, причем этот оттенок будет сильнее на той части белой стены, которая граничит с красной. Аналогичный эффект на красной стене создаст и диффузно отраженный свет от белой стены. Та простая модель, которую мы рассматривали раньше, эти эффекты не учитывает.
Рис. 6.49. Сцена с диффузно рассеивающими поверхностями
Рис. 6.49. Сцена с диффузно рассеивающими поверхностями
Решение уравнения тонирования для этой сцены, если найти приемлемые методы его решения, позволит корректно определить цвет в каждой точке отображаемых поверхностей. Если предположить, что все поверхности характеризуются идеальным диффузным отражением, то уравнения тонирования можно упростить до такой степени, что для его решения годятся численные методы.
Базовый метод анализа излучательности предполагает, что вся
сцена разбивается на большое число многоугольников малого размера— фрагментов
(patches), причем каждый из фрагментов можно считать идеальным диффузным рассеивате
лем, который можно закрашивать равномерно (рис. 6.50). Остается только найти, в какой именно цвет нужно окрасить фрагмент. Это делается в два этапа. Сначала необходимо рас-
Рис. 6.50. Разделение поверхностей объектов сцены на фрагментыРис. 6.50. Разделение поверхностей объектов сцены на фрагментысмотреть пары фрагментов и определить коэффициенты формы {form factors), которые описывают взаимное расположение фрагментов каждой пары и их влияние друг на друга. После вычисления всех коэффициентов формы общее уравнение тонирования, которое первоначально имеет форму интегрального, упрощается и принимает форму системы линейных уравнений для излучательности граней, в частности вследствие отражения. Хотя для определения коэффициентов формы требуется довольно много вычислений — порядка 0{п") для п фрагментов, — полученные значения излучательности не зависят от положения наблюдателя и для статических элементов сцены их можно не пересчитывать при изменении положения наблюдателя, если, конечно, все фрагменты позволительно с приемлемой точностью считать идеально рассеивающими. В результате оказывается, что при перемещении камеры по сцене тонирование изображения выполняется практически с той же скоростью, что и при использовании локальной модели.
Рис. 6.50. Разделение поверхностей объектов сцены на фрагменты
Рис. 6.50. Разделение поверхностей объектов сцены на фрагменты
При тонировании сцен методом анализа излучательности распределенные источники света моделируются активными фрагментами, не только отражающими и рассевающими падающий на них световой поток, но и излучающими собственный.
Резюме
В этой главе была рассмотрена модель освещения и распределения света, которая хорошо сочетается с конвейерной архитектурой графической системы. Эта модель достаточно точно передает множество различных световых эффектов и допускает включение в сцену источников света разных видов. Хотя модель и не учитывает глобальных эффектов, связанных с многократным отражением световых лучей от одного объекта к другому, она позволяет на большинстве рабочих графических станций тонировать сцены, включающие полигональные объекты, используя при этом модель отражения Фонга и интерполяцию распределения цвета по поверхности. Время формирования тонированной сцены с использованием такой модели сравнимо с временем формирования сцены без закрашивания. Включение процедуры тонирования в пользовательскую программу не требует больших усилий — нужно задать параметры источников света и свойства материалов поверхностей.
Несмотря на ограничения, присущие локальной модели, результаты, полученные на ее основе в большинстве прикладных графических систем, в том числе и в OpenGL, впечатляют.
В этой главе вы могли поближе познакомиться и с методом рекурсивного разбиения многогранников, который был положен в основу методики аппроксимации сферы. В последующих главах, в частности 10 и 11, вы встретитесь с различными приложениями этого метода к аппроксимации кривых и криволинейных поверхностей. С этим же методом вы встретитесь и при анализе технологии моделирования геометрических объектов, когда речь пойдет о внутреннем подобии широкого класса природных объектов.
Эта глава завершает изложение основ компьютерной графики полигональных моделей. Материал первых шести глав позволяет вам приступить к самостоятельной разработке программ формирования изображений сцен, в которых присутствуют источники света и выполнено тонирование. Технология формирования более сложных изображений, предполагающих наложение текстуры материалов, требует работы на уровне отдельных пикселей, методика которой описана в главе 9.
После тщательной проработки материала первых шести глав самое время приступить к программированию несложных графических приложений. Поэкспериментируйте с параметрами материалов и источников света. Попытайтесь смоделировать движение источника света в пространстве сцены в разных вариантах — синхронно с другими объектами сцены и неза-
висимо от них. Конечно, на первом этапе вы встретитесь со множеством сложностей, формируемое изображение будет иметь изъяны, не будет передавать всех световых эффектов, характерных для реальной сцены. Например, очень сложно передать проблески света через шель между поверхностями. Часто подобные изъяны связаны с накоплением малых числовых погрешностей в вычислениях. Существует множество искусственных приемов, которые помогают справиться с последствиями таких ошибок. До некоторых из них вы додумаетесь сами в процессе работы, с другими познакомитесь в литературе, которая будет рекомендована в следующем разделе.
В главе 7 мы перейдем к практическим вопросам реализации описанного метода тонирования. Хотя и в этой главе мы затрагивали работу отдельных модулей конвейера в процессе тонирования изображения, в следующей главе мы остановимся на них детально.
Рекомендуемая литература
Тема использования освещения и отражения света в компьютерной графике имеет два основных аспекта: физический и алгоритмический. С точки зрения физики процесса большой интерес представляет работа {Kaj86], в которой выведены уравнения энергетического баланса в среде, основанные на отражательной способности отдельных объектов сцены. Модели отражения, в частности модель Торренса-Спарроу (Torrance-Sparrow) [Тог67] и Кука-Торренса (Cook-Torrance) [Соо82], основаны на моделировании криволинейной поверхности множеством малых плоских граней. Анализ этих моделей читатель найдет в работах Холла (Hall) [На189] и Фоли (Foley) [Fol90].
Фонгу (Phong) принадлежит заслуга создания вычислительной модели, в которой учтены все три главные составляющие распределения световой энергии в среде, — фонового света и света, отраженного в результате диффузного и зеркального отражения от объектов сцены [Pho75]. Использовать вектор половинного направления первым предложил Блинн (Blinn) в работе [ВИ77]. Базовая модель пропускания и преломления света впервые была описана Уит- тедом (Whitted) в работе [Whi80]. Позднее эта модель была усовершенствована Хекбертом (Heckbert) и Хенрехеном (Hanrahan) [Нес84]. Гуро (Gouraud) в работе [Gou71] предложил использовать интерполяционное закрашивание.
Метод трассировки лучей был изобретен Аппелем (Арре!) и впервые описан в работе {Арр68]. Множество ранних работ по трассировке лучей включено в сборник [Joy88]. Книга Гласснера (Glassner) [Gla89] будет особенно полезна тем читателям, которые захотят разработать собственный вариант программы трассировки лучей. Метод анализа излучательности впервые был использован в работе [Sie81] для анализа распространения теплового излучения. Заслуга внедрения этого метода в компьютерную графику принадлежит Горэлу (Goral) и его коллегам [Gor84]. С тех пор этот метод был значительно усовершенствован многими исследователями. В работах [Coh85, Coh88] описаны пути повышения его эффективности, а в [SU89] предложено ввести в рассмотрение дополнительные составляющие.
Множество полезных советов, помогающих эффективно использовать средства тонирования, которыми располагает пакет OpenGL, читатель найдет в руководстве OpenGL Programmer's Guide [Оре97,а].
Упражнения
В большинстве графических систем используются простые модели освещения и отражения, которые были описаны применительно к тонированию поверхностей, состоящих из плоских многоугольников. Какие допущения сделаны в каждой из этих моделей? Для каждого из допущений дайте примеры сцен, в которых подобное упрощение может привести к некорректному тонированию.
При закрашивании больших многоугольников в программе, обращающейся к OpenGL, их цвет на экране оказывается неодинаковым — более ярким в одной об- ■ ласти и более темным — в другой. Объясните, в чем причина такой неравномерности закрашивания. Как, по-вашему, можно избежать этого эффекта.
При анализе модели отражения Фонга мы не рассматривали источники света, лучи от которых по пути к анализируемой поверхности “наталкиваются ” на другую поверхность. Объясните, почему такие источники игнорируются в этой модели.
Как повлияет на вычисления, связанные с тонированием изображения сцены, учет расстояния между отображаемой поверхностью и наблюдателем?
При обсуждении алгоритмов тонирования мы рассматривали только RGB-модель представления цвета. Приведите аргументы в пользу использования при тонировании системы дополняющих цветов CMY.
Найдите на поверхности сферы единичного радиуса четыре точки, находящиеся друг от друга на одинаковом расстоянии. Эти точки— вершины вписанного тетраэдра. Указание. Одну из точек можно выбрать произвольно; пусть это будет точка (0,1,0). Тогда другие три лежат на плоскости у - -d , где d — некоторое положительное число.
Покажите, что если v лежит в той же плоскости, что и 1, п и г, то угол половинного направления удовлетворяет условию 2\|/ = ф. Какое соотношение существует между этими углами в случае, если v не является компланарным остальным векторам?
Рассмотрите все варианты, включающие ближнего и удаленного наблюдателя, ближний и удаленный источники света, плоские и криволинейные поверхности, диффузное и зеркальное отражение. В каких вариантах можно упростить алгоритм закрашивания? В каких вариантах использование вектора половинного направления помогает упростить вычисления? Объясните свои ответы.
Разработайте структуру данных для представления рекурсивно разделяемого тетраэдра. Проследите путь по этой структуре, который нужно совершить при использовании метода Гуро для закрашивания изображения сферы, аппроксимированной в результате рекурсивного разбиения тетраэдра.
Повторите упр. 6.9, но в качестве исходной фигуры используйте не тетраэдр, а икосаэдр.
Разработайте структуру данных для представления сети, состоящей из четырехугольников. Запрограммируйте раскрашивание поверхности, представленной таким способом.
Разработайте программу, которая будет выполнять рекурсивное разбиение четырехугольников в сети, состоящей из многоугольников этого типа.
Пусть граница между двумя средами с разными оптическими свойствами является плоскостью. Предположим, что скорость света в одной среде гь а в другой-— г2. Покажите, что при соблюдении закона Снелля свет дойдет от точки в одной среде до точки в другой среде за минимальное время.
Покажите, что вектор половинного направления h задает такую ориентацию поверхности, которая обеспечит максимальное отражение светового потока к наблюдателю.
Найдите такой вектор h’ для преломленного луча, который определяет ориентацию поверхности, обеспечивающую максимальное отражение падающего луча в сторону наблюдателя.
Хотя в этой главе мы и не рассматривали операции на уровне буфера кадра, вы можете приступить к разработке собственного варианта программы трассировки лучей, предполагая существование в библиотеке функции write_pixel(x, у, color), которая будет закрашивать цветом color (это может быть либо RGB-предствление, либо яркость) пиксель, имеющий в буфере адрес (х, у). Разработайте псевдокод программы ray, которая будет рекурсивно отслеживать приведенный луч. Считайте, что в вашем распоряжении имеется функция, анализирующая пересечение луча с объектом. Подумайте над тем, до каких пор нужно трассировать путь луча.
Разработайте программу трассировки лучей в сцене, которая включает только сферы, полагая, что в вашем распоряжении имеется функция окрашивания отдельного пикселя в буфере кадра. В программе используйте точное математическое описание сферы, а не ее аппроксимацию многогранником.
В программу формирования и отображения лабиринта из упр. 5.13 добавьте источники света и процедуру закрашивания.
Используя в качестве прототипа программу формирования сферы, приведенную в приложении А, разработайте интерактивную программу, которая позволит разместить в пространстве сцены один или два источника света и изменять свойства материала поверхности. Попробуйте с помощью этой программы подобрать свойства материалов, позволяющих смоделировать на экране поверхности объектов из металла, пластика или натурального камня.
По мере того как геометрическая информация проходит по “конвейеру”, она подвергается преобразованиям поворота, сдвига, масштабирования и проецирования. Такое же преобразование выполняется и с векторами, которые определяют косинусные составляющие в модели отражения Фонга. Сохраняет ли какое-либо из этих преобразований углы между векторами? Как повлияет сформулированный ответ на реализацию процедуры закрашивания?
Сравните объем вычислительных операций, требуемых для реализации методов закрашивания Фонга и Гуро. Примите во внимание результаты, полученные при выполнении упр. 6.20.
Обобщите алгоритм формирования теней, описанный в главе 5, на обработку плоских поверхностей, произвольно ориентированных в пространстве.
Модифицируйте алгоритм формирования теней таким образом, чтобы он мог работать с удаленными источниками света. Указание. Вместо перспективного проецирования используйте параллельное.
Сравните алгоритм формирования теней, описанный в главе 5, с тем, который будет использовать метод глобального тонирования сцены. Какие тени можно сформировать одним из этих методов, но нельзя сформировать другим?
ГЛАВА
7
Алгоритмы формирования изображения
В
этой главе мы рассмотрим, как в графической системе реализуется процесс формирования изображения, т.е. те методы обработки информации, о которых шла речь в предыдущих главах. Анализ алгоритмов реализации этих методов позволит вам, во- первых, получить четкое представление о том, что происходит с разработанной вами программой при ее выполнении графическим пакетом, — как вычерчиваются на экране линии и заливаются многоугольники, что происходит с графическими примитивами, которые не умещаются в заданный объем видимости. Во-вторых, как мы уже отмечали выше, не имея четкого представления о том, как реализуется программа, нельзя эффективно использовать графическую систему. Программист должен знать, по крайней мере, какие этапы обработки выполняются системой без привлечения значительных ресурсов, а какие требуют больших вычислительных затрат и использования специализированных, а потому дорогих аппаратных или программных средств. А в-третьих, знакомство с алгоритмами и средствами реализации методов отображения позволит вам открыть для себя новые возможности использования средств нижнего уровня (в частности, буфера кадра), такие как наложение текстур и организация альфа-канала.
Изучение средств реализации графической системы предполагает, прежде всего, подробное знакомство с соответствующими алгоритмами. Как и при анализе алгоритмов другого назначения, мы уделим много внимания сравнению теоретической и реальной производительности и вариантам аппаратной и программной реализации с учетом специфических характеристик приложений. Хотя корректность той или иной программы на OpenGL всегда можно оценить визуально по тому, что формируется, в конце концов, на экране, существует множество вариантов алгоритмов реализации одной и той же процедуры, которые нужно тщательно проанализировать, поскольку от правильного выбора во многом зависит производительность приложения. Я специально буду обращать ваше внимание на то, как базовые операции реализованы в стандартных API, таких как OpenGL, PHIGS и Renderman. Эти операции по-разному организуются в системах с конвейерной архитектурой и в системах, использующих другие технологии обработки, например трассировку лучей.
Четыре основные задачи
В любой системе отображения геометрических объектов, таких как многоугольники в трехмерном пространстве, по мере обработки информации решаются четыре основные задачи:
моделирование;
геометрическая обработка;
преобразование в растр;
отображение.
На рис. 7.1 показано, как организовано решение этих задач в частном случае графической системы с конвейерной архитектурой. Но какой бы ни была архитектура системы, в ней все равно должны быть предусмотрены средства для решения всех четырех задач.
.,I ^ Геометрическая ?^ Преобразование! ^_ ,I
Моделирование ।► обработка |в растр IОтображение I
Miws»0№fl№imM^^ея$^аее«вя«еяя^^wwesawwoewewifM^
Рис. 7.7. Комплекс задач обработки информации в процессе формирования изображения
Моделирование
Результатом решения задачи моделирования является множество вершин, однозначно определяющих набор геометрических объектов тех типов, которые поддерживаются программными и аппаратными средствами конкретной графической системы. В нескольких примерах, рассмотренных в предыдущих главах, вы уже имели возможность познакомиться с отдельными алгоритмами моделирования. В частности, в главе 6 был описан алгоритм аппроксимации сферической поверхности. Подробному анализу алгоритмов моделирования будет посвящена глава 8.
Сейчас мы рассмотрим средства решения задачи моделирования как “черный ящик”, на выходе которого формируются описания геометрических объектов, подлежащих отображению. Хотя моделирование— это прерогатива прикладной программы, существует еще ряд побочных задач, решение которых также возлагается на средства моделирования. Примером такой вспомогательной задачи является отсечение — процесс разделения объектов на две группы: те, которые попадают в заданную зону видимости и будут выведены на экран, и те, которые в нее не попадают. Пользователь формирует в своей программе необходимые ему геометрические объекты и полагает, что графическая система будет обрабатывать их с той же скоростью, с которой они формируются. Программа моделирования может облегчить “участь” графической системы, взяв на себя заботы по минимизации количества объектов, передаваемых для дальнейшей обработки и отображения. Такой подход означает, что средства моделирования в определенной степени дублируют некоторые функции графической системы, но используют при этом иные алгоритмы. В частном случае задачи отсечения прикладная программа моделирования, которая располагает информацией о специфике приложения, может использовать эвристические методы для выделения если не всех, то большинства примитивов, не попадающих в зону видимости еще до того, как они попадут на стандартный “конвейер” обработки.
Геометрическая обработка
Цель геометрической обработки — определить, какие геометрические объекты подлежат отображению, и сформировать степень почернения или оттенок цвета этих объектов. В ходе геометрической обработки решается четыре связанные друг с другом подзадачи: нормализация (normalization), отсечение (clipping), удаление невидимых поверхностей (hidden-surface removal) и закрашивание (shading). После выполнения этих этапов обработки наступает очередь последнего — проективного преобразования трехмерной сцены в двухмерное изображение на картинной плоскости.
Первый этап геометрической обработки — преобразование описания объектов из системы координат пользователя в систему координат камеры или экрана. Учитывая, что геометрический процессор имеет дело с описаниями вершин, характеризующих отображаемые объекты, которые, в свою очередь, заданы в однородных координатах, на этом этапе выполняется последовательность аффинных преобразований. В большинстве графических систем при этом формируется и нормализованная зона видимости. Перспективная нормализация позволяет не только свести все виды проецирования к ортогональному, но и преобразует все виды сцены к ортогональным видам, что существенно упрощает задачу отсечения, как будет показано в разделе 7.6.
Преобразование геометрических объектов выполняется как в процессе моделирования (изменяется форма объекта и его положение в пространстве сцены), так и в процессе его визуализации. В конечном счете после растрового преобразования на экране в любом случае появляются только те объекты, которые находятся в зоне видимости, но было бы крайне неразумно подвергать все объекты растровому преобразованию и только после этого выяснить, что большая их часть не умещается на экране и все усилия на их обработку были потрачены впустую. Эту задачу нужно решить гораздо раньше.
Но даже если объект и находится в зоне видимости, совсем не обязательно, что его изображение появится на экране, — этот объект может быть закрыт от наблюдателя другими объектами. Алгоритмы удаления невидимых поверхностей или определения видимых поверхностей (visible-surface determination) основаны на анализе отношений в пространстве между объектами и реализуются как часть процесса геометрической обработки. Модель распределения освещенности, рассмотренная в главе 6, также оперирует с геометрической информацией для вычисления степени почернения или оттенка цвета, в частности необходимо знать ориентацию закрашиваемых поверхностей, т.е. направление вектора нормали. Следовательно, часть вычислений, связанных с закрашиванием, также реализуется в процессе геометрической обработки.
При решении всех описанных задач используется единый математический аппарат преобразований в трехмерном пространстве, причем все операции выполняются над действительными числами, представленными в формате с плавающей точкой, т.е. реализация всех этих задач требует весьма схожих программных и аппаратных средств.
Растровое преобразование
После выполнения проективного преобразования графическая система имеет дело только с двухмерными объектами. Хотя эти объекты и описаны в системе координат экрана, они представлены только характерными точками — вершинами. Например, после проецирования трехмерный прямолинейный отрезок становится двухмерным, представленным парой двухмерных вершин. Для вывода изображения этого отрезка на экран необходимо сформировать на основе координат этих вершин последовательность точек растра— пикселей. Процесс формирования промежуточных точек графического образа объекта и составляет суть растрового преобразования (rasterization, или scan conversion). В последующих разделах будут рас-
смотрены алгоритмы растрового преобразования отрезков и многоугольников, полагая, что все объекты аппроксимируются этими базовыми примитивами.
С точки зрения прикладного программиста растровое преобразование — заполнение буфера кадра— выполняется графической системой автоматически и прикладной программе нет нужды обращаться к отдельным пикселям экрана. Но, тем не менее, в большинстве графических систем, в том числе и в OpenGL, программисту предоставляются средства для доступа к отдельным пикселям в буфере кадра, что открывает новые возможности формирования изображения. Обсуждение этих возможностей мы отложим до главы 9.
Отображение
В большинстве графических систем извлечение изображения из буфера кадра и передача его на экран выполняются автоматически, и прикладная программа никак не влияет на этот процесс. Однако существует множество проблем, связанных с качеством изображения, в частности проблема появления “зазубрин” {jaggedness) на отображаемых линиях. В последующих разделах мы рассмотрим алгоритмы сглаживания “зазубрин” и воспроизведения цвета при отображении.
Базовые стратегии реализации
Существуют два основных подхода к организации выполнения перечисленных задач в графической системе, которые отличаются порядком выполнения отдельных операций. Чтобы понять суть отличия, будем считать, что вся система реализована в виде единой программы. В качестве исходной информации программа принимает множество вершин, специфицирующих геометрические объекты, а выходом программы является массив кодов засветки пикселей в буфере кадра. В процессе формирования изображения программа должна определить коды засветки всех пикселей в буфере, обработав описания всех геометрических объектов сцены и всех источников света. Следовательно, совершенно логично предположить, что в программе должно существовать множество вложенных циклов обработки базовых переменных, представляющих пиксели, геометрические объекты и источники света.
Приступая к разработке такой программы, нужно первым делом решить, что будет представлять собой внешний цикл. Последовательность отдельных этапов обработки в программе зависит от того, как будет сформулирован ответ на этот вопрос. Существуют два основных варианта ответа, т.е. варианта стратегии обработки: стратегия, ориентированная на формируемое изображение {image-oriented), и стратегия, ориентированная на обрабатываемые объекты сцены {object-oriented). Иногда их именуют подходом с сортировкой в начале {sort- Jirst) и подходом с сортировкой в конце {sort-last), принимая во внимание, какое место в последовательности обработки отводится задаче удаления невидимых поверхностей.
При реализации стратегии, ориентированной на обрабатываемые объекты сцены, внешним в программе формирования изображения является цикл по всем геометрическим объектам:
for(each_object) render(object);
Такая стратегия идеально согласуется с конвейерной архитектурой графической системы. Описания вершин каждого объекта последовательно обрабатываются модулями системы, которые выполняют разного рода преобразования, определяют цвет и видимость. Описание многоугольника проходит через стадии обработки, схематически представленные на рис. 7.2. Обращаю ваше внимание на то, что после геометрических трансформаций при выполнении растрового преобразования очередного многоугольника его изображение может попасть в любую точку экрана, т.е. до выполнения преобразований невозможно сказать, где именно на экране появится изображение этого многоугольника (и появится ли во-
Рис. 7.2. Обработка описания многоугольника в стратегия, ориентированная на объектыРис. 7.2. Обработка описания многоугольника в стратегия, ориентированная на объектыПреобразование _ в растрПреобразование _ в растрБуфер кадраБуфер кадраобще). В большинстве систем, в которых реализуется стратегия, ориентированная на объекты, используется конвейерная архитектура, причем часть задач решается программными средствами, а часть — аппаратными. Данные (описания вершин) последовательно проходят через все эти модули.
Рис. 7.2. Обработка описания многоугольника в стратегия, ориентированная на объекты
Рис. 7.2. Обработка описания многоугольника в стратегия, ориентированная на объекты
Преобразование _ в растр
Преобразование _ в растр
Буфер кадра
Буфер кадра
системе, в которой реализуется
Поскольку такая стратегия предполагает независимую обработку каждого объекта, изображение которого может находиться в любом месте экрана, для ее реализации требуется довольно много памяти. При обработке любого очередного объекта должен обеспечиваться доступ ко всем пикселям в буфере кадра, а также к другим вспомогательным буферам, например буферу глубины, который используется при удалении невидимых поверхностей. В настоящее время эта особенность объектно-ориентированной стратегии уже не вызывает таких затруднений с реализацией, как прежде, поскольку цены на микросхемы памяти значительно снизились, а объем хранимой информации увеличился. Сейчас никого не удивляет аппаратно реализованный геометрический процессор, способный обрабатывать свыше 1 миллиона многоугольников в секунду. Поскольку каждый примитив обрабатывается по одному и тому же алгоритму, значительная часть графической системы может быть реализована аппаратно, причем специализированные модули обеспечивают вполне приемлемую скорость обработки и сравнительно дешевы.
При реализации стратегии, ориентированной на формируемое изображение, внешним в программе является цикл по всем пикселям в буфере кадра или по строкам пикселей — строкам развертки (scan lines):
for(each_pixel) assign_a_color(pixel);
При обработке каждого пикселя (или строки развертки) нужно определить, изображения каких именно объектов “накрывают” этот пиксель (или пересекаются этой строкой развертки). Каждый из таких “объектов-претендентов” потенциально может внести свой вклад в засветку пикселя. Преимущество такого подхода заключается в том, что в каждый момент времени требуется весьма небольшой объем памяти и потенциально можно добиться того, что пиксели будут обрабатываться со скоростью, достаточной для регенерации изображения на экране. Поскольку результаты обработки при переходе от одного пикселя к соседнему не слишком отличаются, можно использовать очень эффективные алгоритмы, работающие с приращениями и учитывающие предысторию. Основной недостаток подхода, ориентированного на изображение, — необходимость предварительного упорядочения описания объектов сцены, которое позволяло бы довольно быстро отыскивать среди них те, которые могут внести свой вклад в засветку определенного пикселя.
В дальнейшем мы сосредоточимся на подходе, ориентированном на объекты, хотя для некоторых процедур разработки будут рассматриваться алгоритмы, приемлемые в обоих случаях.
Реализация геометрических преобразований
Практически во всех существующих на сегодняшний день графических системах реализация геометрических преобразований базируется на математическом аппарате однородных координат. Использование в качестве операторов матриц размером 4x4 позволяет выполнять суперпозицию преобразований перемножением соответствующих матриц. До сих пор при обсуждении преобразований мы ориентировались на используемые в OpenGL матричные параметры состояния — матрицу вида и матрицу проективного преобразования. В этой главе будет использован несколько отличный подход, который, тем не менее, довольно просто привести к указанным матрицам состояния. Мы будем рассматривать преобразования как последовательные изменения системы координат, каждое из которых можно представить в виде матрицы преобразования однородных координат. Нас интересуют пять систем координат:
система координат объектов (мировая);
система координат наблюдателя (камеры);
система координат отсечения;
нормализованная система координат устройства отображения;
система координат окна приложения.
На рис. 7.3 показана последовательность преобразования отдельного геометрического объекта в процессе перехода от одной системы координат к другой. Сначала объект (куб) и наблюдатель специфицируются в мировой системе координат. Матрица вида (model-view matrix) задает переход от мировой системы координат к системе координат наблюдателя. Следующий переход на рис. 7.3 — проективное преобразование. Если в системе используется математический аппарат, описанный в главе 5, то после этого преобразования сохраняется трехмерность описания, но вследствие нормализации проецирования это описание будет представлять предискаженный объект, готовый к выполнению ортогонального проецирования. В ходе нормализации зона видимости, имеющая форму пирамиды для перспективного проецирования или параллелепипеда — для параллельного, преобразуется в куб, центр которого находится в начале координат, а ребра имеют фиксированную длину, составляющую 2 единицы. После выполнения этого преобразования вершины объекта будут представлены в системе координат отсечения (clip coordinates).
Рис. 7.3. Последовательность преобразований
И преобразование вида, и проективное преобразование представлены 4х4-матрицами однородных координат. Если преобразования реализованы в графической системе аппаратно, то для умножения матрицы размером 4x4 на четырехмерный вектор координат вершины используется специализированная микросхема, выполняющая эту операцию за то же время, за которое выполняется перемножение двух действительных чисел.
Следующее преобразование— отсечение (clipping?. При выполнении этой процедуры определяется, попадает ли объект или его часть в зону видимости, заданную в прикладной программе. Если компонент w вектора координат вершины не равен 1, нужно выполнить перспективное деление и перейти от однородных координат к обычным трехмерным. После завершения процедуры остаются только примитивы, которые потенциально могут быть включены в изображение (мы еще не выполняли удаления невидимых поверхностей), т.е. те, которые расположены внутри куба с гранями:
х=±1, у=±1, z=±l.
Сформированная система координат получила название нормализованной системы координат устройства отображения (normalized device coordinates), поскольку она не зависит ни от исходных единиц описания геометрических объектов, ни от единиц растра конкретного устройства отображения, хотя вся необходимая для корректного отображения информация и сохраняется в созданном описании. Обращаю ваше внимание на то, что на этом этапе собственно проецирование еще не завершено — мы по-прежнему имеем дело с трехмерным, а не двухмерным описанием. Остается выполнить заключительную процедуру ортогонального проецирования:
хР=х, Ур^Уг zp=0.
Мы отложили выполнение ортогонального проецирования, поскольку для удаления невидимых поверхностей нам еще понадобится информация о глубине, которая содержится в третьем компоненте представления точки.
Последняя процедура в описываемой последовательности преобразует описания вершин в систему координат экрана (screen coordinates). Сформированная проекция объекта должна появиться на экране в заданном видовом окне (viewport). В OpenGL это преобразование выполняется уже после завершения процедуры проецирования и является, таким образом, двухмерным. В результате предыдущих преобразований зона видимости была нормализована таким образом, чтобы ее стороны соответствовали сторонам видового окна (рис. 7.4), поэтому преобразование выполняется довольно просто:Последняя процедура в описываемой последовательности преобразует описания вершин в систему координат экрана (screen coordinates). Сформированная проекция объекта должна появиться на экране в заданном видовом окне (viewport). В OpenGL это преобразование выполняется уже после завершения процедуры проецирования и является, таким образом, двухмерным. В результате предыдущих преобразований зона видимости была нормализована таким образом, чтобы ее стороны соответствовали сторонам видового окна (рис. 7.4), поэтому преобразование выполняется довольно просто:(х . , у . ) ' min' 'min'Рис 7.4. Преобразование в систему координатэкрана(х . , у . ) ' min' 'min'Рис 7.4. Преобразование в систему координатэкрана
Последняя процедура в описываемой последовательности преобразует описания вершин в систему координат экрана (screen coordinates). Сформированная проекция объекта должна появиться на экране в заданном видовом окне (viewport). В OpenGL это преобразование выполняется уже после завершения процедуры проецирования и является, таким образом, двухмерным. В результате предыдущих преобразований зона видимости была нормализована таким образом, чтобы ее стороны соответствовали сторонам видового окна (рис. 7.4), поэтому преобразование выполняется довольно просто:
Последняя процедура в описываемой последовательности преобразует описания вершин в систему координат экрана (screen coordinates). Сформированная проекция объекта должна появиться на экране в заданном видовом окне (viewport). В OpenGL это преобразование выполняется уже после завершения процедуры проецирования и является, таким образом, двухмерным. В результате предыдущих преобразований зона видимости была нормализована таким образом, чтобы ее стороны соответствовали сторонам видового окна (рис. 7.4), поэтому преобразование выполняется довольно просто:
(х . , у . ) ' min' 'min'
Рис 7.4. Преобразование в систему координат
экрана
(х . , у . ) ' min' 'min'
Рис 7.4. Преобразование в систему координат
экрана
,) vmax ^ymin•^max ^min,) vmax ^ymin•^max ^minV = V + ( v - V J vnun \ Z J nun /УтахУminV = V + ( v - V J vnun \ Z J nun /УтахУminВ некоторых API поддерживаются трехмерные видовые окна, причем завершающий этап проективного преобразования выполняется уже после всех прочих. В этом случае к двум приведенным выше уравнениям необходимо добавить третье:В некоторых API поддерживаются трехмерные видовые окна, причем завершающий этап проективного преобразования выполняется уже после всех прочих. В этом случае к двум приведенным выше уравнениям необходимо добавить третье:+ (z-+ (z-т ^ ^ч-nutx^vmin‘•max‘•minт ^ ^ч-nutx^vmin‘•max‘•min2B литературе, особенно переводной, иногда для обозначения этой процедуры используется термин ‘кадрирование", —Прим, перев.2B литературе, особенно переводной, иногда для обозначения этой процедуры используется термин ‘кадрирование", —Прим, перев.7.2. Реализация геометрических преобразований7.2. Реализация геометрических преобразований293293
,) vmax ^ymin
•^max ^min
,) vmax ^ymin
•^max ^min
V = V + ( v - V J vnun \ Z J nun /
УтахУmin
V = V + ( v - V J vnun \ Z J nun /
УтахУmin
В некоторых API поддерживаются трехмерные видовые окна, причем завершающий этап проективного преобразования выполняется уже после всех прочих. В этом случае к двум приведенным выше уравнениям необходимо добавить третье:
В некоторых API поддерживаются трехмерные видовые окна, причем завершающий этап проективного преобразования выполняется уже после всех прочих. В этом случае к двум приведенным выше уравнениям необходимо добавить третье:
+ (z-
+ (z-
т ^ ^ч-nutx^vmin
‘•max‘•min
т ^ ^ч-nutx^vmin
‘•max‘•min
2B литературе, особенно переводной, иногда для обозначения этой процедуры используется термин ‘кадрирование", —Прим, перев.
2B литературе, особенно переводной, иногда для обозначения этой процедуры используется термин ‘кадрирование", —Прим, перев.
7.2. Реализация геометрических преобразований
7.2. Реализация геометрических преобразований
293
293
Отсечение отрезков
Модуль отсечения анализирует, какие примитивы или части примитивов будут включены в изображение. Примитивы, которые попадают внутрь зоны видимости, модуль отсечения пропускает дольше, а остальные отбрасывает. Возможен и третий вариант— примитив частично попадает в зону видимости. При обработке таких примитивов модуль отсечения разделяет их на части и пропускает дальше только ту из них, которая находится в зоне видимости.
В процессе конвейерной обработки процедура отсечения может выполняться поэтапно на разных стадиях. Например, модуль моделирования может взять на себя часть работы, сразу же отбросив те объекты, которые несомненно не попадут в зону видимости, и тем самым избавить остальные модули от лишних операций. Отсечение можно выполнять и после проективного преобразования, когда из трехмерного описания объектов будет сформировано двухмерное. В OpenGL можно выполнять отсечение на уровне трехмерной зоны видимости еше до того, как система приступит к проективному преобразованию. Ниже мы рассмотрим несколько алгоритмов отсечения. Начнем с двухмерных алгоритмов отсечения отрезков не только потому, чтобы отдать дань пионерам компьютерной графики, но и из педагогических соображений— поняв идею двухмерного отсечения, значительно проще разобраться и в трехмерных алгоритмах.
Алгоритм Коэна-Сазерленда
Суть задачи отсечения двухмерных отрезков поясняется на рис. 7.5. Предположим, что
уже выполнено проецирование и в нашем распоряжении имеется двухмерное описание изображения в картинной плоскости. На этой же плоскости определена и рамка отсечения, которая соответствует видовому окну на экране дисплея. Все параметры заданы вещественными числами. Как видно на рисунке, отрезок АВ полностью попадает на экран, а ни один из участков отрезка CD не попадает. Отрезки EF и GH нужно укоротить перед тем, как выводить на экран. Хотя отрезок прямой однозначно описывается координатами крайних точек, на примере отрезка GH вы можете убедиться, что, хотя крайние точки отрезка и лежат вне зоны
видимости, часть отрезка все-таки может попадать в эту зону.
Можно вычислить координаты точек пересечения прямой с рамкой видимости и использовать эту информацию для отсечения, но весь-то фокус в том и состоит, чтобы по возможности минимизировать объем вычислений и обойтись без определения точек пересечения, которое непременно включает операцию деления чисел с плавающей
Рис. 7.5. Двухмерное отсечениеточкой- Исторически первым, отвечающим этим
требованиям, был алгоритм Коэна-Сазерленда (Cohen-Sutherland algorithm), в котором большинство операций умножения и деления заменены операциями сложения и вычитания действительных чисел и побитовыми логическими операциями булевой алгебры.
Выполнение алгоритма начинается с продления сторон рамки отсечения в обе стороны до бесконечности, в результате чего картинная плоскость делится на девять областей (рис. 7.6). Каждой области присваивается четырехразрядный двоичный номер — характеристический код (outcode)— Ьфф2Ь3, который формируется следующим образом. Пусть (х,у)— координаты некоторой точки на картинной плоскости. Тогда
еслиу = утах, О-в противном случае.
100110011000100000010001000000001010 У = Утах 00101010 У = Утах 0010010101010100010001100110min maxmin maxРис. 7.6. Характеристические коды областей картинной плоскостиРис. 7.6. Характеристические коды областей картинной плоскостиАналогично, bt приравнивается 1, еслиу <ymin, а значения 62 и Ь) определяются отношением между компонентой х и абсциссами левой и правой границ рамки отсечения. В результате девяти областям присваиваются коды, представленные на рис. 7.6. При анализе отрезка первым делом определяется, в каких областях находятся его конечные точки, и им присваиваются соответствующие характеристические коды. Эта процедура требует выполнения восьми операций вычитания на каждый отрезок.Рассмотрим отрезок, конечные точки которого имеют хаАналогично, bt приравнивается 1, еслиу <ymin, а значения 62 и Ь) определяются отношением между компонентой х и абсциссами левой и правой границ рамки отсечения. В результате девяти областям присваиваются коды, представленные на рис. 7.6. При анализе отрезка первым делом определяется, в каких областях находятся его конечные точки, и им присваиваются соответствующие характеристические коды. Эта процедура требует выполнения восьми операций вычитания на каждый отрезок.Рассмотрим отрезок, конечные точки которого имеют ха
1001
1001
1000
1000
0001
0001
0000
0000
1010 У = Утах 0010
1010 У = Утах 0010
0101
0101
0100
0100
0110
0110
min max
min max
Рис. 7.6. Характеристические коды областей картинной плоскости
Рис. 7.6. Характеристические коды областей картинной плоскости
Аналогично, bt приравнивается 1, еслиу <ymin, а значения 62 и Ь) определяются отношением между компонентой х и абсциссами левой и правой границ рамки отсечения. В результате девяти областям присваиваются коды, представленные на рис. 7.6. При анализе отрезка первым делом определяется, в каких областях находятся его конечные точки, и им присваиваются соответствующие характеристические коды. Эта процедура требует выполнения восьми операций вычитания на каждый отрезок.
Рассмотрим отрезок, конечные точки которого имеют ха
Аналогично, bt приравнивается 1, еслиу <ymin, а значения 62 и Ь) определяются отношением между компонентой х и абсциссами левой и правой границ рамки отсечения. В результате девяти областям присваиваются коды, представленные на рис. 7.6. При анализе отрезка первым делом определяется, в каких областях находятся его конечные точки, и им присваиваются соответствующие характеристические коды. Эта процедура требует выполнения восьми операций вычитания на каждый отрезок.
Рассмотрим отрезок, конечные точки которого имеют ха
рактеристические коды О| = outcode^byO и о2 = outcode(x2, у2). Возможны четыре варианта сочетания характеристических кодов двух конечных точек (рис. 7.7).
(О| = о2 = 0.) Обе конечные точки лежат внутри рамки отсечения — этот случай представлен отрезком АВ на рис. 7.7. Весь отрезок при этом также находится внутри рамки отсечения и может быть передан дальше для выполнения растрового преобразования.
(О] * 0, о2 = 0 или наоборот.) Одна точка находится внутри рамки отсечения, а вторая — вне ее (отрезок CD на рис. 7.7). В этом случае отрезок необходимо разделить. Отличный от нуля характеристический код указывает, в какой внешней области находится одна из конечных точек и какая именно граница (или границы) рамки пересекается отрезком. В этом случае необходимо вычислить одну или в худшем варианте две точки пересечения отрезка с границами рамки. Обращаю ваше внимание на то, что после вычисления первой точки пересечения (например, с горизонтальной границей) можно сформировать ее характеристический код и выяснить, требуется ли определять вторую точку пересечения (на этот раз с вертикальной границей).
(О| & о2 * 0.) По результату побитовой операции AND над характеристическими кодами крайних точек можно выяснить, лежат ли они по одну сторону от границы рамки или по разные. Если результат отличен от нуля, то конечные точки лежат по одну сторону от какой-либо границы, а значит, весь отрезок лежит вне рамки отсечения и его можно спокойно отбросить (отрезок EF на рис. 7.7).
(О| & о2 = 0.) Обе конечные точки лежат вне рамки отсечения, но по разные стороны от двух ее границ. Этот вариант представлен отрезками GH и IJ на рис. 7.7, и мы не можем с уверенностью сказать, пересекает отрезок зону видимости или нет. Требуется более тщательный анализ — нужно вычислить точку пересечения с одной из границ рамки и проанализировать характеристические коды крайних точек двух новых отрезков.
Рис. 7.7. Варианты расположения отрезков, анализируемых алгоритмом Коэна- СазерлендаРис. 7.7. Варианты расположения отрезков, анализируемых алгоритмом Коэна- СазерлендаДля анализа характеристических кодов достаточно только булевых побитовых операций над двоичными числами, которые выполняются очень быстро. Вычисление точек пересечения выполняется чрезвычайно редко и только там, где без этой информации не обойтись, — во втором и четвертом вариантах сочетаний характеристических кодов.
Рис. 7.7. Варианты расположения отрезков, анализируемых алгоритмом Коэна- Сазерленда
Рис. 7.7. Варианты расположения отрезков, анализируемых алгоритмом Коэна- Сазерленда
изображение. В этом случае большинство отрезков отбрасывается в результате логическогоизображение. В этом случае большинство отрезков отбрасывается в результате логическогоАлгоритм Коэна-Сазерленда прекрасно работает при анализе сцен, содержащих множество прямолинейных отрезков, немногие из которых действительно попадают в формируемое
изображение. В этом случае большинство отрезков отбрасывается в результате логического
изображение. В этом случае большинство отрезков отбрасывается в результате логического
анализа характеристических кодов конечных точек, что не занимает много времени. Алгоритм без труда можно распространить и на трехмерный случай.
Но мы не рассмотрели, как вычислять точки пересечения отрезка с границами рамки. Какой из возможных методов предпочесть, зависит от формы представления отрезка в программе, но в любом случае потребуется, как минимум, одна операция деления действительных чисел. Если используется стандартная явная форма представления прямой
у = тх + И,
где т — коэффициент наклона прямой, a h — ордината точки пересечения с осью у, то значения т и h можно вычислить по известным координатам крайних точек. Но в такой форме нельзя представить вертикальную линию — известный всем недостаток явной формы представления. Если необходимо вычислять точки пересечения только в процессе реализации алгоритма Коэна-Сазерленда, то соответствующая функция будет довольно простой, поскольку отрезок всегда пересекается линией, параллельной одной из координатных осей. Но, скорее всего, нам потребуется функция, которая бы определяла точку пересечения прямых произвольной ориентации. Поэтому лучше ориентироваться на представление прямой в параметрической форме. Этот способ представления прямой рассматривался в главе 4, а применительно к кривым и поверхностям мы рассмотрим его в главе 10.
Алгоритм Лианга-Барского
В алгоритме Лианга-Барского (Liang-Barsky) используется параметрическая форма представления прямой. Пусть отрезок задан двумя крайними точками pi = [th^i]7 и р2 = [х2,^2] Т- Тогда параметрические уравнения прямой примут вид
х(а) = (l-a)xi + ах2,
Яа) = (1-а)Т1 + «ут-
Переходя к матричной форме записи, получим:
р(а) = (1-а)р] + ар2.
Параметрические уравнения такого вида описывают прямую любой ориентации, не делая исключений и для вертикальной или горизонтальной. При изменении значения параметра а от 0 до 1 точка на прямой перемещается от конечной точки отрезка pi до другой конечной точки р2. Отрицательные значения параметра а задают точки, расположенные на продолжении отрезка перед конечной точкой рь а значения о > 1 — точки, расположенные на продолжении отрезка после конечной точки р2.
Рассмотрим прямую и отрезки на ней, представленные на рис. 7.8,а. Если прямая не параллельна одной из границ рамки (а такая ситуация очень просто выявляется), то она пересекает продолженные в бесконечность в обе стороны границы рамки в четырех точках, причем точкам пересечения соответствуют четыре значения параметра: оь а2, а3, оц. Значение О) соответствует точке пересечения отрезка с продолжением нижней горизонтальной границы, а2 — левой вертикальной границы, о3 — верхней горизонтальной границы, а оц — правой вертикальной границы. Если отрезок пересекает рамку видимости, то одно из этих значений соответствует точке, в которой отрезок “входит” в зону видимости, а другое — точке, в которой отрезок “покидает” зону. Не будем сейчас останавливаться на том, как определяются значения параметра в точках пересечения, а обратим внимание на порядок, в котором располагаются значения параметров для точек пересечения с горизонтальными и вертикальными границами. Для варианта, представленного на рис. 7.8,а, порядок будет следующим:
1 > 0С4 > а3 > а2 > а, > 0.
Все четыре точки пересечения лежат на отрезке, причем значения параметров для двух из них — а2 и «з — задают крайние точки того участка отрезка, который попадает в зону видимости. Теперь рассмотрим случай, представленный на рис. 7.8,6. При таком расположении анализируемого отрезка имеем следующий порядок значений параметров в точках пересечения с продолжением границ рамки:
1 > 0С4 > а2 > а3 > а] > 0.
Отрезок сначала пересекает нижнюю и верхнюю границы, а затем — левую и правую. Поэтому параметры о2 и о3 поменялись местами по сравнению со случаем, представленным на рис. 7.8,а. Аналогично можно проанализировать и другие варианты размещения анализируемого отрезка относительно рамки видимости.
Рис. 7.8. Два варианта размещения отрезка относительно рамки видимости
Для эффективной реализации такой стратегии нужно разработать алгоритм, который минимизировал бы количество операций с числами в формате с плавающей точкой, необходимых для определения точек пересечения. Множество отрезков можно отсеять, не вычисляя все четыре точки пересечения. При определении точек пересечения также желательно не использовать, где это возможно, операции деления действительных чисел. Значение параметра для точки пересечения с верхней границей рамки находится из соотношения
' У2 - У.
Аналогичные соотношения имеют место и для точек пересечения с тремя остальными границами рамки. Приведенное соотношение можно представить в виде
^3 ДУ ДУтах,
где
^У=У2-У\.
ДУтах ~ Утах—У\ •
Практически весь логический анализ можно выполнить в терминах Дутах и Ду и аналогичных им для других границ рамки видимости и таким образом избавиться от большинства операций деления. Они понадобятся только в тех случаях, когда окажется, что отрезок пересекает зону видимости и что нужно определить точные значения параметров в точках пересечения границ. Преимущество описанного алгоритма по сравнению с алгоритмом Коэна-Сазерленда состоит в том, что при пересечении зоны видимости можно сразу определить участок, попадающий внутрь зоны, и избежать повторного анализа.
Существует и множество других алгоритмов отсечения отрезков на картинной плоскости, но в отличие от рассмотренных их не удается распространить на трехмерный случай.
Отсечение многоугольников
Необходимость в отсечении многоугольников возникает в разных ситуациях. Естественно, что отсечение необходимо выполнять перед или во время растрового преобразования, учитывая заданные размеры области экрана, отводимой для изображения. При этом иногда желательно, чтобы рамка отсечения принимала форму, отличную от прямоугольной. При формировании теней и удалении невидимых поверхностей возникает необходимость выполнить отсечение одного многоугольника другим многоугольником произвольной формы. Например, на рис. 7.9 показана тень, сформированная отсечением многоугольника, ближайшего к источнику света, другим, более удаленным от источника многоугольником. Методы сглаживания границ и наложения, которые будут рассмотрены в главе 9, также используют отсечение одного многоугольника другим.
Можно разработать алгоритм отсечения многоугольников, взяв за основу алгоритм отсечения отрезков и применив его последовательно ко всем ребрам многоугольника. Но при этом нужно не забывать, что многоугольник представляет собой единый объект и что при его отсечении может быть сформировано несколько новых объектов-многоугольников.
Рис. 7.11. Слияние многоугольниковРис. 7.11. Слияние многоугольниковРассмотрим представленный на рис. 7.10, а многоугольник общего вида—“вогнутый” {concave) многоугольник. Если применить к нему отсечение прямоугольной рамкой, то получим три новых многоугольника (рис. 7.10,6). Но, к сожалению, реализовать модуль отсечения, который был бы способен из одного объекта сформировать несколько, довольно сложно. Поэтому чаще всего результат отсечения, подобный показанному на рис. 7.10,6, представляется в программе как единый многоугольник, для чего в модуле отсечения выполняется объединение фрагментов в единый объект (рис. 7.11). В таком многоугольнике некоторые ребра накладываются друг на друга, что иногда служит источником проблем при решении других задач.
Рис. 7.11. Слияние многоугольников
Рис. 7.11. Слияние многоугольников
Рис. 7.9. Формирование тени с Рис. 7.10. Отсечение .многоугольника помощью отсечения одно- общего вида: а — до отсечения; го многоугольника другимб — после отсечения
При отсечении одного выпуклого {convex) многоугольника другим выпуклым многоугольником, в частности прямоугольной рамкой зоны видимости, принципиально не может образоваться больше одного многоугольника, причем тоже выпуклого (см. упр. 7.3). Таким образом, в этом случае проблема “размножения” не возникает, и в большинстве графических систем либо разрешается формировать только выпуклые многоугольники, либо имеются средства рассечения {tessellation) многоугольника общего вида на выпуклые (рис. 7.12).
Для отсечения многоугольников прямоугольной рамкой можно использовать оба описанных выше алгоритма отсечения отрезков, применяя их в цикле по отношению к ребрам анализируемого многоугольника. В системах с конвейерной архитектурой более эффективным является другой подход, предложенный Сазерлендом (Sutherland) и Ходжменом (Hodgeman).
Рис. 7.12. Рассечение .многоугольника общего видаРис. 7.12. Рассечение .многоугольника общего вида
Рис. 7.12. Рассечение .многоугольника общего вида
Рис. 7.12. Рассечение .многоугольника общего вида
Модуль отсечения отрезка можно рассматривать как “черный ящик”, на вход которого подаются две пары координат конечных точек исходного отрезка, а на выходе формируются две пары координат конечных точек того участка этого отрезка, который лежит внутри зоны отсечения, или не формируется ничего, если отрезок не пересекает зону отсечения.
<хи yj(х2, У2)<хи yj(х2, У2)МодульотсеченияМодульотсечения(х3, у3)(Х4, У4)(х3, у3)(Х4, У4)
<хи yj
(х2, У2)
<хи yj
(х2, У2)
Модульотсечения
Модульотсечения
(х3, у3)
(Х4, У4)
(х3, у3)
(Х4, У4)
Рис. 7.13. Отсечение отрезка: а — точки разделения; б — модуль отсечения как “черный ящик”
Будем рассматривать рамку отсечения как объект, сформированный пересечением четырех бесконечных прямых, соответствующих верхней, нижней, правой и левой границам зоны видимости. Тогда и модуль отсечения отрезка можно представить как последовательность четырех более простых модулей (конвейер), каждый из которых имеет дело только с одной границей.
(хрУ,) (х2, у2)(хрУ,) (х2, у2)
(хрУ,) (х2, у2)
(хрУ,) (х2, у2)
б)
Рис 7.14. Отсечение верхней границей рамки: а — геометрия; б — модуль, реализующий операцию
Рассмотрим отсечение верхней границей рамки. Модуль, выполняющий эту операцию, получает на входе координаты крайних точек отрезка и значение утах в качестве параметра, задающего зону отсечения (рис. 7.14). Нетрудно показать, используя подобие треугольников
на рис. 7.15, что если отрезок пересекает границу, то координаты точек пересечения определяются уравнениями
■Хз = ^+(ут.х-у1)--—L, Уз - У.
Уз = Утах •
В результате модуль отсечения вернет одну из трех пар координат: {(ХьУ)),^,^)}, {(^УДО^тах)} ИЛИ {(Хз,^), (х2,^)}. По этой же схеме организуются и модули отсечения нижней, правой и левой границами рамки, только в соответствующих уравнениях используются другие параметры рамки, а координаты х и у могут меняться ролями. Каждый модуль работает независи-
Рис. 7.15. Пересечение отрезка с верхней границей рамкиРис. 7.15. Пересечение отрезка с верхней границей рамкимо, но целесообразнее организовать из них конвейер (рис. 7.16). Такой конвейер, реализованный аппаратно, будет корректно обрабатывать четыре вершины. На рис. 7.17 показано, как с помощью
Рис. 7.15. Пересечение отрезка с верхней границей рамки
Рис. 7.15. Пересечение отрезка с верхней границей рамки
такого аппаратного модуля реализуется отсечение многоугольника.
(х3, у3)(х3, у3)(хиУ1)(хиУ1)(х3, у3)
(х3, у3)
(х3, у3)
(хиУ1)
(хиУ1)
(х2, У2)_| (х2, у2)| (х5, У5). | (х5, у5)..) (х4, У4)
► Верх I ► Низ I ► Справа | ► Слева |—
Рис. 7.16. Объединение отдельных модулей в конвейер: а— геометрический смысл операции; б — структура конвейераРис. 7.16. Объединение отдельных модулей в конвейер: а— геометрический смысл операции; б — структура конвейераОтсечение" слева IОтсечение" слева IОтсечение " справаОтсечение " справа6)
Рис. 7.16. Объединение отдельных модулей в конвейер: а— геометрический смысл операции; б — структура конвейера
Рис. 7.16. Объединение отдельных модулей в конвейер: а— геометрический смысл операции; б — структура конвейера
Отсечение" слева I
Отсечение" слева I
Отсечение " справа
Отсечение " справа
Рис. 7.17. Отсечение многоугольника
Отсечение примитивов других типов
В главах 1-6 были рассмотрены программы, в которых все геометрические объекты формировались из прямолинейных отрезков и плоских многоугольников. Криволинейные объекты, которые мы еще будем подробно рассматривать в главе 10, в графических системах, как правило, аппроксимируются маленькими плоскими многоугольниками. В системах с конвейерной архитектурой вы практически всегда встретитесь с тем или иным вариантом модулей отсечения, ориентированных на работу с отрезками или плоскими многоугольниками. Но, тем не менее, встречаются ситуации, когда нужно выполнить отсечение геометрических объектов еще до того, как они “попадут” в аппаратный конвейер, или применить алгоритмы, оптимизированные с учетом специфики примитивов других типов.
Прямоугольные оболочки
Предположим, что нам нужно обрабатывать многоугольники общего вида, имеющие довольно много вершин (рис. 7.18,а). Для этого можно воспользоваться одним из алгоритмов отсечения, рассмотренных выше, и применить его для обработки каждого из ребер многоугольника. Однако очень часто оказывается, что весь многоугольник лежит вне зоны видимости и что все усилия, затраченные на анализ его многочисленных ребер, пропали впустую. Значительно сократить затраты поможет предварительный анализ, в котором используется прямоугольная оболочка {bounding box) сложного объекта, в нашем примере— многоугольника (рис. 7.18,а). Такая оболочка— это прямоугольник минимального размера со сторонами, параллельными границам рамки отсечения, в который вписывается анализируемый объект. Определение параметров прямоугольной оболочки требует только последовательного просмотра всех вершин многоугольника и выявления максимального и минимального значений координат х и у.
Рис. 7.19. Отсечение с применением прямоугольных оболочек объектовРис. 7.19. Отсечение с применением прямоугольных оболочек объектовДалее можно применить алгоритм отсечения к оболочке, что реализуется значительно быстрее, поскольку, во-первых, оболочка имеет только четыре стороны, а во-вторых, эти стороны параллельны границам рамки. Рассмотрим три варианта, представленные на рис. 7.19. Тот многоугольник, который находится выше рамки, сразу отбрасывается, поскольку нижняя сторона его оболочки находится выше верхней границы рамки. Многоугольник, который расположен внутри рамки отсечения, также не имеет смысла “запускать” на конвейер отсечения, поскольку его оболочка полностью умещается в зоне видимости. Процедуру отсечения нужно выполнять только по отношению к третьему многоугольнику, так как его оболочка частично перекрывает зону видимости. Прямоугольная оболочка может использоваться и в трехмерном пространстве, причем она формируется еще на этапе моделирования и сохраняется вместе с описанием объекта.
Рис. 7.19. Отсечение с применением прямоугольных оболочек объектов
Рис. 7.19. Отсечение с применением прямоугольных оболочек объектов
Рис. 7.18. Использование прямоугольной оболочки: а — многоугольник и рамка отсечения; б — многоугольник, прямоугольная оболочка и рамка отсечения
Кривые, поверхности и надписи
Поскольку существует огромное множество типов кривых и криволинейных поверхностей, которые можно задавать аналитически, разработать обобщенный алгоритм их отсечения не представляется возможным. Потенциальные сложности, которые возникают при попытке разработки такого алгоритма, можно продемонстрировать даже на примере двухмерных кривых (рис. 7.20). Вычислить точки пересечения квадратичной кривой с границами прямоугольной рамки можно сравнительно просто, хотя эта процедура и сложнее, чем при работе с прямолинейными отрезками. Но для более сложных кривых, особенно тех, которые имеют характер спирали, довольно сложно определить не только координаты точек пересечения, но даже их количество. Избежать этих сложностей помогает кусочно-линейная аппроксимация. Поможет и анализ прямоугольных оболочек криволинейных объектов, которые позволят сразу выделить те объекты, которые полностью попадают или не попадают в зону видимости.
Рис. 7.20. Отсечение кривыхРис. 7.20. Отсечение кривых
Рис. 7.20. Отсечение кривых
Рис. 7.20. Отсечение кривых
Технология обработки надписей значительно отличается в разных графических системах. Во многих системах пользователю предлагаются средства, позволяющие регулировать степень детализации при воспроизведении надписи. Здесь есть две крайности. В некоторых системах надпись запоминается в виде двоичного образа на уровне пикселей и накладывается на изображение аппаратно без всякой геометрической обработки, а отсечение выполняется уже на стадии занесения образа в буфер кадра. В других системах надпись имеет такой же статус, как и любой другой геометрический объект, и точно так же “запускается” на конвейер обработки. В системе OpenGL поддерживаются оба этих режима, причем прикладному программисту предоставляется возможность выбрать тот из них, который больше подходит для разрабатываемого приложения. Если выбирается режим растровых символов, то они передаются в буфер кадра, минуя все геометрические преобразования. Если же выбирается режим штриховых символов, то надписи из таких символов обрабатываются наравне с прочими геометрическими объектами. В других графических системах, в частности PHIGS и GKS, поддерживаются режимы работы с надписями, занимающие промежуточное положение между двумя описанными крайностями. В них текст является отдельным типом примитива, располагающим множеством атрибутов. Помимо атрибутов размера и цвета символов имеются и управляющие атрибуты, задающие режим поведения системы, если надпись частично отсекается границами зоны видимости, — можно частично воспроизводить такую надпись либо не выводить ее на экран вовсе.
Отсечение в буфере кадра
Конечно, выполнение отсечения можно и отложить до этапа растрового преобразования и заполнения буфера кадра, но целесообразнее каким-либо способом все-таки отсеять те объекты, которые явно не вписываются в заданную зону видимости, а на последнем этапе выполнять отсечение только тех из них, которые по самой своей природе являются растровыми, в частности блоков пикселей. Такие объекты мы рассмотрим в главе 9.
Отсечение в трехмерном пространстве
В трехмерном пространстве при выполнении отсечения анализируется положение объекта по отношению к пространственной зоне видимости, а не по отношению к двухмерной рамке. Наиболее просто рассмотренные двухмерные алгоритмы распространяются на трехмерное пространство в том случае, когда зона видимости имеет форму параллелепипеда, заданного соотношениями (рис. 7.21)
Рис. 7.21. Отсечение в трехмерном пространствеРис. 7.21. Отсечение в трехмерном пространстве
Рис. 7.21. Отсечение в трехмерном пространстве
Рис. 7.21. Отсечение в трехмерном пространстве
Рис. 7.22. Разбиение пространства на области при использовании трехмерной модификации алгоритма Коэна-СазерлендаРис. 7.22. Разбиение пространства на области при использовании трехмерной модификации алгоритма Коэна-Сазерленда
Рис. 7.22. Разбиение пространства на области при использовании трехмерной модификации алгоритма Коэна-Сазерленда
Рис. 7.22. Разбиение пространства на области при использовании трехмерной модификации алгоритма Коэна-Сазерленда
Три алгоритма отсечения (Коэна-Сазерленда, Лианга-Барского и Сазерленда-Ходжмена),
рассмотренные в предыдущих разделах, можно модифицировать применительно к трехмер
ному случаю. Несложно сформировать и трехмерные прямоугольные оболочки и использовать их на этапе предварительного анализа перед собственно отсечением. Модификация алгоритма Коэна-Сазерленда заключается в том, что вместо 4-разрядного характеристического кода используется б-разряд- ный. Один из дополнительных разрядов устанавливается в 1, если точка находится перед передней отсекающей плоскостью, а другой, если она находится за задней отсекающей плоскостью (рис. 7.22). Методика анализа и принятия решения в трехмерном случае ничем не отличается от двухмерного.
В модифицированный алгоритм Лианга-Барского нужно добавить уравнение
z(a) = (l-a)Z| + ас2
и использовать, таким образом, параметрическое уравнение прямой в трехмерном пространстве. В процессе анализа нужно рассматривать шесть точек
пересечения прямой с плоскостями, ограничивающими зону видимости, но использовать при этом ту же логику принятия решения. В конвейерную архитектуру модуля отсечения нужно добавить два дополнительных узла, анализирующих отсечение передней и задней плоскостя
ми зоны видимости.
Но главные изменения по сравнению с двухмерным случаем заключаются в том, что для вычисления точек пересечения сопоставляются прямая и плоскость, а не прямая и прямая. При вычислении точек пересечения используется параметрическое представление прямой и плоскости (рис. 7.23). Обозначив через п вектор нормали к плоскости, а через р0— точку, лежащую в этой плоскости, запишем систему уравнений, решением которой является значение параметра а для точки пересечения прямой с плоскостью:
р(а) = (1-а)р, + ар2, п • (р(а)-ро) = 0.
Решение этой системы имеет вид
а_п(р0-Р|)
п-(р2-Р|) "
Вычисление по этой формуле требует шести операций умножения действительных чисел и одной операции деления. Но следует учитывать, что приведенная формула справедлива для прямой и плоскостей общего вида. Стандартные же зоны видимости, например при ортогональном проецировании (рис. 7.24), имеют граничные плоскости, параллельные координатным, а вычисление точки пересечения с такой плоскостью упрощается и требует только одной операции деления, как и в двухмерном случае.
Рис. 7.23. Пересечение прямой с плоскостьюРис. 7.23. Пересечение прямой с плоскостьюРис. 7.24. Отсечение при ортогональном проецированииРис. 7.24. Отсечение при ортогональном проецировании
Рис. 7.23. Пересечение прямой с плоскостью
Рис. 7.23. Пересечение прямой с плоскостью
Рис. 7.24. Отсечение при ортогональном проецировании
Рис. 7.24. Отсечение при ортогональном проецировании
Z = ZmaxZ = ZmaxРис. 7.25. Отсечение при косоугольном проецированииРис. 7.25. Отсечение при косоугольном проецировании
Z = Zmax
Z = Zmax
Рис. 7.25. Отсечение при косоугольном проецировании
Рис. 7.25. Отсечение при косоугольном проецировании
Несколько сложнее обстоит дело при использовании косоугольного проецирования (рис. 7.25). В этом случае зона видимости уже не является правильным параллелепипедом, а
ее грани не параллельны координатным плоскостям. Вот тут-то нам и поможет нормализация
проецирования, которую мы рассматривали в главе 5. Эта процедура позволяет привести косоугольную проекцию к ортогональной, выполнив предыскажение исходного объекта. Преобразование скоса превращает косоугольный параллелепипед зоны видимости в правильный. На рис. 7.26,а представлен вид сверху на косоугольный параллелепипед видимости, внутри которого находится отображаемый объект — куб, до выполнения преобразования. Эти же объекты после выполнения нормализации показаны на рис. 7.26,6. Если с точки зрения проективного преобразования количество вычислений, необходимых для косоугольного проецирования или
нормализации и ортогонального проецирования, одно и то же, то отсечение в последнем случае выполняется гораздо проще. Именно поэтому следует отдать предпочтение использованию нормализации проецирования. Приведенный пример показывает, насколько важно ана-
лизировать отдельные этапы обработки геометрических объектов в совокупности, учитывая их взаимное влияние на общую производительность графической системы.
Рис. 7.26. Предыскажение зоны видимости при нормализации проецирования: а — вид сверху до выполнения предыскажения; б — вид сверху после преобразования скосаРис. 7.26. Предыскажение зоны видимости при нормализации проецирования: а — вид сверху до выполнения предыскажения; б — вид сверху после преобразования скосаКартинная плоскостьКартинная плоскостьИскаженный объект Новая зона видимостиИскаженный объект Новая зона видимости
Рис. 7.26. Предыскажение зоны видимости при нормализации проецирования: а — вид сверху до выполнения предыскажения; б — вид сверху после преобразования скоса
Рис. 7.26. Предыскажение зоны видимости при нормализации проецирования: а — вид сверху до выполнения предыскажения; б — вид сверху после преобразования скоса
Картинная плоскость
Картинная плоскость
Искаженный объект Новая зона видимости
Искаженный объект Новая зона видимости
Еще более вескими становятся аргументы в пользу выполнения нормализации при перспективном проецировании. Выполняя описанную в главе 5 процедуру перспективной нормализации, мы получаем в результате возможность использовать в процессе отсечения прямоугольную зону видимости, что, естественно, значительно упрощает вычисления.
В OpenGL существует возможность задавать дополнительные, произвольно ориентированные плоскости отсечения. В графической системе, которая обладает такими возможностями, приходится использовать обобщенную процедуру отсечения, реализованную программно, что, естественно, не лучшим образом сказывается на производительности системы в целом. (Бесплатный сыр бывает только в мышеловке.)
Удаление невидимых поверхностей
После того как вершины прошли через все этапы геометрических преобразований и процедуру отсечения, “на конвейере” остались только те геометрические объекты, которые потенциально могут попасть в формируемое изображение. Но прежде чем приступать к их растровому преобразованию, нужно решить еще одну задачу — удалить объекты, перекрываемые с точки зрения наблюдателя другими объектами. Ниже будут описаны методы решения этой задачи для сцен, состоящих из полигональных объектов. Такое ограничение вводится по той простой причине, что в большинстве существующих на сегодняшний день графических систем все объекты, в конце концов, аппроксимируются множеством плоских многоугольников. Отрезки прямых также обрабатываются подобными методами после их незначительной модификации (см. упр. 7.7).
Анализ существующих на сегодняшний день алгоритмов удаления невидимых поверхностей позволяет выявить существенную разницу между подходами, ориентированными на отображаемые объекты и формируемое изображение. Особую роль в реализации этой процедуры играет возможность использовать предысторию анализа, когда результаты, полученные на предыдущем шаге, учитываются в последующих.
Пусть сцена состоит из к трехмерных непрозрачных многоугольников, каждый из которых можно считать независимым объектом. Обобщенный подход, основанный на анализе пространства объектов, предполагает попарное сравнение положения всех объектов по отношению к наблюдателю (центру проецирования). Рассмотрим два таких объекта: многоугольники А и В. Существует четыре варианта их взаимного положения (рис. 7.27):
А полностью закрывает В — в изображение нужно включить только А;
В полностью закрывает А — в изображение нужно включить только В;
А и В не перекрываются — в изображение нужно включить оба объекта, А и В;
А и В частично перекрываются — нужно вычислить, какая часть перекрываемого многоугольника будет видима наблюдателю.
Рис. 7.27. Два .многоугольника: а— В частично закрывает А; б— А частично закрывает В; в — А и В не перекрываются; г — В полностью закрывает А
Для того чтобы оценить сложность процесса, будем считать, что анализ, какой вариант сочетания многоугольников имеет место для текущей пары, и вычисление видимой части многоугольника представляют собой единую операцию. Будем обрабатывать множество имеющихся в сцене объектов итеративно. Берем один из к многоугольников и попарно сравниваем его с каждым из оставшихся к-\ многоугольников. После выполнения этого цикла будет известно, какая часть выбранного многоугольника будет видима наблюдателю (если таковая вообще существует). Закончив с анализом одного многоугольника, перейдем к следующему, который точно так же будем попарно сравнивать с оставшимися к-2 многоугольниками. Такой итерационный цикл продолжается до тех пор, пока не останутся только два многоугольника, которые мы и сравним в последнем итерационном цикле. Несложно показать, что оценка сложности всего процесса имеет вид O^Y Следовательно, даже не вдаваясь в детали выполнения операций сравнения, можно прийти к выводу, что объектно-ориентированный подход лучше всего применять при отображении сцен, состоящих из относительно небольшого количества объектов.
Подход, основанный на анализе пространства изображения, имеет много общего с моделированием процесса визуализации с помощью приведения лучей (рис. 7.28). Рассмотрим луч, исходящий из центра проецирования и проходящий через определенный пиксель на картинной плоскости. Этот луч где-то пересекается с плоскостью каждого из к многоугольников. Мы можем вычислить координаты этих точек, выяснить, лежит ли точка пересечения во внутренней области многоугольника, и затем выбрать тот многоугольник, который первым пересекается этим лучом. Этот пиксель затем можно окрасить в цвет, который присвоен точке пересечения с выбранным многоугольником после выполнения тонирования. Если размер видового окна изображения пхт пикселей, то такая операция должна быть повторена птк раз, т.е. оценка сложности имеет вид О(к\ Полученная оценка является оценкой сверху, но в любом случае тот факт, что сложность процедуры при таком подходе пропорциональна количеству объектов, а не квадрату этого количества заставляет нас отдать предпочтение анализу видимости в пространстве изображения. Но следует учесть, что работа на уровне отдельных пикселей может привести к образованию зубцов на границах, разделяющих изображения отдельных объектов, что не свойственно алгоритмам, работающим в пространстве объектов.
Рис. 7.28. Удаление невидимых поверхностей в пространстве изображенияРис. 7.28. Удаление невидимых поверхностей в пространстве изображения
Рис. 7.28. Удаление невидимых поверхностей в пространстве изображения
Рис. 7.28. Удаление невидимых поверхностей в пространстве изображения
Удаление нелицевых граней
В главе 6 отмечалось, что в OpenGL можно задавать режим тонирования только внешних
граней многоугольников. Если сцена состоит только из выпуклых многогранников, то внутрен
ние грани многоугольников никогда не будут “показываться на глаза” наблюдателю. Но у такого выпуклого многогранника часть граней будет повернута от наблюдателя (так называемые нелицевые грани) и, следовательно, тоже не будет видна наблюдателю. В конце концов, нелицевые грани будут перекрыты другими, и это сможет выявить универсальный алгоритм удаления невидимых поверхностей, но можно облегчить ему работу, сразу же отбраковав нелицевые грани. Принцип отбраковки (culling) нелицевых граней поясняется на рис. 7.29. Внешняя сторона многоугольника будет видима наблюдателю
только в том случае, если нормаль к плоскости многоугольника __ _п ._
7г7Рис. 7.29. Анализ лицевой грани
будет направлена к наблюдателю. Обозначим через 0 угол между внешней нормалью плоскости многоугольника и направ
лением на наблюдателя, тогда многоугольник будет представлять лицевую грань объекта только в том случае, если -90° < 9 < 90°, или, что то же самое, если cos9 > 0.
Второе условие анализируется проще, поскольку знак косинуса легко определяется по скалярному произведению и условие можно сформулировать следующим образом:
п • v>0.
Можно еще упростить этот анализ, если учесть, что обычно он выполняется после преобразования в нормализованную систему координат устройства отображения. В этой системе координат все виды являются ортогональными, и, следовательно, направление на наблюдателя задается координатной осью z. Таким образом, в однородных координатах вектор v имеет вид
’О'
Если уравнение плоскости, в которой лежит многоугольник, имеет вид
ах + by + cz + d = О,
то для определения, является ли грань лицевой или нелицевой, нам потребуется только проверить знак коэффициента с. Такую проверку можно выполнить как программно, так и аппаратно. При использовании отбраковки нелицевых граней нужно только тщательно проанализировать, допустимо ли это делать с учетом специфики конкретного приложения. В графической системе OpenGL включение режима отбраковки нелицевых граней выполняется функцией glCullFace().
Алгоритм z-буфера
Одним из самых распространенных алгоритмов удаления невидимых поверхностей является алгоритм z-буфера. Алгоритм довольно просто реализуется как программно, так и аппаратно, прекрасно сочетается с конвейерной архитектурой графической системы и может выполняться со скоростью, соответствующей скорости обработки вершин остальными модулями конвейера. Хотя алгоритм отталкивается от пространства изображения, он включает цикл просмотра многоугольников, а не пикселей и может быть реализован в процессе растрового преобразования (этот процесс будет подробно рассмотрен в разделе 7.10).
Предположим, что выполняется растровое преобразование одного из двух многоугольников, показанных на рис. 7.30. Используя принятую в графической системе модель закрашивания, можно вычислить цвет точек пересечения с каждым из многоугольников луча, исходя
щие. 730. Алгоритм z-буферащие. 730. Алгоритм z-буферашего из центра проецирования и проходящего через заданный пиксель картинной плоскости. Кроме того, одновременно нужно определить, является ли данная точка пересечения видимой наблюдателю, — из двух анализируемых видимой будет только точка, ближайшая к наблюдателю. Следовательно, если преобразуется многоугольник В, его образ должен появиться на экране только в случае, если расстояние z2 меньше расстояния zt до многоугольника А. И наоборот, если преобразуется многоугольник А, то его цвет не должен никак повлиять на формируемый цвет пикселя и изображение этого многоугольника в этой области экрана не должно появиться. Но есть небольшая загвоздка в реализации этой простой идеи — обработка многоугольников выполняется последо
щие. 730. Алгоритм z-буфера
щие. 730. Алгоритм z-буфера
вательно, а потому, преобразуя в растр один многоугольник, мы не располагаем информацией о том, как по отношению к нему расположены другие. Решается эта проблема с помощью
промежуточного буфера, в который записывается информация о глубине размещения каждого
многоугольника.
Предположим, что в нашем распоряжении имеется буфер — назовем его z-буферо.м, — который имеет такую же организацию, как и буфер кадра, а разрядность каждой ячейки достаточна для хранения информации о глубине с приемлемой для качества изображения точностью. Например, если формат изображения 1024x1280 пикселей и расстояние в графической системе представляется действительным числом с однократной точностью, то в z-буфере должно быть 1024x1280 32-разрядных ячеек. Перед началом растрового преобразования объектов сцены в каждую ячейку заносится код, соответствующий максимальному расстоянию
от центра проецирования. Буфер кадра в исходном состоянии заполняется кодами цвета фона. В процессе растрового преобразования объектов каждая ячейка z-буфера содержит значение расстояния до ближайшего из обработанных ранее многоугольников вдоль проецирующего луча, проходящего через соответствующий пиксель пространства изображения.
Процесс заполнения z-буфера выглядит в первом приближении следующим образом. Все многоугольники в описании сцены последовательно, один за другим, подвергаются растровому преобразованию одним из методов, описанных в разделе 7.10. Для каждой точки на многоугольнике, которая проецируется на определенный пиксель, вычисляется расстояние до центра проецирования. Это расстояние сравнивается с уже хранящимся в той ячейке z-буфера, которая соответствует формируемому пикселю. Если расстояние до текущего многоугольника больше хранящегося в z-буфере, значит, ранее был обработан многоугольник, расположенный ближе к наблюдателю, который, таким образом, загораживает текущий. Следовательно, данная точка текущего многоугольника не будет видима и информацию о ее цвете не нужно заносить в буфер кадра. Если же вычисленное расстояние меньше того, что хранится в z-буфере, значит, текущий многоугольник сам загораживает те, что были обработаны ранее, и его код цвета должен заменить в буфере кадра код цвета, сформированный ранее. Одновременно вычисленное только что расстояние заносится в соответствующую ячейку z-буфера.
При работе с графической системой OpenGL в прикладной программе нужно перед формированием любого нового изображения в явном виде подать команду инициализации z-буфера.
Объем дополнительных вычислений при использовании алгоритма z-буфера относительно невелик, поскольку в нем можно использовать предысторию обработки. Когда выполняется растровое преобразование многоугольника, то точки многоугольника обрабатываются последовательно, строка за строкой (об этом мы подробно поговорим в разделе 7.10). Многоугольник расположен на плоскости, которая описывается уравнением
ах + by + cz + d = 0.
Рис. 7.31. Алгоритм z-буфера в приращенияхРис. 7.31. Алгоритм z-буфера в приращенияхПусть (xx,yx,zx) и ^х2,у2, z2)— две точки внутренней области многоугольника, а следовательно, они принадлежат этой плоскости. Если обозначить
Рис. 7.31. Алгоритм z-буфера в приращениях
Рис. 7.31. Алгоритм z-буфера в приращениях
Ах = х2—хх, ^у = у2-уъ Ar = z2-zv
то уравнение плоскости можно записать в приращениях:
а^х + b^y + c^z = 0.
В этом уравнении фигурируют координаты видового окна, и, следовательно, для каждой строки растра значение у постоянно. Условие Ау = 0 выполняется до тех пор, пока обрабатывается текущая строка, а по мере перемещения по строке от одного пикселя к
другому наращивается только значение х, причем значение Ах также постоянно. Таким образом, при переходе от одного пикселя к другому на той же самой строке растра выполняется соотношение
Az = -—Аг. а
Это значение нужно вычислить только один раз при обработке определенного многоугольника и далее использовать, пока обработка этого многоугольника не завершится.
Сортировка по глубине
Метод сортировки по глубине (depth sort) реализует подход к решению проблемы удаления невидимых поверхностей, основанный на анализе пространства объектов. Мы рассмотрим этот метод применительно к сценам, состоящим из плоских многоугольников, но его можно распространить и на другие классы объектов. Метод сортировки по глубине является вариантом еще более простого алгоритма, получившего название алгоритма маляра (painter's algorithm).
Предположим, что имеется набор многоугольников, отсортированных по расстоянию от них до наблюдателя. В примере, показанном на рис. 7.32,а, имеются два многоугольника. С точки зрения наблюдателя они выглядят так, как показано на рис. 7.32,6, — один многоугольник частично закрывает от наблюдателя второй. Для корректного отображения такой сцены нужно определить, какая часть дальнего многоугольника перекрыта тем, что расположен ближе, “вырезать” ее, а оставшуюся часть подвергнуть растровому преобразованию и вывести на экран. Но можно поступить и по-другому — некоторые считают, что именно так делает маляр, нанося новый рисунок поверх старого. Образы объектов можно формировать в порядке от наиболее удаленного к менее удаленным. В результате образы объектов, расположенных ближе к наблюдателю, автоматически перекроют образы объектов, расположенных дальше, и необходимость в каком-либо “искусственном” анализе перекрытия полностью отпадет. Для такого метода придуман даже специальный термин — отображение многоугольников от дальнего к ближнему (back-to-front rendering/. Но реализация описанного подхода требует решения двух проблем: как рассортировать многоугольники в пространстве сцены и что делать, если многоугольники пересекаются. Обе эти проблемы решаются методом сортировки по глубине, хотя для некоторых ситуаций можно разработать и более эффективные методы (см. упр. 7.10).
Рис. 7.32. Алгоритм маляра: а — два многоугольника и наблюдатель; б — многоугольник А частично закрывает от наблюдателя многоугольник В
Предположим, что для каждого многоугольника уже вычислены параметры прямоугольной оболочки. Следующая операция — отсортировать все многоугольники по степени
8 Некоторые прикладные проблемы можно решить обратным методом — отображением многоугольников от ближнего к дальнему <front-to-back rendering). Эту тему мы затронем в главе 12.
удаления от наблюдателя, причем в качестве основного параметра сортировки берется максимальное значение координаты z-оболочки. Именно эта операция и дала название всему алгоритму— сортировка по глубине (depth sort). Предположим, что после сортировки многоугольники “выстроились” в порядке, показанном на рис. 7.33. По оси ординат на этом рисунке отложены значения параметров z-оболочек. Если минимальная глубина — значение -mm — для данного многоугольника больше, чем максимальная глубина следующего за ним многоугольника, то после формирования их образов на экране в обратном порядке будет получено вполне корректное изображение сцены. Например, многоугольник А на рис. 7.33 расположен позади всех остальных, и его образ следует формировать первым. Но, к сожалению, в других случаях дело обстоит гораздо сложнее, и одними значениями
параметров с-оболочек не обойтись.
Если z-оболочки двух многоугольников перекрываются, то порядок формирования их образов выявляется индивидуальным сравнением каждой пары таких объектов. В алгоритме сортировки по глубине для этого используется множество довольно сложных проверок. Рассмотрим пару многоугольников, z-оболочки которых перекрываются (рис. 7.34). Первой выполняется самая простая проверка— сравнение х- и у- оболочек (рис. 7.34,а). Если либо х-, либо у-оболочки не перекрываются, многоугольники не закрывают друг друга с точки зрения наблюдателя и порядок формирования их образов не имеет значения. Но и в случае, если х- или у-оболочки перекрываются, можно отыскать порядок формирования их образов, который позволит корректно воспроизвести сцену на экране. Такой случай показан на рис. 7.35. Все вершины одного
Рис. 7.33. z-оболочки сортируемых многоугольниковРис. 7.33. z-оболочки сортируемых многоугольниковмногоугольника лежат по одну сторону от плоскости
Рис. 7.33. z-оболочки сортируемых многоугольников
Рис. 7.33. z-оболочки сортируемых многоугольников
Рис. 7.34. Анализ перекрытия л- и у-оболочек многоугольников: а — х-оболочки не перекрываются; б — у-оболочки не перекрываютсяРис. 7.34. Анализ перекрытия л- и у-оболочек многоугольников: а — х-оболочки не перекрываются; б — у-оболочки не перекрываютсядругого, и выявить такую ситуацию позволит индивидуальный анализ всех вершин одного из многоугольников (см. упр. 7.12).
Рис. 7.34. Анализ перекрытия л- и у-оболочек многоугольников: а — х-оболочки не перекрываются; б — у-оболочки не перекрываются
Рис. 7.34. Анализ перекрытия л- и у-оболочек многоугольников: а — х-оболочки не перекрываются; б — у-оболочки не перекрываются
Но остаются еще две ситуации, с которыми справиться особенно трудно.
Во-первых, три многоугольника могут перекрывать друг друга циклически, как показано на рис. 7.36. В таком случае вообще не существует такого порядка формирования образов многоугольников, который позволил бы получить корректное изображение. Лучшее, что
можно предложить, — разделить по крайней мере один из них на две части и попытаться повторить анализ образовавшегося набора из четырех многоугольников.
Во-вторых, возможна ситуация, когда один многоугольник “пронзает” другой (или другие), как показано на рис. 7.37. В такой ситуации придется детально проанализировать пересечение, используя обобщенные методы отсечения одного многоугольника общего вида другим аналогичным многоугольником. Если пересекающиеся многоугольники имеют много вершин, можно попытаться использовать какие-либо искусственные методы, учитывающие специфику ситуации и требующие меньшего объема вычислений.
Оценить производительность метода сортировки по глубине очень сложно, поскольку многое зависит от специфики приложения. Например, работая с многоугольниками, представляющими собой грани сплошных объемных объектов, мы заранее знаем, что многоугольники не могут пересекаться. Но как бы там ни было, необходимость в предварительной сортировке явно указывает на то, что оценка производительности, как функции от сложности отображаемой сцены, не может быть линейной.
Рис. 7.35. Многоугольники с перекрывающимися оболочкамиРис. 7.35. Многоугольники с перекрывающимися оболочкамиРис. 7.36. Циклическое перекрытие многоугольниковРис. 7.36. Циклическое перекрытие многоугольниковРис. 7.37. Многоугольник, “пронзающий" кубРис. 7.37. Многоугольник, “пронзающий" куб
Рис. 7.35. Многоугольники с перекрывающимися оболочками
Рис. 7.35. Многоугольники с перекрывающимися оболочками
Рис. 7.36. Циклическое перекрытие многоугольников
Рис. 7.36. Циклическое перекрытие многоугольников
Рис. 7.37. Многоугольник, “пронзающий" куб
Рис. 7.37. Многоугольник, “пронзающий" куб
Алгоритм построчного сканирования
В те не столь уж давние времена, когда память стоила довольно дорого и о специализированных БИС для создания графических систем с конвейерной архитектурой только мечтали, метод построШьго сканирования считался наиболее эффективным алгоритмом удаления невидимых поверхностей. И по сей день отдельные идеи этого метода применяются в специализированных системах. Алгоритм построчного сканирования сочетает удаление невидимых поверхностей с растровым преобразованием. Хотя детальное обсуждение растрового преобразования еще ждет нас в последующих разделах, основные идеи этого алгоритма мы можем рассмотреть, воспользовавшись рис. 7.38. На этом чертеже вы видите два многоугольника, пересекающихся в пространстве. При растровом преобразовании многоугольников, которое выполняется последовательно, строка за строкой, можно воспользоваться методом вычисления глубины в приращениях, рассмотренным ранее. Но присмотревшись к этому чертежу внимательнее, можно отыскать еще более эффективный способ обработки. Перемещаясь вдоль строки растра i, мы пересекаем ребро а многоугольника А. Поскольку это первый многоугольник, который пересекается строкой, нет смысла выполнять какие-либо вычисления, анализирующие его глубину. Начиная с этого пикселя, последующим присваивается код цвета, соответствующий окраске многоугольника А. Но когда по мере перемещения по строке мы встретимся со следующим ребром этого многоугольника, Ь, то это будет означать, что с обработкой многоугольника А на этой строке покончено и можно присваивать последующим пикселям код цвета фона. Так будет продолжаться до тех пор, пока не встретится ребро с многоугольника В; поскольку перед этим на строке отображался фон, можно опять не при-
нимать во внимание информацию о глубине и считать дальнейший участок строки принадлежащим образу многоугольника В. Более сложная ситуация возникает на строке j. Сначала мы опять обнаруживаем ребро а и, не прибегая к анализу глубины, можем, начиная с этого пикселя, присваивать последующим код цвета, соответствующий окраске многоугольника А. Но далее встречается ребро с “конкурирующего" многоугольника, и здесь-то без анализа глубины не обойтись. Этот анализ придется проводить для всех пикселей строки j, пока не встретится ребро d, причем для анализа можно использовать описанный выше метод вычисления глубины в приращениях.
Рис. 7.38. Метод построчного сканированияРис. 7.38. Метод построчного сканированияХотя на первый взгляд кажется, что этот алгоритм во многом напоминает алгоритм z-буфера, между ними есть одно принципиальное отличие— алгоритм построчного сканирования при обработке каждого пикселя должен просматривать все многоугольники, претендующие на отображение в данной зоне экрана, а алгоритм z-буфера в каждый момент времени имеет дело только с одним многоугольником. Реализация алгоритма построчного сканирования требует тщательно продуманной организации данных о многоугольниках, которая позволит быстро отбирать среди них те, которые претендуют на отображение в текущей строке. Чаще всего структура данных базируется
Рис. 7.38. Метод построчного сканирования
Рис. 7.38. Метод построчного сканирования
на массиве указателей, по одному на каждую строку, каждый из которых указывает на специальную структуру описания ребер многогранников, пересекаемых этой строкой.
Растровое преобразование
Теперь перейдем к анализу методов выполнения завершающей стадии создания изображения — алгоритмам растрового преобразования примитивов и заполнения буфера кадра. Мы детально рассмотрим только два вида примитивов — отрезки и многоугольники, причем и те и другие заданы своими вершинами. Считаем, что все необходимые геометрические преобразования, в том числе и отсечение, уже выполнены и образы всех оставшихся примитивов должны включаться в конечное изображение сцены. Чтобы не отвлекаться, пренебрежем необходимостью удаления невидимых поверхностей и преобразуем каждый объект индивидуально, не используя информацию о его пр^транственных отношениях с другими объектами сцены. Будем также считать, что в резу;Ж'ате проективного преобразования все вершины представлены только двумя координатами, причем в системе координат экрана.
Рассмотрим такую организацию буфера кадра: массив из п х т элементов, каждый из которых соответствует одному пикселю, причем элемент с адресом в массиве (0, 0) соответствует пикселю, расположенному в левом нижнем углу экрана. Для установки определенного кода цвета пикселя используется единственная функция графической системы, формат вызова которой имеет вид write_pixel(int ix,int iy, int value);
Аргумент value может представлять собой либо индекс цвета в режиме индексного задания цвета, либо 32-разрядное число, представляющее цвет в формате RGB А. Адрес в буфере кадра, естественно, является дискретной величиной, но экранные координаты могут быть представлены и действительными числами. Мы вправе говорить о том, что точка имеет координаты на экране, например (63.4, 157.9), но центр ближайшего к ней пикселя находится в точке (63, 158) или (63.5, 157.5). В разных устройствах отображения пиксели могут иметь разную форму и размеры (об этом речь пойдет в разделе 7.11), но сейчас будем считать, что все они являются квадратиками, причем координатами пикселя является центр этого квадратика, а размер в точности
равен шагу размещения пикселей на экране10. Будем также считать, что извлечение информации из буфера кадра производится другим процессом параллельно с его заполнением модулем растрового преобразования. Такое предположение, которое справедливо для большинства графических систем, использующих так называемую двухпортовую память, избавляет нас от необходимости обращать внимание на детали вывода изображения из буфера на экран.
Простейший алгоритм растрового преобразования линейного отрезка известен как алгоритм ЦДЛ (DDA algorithm}. Этот алгоритм “позаимствован” у специализированных вычислителей — г^ифровых дифференциальных анатзаторов, — которые в прежние времена использовались для численного решения систем дифференциальных уравнений. Поскольку прямая описывается дифференциальным уравнением вида dy/dx = т, где т — коэффициент наклона, формирование прямолинейного отрезка есть не что иное, как решение этого простейшего уравнения численным методом.
Пусть отрезок задан крайними точками (Х],^|) и (х2,^2) (рис. 7.39). Будем считать, что значения координат округлены до ближайшего целого, т.е. отрезок начинается и заканчивается точно в центре соответствующего пикселя". Коэффициент наклона отрезка определяется соотношением
у, - у, Ду т = ——— = — .
х, - х, Дг
Будем считать, что 0 < т < 1. При других вариантах значения т можно использовать симметричную модификацию алгоритма. В основе алгоритма лежит запись кода цвета в ячейку буфера, представляющую определенный пиксель, для каждого значения ix с помощью функции write pixel, по мере того как х изменяется от xt до х2. Между элементарными приращениями &х и 5у координат отображаюгцей точки, которая “перемешается” от одной конечной точки отрезка к другой, существует связь:
5у = т &х.
Если на каждом шаге увеличивать значение х на 1, т.е. принять 8х = 1, то координату у отображающей точки нужно увеличить на величину 5у = т. Хотя каждое очередное значение х представляет целое число, очередное значение у таковым не является, поскольку т в общем случае — дробь. Поэтому значение у нужно округлить и найти таким образом подходящий пиксель, предсцвляющий текущее положение отображающей точки (рис. 7.40). В результате наш алгоритм будет выглядеть следующим образом: for(ix=xl, ix <= х2, ix++) {
y+=m;
write_pixel(x, round(y) , line_color);
}
Здесь функция round() выполняет округление действительного числа до ближайшего целого. Причина, по которой мы ограничили значение коэффициента наклона единицей, становится ясной при взгляде на рис. 7.41. Тот алгоритм, который мы сейчас рассматриваем, отыскивает значение координаты у отображающей точки, задаваясь приращениями ее координаты х. При больших значениях коэффициента наклона координаты х и у должны “поменяться
*°В OpenGL считается, что координаты центров пикселей представляют собой полусумму соседних целых чисел. В таком выборе есть определенный резон — см. упр. 7.19.
ролями” — задаваясь значениями координаты .у отображающей точки, будем определять подходящее значение координаты х (рис. 7.42). Заодно обращаю ваше внимание на то, что использование такой симметричной модификации алгоритма устраняет потенциальные проблемы с горизонтальными и вертикальными отрезками. Попытайтесь самостоятельно сформулировать варианты этого же алгоритма для отрицательных значений коэффициента наклона.
Рис. 7.39. Прямолинейный отрезок в системе координат экранаРис. 7.39. Прямолинейный отрезок в системе координат экранаРис. 7.40. Формирование пикселей при использовании алгоритма ЦДАРис. 7.40. Формирование пикселей при использовании алгоритма ЦДАния при больших значениях коэффициента наклонания при больших значениях коэффициента наклонаРис. 7.42. Симметричная модификация алгоритма ЦДАРис. 7.42. Симметричная модификация алгоритма ЦДА
Рис. 7.39. Прямолинейный отрезок в системе координат экрана
Рис. 7.39. Прямолинейный отрезок в системе координат экрана
Рис. 7.40. Формирование пикселей при использовании алгоритма ЦДА
Рис. 7.40. Формирование пикселей при использовании алгоритма ЦДА
ния при больших значениях коэффициента наклона
ния при больших значениях коэффициента наклона
Рис. 7.42. Симметричная модификация алгоритма ЦДА
Рис. 7.42. Симметричная модификация алгоритма ЦДА
Поскольку прямолинейный отрезок определяется вершинами, то при назначении кода цвета каждому очередному пикселю можно также использовать интерполяцию, но не пространственных координат, а “цветовых”. Кроме того, в процессе интерполяции отрезка можно, управляя цветом, накладывать на образ отрезка штрихи, точки и т.п. Все эти дополнительные эффекты не рассматриваются как часть алгоритма растрового преобразования, но часто реализуются синхронно с ним.
Алгоритм Брезенхэма
Описанный алгоритм ЦДА при всей его простоте и наглядности обладает одним недостатком— при формировании координат каждой отображающей точки необходимо выполнять сложение действительных чисел. Более простой целочисленный алгоритм предложил Брезенхэм (Bresenham) в 1965 году, и с тех пор этот алгоритм используется практически во всех графических системах, причем существуют как программные, так и аппаратные его реализации.
Как и при анализе алгоритма ЦДА, будем считать, что конечные точки отрезка представлены целочисленными координатами (хь.у]) и (х2,^2), а коэффициент наклона удовлетворяет условию 0 < т < 1.
Это условие достаточно критично для работоспособности алгоритма. Суть алгоритма в том, что при построении растрового образа отрезка выбирается пиксель, ближайший по вертикали к соответствующей прямой. Рассмотрим промежуточный шаг растрового преобразования отрезка после того, как будет сформирован пиксель (z + 1/2,у + 1/2) (рис. 7.43). Известно, что прямая описывается уравнением
у = тх + h.
При х = i + 1/2 прямая проходит от центра пикселя — точки с координатами (/ + 1/2, j + 1/2)— не далее, чем на 1/2 шага между пикселями. В противном случае образ прямой вследствие округления не прошел бы через этот пиксель. При переходе к следующей точке, для которой х = i + 3/2, учитывая ограничения, наложенные на значение коэффициента наклона, образ отрезка должен пройти либо через пиксель с центром в (z + 3/2,у + 1/2), либо через пиксель с центром в (z + 3/2,у + 3/2).
Проблема выбора решается с помощью характеристической переменной d = b-a, где а и b — суть расстояния между прямой в точке х = z + 3/2 и верхним и нижним пикселями- кандидатами (рис. 7.44). Если d отрицательно, прямая проходит ближе к центру нижнего пикселя и в качестве следующего пикселя образа отрезка выбирается (/ + 3/2,у+ 1/2); в противном случае выбирается пиксель с центром в (z + 3/2,у + 3/2). Можно было бы определить значение d, вычислив у = тх + Ь, но тогда теряется одно из заявленных достоинств алгоритма — использование только целочисленных операций, поскольку т является действительной дробью.
Рис. 7.44. Характеристическая переменная алгоритма БрезенхзмаРис. 7.44. Характеристическая переменная алгоритма Брезенхзма
Рис. 7.44. Характеристическая переменная алгоритма Брезенхзма
Рис. 7.44. Характеристическая переменная алгоритма Брезенхзма
Поэтому, во-первых, операции над числами с плавающей точкой заменяются операциями над числами с фиксированной точкой, а во-вторых, все вычисления выполняются в приращениях.
Изменим определение характеристической переменной. Будем использовать
d = (х2 -Х\ХЬ-а) = ^х(Ь-а).
Это изменение не влияет на выбор пикселей-кандидатов, поскольку для принятия решения используется только знак характеристической переменной, а он в новой формулировке будет таким же, как и в прежней. Подставляя в выражение d значения а и Ь, полученные из уравнения прямой, и принимая во внимание, что
х, - X] Дх
h = у 2 - тхг,
приходим к выводу, что d принимает только целочисленные значения. Итак, нам удалось избавиться от операций над действительными числами, но непосредственное вычисление d все же требует большого количества вычислений, хотя и целочисленных.
Поэтому несколько изменим подход. Пусть dk— значение d при х = к + 1/2. Попробуем вычислить d^ в приращениях относительно dk. Значение приращения будет зависеть от того, выполнялось ли на предыдущем шаге приращение координаты у пикселя. Возможные варианты представлены на рис. 7.45. Для параметров а и b — расстояний между прямой и пикселями-кандидатами — справедливы следующие соотношения:
если на предыдущем шаге произошло приращение у, то
«и\ = «к-т + 1,
Ьм = Ьк* т-1,
Ьк*\—ак+х = (Ьк-а^ + 2т-2;
в противном случае
«к+\ = ак-т,
Ьк+\ = Ьк + т,
Ьм-ак+х = (Ьк-ак) + 2т.
Умножив полученные выражения на Ах, получим, что при переходе к следующему значению х значение d изменяется либо на 2Ау, либо на 2(Ду-Ах). Этот результат можно записать в таком виде:
2Ду,если dk < 0;
2(Ду-Ах)-в противном случае.
В результате вычисление каждого очередного пикселя растрового образа отрезка потребует только одной операции сложения целых чисел и анализа знака. Алгоритм настолько эффективен, что в СБИС он реализован в виде одной инструкции. Метод вычисления исходного значения d0 я предлагаю читателям разработать самостоятельно (см. упр. 7.14).
Рис. 7.45. Приращение значений параметров а « bРис. 7.45. Приращение значений параметров а « b
Рис. 7.45. Приращение значений параметров а « b
Рис. 7.45. Приращение значений параметров а « b
Растровое преобразование многоугольников
Одним из главных преимуществ первых растровых систем отображения перед векторными была возможность формировать изображения областей со сплошной закраской. В те не столь уж давние времена было невозможно в реальном времени сформировать оттенки цвета отдельных пикселей области, а потому термин растровое преобразование многоугольника означал формирование его образа с заливкой внутренней области одним цветом. В отличие от растрового преобразования отрезков, которое практически во всех графических системах реализуется одним и тем же алгоритмом, для растрового преобразования многоугольников
существует множество методов, причем отдать предпочтение какому-либо одному довольно сложно. Выбор во многом зависит от архитектуры графической системы, и мы сосредоточим внимание на тех методах, которые совместимы с конвейерной архитектурой и способны поддерживать тонирование внутренней области многоугольника разными оттенками цвета. Обзор прочих методов будет сделан в разделах 7.10.4-7.10.6.
Тест принадлежности внутренней области
Внутренняя область простого плоского многоугольника определяется однозначно, если многоугольник выпуклый. Он корректно отображается в любой графической системе, в том числе и в OpenGL. Но иногда приходится включать в изображение и многоугольники общего вида, для которых с первого взгляда трудно сказать, что же именно следует считать его внутренней областью. Если все вершины многоугольника не лежат в одной плоскости, то приходится работать с его проекциями (см. раздел 7.10.2) или использовать для формирования внутренней области плоскость, определяемую первыми тремя вершинами этого многоугольника. Для многоугольников, имеющих пересекающиеся ребра (самопересекающихся многоугольников), нужно сформулировать правило принятия решения, как определять принадлежность точки внутренней области такого многоугольника. Концептуально задача заполнения внутренней области многоугольника определенным цветом или трафаретом сводится к задаче определения принадлежности точки его внутренней области.
Для решения этой задачи наиболее широкое распространение получил тест пересечения (crossing) или тест на четность-нечетность (odd-even test). Пусть р — точка внутри многоугольника. Любой луч, исходящий из точки р в бесконечность, должен пересечь нечетное количество ребер многоугольника. Любой луч, исходящий из точки, расположенной извне многоугольника, и пересекающий его, должен пересечься с четным количеством его ребер прежде, чем уйдет в бесконечность. Следовательно, можно так определить внутреннюю точку многоугольника: провести через эту точку прямую и, взяв в качестве исходной точку на этой прямой, заведомо расположенную извне многоугольника, двигаться вдоль прямой и подсчитывать количество пересечений с ребрами многоугольника до тех пор, пока не будет достигнута тестируемая точка. Если в этот момент окажется, что зафиксированное количество пересечений нечетно, тестируемая точка принадлежит внутренней области многоугольника, а в противном случае — внешней. На рис. 7.46 заливкой показаны внутренние области, найденные таким способом в многоугольнике с самопересекаюшимися ребрами в виде пятиконечной звезды.
Тест четности-нечетности легко встраивается в стандартные алгоритмы отображения, причем в качестве “испытательных” прямых используются строки растра.
Рис. 7.46. Закраска .многоугольника с самопересе- кающимися ребрами внутренних областей, выявленных тестом четности- нечетностиРис. 7.46. Закраска .многоугольника с самопересе- кающимися ребрами внутренних областей, выявленных тестом четности- нечетностиНо в некоторых случаях желательно закрасить звезду так, как показано на рис. 7АЗ. Это можно сделать с помощью теста охватывания (winding test). В этом тесте многоугольник рассматривается как своего рода узел из нескольких петель, завязанный вокруг точки или линии. Идея состоит в том, что в тестируемой точке помешается начало системы полярных координат. Далее рассматривается зондирующий луч, исходящий из этой точки, второй конец которого движется вдоль контура многоугольника (направление движения значения не имеет). В тесте фиксируется количество полных оборотов (winding number) W, которое совершит этот луч в процессе обхода
Рис. 7.46. Закраска .многоугольника с самопересе- кающимися ребрами внутренних областей, выявленных тестом четности- нечетности
Рис. 7.46. Закраска .многоугольника с самопересе- кающимися ребрами внутренних областей, выявленных тестом четности- нечетности
контура, причем оборот в направлении хода часовой стрелки учитывается положительным приращением значения W, а против часовой стрелки — отрицательным. На рис. 7.47 пока-
зано, как будет обходиться контур звезды при тестировании точки р. Если тестируемая точка находится вне многоугольника, зондирующий луч не совершит ни одного полного оборота вокруг нее при обходе контура многоугольника и для этой точки 1^= 0. Для тех точек внутри лучей звезды, которые залиты на рис. 7.46, W = 1, а для точки р на рис. 7.47,6 IV = 2. Таким образом, если считать внутренними точками многоугольника все точки, для которых IV > 0, то получим закраску, как на рис. 7.47,а.
Рис. 7.47. Сплошная закраска звездыРис. 7.48. S-образнаязамкнутая криваяРис. 7.47. Сплошная закраска звездыРис. 7.48. S-образнаязамкнутая криваяПри определении значения IV возникает ряд проблем. Рассмотрим S-образную замкнутую кривую на рис. 7.48, которую можно аппроксимировать многоугольником со множеством вершин. Не совсем ясно, как в этом случае определить “обматывание” для точек, расположенных внутри кривой. Можно модифицировать сформулированное ранее определение четности-нечетности, распространить его на количество оборотов и получить таким образом приемлемый способ измерения значения IV для произвольной точки. Рассмотрим произвольную прямую (зондирующую прямую), проходящую через внутреннюю точку р, как показано на рис. 7.47,6, причем эта прямая не должна быть параллельна ни одному из ребер многоугольника. Значение параметра IV для этой точки равно количеству ребер, которые пересекают зондирующую прямую в направлении сверху вниз, минус количество ребер, которые пересекают ее в направлении снизу вверх. Если IV не равно 0, то точка лежит внутри многоугольника. Обращаю ваше внимание на то, что для этого теста не имеет значения, что считается низом, а что верхом.
Рис. 7.47. Сплошная закраска звездыРис. 7.48. S-образная
замкнутая кривая
Рис. 7.47. Сплошная закраска звездыРис. 7.48. S-образная
замкнутая кривая
J'
Обработка многоугольников общего вида в OpenGL
OpenGL гарантирует корректное отображение только выпуклых многоугольников, а потому для отображения многоугольников общего вида приходится изобретать различные искусственные приемы. Один из подходов заключается в том, что прикладная программа должна самостоятельно следить за тем, чтобы все многоугольники в сцене удовлетворяли этому ограничению. Другой подход — разбить (tessellate) многоугольник общего вида на плоские выпуклые многоугольники, как правило, треугольники. Существует множество методов выполнения такого разбиения. Хорошим считается метод, который формирует не слишком длинные и узкие треугольники. Кроме того, желательно, чтобы после разбиения многоугольник можно было представить одним из составных примитивов, имеющихся в графической системе. В OpenGL для этого используются примитивы типа полоса из треугольников (triangle strips) и розетка из треугольников (triangle fans). Функция разбиения включена в состав библиотеки GLU. При обработке многоугольника без отверстий сначала создается новый объект разбиения (tessellator object), и в него передаются вершины исходного многоугольника:
mytess = gluNewTess();
gluTessBeginPolygon(mytess, NULL);
gluTessBeginContour(mytess);
for(i=0;i<nvertices;i++)
glTessVertex(mytess, vertex[i], vertex[i]);
gluTessEndContour();
gluTessEndPolygon(mytess);
Основная идея использования имеющихся в OpenGL средств разбиения состоит в том, что от прикладной программы требуется описать контур исходного многоугольника, a OpenGL самостоятельно сформирует его составные части и выполнит их заполнение в соответствии с заданными параметрами.
В алгоритмах разбиения, реализованных в OpenGL, используется описанный выше тест охватывания и формируемый в нем параметр W. В зависимости от того, как точка внутри многоугольника охватывается описанным контуром, OpenGL по-разному заполняет области внутри многоугольника при заданных параметрах разбиения (рис. 7.49). В приведенном в конце главы обзоре литературы читатель встретит указания на источники, в которых анализируются различные варианты алгоритмов разбиения. Что касается пользователей OpenGL, то для них обращение к имеющимся в этой системе средствам разбиения гарантирует, что все дальнейшие операции будут совершаться только над выпуклыми плоскими многоугольниками.
а)б)в)
Рис. 7.49. Разбиение многоугольника: а — направление обхода контуров; б и в — варианты разбиения
Растровое преобразование с использованием z-буфера
Теперь сведем воедино все, что было сказано в предыдущих разделах и главах об отображении многоугольников. Проследим, что происходит с многоугольником по мере его продвижения по конвейеру обработки. Сначала вершины и нормаль к плоскости многоугольника подвергаются геометрическим преобразованиям. На этом этапе не принимается во внимание, что вершины описывают именно многоугольник, — они обрабатываются как несвязанные геометрические объекты. При выполнении отсечения вершины уже связываются в многоугольник. Если многоугольник не отбрасывается в результате отсечения, он передается в модуль закрашивания и удаления невидимых поверхностей. На этой стадии, несмотря на выполнение нормализации проецирования, информация о глубине сохраняется. Если желательно использовать метод интерполяционного закрашивания, этой информации достаточно, чтобы вычислить освещенность каждой вершины многоугольника.
Остается решить три задачи: выполнить окончательное ортогональное проецирование, удалить невидимые поверхности и тонировать образ многоугольника. Тщательно продуманный алгоритм обработки z-буфера позволяет решать эти три задачи одновременно. Рассмотрим два варианта представления многоугольника, показанные на рис. 7.50. В левой части ри-
сунка многоугольник изображен в трехмерной нормализованной системе координат устройства отображения, а в правой — в двухмерной системе координат экрана.
Многоугольник обрабатывается построчно, и каждая строка соответствует отрезку на поверхности многоугольника, все точки которого имеют постоянное значение координаты у в нормализованной системе координат устройства отображения (рис. 7.51). Сканируя образ многоугольника в координатах экрана, мы перемещаемся на каждом шаге на один пиксель вправо, а использование нормализованных координат позволяет одновременно интерполировать параметр глубины очередной точки поверхности и анализировать ее видимость, используя информацию из z-буфера. Располагая данными об освещенности (цвете) каждой вершины обрабатываемого многоугольника, можно одновременно интерполировать и цвет точки поверхности. Каждая из описанных операций не требует громоздких вычислений, и все их можно выполнять в едином цикле сканирования. К описанным можно добавить и операции, необходимые для наложения текстур, о которых речь пойдет в главе 9.
Рис. 7.50. Дуальность представления многоугольника: а — нормализованная система координат устройства отображения; б — система координат экранаРис. 7.50. Дуальность представления многоугольника: а — нормализованная система координат устройства отображения; б — система координат экранаРис. 7.51. Строка растра в нормализованных координатах устройства отображения и в координатах экранаРис. 7.51. Строка растра в нормализованных координатах устройства отображения и в координатах экрана6)6)
Рис. 7.50. Дуальность представления многоугольника: а — нормализованная система координат устройства отображения; б — система координат экрана
Рис. 7.50. Дуальность представления многоугольника: а — нормализованная система координат устройства отображения; б — система координат экрана
Рис. 7.51. Строка растра в нормализованных координатах устройства отображения и в координатах экрана
Рис. 7.51. Строка растра в нормализованных координатах устройства отображения и в координатах экрана
6)
6)
Заполнение внутренней области и сортировка
Другой подход к растровому преобразованию многоугольников основан на идее отдельного блока обработки многоугольников — процессора многоугольников. Концептуально такой процессор представляет собой “черный ящик”, на вход которого подаются вершины множества двухмерных многоугольников, а выходом является корректно заполненный буфер кадра.
Рассмотрим частный случай — контур каждого многоугольника заполняется одним цветом. Это ограничение введено только для того, чтобы изложение идеи было более ясным. Сначала рассмотрим отдельный многоугольник. Основное правило заполнения его контура формулируется совершенно очевидно: если пиксель соответствует точке, которая находится внутри контура .многоугольника, ей должен быть присвоен цвет, назначенный .многоугольнику (цвет закраски). Из такой формулировки концепции следует, что закраска многоугольника сводится, в конце концов, к задаче сортировки — нужно рассортировать все пиксели в буфере кадра на те, которые принадлежат внутренней области многоугольника, и те, которые ей не принадлежат. С точки зрения этой концепции алгоритмы закраски многоугольников отличаются друг от друга способом сортировки точек. Мы рассмотрим три варианта алгоритмов:
заливка (floodfill);
закраска при построчном сканировании (scan-line fill);
оценка четности-нечетности перехода (odd-even fill).
Заливка
Образ контура многоугольника в буфере кадра можно сформировать, обрабатывая отдельные прямолинейные участки контура по алгоритму Брезенхэма. Предположим, что изображение состоит только из двух цветов: цвета фона (белый) и цвета переднего плана (черный). Образ контура многоугольника в буфере кадра формируется цветом переднего плана (рис. 7.52). Если известна какая-либо точка (х,у), лежащая внутри контура многоугольника— точка-источник (родник),— то окружающие ее точки можно просматривать рекурсивно и окрашивать их в цвет переднего плана, если точка не принадлежит ребру кон"тура. Алгоритм заливки можно представить на языке описа
ния алгоритмов (псевдокоде), подразумевая, что функция read_pixel () возвращает код цвета, ранее назначенный указанному пикселю следующим образом:
—————————— Рис. 7.52. Образ контура многоугольника
flood_fill(int x, int у) {
if(read_pixel(x,у)==WHITE) {
write_pixel(x,у,BLACK); flood_fill(x-l,y); flood_fill(x+l,y); flood_fill(x,y-l); flood_fill(x,y+l);
} }
Существует множество вариантов этого алгоритма, не использующих рекурсию. Один из таких способов предусматривает построчное сканирование.
Алгоритмы заполнения построчным сканированием
Потенциальное достоинство алгоритмов построчного сканирования состоит в том, что они позволяют при соответствующей реализации формировать коды засветки пикселей синхронно с их отображением на экране. Рассмотрим многоугольник на рис. 7.53, на котором
Рис. 7.53. Строка растра, пересекающая многоугольникРис. 7.53. Строка растра, пересекающая многоугольникпоказана одна строка растра. Пользуясь сформулированным ранее правилом четности-нечетности для определения принадлежности точки внутренней области многоугольника, среди пикселей анализируемой строки можно выделить три группы или участка (spans) таких пикселей. Обращаю ваше внимание на то, что для определения освещенности и глубины каждый участок можно обрабатывать независимо. Эта стратегия используется в некоторых вариантах аппаратной реализации алгоритма построчного сканирования, которые
Рис. 7.53. Строка растра, пересекающая многоугольник
Рис. 7.53. Строка растра, пересекающая многоугольник
предусматривают параллельную работу нескольких процессоров обработки участков. В рассматриваемом простом примере равномерной закраски внутренней области после выявления интересующих нас участков все входящие в него пиксели закрашиваются одним цветом.
Закрашиваемые участки строки определяются в результате анализа точек пересечения линии строки и контура многоугольника. Вся информация, необходимая для определения точек
пересечения, содержится в списке вершин, и порядок выявления этих точек зависит от порядка следования вершин в списке представления многоугольника. Рассмотрим, например, многоугольник, представленный упорядоченным списком вершин (рис. 7.54). Само собой напрашивается решение формировать точки пересечения строк растра с ребрами многоугольника в порядке описания ребер в списке вершин. Номера точек пересечения на рис. 7.54 показывают порядок их вычисления при последовательном просмотре ребер. Весь процесс вычислений можно сформулировать в приращениях (см. упр. 7.18). Но для выполнения закраски внутренней области многоугольника такой порядок следования точек пересечения не годится (или, по крайней мере, очень неудобен). Если в каждом цикле алгоритма нужно формировать участок закраски на одной строке, то необходимо рассортировать точки пересечения в порядке следования соответствующих строк, а затем — по значению координаты х на каждой строке, как показано на рис. 7.55. Очевидное решение — отсортировать список точек пересечения после того, как он будет сформирован в процессе последовательного анализа ребер, — не приемлем, поскольку при обработке многоугольника сложной формы (с “зубцами”) количество точек пересечения п может оказаться очень большим и сортировка займет много времени. Если, например, многоугольник пересекает примерно половину строк растра, то оценка сложности сортировки имеет вид О(п log«) и для реализации в реальном масштабе времени такой способ явно не годится.
Рис. 7.54. Порядок следования точек пересечения при последовательной обработке ребер многоугольникаРис. 7.54. Порядок следования точек пересечения при последовательной обработке ребер многоугольникаРис. 7.55. Желательный порядок следования точек пересеченияРис. 7.55. Желательный порядок следования точек пересечения
Рис. 7.54. Порядок следования точек пересечения при последовательной обработке ребер многоугольника
Рис. 7.54. Порядок следования точек пересечения при последовательной обработке ребер многоугольника
Рис. 7.55. Желательный порядок следования точек пересечения
Рис. 7.55. Желательный порядок следования точек пересечения
Рис 7.56. Структура данных, используемая при реализации у-х алгоритмаРис 7.56. Структура данных, используемая при реализации у-х алгоритма
Рис 7.56. Структура данных, используемая при реализации у-х алгоритма
Рис 7.56. Структура данных, используемая при реализации у-х алгоритма
Существует множество способов, позволяющих обойтись без глобальной сортировки списка точек пересечения. Один из них, получивший название у-х алгоритма (у-х algorithm), предусматривает формирование отдельного подсписка для каждой строки. По мере обработки
ребер многоугольника информация о каждой очередной точке пересечения помещается в соответствующий подсписок, причем процедура включения нового элемента в подсписок сразу выполняет его упорядочение по значению координаты х. В результате формируется структура данных, показанная схематически на рис. 7.56. Здесь вы видите еще один пример того, как продуманная организация структуры данных повышает скорость выполнения операций в графической системе.
Можно еще усовершенствовать алгоритм построчного сканирования, изменив, например, способ представления многоугольника. В результате придем к методу построчного сканирования, описанному в разделе 7.7.
Особые случаи
При распространении методов закраски внутренней области многоугольников на другие геометрические фигуры нужно не забывать о существовании особых случаев (см. упр. 7.17). Существенным преимуществом работы с многоугольниками является то, что положение их ребер точно известно. Но даже при работе с этими геометрическими фигурами могут возникнуть проблемы, если строка растра проходит через вершину. Рассмотрим два варианта такой ситуации (рис. 7.57). При использовании правила четности-нечетности эти два случая следует обрабатывать по-разному. В случае (а) точку пересечения строки с вершиной следует считать либо за нуль пересечений, либо за два пересечения. В случае (б) точка пересечения строки с вершиной должна учитываться в анализе четности-нечетности как одно пересечение.
Учесть эти особые случаи в алгоритме можно двумя способами.
/\,Первый вариант — при выявлении пересечения с вершиной анализиро-
\вать, какая из двух описанных ситуаций имеет место, и соответственно
у' модифицировать счетчик текущего количества пересечений. Другой вариант — обеспечить, чтобы значения координаты у вершин всегда были
°)дробными. Тогда вершина многоугольника никогда не будет лежать на
6)6)строке растра и особые ситуации просто не будут иметь места.
6)
6)
Рис. 7.57. Особые случаи: а — при анализе четности-нечетности пересечение с вершиной считается за нуль или за два пересечения; б — пересечение с вершиной считается за одно пересечение
Сглаживание ступенек на изображении линий
После растрового преобразования образы прямолинейных отрезков и ребер многоугольников выглядят как бы состоящими из мелких ступенек. Даже на экране ЭЛТ с разрешением выше 1024x1280 можно невооруженным глазом заметить такие искажения. Природа их кроется в самом принципе преобразования непрерывной функции в дискретную, которая представляется на сетке пикселей с конечным разрешением.
Появление ступенек объясняется тремя причинами. Во-первых, объем буфера кадра, в котором формируется дискретизированный образ графического объекта, конечен, и представление в нем прямолинейного отрезка в принципе не является взаимно однозначным, т.е. одному и тому же растровому образу может соответствовать множество исходных отрезков. Во-вторых, все пиксели имеют строго фиксированное положение на экране и образуют сетку с постоянным шагом. В-третьих, пиксели имеют фиксированный размер и форму.
На первый взгляд кажется, что, поскольку все эти причины органически присуши дискретному способу представления непрерывного сигнала, вряд ли что-нибудь удастся сделать и что отмеченные погрешности изображения неустранимы. Алгоритмы, подобные предложенному Брезенхэмом, оптимальны в том смысле, что позволяют определить на дискретной сетке позиции, ближайшие к исходной линии. Но если конструкция устройства отображения позволяет воспроизводить оттенки цветов или даже градации одного цвета, эффект ступенчатости изображения можно значительно ослабить. Как известно, линия как математический объект является одномерным многообразием— характеризуется только длиной, но не имеет ширины (толщины). В отличие от исходного математического объекта, его растровый образ имеет конечную ширину, иначе он просто не был бы различим визуально. Каждый пиксель, из которых этот образ состоит, имеет вполне определенные размеры — размер пикселя принят в наших рассужде-
ниях за некую метрическую единицу. Поэтому можно считать, что идеализированный образ математической прямой линии на экране имеет толщину 1 единицу (рис. 7.58). Вычертить на экране растрового устройства отображения такой идеальный образ прямой невозможно, поскольку он не состоит из отдельных квадратных пикселей. Алгоритм Брезенхэма можно считать способом приближенного представления этого идеального образа на сетке из реальных пикселей. Если присмотреться к идеальному образу прямой, то нетрудно подметить, что многие пиксели он перекрывает частично. В “чистом” виде алгоритм Брезенхэма предполагает, что при коэффициенте наклона, меньшем 1, за каждый шаг перемещения по оси х засвечивается точно один пиксель. Но можно поступить и по-другому — засвечивать и те пиксели, которые частично перекрываются идеальным образом линии, но задавать им не пол-
бражениябражениятивов других типов, в частности многоугольников.тивов других типов, в частности многоугольников.Рис. 7.59. Сглаживание изображения прямолинейного отрезка: а— несглаженное ступенчатое изображение; б — изображение, сглаженное усреднением по области; в — увеличенное несглаженное изображение; г —увеличенное сглаженное изображениеРис. 7.59. Сглаживание изображения прямолинейного отрезка: а— несглаженное ступенчатое изображение; б — изображение, сглаженное усреднением по области; в — увеличенное несглаженное изображение; г —увеличенное сглаженное изображениеную интенсивность, а частичную, приблизительно пропорциональную степени перекрытия пикселя. В результате сформируется сглаженное изображение прямой, примерно такое, как на рис. 7.59,6. Этот способ получил название сглаживание усреднением по области (antialiasing by area averaging). Для реализации такого способа необходимо выполнить операции, весьма схожие с теми, которые выполняются при отсечении многоугольника. Существуют и другие алгоритмы сглаживания ступеней на изображении, которые применяются при отображении прими
бражения
бражения
тивов других типов, в частности многоугольников.
тивов других типов, в частности многоугольников.
Рис. 7.59. Сглаживание изображения прямолинейного отрезка: а— несглаженное ступенчатое изображение; б — изображение, сглаженное усреднением по области; в — увеличенное несглаженное изображение; г —увеличенное сглаженное изображение
Рис. 7.59. Сглаживание изображения прямолинейного отрезка: а— несглаженное ступенчатое изображение; б — изображение, сглаженное усреднением по области; в — увеличенное несглаженное изображение; г —увеличенное сглаженное изображение
угольников в обугольников в области одного пикселяласти одного пикселя
угольников в об
угольников в об
ласти одного пикселя
ласти одного пикселя
Аналогичные проблемы с дискретизацией непрерывного изображения на сетке растра возникают и при использовании алгоритма z-буфера в простейшем варианте. При описании
алгоритма было сказано, что цвет пикселя определяется цветом единственного примитива. Но рассмотрим вариант, когда поле пикселя разделено между несколькими примитивами — тремя многоугольниками, как в случае, показанном на рис. 7.60. Если цвет каждого многоугольника отличается от цвета остальных “претендентов”, то пикселю присваивается цвет того из них, который ближе к наблюдателю. Но можно более точно передать сложившуюся ситуацию, если присвоить пикселю смешанный цвет, составляющие которого несут информацию о том, какую часть площади пикселя занимает каждый из треугольников-“претендентов”. В главе 9 будет рассмотрен алгоритм, который использует в такой ситуации накапливающий буфер и с его помощью реализует взвешенное усреднение.
Выше мы рассмотрели только эффект пространственной дискретизации. Но при формировании динамических изображений появляются и эффекты, связанные с дискретизацией во временной области.
Пусть, например, в последовательных кадрах динамического изображения образ объекта достаточно малого размера, сравнимого с размерами пикселя, перемещается перед “объективом”
Рис. 7.61. Эффект дискретизации во временной области при формировании динамических изображенийРис. 7.61. Эффект дискретизации во временной области при формировании динамических изображенийкамеры (рис. 7.61). Стандартная процедура тонирования предполагает анализ лучей, проходящих через центры пикселей, и определяет, на какой объект сцены этот луч “натолкнется” первым. При работе с таким малым объектом может оказаться, что в одном кадре анализируемый луч наталкивается на объект и что этот объект соответственно попадет в изображение, а в следующем кадре объект окажется между двумя соседними лучами, и ни один из них его “не заметит”. Тогда объект просто не попадет в следующий кадр. В результате пользователь увидит на экране движущуюся мерцающую точку. С этим явлением можно бороться по-разному. Один из вариан
Рис. 7.61. Эффект дискретизации во временной области при формировании динамических изображений
Рис. 7.61. Эффект дискретизации во временной области при формировании динамических изображений
тов — использовать несколько зондирующих лучей на каждый пиксель и коды засветки, сформированные в результате анализа совокупности лучей.
Резюмируя наш краткий обзор, можно с уверенностью сказать, что, в конце концов, любой метод преодоления эффектов, порождаемых дискретизацией, сводится к уменьшению тем или иным способом шага квантования и соответственно увеличению объема вычислений. Поэтому создание высококачественных изображений со сглаживанием ступенек выполняется в современных графических системах в пакетном режиме, когда можно позволить себе рос
кошь выполнять длительные вычисления.
Отображение информации
Разрабатывая прикладную программу, ориентированную на применение какой-либо из большинства существующих графических систем, программист, как правило, не думает о таких частностях, как вывод на экран образов, сформированных в буфере кадра. В растровых системах выводом на экран занимаются средства, реализующие алгоритм растрового преобразования, причем в большинстве специализированных графических станций для этого используется память с двойным доступом (так называемые двухпортовые запоминающие устройства — dual-ported memory). В этих устройствах процессы записи в буфер кадра и считывания из него разделены и выполняются независимо. Вывод изображения на экран выполняется аппаратно со скоростью, обеспечивающей требуемую частоту регенерации (в современных системах от 65 до 85 Гц), а от программиста, разрабатывающего прикладную программу, требуется только обеспечить заполнение буфера со скоростью, определяемой спецификой конкретного приложения. При создании динамических изображений часто применяется двойная буферизация, о которой уже шла речь в главе 3.
На качество формируемого изображения влияет довольно много факторов. Часто поработав с приложением в системе, которая не обеспечивает хорошего качества “картинки”, пользователь переносит свое недовольство на приложение в целом, хотя причина кроется, прежде всего, в подсистеме отображения. Например, две ЭЛТ с одним и тем же номинальным разрешением могут иметь разные размеры пикселей, что в итоге приводит к разному качеству изображения (см. упр. 7.22 и 7.23).
Цветовые системы
До сих пор при обсуждении вопросов, связанных с формированием цвета пикселей, мы все время молчаливо предполагали, что используется система аддитивных цветовых составляющих красного, зеленого и синего цветов (RGB-система). Это предположение, базирующееся на теории цветовосприятия человека, состоит в том, что три компонента цветового кода, который определяется для каждой точки тем или иным способом, соответствуют трем основным цветам спектра, о которых шла речь в главе 2. Однако между разными вариантами RGB-систем есть существенная разница. Например, в OpenGL желтый цвет представляется RGB-триадой (0.8, 0.6, 0.0). Если использовать эту триаду для управления компьютерным монитором на ЭЛТ и устройством вывода на пленку, то результаты будут значительно отличаться, хотя в обоих случаях задается один и тот же процентный состав интенсивности основных цветов. Дело в том, что два этих устройства характеризуются разной цветопередачей красителя, используемого в устройстве вывода на пленку, и люминофоров ЭЛТ.
Основная парадигма, одобренная сообществом разработчиков средств компьютерной графики, состоит в том, что прикладные программы должны быть независимы от аппаратных средств отображения. Поэтому в большинстве графических API никак не учитывается специфика тех или иных устройств отображения. Необходимую информацию об этой проблеме можно найти в специальной литературе, относящейся к колориметрии. Существуют стандарты для большинства существующих систем воспроизведения цвета. Например, электронно-лучевые трубки должны удовлетворять требованиям к RGB-системам отображения, сформулированным Национальным комитетом по телевизионным системам (NTSC — National Television Systems Committee). Отличие одной системы представления цветов от другой можно рассматривать как отличие в системах координат представления первичных цветов. Если считать, что Ci = [/?b Gb SJ7 и С2 = [/?2, G^B^1— два представления одного и того же цвета в разных системах координат, то должна существовать матрица преобразования цвета М размера 3x3, такая, что
с2 = мсь
Как бы ни были определены значения элементов этой матрицы — экспериментально или с помощью специальной литературы, — она позволит добиться единой цветовой гаммы формируемого изображения при использовании в системе разных устройств отображения.
На этом пути нас подстерегает множество “подводных каменей”. Во-первых, устройства отображения разного типа обладают отличной друг от друга цветовой гаммой. Поэтому, даже уравняв чувствительность по отдельным компонентам, можно не добиться в одной системе того же оттенка, что и в другой. Во-вторых, в полиграфии используется четырехцветная система дополняющих цветов (CMYK-система), в которую добавлен еще один первичный цвет— черный (К). Преобразование между RGB и СМУК часто требует особого искусства, которым обладают только эксперты в этой области. В-третьих, мы используем в своих рассуждениях допущение о линейности наложения первичных цветовых компонентов, в то время как в действительности эта функция нелинейна. Расстояние между точками разных цветов в цветовом кубе не передает точно воспринимаемое глазом человека отличие между этими же цветами. Например, человек воспринимает сильнее цвета, находящиеся в синей части спектра. Эти особенности восприятия цвета человеком учтены в цветовых системах YUV и Lab.
Большинство вариантов RGB-систем базируется на цветовых характеристиках реальных первичных компонентов — люминофоров в ЭЛТ и красителей в полиграфических системах. Ни один из существующих вариантов систем не способен воспроизвести всю гамму цветов, воспринимаемых человеком. Большинство цветовых стандартов основано на теории трех первичных цветов, известной среди специалистов под названием цветовой системы XYZ (XYZ color system). В этой системе компонент Y соответствует интенсивности цвета. В системе XYZ все цвета задаются положительными значениями первичных цветовых составляю-
щих. Для перехода от представления в системе XYZ к любой стандартной системе представления цвета используются ЗхЗ-матрицы. Специалисты в области колориметрии часто предпочитают работать с хроматическими координатами (chromaticity coordinates), а не с первичными цветовыми составляющими. Хроматические координаты определенного цвета представляются отношениями интенсивностей каждого из трех первичных цветов к их сумме. Если для какого-то цвета интенсивности первичных составляющих в RGB-представлении равны Тъ Т2 и 7"з, то хроматические координаты этого цвета будут иметь вид
Т.Т,Д
( =/ =5 f =2
т; + г2 + т; ’ 2 т; + т; + т; ’ 3 т; + т, + т;'
Суммируя эти три соотношения, получим:
/] + /2 + /3 = 1.
Из него следует, что мы можем работать с хроматическими координатами в двухмерном пространстве tb t2, поскольку третья координата /3 однозначно определяется двумя первыми и при необходимости ее всегда можно вычислить. При переходе от представления цвета в исходной системе к хроматическим координатам теряется информация о сумме первичных составляющих 7'] + 72 + Т'з, которая, в конце концов, представляет суммарную интенсивность цвета. В колориметрии она не так важна, поскольку главное в этой области — правильная передача оттенков и однозначность представления цветов, заданных в разных системах.
Поскольку в системе хроматических координат все дробные компоненты должны быть неотрицательными, т.е. должно соблюдаться ограничение
1 > t, > О,
то для любого цвета точка, представляющая его в координатах tx, t2, должна находиться внутри треугольника, изображенного на рис. 7.62. Кривая на рис. 7.63 ограничивает на плоскости fb t2 область цветов, воспринимаемых зрительной системой человека. На этом же рисунке показана область, представляющая цветовую гамму типичных цветных ЭЛТ и цветных принтеров. Из этого рисунка ясно, что существующие на сегодняшний день устройства отображения способны воспроизводить отнюдь не все цвета видимой части спектра.
Рис. 7.62. Треугольная область цветов, воспроизводимых в системе хроматических координат
Представление цвета в аддитивной системе RGB базируется на том, как цвет воспроизводится, а не на том, как он воспринимается. Субъективное восприятие человеком цвета принято характеризовать тремя взаимно связанными атрибутами — цветовым тоном, насыщенностью и светлотой. На этих атрибутах основана система HLS (по первым буквам их названий hue — цветовой тон, saturation — насыщенность, lightness — светлота), которая используется художниками и некоторыми изготовителями систем отображения. Цветовой тон (иногда его называют оттенком цвета) ассоциируется в человеческом сознании с обусловленностью окра
ски предмета, наличием в нем определенного пигмента краски или красителя — это название чистого цвета, например красный, желтый, золотистый и т.п. Светлота (lightness) характеризует степень, уровень, силу выражения цветового тона. Серые тона называются ахроматическими (бесцветными), и считается, что они не имеют светлоты, а различаются по насыщенности. Изменение насыщенности (saturation) хроматического цвета связывается в сознании с примешиванием к нему ахроматического цвета— белого или черного красителя. В результа
те получаются так называемые “грязные” цвета. Некоторые из них (коричневые, бурые) субъективно представляются самостоятельными цветами, хотя являются на самом деле грязными оранжевым и красным. Цветовой тон, насыщенность и светлота называются основными “качествами цвета”.
Рис. 7.64. Атрибуты HLS-системы представления цвета:а — связь между атрибутами HLS-системы и цветовым кубом в системе RGB; б — цветовой конус HLS-системыРис. 7.64. Атрибуты HLS-системы представления цвета:а — связь между атрибутами HLS-системы и цветовым кубом в системе RGB; б — цветовой конус HLS-системыРис. 7.63. Видимая часть спектра и цветовая гамма типичных устройств отображенияРис. 7.63. Видимая часть спектра и цветовая гамма типичных устройств отображенияМожно связать эти атрибуты качественно и количественно с координатами цвета в системе координат основных цветов RGB, как показано на рис. 7.64,а. Для некоторого цвета, представленного точкой в пространстве
Рис. 7.64. Атрибуты HLS-системы представления цвета:
а — связь между атрибутами HLS-системы и цветовым кубом в системе RGB; б — цветовой конус HLS-системы
Рис. 7.64. Атрибуты HLS-системы представления цвета:
а — связь между атрибутами HLS-системы и цветовым кубом в системе RGB; б — цветовой конус HLS-системы
Рис. 7.63. Видимая часть спектра и цветовая гамма типичных устройств отображения
Рис. 7.63. Видимая часть спектра и цветовая гамма типичных устройств отображения
цветового куба, светлота — есть мера удаления этой точки от начала координат (вершины куба, соответствующей черному цвету), т.е. параметр L на рис. 7.64,а. Цвета, точки представления которых находятся на главной диагонали цветового куба, являются ахроматическими — оттенками серого. Они отличаются только по светлоте, а насыщенность этих цветов равна нулю. Значение насыщенности хроматического цвета характеризует, насколько удалена представляющая его точка от главной диагонали цветового куба (параметр S на рис. 7.64,а).
Цветовой тон характеризует направление вектора, связывающего начало координат с точкой представления цвета, — параметр Н на рис. 7.64,а. Из всего сказанного следует, что HLS-систему представления цвета можно описать в терминах цветового конуса, показанного на рис. 7.64,6. С точки зрения системы компьютерной графики систему HLS можно рассматривать как представление RGB-цвета в полярной системе координат.
Гамма-коррекция
Рис. 7.65. Логарифмическая кривая яркостиРис. 7.65. Логарифмическая кривая яркостиЯркость характеризует восприятие наблюдателем интенсивности света, причем для зрительной системы человека это восприятие описывается логарифмической кривой (рис. 7.65). Из этого факта следует практический вывод, существенный для систем отображения: если необходимо сформировать шкалу нарастания яркости с постоянным шагом, то интенсивность засветки соответствующих пикселей экрана должна возрастать экспоненциально. Приращения интенсивности засветки участков шкалы можно вычислить, взяв за основу значения максимальной и минимальной интенсивностей, характеризующих данную систему отображения (см. упр. 7.25).
Рис. 7.65. Логарифмическая кривая яркости
Рис. 7.65. Логарифмическая кривая яркости
Кроме того, интенсивность / свечения участка экрана ЭЛТ связана с напряжением Г на управляющем электроде соотношением
log/= с0 + у log Г,
где константы у и с0 характеризуют конкретную ЭЛТ. В результате образ, сформированный в буфере кадра, при отображении на экранах разных мониторов будет иметь разную яркость. Один из способов
преодоления этого недостатка состоит в том, чтобы сформировать специальную таблицу, значения в которой настраиваются в соответствии с характеристиками конкретного монитора, и использовать эти значения при отображении содержимого буфера кадра. Этот механизм получил название гамма-коррекции (gamma correction). Структура таблицы гамма-коррекции будет рассмотрена в главе 9.
Формирование полутонов
Буфер кадра характеризуется пространственным разрешением — количеством пикселей, а также глубиной — количеством цветов, которые может содержать формируемый в нем образ. Если рассматривать обе эти характеристики как независимые, то можно считать, что чернобелый лазерный принтер с высоким разрешением способен воспроизводить изображение, состоящее только из одноразрядных пикселей. Рассуждая аналогичным образом, можно прийти к выводу, что в любой черно-белой среде, например в книге, нельзя воспроизвести изображения, в которых присутствовали бы градации серого, промежуточные между белым и черным. Но наш собственный жизненный опыт говорит, что это не так. Весь фокус в том, что при работе с подобной средой пространственное разрешение приносится в жертву способности воспроизводить градации серого. Технология воспроизведения полутонов в полиграфии основана на моделировании градаций серого цвета плотностью размещения точек, окрашенных “полновесным” черным цветом. Поскольку зрительная система человека усредняет точки малого размера, покрывающие некоторый участок изображения, то изменение плотности их размещения воспринимается как изменение уровня почернения, т.е. как промежуточная градация серого.
Метод цифрового формирования полутонов предполагает, что размещение и размер отображаемых пикселей фиксирован. Рассмотрим группу одноразрядных пикселей с размерами 4x4 (рис. 7.66). Если покрыть такими группами пикселей — трафаретом — участок изображения и смотреть на него издали, то мы увидим не отдельные пиксели, а участок серого цве-
та» причем степень почернения участка будет зависеть от соотношения между количеством белых и черных пикселей в трафарете. При размере трафарета 4x4 теоретически существует 216 разных вариантов трафаретов, но все эти варианты способны воспроизвести только 17 градаций серого, что соответствует изменению количества черных пикселей в группе от О (абсолютно белый цвет) до 16 (абсолютно черный цвет). Существует множество вариантов формирования полутонового изображения с помощью подобных трафаретов. Самый простой из них — выбрать 17 из 216 вариантов распределения черных и белых пикселей на матрице с размерами 4x4, причем каждый отобранный вариант должен иметь отличное количество зачерненных пикселей и использовать отобранные трафареты для воспроизведения одной из 17 возможных градаций серого, независимо от характера формируемого изображения. За это
придется заплатить снижением пространственного разрешения в 4 раза.
Рис. 7.66. Трафареты для представления в изображении градаций серого цветаРис. 7.66. Трафареты для представления в изображении градаций серого цветаНо такой простой алгоритм может привести к образованию на изображении .муара. Чаще всего это происходит, если в изображении имеются фрагменты с регулярной структурой. Более сложные алгоритмы используют случайный выбор среди трафаретов с одинаковой характеристикой почернения, что позволяет избавиться от появления муара на изображении (см. упр. 7.26).
Рис. 7.66. Трафареты для представления в изображении градаций серого цвета
Рис. 7.66. Трафареты для представления в изображении градаций серого цвета
Такие технологии используются и при формировании цветных изображений в устройствах печати, в частности в цветных струйных принтерах. Отдельная точка оттиска может представлять собой только пят-
нышко какого-либо полного цвета, и промежуточные оттенки воспроизводятся за счет изменения плотности расположения этих цветовых пятнышек. В графической системе OpenGL поддерживается такой способ формирования градаций серого или оттенков цветов, для чега следует вызвать функцию glEnable(), передав ей в качестве аргумента константу GL DITHER.
Резюме
В этой главе представлен обзор методов реализации функций графической системы, включая примеры некоторых алгоритмов. Независимо от характеристик конкретных средств реализации (программных или аппаратных), существует ряд задач, которые необходимо решать в системе с любой организацией. К ним относятся задачи геометрических преобразований, отсечения и растрового преобразования. Мы рассмотрели, как при реализации этих задач взаимодействуют программные, аппаратные средства и функции API графической системы.
В графических рабочих станциях фирмы Silicon Graphics для решения задач геометрических преобразований и отсечения используется специализированный геометрический процессор Geometry Engine, реализованный аппаратно в виде СБИС с конвейерной архитектурой. Система GL, предшественница OpenGL, разрабатывалась как набор функций API для пользователей этих графических станций и, естественно, основной упор при ее разработке делался на использование особенностей конвейерной архитектуры. Но нельзя забывать, что, в конце концов, OpenGL представляет собой именно API и что ее функции с таким же успехом могут реализовать и алгоритм трассировки лучей при решении задач тонирования изображения. Акцентирование внимания на конвейерной архитектуре в этой главе объясняется тем, что на сегодняшний день именно эта архитектура обеспечивает наиболее эффективное решение всех необходимых в графической системе задач как программными, так и аппаратными средствами.
Но что же ожидает нас в будущем? Совершенно очевидно, что графические системы станут работать быстрее, а стоимость их уменьшится. Залогом тому являются успехи в создании новых аппаратных средств, обеспечивающих распараллеливание трудоемких задач, и именно аппаратные средства определяют дальнейшие пути развития графических систем. С точки зрения разработчиков программного обеспечения графических приложений снижение стоимости и повышение производительности используемых аппаратных средств позволит реали-
зовать в прикладных системах алгоритмы, обеспечивающие более высокое качество изображения, в частности учет большинства объективно существующих световых эффектов при выполнении тонирования, и сохранить при этом приемлемое время формирования изображения. Следовательно, пользователь такой системы получит возможность включать в отображаемую сцену больше объектов с самыми разнообразными оптическими свойствами, которые будут моделироваться графической системой в процессе формирования изображения сцены.
Пример использования алгоритма z-буфера демонстрирует, как взаимодействуют при решении задач компьютерной графики программные и аппаратные средства. Лет десять назад существовало множество алгоритмов удаления невидимых поверхностей, из которых до сегодняшнего дня фактически “дожил” только алгоритм z-буфера. Это объясняется тем, что ключевым элементом для его реализации являлась память, а именно модули памяти за эти годы стали значительно быстрее и дешевле. Аналогичным примером может служить и конвейерная архитектура графических рабочих станций, для реализации которой в современных системах используется множество стандартных БИС для выполнения арифметических операций с числами в формате с плавающей точкой, сигнальных процессоров и специализированных БИС (ASICS) для выполнения немногих специфических операций.
В настоящее время внимание исследователей переключилось на использование принципа распараллеливания операций применительно к графическим системам. Предмет большинства дискуссий — для решения каких задач компьютерной графики параллелизм принесет наибольший эффект. Рассмотренные нами два подхода к выполнению тонирования сцены (отталкиваясь от пространства объектов или от пространства изображения) предполагают и два принципиально различных пути внедрения параллелизма.
К сожалению, ограниченный объем книги не позволяет привести на ее страницах описания множества существующих на сегодняшний день решений. Читатель может обратиться к рекомендуемой в следующем разделе литературе и получить полное представление о разнообразных алгоритмах решения задач компьютерной графики. В главе 8 мы вновь вернемся к современным технологиям, которые поддерживаются в графических API, таких как OpenGL.
Рекомендуемая литература
Множество алгоритмов, которые мы не смогли представить в этой главе из-за ограниченного объема, содержатся в книгах Роджерса (Rogers) [Rog98] и Фоли (Foley) [Fol90], Я бы рекомендовал также познакомиться с выпусками серии Graphic Gems [Gra90-Gra92, Gra94, Gra95].
Алгоритм отсечения Коэна-Сазерленда появился еще в первые годы становления компьютерной графики, как и алгоритм Брезенхэма [ВгебЗ, Вге87], первая версия которого была предназначена для использования в перьевых плоттерах. Оригинальные описания алгоритмов отсечения Лианга-Барского и Сазерленда-Ходжмена представлены в статьях [Ыа84] и [Sut74, а].
Алгоритм z-буфера был впервые описан Кэтмулом (Catmull) [Cat75]. Обсуждение различных подходов к решению задачи удаления невидимых поверхностей содержится в работе Сазерленда (Sutherland) [Sut74, bj.
В этой главе мы обошли вниманием вопросы аппаратной реализации различных алгоритмов отнюдь не потому, что они слишком просты или не представляют особого интереса. Скорость, с которой в современной системе графический процессор формирует новое изображение, предполагает использование достаточно остроумных алгоритмов и продуманной конструкции аппаратуры [С1а82, Аке88, Аке93]. Множество подходов к разработке такого рода средств рассмотрено Мольнаром (Molnar) и Фучем (Fuchs) в сборнике под редакцией Фоли (Foley) [Fol90],
В книге Пратта (Pratt) [Рга78] описаны матричные методы преобразования между различными системами представления цвета. Формирование полутонов рассматривается в статьях Джарвиса (Jarvis) [Jar76] и Кнута (Knuth) [Кпи87].
Упражнения
Пусть в параметрическом виде заданы два прямолинейных отрезка:
p(a) = (l-a)pi + ap2, q(|3) = (l-p)ql + Pq2.
Разработайте процедуру, которая будет определять, пересекаются ли эти отрезки, и если пересекаются, вычислять координаты точки пересечения.
По образцу процедуры, разработанной при выполнении упр. 7.1, разработайте процедуру анализа пересечения двух плоских многоугольников.
Докажите, что при отсечении одного выпуклого объекта другим выпуклым объектом образуется не больше одного выпуклого объекта.
Как можно использовать распараллеливание операций при анализе сцены со стороны пространства объектов и со стороны пространства изображения?
Поскольку и векторы, в частности нормали, и вершины можно представить в однородных координатах, то и к тем, и к другим можно применить преобразование, заданное матрицей вида. Покажите, что при таком преобразовании свойства вектора как нормали в общем случае не сохраняются.
Разработайте метод преобразования видового окна. Сформулируйте его в терминах матриц масштабирования и сдвига, которые представляют аффинные преобразования в двухмерном пространстве.
На ранних этапах развития компьютерной графики доминирующее положение занимали векторные системы, которые позволяли формировать только изображения, состоящие из отдельных линий (штриховые изображения). При обработке трехмерных сцен программно выполнялась процедура удаления невидимых линий. Многие из современных графических API позволяют создавать так называемые проволочные, или каркасные (wireframe), изображения объектов, которые также состоят только из линий (ребер поверхностей объектов сцены), но при этом часть линий, представляющих невидимые ребра поверхностей, удаляется из изображения. Чем отличается формулировка задачи удаления невидимых линий от задачи удаления невидимых поверхностей? Разработайте алгоритм удаления невидимых линий применительно к изображению, состоящему из ребер плоских многоугольников.
Часто в графических приложениях приходится формировать семейства графиков функций вида у =Дх, г). Такой график строится в виде сетки из прямоугольных ячеек, формируемых множеством значений {Дх„ с,)}, элементы которого рассчитываются с постоянным шагом приращений по переменным х и z. В результате образуется изображение, которое должно быть обработано алгоритмом удаления невидимых поверхностей, поскольку некоторые его участки закрыты от наблюдателя другими участками. Разработайте два алгоритма отображения такой сетки, один из которых будет использовать метод удаления невидимых поверхностей, а другой — метод удаления невидимых линий.
Хотя теоретически сложность решения задачи удаления невидимых поверхностей на основе анализа пространства изображения пропорциональна количеству многоугольников, экспериментальное исследование производительности систем показало, что она (производительность) остается практически постоянной. Как вы объясните этот феномен?
Рассмотрите сцену, которая состоит исключительно из сплошных трехмерных многогранников. Как в таком случае можно применить алгоритм удаления невидимых
поверхностей, анализирующий пространство объектов? Насколько облегчит решение этой задачи информация о том, что все многогранники выпуклые?
Можно рассматривать методы удаления невидимых поверхностей, анализирующие пространство объектов, как некий аналог методов сортировки. Однако было показано, что оценка сложности решения такой задачи имеет вид О(к"\ Но известно, что такая оценка является завышенной и справедлива только для несовершенных методов сортировки. Для большинства же алгоритмов сортировки оценка имеет вид О^к logA). Следует ли из этого, что такой же вид имеет и оценка сложности методов решения задачи удаления невидимых поверхностей, предполагающих анализ пространства объектов? Объясните свой ответ.
Разработайте метод проверки, расположен ли один плоский многоугольник полностью по одну строну от другого плоского многоугольника.
Чем отличается подход к удалению невидимых поверхностей путем анализа пространства изображения от методов трассировки лучей? Можно ли использовать метод трассировки лучей в качестве альтернативного для решения задачи удаления невидимых поверхностей? В чем достоинства и недостатки такого подхода?
Разработайте программу, которая будет использовать алгоритм Брезенхэма для формирования координат пикселей, аппроксимирующих прямолинейный отрезок. Обеспечьте возможность обработки этой программой прямых с любым коэффициентом наклона и любыми вариантами взаимного положения конечных точек. Как формируется исходное значение переменной сП
Принцип, положенный в основу алгоритма Брезенхэма, можно распространить и на формирование растрового образа окружности. Покажите, как это сделать на примере окружности, центр которой находится в начале координат. Какая часть окружности будет формироваться основным алгоритмом, а какие части можно сформировать, используя принцип симметрии? Можно ли выделить такую часть окружности, для которой информация о точке, сформированной на предыдущем шаге, может существенно сократить количество вычислений на следующем шаге?
Подумайте над тем, как использовать заливку областей при формировании изображения лабиринта, подобного тому, который был построен при выполнении упр. 2.8.
Предположим, что предпринята попытка распространить алгоритм заливки, сформулированный в этой главе для многоугольников, на формирование изображений произвольных замкнутых кривых. Какие проблемы поджидают вас на этом пути?
Ребро многоугольника задано вершинами (Х],^) и (х2,у2). Разработайте эффективный алгоритм вычисления точек пересечения всех строк растра с этим ребром. Считайте, что операции выполняются в системе координат видового окна.
При реализации алгоритмов заполнения внутренних областей многоугольников часто приходится сталкиваться с определенными проблемами в ходе обработки вертикальных и горизонтальных ребер. Как следует обрабатывать эти ребра в тех алгоритмах, которые описаны в тексте этой главы?
В системе двухмерной графики управление порядком отображения накладывающихся многоугольников можно реализовать с помощью специального атрибута приоритета, который ассоциируется с каждым примитивом такого типа. При реализации этой идеи объект с наивысшим значением этого атрибута будет отображаться последним и закроет все находящиеся “под ним”. Как следует модифицировать рассмотренные в тексте главы алгоритмы растрового преобразования многоугольников, чтобы можно было воспользоваться таким атрибутом?
При трассировке лучей для выравнивания ступенек растрового образа часто используется приведение не только луча, проходящего через центр идеального пикселя, но и еще четырех лучей, проходящих через углы квадратного пикселя. Насколько, по-вашему, при этом возрастает объем вычислений по сравнению с приведением единственного луча?
Хотя идеальный пиксель должен иметь форму квадрата со стороной единичной длины, в реальных ЭЛТ формируется круглое пятно, которое можно аппроксимировать кругом с постоянной интенсивностью свечения. Если полностью засвеченный единичный квадрат имеет интенсивность 1.0, а незасвеченный— интенсивность 0.0, как должна изменяться интенсивность свечения реального пикселя в зависимости от радиуса круга?
Рассмотрим двухуровневое устройство отображения с пикселями в виде круга. Как, по-вашему, следует отображать пиксели, окрашенные в цвет переднего плана, — кругами большего или меньшего радиуса? Поясните свой ответ.
Почему слабую расфокусировку луча ЭЛТ иногда называют “технологией сглаживания для бедных”?
Предположим, что минимальная интенсивность засветки пикселей монохромного устройства отображения равна /min (экран ЭЛТ никогда не бывает абсолютно черным), а максимальная — /тах. Как разделить диапазон интенсивности на к уровней, учитывая, что воспринимаемая яркость зависит от интенсивности засветки по логарифмическому закону, чтобы перепады яркости между соседними уровнями казались наблюдателю постоянными?
Разработайте алгоритм формирования полутонов на основе следующей идеи. Предположим, что уровни серого должны изменяться от 0.0 до 1.0 и что в вашем распоряжении имеется генератор случайных чисел с равномерным законом распределения в диапазоне от 0.0 до 1.0. Если задан уровень интенсивности серого g, то 100g процентов сформированных этим генератором чисел окажется меньше g.
Т.П. Устройства отображения с малой глубиной управления по интенсивности формируют изображения, в которых явно заметна граница между участками с разным почернением. Один из методов устранения этого недостатка состоит в том, что на сигнал интенсивности засветки пикселей накладывается слабый шум. Каким образом, по-вашему, это помогает сгладить наблюдаемые перепады яркости между соседними участками? Шум какой амплитуды следует накладывать? Можно ли отсюда заключить, что искаженное таким способом изображение будет иметь более высокое качество, чем неискаженное?
ГЛАВА
8
Иерархические графические модели
М
одели — это абстрактные представления сущностей реального мира, в котором мы живем, и сущностей виртуального мира, сотворенных программами. Думаю, большинство читателей неоднократно имели дело с математическими моделями, которые применяются во всех областях науки и техники. В таких моделях для представления реальных физических явлений используются математические уравнения. В информатике с помощью абстрактных типов данных моделируется организационная структура объектов, а в компьютерной графике нам потребуются графические модели геометрических объектов. При создании математических моделей тщательно выбирается подходящий математический аппарат, который наилучшим образом позволяет отразить в модели сущность моделируемого реального феномена. Например, для моделирования динамического взаимодействия компонентов системы твердых тел используется аппарат обыкновенных дифференциальных уравнений, а для явлений турбулентности в потоке жидкости больше подходит аппарат дифференциальных уравнений в частных производных. Как правило, одно и то же явление можно моделировать по-разному, в зависимости от того, какие характеристики этого явления подлежат исследованию и какими средствами работы с моделью располагает исследователь. В этом свете наша задача состоит в том, чтобы найти вид модели, позволяющий наилучшим образом использовать возможности графической системы.
В этой главе будут рассмотрены разные подходы к созданию моделей графических объектов и работе с ними в среде, которую поддерживает система компьютерной графики. Будут проанализированы модели, которые используют набор простых графических объектов, — как примитивов, встроенных в графическую систему, так и объектов, созданных пользователем из этих примитивов. Будет также показано, как использовать геометрические преобразования, которые мы рассматривали в главе 4, для отображения иерархических отношений между графическими объектами. Рассмотренные в этой главе методики находят широкое применение в таких областях, как робототехника и компьютерная анимация, где динамической характер поведения объектов во многом зависит от пространственных отношений между отдельными компонентами модели.
Понятие иерархии играет особую роль в объектно-ориентированных методологиях. Мы распространим иерархический подход к организации моделей отдельных объектов на все пространство сцены и включим в иерархическую модель не только отображаемые геометрические объекты, но и камеры, источники света и свойства материалов. Такие модели позволят расширить возможности средств графического API и превратить их в объектно-ориентированную систему, что открывает путь к использованию графических приложений в распределенной сетевой среде, такой как World Wide Web.
Символы и экземпляры
Сначала рассмотрим, каким образом можно хранить модель, которая включает множество достаточно сложных объектов. Тут сразу возникают два вопроса: как описывать объекты, более сложные, чем те, с которыми мы работали до сих пор, и как представлять множество таких объектов. В большинстве графических API пользователя не особенно балуют разнообразием типов встроенных примитивов. Считается, что можно обойтись минимумом основных примитивов, предоставив пользователю возможность самостоятельно конструировать из них более сложные объекты. Иногда в состав графической системы включаются дополнительные библиотеки, такие как GLU и GLUT, которые мы уже не раз использовали при работе с OpenGL, и в этих библиотеках имеются средства поддержки расширенного набора объектов, созданных на основе базового комплекта примитивов. Будем считать, что нам доступны некоторые трехмерные объекты, поддерживаемые такой библиотекой. В последующих главах будут рассмотрены и другие типы геометрических объектов, в частности кривые и криволинейные поверхности (глава 10) и рекурсивно определяемые объекты (глава 11).
Можно воспользоваться неиерархическим подходом к моделированию и рассматривать объекты из базового набора как символы, из которых строится модель. В состав набора сим-
Рис. 8.1. Символ цилиндраРис. 8.1. Символ цилиндраволов могут входить не только графические объекты, но и шрифты, и условные графические обозначения, специфичные для определенной области, например обозначения электрических элементов и узлов в системах проектирования электрических схем. Обычно символы в библиотеке представлены как объекты стандартного размера и ориентации, удобные для данного символа. Например, ось цилиндра обычно параллельна одной из координатных осей, сам цилиндр имеет единичную высоту и единичный радиус, а центр основания располагается в начале системы координат (рис. 8.1).
Рис. 8.1. Символ цилиндра
Рис. 8.1. Символ цилиндра
В некоторых графических API, например в PHIGS и GKS, фрейм, в котором определен символ (фрейм модели — model frame или система координат модели — model coordinates'), не совпадает с мировым фреймом. Такое разделение весьма существенно, если символ
представляет собой некую чисто геометрическую фигуру, например условное обозначение электрического элемента. В прикладной программе нужно определить преобразования, связывающие фрейм символа и мировой фрейм, и применить их к матрице вида еще до того, как вызывать функции формирования символа.
Преобразования экземпляра, о которых шла речь в главе 4, позволяют разместить в модели каждый экземпляр символа на отведенном ему месте, причем размер символа и его ориентация также могут изменяться индивидуально для каждого экземпляра. Матрица преобразования экземпляра имеет вид
M = TRS
и представляет собой суперпозицию преобразований сдвига, поворота и масштабирования (рис. 8.2). При необходимости к ним можно еще добавить и преобразование скоса. В результате в OpenGL-программе часто можно встретить фрагменты такого вида:
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(...);
glRotatef(...);
glScalef(...);
glutSolidCylinder(...); /* или какой-либо другой символ */
Эту модель можно представить в виде таблицы, в частности такой, как изображена на рис. 8.3. При заполнении таблицы предполагается, что каждый символ имеет уникальный идентификатор. Такая технология моделирования не несет информацию о взаимных связях между объектами, но той информации, которая включена в таблицу, вполне достаточно для того, чтобы сформировать изображение всей совокупности объектов. Модель позволяет отыскать любой объект и изменить параметры преобразований, заданных для этого объекта, удалить из модели объект или добавить новый. Но отсутствие в модели связей между объектами ограничивает возможности работы с ней.
Рис. 8.2. Преобразования экземпляра
Иерархические модели
Предположим, что перед нами стоит задача сформировать модель автомобиля, которую затем можно было бы использовать в программе анимации. Такую модель можно составить из пяти компонентов — кузова и четырех колес (рис. 8.4), — каждый из которых, в свою очередь, состоит из примитивов графической системы. Два кадра анимации этой модели показаны на рис. 8.5. В программе, которая будет формировать такие кадры мультфильма, нужно учитывать, что при повороте колеса на 360° автомобиль смещается на расстояние 2лг, где г— радиус колеса. В состав программы должна входить функция, которая будет формиро-
Рис. 8.4. Модель автомобиляРис. 8.4. Модель автомобилявать колесо, и функция, которая будет формировать кузов. Обеим функциям передается информация о скорости и направлении движения модели. На языке псевдокода программа должна выглядеть примерно так: main() {
Рис. 8.4. Модель автомобиля
Рис. 8.4. Модель автомобиля
float s= ...; /* скорость */
float d[3]={...}; /* направление */
draw_right_front_wheel(s,d);
draw_left_front_wheel(s,d);
draw_right_rear_wheel(s,d);
draw_left_rear_wheel(s,d); draw_chassis(s,d);
}
Puc. 8.5. Два кадра мультфильма
Мы привели эту программу только для того, чтобы показать, как не надо писать такого рода программы. В ней абсолютно игнорируется топологическая связь между отдельными компонентами модели автомобиля. Существуют два вида связей, которые желательно отобразить в модели. Одна связь определяет, что невозможно отделить движение автомобиля от движения отдельного колеса. Если автомобиль движется вперед, колесо должно вращаться. Но, кроме того, следует учесть и тот факт, что все колеса автомобиля одинаковы, просто они по-разному расположены по отношению к кузову и имеют разную ориентацию.
Отношения между компонентами в модели можно представить и абстрактно, и наглядно — в виде графа. С точки зрения математики граф состоит из множества узлов и ребер. Ребра связывают пары узлов, причем не исключен вариант, когда некоторый узел связывается сам с собой. С ребрами можно ассоциировать направление связи, и в этом случае граф называется ориентированным. Ребро ориентированного графа исходит из одного узла и заходит в другой.
Мы будем работать с графами одного из самых распространенных видов— древовидным графом (или деревом). Деревом называется конечный связный ориентированный граф, не имеющий циклов (замкнутых путей). Каждый узел дерева, кроме одного — корня, — имеет точно одно заходящее в него ребро. Следовательно, каждый узел имеет точно один родительский узел, с которым его связывает заходящее ребро. Но любой узел может иметь одно, ни одного или несколько исходящих ребер, а соответственно один, ни одного или несколько дочерних узлов. Узел, не имеющий дочерних, называется терминальным узлом, или листам дерева. На рис. 8.6 показано дерево, представляющее отношения между компонентами нашей модели автомобиля. Корнем дерева является объект кузова, а все четыре объекта, представляющие колеса, — его дочерними узлами. Хотя математический граф представляет собой набор узлов и ребер, при программной реализации с узлами и ребрами связывается дополнительная информация. В рассматриваемом примере с каждым узлом может быть связано описание соответствующего
геометрического объекта. Информация о положении и ориентации каждого колеса может быть связана либо с соответствующим узлом графа, либо с заходящим в него ребром.
В некоторых моделях используется по несколько экземпляров идентичных объектов. Именно такой вариант мы наблюдаем и в нашей модели автомобиля — все четыре колеса в ней идентичны. Совершенно очевидно, что нет необходимости дублировать в каждом узле колеса описание этого геометрического объекта, а следует воспользоваться той же идеей, которую мы уже рассматривали при описании приемов работы с экземплярами, — использовать единственное представление объекта-прототипа. Если выполнить такую трансформацию, наше дерево превратится в ориентированный ациклический граф (ОАГ), представленный на рис. 8.7. Хотя в графе такого типа и существуют циклы, в каждом из них обязательно встречается ребро, направленное навстречу направлению обхода. Следовательно, путь обхода в соответствии с направлением ребер обязательно приведет к терминальному узлу. Работа с ациклическими ориентированными графами не сложнее работы с обычными деревьями. В нашем графе, представляющем модель автомобиля, информацию о положении и ориентации каждого колеса можно хранить либо в описании узла кузова, либо в описаниях ребер, связывающих узел кузова с узлом колеса.
Рис. 8.6. Дерево представления модели автомобиляРис. 8.6. Дерево представления модели автомобиляРис. 8.7. Ориентированный ациклический граф модели автомобиляРис. 8.7. Ориентированный ациклический граф модели автомобиля
Рис. 8.6. Дерево представления модели автомобиля
Рис. 8.6. Дерево представления модели автомобиля
Рис. 8.7. Ориентированный ациклический граф модели автомобиля
Рис. 8.7. Ориентированный ациклический граф модели автомобиля
Оба варианта графов — и деревья, и ациклические ориентированные графы — представляют собой иерархические методы описания взаимосвязей, которые существуют между компонентами физической модели. Далее мы рассмотрим, как использовать такую иерархическую модель в графических программах.
Модель руки робота
Робототехника представляет собой благодатное поле для использования методов иерархического моделирования. Рассмотрим упрошенную руку робота, показанную на рис. 8.8,а. Ее модель можно построить из трех простейших объектов или символов, воспользовавшись, например, параллелепипедами и цилиндром. Каждый из этих символов формируется из базовых примитивов.
Три детали (звенья) руки робота показаны на рис. 8.8, б. Механизм имеет три степени свободы, каждая из которых описывается углом поворота в сочленении, связывающем каждое звено с предыдущим*. Этот угол определяет, как будет располагаться следующее звено (и все дальнейшие) относительно предыдущего. Первое звено поворачивается относительно всего остального мира. Угол
2В этой модели не показано неподвижное основание, относительно которого вращается первое звено. — Прим. ред.
поворота в сочленении измеряется относительно собственного фрейма каждого звена. Первое звено — тело робота — поворачивается на угол 0 относительно вертикальной оси своего фрейма, а угол измеряется от оси х до некоторой фиксированной точки на нижней плоскости первого звена. Второе звено — плечо робота — связано с телом сочленением, позволяющим ему наклоняться в плоскости 2 = 0 фрейма плеча на угол ф, который измеряется от оси х этого же фрейма. Третье звено — предплечье — связано аналогичным сочленением с плечом и может наклоняться относительно плеча на угол у, измеряемый также в его собственном фрейме. Изменение углов в сочленениях приводит к перемещению фреймов плеча и предплечья относительно фрейма тела робота. Управляя этими тремя углами, можно перемещать конечную точку предплечья (на которой в реальном роботе закрепляется кисть со схватом) в трехмерном пространстве.
Рис. 8.8. Рука робота: а — составная модель; б — компонентыРис. 8.8. Рука робота: а — составная модель; б — компоненты
Рис. 8.8. Рука робота: а — составная модель; б — компоненты
Рис. 8.8. Рука робота: а — составная модель; б — компоненты
Рис. 8.9. Перемещение звеньев робота и связанных с ними фреймовРис. 8.9. Перемещение звеньев робота и связанных с ними фреймов
Рис. 8.9. Перемещение звеньев робота и связанных с ними фреймов
Рис. 8.9. Перемещение звеньев робота и связанных с ними фреймов
Перед нами стоит задача разработать программу, которая будет формировать динамиче
ское изображение движущегося робота. Вместо того чтобы независимо друг от друга описы-
вать каждый элемент робота и его движение, мы воспользуемся подходом, основанным на суперпозиции движения отдельных звеньев. Тело робота может поворачиваться вокруг оси у собственного фрейма на угол 0. Следовательно, можно описать перемещение любой точки р на теле робота, воспользовавшись матрицей преобразования поворота Rr(0).
Плечо робота поворачивается вокруг оси г собственного фрейма, но этот фрейм нужно привязать к центру верхней плоскости тела робота. Это можно сделать с помощью матрицы преобразования сдвига Т(0, hv 0), где hx — высота тела относительно плоскости основания. Именно в этой точке находится сочленение между первым и вторым звеньями. Но если поворачивается тело робота, то вместе с ним поворачивается и плечо, точнее фрейм плеча. Следовательно, в дополнение к преобразованию сдвига нужно применить и преобразование поворота, воспользовавшись матрицей R/0). Таким образом, положение любой точки на втором звене, в том числе и вершин многогранника, представляющего параллелепипед, в процессе изменения углов в первом и втором сочленениях будет зависеть
от произведения матриц Ry(0)T(O, hx, O)Rr(0). Матрицу RJ.(0)T(O, hx, 0) можно рассматривать как матрицу преобразования фрейма плеча относительно мирового фрейма, а матрицу Rr^) как матрицу положения плеча относительно тела робота (рис. 8.9).
Рассуждая аналогично в отношении предплечья, придем к выводу, что это звено описывается матрицей сдвига Т(0, Ьъ 0) относительно фрейма предплечья, а затем матрицей поворота R-(V)- Положение точек предплечья — вершин представляющего это звено параллелепипеда— определяется произведением R,(0)T(0, hx, 0)К..(Ф)Т(0, h2, O)R-(V). Функция отображения в OpenGL-программе построена таким образом, что матрица вида изменяется “в приращениях" (на сей раз приращения имеют вид матриц преобразования) перед формированием изображения каждого очередного звена: display() {
glRotatef(theta, 0.0, 1.0, 0.0);
base();
glTranslatef(0.0, hl, 0.0);
glRotatef(phi, 0.0, 0.0, 1.0);
lower_arm();
glTranslatef(0.0, h2, 0.0);
glRotatef(psi, 0.0, 0.0, 1.0);
upper_arm();
}
Обращаю ваше внимание на то, что положение звеньев задается независимо от формы каждого из звеньев. При одних и тех же углах в сочленениях можно изменить внешний вид робота, изменив функции формирования объектов, представляющих его зве- Тело обота I нья" ^акое разделение позволяет отдельно разрабатывать программы, описы-
,J вающие движение робота и форму его звеньев. На рис. 8.10 иерархия отноше-
Г ний между звеньями робота представлена в виде дерева. На ftp-сервере по ад-
Т ресу ftp.cs.unm.edu в каталоге pub/angel/BOOK читатели найдут текст Предплечье I программы robot.с, в которой реализована эта структура. Программа позволя- -А ,- | ет “оживлять" на экране изображение робота по командам, формируемым с
I помощью меню. В этой программе тело робота имеет форму цилиндра, а плечо . Л и предплечье — параллелепипедов.
' Плечо I
I Рис 8.10. Дерево представления иерархии звеньев робота
Теперь рассмотрим, каким образом можно реализовать в программе структуру данных, соответствующую структуре дерева на рис. 8.10. Если хранить информацию в описаниях узлов, то каждый узел должен содержать по крайней мере три элемента данных (рис. 8.11):
указатель на функцию, которая формирует изображение объекта, представленного этим узлом;
матрицу преобразований в однородных координатах, задающую отношение в пространстве между объектом данного узла (и всеми объектами его дочерних узлов) и объектом его родительского узла;
указатель на дочерний узел.
В структуру узла можно включать и другую информацию, например набор атрибутов (цвет, трафарет за
Отображение
М
Дочерний узел
Дочерний узел
Рис. 8.11. Представление узла струк-турой данных в программе
полнения, свойства материала и т.п.). Формирование изображения модели, описанной такой иерархической структурой данных, требует обхода дерева {tree traversal) — нужно “посетить” каждый узел и при обработке каждого узла вычислить элементы матрицы, которая
Рис. 8.13. Представление фигурки киборга в виде дереваРис. 8.13. Представление фигурки киборга в виде дерева
Рис. 8.13. Представление фигурки киборга в виде дерева
Рис. 8.13. Представление фигурки киборга в виде дерева
будет определять преобразования примитивов, описывающих соответствующий графический объект, и затем уже сформировать их изображения. В той программе, которую мы рассмотрели выше, такой обход был реализован неявно.
Рассмотренный пример довольно прост — каждый узел в нем имеет только один дочерний. В следующем примере мы встретимся с более сложной иерархией объектов.
Обход деревьев
На рис. 8.12 показана фигура киборга — персонажа множества комиксов. Если выбрать корпус киборга в качестве объекта корневого узла иерархии, то всю фигуру можно представить деревом, показанным на рис. 8.13. После того как положение корпуса будет задано, положение и
ориентацию его конечностей можно определить углами поворота в соответствующих сочленениях. Можно “оживить” в программе фигуру киборга, изменяя значения углов в сочленениях. В этой простейшей модели в коленях и локтях имеются сочленения с одной степенью свободы, но можно создать и более сложную модель, поместив сочленения с двумя степенями свободы. А в сочленении, моделирующем шею, можно и в этом простом варианте определить две степени свободы.
Рис. 8.12. Фигурка киборга
Предположим, что разработано несколько функций вроде head() или left_upper_arm(), которые формируют изображения отдельных компонентов фигурки (символов) в собственных системах координат. Далее можно сформировать набор описаний узлов дерева определить в них матрицы преобразования символа, с которым связан узел, относительно символа родительского узла, точно так, как мы это делали при построении иерархии звеньев робота в предыдущем разделе. Будем считать, что каждый символ описан таким образом, что не требуется изменять его масштаб. Тогда матрицы преобразований в узлах будут включать произведение только матриц сдвига и поворота. На схеме дерева соответствующие матрицы можно использовать для маркировки ребер, как это показано на рис. 8.143. Не забывайте, что каждая матрица представляет преобразование относительно предыдущего узла и что для получения преобразования относительно мирового фрейма нужно перемножить матрицы по пути обхода графа.
J Индексы матриц на рис. 8.14 — аббревиатуры от английских названий элементов .модели киборга: h — head (голова), lua— left-upper arm (левое плечо), ша— right-upper arm (правое течо). lul— left-upper leg (левое бедро), nil — right-upper leg (правое бедро), Ila — left-lower апп (левое предплечье), ria — right-lower arm (правое предплечье), 111 — left-lower leg (левая голень), rll — right-lower leg (правая голень). — Прим. ред.
Рис. 8.14. Маркированное дерево представления модели киборга
Самое интересное в этом примере — способ обхода дерева при формировании изображения фигурки. В принципе можно использовать один из множества существующих на сегодняшний день алгоритмов, например алгоритмы поиска в глубину {depth-first) или поиска в ширину {breadth-first). Хотя в большинстве приложений не имеет существенного значения, какой именно алгоритм обхода используется, но желательно, чтобы это был один и тот же алгоритм для всех графов. Мы будем всегда обходить дерево слева направо, реализуя поиск в глубину. Таким образом, начнем с левого ребра, исходящего из корневого узла, и будем следовать по левым ребрам, исходящим из каждого очередного дочернего узла, пока не придем к терминальному. Затем вернемся на уровень вверх по дереву и исследуем следующее его ребро, придерживаясь того же принципа. Вся процедура рекурсивно повторяется. Такой способ обхода получил название обхода в прямом порядке {pre-order traversal).
Функцию обхода дерева можно запрограммировать двумя способами: выполнять обход в программном коде в явном виде, используя стек для хранения матриц и атрибутов по мере перемещения от узла к узлу, или использовать рекурсивную процедуру. Второй вариант приводит к более компактной программе, причем сохранение матриц и атрибутов выполняется неявно. Мы рассмотрим ниже оба варианта, поскольку по ходу их анализа вы получите возможность по-новому взглянуть на методику построения графических систем.
Алгоритм обхода с использованием стека
Будем считать, что формирование изображения фигурки выполняется функцией figure (), которую можно вызывать как из функции отображения display (), так и из функции обработки сообщений мыши mouse(). Последний вариант используется, когда с помощью мыши пользователь управляет углами поворота в сочленениях. Матрица вида М в момент обращения к функции f igure() определяет положение фигурки в пространстве сцены по отношению к камере. При обработке первого узла дерева, представляющего корпус фигурки, соответствующий примитив отображается с использованием преобразования, заданного матрицей М. Затем следует переход по левому ребру, исходящему из первого узла дерева, к узлу, который представляет голову фигурки. Голова отображается функцией head(), причем перед вызовом этой функции матрица вида модифицируется, и при отображении используется произведение ММ/,. Узел Голова является терминальным, а потому алгоритм обхода возвращается к узлу Корпус и анализирует следующее по порядку ребро. Оно “приводит” алгоритм к поддереву Левое плечо, которое повторяет дерево модели робота, рассмотренное нами в предыдущем разделе. Для вычерчивания левого плеча используется матрица вида ММ^, а левого предплечья — матрица вида ММ/„оМ//о. Узел Левое предплечье является терминальным, а потому алгоритм обхода возвращается к узлу Левое плечо. Этот узел имеет только один дочерний
узел, который алгоритм уже “посетил”, а потому вновь возвращаемся к узлу Корпус. Далее исследуется следующее по порядку ребро, исходящее из этого узла, которое ведет к поддереву Правое плечо. Процедура обхода этого поддерева аналогична уже рассмотренной процедуре обхода поддерева Левое плечо. Поддеревья Левое бедро и Правое бедро обходятся точно таким же образом. При завершении обхода каждого поддерева алгоритм возвращается к корневому узлу и восстанавливает исходное значение М матрицы вида.
Возможно, проще было бы рассматривать весь этот процесс с помощью текущей матрицы преобразования — матрицы вида С, которая применяется по отношению к примитивам, определенным в узле (ее структура и роль в графической системе описаны в главе 4). Исходное значение С равно М и преобразуется в ММЛ при обработке узла Голова, потом в ММ/Ю затем в ММ/„иМ//и и т.д. Обновление значения С должно выполняться прикладной программой перед вызовом каждой функции отображения примитива, заданной в очередном узле дерева в порядке его обхода. Для реализации этого алгоритма обхода в графической системе OpenGL, кроме функций формирования матриц поворота, сдвига и масштабирования, нам потребуются еще и функции glPushMatrix() — для сохранения текущей матрицы преобразования в стеке и glPopMatrix() —для извлечения значения матрицы из стека.
Рассмотрим программный код начального фрагмента функции figure(), в котором опущены значения аргументов функций преобразования:
figure()
{
glPushMatrix();
torso();
glTranslate
glRotate3
head();
glPopMatrix();
glPushMatrix();
glTranslate
glRotate3
left_upper_arm();
glTranslate
glRotate3
left_lower_arm()
glPopMatrix();
glPushMatrix()
glTranslate
glRotate3
right_upper_arm();
glPopMatrix();
glPushMatrix()
При первом вызове glPushMatrix() в стеке сохраняется текущее значение матрицы вида (предполагается, что перед этим уже была вызвана функция glMatrixMode(GL_MODELVIEW)). Сохранив это значение на будущее, можно сразу же приступать к модификации матрицы вида— далее вызываются функции glTranslate() и glRotate(), которые формируют матрицу М/, и одновременно домножают текущую матрицу вида на сформированные матрицы преобразования. После этого вызывается функция отображения головы фигурки head(). Поскольку узел Голова является терминальным, нам нужно вернуться к его родительскому узлу. В программе это реализуется восстановлением предыдущего значения текущей матрицы преобра-
зования из стека— вызовом функции glPopMatrix(). Затем программа переходит к обходу следующего поддерева, и первая операция — вновь сохранить текущее значение матрицы вида в стеке, т.е. вызвать функцию glPushMatrix(). Обход последующих поддеревьев выполняется в программе аналогично.
На йр-сервере по адресу ftp.cs.unm.edu в каталоге pub/angel/BOOK читатели найдут текст программы figure.с, в которой добавлено обращение к специальному меню, позволяющему пользователю управлять углами поворота в сочленениях. В этой программе примитивы, описывающие отдельные элементы фигурки, сформированы из эллипсов и цилиндров, а вся модель закрашивается с помощью методов, рассмотренных в главе 6.
При подготовке текста рассмотренной программы совершенно не учитывалась обработка атрибутов (цвета и свойств материалов), которые можно ассоциировать с каждым узлом иерархической модели. Атрибуты являются в OpenGL параметрами режима, т.е. они устанавливаются программой в режимных переменных и воздействуют на отображение всех последующих примитивов до тех пор, пока программа не изменит текущее значение атрибута. Поэтому при обходе дерева с атрибутами следует поступать так же, как и с текущими значениями матрицы преобразования, — сохранять в стеке и восстанавливать из стека. Предположим, например, что при выполнении функции вычерчивания корпуса torso() был установлен красный цвет, а при вычерчивании следующего компонента фигурки — головы — соответствующая функция устанавливает синий цвет. Если в программах отображения остальных компонентов фигурки нет операторов установки текущего цвета, то все они будут нарисованы синим цветом. Этот же цвет останется текущим и после завершения выполнения функции figure(). При обходе дерева в другом порядке может случиться так, что большинство элементов будет вычерчено красным цветом.
Такая ситуация может привести вас в замешательство, но существуют методы, которые позволяют успешно справиться с ней. С атрибутами следует поступать так же, как и с текущей матрицей преобразования, — помещать текущее значение в стек при переходе к очередному узлу (поддереву) и восстанавливать из стека после возврата из этого узла (поддерева). В OpenGL это выполняется с помощью функций glPushAttrib() и glPopAttrib(). Если при входе в функцию figure() поместить атрибуты в стек, а перед выходом извлечь их из стека, текущие значения атрибутов восстановятся. Более того, внутри функции figure() можно дополнительно сохранять и восстанавливать значения атрибутов. В OpenGL переменные состояния можно разделить на группы и записывать в стек или извлекать из стека всю группу единым обращением к соответствующим функциям. Для этого в прикладной программе нужно установить определенные разряды в маске, которая является аргументом функции glPushAttrib(). Группы атрибутов включают и источники света, информацию о которых можно таким же способом помещать в стек и извлекать из него.
Описанные идеи можно применять в моделях рекурсивно. Если, например, желательно сформировать более сложную форму головы фигурки — выделить в ней нос, глаза, уши и т.п.,— все эти компоненты можно описывать и отображать отдельно. В результате объект Голова будет иметь собственную иерархию, а программный код формирования изображения головы также будет включать обращения к функциям записи в стек и извлечения из стека матриц преобразования и атрибутов.
Если дерево включает два или несколько идентичных поддеревьев, то его можно преобразовать в ориентированный ациклический граф. Запись в стек и восстановление из стека позволяют изолировать процесс выполнения отдельных фрагментов программного кода. В графической системе OpenGL один дисплейный список может вызывать другой дисплейный список, и эта возможность позволяет программисту использовать еще один метод обработки деревьев и ориентированных ациклических графов.
В системе PH1GS используется аналогичный, но менее мощный метод для работы с деревьями и ориентированными ациклическими графами. В этой системе данные помешаются в группы — структуры, — которые в первом приближении можно считать аналогами дисплейных списков. Одни структуры могут вызываться из других структур функцией execute_structure(). Такой механизм позволяет формировать ориентированные ациклические графы. PH1GS поддерживает как локальные, так и глобальные матрицы преобразования, оперируя с которыми прикладная программа может эмулировать те же процедуры обращения к стеку матриц и атрибутов, что и в системе OpenGL. И в OpenGL, и в PH1GS используется фиксированный порядок обхода графов. В других API, включая и JAVA, поддерживаются древовидные структуры, которые можно обходить в разном порядке, формируя при этом одно и то же изображение, которое не зависит от порядка обхода узлов графа.
Тот подход, который был рассмотрен выше, позволяет создавать работоспособные программы, но имеет известные ограничения. Структура модели представлена неявно в программном коде в виде последовательности операторов записи в стек и извлечения из стека, а это не позволяет гибко модифицировать модель. Чтобы избежать этого, следует отделить описание структуры модели от программного кода ее обработки. Мы рассматривали метод “жесткого” программирования модели с единственной целью — дать читателям наглядное представление о процессе обхода дерева. В следующем разделе мы рассмотрим более общий метод описания иерархических моделей и обхода их графов.
Обход древовидных структур
Описанный в этом разделе подход основан на стандартных средствах представления в программе древовидных структур иерархических моделей, обработка которых производится средствами, не зависящими от вида конкретной модели. Мы будем рассматривать структуру, которая получила название слева — ребенок, справа — братья и сестры {left-child right-sibling')
Рассмотрим альтернативное представление иерархии компонентов фигурки (рис. 8.15).
Это дерево построено таким образом, что все элементы одного уровня иерархии связаны слева направо. Дочерние узлы данного узла представлены вторым списком, элементы в котором перечислены в порядке от самого левого до самого правого. Этот второй список представлен на рис. 8.15, снизу. Такое представление описывает структуру фигурки, но в нем отсутствует графическая информация.
Каждый узел должен хранить информацию, необходимую для отображения соответствующего компонента фигурки: функцию формирования изображения компонента и матрицу преобразования в однородных координатах, которая определяет положение компонента относительно его родителя. Рассмотрим программную структуру, представляющую узел такого дерева: typedef struct treenode {
GLfloat m[ 16];
void (*f)();
struct treenode ‘sibling;
struct treenode ‘child;
Puc. 8.15. Представление фигурки с помощью структур слева — ребенок, справа — братья и сестрыPuc. 8.15. Представление фигурки с помощью структур слева — ребенок, справа — братья и сестры}treenode;
Puc. 8.15. Представление фигурки с помощью структур слева — ребенок, справа — братья и сестры
Puc. 8.15. Представление фигурки с помощью структур слева — ребенок, справа — братья и сестры
Массив m хранит по столбцам 4х4-матрицу преобразования в однородных координатах, как это принято в графической системе OpenGL. При обработке такой структуры в программе сначала выполняется умножение текущей матрицы вида на матрицу, хранящуюся в массиве ш, а затем вызывается функция формирования изображения компонента f(), в которой генерируются нужные графические примитивы. В структуре также хранятся указатель child на аналогичную структуру, представляющую самого левого ребенка, и указатель sibling на структуру, представляющую цепочку расположенных правее его братьев и сестер. Если узлы того или иного вида в иерархии модели отсутствуют, соответствующий указатель имеет значение NULL. Для представления рассмотренной в предыдущем разделе фигурки киборга нам понадобится восемь узлов, соответствующих восьми компонентам фигурки:
treenode torso_node, head_node, lua_node, rua_node,
lll_node, rll_node, lla_node, rla_node, rul_node, lul_node;
В программе эти узлы можно сформировать либо в функции main(), либо в функции myinit(). Рассмотрим, например, корневой узел дерева, представляющий компонент Корпус. Его ориентацию можно изменять, поворачивая вокруг оси у. В OpenGL формирование соответствующей матрицы поворота выполняется функцией glRotatef(), а затем элементы сформированной матрицы копируются в массив m структуры treenode. Реализующий эти операции фрагмент программного кода приведен ниже:
glLoad!dentity();
glRotatef(theta(O), 0.0, 1.0, 0.0);
glGetFloatv(GL_MODELVIEW_MATRIX,torso_node. m);
Формирование изображения компонента выполняется функцией torso(), которая вычерчивает цилиндр. Следовательно, остальная часть программного кода заполнения структуры узла выглядит следующим образом:
torso_node.f = torso;
torso_node.sibling = NULL;
torso_node.child = 6head_node;
Фрагмент программного кода, заполняющий структуру описания узла Левое плечо, выглядит так:
glLoad!dentity();
glTranslatef(-(TORSO_RADIUS+UPPER_ARM_RADIUS),
0.9*TORSO_HEIGHT, 0.0);
glRotatef(theta[3], 1.0, 0.0, 0.0);
glGetFloatv(GL_MODELVIEW_MATRIX,lua_node.m);
lua_node.f = left_upper_arm;
lua_node.sibling = 6rua_node;
lua_node.child = &lla_node;
Для этого узла матрица преобразования есть произведение матрицы сдвига левого плеча относительно фрейма корпуса и матрицы поворота на заданный угол в сочленении. Параметр сдвига фактически определяет положение сочленения с правым плечом на корпусе фигурки. Дочерним для этого узла является узел Левое предплечье, а очередным братом/сестрой — узел Правое плечо. Прочие узлы формируются аналогичными процедурами.
Обход дерева выполняется тем же способом прямого обхода (preorder traversal}, что и в ранее рассмотренном примере, но на сей раз реализуем рекурсивную процедуру обработки узла:
void traverse(treenode* root)
{
if(root==NULL) return;
glPushMatrix();
glMultMatrixf(root->m);
root->f();
if(root->child!=NULL) traverse(root->child);
glPopMatrix();
if(root->sibling!=NULL) traverse(root->sibling);
}
При обработке узла, указатель на который не равен NULL, сначала в стеке сохраняется текущая матрица преобразования, для чего вызывается функция glPushMatrix(). Затем матрица, хранящаяся в структуре узла, модифицирует текущую матрицу вида (предполагается, что соответствующий режим работы с матрицами установлен раньше). Далее формируется изображение компонента, для чего вызывается функция, указатель на которую хранится в члене f структуры узла. Затем следует рекурсивное обращение к этой же функции, но для дочернего узла. Обращаю ваше внимание на то, что перед обращением к обработке дочернего узла в матрице вида уже установлена матрица преобразования текущего узла и именно она будет использована при формировании матрицы вида для отображения дочернего узла. А вот перед переходом к обработке “братского” узла нужно восстановить прежнюю матрицу вида — извлечь ее из стека с помощью функции glPopMatrix(). Если при обработке узла нужно устанавливать новые значения атрибутов, то прежние следует сначала запомнить в стеке, а затем восстановить. Лучше всего это делать в функции отображения компонента либо выполнять эти операции синхронно с записью/извлечением матрицы вида.
Одна из наиболее привлекательных особенностей рассмотренного метода программной реализации обхода такого иерархического дерева состоит в том, что он никак не связан со структурой конкретной модели. В результате в программе можно использовать родовую функцию отображения с обратным вызовом, которая имеет следующий вид:
void display(void) {
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glLoad!dentity();
traverse(&torso_node);
glutSwapBuffers();
}
Файл figuretree.с текста программы отображения подвижной фигурки киборга, в которой используется рекурсивная функция обработки узлов дерева, читатель найдет на ftp- сервере по адресу ftp.cs.unm.edu в каталоге pub/angel/BOOK. Работая с этой программой, пользователь может изменять углы в сочленениях фигурки, вызывая те или иные команды меню, и наблюдать на экране, как движется фигурка. Поэтому все управление динамикой изображения сосредоточено в функции обработки сообщений мыши. Здесь изменяются углы в сочленениях, заново формируются матрицы преобразования для соответствующих узлов, а затем вызывается функция отображения:
void mouse(int btn, int state, int x, int y) {
if(btn==GLUT_LEFT_BUTTON && state == GLUT_DOWN)
theta[angle] += 5.0;
if( theta[angle] > 360.0 ) theta[angle] -= 360.0;
}
if(btn==GLUT_RIGHT_BUTTON && state == GLUT_DOWN)
{
theta[angle] -= 5.0;
if( theta[angle] < 360.0 ) theta[angle] += 360.0;
}
glPushMatrix();
switch(angle)
{
case 0 :
glLoad!dentity();
glRotatef(theta[0], 0.0, 1.0, 0.0);
glGetFloatv(GL_MODELVIEW_MATRIX,torso_node.m);
break;
/* блоки case для других значений индекса angle */
glPopMatrix();
glutPostRedisplay();
}
Для того чтобы продемонстрировать гибкость рассмотренного метода, добавим в программу возможность динамически вставлять и удалять узлы в дерево. Такой динамический узел формируется приведенным ниже фрагментом программы:
typedef treenode* tree_ptr;
tree_ptr torso_ptr;
torso_ptr = malloc(sizeof(treenode));
Заполняется узел, как и в предыдущем варианте, следующим программным кодом:
glLoad!dentity();
glTranslatef(-(TORSO_RADIUS+UPPER_ARM_RADIUS),
0.9*TORSO_HEIGHT, 0.0);
glRotatef(theta[3], 1.0, 0.0, 0.0);
glGetFloatv(GL_MODELVIEW_MATRIX,lua_ptr->m);
lua_ptr->f = left_upper_arm;
lua_ptr->sibling = rua_ptr;
lua_ptr->child = lla_ptr;
Для обхода дерева, состоящего из динамических узлов, рекурсивной функции передается в качестве аргумента указатель на структуру корневого узла дерева:
traverse(torso_ptr);
Конечно, для отображения фигурки киборга использование динамических узлов вряд ли может дать какие-либо преимущества, но в более сложных программах этот подход позволяет формировать модели в интерактивном режиме, по командам пользователя. В частности, на его основе можно разработать приложение, при работе с которым пользователь сможет редактировать структуру сложного объекта, добавляя или удаляя компоненты по своему выбору. Именно на таком принципе создаются программы редактирования сцены, которые мы рассмотрим в разделе 8.8.
Анимация
В обоих рассмотренных примерах используются подвижные, шарнирные {articulated} объекты, которые состоят из компонентов неизменной формы, соединенных подвижными сочленениями — шарнирами. Такие модели несложно заставить двигаться — для этого нужно динамически изменять состояние сочленений, количество которых, как правило, невелико. Иерархическая программная модель позволяет сформировать сложное движение за счет одновременного изменения состояния нескольких сочленений. Но в предыдущих разделах мы не рассматривали вопрос, как организовать в программе управление изменением состояния сочленений во времени, которое воспроизводило бы задуманное пользователем перемещение объекта и позволяло создавать “оживляемые” изображения (выполнять анимацию}.
Особое место среди множества существующих на сегодняшний день методов анимации занимают два метода, один из которых является дальнейшим развитием традиционных технологий создания мультипликационных фильмов, а другой “заимствован” у робототехники.
Рассмотрим проблему перемещения из одной позиции в другую конечной точки руки робота, со структурой которого мы познакомились в разделе 8.3. Как вы, надеюсь, помните, робот состоит из трех звеньев, связанных тремя сочленениями (считая и сочленение с неподвижным основанием), и, следовательно, имеет три степени свободы. Для каждого сочетания углов поворота в сочленениях (в робототехнике они называются обобщенными координатами робота) несложно определить положение конечной точки руки в декартовом пространстве, но выполнить обратный расчет значительно сложнее. Возможны случаи, когда рука не может прийти в заданную точку ни при каких значениях обобщенных координат, заданное положение достигается при единственном значении набора обобщенных координат или при двух, или нескольких значениях набора.
Задача отыскания набора обобщенных координат, позволяющего привести характеристическую точку робота (как правило, конечную точку руки) в желаемое положение, в робототехнике получила название обратной задачи кинематики {inverse kinematics problem}. Раньше мы имели дело с прямой задачей кинематики {direct kinematics problem}, суть которой — определить положение характеристической точки в декартовом пространстве по заданным обобщенным координатам. В робототехнике прямая задача решается либо на основе той же самой иерархической модели объединения звеньев робота, либо с помощью записанных в явном виде тригонометрических соотношений между углами поворота в сочленениях и декартовыми координатами характеристической точки. Если вектор обобщенных координат выразить через 0, а через р — положение характеристической точки в декартовом пространстве, то прямое уравнение кинематики можно записать в виде
Р -/в).
Аналогично, задавшись скоростями изменения обобщенных координат (углов в сочленениях), можно вычислить вектор скорости характеристической точки.
В кинематической модели робота не учитываются факторы, которые проявляются в динамике, — инерция и трение. Можно вывести уравнение динамики робота, в котором учитываются обобщенные силы, — эта тема активно исследуется в робототехнике.
Но нас больше интересуют обратные задачи кинематики и динамики. Для определенных структур объединения звеньев в единый механизм функция
в =g(p)
может быть неоднозначной или иметь точки разрыва. Задача осложняется еще и тем, что нам не только нужно найти сочетание углов поворота в сочленениях, которое обеспечит требуемое положение характеристической точки, но и такую траекторию движения робота из текущего состояния в целевое, которая обеспечит обход препятствий — других объектов, распо-
ложенных в рабочей зоне робота. Существует и множество других ограничений, которым должна удовлетворять сформированная траектория.
В явном виде обратные задачи удается решить только для таких простых кинематических структур, как у нашего трехстепенного робота. Более сложные кинематические структуры требуют иных подходов, значительно более сложных, в чем нетрудно убедиться, рассматривая кинематическую структуру фигурки киборга.
В системах компьютерной графики, где не требуются учет всех физических факторов и высокая точность, для решения обратной задачи кинематики может быть использован подход, заимствованный у создателей рисованных мультипликационных фильмов. Этот подход предусматривает формирование так называемых ключевых кадров (key-frame), которыми художник-мультипликатор “размечает” траекторию сложного движения персонажа. Затем создаются кадры, отображающие промежуточные фазы движения. В компьютерной графике этот процесс несложно реализовать, используя линейную интерполяцию в пространстве обобщенных координат шарнирного механизма. Можно применять и более сложные виды интерполяции, в частности непрерывными сплайнами, о которых речь пойдет в главе 10. Сплайн-интерполяция обеспечивает гладкий переход между ключевыми кадрами.
Эту идею можно реализовать разными методами, с которыми вы сможете ознакомиться в специальной литературе. Частично перечень таких источников приведен в последнем разделе данной главы. Например, можно изменять не только положение, но и форму отдельных объектов в последовательных ключевых кадрах и выполнять интерполяцию параметров примитивов, определяющих форму трансформируемого объекта. Эта технология получила название .морфинга (morphing). Основная сложность при реализации морфинга— правильно сопоставить вершины модифицируемого объекта в последовательных ключевых кадрах и подобрать метод удаления “лишних” вершин или формирования новых (при необходимости).
Графические объекты
Мы познакомили вас со множеством парадигм организации графических систем, но основное внимание в этой книге все же уделяется конвейерной реализации модели синтезированной камеры. Это сделано по той простой причине, что именно эта архитектура в настоящее время поддерживается наиболее распространенными и доступными программными и аппаратными средствами. Более того, в подавляющем большинстве примеров программ мы используем режим немедленного воспроизведения (immediate-mode), который предполагает, что объект отображается на экране сразу же после выполнения фрагмента программы, формирующего этот объект. После того как объект появится на экране, никаких его следов в памяти программы не остается. Любые изменения атрибутов объекта или его положения требуют повторного выполнения фрагмента прикладной программы, формирующего этот объект.
Кроме того, желание познакомить читателей с основными методиками реализации функций графической системы заставляет нас представлять программы, в которых детали реализации методов видны довольно отчетливо. Этот подход, несмотря на довольно очевидные методические достоинства, не позволяет нам продемонстрировать альтернативные методы разработки графических приложений, основанные на более высоком уровне абстракции.
В этом разделе мы постараемся восполнить этот пробел и познакомим вас с двумя концепциями создания именно таких высокоуровневых графических приложений. Во-первых, мы расширим понятие объекта и будем рассматривать не только геометрические объекты, такие как многоугольники и векторы, но и другие элементы графических программ — камеры (наблюдателей), источники света и свойства материалов. Во-вторых, мы остановимся на таких объектах, которые продолжают существовать в программе и после того, как их образ появится на экране. Более того, такие объекты могут существовать в программе, ни-
когда не появляясь на экране. Выше уже говорилось о том, что такие объекты можно формировать, используя дисплейные списки, а в этой главе мы рассмотрим альтернативные подходы, основанные на использовании классов языка C++ или структурных типов языка С. Хотя существующие на сегодняшний день версии API OpenGL не поддерживают объектно-ориентированную технологию в явном виде, мы отнюдь не собираемся порывать с этой графической системой. Она будет использоваться по своему прямому назначению — формировать изображения объектов на экране. Программы, о которых пойдет речь в этом разделе, образуют своеобразную надстройку над OpenGL.
Методы, атрибуты и сообщения
Создаваемые нами программы манипулируют данными. Данные могут представлять различную информацию — от чисел и строк символов до информации о составных геометрических объектах, сформированных в приложении. Традиционная процедурная технология программирования предполагает, что программист разрабатывает программный код, в котором манипулирование данными выполняется набором функций или процедур. Данные передаются в функцию через список аргументов и возвращаются аналогичным образом. Для того чтобы функция могла корректно оперировать с данными, она должна знать, как эти данные организованы. Рассмотрим, например, куб, который мы не раз использовали в предыдущих примерах. Уже не раз отмечалось, что его можно моделировать разными способами — с помощью указателей на вершины, списка ребер или списка вершин многоугольников граней. Программист, разрабатывающий прикладную программу, не очень-то хочет входить во все подробности конкретной реализации модели куба— он предпочитает рассматривать его как некую атомарную сущность или объект. Более того, прикладному программисту даже не желательно уделять внимание тому, каким образом формируется образ куба на экране, какая модель закрашивания и алгоритм заливки многоугольников при этом используются. Программист скорее предпочел бы, чтобы куб “сам знал”, как отобразить себя на экране, т.е. чтобы алгоритм отображения уже был “встроен” в этот куб, а ему (программисту) оставалось бы только вызвать этот алгоритм в том месте прикладной программы, где этот куб следует включить в изображение. В определенной мере система OpenGL поддерживает такую точку зрения, используя для управления процессом формирования изображения параметры состояния графической системы. Например, цвет куба, его ориентация и освещение граней могут рассматриваться как компоненты текущего состояния и, следовательно, не зависеть от способа моделирования конкретного куба.
Однако такой взгляд на вещи несколько расходится с привычной методикой работы с физическим кубом. Положение куба, его цвет, размер и ориентация кажутся нам тесно связанными с самим физическим объектом. Хотя в OpenGL и можно связать с виртуальным кубом некоторые свойства — для этого можно воспользоваться технологией записи в стек и извлечения из стека матриц и атрибутов, — сама по себе программная модель объекта не поддерживает этой идеи, а описанная технология выглядит довольно искусственной. Например, процедуры преобразования куба, отличные от функций работы с однородными координатами, должны точно “знать”, как организована модель куба в программе, и будут работать по схеме, показанной на рис. 8.16.
Рис. 8.16. Парадигма процедурного программирования
Программист разрабатывает программу, которая принимает в качестве аргументов указатель на данные о кубе и список параметров преобразования. Получив эту информацию, программа должна что-то сделать с описанием куба и вернуть затем управление прикладной программе, ее вызвавшей. При этом, возможно, в вызывающую программу будут переданы какие-то дополнительные данные.
Совершенно другую технологию манипулирования объектами предлагает объектно-ориентированный подход к программированию. Еще на ранней стадии становления этого подхода авторы Smalltalk, одного из первых объектно-ориентированных языков программирования, обратили внимание на то, что компьютерная графика является одной из наиболее многообещающих областей приложения этой новой идеи. Сейчас одной из доминирующих тенденций в развитии компьютерной графики является симбиоз конвейерной архитектуры и объектно-ориентированного подхода, который позволит создавать еще более мощные графические системы.
В объектно-ориентированных языках программирования объекты играют роль модулей, строительных блоков, из которых строится программа. Такой модуль включает данные, характеризующие объект, например вершины нашего куба, его свойства (в нашей терминологии — атрибуты) и функции (.методы) манипулирования данными объекта и его атрибутами. Для вызова того или иного метода объекта ему нужно послать сообщение. Такая модель представлена на рис. 8.17.
Рис. 8.17. Объектно-ориентированная парадигма программирования
Специалиста, занимающегося разработкой прикладной программы, такой подход избавляет от необходимости вдаваться в какие-либо подробности внутреннего представления объекта, в нашем случае — куба. Единственное, что его теперь интересует, — спецификация методов, которые этот объект поддерживает (или которыми располагает). Именно эти спецификации и определяют формат сообщений, передаваемых объекту прикладной программой.
Структурные типы в языке С обладают определенными чертами объекта в том понимании, которое вкладывает в него парадигма объектно-ориентированного программирования, но далеко не всеми. В языке C++ на смену структурному типу struct пришел тип class. Классы C++ позволяют в полной мере воспользоваться всеми преимуществами объектноориентированного подхода, причем для тех, кто имеет достаточный опыт работы с языком С, переход на новый язык не представляет особого труда.
Объект cube
Пусть перед нами стоит задача определить на языке С объект, представляющий куб, который обладает атрибутом цвета и характеризуется определенным преобразованием в однородных координатах. Воспользуемся для этого структурным типом языка С: struct cube {
float color[3];
float matrix[4][4];
/* здесь будут размещены данные, описывающие собственно куб */
}
Та часть определения, в которой представлены данные о структуре куба, как правило, мало интересует программиста прикладных программ. Глядя с его “колокольни”, интерес представляют только атрибуты цвета и матрица преобразования, определяющая положение и ориентацию куба (и, возможно, его размеры).
Если в программе определен структурный тип cube, то экземпляры (instances') объекта куба создаются в программе точно так же, как и переменные базовых типов: cube а, Ь;
Атрибуты, которые являются частью определения класса, можно изменять индивидуально для каждого экземпляра куба. Если, например, нужно установить для куба а красный цвет, то это выполняется следующими операторами:
a.color[0]=1.0;
а.color[1]=а.color[2]=0.О;
В OpenGL-программе атрибуты созданного объекта используются так же, как обычные переменные. Например, в программе, формирующей изображение куба, вы наверняка встретите оператор установки текущего цвета
glColor3fv(a.color);
Но хотя таким способом мы и создали в программе объект, сохраняющийся и после его вывода на экран, возможности манипулирования им довольно ограничены. Как правило, это отдельные функции. Например, обращение к функции формирования изображения куба будет выглядеть примерно так:
render_cube(а);
Для поворота куба в пространстве сцены можно использовать функцию rotate_cube(а, theta, d);
где d — массив, представляющий вектор оси поворота.
Такой способ реализации объекта создает определенные неудобства. Одно из них состоит в том, что нужно разработать отдельные функции отображения и поворота для каждого типа объектов, существующих в приложении.
Язык C++ предлагает другую методику организации объектов. Классы в C++ могут содержать общедоступные и закрытые члены. Общедоступные члены (public members) аналогичны членам структурного типа struct в языке С, и к ним возможен доступ извне объекта, т.е. со стороны прикладной программы. Закрытые члены (private members) невидимы извне и могут быть изменены только функциями, которые сами являются член*.».и этого класса. Таким образом, определение объекта cube на языке C++ будет иметь следующий вид: class cube {
public:
float color[3];
float matrix[4][4];
private:
/* здесь будут размещены члены данных, описывающие собственно куб */ }
В объекте такого типа детали реализации внутренней структуры куба скрыты от прикладной программы. Класс C++ может располагать встроенными функциями — членами класса или .методами класса. Собственно в этом и есть одно из главных отличий класса C++ от структурного типа С. Добавим в определение класса cube общедоступные методы:
public:•
void render();
void translate(float x, float y, float z);
void rotate(float theta, float axis_x, float axis_y, float axis_z);
Обращаясь к этим методам, прикладная программа сможет создавать, сдвигать, поворачивать и отображать объект с помощью следующих операторов:
cube а;
a.rotate(45.0, 1.0, 0.0, 0.0);
a.translate(1.0, 2.0, 3.0);
a.render();
Концептуально этот код предполагает, что, когда вызывается метод a.rotate(..), экземпляр объекта а сам “знает”, как “поворачиваться”. Можно считать, что, вызывая этот метод, прикладная программа посылает экземпляру а класса cube сообщение. Зная особенности графической системы OpenGL, несложно разработать программный код реализации методов rotate(), translate() и пр. Тут следует отметить одну интересную особенность. Разработкой системы классов и прикладной программы, работающей с этими классами, могут заниматься разные специалисты. Более того, библиотека классов может быть создана в третьей организации и распространяться как коммерческий продукт, доступный любому желающему. Программист, разрабатывающий прикладную программу, нуждается только в описаниях классов и их методов, т.е. он должен располагать информацией о том, к какому результату приведет вызов того или иного метода класса, и его может совершенно не интересовать, как именно будет это выполнено.
Итак, задумаемся над вопросом, к какому результату должен привести вызов метода render () или метода rotate () класса cube в программе, работающей в среде системы OpenGL? Прежде всего, нужно четко представлять, что переход на объектно-ориентированное программирование в графической системе OpenGL означает отказ от режима немедленного вывода изображения. Каждый экземпляр какого-либо графического объекта существует в программе сколь угодно долго и в любой момент может быть изменен соответствующими методами. Одним из способов реализации таких “долгоживущих” экземпляров в OpenGL является использование дисплейного списка, но возможны и другие. Что для нас важнее всего, так это то, что после создания экземпляра объекта он где-то продолжает существовать в программе, причем его внутренняя структура может быть совершенно неизвестна прикладной программе.
Атрибуты объекта можно изменять специальными методами, подобными rotate(). Метод render () формирует изображение объекта, используя при этом не столько текущее состояние графической системы, сколько значения внутренних атрибутов экземпляра, включая и матрицу преобразования. В OpenGL в реализации метода render () вы, скорее всего, встретите такую последовательность операторов: glBegin(GL_POLYGON)
glVertex3f()
Координаты вершин, необходимые для формирования образа многоугольника, хранятся в тех членах класса, которые находятся в секции private. Возможен и другой вариант — метод будет запускать процедуру повторного вывода изображения фрагмента дисплейного списка, включающего экземпляр объекта.
Объекты и иерархия
Одним из основных достоинств объектно-ориентированной технологии программирования является возможность повторного использования программного кода и построения более сложных объектов из небольшого набора простых. В разделе 8.4 была рассмотрена иерархическая структура фигурки киборга. Пользуясь объектно-ориентированной технологией, можно построить объект такой фигурки, состоящей из цилиндров, причем каждый экземпляр фигурки может иметь собственный цвет, размеры, положение и ориентацию в пространстве сцены. Класс фигурки будет ссылаться на классы рук и ног. В приложениии, формирующем изображение автомобиля, класс автомобиля будет ссылаться на классы колес и кузова. Следовательно, в системе классов мы будем иметь древовидное представление структуры объекта, в чем-то сходное с рассмотренным в разделе 8.5.
При работе с классами часто возникает необходимость представить более сложные отношения между объектами, чем отношение “родитель-потомок”. Поскольку мы используем древовидные структуры, т.е. такие, в которых самый верхний уровень иерархии принадлежит корневому объекту, то отношения “родитель-потомок” в такой структуре имеют скорее смысл “обладает...”. Так, наша фигурка киборга “обладает” двумя руками и двумя ногами, а автомобиль “обладает” кузовом и четырьмя колесами.
Можно и по-другому рассматривать иерархию объектов, расположив простейшие из них на самом верхнем уровне. В таком случае между объектами более верхнего уровня и связанными с ними объектами нижних уровней существуют отношения “принадлежит к...”. Такой тип иерархии присущ таксономии (системы классификации). Примат принадлежит к животным, а человек — к приматам. Такая иерархия уже использовалась раньше при рассмотрении методов проецирования. Параллельная проекция принадлежит к проекциям на плоскость, а косоугольная проекция принадлежит к параллельным. Отношение “принадлежит к...” позволяет описать, как сложный объект состоит из более простых, а также организовать наследование сложным объектом свойств более простых. Таким образом, если разработать программу обобщенного параллельного проецирования, то программа косоугольного проецирования может использовать фрагменты ранее разработанной программы и изменить только те ее части, которые касаются специфики косоугольного варианта параллельного проецирования. При работе с геометрическими объектами можно определить базовый класс, в котором имеется набор свойств, присущих всем геометрическим объектам данного вида, например цвет и свойства материала. В классах объектов частного вида можно использовать этот же набор свойств или добавить к нему новые, специфичные для данного вида объекта. Эта концепция поддерживается практически во всех объектно-ориентированных языках, таких как C++, в виде механизма наследования классов.
Геометрические объекты
А теперь рассмотрим, как можно реализовать идеи объектно-ориентированного программирования в графической системе. Прежде всего, следует задуматься над вопросом, а какие объекты (классы) имеет смысл включить в такую систему? Совершенно очевидно, что в ней должны присутствовать классы точек, векторов, многоугольников, прямоугольников, треугольников (два последних, скорее всего, должны быть оформлены как подклассы класса “многоугольник”). Менее очевидно, как следует поступить с атрибутами, источниками света и камерами (наблюдателями). Например, неясно, стоит ли ассоциировать свойства материалов с объектом, таким как куб, или они должны представлять собой отдельный класс? Имеет право на существование и первый, и второй варианты. Можно так определить класс куба, что в нем будут отдельные члены, представляющие оптические свойства для фоновой, диффузной и зеркальной составляющих отраженного света, которые используются в модели Фонга,
описанной в главе 6. Альтернативный вариант— объявить отдельный класс оптических свойств материала, например, в таком виде:
class material
{
public:
float specular[3];
float shininess;
float diffuse[3 ];
float ambient[3];
}
Можно связать свойства материала с геометрическим объектом с помощью соответствующего метода класса геометрического объекта:
cube а;
material b;
a.setMaterial(b);
Источники света относятся к геометрическим объектам, поскольку обладают такими характеристиками, как положение и ориентация. Объявление класса источника света может выглядеть так:
class light {
public:
boolean type;
boolean near;
float position!3];
float orientation!3];
float specular!3];
float diffuse!3];
float ambient[3];
}
Определив набор классов геометрических объектов, мы сможем описать сцену, включающую экземпляры таких объектов. Описание сцены также удобно оформить в виде древовидной структуры. Эту новую структуру будем называть графом сцены (scene graph).
Граф сцены
Мы считаем сцену совокупностью геометрических объектов, образы которых должны появиться на экране, и объектов других типов (камеры и источники освещения), влияющих на то, как будут выглядеть геометрические объекты. Оба типа объектов характеризуются определенными геометрическими параметрами и негеометрическими атрибутами, в частности цветом. Между всеми объектами сцены существуют определенные иерархические отношения. Например, в тот момент, когда примитив объявляется в программе, текущие параметры камеры используются для формирования образа этого примитива. Если в дальнейшем изменить параметры камеры, например фокусное расстояние объектива, и создать новый геометрический объект (пусть даже такой же формы), его образ будет отличаться от образа первого объекта. Хотя с помощью реальной фото- или кинокамеры такое изображение получить невозможно, компьютер позволяет нам проделывать еще и не такие фокусы. Для описания отношений между объектами сцены — геометрическими, камерой и источниками света — мы можем использовать все ту же древовидную структуру графа.
Поскольку методы программирования обхода дерева в графической программе уже были описаны выше, нам остается только задуматься над изменением структуры описания узла. Помимо матрицы преобразования экземпляра и указателя на функцию отображения объекта в нее необходимо включить еще и разнообразные атрибуты объекта. Но есть и другой вариант— включить в дерево узлы новых типов, один из которых будет определять атрибуты, а другой — матрицы преобразования. Рассмотрим дерево (рис. 8.18), в котором присутствуют отдельные узлы цвета, матриц вида и специальные узлы разветвления. Узлы разветвления служат для программы обхода своего рода “дорожными знаками”, указывающими, что в этом месте нужно сохранить текущее состояние и таким образом изолировать состояние поддерева, которое начинается после этого узла, от остального дерева. OpenGL-программа обхода дерева, в которой используется метод прямого обхода, будет выглядеть примерно так: glPushAttrib glPushMatrix glColor glTranslate glRotate objectl() glTranslate object2() glPopMatrix glPushMatrix glTranslate glRotate objects() glPopMatrix glPopAttrib
Узлам-разделителям в этом фрагменте соответствуют вызовы функций glPushMatrix () и glPopMatrix (). При входе в этот фрагмент текущие атрибуты и матрица вида сохраняются, а перед завершением программы они восстанавливаются. Программа устанавливает цвет, который используется при формировании образов всех прочих объектов сцены, и обходит дерево примерно так же, как это делалось в рассмотренном выше примере отображения фигурки киборга.
г~Цветг~Цвет1
г~
Цвет
г~
Цвет
Объект 3
Рис. 8.18. Дерево сцены
Поскольку стеки атрибутов и матриц можно использовать для сохранения параметров визуализации, то в дерево можно включить и узлы, представляющие камеру. Хотя вряд ли в каком-то приложении понадобится формировать изображение, в котором каждый объект будет использовать свою камеру, но вполне возможен вариант, когда на одном экране (но в разных видовых окнах) сцена отображается разными камерами, т.е. с разных точек зрения. Вот такое изображение и можно сформировать, используя в дереве сцены несколько камер.
Граф сцены, который мы только что рассмотрели, эквивалентен тексту OpenGL-npor-
раммы в том смысле, что можно чисто механически написать текст программы, имея перед глазами этот граф. Такой подход реализован в надстройке над OpenGL — объектно-ориентированном API Open Inventor. Программой для Open Inventor является описание дерева сцены. Выполнение программы организовано как обход графа сцены и вызов необходимых функций OpenGL.
Граф сцены очень хорошо сочетается с идеями объектно-ориентированного программирования. Все объекты, представленные узлами графа (примитивы, атрибуты, преобразова
ния), можно реализовать в виде отдельных классов и манипулировать экземплярами этих классов в программе обхода графа. Более того, такой подход позволяет легко организовать анимацию. Изменяя определенные параметры отдельных объектов, можно получать последовательные фазы движения персонажей, повторно запускать после этого программу обхода графа и выводить на экран следующий кадр мультфильма. В Open Inventor программные объекты реализованы с помощью функций OpenGL, а граф сцены представляет собой базу данных, которая включает все элементы сцены. OpenGL выступает в роли средства создания изображения, но не принимает никакого участия в спецификации сцены. В этом смысле Open Inventor очень напоминает систему PHIGS: задача прикладной программы в этой графической системе состоит в том, чтобы поместить описания объектов в базу данных. Часть базы данных может быть отослана на рабочую графическую станцию, которая и берет на себя все заботы, связанные с формированием изображения сцены.
Программные графические системы занимают один из уровней в иерархии, представленной на рис. 8.19.
Рис. 8.19. Современная архитектура графических системРис. 8.19. Современная архитектура графических системТакие системы, в частности OpenGL, берут на себя функции формирования изображения и могут управлять либо непосредственно аппаратными средствами отображения, либо программными средствами более нижнего уровня иерархии. В качестве последних могут использоваться программные продукты, функциональные возможности которых аналогичны программе DirectX фирмы Microsoft. Прикладная программа занимает в этой иерархии самый верхний уровень.
Рис. 8.19. Современная архитектура графических систем
Рис. 8.19. Современная архитектура графических систем
Другие типы древовидных структур
Деревья и ориентированные ациклические графы являются не только мощным средством описания сцен, но и находят широкое применение при решении других проблем компьютерной графики. В этом разделе будут рассмотрены три из них: использование деревьев для описания структуры сложных сплошных (solid) объектов в рамках конструктивной геометрии тел и два подхода к описанию пространственной иерархии, которые позволяют значительно повысить эффективность алгоритмов отображения.
Деревья в конструктивной геометрии тел
Полигональное представление геометрических объектов, которое мы использовали до сих пор, имеет не только множество достоинств, но и ряд недостатков. Наиболее серьезным из них является то, что многоугольники — базовый элемент полигонального представления — позволяют описывать только “оболочку” вокруг какого-либо объема, а не собственно объем. Этот недостаток особенно отчетливо проявляется в системах автоматизации проектирования, где проектировщика интересует не только внешняя форма объекта, но и его объемные свой-
ства — масса, моменты инерции и т.п. Кроме того, представление пространственного объекта при отображении ребрами или гранями может стать причиной неоднозначной интерпретации пользователем предъявляемого образа. Например, “проволочное” изображение объекта, представленное на рис. 8.20, можно интерпретировать и как куб, в котором высверлено отверстие, и как узел, составленный из двух деталей.
Рис. 8.20. Проволочное изображение объекта, которое может быть неоднозначно интерпретировано наблюдателемРис. 8.20. Проволочное изображение объекта, которое может быть неоднозначно интерпретировано наблюдателемКонструктивная геометрия тел (CSG — constructive solid geometry) — направление в компьютерной графике, призванное решать подобные проблемы. В рамках этого направления в качестве примитивов используются сплошные геометрические тела канонических типов — параллелепипед, цилиндр, сфера и т.п. Атрибуты объектов включают как оптические свойства их поверхностей (фактуру, отражательные свойства, цвет и т.п.), так и объемные свойства— размеры и плотность. При описании сцены, состоящей из таких объектов, принимаются во внимание точки внутреннего подпространства, принадлежащего объектам, и, наоборот, объекты рассматриваются как совокупности (множества) определенных точек пространства, из которых (множеств) с помощью операций алгебры множеств можно формировать другие множества — новые объекты.
Рис. 8.20. Проволочное изображение объекта, которое может быть неоднозначно интерпретировано наблюдателем
Рис. 8.20. Проволочное изображение объекта, которое может быть неоднозначно интерпретировано наблюдателем
Методика моделирования в конструктивной объемной геометрии основана на использовании трех основных операций алгебры множеств1.
объединение (union) двух множеств А и В, которое записывается в форме А и В, — есть новое множество, включающее все элементы (точки) обоих исходных множеств А и В;
пересечение (intersection) множеств А и В (А п В) представляет собой множество всех точек, которые одновременно входят и в множество А, и в множество В;
операция вычитания множеств (set difference), A-В, формирует новое множество, состоящее только из тех точек множества/}, которые не принадлежат множеству В.
На рис. 8.21 показаны два исходных объекта и три объекта, сформированных из них разными операциями теории множеств.
Рис 8.21. Операции теории множеств
Эти операции описывают объекты, созданные из примитивов (или других объектов, созданных ранее из примитивов). На рис. 8.22 показаны четыре примитива и объект, описываемый выражением (.4-5) п (Си D).
Рис. 8.22. Формирование объекта в конструктивной геометрии телРис. 8.22. Формирование объекта в конструктивной геометрии тел
Рис. 8.22. Формирование объекта в конструктивной геометрии тел
Рис. 8.22. Формирование объекта в конструктивной геометрии тел
Рис. 8.23. Дерево выражения, формирующего объект методами конструктивной геометрии телРис. 8.23. Дерево выражения, формирующего объект методами конструктивной геометрии тел
Рис. 8.23. Дерево выражения, формирующего объект методами конструктивной геометрии тел
Рис. 8.23. Дерево выражения, формирующего объект методами конструктивной геометрии тел
Как правило, для обработки алгебраических выражений используется дерево операций, внутренние узлы которого представляют операции, а терминальные — операнды (примитивы и объекты, созданные другими выражениями). Выражение (.4-5) n (С u D) будет представлено в графической системе деревом, показанным на рис. 8.23. Для обхода такого дерева и вычисления выражения используется метод обратного Vpost-order) обхода. Суть его состоит в том, что дерево обрабатывается рекурсивно — сначала левое поддерево, исходящее из узла, а потом правое поддерево, исходящее из узла. Результаты обработки каждого поддерева используются в качестве операндов при выполнении операции (как правило, двухместной), специфицированной в самом узле.
Изображение моделей, созданных с помощью конструктивной геометрии тел, формируется, как правило, с помощью метода трассировки лучей (см. упр. 8.10).
Читатель вправе задать вопрос, а чем же кардинально отличается полигональное представление объекта от представления такого же по форме объекта в рамках конструктивной геометрии тел? Отличие— в представлении объема объекта. При полигональном представлении объем, как таковой, не существует. Существуют только участки поверхностей, которые могут ограничивать какой-то объем, но, в принципе, это совсем не обязательно. В конструктивной геометрии тел существуют только объемы, а поверхности — это только границы объемов. Проще всего уяснить себе суть этого отличия на простом примере. Пусть необходимо смоделировать некоторый реальный объект, который имеет форму параллелепипеда. С точки зрения полигонального представления параллелепипед— это полый ящик, стенки которого сделаны из какого-либо листового материала, причем достаточно тонкого, в идеале — нулевой толщины (картон, фанера, жесть и т.п.). Этот ящик может быть закрытым (состоять из шести граней) или открытым (состоять из пяти или даже четырех граней). На экране образ открытого и закрытого ящиков будет выглядеть по-разному, если по отношению к этому образу будет применен алгоритм удаления невидимых поверхностей, но проволочное изображение закрытого ящика нельзя будет отличить от изображения открытого. В конструктивной геометрии тел ящик, как таковой, смоделировать очень трудно. В этом варианте модели примитивом является именно параллелепипед— брусок из какого-либо материала (дерева, металла, пластмассы и т.п.). Объ-
ем бруска — основная характеристика этого объекта. Если же нам нужен именно ящик, то в конструктивной геометрии тел он создается из параллелепипедов конечной толщины, представляющих отдельные стенки (например, тарные дощечки). Нужно будет “сбить” из таких дощечек ящик точно так же, как это делает столяр. Объем ящика — сумма объемов отдельных дощечек, естественно, с учетом их толщины. При этом никто не заставляет конструктора делать ящик из дощечек одинаковой толщины — на дно можно пустить доски потолще, а боковые стенки сделать, например, из фанеры. Чтобы определить объем внутреннего пространства такого ящика, его придется чем-то “заполнить”. Эта процедура выполняется с помощью все тех же базовых операций теории множеств. С подобной проблемой часто встречаются конструкторы суден. Корпус судна— это своеобразный ящик (хотя и криволинейный), а водоизмещение— это масса воды, заполняющей часть этого ящика (по ватерлинию). Конструктора интересует как объем (масса) самого корпуса, так и его водоизмещение. Решить эту задачу можно только в рамках конструктивной геометрии тел, хотя отобразить на экране форму корпуса вполне возможно и с помощью полигонального моделирования.
Бинарные деревья разделения пространства
Деревья, представляющие графы сцен и объекты в конструктивной геометрии тел, описывают иерархические отношения между компонентами объектов. Эти деревья можно использовать для описания мирового пространства объектов и инкапсулировать в них пространственные отношения между группами объектов. Один из подходов к представлению пространственной иерархии объектов основан на том очевидном факте, что плоскость разделяет пространство на две части — два подпространства. Полученные подпространства можно затем делить плоскостями на подпространства все меньших размеров. Наглядно это можно представить в двухмерном случае, когда пространство — двухмерная плоскость — делится прямыми.
Рассмотрим многоугольники и наблюдателя, показанных на рис. 8.24.
Рис. 8.24. Наблюдатель и множество .многоугольников
Как мы показали в главе 7, существует такой порядок включения образов многоугольников в суммарное изображение сцены, который обеспечивает последовательное перекрытие образов одних многоугольников другими, и в результате создается корректное изображение всей совокупности. Эту идею можно реализовать не только с помощью метода сортировки, но и с помощью дерева, представляющего пространственные отношения между многоугольниками в совокупности. Процесс формирования дерева начинается с разделения пространства сцены плоскостью одного из многоугольников, причем, какого именно, принципиального значения не имеет. В результате такого разбиения все многоугольники оказываются разделены на две группы — одни принадлежат тому полупространству, которое находится перед разделяющей плоскостью, а другие принадлежат полупространству, которое находится за
разделяющей плоскостью. Например, рассмотрим простую сцену, в которой все многоугольники параллельны и ориентированы таким образом, что их нормали направлены вдоль оси z. Это предположение не влияет на общность метода, а только упрощает его понимание и графическое представление. Если многоугольники не параллельны и разделяющая плоскость пересекает какие-либо из них, то последние разбиваются на два многоугольника, один из которых включается в первую группу, а второй — во вторую. Вид на такое пространство со стороны координатной оси у показан на рис. 8.25.
D
А
Рис. 8.25. Вид сверху на разделяемые многоугольники
Рис. 8.26. Бинарное дерево разделения пространстваРис. 8.26. Бинарное дерево разделения пространстваПлоскость многоугольника А разделяет все многоугольники на две группы, одна из которых содержит В и С (они находятся перед плоскостью А), а вторая — многоугольники D, Е и F. которые находятся за плоскостью А. С этой плоскости мы начнем формирование бинарного дерева разделения пространства (BSP— binary spatial-partition tree\ Узлы этого дерева представляют плоскости, разделяющие пространство (плоскости многоугольников, входящих в состав сцены), а ребра задают последовательность разделения. В корне бинарного дерева на рис. 8.26 находится многоугольник А, многоугольники В и С принадлежат левому поддереву, a D, Е и F — правому. Просматривая дерево рекурсивно, приходим к выводу, что С расположен за плоскостью многоугольника В, а в правом поддереве плоскость D разделяет многоугольники Е и F. Обращаю ваше внимание на то, что одна и та же конфигурация многоугольников может быть по-разному представлена с помощью бинарного дерева пространственного разбиения. Форма дерева существенно зависит от порядка разбиения, т.е. порядка, в котором обрабатываются многоугольники в ходе формирования дерева. В общем случае, если обнаруживается, что плоскость некоторого многоугольника разрезает другие многоугольники, пересекаемый многоугольник разделяется на два, первый из которых помещается в левое поддерево, а второй — в правое, подобно тому, как мы поступали в главе 7 при реализации алгоритма сортировки.
Рис. 8.26. Бинарное дерево разделения пространства
Рис. 8.26. Бинарное дерево разделения пространства
Это дерево может использоваться при выводе на экран образов многоугольников, причем для обхода дерева используется алгоритм обратного обхода в центрированном порядке (backward in-order traversal). В соответствии с этим алгоритмом дерево обходится рекурсивно. В каждом цикле сначала “посещается” правое поддерево, потом корень, а затем левое поддерево.
Одно из главных достоинств бинарных деревьев разделения пространства состоит в том, что одно и то же дерево можно использовать при формировании изображения, видимого разными наблюдателями (с разных точек зрения). Наблюдатель может переместиться, например, в противоположный угол сцены, как показано на рис. 8.27, а мы сможем сформировать порядок вывода образов многоугольников, обходя дерево с помощью стандартного центрированного порядка обхода, — левое поддерево, корень, правое поддерево (когда наблюдатель располагался так, как на рис. 8.24, мы использовали обратный центрированный порядок). Обращаю ваше внимание также на то, что алгоритм выполняется рекурсивно во всех тех случаях, когда можно разделить множество многоугольников или любых объектов на группы, называемые кластерами (clusters). Следовательно, сгруппировав многоугольники в многогранник, можно затем сгруппировать многогранники в кластеры и рекурсивно применять алгоритм к кластерам. В приложениях типа симуляторов полета, для которых модель остается практически неизменной в процессе работы программы, а меняется в основном только положение наблюдателя, использование бинарных деревьев рассмотренного типа позволяет с высокой скоростью формировать изображение, которое “видит” пилот из кабины летящего самолета. Дерево содержит всю информацию об объектах сцены и их расположении относительно друг друга, а положение наблюдателя определяет выбор алгоритма обхода дерева.
Рис. 8.27. Перемещение наблюдателя относительно объектов сцены
4-арное и 8-арное деревья
Поскольку в качестве плоскостей разбиения в ходе формирования бинарных деревьев разделения пространства используются плоскости многоугольников, эти плоскости могут иметь произвольную ориентацию. Но расчет положения точки относительно произвольной плоскости занимает довольно много времени, учитывая то огромное количество точек, которые нужно обработать при построении дерева. Справиться с этой проблемой позволяет использование 8-арных (octree) и 4-арных (quadtree) деревьев, поскольку при их построении используются разделяющие плоскости, параллельные координатным. Рассмотрим эти типы деревьев применительно к двухмерному пространству (рис. 8.28).
Предположим, что изображение состоит из пикселей только двух цветов — черных и белых, — которые сформированы каким-то алгоритмом отображения трехмерной сцены. Чтобы сохранить сцену, это изображение нужно поместить в двоичный массив. Но можно поступить и по-другому, учитывая согласованность в расположении групп пикселей. Можно провести две прямые, как на рис. 8.29, и разделить всю область на четыре квадранта. Обратите внима-
ние — один квадрант заполнен только белыми пикселями, а в остальных есть пиксели и того и другого цвета. Поскольку один квадрант полностью состоит из пикселей белого цвета, то этот цвет можно назначить всему квадранту. Три остальных квадранта придется опять разделить и продолжать операцию рекурсивно с каждым из них таким же образом, пока в результате последовательных разбиений не окажется, что какая-либо из областей полностью заполнена пикселями одного цвета. Вся информация сохраняется в дереве, которое получило название 4-арного (quadtree). Уровни такого дерева соответствуют последовательным разбиениям, а каждый узел имеет четырех потомков (рис. 8.30).
Поскольку при формировании 4-арного дерева мы разбиваем пространство линиями, параллельными осям координат, то эта процедура выполняется быстрее по сравнению с аналогичным разбиением в процессе формирования бинарного дерева. Не менее существенно и то, что 4-арное дерево требует для хранения описания изображения меньшего объема памяти.
4-арные деревья представляют разбиение области двухмерного пространства. Их можно использовать и для разбиения объектного пространства примерно по такому же принципу, что и бинарные деревья, а затем обходить в порядке, зависящем от положения наблюдателя. Это позволит сформировать корректное изображение каждой подобласти. Аналогом 4-арного дерева для трехмерного пространства служит 8-арное дерево. Разбиение выполняется не прямыми, а плоскостями, параллельными координатным, и каждый очередной цикл разбиения приводит к созданию восьми октантов трехмерного пространства (рис. 8.31).
Рис. 8.31. 8-арное деревоРис. 8.31. 8-арное дерево
Рис. 8.31. 8-арное дерево
Рис. 8.31. 8-арное дерево
8-арные деревья используются для представления множества объектов, которые состоят из объемных элементов, называемых векселями (voxels') (рис. 8.32). Приложения, в которых используется такое разбиение пространства, будут рассмотрены в главе 12.
Рис. 8.32. Множество объемных элементов
Графика и Web
World Wide Web оказывает огромное влияние на пути развития компьютерных приложений практически любого типа, и графические приложения не является исключением. Глобальные информационные сети, подобные Web, позволяют выполнять обмен информацией в любой форме, предлагая новые технологии взаимодействия пользователя и программы. Для графических приложений подключение к Web открывает возможность более эффективного распространения информации, предъявления ее многим пользователям, расположенным где угодно, и доступ к распределенным ресурсам. Система OpenGL и ее расширения оказали большое влияние на развитие трехмерных сетевых графических приложений и соответствующих стандартов. Ниже мы рассмотрим, какие возможности сулит графическим приложениям использование сетевой среды и что необходимо сделать для реализации этих возможностей. Думаю, что некоторые концепции, в частности модель “клиент/сервер”, читателям уже знакомы. Мы затронем и вопрос о том, как следует организовать графическое приложение, чтобы его реализация не зависела от API.
Сети и протоколы
При обсуждении в главе 3 характеристик графических приложений, работающих в сетевой среде, в контексте систем “клиент/сервер” мы не останавливались на вопросе, как именно циркулирует информация между отдельными компонентами такой среды. Большинство моделей сети базируется на представлении ее в виде многоуровневой структуры, подобной той, что показана на рис. 8.33. Здесь представлена крайне упрощенная версия модели, разработанной в Международной организации по стандартизации (ISO — International Standards Organization). На самом нижнем уровне располагаются физические средства, работающие на уровне отдельных битов. Следующий уровень занимает аппаратура передачи данных. Элементы этого уровня — кабельные каналы связи, телефонные линии, оптоэлектронные каналы и множество других видов аппаратных средств, физически реализующих передачу информации на большие расстояния. Далее в дело вступают программные средства. Самый низкий уровень программных средств принадлежит протоколам передачи битовой информации меж-
ду абонентами сети, гарантирующим сохранность сообщения и устранение возможных искажений. На этом уровне мы чаще всего встречаемся со схемами адресации, обеспечивающими отсылку сообщений и корректную их доставку адресатам, обнаружение искажений и восстановление исходной информации на основе избыточности. На следующем уровне располагаются протоколы, определяющие обмен информацией между процессами, реализуемыми на разных компьютерах сети. В то время как протоколы более низкого уровня определяют порядок передачи информации от одного узла сети к другому, в компетенцию протоколов этого уровня входит обмен содержательной информацией между процессами. Для множества компьютеров, подключенных к сети Internet, главным из протоколов этого уровня является TCP/IP (Transmission Control Protocol/lnternet Protocol). Одно из определений сети Internet гласит, что она является “сетью тесно связанных компьютеров, использующих Internet Protocol”. Самый верхний уровень иерархии занимают пользовательские приложения, обменивающиеся информацией по сети. При использовании таких графических API, как OpenGL, мы работаем именно на этом уровне.
Рассмотрим приложение, в котором используется дисплейный список (архитектуру таких приложений мы обсуждали в главе 3). На одном компьютере в этом приложении выполняется пользовательская программа (в терминологии систем “клиент/сервер”— программа клиента), которая использует удаленный графический сервер для создания графических образов объектов, причем описания объектов создаются этой же клиентской программой и помещаются в дисплейный список. Самый нижний уровень в таком приложении занимают аппаратура и стандарты подключения к сети Ethernet (рис. 8.34). Средний уровень отводится протоколу TCP/IP, согласно которому выполняется обмен содержательной информацией между компьютерами клиента и сервера. Следующий уровень занимают, во-первых, Х-протоколы обмена графической информацией, которые являются расширением стандарта OpenGL, а во- вторых, сама система OpenGL. С точки зрения приложения эта графическая система занимается единственной работой — воспроизведением на экране содержимого дисплейного списка. Самый верхний уровень принадлежит, естественно, прикладной программе.
работанная в ISOработанная в ISOРис. 8.34. Иерархия протоколов в приложении, использующем OpenGLРис. 8.34. Иерархия протоколов в приложении, использующем OpenGL
работанная в ISO
работанная в ISO
Рис. 8.34. Иерархия протоколов в приложении, использующем OpenGL
Рис. 8.34. Иерархия протоколов в приложении, использующем OpenGL
Существуют специальные протоколы стыковки, которые позволяют приложениям высокого уровня взаимодействовать через сети разных стандартов. Если бы можно было так организовать дело, чтобы все пользователи использовали в графических приложениях только OpenGL, в Web циркулировали бы огромные потоки графической информации в форме дисплейных списков, поскольку многие приложения использовали архитектуру “клиент/сервер”. Но отнюдь не все приложения используют один и тот же интерфейс API, и не все компьюте
ры, поддерживающие один и тот же интерфейс API, имеют одинаковую архитектуру. Вследствие этого появляются дополнительные возможности. Оставим на время графику и рассмот-, рим сетевую среду с несколько другой точки зрения — с точки зрения накопления информации, поступающей от множества источников.
Гипермедиа и HTML•
По мере развития Internet и проникновения ее во все сферы жизни современного общества расширялся и набор протоколов высокого уровня, применяемых для передачи сообщений электронной почты, файлов и информации других видов. Системы, подобные X Window, позволяют пользователю открывать окна на удаленном компьютере и передавать в это окно графическую информацию. Расширение объема информации, доступной широким кругам пользователей по сети Internet, потребовало от инженеров разработки все более сложных методов совместного использования информации, хранящейся на разных узлах сети в отличающихся форматах. Создание таких методов потребовало решения трех ключевых вопросов.
Разработка схемы адресации, которая позволила бы пользователям однозначно идентифицировать ресурсы в сети.
Разработка методов кодирования информации разного вида, не только текстовой — изображений, ссылок на другие ресурсы.
Разработка методов интерактивного поиска необходимых ресурсов.
Первые две проблемы были решены исследователями из Европейского центра ядерной физики CERN, которые разработали спецификацию сетевой гипертекстовой системы World Wide Web. В этой системе ресурсы — файлы — идентифицируются уникальным универсальным локатором ресурса (URL— Uniform Resource Locator), который состоит из трех компонентов: идентификатора протокола передачи документа, идентификатора сервера и идентификатора раздела (каталога) сервера, в котором хранится документ. Например, URL для текстов программ из этой книги выглядит так: ftp://cs.unin.edu/pub/angel/BOOK. Первая часть URL— ftp— означает, что для передачи этой информации будет использоваться стандартный протокол передачи файлов MP (File Transfer Protocol). Второй компонент URL— cs.unm.edu— идентифицирует Web-сервер, а последний — pub/angel/BOOK— каталог на этом сервере, в котором хранятся файлы текстов программ. URL сервера Организации пользователей OpenGL (OpenGL Organization) — http://www.opengl.org. Первый компонент этого URL — http — указывает, что для считывания документов с этого сервера следует использовать протокол пересылки гипертекстов HTTP (HyperText Transport Protocol), a документы находятся на сервере www.opengl.org. Поскольку в URL не указан конкретный каталог на сервере, после обращения по этому адресу пользователь получит доступ к каталогу по умолчанию — начальной странице (home page).
Другой важнейший вклад специалистов CERN — разработка языка разметки гипертекста (HTML — Hypertext Markup Language). Этот язык позволяет довольно просто, на уровне, доступном и непрофессионалу, описать документ, состоящий из собственно текста, ссылок на другие ресурсы (связей — links) и изображений. HTML-документ представляет собой текст, в котором используется ASCII-кодировка латинских букв и один из стандартных наборов символов расширения для языков, отличных от английского, в том числе для славянских, кавказских и пр.
В совокупности схема адресации, принятая для формирования URL, и стандартизация формата HTML-документов составляют тот базис, на который могут опираться разработчики
средств доступа к ресурсам. Первым из таких средств, ориентированных именно на широкие круги пользователей-непрофессионалов, был броузер Mosaic, разработанный в Национальном центре суперкомпьютерных приложений (NCSA — National Center for SuperComputer Applications). Появление Mosaic дало толчок развитию отдельного класса интерактивных программ просмотра и перегрузки документов, доступных по сети Web, — броузеров. Mosaic и пришедший вскоре на смену ему броузер Netscape Navigator “прорубили" для огромной массы пользователей окно в мир Web.
Базы данных и VRML
При всех его достоинствах, главнейшие из которых — простота и доступность в освоении, язык HTML не рассчитан на поддержку работы в интерактивном режиме с трехмерной графикой в сети Web. Это связано с тем, что изображение в HTML-документе — это только набор пикселей, представленных в одном из двух возможных форматов — TIFF и JPEG. В HTML отсутствуют какие-либо средства представления геометрических объектов, даже двухмерных. После того как спала первая волна энтузиазма, пользователи довольно быстро осознали этот недостаток, и в ответ на потребность компьютерного сообщества появилось множество предложений с проектами других языков. В результате поисков, сравнения и анализа из множества предложений “выжил” только проект языка моделирования виртуальной реальности VRML (Virtual Reality Modeling Language).
Существует множество способов передавать графическую информацию по сети. Можно, как это сделано в HTML, передавать статические изображения, но, совершенно очевидно, этот подход не сулит особенно широких возможностей взаимодействия пользователя с графической информацией. Второй метод — пересылать исходный код программы формирования изображения на каком-либо общедоступном и распространенном языке, например OpenGL. Такой подход имеет очевидные достоинства и является еще одним серьезным доводом в пользу стандартизации языковых средств компьютерной графики. Но использование в качестве носителя графической информации кода программы потребует установки на клиентском компьютере сети компилятора графической программы, например включения его в состав броузера. Третий подход— использование модели “клиент/сервер”, подобной той, что применяется в OpenGL. Но при этом нужно согласиться на использование единого API и приемлемого для всех формата передачи дисплейного списка. Именно такая философия положена в основу Java.
Существует еще и четвертый подход, который сулит существенное повышение гибкости методов создания, хранения и передачи графической информации. Этот подход основан на идее применения баз данных для описания сцены, включая такие ее компоненты, как геометрические объекты, источники света, материалы и пр. База данных сохраняется в стандартном текстовом формате и легко передается по сети. Получатель описания сцены в виде базы данных может сформировать соответствующее изображение, установив нужные программы на своем компьютере. Этот подход используется в системах, основанных на языке VRML.
Разработка баз данных такого типа основана на тех же концепциях, которые мы уже обсуждали в главе 2, когда речь шла о функциональных возможностях графических API. Поскольку многие вопросы, в частности выбор набора примитивов, остаются прежними, имеет смысл выбрать такой состав элементов базы данных, который соответствует возможностям наиболее распространенных API, поддерживаемых на большинстве аппаратных и программных платформ. Одним из фаворитов в конкурсе API является OpenGL, но у этой системы, по крайней мере в том виде, в котором она существует на сегодняшний день, есть существенный недостаток— отсутствие встроенных объектно-ориентированных средств для описания сложных сцен. Разработчики VRML отдали предпочтение базе данных Open Inventor. Как уже отмечалось в разделе 8.7, Open Inventor представляет собой надстройку над OpenGL, Ко-
торая расширила функции этой графической системы и, в частности, добавила возможность работы с графами сцен. Open Inventor позволяет пользователю сохранять описание сцены в виде текстового файла. Добавив связи в базу данных, мы получаем в свое распоряжение практически все возможности, необходимые для описания трехмерных сцен, компоненты которых распределены по разным узлам сети.
Язык VRML очень тесно связан с OpenGL. VRML предоставляет пользователю методы описания распределенного мира трехмерных объектов, а броузер VRML выполняет “сборку” изображения, объекты которого разбросаны по узлам сети Web. Хотя в принципе броузер может использовать любую технологию формирования изображения — конвейерную, основанную на трассировке лучей или на методе излучательности, — тот факт, что язык базируется на Open Inventor, склоняет к выбору технологии, реализованной в OpenGL.
Java и аплеты
Использовать те средства, которые были рассмотрены в предыдущих главах, для организации взаимодействия пользователя с программой при работе в сети Web довольно проблематично. Базы данных VRML существуют в текстовом формате, а это предполагает, что сервер формирует образы всех объектов, “прописанных” в базе данных. Следовательно, пользователь не имеет возможности разработать программный код на одном узле сети и быть уверенным, что этот код будет выполняться на другом, поскольку компьютеры на этих узлах могут быть совершенно различными.
Язык Java позволяет частично решить эту проблему, поскольку он предполагает работу в программно созданной “виртуальной” машине, которая может, по крайней мере теоретически, быть реализована на любой аппаратной и программной платформе. Программа на языке Java компилируется в байтовый код {byte code), который затем выполняется на любой Java- машине, независимо от того, на какой платформе она реализована. Программы небольшого объема в байтовом коде получили название аплеты {applets). Такие программы “понимаются” любыми моделями Web-броузеров и в настоящее время широко используются для расширения возможностей динамической обработки информации в сети Web.
В исходной версии язык Java поддерживал только двухмерную графику. В последнее время появилось много предложений о создании версии Java-3D, способной работать с трехмерными графическими объектами, но на момент подготовки этой книги к печати автору не была известна ни одна практическая реализация этой идеи. Тем не менее надежда на то, что в ближайшее время появятся универсальные технологии обмена трехмерной графической информацией по сети Web, имеет достаточно твердую почву.
Резюме
Разительное повышение быстродействия современных аппаратных средств формирования графических изображений открывает широкие возможности для реализации самых разных технологий моделирования в разных областях приложения компьютерной графики. В этой главе мы познакомили читателей с основами иерархического моделирования геометрических объектов. В главах 9, 11 мы продолжим эту тему, и читатель получит возможность комбинировать разные технологии моделирования, выбирая наиболее подходящую к специфике приложения. Освоить соответствующие методики читателям поможет литература, предложенная в следующем разделе для самостоятельного изучения.
В этой главе мы осветили основные темы, которые имеют отношение к большинству существующих методик моделирования. Одна из главных— использование иерархического подхода, который позволяет адекватно описать отношения между объектами сцены. Было показано, как в рамках этого подхода использовать фундаментальные структуры данных —
деревья и ориентированные ациклические графы — для представления таких отношений, как реализовать обход графа в процессе формирования изображения. Использование древовидных структур для описания сцен с помощью Open Inventor, языков VRML и Java-3D позволит пользователям создавать сложные динамически обновляемые сцены, комбинируя заранее подготовленные и самостоятельно созданные программные модули. Модели, базирующиеся на древовидных структурах, используются и в языке Renderman Shading Language. С помощью этого языка можно описать сложные варианты закрашивания сцены, в которых изображение создается с учетом взаимодействия источников света, свойств материалов, эффектов распространения света в воздушной или иной среде и используются различные локальные модели отражения. Существуют технологии тонирования, развивающие эту идею и позволяющие использовать разные методы тонирования по отношению к отдельным объектам сцены в сложных графических приложениях, в том числе и распределенных по узлам сети. Но еще раз оговорюсь, что потенциальные возможности Renderman Shading Language используются в настоящее время не полностью. Это объясняется тем, что большинство оконечных систем основано на OpenGL, поскольку эта система наиболее эффективно реализуется имеющимися аппаратными средствами.
Рекомендуемая литература
Методика использования иерархических преобразований посредством стека матриц описана еще более 20 лет назад [Nexv73]. Впервые эта методика была реализована в виде стандартного компонента системы PHIGS [ANSI88]. Описание технологии использования шарнирных фигурок в программах анимации рассмотрено в книге Уатта (Watt) [Wat93J. В статье Лесситера (Lassiter) [Las87] анализируется взаимосвязь традиционной технологии создания движущихся изображений при производстве мультфильмов и технологии, используемой в компьютерной графике.
Графы сцен— это базовый компонент системы Open Inventor [Wer94]. Формат базы данных Open Inventor послужил основой для языка VRML [Наг96]. Большинство современных API, таких как Java-3D [Rus97] и Direct 3D [Kov97], являются объектно-ориентированными. С анализом графических возможностей языка Java и аплетов читатель может познакомиться в книгах [Cha98] и [Агп96]. Древовидные структуры являются базовым элементом языка Renderman Shading Language [Ups89], где они используются для формирования модулей тонирования.
Упражнения
Определите множество точек, которые входят в зону “досягаемости” робота, представленного в разделе 8.3.
Выведите уравнения, описывающие прямоугольные координаты положения любой точки, принадлежащей последнему звену робота, в зависимости от его обобщенных координат (углов поворота в сочленениях). Можно ли определить эти углы по заданному положению конечной точки последнего звена? Поясните свой ответ.
Пусть заданы две точки в рабочем пространстве робота. Опишите траекторию пути между этими точками в терминах углов в сочленениях.
Разработайте простую циклическую программу, использующую таблицу “символ — преобразование экземпляра”. Символами являются условные обозначения элементов принципиальных электрических схем (резисторов, конденсаторов, диодов и т.п.) или условные обозначения логических элементов (И, ИЛИ, НЕТ).
Бинарное дерево, которое нужно обходить в процессе формирования изображения, можно представить в виде списка узлов, составными элементами которых являются указатели на узлы-потомки (правый и левый). Разработайте OpenGL-программу, которая будет формировать изображение дерева, принимая в качестве исходной информации такой список.
Роботы — далеко не единственный вид составных объектов, для которых характерна зависимость движения одних компонентов от других. К таким объектам можно отнести большинство машин — велосипед, у которого вращаются колеса и педали, аэроплан, у которого вращаются пропеллеры, и даже детскую карусель с лошадками. Выберите какой-либо объект, реализующий подобные сложные движения, и разработайте графическую программу, которая воспроизводила бы это движение на экране.
Пусть заданы два многоугольника с равным количеством вершин. Разработайте программу, которая будет формировать динамическое изображение, демонстрирующее процесс трансформации одного многоугольника в другой.
В структуру узла, описанную в разделе 8.5, добавьте новый член, представляющий атрибут, и внесите соответствующие изменения в алгоритм обхода.
Разработайте простую систему построения графа сиены, который включал бы многоугольники, источники света, наблюдателя и материалы.
Почему методы трассировки лучей и приведения лучей наилучшим образом сочетаются с использованием описания сцены в виде дерева в рамках конструктивной геометрии тел.
Покажите, каким образом 4-арные деревья можно использовать для вычерчивания изображения с разным разрешением.
Разработайте программу, которая позволит пользователю конструировать шарнирные фигурки из небольшого набора базовых компонентов. С помощью этой программы пользователь должен иметь возможность указать места сочленений и “оживлять” сконструированную фигурку.
ГЛАВА
9
Операции с изображением на уровне растрового представления
В
течение многих лет графические программы имели дело исключительно с геометрическими объектами, такими как прямые, многоугольники и многогранники. Хотя первые растровые системы появились еще лет 20 назад, но до последнего времени прикладные программы не имели прямого доступа к буферу кадра, поскольку в составе подавляющего большинства графических API отсутствовали функции чтения или записи отдельных пикселей. В последнее десятилетие появились методы, которые, оперируя напрямую с содержимым буфера кадра и других внутренних двухмерных буферов графической системы, позволяют добиваться очень интересных изобразительных эффектов. Именно таким способом выполняется наложение текстур, сглаживание дискретизированного изображения, реализуется идея полупрозрачного альфа-канала. В этой главе читатель познакомится с такого рода технологиями, причем, как и раньше, главное внимание будет уделено тем из них, которые поддерживаются средствами OpenGL и аналогичных ей API.
Начнем мы с анализа методов наложения {mapping). Наложение применяется в процессе тонирования изображения и позволяет создавать иллюзию поверхности со сложной текстурой, хотя в геометрической модели эта поверхность может быть представлена самым заурядным многоугольником. Методы наложения основаны на использовании массива пикселей, который определяет, как модифицируются параметры алгоритма тонирования (о таких алгоритмах шла речь в главе 6), чтобы создать иллюзию сложной рельефной поверхности. Далее будут рассмотрены средства, позволяющие прикладной программе модифицировать отдельные пиксели в буфере, виды дискретных буферов и их назначение, методы доступа к ним, поддерживаемые OpenGL. В частности, будут рассмотрены технологии создания составных изображений, компонентами которых являются пара или более первичные изображения. Для этого нам понадобится четвертый компонент вектора RGBA, и вы узнаете, как с помощью альфа-канала можно накладывать изображения друг на друга, создавая эффект полупрозрачности. В завершение мы еще раз вернемся к проблеме сглаживания границ областей, необходимость в котором возникает вследствие дискретной природы растрового преобразования.
Буферы и наложение
В некоторых алгоритмах, рассмотренных в предыдущих главах, уже использовались два внутренних буфера графической системы — буфер кадра и буфер глубины (или z-буфер). Позже будет рассказано и о других. У всех внутренних буферов системы есть одна общая черта— их дискретная структура. Все буферы имеют ограниченное разрешение— ограниченное количество ячеек (пространственное разрешение) и ограниченную разрядность (разрешение по глубине). Можно дать определение двухмерному* буферу как блоку памяти с
пространственным разрешением (размерностью) п х т ячеек и разрядностью каждой ячейки к бит (рис. 9.1). Параметры и и т буфера кадра соответствуют параметрам разрешения экрана, а от параметра к зависит максимальное количество цветов, которые система способна создать в одном изображении. Параметры пит буфера глубины также соответствуют пространственному разрешению экрана, а параметр к определяет разрешение графической системы по глубине — расстоянию между объектом и картинной плоскостью. Под термином к-я битовая плоскость {bitplane} будем понимать совокупность к-х разрядов во всех
п х т ячейках буфера, а под термином пиксель — все к разрядов одной ячейки, которая соответствует одному элементу изображения. Такое определение означает, что пиксель может иметь формат byte, integer или даже float, в зависимости от того, как используется данный
буфер и какой формат выбран для хранения в нем информации.
Рис. 9.1. Структура внутреннего буфера графической системыРис. 9.1. Структура внутреннего буфера графической системыНаиболее интенсивно внутренние буферы используются при тонировании изображения поверхностей. Возможности моделирования объектов набором геометрических примитивов и воспроизведения образов этих примитивов на экране отнюдь не беспредельны. Предположим, что нужно изобразить на экране апельсин. Первое, что приходит в голову,— сформировать сферу примерно так, как это было сделано в главе 6. В результате будет сформирована аппроксимация сферической поверхности множеством маленьких треугольников, затем эту аппроксимированную поверхность можно “пропустить” через один из уже известных нам алгоритмов тонирования. Но вряд ли вас устроит результат — полученное изображение апельсина очень легко будет принять за изображение баскетбольного мяча, — слишком уж “правильный” получится апельсин. Такого в
Рис. 9.1. Структура внутреннего буфера графической системы
Рис. 9.1. Структура внутреннего буфера графической системы
супермаркете не увидишь. Можно поступить по-другому. В главе 10 рассказывается о том, как создавать поверхности нерегулярной формы, представляющие собой случайные искажения правильной формы. Описанные в ней методы дают программисту возможность контролировать нерегулярности формы объекта, и полученное изображение уже не спутаешь с мячом, но на настоящий апельсин оно тоже будет походить мало. В главе 11 описаны еще более сложные методы внесения нерегулярностей, но использовать их в графических системах широкого применения (не супербыстродействующих) довольно затруднительно, поскольку, в конце концов, все они сводятся к усложнению трехмерной модели объекта— возрастанию количества треугольных граней, аппроксимирующих поверхность объекта.
Но существует и альтернативный подход. Можно не усложнять трехмерную модель, а попытаться внести нерегулярность в двухмерный образ модели на стадии тонирования и растрового преобразования. При построении образа поверхности — то ли криволинейной, то ли плоского многоугольника— его (образ) можно разбить на мелкие фрагменты, каждый из которых имеет размер, не превышающий размер пикселя экрана. То, что размер фрагмента меньше одного пикселя, позволяет объединять образы нескольких поверхностей, каждый из
Можно организовать одно-, двух-, трех- и четырехмерные буферы. Выбор того или иного варианта зависит от назначения буфера и .методов работы с ним.
которых вносит свой “вклад” в засветку пикселя (его цвет). При формировании окончательного изображения нужно назначить тон или цвет каждому фрагменту. Все начинается с выбора подходящей модели закрашивания из тех, что были рассмотрены в главе 6. Далее в работу вступает алгоритм наложения (mapping algorithm). Его можно рассматривать либо как алгоритм модификации параметров модели закрашивания, который опирается на некоторый двухмерный массив данных — карту (тар), либо как алгоритм модификации параметров поверхности, обрабатываемой алгоритмом закрашивания, например свойств материала или направления нормали. Таким образом, в рамках этого подхода можно выделить
наложение проективной текстуры;
наложение микрорельефа;
наложение параметров среды (environmental mapping).
Алгоритм наложения проективной текстуры (texture mapping) использует некоторый шаблон (или текстуру) для формирования цвета фрагмента. Текстура может иметь регулярный характер и храниться как фиксированный массив — именно такого типа текстуры часто используются для заполнения внутренних областей многоугольников. Иногда применяются методы динамического формирования текстур или в качестве текстуры используется внешнее изображение. В любом случае можно считать, что наложение проективной текстуры на поверхность, как это показано на рис. 9.2, является частью процесса тонирования этой поверхности.
Рис. 9.2. Наложение проективной текстуры на поверхностьРис. 9.2. Наложение проективной текстуры на поверхностьНаложение проективных текстур позволяет “проработать” детали образа гладкой поверхности. Другой метод — наложение микрорельефа (bump mapping)— позволяет сделать поверхность менее гладкой, наложив на нее “пупырышки” — карту микрорельефа (bump тар), и превратить баскетбольный мяч в некое подобие апельсина. Наложение карт отражения (reflection maps) или карт среды (environmental maps) позволяет сформировать изображение, напоминающее то, которое создается при трассировке лучей, хотя сама процедура трассиров-
Рис. 9.2. Наложение проективной текстуры на поверхность
Рис. 9.2. Наложение проективной текстуры на поверхность
ки и не выполняется. В этом случае изображение окружающих предметов (среды) накладывает
ся на поверхность и создается иллюзия зеркального отражения.
Три перечисленные группы методов имеют много общего. Все они модифицируют результат тонирования, никак не затрагивая форму объекта (точнее, его геометрическую модель). Все методы реализуются синхронно с тонированием как часть заключительного этапа этого процесса. Все методы базируются на некоторых образцах — картах, которые хранятся в виде одно-, двух- или трехмерного дискретизированного изображения. Все методы требуют принятия специальных мер для сглаживания ступенек или зубцов на границах областей.
Наложение проективных текстур
Образцы текстур могут иметь самый разный вид— от полос и клеток до сложных изображений, воспроизводящих срез натуральных материалов, таких как мрамор, гранит, дерево. Очень часто мы распознаем материал реального объекта именно по его текстуре. Поэтому, если перед разработчиком графического приложения стоит задача создать иллюзию натурального объекта, ему никак не обойтись без наложения на объект соответствующей текстуры. В этом разделе мы рассмотрим только работу с двухмерными текстурами, хотя используемые при этом методы вполне приложимы и к одно-, трех- и четырехмерным текстурам.
На цветной вклейке вы найдете множество примеров применения наложения проективных текстур. На рис. 6 цветной вклейки показано изображение, созданное в среде графической системы OpenGL, причем текстуры сформированы также средствами этой системы. На ил. 10 наложение текстуры позволило передать фактуру деревянной столешницы, а на рис. 4 — фактуру кирпичных стен интерьера церкви. Большинство деталей поверхностей, изображенных на ил. 7, сформировано также с применением технологии наложения проективных текстур. Это изображение создано в приложении, к которому предъявляются весьма жесткие требования по скорости обработки графической информации, поскольку они моделируют динамические процессы. Когда такие приложения выполняются на рабочих графических станциях, укомплектованных аппаратными средствами выполнения наложения, процесс наложения текстуры никак не сказывается на скорости обработки информации.
Наложение двухмерных проективных текстур
Процесс наложения проективных текстур выполняется в несколько этапов. Перед началом процедуры мы располагаем двухмерным образцом (шаблоном) текстуры — функцией интенсивности T(s, t). Независимые переменные s и / называются координатами текстуры (texture coordinates). Сейчас можно считать образец текстуры массивом бесконечного (или, по крайней мере, очень большого) размера, хотя реально он, естественно, хранится в памяти конечного объема из п х т ячеек. Элементы массива образца текстуры иногда по аналогии с пикселями называют текселями (texels)1. Не теряя общности, можно изменить масштаб представления координат текстуры и выйти за диапазон (0, 1).
Карта наложения (texture тар) ассоциирует с каждой точкой геометрического объекта интенсивность Т соответствующей точки образца, причем точки поверхности, в свою очередь, отображаются на пространство координат экрана. Если объект представлен в пространственных (геометрических) координатах ((х, у, z) или (х, у, z, и )), можно далее рассуждать в терминах математической функции отображения пространства координат текстуры на пространство геометрических координат и функции проективного преобразования, отображающей пространство геометрических координат на пространство координат экрана. Если описать геометрический объект в параметрической форме в виде функции параметров (и, v), как мы это делали при работе со сферической поверхностью в главе 6, то в этой цепочке отражений появится еще одно звено. В этом случае нужно учитывать существование двух параллельных процессов отражения: первый отражает координаты текстуры на геометрические координаты, а второй — параметрические координаты на геометрические, как это схематически показано на рис. 9.3. Третья функция отражения переносит нас в пространство координат экрана.
Если не вдаваться в подробности, то процесс наложения текстуры выглядит достаточно простым. Малая область образца отображается на область геометрической поверхности, соответствующую одному пикселю окончательного изображения. Предполагается, что значения интенсивности образца Т представляют собой цветовой код RGB и их можно использовать либо для модификации цвета поверхности, определяемого из других соображений (источники света, отражение и т.п.), либо для назначения поверхности цвета только на основании образца текстуры. Этот процесс выполняется синхронно с алгоритмом тонирования или закрашивания поверхности.
Но при более внимательном взгляде обнаруживается множество “подводных камней”. Во- первых, нужно определиться с методом сопоставления координат текстуры с геометрическими координатами. Двухмерный образец обычно определяется на прямоугольной области в пространстве текстуры. Функция отображения этого прямоугольника на область произволь-
2В четырехмерном случае значение текселя Т есть точка в пространстве /s. t, г, q/.
ной формы в трехмерном пространстве может быть достаточно сложной или обладать нежелательными свойствами. Например, если желательно отобразить прямоугольную область на сферу, это невозможно сделать, не искажая форму образца или пропорции его компонентов (отношения между расстояниями). Во-вторых, сама организация процесса тонирования (он выполняется последовательным перебором пикселей) заставляет нас интересоваться скорее обратной функцией отображения, т.е. отображением координат экрана на координаты текстуры. Нас интересует при этом, какая точка на образце соответствует определенному пикселю образца поверхности, а это требует пересчета координат экрана в координаты текстуры. В-третьих, поскольку мы вычисляем коды засветки пикселей, каждый из которых определяет цвет элементарной прямоугольной области изображения, то нас интересует не функция отображения точки одного пространства на точку другого, а области в одном пространстве на область в другом. Здесь мы вновь сталкиваемся с проблемой сглаживания границ между областями, которой следует уделить особое внимание. В противном случае появляются совершенно неожиданные эффекты типа муара.
Рис. 9.4. Обратное отображение пикселя
На рис. 9.4 схематически показана суть возникающих при наложении сложностей. Пред
положим, что вычисляется цвет квадратного пикселя, центр которого находится в точке (X. у,) в системе координат экрана. Точка (х„ у5) соответствует точке (х у, с) в пространстве геометрического объекта, но если объект имеет криволинейную форму, то проекция угловых точек прямоугольной области пикселя обратно на пространство объекта образует криволинейное отображение пикселя. В терминах изображения (функции интенсивности) образца Дх. f) обратное отображение пикселя приводит к выделению некоторой области с криволинейными границами, которая вносит вклад в закраску пикселя.
Теперь на время оставим задачу определения функции отображения и обратим взор на метод определения засветки пикселей. Одно из возможных решений — использовать точку, полученную при обратном отображении центра пикселя, и извлечь соответствующее значение функции окраски образца. Этот метод прост, но приводит к появлению на изображении дефектов квантования, которые особенно заметны в том случае, когда изображение образца носит периодический характер. На рис. 9.5 показано, в чем причина подобных искажений. На
этом рисунке слева вы видите регулярную текстуру в виде полос, которую нужно наложить на плоскую поверхность. Обратная проекция центра каждого пикселя попадает как раз в промежуток между темными полосами, и в результате вся поверхность будет светлой. В общем случае, если не принимать во внимание конечные размеры пикселя, на изображении появится
Рис. 9.5. Эффект дискретизации при наложении текстурыРис. 9.5. Эффект дискретизации при наложении текстурымуар. Лучшие результаты можно получить другим методом (но его сложнее реализовать) — формировать возвращаемое значение интенсивности образца, усредняя значения интенсивности Т по области, обратной проекции пикселя. Этот метод дает лучший результат, но и он не идеален. Например, на рис. 9.6 показано, что происходит при его применении. На сей раз мы опять не получим на изображении полос, поскольку оно будет иметь усредненный цвет— ни светлый, ни темный. Таким образом, нам и на этот раз не удалось избавиться от дефектов, связанных с огра
Рис. 9.5. Эффект дискретизации при наложении текстуры
Рис. 9.5. Эффект дискретизации при наложении текстуры
ниченным разрешением буфера кадра и образца. Наиболее заметны эти дефекты при регуляр
ной структуре образца текстуры.
Вернемся вновь к проблеме отображения одного пространства на другое. В компьютерной графике большинство криволинейных поверхностей задается в параметрической форме. Точка р на поверхности является функцией двух параметров и и v. Для каждой пары значений параметров формируется точка:
p(m,v)p(m,v)x(l/,v) y(«-v) ^U,v\
p(m,v)
p(m,v)
Процесс формирования параметрической поверхности мы более подробно рассмотрим в главе 10, а сейчас нас интересует, как отобразить точку в пространстве текстуры (s, /) на точку р(г/, v), принадлежащую такой поверхности. Рассмотрим линейную форму функции отображения:
и = ax+bt+c,
v = dx+et^f
Эта функция отображения является обратимой, если ае * bd. Линейная функция отображения позволяет легко сопоставить текстуру с группой участков параметрически заданной
поверхности. Например, как показано на рис. 9.6, участок образца, заданный угловыми точками (smin, /min), (smax, /тах), соответствует участку поверхности с параметрами угловых точек (“min, vmin), (“max, vmax), и в пределах участков функция отображения имеет вид
И = “min + —,
nunу тих пип / ’
^пых *$тт
V = V 4—-———— V . ) . пип\ max пип /
max * min
Такую функцию отображения несложно реализовать, но она не учитывает кривизны поверхности. В результате участки образца с равными размерами “натягиваются” на участки поверхности.
Рис. 9.6. Линейное отображение пространствРис. 9.6. Линейное отображение пространствСуществует другой подход к проблеме выбора функции отображения, который предполагает разбиение процесса на две стадии. На первой стадии текстура отображается на промежуточную поверхность стандартного вида— сферу, цилиндр или куб. На второй стадии стандартная поверхность отображается на поверхность тонируемого объекта. Такой двухэтапный процесс можно применять в отношении как параметрически заданных поверхностей, так и поверхностей,
Рис. 9.6. Линейное отображение пространств
Рис. 9.6. Линейное отображение пространств
описываемых функциями в геометрических координатах. Рассмотренный ниже пример будет, по существу, одинаковым при любом способе задания поверхности.
Предположим, что координаты текстуры имеют интервал представления (0, 1) и что в качестве промежуточного объекта используется цилиндр высотой h и радиусом г, как показано на рис. 9.7. Точки цилиндрической поверхности заданы в параметрической форме:
х = г cos(2nz/),
у = г sin(2nz/),
- = v/h,
причем параметры и и г также изменяются на интервале (0, 1). Следовательно, можно использовать функцию отображения в виде
s = г/, / = г.
Значит, образец накладывается на цилиндр без искажения формы, т.е. без изменения соотношения между отдельными элементами. Однако при отображении на замкнутую поверхностью, например сферическую, нужно будет каким-то образом ввести искажение формы. Эта задача носит примерно такой же характер, как отображение глобуса на плоскую поверхность географической карты, — при этом неизбежно возникают искажения пропорций. В обоих случаях — и при картографировании больших участков земной поверхности, и при наложении текстуры на сферическую поверхность — нужно выбрать один из подходящих способов представления, которые отличаются характером искажений. Например, знакомая многим проекция Меркатора предполагает наибольшие искажения в приполярных областях. Если в качестве промежуточного объекта используется сфера радиуса г, то один из вариантов функции отображения имеет вид
х- г cos(2nzz),
у = г sin(2nzz) соз(2лг), z = г sin(2nzz) sin(2nv).
На рис. 9.8 показано, как использовать в качестве промежуточной поверхности куб. В этом случае текстура фактически проецируется на развертку куба, причем проективное наложение часто применяется в комбинации с наложением среды, которое будет описано в разделе 9.3.
ttРис. 9.8. Наложение образца текстуры на развертку кубаРис. 9.8. Наложение образца текстуры на развертку кубаРис. 9.7. Отображение образца текстуры на цилиндрическую поверхностьРис. 9.7. Отображение образца текстуры на цилиндрическую поверхностьПромежуточная поверхностьПромежуточная поверхностьРис. 9.9. Второй этап наложения: а— использование нормали к промежуточной поверхности; б — использование нормали к поверхности тонируемого объекта; в — использование центра тонируемого объектаРис. 9.9. Второй этап наложения: а— использование нормали к промежуточной поверхности; б — использование нормали к поверхности тонируемого объекта; в — использование центра тонируемого объектаСуть второго этапа — отображение промежуточной поверхности с уже наложенной текстурой на поверхность тонируемого объекта сцены. На рис. 9.9 схематически представлены три возможных варианта стратегии отображения. В первом варианте (рис. 9.9,а) значение интенсивности образца в точке промежуточной поверхности передается точке объекта, в которую “упирается” нормаль к промежуточной поверхности. Второй вариант реализует примерно такую же стратегию, но в обратном направлении — от объекта к промежуточной поверхности. Точке объекта передается значение интенсивности образца текстуры в той точке промежуточной поверхности, в которую “упирается” нормаль к поверхности объекта (рис. 9.9,6). Третий вариант реализует “центральную” стратегию. Если объект имеет такую форму, что к ней приложимо понятие центральной точки (например, уже упоминавшийся выше апельсин), то можно переносить на поверхность объекта значения интенсивности текстуры в точках промежуточной поверхности, лежащих на пересечении с исходящим из центра лучом (рис. 9.9,в). Внимательный читатель, несомненно, отметит, что математически первый и третий варианты должны дать один и тот же результат, если промежуточная поверхность имеет форму сферы, центр которой совпадает с центром тонируемого объекта сцены.
t
t
Рис. 9.8. Наложение образца текстуры на развертку куба
Рис. 9.8. Наложение образца текстуры на развертку куба
Рис. 9.7. Отображение образца текстуры на цилиндрическую поверхность
Рис. 9.7. Отображение образца текстуры на цилиндрическую поверхность
Промежуточная поверхность
Промежуточная поверхность
Рис. 9.9. Второй этап наложения: а— использование нормали к промежуточной поверхности; б — использование нормали к поверхности тонируемого объекта; в — использование центра тонируемого объекта
Рис. 9.9. Второй этап наложения: а— использование нормали к промежуточной поверхности; б — использование нормали к поверхности тонируемого объекта; в — использование центра тонируемого объекта
Проективное наложение текстуры в системе OpenGL
Графическая система поддерживает несколько вариантов реализации проективного наложения текстур. В первой версии OpenGL можно было накладывать одно- и двухмерные текстуры на одно-, двух-, трех- и четырехмерные графические объекты. В последней версии реализовано наложение трехмерных текстур, но эта функция поддерживается только аппаратными средствами профессиональных графических станций. Поскольку управление процессом требует настройки множества параметров, мы рассмотрим здесь только методику работы в OpenGL с двухмерными текстурами.
Метод наложения, реализованный в OpenGL, учитывает особенности конвейерной архитектуры этой графической системы. До сих пор мы делали основной упор на конвейерной обработке геометрической информации. Но в системе имеется и отдельный конвейер обработки пикселей, причем результаты обработки на обоих конвейерах сливаются на этапе тонирования изображения (рис. 9.10). В последующих разделах будут описаны операции, выполняемые в этом конвейере, но главное, что любое изображение, сформированное им, может быть наложено на изображение геометрических объектов. Такая архитектура предопределяет тип функций наложения, которые она способна поддерживать. В частности, проективное наложение выполняется в процессе растрового преобразования. Этот процесс отображает трехмерные точки пространства сцены на двухмерное пространство экрана (пиксели). Каждый генерируемый фрагмент тестируется на видимость (с помощью z-буфера) и, если он видим, закрашивается. Можно считать, что наложение текстуры входит как отдельный этап в процесс закрашивания. Вершины отображаются на координаты текстуры, а нужные значения интенсивности текстуры вычисляются путем интерполяции, как это делалось при закрашивании внутренней области многоугольника, вершинам которого назначены разные цвета.
Рис. 9.10. Конвейеры обработки геометрической информации и пикселей
Непременным компонентом программы наложения двухмерной текстуры является массив текселей. Предположим, что ранее программой каким-то образом сформировано или считано из файла изображение с размерами 512x512 и сохранено в массиве my_texels.
GLubyte my_texels[512][512];
Вызов функции glTex!mage2D() специфицирует этот массив как предназначенный для хранения двухмерного образца текстуры:
glTex!mage2D(GL_TEXTURE_2D, 0, 3, 512, 512, О, GL_RGB, GL_UNSIGNED_BYTE, my_texels);
Формат вызова функции glTex!mage2D() для спецификации двухмерного образца текстуры выглядит следующим образом:
glTexImage2D(GL_TEXTURE_2D, <уровень>( <компонентЫ>, <ширина>,
<высота>, <граница>, <формат>, <тип>, <массив>);
Массив для хранения образца текстуры должен иметь размерность <ширина> х <высота>. Значение параметра <компоненты> — количество (от 1 до 4) цветовых компонентов (RGBA), которые желательно использовать в образце. Задавая значения параметров <уровень> и <граница>, можно выполнять тонкую настройку режима работы с образцом, и мы на методике такой настройки останавливаться не будем.
Для того чтобы выполнять наложение в программе, требуется явно разрешить этот режим с помощью функции glEnable():
glEnable(GL_TEXTURE_2D);
Следующий этап — указать графической системе, как текстура должна отображаться на геометрический объект. Образец текстуры имеет две координаты, s и /, которые, как правило, изменяются в интервале (0.0, 1.0). В нашем случае пара значений (0.0, 0.0) соответствует элементу массива my_texels[0] [0], а пара (1.0, 1.0)— элементу массива my texels[511 ] [511] (рис. 9.11).
Рис. 9.11. Соответствие между координатами текстуры и элементами массива хранения образцаРис. 9.11. Соответствие между координатами текстуры и элементами массива хранения образцаНазначить координаты текстуры вершинам можно с помощью функции glTexCoord2f():
Рис. 9.11. Соответствие между координатами текстуры и элементами массива хранения образца
Рис. 9.11. Соответствие между координатами текстуры и элементами массива хранения образца
glTexCoord2f(s,t);
Как и при работе с цветом и нормалями, при этом устанавливается переменная состояния (текущие координаты текстуры), которая является компонентом набора переменных текущего режима работы системы. Сле-
довательно, координаты текстуры должны быть установлены перед тем, как будет вызвана функция формирования вершины. Ниже приведен фрагмент программы, в котором
текстура накладывается на четырехугольник (четырем его вершинам назначаются соответствующие координаты текстуры).
glBegin{GL_QUAD);
glTexCoord2f(0.0, 0.0); glVertex2f(xl, yl, zl); glTexCoord2f(1.0, 0.0); glVertex2f(x2, y2, z2); glTexCoord2f(1.0, 1.0); glVertex2f(x3, y3, z3); glTexCoord2f(0.0, 1.0); glVertex2f(x4, y4, z4);
glEnd();
Перед формированием очередной вершины можно не только назначить новые текущие координаты текстуры, но и новый цвет или направление нормали. В приведенном выше фрагменте для наложения использовался весь массив текселей, что соответствует изменению координат текстуры от 0.0 до 1.0 (рис. 9.12,а).
Если желательно использовать только часть полного интервала изменения координат s и 1, например (0.0, 0.5), нужно соответственно использовать и часть массива my texels. Изменив значения аргументов вызова функций glVertex2f() в приведенном выше фрагменте программы, получим изображение, представленное на рис. 9.12,6. Исполнительная система OpenGL будет при выполнении программы интерполировать значения координат s и / при закраске внутренней области четырехугольника и отображать интерполированные значения на
пространство индексов в массиве my_texels. Пример с наложением текстуры на внутреннюю область четырехугольника кажется тривиальным, поскольку способ совмещения координат текстуры с вершинами четырехугольника совершенно очевиден. В случае многоугольников другой формы выбор того или иного варианта ассоциирования вершин с координатами текстуры существенно изменяет вид сформированного изображения (рис. 9.13).
Рис. 9.12. Наложение текстуры в виде шахматной клетки на четырехугольник: а — использование всего массива текселей; б — использование части массива текселей
Рис. 9.13. Наложение текстуры на многоугольники: а и б — наложение текстуры в виде шахматной клетки на треугольники; в — наложение текстуры в виде шахматной клетки на трапецию
В общих чертах методика программирования наложения текстуры выглядит очень просто. Сначала нужно сформировать массив текселей, описывающий образец текстуры. Элементы массива должны иметь формат представления цвета, поддерживаемый графической системой. Затем следует назначить значения нормированных координат текстуры формируемым вершинам графических объектов. Но в этой методике есть некоторые нюансы, без знания которых вы не сможете эффективно пользоваться наложением текстур. Как часто бывает в подобных случаях, перед программистом всегда стоит задача выбора между более высоким качеством изображения и высокой производительностью. В частности, существует проблема выбора формата представления образца текстуры (мы рассмотрим ее в разделе 9.5).
Вторая проблема связана с тем, что назначенные значения координат s или / могут выйти за пределы интервала (0.0, 1.0). В этом случае возможны два варианта: либо повторять текстуру за пределами интервала, либо использовать предельные значения: 1.0 — если значение координаты превышает 1.0, или 0.0— если значение координаты оказывается меньше 0.0. Настройка режима повторения образца за пределами номинального интервала изменения координаты s выполняется вызовом функции
glTexParameter(GL_TEXTURE_WRAP_S, GL_REPEAT);
Для аналогичной настройки координаты / следует использовать константу GL_TEXTURE_WRAP_T. Для настройки режима ограничения в качестве второго аргумента функции glTexParameter() необходимо передать константу GL_CLAMP.
Третья проблема— влияние дискретизации на изображение наложенной текстуры. При отображении координат текстуры на пространство индексов массива текселей очень редко получается так, что координаты соответствуют центру текселя. Кроме того, размер закрашиваемого пикселя на экране может быть больше или меньше размера текселя (рис. 9.14).
Рис. 9.14. Отображение текселей на пиксели: а —увеличение; б —уменьшениеРис. 9.14. Отображение текселей на пиксели: а —увеличение; б —уменьшениеб)б)
Рис. 9.14. Отображение текселей на пиксели: а —увеличение; б —уменьшение
Рис. 9.14. Отображение текселей на пиксели: а —увеличение; б —уменьшение
б)
б)
В первом случае размер текселя больше размера пикселя, а во втором — меньше. В обоих случаях быстрее всего изображение будет формироваться, если задать режим выбора ближайшего текселя. Режим увеличения или уменьшения текстуры настраивается с помощью аргументов вызова функции glTexParameterf():
glTexParameterf(GL TEXTURE 2D, GL TEXTURE MAG_FILTER,
“~" GL NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL TEXTURE MIN FILTER, ” GL_NEAREST);
Но такая стратегия может привести в снижению качества изображения. Лучшие результаты дает усреднение интенсивности текселя. В OpenGL используется область усреднения с размерами 2x2, и такой режим настраивается заменой в списке аргументов функции glTexParameterf () константы GL NEAREST на константу GL_LINEAR.
В OpenGL можно использовать и другой способ разрешения проблемы неравенства размеров — он получил название мип-наложения (mipmapping). Если проекция объекта на экран мала по сравнению с размерами образца текстуры, нет смысла в полной мере использовать степень детализации текстуры, обеспечиваемую массивом хранения образца. OpenGL позволяет создать несколько массивов образцов с уменьшенным разрешением. Если исходный массив имел размеры 64x64, можно, воспользовавшись функцией gluBuild2DMipmaps() из библиотеки GLU, создать массивы с размерами 32x32, 16x16, 8x8, 4x4, 2x2 и 1x1: gluBuild2DMipmaps(GL TEXTURE_2D, 3, 64, 64, GL RGB,
GL_UNSIGNED_BYTE, my_texels); '
Ту же самую процедуру можно выполнить и с помощью функций библиотеки GL. Настройка режима автоматического использования полученных мип-образцов текстуры выполняется передачей соответствующих аргументов функции glTexParameterf (): glTexParameterf(GL_TEXTURE_2D,
GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
Еще одну проблему порождает механизм взаимодействия процессов тонирования и наложения. При использовании RGB-цветов программисту нужно выбрать один из двух вариантов.
Текстура может “модулировать” оттенок, назначенный участку изображения в процессе тонирования. Это означает, что значения интенсивности наложенной текстуры умножаются на значения интенсивностей основных цветов, сформированных алгоритмом тонирования. Режим модуляции считается в OpenGL основным и устанавливается по умолчанию. В явном виде этот режим устанавливается вызовом функции glTexEnv():
glTexEnv(GL_TEX_ENV, GL_TEX_ENV_MODE, GL_MODULATE);
Если в вызове этой функции в качестве третьего аргумента передать не GL_MODULATE, а GL DECAL, то цвет, назначенный алгоритмом наложения, “убьет” цвет, назначенный алгоритмом тонирования. В результате имеющиеся в пространстве сцены источники света никакого влияния на изображение объекта оказывать не будут. Эта технология называется декодированием (deca И ng).
Эффект от наложения текстуры зависит и от того, какой тип проецирования используется при формировании изображения. По умолчанию исполнительная система OpenGL использует для формирования интенсивности накладываемой текстуры линейную интерполяцию в пространстве координат экрана. При ортогональном проецировании такое линейное отражение пространства текстуры на поверхность объекта (фактически на образ поверхности) является корректным, но при перспективном проецировании следовало бы учитывать нелинейные эффекты по глубине. Можно так настроить режим работы исполнительной системы OpenGL, что она будет использовать более сложный метод интерполяции (если таковой поддерживается в данной реализации системы), но за это придется платить снижением производительности. Настройка выполняется оператором
glHint(GL_PERSPECTIVE_CORRECTION, GL_NICEST);
Графическая система OpenGL поддерживает и несколько функций тонкой настройки процесса наложения. Например, можно выделить на изображении полосой заданной ширины (п пикселей) границы области, на которую накладывается текстура. В этом случае пользователь увидит на экране нечто, напоминающее мозаику из плиток разных материалов. Для автоматической генерации координат текстуры в OpenGL можно использовать функцию glTexGen(), которая имеет несколько режимов работы. Один из них позволяет сформировать линейное отражение пространства геометрических координат на пространство координат текстуры. Одно из интересных приложений этого режима— воспроизведение на экране рельефа с наложенной текстурой (скалы, трава и т.п.). Текстура непосредственно проецируется на трехмерную сеть. Другой режим позволяет использовать проекцию текстуры на сферическую поверхность. Очень интересные эффекты можно получить, управляя режимом “привязки” текстуры к координатам объекта или наблю^/еля. Если привязать текстуру к координатам наблюдателя, то при перемещении объекта в поле зрения наблюдателя текстура будет оставаться на месте и у пользователя создастся иллюзия, будто объект “проходит” сквозь текстуру.
Технология работы с текстурами требует очень много памяти, особенно если в изображении используется множество образцов текстур. В подобных приложениях может потребоваться по ходу дела повторно генерировать образцы или загружать из файлов, что, конечно же, отнимает много времени. В OpenGL 1.1 появились объекты текстур (texture objects), благодаря которым в прикладной программе можно определить несколько образцов и сохранять их в памяти, выбирая одну в качестве текущей.
Генерация образцов текстур
Одна из наиболее привлекательных особенностей технологии наложения текстур— это возможность воспроизводить в изображении достаточно много деталей, не затрудняя себя необходимостью формировать множество геометрических объектов. Высококлассные графические системы, например Infinite Reality Engine фирмы SG1, способны выполнять трехмер-
ное наложение текстур в реальном масштабе времени, т.е. обновление вида текстуры от кадра к кадру практически не сказывается на скорости формирования изображения по сравнению с воспроизведением чистой геометрии объектов. Каждая графическая карта в современных ПК оснащается памятью для хранения текстуры, что позволяет разработчикам игровых программ передавать на экране сложную, динамически обновляемую обстановку и создавать полную иллюзию погружения в реальную среду.
Если, например, нам нужно имитировать на экране поле, поросшее травой, то можно получить текстуру травы, отсканировав фотографию. Это будет значительно быстрее, чем формировать двух- или трехмерные объекты, которые будут выглядеть, как стебельки травы. В картографических приложениях воспроизведение мелких деталей рельефа в виде геометрических объектов можно заменить наложением текстуры, отсканированной с карты.
В приложениях можно использовать не только “заимствованные” текстуры, но и формировать их непосредственно в программе с помощью соответствующих процедур. Особый интерес представляют текстуры, характерные для натуральных материалов,— песк", травы и различных минералов. В подобных текстурах можно выделить две составляющие — регулярную и случайную. В большинстве методов генерирования таких текстур используется генератор случайных чисел, выходные данные которого “пропускаются” через фильтр (рис. 9.15). Идеальный генератор случайных чисел формирует белый шум — последовательность некоррелированных в статистическом смысле чисел. Управляя передаточной функцией фильтра, можно сформировать из белого шума последовательность чисел с заданными статистическими характеристиками (дисперсией, функцией корреляции и т.п.) и сформировать с их помощью псевдослучайную текстуру.
Белый шум| Текстура-►- Фильтр I—ВБелый шум| Текстура-►- Фильтр I—ВМетоды обработки случайных чисел используются и для формирования трехмерных текстур. Функция интенсивности T(s, t. г) образца такой текстуры зависит от трех параметров, и ее можно непосредственно ассоциировать с
Белый шум| Текстура
-►- Фильтр I—В
Белый шум| Текстура
-►- Фильтр I—В
Рис. 9.15. Процедурная генерация точкой (х, у. г) трехмерного пространства, что позволяет текстуробойти все сложности процесса наложения двухмерной
текстуры на трехмерный объект. От пользователя (программиста, разрабатывающего прикладную программу) требуется только подобрать подходящую трехмерную текстуру с определенными пространственными свойствами. Концептуально работа с трехмерной текстурой напоминает нанесение рельефа на заготовку объемной скульптуры. Подобная технология используется при создании объектов, которые будут выглядеть подобно высеченным из каменной глыбы — мрамора или гранита.
Наложение изображения окружающих предметов
Если в составе сцены есть предметы с гладкой поверхностью, то в реальной обстановке на поверхности таких предметов мы всегда видим отраженное изображение близлежащих объектов сцены — отражение среды. Примеров тому не счесть. Если в центр комнаты поместить гладкий металлический шар, то на его поверхности вы увидите, как в кривом зеркале, всю обстановку комнаты. Такое изображение в графической системе можно сформировать, воспользовавшись методом трассировки лучей. Но применение этого метода связано с большим объемом вычислений, а потому формирование изображения, если не использовать специализированную аппаратуру сверхвысокого быстродействия, отнимает довольно много времени. Отраженное изображение можно сформировать и другим способом, во многом аналогичном наложению текстуры. Этот метод использует примерно такую же технологию, как и метод наложения текстур, но вместо искусственно созданных образцов текстур используются карты среды (environmental maps) или карты отражения (reflection maps).
В основе метода лежит чрезвычайно простая идея. Всем известно, что на поверхности зеркала мы видим отражение окружающей обстановки. Если не пытаться воспроизводить программно процесс отражения, а просто принять к сведению, что на поверхности нужно сформировать подобное изображение, то это можно сделать с помощью двухэтапной процедуры. На первом этапе нужно получить изображение среды в том виде, в каком оно проецируется на промежуточную воображаемую картинную поверхность (не обязательно плоскость). Это можно сделать примерно так, как было описано в разделе 9.2. Центр проецирования размещается при этом в центре объекта с отражающей поверхностью, а при формировании проекции сам объект изымается из сцены (рис. 9.16). Сформированные в результате проекции изображения в дальнейшем используются как обычные образцы текстуры, а их наложение выполняется одним из ранее рассмотренных способов. Но следует учесть, что, поскольку мы стремимся воспроизвести эффект отражения, нужно в процессе переноса изображения с промежуточной поверхности на поверхность объекта изменить положение наблюдателя и направление нормали к поверхности, как показано на рис. 9.17.
Рис. 9.16. Наложение изображения окружающей средыРис. 9.16. Наложение изображения окружающей средыРис. 9.17. Наложение изображения, сформированного на промежуточной поверхностиРис. 9.17. Наложение изображения, сформированного на промежуточной поверхности
Рис. 9.16. Наложение изображения окружающей среды
Рис. 9.16. Наложение изображения окружающей среды
Рис. 9.17. Наложение изображения, сформированного на промежуточной поверхности
Рис. 9.17. Наложение изображения, сформированного на промежуточной поверхности
Технология наложения изображений окружающих предметов поддерживается набором функций OpenGL. После того как сформирована нужная текстура— то ли сканированием изображения, то ли с помощью проецирования сцены, — OpenGL автоматически формирует координаты текстуры для сферического наложения. Алгоритмы, реализованные в виде функций OpenGL, сопоставляют касательную к поверхности объекта с касательной к поверхности сферы, на которую накладывается текстура, как показано на рис. 9.18.
Рис. 9.18. Принцип наложения изображения среды, реализованный в OpenGLРис. 9.18. Принцип наложения изображения среды, реализованный в OpenGL
Рис. 9.18. Принцип наложения изображения среды, реализованный в OpenGL
Рис. 9.18. Принцип наложения изображения среды, реализованный в OpenGL
Располагая координатами вершин и компонентами векторов нормалей, можно вычислить угол отражения. Затем нужно отыскать на сфере точку, касательная к которой имеет такое же направление. В программе этот алгоритм реализуется следующей последовательностью операторов:
glTexGenfv(GL_S, GL_SPHERE_MAP, 0);
glTexGenfv(GL_T, GL_SPHERE_MAP, 0);
glEnable(GL TEXTURE GEN_S);
glEnable(GL~TEXTURE~GEN_T);
На ил. 14 цветной вклейки показано изображение, сформированное в игровой программе Geri’s Game, разработанной в Pixar Animation Studio. При создании изображения очков персонажа в этом кадре использован метод наложения среды. Для формирования карты отражения была использована поверхность куба (ил. 15 вклейки), а затем сформированное на ней изображение было наложено на очки персонажа.
Наложение микрорельефа
Теперь вернемся к нашему примеру с формированием изображения апельсина. Его можно создать, воспользовавшись фотографией настоящего апельсина, — отсканировать участок его поверхности на фотографии, сформировать из отсканированного изображения образец проективной текстуры и наложить его на объект апельсина в моделируемой сцене. Но если сценарием предусмотрен поворот апельсина или изменение положения источников света в пространстве сцены, то внимательный зритель сразу отметит “искусственность” созданного изображения апельсина. Дело в том, что для поверхности реального апельсина характерна не столько вариация окраски, сколько вариация рельефа — микрорельеф, — а последние не передаются наложением на изображение проективных текстур. Технология наложения микрорельефа {bump mapping) вносит определенные искажения в параметры формы поверхности, в частности в компоненты нормали к разным ее участкам, которые используются алгоритмами тонирования. В результате этот алгоритм вносит нерегулярность в вычисление цвета отдельных пикселей изображения поверхности на экране.
Начнем с констатации того факта, что нормаль к участку поверхности в определенной точке характеризует форму поверхности в этой точке. Внесение небольших искажений в компоненты вектора нормали приведет к появлению локальных искажений формы поверхности— созданию эффекта микрорельефа. Учет этих искажений на стадии тонирования поверхности объекта сохранит влияние на изображение текущей конфигурации источников света и прочих факторов, учитываемых при тонировании.
Внести искажения в компоненты вектора нормали можно разными способами— ниже будет продемонстрировано, как это делается, если поверхность задана в параметрической форме. Пусть p(w, v) — точка на поверхности, описываемой параметрическими уравнениями. Единичный вектор нормали к поверхности в этой точке есть векторное произведение векторов касательных, образуемых как частные производные по основным параметрам:
п=^Ь_
|р« ХР1.|
Предположим, что в некоторой точке выполнено смещение в направлении нормали малого участка поверхности. Это смещение описывается известной функцией микрорельефа {bump function), d{u, v), причем значения функции достаточно малы {\d{u, v)| « 1):
р' = р + d{u, v)n.
Но нам требуется не изменять геометрию объекта, а только создать у наблюдателя иллюзию такого изменения. Для этого будем изменять не координаты точки р, а компоненты вектора нормали в этой точке п, а затем измененный вектор передавать алгоритму тонирования.
Нормаль в точке р' есть векторное произведение
п' = р'„ X р',,.
Векторы р'„ и р'у можно вычислить следующим образом:
, Bd
Ри =Ри +—n + </(«.vX, ои
р, =р, +—-n*d(u,v)nv.
OV
Если d мало, то последними членами в правой части обоих уравнений можно пренебречь и, учитывая п х п=0, после их подстановки в выражение для векторного произведения получим выражения для вычисления компонентов искаженной нормали:
, Bd Bd
n =n +—nxp, + —nxpu. OUOV
Вектор разности между исходной и искаженной нормалями лежит в плоскости, касательной к поверхности в точке р. Чтобы выполнить наложение карты микрорельефа, нам потребуются два массива, которые будут содержать значения Bd/Bu и BdlBv. Элементы этих массивов следует вычислить заранее, воспользовавшись методами, аналогичными тем, которые применялись при формировании проективных структур. Искажение компонентов нормалей реализуется в процессе тонирования.
Запись в буферы
Работая с современной графической системой, программист может записывать данные в промежуточные буферы и считывать данные из них. Но на выполнение этих операций оказывают влияние два фактора, которые заставляют выполнять их не так, как обычные операции чтения/записи в память. Надо сказать, что потребность в таких операциях на уровне отдельных пикселей возникает довольно редко в прикладной графической программе. Как правило, требуется считывать или записывать целый блок пикселей (или битов). Например, при заливке внутренней области многоугольника единой процедурой формируется целая строка растра. Чтобы вывести на экран изображение точечного символа, его образ переносится в небольшой блок пикселей. При очистке экрана нужно единой процедурой записать нули во все ячейки буфера кадра. Следовательно, необходимо располагать как программными, так и аппаратными средствами выполнения таких групповых операций над блоками пикселей, как правило, — прямоугольными, причем эти средства должны обеспечивать максимальную скорость выполнения операций. Такие операции даже обозначаются специальным термином “пересылка битового блока" (bit-block transfer, или bitbit}). Иногда можно встретить и термин растровые операгрш (raster operations, или raster-ops).
Предположим, что возникла необходимость перенести блок пикселей размером пхт из одного буфера в другой. Первый буфер в такой операции — буфер-источник (source buffer), а второй— буфер-приемник (destination buffer). На рис. 9.19 схематически представлен процесс передачи битового блока, причем в роли буфера-приемника в этой операции выступает буфер кадра. В программе операция пересылки битового блока выглядит примерно так: write_block(source, n, m, x, у, destination, u, v);
Буфер-приемникБуфер-источник—^^Буфер кадраПамятьРис. 9.19. Пересылка битового блока из одного буфера в другойБуфер-приемникБуфер-источник—^^Буфер кадраПамятьРис. 9.19. Пересылка битового блока из одного буфера в другойВ этом операторе source и destination — адреса буферов, тип — размеры блока. В результате выполнения оператора блок с размерами nxm , начальный элемент (точка) которого имеет параметры (х, у), будет перемешен из буфера-источника source в буфер-приемник destination и размещен в этом буфере, начиная с элемента (u, v). Хотя существует множество нюансов, на которые следует обращать внимание при программировании подобной операции (например, а что произойдет, если блок выходит за границы буфера-приемника), суть ее состоит в том, что вся процедура выполняется как единая операция, которая изменяет содержимое буфера-приемника. Обратите внимание на то, что с точки зрения аппаратной реализации выполняемые при этом действия никак не связаны с характеристиками, затрагивающими обработку геометрических
Буфер-приемник
Буфер-источник—^^Буфер кадра
Память
Рис. 9.19. Пересылка битового блока из одного буфера в другой
Буфер-приемник
Буфер-источник—^^Буфер кадра
Память
Рис. 9.19. Пересылка битового блока из одного буфера в другой
объектов. Следовательно, аппаратные средства для выполнения операций с битовыми блоками имеют совершенно другую архитектуру, чем аппаратные средства реализации конвейерной обработки геометрических объектов.
Режимы записи битовых блоков
Второе отличие операций с битовыми блоками от обычных операций с ячейками памяти состоит в том, что содержимое соответствующего блока в буфере-приемнике после выполнения операции может быть изменено множеством способов, т.е. имеется множество режимов записи информации в буфер-приемник. При обычных операциях записи в память новое значение заменяет значение, хранившееся в заданной ячейке (или блоке) ранее. При выполнении в программе, написанной на языке С, оператора такого вида (оператора присваивания):
у=х;
значение из ячейки памяти х дублируется в ячейке памяти у.
Но это не единственно возможный вариант передачи информации в буфер-приемник. Предположим, что нам нужно поразрядно обрабатывать информацию в буферах. Рассмотрим модель, представленную на рис. 9.20. Обозначим через s выбранный бит буфера-источника, а соответствующий ему бит в буфере-приемнике — через d. Если возможно перед записью в буфер-приемник прочесть текущее значение бита d, то изменение информации в буфере- приемнике можно описать функцией преобразования:
Буфер-источникБуфер-источник
Буфер-источник
Буфер-источник
wrlte_plxel
read_plxel
•Буфер-приемник
Рис. 9.20. Режимы записи
Существует 16 вариантов совместной обработки двух одноразрядных двоичных чисел — 16 логических функций f. Эти логические функции представлены в таблице на рис. 9.21, причем каждая колонка в правой части таблицы соответствует одному из возможных вариантов функции / Десятичный эквивалент двоичного числа, образованного содержимым ячеек таблицы в каждой из колонок, будем использовать в качестве номера режима побитовой записи (writing mode). Можно обозначать режимы и соответственно наименование логической функции, определяемой каждой колонкой таблицы (дизъюнкция, конъюнкция, импликация и т.д.). Предположим, что значение 1 соответствует цвету фона (background color) (например, черному), а значение 0 — цвету переднего плана (скажем, белому). Можно наглядно представить результат, который получится в каждом из режимов (при каждом варианте определения вида функции У), в виде соответствующего изображения. Режимы 0 и 15 задают операции очистки — независимо от значения s и прежнего значения d новое значение d будет равно либо О (в режиме 0), либо 1 (в режиме 15). Режимы 3 и 12 являются режимами обычной записи (режим 12 — записи с инвертированием). Режим 3 соответствует функции
d^- s.
В этом режиме значение s заменяет прежнее значение d. Режим 7 соответствует логической операции 0R (ИЛИ):
d s-d.
Если выполняется запись цвета переднего плана поверх цвета фона, то в режимах 3 и 7 получится одинаковый результат, но если записывается цвет фона поверх цвета переднего плана, то результат будет разным. Какой режим выбрать, зависит от того, какой эффект желательно получить в данном приложении.
Режим записи XOR
Режим 6 соответствует логической операции XOR (исключающее ИЛИ), которая обозначается значком Ф. Этот режим позволяет получать очень интересный эффект при использовании его в графических системах для операций с содержимым буфера кадра. В отличие от режимов Зи 12, режим 6 нельзя реализовать, если невозможно прочесть прежнее состояние бита d. Интересные особенности режима 6 связаны с тем, что для функции XOR переменных х и у справедливо соотношение
(х Ф jO Ф у = х.
Таким образом, если по отношению к некоторому биту дважды применить функцию XOR, то получим исходное значение бита, каким оно было до выполнения операций.
На рис. 9.22 режимы 6 и 7 представлены в виде изображений, которые образуются в результате вывода содержимого буфера кадра, образованного после выполнения операций записи в этих режимах.
Режим 3Режим 7Режим 3Режим 7Рис. 9.22. Операции записи в режимах би 7Рис. 9.22. Операции записи в режимах би 7Очень часто этот режим используется в приложениях, в которых пользователь интенсивно взаимодействует с программой с помощью разных меню. (Такую программу мы рассматривали в главе 3.) В ответ на щелчок кнопкой мыши на экране должно появиться меню, которое частично перекроет ранее сформированное изображение. После того как пользователь завершит работу с меню, его нужно убрать с экрана, а в области, перекрытой этим меню, восстановить исходное изображение. Иногда такая операция реализуется с помощью дополнительной памяти, в которой хра
Режим 3Режим 7
Режим 3Режим 7
Рис. 9.22. Операции записи в режимах би 7
Рис. 9.22. Операции записи в режимах би 7
нится растровый образ меню в виде битового блока М. Обозначим через S содержимое той части буфера кадра, которая соответствует участку основного изображения, перекрываемому образом меню при выводе последнего на экран. Рассмотрим последовательность операций:
S <- 5 Ф М,
M^S®M,
S^-S®M.
Предполагается, что операция XOR применяется к соответствующим битам в блоках S и М. Если подставить первое соотношение во второе, а второе — в третье, то окажется, что после выполнения третьей операции на экране появится изображение меню. Исходное изображение при этом окажется во вспомогательной памяти, на том месте, где раньше было изображение меню. Таким образом, с помощью операций пересылки битового блока будет выполнен обмен изображениями между буфером кадра и вспомогательной памятью. Этот метод обмена содержимым двух буферов существенно отличается от привычного, который предполагает использование еще одного, промежуточного буфера.
Существует множество вариантов применения такой технологии. Например, она используется при перемещении по экрану изображения курсора, вычерчивании резиновых линий или фигур. Последнюю функцию очень трудно реализовать в API, не рассчитанных на специфику растровых дисплеев (см. упр. 9.1 и 9.2).
Операции с пикселями в OpenGL
Оцифрованное изображение состоит из ^-разрядных пикселей, где к может быть любым числом от 1 (черно-белое изображение без градаций серого) до 32 и даже больше (изображение в формате RGBA с высоким разрешением по цвету). Хотя в принципе черно-белое (одноразрядное — bitmap) изображение отличается от многоградационного (скажем, одно- или двухбайтового) только количественно, оно обрабатывается и программными, и аппаратными средствами совершенно по-другому. Для аппаратной обработки черно-белого изображения требуются только логические операции с одной битовой плоскостью буфера. Как правило, двуградационное представление используется для шрифтов, масок и образцов. OpenGL располагает средствами работы как с черно-белым, так и с многоградационным изображением.
Операции с блоками битов имеют смысл при работе с черно-белым изображением, например изображением символов шрифта или курсоров. Значительно менее понятно, как использовать такие операции при работе с многоразрядными пикселями или многоградационным представлением цвета, поскольку далеко не все логические операции из имеющихся 16 типов дают визуально ощутимый эффект. OpenGL поддерживает операции с блоками битов только в режиме индексирования цвета, для чего в составе API имеется функция glLogicOp().
OpenGL поддерживает работу не только отдельного конвейера обработки пикселей, но и множество отдельных буферов, предоставляя в распоряжение программиста операции пере-
сылки данных между этими буферами и между буферами и памятью процессора. Вид информации, которую можно хранить в том или ином буфере — от индексов основных цветовых компонентов до данных о глубине, — зависит от типа буфера. Кроме того, программист может обращаться и к основной памяти процессора, используя стандартные типы переменных— байты, целые числа или вещественные числа в формате с плавающей точкой. Такое разнообразие форматов имеет и обратную сторону— оно подчас затрудняет выбор самого эффективного способа организации данных при создании сложных приложений. В этой книге мы не будем обсуждать все нюансы, а ограничимся кратким обзором тех функциональных возможностей, которые предоставляются программисту при работе с OpenGL.
Буферы OpenGL
Графическая система OpenGL поддерживает работу со следующими буферами:
буферы цвета;
буфер глубины;
буфер-накопитель (аккумулятор);
буфер маски.
Группа буферов цвета включает буфер кадра, но, как мы уже говорили при обсуждении методики двойной буферизации в главе 3, таких буферов может быть несколько. При использовании двойной буферизации обычно говорят о рабочем {front} и фоновом {back} буферах. В приложении можно сформировать и стереопару, для чего используется буфер правого и левого изображений стереопары, т.е. изображения, которое видимо правым или левым глазом. Как правило, в одном из буферов цвета программа создает образы геометрических объектов, но программист может и напрямую обращаться к некоторому пикселю в буфере, считывать его текущее значение или записывать в него определенный код цвета. Только информация из буферов цвета может появиться на экране.
Буфер глубины обычно используется при реализации алгоритмов удаления невидимых поверхностей, но к нему также можно обращаться непосредственно и считывать/записывать данные. В разделе 9.8 будет показано, как при выполнении различных операций можно использовать буфер-накопитель (аккумулятор). Буфер .маски используется для формирования масок, “вырезающих” из общего массива те пиксели, которые следует вывести на экран.
В дальнейшем, если не оговаривается иное, рассматривая операции с буферами, будем считать, что речь идет о буферах цвета, чаще всего — буфере кадра, хотя аналогичные операции можно применять и в отношении других буферов. Обращаю ваше внимание на то, что конкретная версия системы, установленная на вашем компьютере, может содержать только часть из перечисленных выше буферов, причем разрядность (глубина) буферов также меняется от версии к версии.
OpenGL работает с позицией растра {raster position}, не принимая во внимание информацию о размещении геометрических объектов на экране. Параметр позиция растра является одним из компонентов текущего состояния графической системы. Можно считать, что для графической системы позиция растра определяется положением курсора в координатах видового окна, начиная с которой размещается специфицированный в очередных операциях растровый примитив (массив пикселей). Позиция растра устанавливается в OpenGL с помощью одного из вариантов функции glRasterPos*(). Например, в качестве аргументов вызова этой функции можно использовать набор координат (х, у. с} в формате с плавающей точкой, причем в отношении точки, заданной этим набором, все текущие преобразования вида и проецирования выполняются прежде, чем она будет подвергнута растровому преобразованию: glRasterPos3f(х, у, г);
Эта позиция, как и все вершины, сохраняется в формате четырехмерного вектора однородных координат. Но после определения положения точки в координатах видового окна запись кодов засветки пикселей или иной двоичной информации, начиная с этой позиции, требует задания аргументов в единицах растра, т.е. в формате целых чисел. Считается, что центры пикселей размешаются на середине интервала между соседними целыми числами.
Использование растровых образов символов шрифта
В этом разделе будет продемонстрирована технология работы с позицией растра на примере операций с текстовыми шрифтами. В главе 3 уже шла речь о том, что вывод на экран текста, представленного растровым шрифтом, осуществляется значительно быстрее, чем текста, представленного штриховым шрифтом. Определенный шрифт специфицирует набор символов единого начертания, или стиля, например Times, Courier, Computer Modern. Как правило, каждый шрифт включает несколько наборов символов разных размеров, например размером 10 пунктов или 24 пункта, и разных начертаний — курсив, полужирный, с подчеркиванием. Единица измерения высоты шрифта— пункт— соответствует 1/72 дюйма. Но, когда мы будем говорить о растровом шрифте, речь будет идти только об одном наборе
Рис. 9.23. Символ растрового шрифтаРис. 9.23. Символ растрового шрифтасимволов единого размера и начертания. Так, шрифт размером 8x13, который поддерживается библиотекой GLUT, обозначается системной константой GLUT BITMAP_8_BY_13, а пропорциональный шрифт высотой 10 пунктов— константой GLUT_BITMAP_TIMES_ROMAN_10. Как правило, шрифт включает 128 или 256 символов. Каждый символ растрового шрифта задается определенным растровым трафаретом (рис. 9.23), представленным в виде двоичного кода. Учтите, что при таком представлении растрового шрифта его размер на экране зависит от разрешения конкретной системы отображения, что принципиально отличает растровые шрифты от штриховых.
Рис. 9.23. Символ растрового шрифта
Рис. 9.23. Символ растрового шрифта
При разработке формы символов растрового шрифта приходится учитывать множество факторов, определяющих “читабельность” символов, и в то же время “уложиться” в заданное поле пикселей. Как видно на примере символа, изображенного на рис. 9.23, при выборе формы символа даже самого малого размера на поле пикселей следует оставлять интервал
между буквами как по горизонтали, так и по вертикали, предусмотреть место для выступающих частей таких букв, как g и у (а в наборе символов кириллицы еще и буквы Й), обеспечить различную высоту строчных и прописных букв. Разработанный шрифт сохраняется в виде
двоичного кода. Например, для шрифта из 128 символов размером 8x13 потребуется выде
лить массив
GLubyte my_font[128][13];
Перенос двоичного массива bitmap некоторого символа на экран в текущую позицию растра выполняется обращением к функции glBitmap():
glBitmap(width, height, xo, yo, xi, yi, bitmap);
Аргументы width и height задают соответственно ширину и высоту символа в пикселях.
Аргументы хо и уо имеют формат числа с плавающей точкой и задают смещение положения
выводимого образа (его левого нижнего угла) относительно текущей позиции растра, а аргументы xi и yi, которые также имеют формат чисел с плавающей точкой, — приращение текущей позиции растра после завершения операции (рис. 9.24).
Рис. 9.24. Вывод на экран образов символов растрового шрифта
В главе 3 уже рассматривалась методика использования дисплейных списков для вывода текста. Если массив образов символов my_font уже определен, то новый дисплейный список будет сформирован следующим фрагментом программы5: base=glGenLists(128) for(i=0, i<127, i++) {
glNewList(base+i, GL_COMPILE);
glBitMap(8, 13, 0.0, 2.0, 10.0, 0.0, my_font[i]);
glEndList();
}
Обратите внимание на то, что каждый символ размещается на два пикселя вверх относительно текущей позиции растра, а после завершения вывода символа позиция растра “сдвигается” вправо не на 8, а на 10 пикселей, что обеспечивает интервал между соседними символами и соседними строками в тексте.
Пиксели и изображения
В OpenGL пиксели обрабатываются в виде групп 8-разрядных байтов, которые могут представлять разные форматы описания изображения. В RGB-формате интенсивность каждого первичного цвета кодируется одним байтом, а в формате представления монохромного (одноцветного) изображения каждому пикселю соответствует число в формате с плавающей точкой.
В составе API OpenGL есть три функции перемещения участков описания изображения:
'Обращаю ваше внимание на то. что выделять память под первые 32 символа набора в кодировке ASCII не имеет смысла, поскольку эти символы играют роль управляющих и на экран не выводятся.
glReadPixels(x, у, width, height, format, type, image); glDrawPixels(width, height, format, type, image);
glCopyPixels(x, y, width, height,type);
Функции glReadPixels() и glDrawPixels () позволяют считывать и записывать прямоугольные участки пикселей из массива или в массив, заданный аргументом image. В первом случае буфер OpenGL является буфером-приемником, а во втором — буфером-источником. Функция glCopyPixels() копирует участок пикселей из одного места определенного буфера цвета в другое. Аргументы х и у функции glReadPixels() задают смещение начала считываемого участка пикселей относительно начала буфера кадра, а аргументы width и height — соответственно ширину и высоту участка. Хотя в большинстве приложений операции на уровне пикселей с помощью функций glReadPixels() и glDrawPixels () выполняются над содержимым буфера кадра (этот режим устанавливается по умолчанию), их можно выполнять с любым буфером.
Таблицы соответствия цветов
Индекс
Красный
Зеленый
Синий
0 1
255
8 бит 8 бит 8 бит8 бит 8 бит 8 битЕсли в приложении используется режим индексированных цветов, то OpenGL позволяет изменить палитру цветов сформированного изображения с помощью внесения изменений в таблицу соответствия цветов. Предположим, что во всех буферах содержатся 8-разрядные коды цветов. Следовательно, каждому коду может быть сопоставлена строка в таблице соответствия, имеющей 256 строк (рис. 9.25). Таким образом, в буферах фактически содержатся не абсолютные коды цветов, а индексы, отсылающие исполнительную систему OpenGL к таблице соответствия. Загрузка сформированной пользователем таблицы выполняется функцией glPixelMap().
8 бит 8 бит 8 бит
8 бит 8 бит 8 бит
Рис 9.25. Таблица соответствия цветовРис 9.25. Таблица соответствия цветовИспользование таблицы соответствия позволяет очень легко преобразовать монохромное изображение в цветное. Единственное, что требуется сделать для такого преобразования, — настроить исполнительную систему OpenGL на работу с другой таблицей соответствия. Создаваемое таким способом изображение иногда называют псевдоцветным (pseudocolor). В главе 11 будет показано, как на практике используется такая технология на примере вывода изображения множества Мандельброта (Mandelbrot).
Рис 9.25. Таблица соответствия цветов
Рис 9.25. Таблица соответствия цветов
Если в приложении решено по тем или иным причинам использовать технологию индексирования цвета, то перед разработчиком возникает проблема выбора палитры, которая включала бы 256 цветов из 224 доступных на современном типовом устройстве отображения. Одна из методик рекомендует использовать так называемую термальную шкалу, в которой более “теплые” цвета (красный, белый и желтый) передают большие значения, а “холодные” цвета (синий и зеленый) — меньшие. Предположим, что прикладная программа сформировала монохромное изображение, в котором между важностью участка и значением его интенсивности установлена монотонная связь (не обязательно даже пропорциональная). На рис. 9.26 показан возможный вид функций отображения интенсивности на значения основных цветовых составляющих (красной, зеленой и синей), который примерно соответствует термальной шкале. На ил. 9 цветной вклейки показано, как используется термальная шкала при визуализации результатов научных расчетов. Цвет на этой картинке позволяет зрителю оценить распределение температуры в мантии Земли, смоделированное компьютерной программой.
Интенсивность серогоИнтенсивность серогоИнтенсивность серогоИнтенсивность серогоИнтенсивность серогоИнтенсивность серогоРис. 9.26. Функции отображения интенсивности на значения основных цветовых составляющих, воспроизводящие термальную шкалуРис. 9.26. Функции отображения интенсивности на значения основных цветовых составляющих, воспроизводящие термальную шкалу
Интенсивность серогоИнтенсивность серогоИнтенсивность серого
Интенсивность серогоИнтенсивность серогоИнтенсивность серого
Рис. 9.26. Функции отображения интенсивности на значения основных цветовых составляющих, воспроизводящие термальную шкалу
Рис. 9.26. Функции отображения интенсивности на значения основных цветовых составляющих, воспроизводящие термальную шкалу
Другая область применения технологии индексирования цветов — преобразование цветов и настройка нелинейной шкалы яркости. Например, при использовании большинства упрощенных алгоритмов закрашивания (а такие алгоритмы получили наибольшее распространение в графических системах на базе ПК, поскольку применить более сложные не позволяют аппаратные средства) изображение получается темным и неконтрастным. График распределения яркости (гистограмма яркости) в типовом изображении, полученном таким способом, выглядит примерно так, как на рис. 9.27. Эта кривая свидетельствует о том, что большая часть изображения имеет слабую яркость. Можно частично исправить качество изображения, применив таблицу соответствия, воспроизводящую нелинейную функцию передачи, приведенную на рис. 9.28. Приращение интенсивности между элементами таблицы, соответствующими малым значениям индексов (входного параметра), будет тогда больше, чем между элементами, которые соответствуют большим значениям. В результате удается выровнять гистограмму яркости изображения на экране и несколько увеличить его контрастность.
Рис. 9.27. Гистограммы распределения яркости типового изображения до коррекцииРис. 9.27. Гистограммы распределения яркости типового изображения до коррекцииПрежняя интенсивностьПрежняя интенсивностьРис. 9.28. Нелинейная характеристика, воспроизводимая корректирующей таблицей соответствияРис. 9.28. Нелинейная характеристика, воспроизводимая корректирующей таблицей соответствия
Рис. 9.27. Гистограммы распределения яркости типового изображения до коррекции
Рис. 9.27. Гистограммы распределения яркости типового изображения до коррекции
Прежняя интенсивность
Прежняя интенсивность
Рис. 9.28. Нелинейная характеристика, воспроизводимая корректирующей таблицей соответствия
Рис. 9.28. Нелинейная характеристика, воспроизводимая корректирующей таблицей соответствия
Таблицу соответствия можно использовать и для преобразования цветов. В главе 7 уже отмечалось, что используемые в большинстве API, в том числе и в OpenGL, значения компонентов RGB-формата не “привязаны” к какой-либо конкретной системе воспроизведения изображения. Если сфотографировать изображение с экрана ЭЛТ, то даже невооруженным глазом можно обнаружить несовпадение цветовой палитры фотографии и исходного изображения. Это объясняется различием спектральных кривых чувствительности фотографических эмульсий фотопленки, фотобумаги, люминофоров ЭЛТ и глаза наблюдателя. С помощью таблицы соответствия можно добиться коррекции цветовой гаммы в определенном диапазоне значений, подстраивая значения по каждому из основных цветовых компонентов (R, G и В). Этот процесс получил название регулировки цветового баланса (color balancing) и выполняется перед фотографированием изображения с экрана монитора. Вообще говоря, повозившись с содержимым таблицы соответствия, можно добиться практически одинаковой цветопередачи изображения, сформированного разными системами отображения.
Использование буферов в процедуре указания объекта
Поработав некоторое время с буферами и набравшись опыта, вы убедитесь, что с их помощью можно выполнять практически любые процедуры формирования и модификации изображения. Но операции с буферами могут использоваться и при выполнении интерактивных процедур. Рассмотрим самую распространенную из них — указание графического объекта на экране. При выполнении этой процедуры система должна вернуть прикладной программе идентификатор того графического объекта, на котором находится курсор (маркер положения мыши). В составе API OpenGL имеется механизм поддержки обратной связи между растровым изображением и описанием геометрических объектов сцены с помощью специальных списков (hit lists). Выбранный способ поддержания обратной связи объясняется особенностями работы геометрического конвейера при формировании изображения. При появлении запроса на выполнение процедуры указания организуется повторный цикл формирования изображения, но если в изображении не слишком много геометрических объектов, то эту задачу можно решить и иным способом, воспользовавшись буферами.
Предположим, что в изображение входит не более 256 объектов (примитивов), т.е. за каждым объектом можно “закрепить” уникальный 8-разрядный идентификатор. В программе организуется дополнительный 8-разрядный двоичный pick-буфер, размер которого соответствует разрешающей способности изображения, т.е. за каждым пикселем изображения будет “закреплена” 8-разрядная ячейка. При формировании экранного образа очередного примитива синхронно с заполнением буфера кадра в одноименные ячейки pick-буфера записывается код идентификатора примитива (объекта). Располагая таким буфером, не представляет труда, пользуясь описанными выше функциями OpenGL, считать по заданным координатам курсора мыши идентификатор объекта, образ которого включает эту точку экрана. Если объединить этот процесс с алгоритмом z-буфера, то из всех объектов, проецирующихся в указанную точку, можно выбрать тот, который ближе всех расположен к камере.
Технология комбинирования изображений
В главе 6 были рассмотрены алгоритмы формирования изображений прозрачных объектов. Описанные в той главе методы могут применяться совместно с технологией трассировки лучей, которая сопряжена с достаточно большим объемом вычислений. OpenGL предоставляет в распоряжение прикладного программиста иной механизм формирования прозрачных объектов, позволяющий обойтись без трассировки лучей в явном виде. Этот механизм получил название альфа-смешивание (alpha-blending, или a.-blending\ Интенсивность альфаканала входит в качестве четвертой компоненты в вектор цвета в формате RGBA (или RGBa). Прикладная программа может управлять интенсивностью альфа-канала точно так же, как и интенсивностью каждого из основных цветов, т.е. задавать значение интенсивности А для каждого пикселя. Но исполняющая система OpenGL трактует это значение по-иному. Для нее заданное значение интенсивности компоненты А определяет коэффициент пересчета значений основных цветов при записи их в буфер кадра. В результате оказывается, что в код засветки пикселя вносит свой вклад несколько геометрических объектов и формируется смешанное, или комбинированное, изображение.
Поглощение света и смешивание изображений
Коэффициент поглощения а определенной среды есть количественная мера, позволяющая оценить, какая доля светового потока, падающего на поверхность раздела сред, проходит дальше, а какая поглощается. Если коэффициент поглощения равен 1 (а=1), то свет полностью поглощается на поверхности раздела. Поверхность, для которой коэффициент а равен О,
является идеально прозрачной — весь падающий на нее световой поток проходит дальше.
Коэффициент прозрачности т (transparency) поверхности (фактически среды) связан с ко
эффициентом поглощения простой зависимостью т=1-ос Рассмотрим три равномерно окрашенных многоугольника, показанных на рис. 9.29. Предположим, что средний многоугольник непрозрачен, а ближайший к наблюдателю — прозрачен. Если он будет сделан из идеально прозрачного материала, то наблюдатель вообще его не увидит — он увидит только расположенные за ним непрозрачные предметы. Однако если ближайший многоугольник только частично прозрачен (часто говорят— полупрозрачен), как солнцезащитные очки, то наблюда
тель увидит в этом месте некоторый совмещенный цвет, в котором присутствует и цвет полупрозрачного объекта, и цвет расположенного за ним непрозрачного. Поскольку средний многоугольник непрозрачен, то третьего многоугольника наблюдатель за ним не увидит. Если ближайший многоугольник красный, а расположенный за ним — синий, то комбинированный цвет будет иметь фиолетовый оттенок вследствие наложения (смешивания) цветов. Если же и средний многоугольник полупрозрачен, то наблюдатель увидит еще более сложное сочетание цветов всех трех объектов.
Рис. 9.29. Наложение полупрозрачных и непрозрачных объектовРис. 9.29. Наложение полупрозрачных и непрозрачных объектовВ системах компьютерной графики образы многоугольников формируются последовательно, причем порядок их размещения по отношению к наблюдателю в типовой системе не влияет на порядок формирования экранных образов (об этом шла речь в главе 7). Следовательно, если желательно получить такое смешанное изображение, нужно изобрести способ, позволяющий учитывать коэффициент прозрачности объектов на стадии растрового преобразования. Мы будем в дальнейшем использовать понятия пикселя-источника (source pixel) и пикселя-приемника (destination pixel) в том же смысле, что и понятия буфера-
Рис. 9.29. Наложение полупрозрачных и непрозрачных объектов
Рис. 9.29. Наложение полупрозрачных и непрозрачных объектов
источника и буфера-приемника, которые были введены в разделе 9.1. При обработке очередного многоугольника вычисляется фрагмент изображения размером с пиксель, и если этот фрагмент видим, то пикселю присваивается цвет, сформированный в соответствии с принятой моделью закрашивания. До сих пор мы использовали вычисленный алгоритмом тонирования цвет фрагмента, на который при необходимости еще и была наложена определенная текстура, для замены кода засветки соответствующего пикселя в буфере кадра. Если рассмат
ривать вычисленный код засветки в качестве интенсивности пикселя-источника, а текущий код засветки в этой ячейке буфера кадра как интенсивность пикселя-приемника, то эти две
интенсивности можно скомбинировать самыми разными способами. Использование а-канала является одним из способов выполнить наложение (смешивание) цветов на уровне отдельных пикселей. Такое наложение моделирует замену двух полупрозрачных цветных стекол одним, которое будет иметь цвет и коэффициент поглощения, отличные от соответствующих харак
теристик каждого из исходных компонентов.
Если представить пиксель-источник и пиксель-приемник в виде массивов из четырех элементов (RGBa):
s (5^ Sg sh S^J,
d — [dr dg dh do],
то результатом смешивания цветов будет новый код засветки: d* = YbMcA bgSg+Cgdg b^b+chdh Ь^^с^Д.
Массивы констант b и с называются соответственно коэффициентами смешивания (blending factors) источника и приемника. Как и базовые цветовые составляющие RGB, значение параметра а не может превышать 1.0 и быть меньше 0.0. Подбирая значения компонента а и коэффициентов смешивания, можно получить массу интересных эффектов при смешивании изображений.
Смешивание изображений
Самый очевидный способ использовать имеющийся в графической системе механизм смешивания цветов с помощью альфа-канала— совмещать на уровне отдельных пикселей изображения объектов, образы которых формируются независимо друг от друга. В таком случае можно рассматривать каждый из них как компонент сложного изображения, который вносит посильный вклад в результирующее изображение. Поскольку обычно желательно удерживать в окончательном изображении значения основных цветовых составляющих в интервале 0.0-1.0 (т.е. не заходить в зону насыщения), то необходимо соответствующим образом подобрать или значения а, или коэффициенты смешивания источника и приемника.
Предположим, что нужно сформировать смешанное изображение п объектов, причем каждый должен внести равный вклад. Для данного пикселя взнос /-го объекта будет равен С,а,. Здесь через С, обозначена тройка основных цветовых составляющих (R„ Gh Bf Подставим вместо С, значение С,in, а вместо а,— значение \!п. Теперь можно будет просто добавлять растровый образ каждого очередного объекта в буфер кадра (перед началом операции во всех ячейках буфера кадра необходимо установить код черного цвета и значение а=0). Другой вариант— настроить значение коэффициента смешивания источника равным \/п, а коэффициента смешивания приемника— равным 1. Хотя математически оба варианта должны привести к одному результату, второй вариант аппаратно реализуется более эффективно.
Смешивание изображений в OpenGL
Настройка режима смешивания изображений в OpenGL выполняется с помощью функции glEnable(), которой в качестве аргумента передается константа GL_BLEND: glEnable(GL_BLEND);
Затем нужно выполнить настройку значений коэффициентов смешивания источника и приемника:
glBlendFunc(source_factor, destination_factor);
В OpenGL существует множество предопределенных значений коэффициентов смешивания, представленных символическими константами, включая значения 1 (константа GL_ONE) и 0 (константа GL_ZERO). Можно задать использование в качестве коэффициентов смешивания источника значения а или 1-а (константы GL SRC_ALPHA и GL_ONE_MINUS_SRC_ALPHA). Аналогичную настройку приемника выполняют константы GL DST_ALPHA и GL_ONE_MINUS_DST_ALPHA. В прикладной программе сначала задаются необходимые настройки, после чего можно использовать формат представления цвета RGBA.
Основная сложность в управлении процессом формирования смешанного изображения состоит в том, что на результат влияет порядок формирования образов многоугольников, который очень сложно контролировать из прикладной программы. Например, в некоторых приложениях используется режим, когда коэффициент смешивания источника приравнивается к значению а, а коэффициент смешивания приемника— к значению (1-<х). В результате после выполнения смешивания очередного изображения получим:
(Rd-, Gj-, Bd., adi) = (asRs+(\-as)Rd, c^G+Q-aJGj, а^-НД-^дВ* atO^l-oQc^).
Эта формула показывает, что результат будет корректен как для прозрачных, так и для непрозрачных объектов, но он все-таки существенно зависит от того, в каком порядке будут обрабатываться объекты при растровом преобразовании. Следовательно, в отличие от других случаев, когда прикладной программе не было никакого дела до порядка обработки объектов при растровом преобразовании, в данном случае желаемый эффект может быть получен только при условии соблюдения определенного порядка.
При использовании смешивания изображений возникает проблема и с механизмом удаления невидимых поверхностей. Обычно в такой ситуации этот механизм нужно отключать, поскольку он не делает различия между непрозрачными и прозрачными объектами, а следовательно, любой объект, который расположен за другим объектом, пусть он даже имеет коэффициент поглощения 0.01, просто не будет подвергаться растровому преобразованию и никак не “проявится” в результирующем изображении. Нам же нужно, чтобы те объекты, которые расположены за полупрозрачными объектами, включались в изображение. Этого можно достичь достаточно простым приемом, управляя маской обновления z-буфера. Дело в том, что для определенных объектов, в данном случае — прозрачных, можно при работе с z-буфером выборочно настраивать режим “только для чтения”. Для этого следует вызывать функцию glDepthMask(GL_FALSE);
Когда буфер глубины работает в режиме “только для чтения”, прозрачный объект, расположенный за непрозрачным, образ которого уже сформирован, растровому преобразованию подвергаться не будет. Если же прозрачный объект расположен перед непрозрачным, то он будет подвергаться растровому преобразованию, но в результате сформируется смешанный цвет. При этом данные о глубине размещения прозрачного объекта в буфер глубины занесены не будут. При обработке непрозрачных объектов восстанавливается нормальный режим работы z-буфера, и процесс идет как обычно. Программа, демонстрирующая методику смешивания цветов, находится в файле blend.с.
Сглаживание погрешностей дискретизации
Очень часто альфа-канал применяется для устранения погрешностей изображения, связанных с дискретностью растра (antialiasing). В главе 7 было отмечено, что в идеале образ прямой на растре должен везде иметь одну и ту же толщину, причем она должна быть равна шагу между точками растра. Такой идеальный образ частично перекрывает некоторое количество пикселей, как видно на рис. 9.30. Предположим, что при обработке фрагмента прямой (напомню, фрагментом в данном случае считается участок образа прямой, не превышающий размеров пикселя), которая выполняется на этапе обработки геометрических данных в ходе растрового преобразования, мы установим значение а для соответствующих пикселей пропорционально (или почти пропорционально) той части площади пикселя, которая перекрывается образом прямой. В самом простом случае значение а может быть приравнено к относительной площади пикселя, перекрытой образом. Затем это значение можно использовать в качестве коэффициента полноты значений цветовых составляющих, вычисленных алгоритмом тонирования. При этом следует настроить такой режим смешивания, который предусматривает применение в качестве коэффициента смешивания приемника значения 1-а, а в качестве коэффициента смешивания источника — значение а. Однако если пиксель частично перекрывается не одним, а двумя или несколькими фрагментами, то появляется несколько вариантов их наложения (рис. 9.31). В правой части рисунка показан случай, когда фрагменты не накладываются друг на друга, а в левой части — когда накладываются. Рассмотрим эту проблему с точки зрения программирования преобразования, которое по очереди независимо обрабатывает все объекты (многоугольники). Предположим, что перед началом процесса в пикселе-приемнике установлен непрозрачный фон цвета Со. Можно установить значение
«0=0, поскольку пиксель еще не перекрыт фрагментом какого-либо многоугольника. Затем обрабатывается первый многоугольник. В результате в пикселе-приемнике будет установлен код цвета Cj и значение а^:
С(/ = (1-а1)С0+а1Сь 0^=0,.
Рис. 9.30. Растровый образ Рис. 9.31. Перекрытие одного пикселя двумя фрагментами прямой
Из приведенных соотношений следует, что если фрагмент первого многоугольника полностью перекрывает пиксель (в этом случае а,= 1), то пикселю-приемнику присваивается цвет этого многоугольника, причем пиксель-приемник после завершения операции становится абсолютно непрозрачным. Если же до выполнения операции пиксель-приемник имел черный цвет (Со=О), то после завершения он будет окрашен в цвет а,С].
Теперь рассмотрим, что произойдет после обработки фрагмента второго многоугольника, “претендующего” на тот же пиксель. Как будут учтены в окончательной засветке пикселя- приемника цвет второго многоугольника и значение его a-компоненты, зависит от того, какой способ интерпретации перекрытия мы выберем. Если перекрытие отсутствует (как на рис. 9.31, слева), можно использовать обычную процедуру смешивания полупрозрачных пикселей:
О/ = (1—«2)(( 1—«|)Со+«|С|)+а2С2,
ad = а,+а2.
Значения компонентов смешанного цвета Cj не выходят за пределы заданного диапазона, следовательно, к ним не нужно применять отсечение. Значение О^ после выполнения операции будет соответствовать размеру части пикселя, закрытой фрагментами обоих многоугольников. Из приведенного выше соотношения для Cj следует, что результирующий цвет будет зависеть от порядка обработки многоугольников.
Значительно сложнее решается вопрос определения смешанного цвета, если фрагменты перекрываются, как в правой части рис. 9.31. Не так просто определить и сам факт перекрытия. Это можно сделать, например, исходя из вероятности наступления такого события, рассматривая ее как функцию значений at и а2. Эти значения пропорциональны доле площади пикселя, закрытой соответствующим фрагментом, а другой информацией о взаимном положении фрагментов мы, как правило, не располагаем (если не усложнять алгоритмы растрового преобразования и не разбивать пиксель еще на несколько более мелких элементов). В среднем область перекрытия двух фрагментов можно считать равной произведению «|«2. На рис. 9.32 представлена графическая интерпретация такого усредненного случая. Новое значение а(/ пикселя-приемника при такой интерпретации можно получить следующим образом:
0^= a,+a2-«ia2.
Pug 9.32. Усредненный вариант перекрытия двух фрагментов в пределах одного пикселяPug 9.32. Усредненный вариант перекрытия двух фрагментов в пределах одного пикселяСложнее решить, какой цвет должен иметь пиксель-приемник, поскольку для этого нужно знать, какой из фрагментов находится ближе к наблюдателю. Такая информация может стать доступной, если объединить процесс смешивания с процессом удаления невидимых поверхностей и таким способом получить доступ к буферу глубины.
Pug 9.32. Усредненный вариант перекрытия двух фрагментов в пределах одного пикселя
Pug 9.32. Усредненный вариант перекрытия двух фрагментов в пределах одного пикселя
Настройка графической системы OpenGL на режим сглаживания выполняется таким образом, что прикладной программе не приходится в явном виде обрабатывать значения компонентов а. В программе требуется только заказать режимы смешивания и сглаживания прямых и точек. Это можно сделать, например, в такой последовательности операторов: glEnable(GL_POINT_SMOOTH);
glEnable(GL_LINE SMOOTH); glEnable(GL_SMOOTHING); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Но учтите, что выполнение сглаживания с использованием альфа-канала сопряжено с большими затратами времени.
Тонирование сцен со множеством полупрозрачных объектов
Хотя использование альфа-канала и позволяет создать в изображении иллюзию прозрачности объектов, с помощью этой методики нельзя корректно передать характер реальных оптических эффектов, которые возникают при прохождении света через полупрозрачные объекты. На рис. 9.33 показаны некоторые из таких эффектов в реальной сцене. В частности, методика работы с альфа-каналом совершенно игнорирует эффект преломления световых лучей при прохождении через границу раздела полупрозрачных сред. Предположим, что объект, расположенный в глубине сцены, абсолютно непрозрачен, но обладает свойствами частичного зеркального отражения, а два объекта, расположенных ближе к наблюдателю, полупрозрачны (рис. 9.33). Рассмотрим, какие пертурбации претерпевают в этой сцене лучи света, испускаемые источником. Часть из них освещает объект, расположенный в глубине, и соответствующие пиксели изображения должны быть окрашены в цвет, который образуется в результате взаимодействия цвета источника и материала поверхности этого объекта. Причем есть еще один нюанс — часть лучей попадает на *■■■■■■■■■■■■■■■■*■■*■■* поверхность этого объекта прямо от источника света, а часть— отразившись от поверхности по/лупрозрачных объектов, расположенных ближе к
/AZ \наблюдателю. Эти последние лучи будут окраше-
\\ ны соответственно цветом тех объектов, от кото-
\рых они отразились по пути. Еще более сложный
вариант представляют лучи, которые по очереди Рис. 9.33. Сцена с несколькими полупро- проходят через оба полупрозрачных объекта или, зрачными объектамиотразившись от первого, попадают на объект в
глубине сцены, отражаются от него, проходят через второй полупрозрачный объект и только потом попадают к наблюдателю.
Графической системе, имеющей конвейерную архитектуру, не под силу решить задачу корректного моделирования оптических эффектов в такой сцене, а потому для создания фотореалистических изображений сцен со сложными зеркальными и прозрачными объектами используются значительно более сложные алгоритмы трассировки лучей. Такие алгоритмы не удается реализовать на существующей аппаратуре в реальном масштабе времени.
ЧП Л. Эффект тумана и создание иллюзии глубины пространства
Иллюзию глубины пространства можно передать в изображении не только чисто геометрическим преобразованием (перспективным искажением размеров), но и “квазиоптическими” средствами. Живописцы давно подметили, что контуры удаленных предметов кажутся нечеткими, размытыми, а цвета не таким яркими, как предметов, расположенных близко к наблюдателю. В компьютерной графике эту иллюзию можно создать, сформировав полупрозрачную среду между наблюдателем и объектами сцены, и получить таким образом эффект тума
РасстояниеРасстояниеРис. 9.34. Моделирование оптической прозрачности средыРис. 9.34. Моделирование оптической прозрачности средына. Технически это выполняется смешиванием при формировании растрового представления объекта его цвета с цветом, плотность которого зависит от расстояния между наблюдателем и объектом.
Расстояние
Расстояние
Рис. 9.34. Моделирование оптической прозрачности среды
Рис. 9.34. Моделирование оптической прозрачности среды
Введем в рассмотрение коэффициент туманности (fog factor) f и обозначим через с расстояние между объектом и наблюдателем. Если фрагмент объекта имеет цвет С5, а полупрозрачная среда (туман) — цвет С/, то для окраски пикселя можно использовать цвет
с\ =jcs^(H)Cj.
Если коэффициент / изменяется линейно на некотором интервале, то с помощью такого нехитрого механизма можно создать оптический эффект глубины пространства сцены. Используя нелинейную функциюfz) (рис. 9.34), можно с помощью того же самого механизма создать эффект тумана. В OpenGL поддерживается линейная, экспоненциальная и гауссова формы кривой оптической прозрачности среды. Например, приведенный ниже фрагмент программы настраивает в графической системе функцию оптической прозрачно
сти среды в виде f = е"°5
GLfloat fcolor[4]={...};'
glEnable(GL_FOG);
glFogf(GL_FOG_MODE, GL_EXP);
glFogf(GL_FOG_DENSITY, 0.5);
glFogfv(GL_FOG_COLOR, fcolor);
Обращаю ваше внимание на то, что при формировании значения^) используется координата z в системе координат наблюдателя (камеры).
Использование буфера-накопителя
В некоторых графических системах, в том числе и в OpenGL, есть буфер-накопитель (accumulation buffer), с помощью которого можно на двоичном уровне реализовать дополнительную пространственную фильтрацию изображения. Этот буфер имеет такой же размер, как и буфер кадра, но большую разрядность, что позволяет накапливать в нем последовательные кадры изображения.
В OpenGL для работы с буфером-накопителем требуется предварительно очистить его (эта процедура выполняется функцией glClear(), как и очистка других буферов), а затем вызвать функцию glAccum(), которая позволяет либо добавить к содержимому буфера- накопителя данные из буфера кадра, либо поэлементно их перемножить, либо переписать со-
держимое накопителя в буфер кадра. Ниже приведен фрагмент программы, в котором созданная пользователем функция display image() используется для формирования последовательности изображений в буфере кадра.
glClear(GL_ACCUM_BUFFER_BIT) for(i=0; i< num_images; i++) {
glClear(GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT);
display_image(i);
glAccum(GL_ACCUM, 1.0/(float)num_images);
}
glAccum(GL_RETURN, 1.0);
Каждое очередное изображение накапливается в буфере-накопителе, причем при накоплении используется масштабный множитель, обратно пропорциональный количеству изображений в последовательности. После завершения цикла формирования всей серии усредненное в результате накопления изображение возвращается в буфер кадра. В принципе, ту же задачу можно было решить и с помощью смешивания цветов, но при смешивании пришлось бы снизить точность представления цвета во избежание выхода за пределы интервала представления. Использование буфера-накопителя, имеющего большую разрядность, позволяет выполнить эту процедуру, не снижая точности.
Одно из наиболее интересных применений буфера-накопителя связано со сглаживанием погрешностей дискретизации изображения. Вместо того чтобы сглаживать растровые образы отдельных примитивов, можно за счет накопления реализовать сглаживание изображения сцены целиком. Идея состоит в следующем. Если сформировать два изображения одной и той же сцены, сдвинув в одном из них все объекты (или наблюдателя) на расстояние, меньшее, чем размер пикселя, то погрешности дискретизации в этих изображениях проявятся по- разному. Усреднив их с помощью накопления, получим сглаженное изображение. Естественно, что программно проще всего изменить положение наблюдателя, для чего требуется только изменить один аргумент вызова функций glPersective() или glOrtho(). Но перед этим нужно вычислить, на сколько нужно сдвинуть камеру, чтобы приведенное к координатам экрана значение сдвига не превышало, скажем, половины размера пикселя. В терминах теории квантования (о ней подробно рассказывается в разделе 9.9) нам потребуется уменьшить шаг квантования по независимым переменным (в данном случае— пространству), а затем использовать усреднение для восстановления непрерывного сигнала на основе квантованного.
Можно объединить в программе накопление в буфере-накопителе с наложением пикселей и таким способом реализовать различные виды цифровой фильтрации изображения. Предположим, что мы имеем дискретизированное изображение. Оно может быть как синтезированным программно с помощью одного из алгоритмов тонирования, так и полученным в результате сканирования фотографии (непрерывного изображения). Дискретизированное изображение можно представить в виде матрицы отсчетов размера NxM, элементами которой являются скалярные величины:
А =[<=,]•
Если каждую из основных цветовых составляющих обрабатывать независимо от других, то можно интерпретировать элементы матрицы А либо как интенсивности отдельных составляющих, либо как интенсивность серого при воспроизведении монохромного изображения. В результате линейной фильтрации формируется новая матрица В, элементы которой вычисляются по формуле
Ь» =•
Мы говорим, что В есть результат свертки (convolving) изображения А матрицей свертки
(фильтром) Н. Как правило, на практике используются малые значения т и п, и матрицу Н
можно без особого труда представить поэлементно. Схематически процедура фильтрации по
казана на рис. 9.35. На каждый пиксель ап в изображении А “накладывается” матрица свертки, и формируется взвешенная сума пикселей, окружающих а,. Значения элементов матрицы свертки — суть веса, с которыми в этой сумме учитываются соответствующие соседние пиксели.
При /?=/л—1 усредняется обрабатываемый пиксель и восемь его ближайших “соседей”, а матрица фильтра имеет размеры 3x3. Варьируя значения элементов матрицы фильтра, можно из этих восьми элементов отбирать те, которые считаете
н = 1н = 155О 1 ОО 1 О1111О 1 оО 1 онужными. Например, если желательно учитывать в сумме только четыре из восьми соседних элементов, используйте матрицу фильтра вида
н = 1
н = 1
5
5
О 1 О
О 1 О
1
1
1
1
О 1 о
О 1 о
Такой фильтр помогает сгладить контуры изображения. Можно использовать и более сложный фильтр, в котором центральные элементы включаются в сумму с более высокими весами, чем угловые:
1116161211212 14 22 12 14 22 1
1
1
16
16
1
2
1
1
2
1
2 1
4 2
2 1
2 1
4 2
2 1
Обратите внимание на то, что вокруг исходного изображения А нужно сформировать дополнительную “каемку” шириной т пикселей по бокам и п пикселей сверху и снизу. Только в этом случае результирующее изображение В будет иметь такой же размер, как и А. Пользуясь матрицами фильтра небольшой размерности, можно получать разные интересные эффекты. Например, приведенная ниже матрица фильтра позволяет подчеркнуть границы областей, залитых разным цветом:
0-10
Н= -1 4 -1
0-10
Можно использовать и матрицы фильтров большой размерности, kxk, и накапливать к' изображений в буфере-накопителе, причем каждый раз добавлять слегка измененную версию А, варьируя коэффициент в функции glAccum().
Буфер-накопитель позволяет организовать фильтрацию не только в плоскости изображения, но и во временной области или по глубине. Если, например, в накапливаемых в буфере изображениях один из объектов слегка сдвигать (на величину, меньшую размера пикселя), а остальные оставить нетронутыми, то в суммарном изображении этот объект будет выглядеть слегка размытым. Если объект сдвигать не случайным образом, а перемещать по определенной траектории, то в результирующем изображении увидим “тянущийся” образ объекта, хорошо передающий динамику движения. Этот эффект очень напоминает фотографии быстро движущихся объектов, сделанные при большом времени экспозиции. Варьируя значение ве-
сового коэффициента при вызове функции glAccum() для добавления в буфер, можно доби
ваться самых неожиданных эффектов, выделяя изображения одних объектов и “забивая” изо
бражения других.
Фильтрация по глубине позволяет сформировать эффект ограниченной глубины резкости. При работе с фотокамерой далеко не все объекты оказываются в фокусе. В зависимости от величины относительного отверстия объектива изменяется и глубина резко воспроизводимо
го пространства — глубина резкости. В системе компьютерной графики этот эффект можно сымитировать с помощью фильтрации по глубине. Для этого следует несколько сдвинуть камеру в поперечном направлении и одновременно изменить ее ориентацию таким образом, чтобы определенная плоскость в пространстве сцены (та, которую желательно изобразить “в фокусе”) осталась на своем месте, а остальные после
повторного проецирования оказались сдви- Рис. 9.36. Имитация ограниченной глубины резкости
нутыми (рис. 9.36). Предположим, что же
лательно сфокусировать камеру на плоскость z^zj, причем ближняя (z=zmin) и дальняя (z=zmax) плоскости отсечения должны остаться неизменными. В вызове функции glFrustum() можно
задать положение отсекающих прямоугольников (xmin, xmax, ymin, ^тах) в пирамиде видимости. Если после этого сдвинуть положение камеры на Ах, то значение xmm нужно будет изменить:
•min ^nun
Аналогичным образом перед формированием второго изображения нужно скорректировать и значения хтах, >т1П и j'max. Оба изображения накладываются друг на друга в буфере- накопителе, и в результате получим изображение, в котором одни объекты будут выглядеть резкими, а другие — размытыми. Чем большее значение Лх и Ду будет использовано для сдвига камеры перед формированием второго изображения, тем меньшую глубину резкости получим в результате.
Дискретизация изображения
Мы уже неоднократно встречались на страницах этой книги с приложениями, в которых преобразование непрерывного представления изображения в дискретное приводило к появлению определенных искажений. При работе с буферами нам всегда приходится иметь дело с дикретизированным изображением, и если не принять определенных мер, то эти искажения могут значительно ухудшить качество картинки. В этом разделе мы проанализируем характеристики дискретного изображения и выясним, какие факторы оказывают наиболее существенное влияние на его качество и как можно улучшить качество.
Начнем с анализа непрерывной двухмерной функции^,у\ описывающей некоторое изображение. Значение/этой функции — суть интенсивность почернения (плотность) точки монохромного изображения с координатами (х,у^ или значение интенсивности любого из базовых цветов в цветном изображении. В компьютере мы можем обрабатывать только дискретизированное изображение, которое представляется в форме массива пикселей с размерами пхт (и строк по т пикселей в каждой). Каждый пиксель может иметь интенсивность, представленную в виде ^-разрядного двоичного числа. При переходе от непрерывного представления к дискретному сначала нужно выполнить квантование по пространству— сформировать
выборки (sample) непрерывной функции в пт точках на некоторой регулярной сетке с шагом (hx, Н0 и получить множество значений ^. Каждая выборка представляет собой значение интенсивности / измеренной в бесконечно малой окрестности точки (xQ+ihx, y^jh^ в области представления функции Дх, у). Затем значение каждой выборки /, нужно представить в виде ^-разрядного двоичного числа — квантовать по уровню.
Теория квантования по независимым переменным
Рассмотрим прямоугольную сетку на плоскости {х,у), которая служит для выполнения квантования по пространству функции Дх,у) (рис. 9.37). Предполагается, что шаг сетки постоянный во всей области существования функции, а значения выборок формируются идеальным импульсным эле.менто.м:
f.^Ax^ih^y^jh^.
Здесь hx и hv— шаг сетки квантования в направлении осей х и у. Оставим в стороне вопрос о технической реализации идеального импульсного элемента применительно к изображению (реальный элемент имеет апертуру конечного размера, т.е. обязательно усредняет по какой-то, пусть и малой, области), а сосредоточим внимание на двух вопросах. Во-первых, нас интересует, какие ошибки могут возникнуть в процессе считывания выборок, т.е. какая информация, имеющаяся в исходном изображении, может быть утеряна в процессе квантования по пространству. Во-вторых, попробуем проанализировать, насколько точно по квантованному изображению можно восстановить ис-Здесь hx и hv— шаг сетки квантования в направлении осей х и у. Оставим в стороне вопрос о технической реализации идеального импульсного элемента применительно к изображению (реальный элемент имеет апертуру конечного размера, т.е. обязательно усредняет по какой-то, пусть и малой, области), а сосредоточим внимание на двух вопросах. Во-первых, нас интересует, какие ошибки могут возникнуть в процессе считывания выборок, т.е. какая информация, имеющаяся в исходном изображении, может быть утеряна в процессе квантования по пространству. Во-вторых, попробуем проанализировать, насколько точно по квантованному изображению можно восстановить ис-Рис. 9.37. Квантование изображения по пространствуРис. 9.37. Квантование изображения по пространствуо)WWW6)Рис. 9.38. РазложениеФурье функцииодной перемен-ной: а — исход-ная функция;б — ее состав-о)WWW6)Рис. 9.38. РазложениеФурье функцииодной перемен-ной: а — исход-ная функция;б — ее состав-ляющиеляющиеходное непрерывное изображение. Восстановление изображения осуществляется на экране устройства отображения (чаще всего — ЭЛТ) в процессе переноса на него содержимого буфера кадра.
Здесь hx и hv— шаг сетки квантования в направлении осей х и у. Оставим в стороне вопрос о технической реализации идеального импульсного элемента применительно к изображению (реальный элемент имеет апертуру конечного размера, т.е. обязательно усредняет по какой-то, пусть и малой, области), а сосредоточим внимание на двух вопросах. Во-первых, нас интересует, какие ошибки могут возникнуть в процессе считывания выборок, т.е. какая информация, имеющаяся в исходном изображении, может быть утеряна в процессе квантования по пространству. Во-вторых, попробуем проанализировать, насколько точно по квантованному изображению можно восстановить ис-
Здесь hx и hv— шаг сетки квантования в направлении осей х и у. Оставим в стороне вопрос о технической реализации идеального импульсного элемента применительно к изображению (реальный элемент имеет апертуру конечного размера, т.е. обязательно усредняет по какой-то, пусть и малой, области), а сосредоточим внимание на двух вопросах. Во-первых, нас интересует, какие ошибки могут возникнуть в процессе считывания выборок, т.е. какая информация, имеющаяся в исходном изображении, может быть утеряна в процессе квантования по пространству. Во-вторых, попробуем проанализировать, насколько точно по квантованному изображению можно восстановить ис-
Рис. 9.37. Квантование изображения по пространству
Рис. 9.37. Квантование изображения по пространству
о)
WWW
6)
Рис. 9.38. РазложениеФурье функцииодной перемен-ной: а — исход-ная функция;б — ее состав-
о)
WWW
6)
Рис. 9.38. РазложениеФурье функцииодной перемен-ной: а — исход-ная функция;б — ее состав-
ляющие
ляющие
Для математического анализа процесса квантования используется аппарат преобразования Фурье. Суть его состоит в том, что любая функция (не имеет значения, задана она в пространственной или во временной области) может быть представлена в виде суммы ряда, возможно, бесконечного, элементами которого являются синусоиды с разной частотой. Эта концепция легко интерпретируется в отношении звуковых сигналов в терминах спектра такого сигнала. Когда речь идет о двухмерной функции изображения, составляющие Фурье имеют вид пространственных синусоид с кратными частотами. На рис. 9.38,а показан одномерный периодический сигнал сложной формы, а на рис. 9.38,6 — две его спектральные составляющие. На рис. 9.39 показаны две пространственные синусоиды — периодические функции двух переменных. Как следует из теории Фурье, любая двухмерная пространственная функция имеет две эквивалентные формы представления — в пространственной областных, у) и в частотной области g^ Т]). Значения функции g — это “взнос” периодической функции с частотами (£, Г)) в пространственный спектр изображения. Использование представления изображения в частотной области позволяет проанализировать многие его свойства, в том числе и те, которые сказываются
на результатах квантования по пространству.
Рис. 9.39. Периодические функции двух переменных
Постараемся пояснить суть процесса квантования, не прибегая к сложным математическим выкладкам. Теория дискретизации изображения базируется на теореме Найквиста о выборках {Nyquist sampling theorem). Эта теорема состоит из двух частей, первую из которых мы будем использовать при анализе погрешностей квантования, а вторую — при анализе восстановления непрерывного изображения по дискретным выборкам.
Теорема Найквиста о выборках (часть 1). Ансамбль идеальных выборок непрерывной функции содержит всю информацию об исходной функции тогда и только тогда, когда частота квантования как минимум вдвое превышает максимальную частоту в спектре исходной функции.
Из этой теоремы следует, что если перед нами стоит задача не потерять информацию об изображении в процессе его дискретизации, то это можно осуществить только в том случае, если в частотной области все компоненты исходного изображения, выходящие за пределы интервала шириной в частоту квантования по обе стороны от начала координат, имеют нулевую интенсивность. Наивысшая частота в спектре исходной функции, не превышающая половину частоты квантования, называется частотой Найквиста {Nyquist frequency). Функции, спектр которых не выходит за определенный интервал, называются функциями с ограниченным спектром. При работе с функциями двух переменных, подобных функциям интенсивности изображения, пространственные частоты квантования по каждой переменной на двухмерной сетке с осями х и у равны соответственно \/hx и \ihy. В формулировке теоремы предполагается ансамбль идеальных выборок бесконечной длины, причем каждая выборка соответствует значению исходной функции в бесконечно малой окрестности узла сетки. На практике этот идеал недостижим, поскольку количество выборок ограничено размером буфера, имеющегося в графической системе. Следовательно, мы не сможем точно воспроизвести идеальную функцию с ограниченным спектром.
Результатом нарушения сформулированного Найквистом критерия является лестничный эффект {aliasing) — проявление погрешности квантования в сформированном изображении. И исходная непрерывная функция, и последовательность выборок — решетчатая функция, полученная в результате квантования по независимой переменной, — могут быть представлены в частотной области. Спектр решетчатой функции — ее фурье-образ — имеет периодический характер и представляет собой сумму фурье-образов исходной непрерывной функции, смешенных по оси частот на величину, кратную частоте квантования. На рис. 9.40,а показаны
функция одной переменной и выборки на ней, а на рис. 9.40,6 — ограниченный спектр этой непрерывной функции. На рис. 9.40,в вы видите спектр решетчатой функции, который повторяет в каждом периоде спектр исходной непрерывной функции. Поскольку частота квантования при построении решетчатой функции была выбрана выше частоты Найквиста, между периодами в спектре решетчатой функции отчетливо видны участки с нулевой интенсивностью.
А теперь рассмотрим случай, представленный на рис. 9.41. В нем критерий Найквиста нарушен, а потому копии фурье-образа исходной функции в спектре решетчатой функции перекрываются. Центральная часть этого графика в увеличенном масштабе показана на рис. 9.42. На нем вы видите копию фурье-образа исходной непрерывной функции, построенную в начале координат частотной области, и первую правую от нее копию, сдвинутую на период частоты квантования ^,. Частота ^о выше частоты Найквиста ^,/2. В точке ^-£д, которая ниже частоты Найквиста, на составляющую центральной копии накладывается составляющая ^ правой копии. Если исходная функция представляет звуковые колебания, то в квантованном сигнале мы услышим усиление мощности звука на этой частоте. Точно так же, если сигнал с подобными характеристиками (но уже двухмерный) представляет изображение, то на экране будут заметны искажения формы картинки. Мы говорим, что в квантованном сигнале частота ^о имеет побочную реплику (alias) на частоте £,-^. Обращаю ваше внимание на то, что при появлении побочной реплики ее нельзя отличить от составляющей основной копии фурье- образа исходной функции, т.е. информация, которую несет эта частота в исходном сигнале, будет искажена побочной репликой.
fWfW№1№1|дД)1|дД)1Рис. 9.40. Функция с ограниченным спектром: а — функция и выборки на ней в пространственной области; б — спектр непрерывной функции;в — спектр решетчатой функцииРис. 9.40. Функция с ограниченным спектром: а — функция и выборки на ней в пространственной области; б — спектр непрерывной функции;в — спектр решетчатой функцииРис. 9.41. Перекрытие фурье-образов исходной функции в спектре решетчатой функцииРис. 9.41. Перекрытие фурье-образов исходной функции в спектре решетчатой функцииРис. 9.42. Появление побочной низкочастотной составляющей в спектре решетчатой функцииРис. 9.42. Появление побочной низкочастотной составляющей в спектре решетчатой функции
fW
fW
№1
№1
|дД)1
|дД)1
Рис. 9.40. Функция с ограниченным спектром: а — функция и выборки на ней в пространственной области; б — спектр непрерывной функции;
в — спектр решетчатой функции
Рис. 9.40. Функция с ограниченным спектром: а — функция и выборки на ней в пространственной области; б — спектр непрерывной функции;
в — спектр решетчатой функции
Рис. 9.41. Перекрытие фурье-образов исходной функции в спектре решетчатой функции
Рис. 9.41. Перекрытие фурье-образов исходной функции в спектре решетчатой функции
Рис. 9.42. Появление побочной низкочастотной составляющей в спектре решетчатой функции
Рис. 9.42. Появление побочной низкочастотной составляющей в спектре решетчатой функции
Можно продемонстрировать эффект искажения сигнала и без анализа Фурье. Рассмотрим гармоническое колебание (рис. 9.43). Если выбрать частоту квантования вдвое выше частоты синусоиды, то исходный сигнал можно будет восстановить по двум выборкам решетчатой функции. Но точно такой же вид будет иметь и решетчатая функция, полученная при квантовании гармонического колебания с вдвое большей частотой, а в общем случае — колебаний с частотами, кратными этой частоте. Все эти частоты являются побочными репликами основной частоты. Но если заранее известно, что исходная функция имеет ограниченный спектр,
не выходящий на предел частоты Найквиста, то решетчатая функция однозначно описывает
исходную непрерывную.
Рис. 9.43. Побочные составляющей гармонического колебанияРис. 9.43. Побочные составляющей гармонического колебанияЕсли провести спектральный анализ реальных изображений, то окажется, что в большинстве случаев в спектре преобладают низкие частоты, а составляющие выше частоты Найквиста весьма незначительны. Следо
Рис. 9.43. Побочные составляющей гармонического колебания
Рис. 9.43. Побочные составляющей гармонического колебания
вательно, хотя теоретически изображение ограниченного размера и не может быть описано функцией с ограниченным спектром, погрешности пространственной дискретизации, вызванные наличием побочных частот, оказыва
ются очень малыми. Исключения составляют изображения, которые имеют явно выраженный регулярный (периодический) характер. Если соответствующие частоты оказываются выше частоты Найквиста, то на восстановленном после дискретизации изображении может появиться муар. Такой эффект заметен, например, на видеоизображениях, где персонажи носят рубашки в полоску или галстуки в клетку. Минимизировать такой эффект
можно с помощью предварительной фильтрации на стадии сканирования изображения или настройки размеров области усреднения. На рис. 9.44 показаны два способа сканирования одного и того же изображения. Слева вы видите “идеальное” сканирование, когда апертура приемника минимальна. В таком случае можно считать, что формируется идеальная решетчатая функция:
Л =Ж у,у
Рис. 9.44. Сканирование изображения: а — точечная выборка;б —усреднение по областиРис. 9.44. Сканирование изображения: а — точечная выборка;б —усреднение по областиСправа сканер настроен таким образом, что выполняет усреднение (часто взвешенное) по некоторой области. В результате выборки имеют значения
Рис. 9.44. Сканирование изображения: а — точечная выборка;
б —усреднение по области
Рис. 9.44. Сканирование изображения: а — точечная выборка;
б —усреднение по области
л=/. _, z Л,., / х- -v ^х> у ^dx •
Подбирая 5, размер области усреднения, и весовую функцию и1, можно ослабить высокочастотные составляющие в изображении и таким образом частично избавиться от нежелательных эффектов. К сча-
стью, реальные сканеры всегда имеют конечную
апертуру, так что усреднение происходит в любом случае, даже если пользователь об этом и не подозревает.
Восстановление непрерывной функции по дискретным выборкам
Предположим, что в нашем распоряжении имеется решетчатая функция (множество выборок), полученная при дискретизации функции одной переменной, причем частота дискретизации выбрана таким образом, что критерий Найквиста удовлетворяется. Восстановление исходной непрерывной функции по имеющейся решетчатой основано на второй части теоремы Найквиста о выборках.
Теорема Найквиста о выборках (часть 2). Непрерывную функцию J{x} можно восстановить по соответствующей решетчатой {/}, воспользовавшись формулой
fM = У /, sinc(x-x,),
где функция sinc(x) определена следующим образом (рис. 9.45):
.. . Sin 7LY
sinc(x) = лх
Для функции двух переменных у(х, у) формула восстановления по идеальной решетчатой функции {/,} принимает вид
Дх,у) = У У 4sinc(x-x,)sinc(y-y;).
Из этих формул следует, что при отсутствии искажающих побочных частот в спектре решетчатой функции можно восстановить по ней исходную непрерывную функцию с помощью фильтра с полосой частот, ограниченной интервалом (-^/2, ^,/2), — фильтра низких частот. Восстановление функции одной переменной показано на рис. 9.46. При восстановлении функции двух переменных необходимо использовать фильтр, воспроизводящий двухмерную функцию sinc(x, у\ представленную на рис. 9.47. К сожалению, не представляется возможным воспроизвести эту функцию в реально существующих системах отображения, поскольку в некоторой области значения функции отрицательны. Рассмотрим с этой точки зрения дисплей на ЭЛТ. В нашем распоряжении имеется дискретизированное изображение— массив выборок. Подавая определенные сигналы в отклоняющую систему, можно вывести электронный луч ЭЛТ в точку экрана, соответствующую узлу сетки дискретизации. Значение выборки задает интенсивность свечения этой точки, которым можно управлять, изменяя ток электронного луча. Форма светового пятна на экране определяется системой фокусировки электронного луча. Если пытаться точно воспроизвести формулу восстановления, то распределение интенсивности в пятне должно воспроизводить вид функции sinc(x, у), но это физически невозможно, поскольку электронный луч не может иметь отрицательную интенсивность. Следовательно, использование для восстановления изображения реальных физических приборов принципиально сопряжено с внесением искажений в исходное изображение. Можно проанализировать, насколько отличается реальная функция распределения интенсивности пятна от идеальной функции sine. На рис. 9.49 показаны возможные варианты приближенного воспроизведения одномерной функции sine. Гауссова кривая распределения характерна для ЭЛТ, а прямоугольная форма — для жидкокристаллических экранов с квадратной формой пикселя. Учтите, что в определенных пределах размер пятна можно регулировать. Анализ функции распределения интенсивности пятна в частотной области показывает, что при увеличении
размеров пятна лучше воспроизводятся низкие частоты в спектре исходной функции, а при уменьшении— высокие частоты. На практике размер пятна выбирается компромиссным, причем даже невооруженным глазом видны отличия при воспроизведении одного и того же изображения на мониторах с разной настройкой.
Рис. 9.46. Восстановление функции одной переменнойРис. 9.46. Восстановление функции одной переменнойРис. 9.47. Двухмерная функция sinc(x, у)Рис. 9.47. Двухмерная функция sinc(x, у)Рис. 9.48. Электронно-лучевая трубкаРис. 9.48. Электронно-лучевая трубкаРис. 9.49. Распределение интенсивности в пятне: а — идеальное; б — прямоугольное; в — кусочнолинейное; г — гауссовоРис. 9.49. Распределение интенсивности в пятне: а — идеальное; б — прямоугольное; в — кусочнолинейное; г — гауссово
Рис. 9.46. Восстановление функции одной переменной
Рис. 9.46. Восстановление функции одной переменной
Рис. 9.47. Двухмерная функция sinc(x, у)
Рис. 9.47. Двухмерная функция sinc(x, у)
Рис. 9.48. Электронно-лучевая трубка
Рис. 9.48. Электронно-лучевая трубка
Рис. 9.49. Распределение интенсивности в пятне: а — идеальное; б — прямоугольное; в — кусочнолинейное; г — гауссово
Рис. 9.49. Распределение интенсивности в пятне: а — идеальное; б — прямоугольное; в — кусочнолинейное; г — гауссово
Квантование по уровню
Проведенный математический анализ процесса квантования функции по независимым переменным показал, что в этом процессе есть довольно много интересных моментов, сказывающихся на качестве конечного результата. Однако мы на время отложили рассмотрение
квантования выборок по уровню. Будем считать, что любая выборка может быть представлена числом, соответствующим одному из к дискретных уровней. Пусть задана скалярная величина g, значение которой лежит в некотором интервале:
^min — g — ^max-
Определим следующим образом функцию квантования по уровню q’.
если g, < g < g,+l, то q^ = q,.
Следовательно, для каждого значения g назначается один из к уровней квантования, как показано на рис. 9.50. В общем случае разработка преобразователя, реализующего квантование по уровню, включает выбор ансамбля уровней квантования {</,} и пороговых значений {g,}. Если известно распределение вероятностей p(_g) значений преобразуемой величины g, то при выборе {</,} и {g,} можно руководствоваться принципом минимизации среднеквадратичной ошибки:
e = S(8-qU')t PW)d8 •
Однако чаще всего при разработке преобразователя важнейшую роль играют соображения, которые мы обсуждали в главе 1. Простое правило, которым часто руководствуются на
Рис 9.50. Квантование по уровнюРис 9.50. Квантование по уровнюпрактике, гласит, что мы не должны в конечном результате различать изменения на уровне соседних уровней квантования, но перепад значения в два уровня квантования должен быть отчетливо виден. Выбор на основе этих соображений пороговых значений для квантования интенсивности изображения приводит к тому, что нам потребуется 7 или 8 двоичных разрядов для хранения результата квантования. В конечном счете, как правило, используется именно такая точность представления преобразованных в двоичный код значений интенсивности, что соответствует 128 или 256 уровням квантования. Следует также принимать во внимание и логарифмический характер восприятия интенсивности светового сигнала зрительной системой человека. Это приводит к тому, что уровни квантования распределяются не равномерно в заданном диапазоне значений, а по экспоненциальному закону. В результате оказывает
Рис 9.50. Квантование по уровню
Рис 9.50. Квантование по уровню
ся, что погрешность восприятия при переходе от одного уровня к следующему остается практически единой во всем диапазоне значений.
Резюме
В прежние времена существовало представление, что те, кто работает с компьютерной графикой, имеют дело исключительно с трехмерными геометрическими объектами и не “опускаются” до уровня отдельных пикселей, а работа с двухмерными картинками — исключительная прерогатива специалистов по обработке изображений (распознаванию образов, телевизионным измерениям и т.п.). Развитие аппаратных средств за последние два десятилетия привело к тому, что области компьютерной графики и обработки изображений практически стали неразличимы. Для тех, кто занимается синтезом изображений — а это подавляющая часть задач в компьютерной графике,— такое слияние областей исследования послужило мощным толчком к расширению арсенала применяемых технологий. Мысль о том, что наложение двухмерного изображения (текстуры) на трехмерную поверхность может занимать не больше времени, чем тонирование этой поверхности по известным алгоритмам, казалась еще
совсем недавно чем-то из области околонаучной фантастики. А теперь такая технология применяется повсеместно.
Технологии, подобные наложению текстур, позволяют передавать совершенно фантастические эффекты в приложениях, связанных с анимацией, созданием виртуальной реальности, визуализации результатов научных расчетов. Современная техника позволяет реализовать эти технологии на аппаратном уровне и выполнять операции в реальном масштабе времени.
В этой главе мы ограничились описанием только тех технологий, которые поддерживаются существующими в настоящее время аппаратными и программными средствами, а следовательно, доступны рядовому читателю. Но существует еще много других технологий, которые на момент подготовки книги к печати находились в стадии становления, хотя я и не могу гарантировать, что в то время, когда вы читаете эти строки, они уже не стали повседневным инструментом специалистов по компьютерной графике.
Рекомендуемая литература
Методика наложения микрорельефа впервые была описана Блинном (ВНпп) в работе [ВП77]. Он же совместно с Ньюэллом (Newell) в работе [BU76] предложил методику наложения изображения окружающих предметов. Наложение текстур впервые было использовано Кэтмуллом (Catmull) [Нес86]. Аппаратная реализация наложения текстур впервые появилась в системе SGI Reality Engine— ее описание есть в работе [Аке93]. Перлин (Perlin) и Хофферт (Hoffert) в работе [Рег89] предложили использовать случайный шум для процедурного формирования двух- и трехмерных текстур. Множество методов формирования текстур рассмотрено в работе Эберта (Ebert) [ЕЬе94]. Проблема погрешностей дискретизации в компьютерной графике подробно исследована в работе Кроу (Crow) [Сго81 /. Разным аспектам этой проблемы посвящены также работы [Mag85] и [Gla89]. Тем, кто интересуется проблемами дискретизации сигналов, я рекомендую познакомиться с книгами [Pra78], [Gon87], [Cas96]. Книга Гласснера (Glassner) [Gla95] представляет взгляд на эту проблему с точки зрения специалиста по компьютерной графике.
Множество методов комбинирования изображений, в том числе и использующих идею а- канала, рассмотрено Портером (Porter) и Даффом (Duff) в работе [Рог84]. В “Руководстве программиста OpenGL” {OpenGL Programmer's Guide) [Ope93, а] вы найдете много практических примеров программ, работающих с буферами. Наиболее свежую информацию по этому вопросу можно найти на страницах периодических изданий Computer Graphics и IEEE Computer Graphics and Applications.
Упражнения
Покажите, что режим записи XOR можно использовать для реализации алгоритма заливки многоугольников по принципу тестирования на четность-нечетность.
Какой визуальный эффект даст использование режима XOR записи в буфер при перемещении курсора по экрану?
Чем изображение, полученное в результате наложения окружающих предметов, отличается от изображения той же сцены, полученного методом трассировки лучей?
При просмотре кинофильма или телевизионной передачи часто создается впечатление, что колеса автомобилей вращаются в обратную сторону. Чем это, по-вашему, можно объяснить? Что необходимо сделать для устранения такого эффекта? Поясните свой ответ.
Можно воспроизвести на экране график некоторой функции, выведя точки, соответствующие ее выборкам, и предоставив пользователю самому мысленно объеди-
нить их в кривую. Какую потенциальную опасность таит в себе такой способ, если при дискретизации не были выполнены условия теоремы Найквиста?
Почему изображение рубашки или галстука в полоску изменяется на экране при движении актера в пространстве сцены?
Почему для устранения отрицательных эффектов дискретизации предпочтительнее использовать методику предварительной обработки данных, чем постобработки?
Предположим, что имеются две полупрозрачные поверхности с коэффициентами поглощения а, и а2. Какой коэффициент поглощения будет иметь двухслойный материал, созданный наложением одной поверхности на другую? Выведите формулу для расчета коэффициента поглощения комбинированного материала.
Предположим, что полупрозрачный материал используется в качестве светофильтра. Разработайте модель смешения цветов для такого светофильтра, используя формат представления цвета CMY.
В разделе 9.8 в качестве значения альфа-составляющей приемника и источника использовались 1-а и а соответственно. Изменим значение альфа-составляющей приемника и установим его равным 1. Как это проявится в созданном изображении?
В программу закрашивания, рассмотренную в главе 3, добавьте кисть, которая сможет изменять цвет по определенной шкале. Используйте в этой программе метод смешивания цветов для частичного затенения (затуманивания) изображения на экране.
Разработайте метод использования наложения текстур для отображения объектов, представленных в виде множества элементарных кубиков — векселей.
Покажите, как можно использовать гистограмму распределения яркости изображения (см. рис. 9.27) для формирования такой таблицы соответствия цветов, которая позволила бы выровнять форму гистограммы.
Почему при применении метода размывания границ для сглаживания изображения нужно использовать генератор случайных чисел?
Предположим, что при отображении нескольких объектов использовалось наложе- . ние текстуры в виде полосок или шахматной клетки. Как изменится вид картинки при переходе от параллельной проекции к перспективной?
Пусть имеется сцена, состоящая из объектов стандартной формы (например, параллелепипедов), но разного размера, причем в вашем распоряжении есть единственная текстура, которую требуется применить ко всем объектам. Как следует настроить процесс наложения, чтобы узор имел одинаковый размер на всех гранях объектов?
ГЛАВА
10
Кривые и криволинейные поверхности
В
том, что наш мир не является плоским, просвещенное человечество перестало сомневаться в 1492 году. Но, тем не менее, в компьютерной графике виртуальное пространство продолжает “заселяться” преимущественно плоскими объектами. И на то есть довольно веские причины. Современная графическая система способна с высокой скоростью выполнять операции закраски проекций плоских многоугольников, удаления невидимых поверхностей, представленных в форме множества плоских многоугольников, и наложения на них разного рода текстур. Поэтому, если возникает необходимость включить в состав сцены криволинейный объект, например сферу, его зачастую стремятся с самого начала приближенно представить (аппроксимировать) множеством плоских многоугольников. Альтернативный способ, который мы рассмотрим в этой главе, состоит в том, чтобы предоставить пользователю средства работы с криволинейными объектами, возложив функцию их тонирования, которое не обходится без аппроксимации, на реализацию графической системы.
В этой главе читатели познакомятся с тремя методами моделирования (формами представления) кривых пчний и криволинейных поверхностей, причем основное внимание будет уделено параметрической полиномиальной форме представления. Мы также обсудим, как можно в современной графической системе реализовать тонирование таких объектов. Этот процесс, как правило, включает разбиение криволинейных поверхностей на большое число мелких плоских примитивов. Этот процесс абсолютно прозрачен с точки зрения программиста, разрабатывающего прикладную программу, поскольку его реализация возлагается на системные средства графического пакета. Но о том, как выполняются эти операции, программист должен четко себе представлять — характер операций определяет ограничения, которые накладывает процесс реализации на неуемную фантазию пользователя.
10.L Представление кривых линий и поверхностей
В этом разделе мы кратко повторим, что нам известно о трех главных формах математического представления кривых и поверхностей — явной, неявной и параметрической, — и проанализируем достоинства и недостатки каждой из них. Для иллюстрации тех или иных
положений будем использовать простейшие геометрические объекты — прямые линии, окружности, плоскости и сферические поверхности.
Представление в явной форме
Явная форма представления кривой в двухмерном пространстве (иногда говорят “представление кривой в виде явной функции”) представляет собой уравнение, в левой части которого стоит зависимая переменная, а в правой части — функция, аргументом которой является независимая переменная. В пространстве переменных х, у уравнение линии в явной форме имеет вид
У =Лх\
Некоторые функции/имеют обратную g, которая позволяет изменить назначение зависимой и независимой переменных в уравнении, т.е. выразить х как функцию от у;
х = gkyY
Нет никакой гарантии, что для определенной линии существует явное уравнение в том или ином виде (у от х или х от у). Для прямой линии уравнение обычно записывается в терминах углового коэффициента т и ординаты h точки пересечения с осью у, хотя в такой форме невозможно описать вертикальную прямую. Это одно из множества особенностей, которыми обладают формы представления, зависимые от системы координат, и которые следует учитывать при разработке программных средств обработки объектов в графических системах. Линии (мы будем говорить о прямых или окружностях, но это касается линий любого вида) существуют независимо от формы представления, а потому невозможность представить в некоторой форме определенный вариант линии является серьезным недостатком такой формы представления.
Еще более интересную пищу для размышления предоставляет окружность. Рассмотрим окружность радиуса г, центр которой находится в начале координат. Окружность имеет постоянную кривизну— меру изгиба линии в точке. Более симметричной кривой в двухмерном пространстве, чем окружность, не существует. Но, пользуясь явной формой уравнения окружности, единственное, что можно сделать, — описать половину окружности, находящуюся в верхней полуплоскости, одним уравнением:
y = -Jr -х1 ,
а вторую половину, находящуюся в нижней полуплоскости, другим:
у = —Jr — х' .
В трехмерном пространстве линия описывается системой из двух уравнений в явной форме. Если, например, переменная х снова выбрана в качестве независимой, то имеем два уравнения для зависимых переменных:
У
Для описания поверхности потребуется использовать две независимые переменные, и уравнение поверхности в явном виде будет выглядеть так:
- ^K^yY
Как и в двухмерном пространстве, в трехмерном пространстве также не все кривые и поверхности могут быть описаны уравнениями в явной форме. Например, система уравнений
у = ах + Ь,
с = сх + d
описывает прямую в трехмерном пространстве, но таким способом нельзя описать прямую, лежащую в вертикальной плоскости x=const. Точно так же сферу нельзя представить уравнением вида z-Дх, у), поскольку заданным значениям х и у соответствуют две точки на сфере.
Неявная форма представления
Большинство кривых и поверхностей, с которыми приходится работать на практике, можно описать с помощью уравнений в неявной форме. В двухмерном пространстве неявная форма уравнения линии имеет вид
Дх, у)=0.
Прямая и окружность с центром в начале координат описываются соответственно уравнениями
ах + by + с = О,
2.22 Лх + у - г = 0.
Функция /, по сути, выделяет из всех точек пространства те, которые принадлежат описываемой линии. Значение этой функции позволяет проверить для каждой пары значений координат х, у, лежит ли описываемая ими точка на данной линии. В общем случае, однако, неявная функция не позволяет определить значение координаты у точки на кривой для заданного значения х, или наоборот. Неявная форма представления является менее зависимой от системы координат, поскольку позволяет представлять прямые или окружности во всех вариантах.
В трехмерном пространстве уравнение в неявной форме вида
Дх,у, 2)=0.
описывает поверхность. Например, плоскость описывается уравнением
ах + by - с- + d = 0,
где а, Ь, с и d — константы. Сфера радиуса г с центром в начале координат описывается уравнением
Z+ у"+ z2- г2= 0.
Описать линию в трехмерном пространстве не так просто. Она может быть представлена только системой уравнений, описывающих поверхности, пересечение которых и образует эту линию, если таковые существуют:
Дх,у\2) = 0, g(x,y, -) = 0.
Следовательно, задавшись точкой с координатами (х, у, z), нужно проверить, лежит ли она на обеих поверхностях, и, если лежит, считать ее точкой, принадлежащей формируемой линии.
Алгебраической считается поверхность, для которой функция Дх, у, z) есть сумма полиномов трех переменных. Частный случай алгебраической поверхности — квадратичные поверхности, в функции / представления которых отсутствуют степени переменных1 выше 2. Квадратичные поверхности представляют для нас особый интерес не только потому, что к ним относятся такие распространенные поверхности, как сфера, цилиндр и конус, но и потому, что такая поверхность пересекается прямой не более чем в двух точках. Это свойство квадратичных поверхностей будет использовано при разработке методов их тонирования в разделе 10.9.
'Степень полинома определяется как .максимальная сумма степеней переменных в отдельном члене. Так, в полиноме второй степени могут присутствовать члены ху или z2, а член ху2 не может,
Параметрическая форма представления
В параметрической форме значение каждой координаты точки, принадлежащей кривой, представляется функцией независимой переменной и, которая называется параметром этой кривой. В трехмерном пространстве кривая описывается системой из трех параметрических уравнений:
X = х^и), У=Л.и), - = Ли).
Puc. 10.1. Кривая, заданная в параметрической формеPuc. 10.1. Кривая, заданная в параметрической формеОдно из главных достоинств параметрической формы представления — ее единообразие в двух- и трехмерном пространствах. В первом случае нужно просто отбросить третье уравнение для координаты z. Параметрическую форму представления можно интерпретировать как способ представления на экране образа кривой p(u)=[x(z/) Xм) -(М)]Г, изменяя значения параметра и, как показано на рис. 10.1. Производную от параметрически заданной векторной функции
Puc. 10.1. Кривая, заданная в параметрической форме
Puc. 10.1. Кривая, заданная в параметрической форме
dr(«) du.
dp(u) _ dy(u) du du dz(u) du•
можно рассматривать как вектор скорости обхода кривой, который в каждой точке этой кривой направлен по касательной к ней.
Для описания поверхности в параметрической форме требуется использовать два параметра. Система уравнений поверхности имеет вид
X = х^и, v),
У = Ли, V),■
Z = z^u, v).
Эти же уравнения можно записать в виде матрицы-столбца p(w, v)=[x(m, v) y^u, v) Ли. v)]r. Изменяя значения параметров и и v в некотором интервале, можно сформировать значения координат р(м, v) всех точек поверхности. При анализе примера сферической поверхности в главе 6 мы уже отмечали, что матрицы-столбцы dp/dw и dp/dv описывают плоскости, касательные к поверхности в каждой ее точке.
Параметрическая форма описания кривых и поверхностей является, во-первых, наиболее гибкой, а во-вторых, устойчивой к любым вариациям формы и ориентации объектов, что делает ее особенно удобной для использования в математическом обеспечении систем компьютерной графики. Возможно, у кого-либо из читателей появится возражение, что и в этой форме мы привязаны к системе координат х, у, z, но это не так. Можно модифицировать параметрические уравнения и использовать для описания кривой в трехмерном пространстве только касательные и нормали, которые образуют так называемый сопрово.ждаюи!ий трехгранник (трехгранник Френе), имеющий отличную ориентацию в каждой точке кривой. Что касается задач компьютерной графики, то использование параметрической формы представления координат х, у, z вполне удовлетворяет все их потребности.
Параметрические полиномиальные кривые
Параметрическая форма представления определенной кривой или поверхности не является уникальной. Объект можно представить множеством способов, но мы сосредоточим внимание на полиномиальной форме, т.е. все функции параметра и при описании кривых или параметров г/ и г при описании поверхностей являются полиномами. Эта форма чаше всего используется в компьютерной графике, а ее достоинства будут проанализированы в разделе 10.2.
Рассмотрим уравнения кривой в виде"
^(и)
Р(и) =Р(и) =у(") 4»)у(") 4»)
Р(и) =
Р(и) =
у(") 4»)
у(") 4»)
Полиномиальная параметрическая кривая степени и имеет вид
P(«) = 5«<ci ,
где с* имеет независимые компоненты х, у, z, т.е.
Рис. 10.2. Сегмент кривойРис. 10.2. Сегмент кривойP(t/.v)P(t/.v)
Рис. 10.2. Сегмент кривой
Рис. 10.2. Сегмент кривой
P(t/.v)
P(t/.v)
Матрица {с*}, состоящая из м+1 столбцов, объединяет коэффициенты полиномов для компонентов р; это означает, что у нас есть 3(м+1) степеней свободы в выборе коэффициентов для конкретной кривой р. Уравнения для трех компонентов х, у, z независимы и каждое из них имеет вид
pW=^uct ,
а следовательно, обладает м+1 степенью свободы. Кривую можно определить на любом интервале изменения параметра и
^min — W — Wmax,
но, не теряя общности рассуждений, можно положить, что 0 < и < 1 (см. упр. 10.3). По мере того как параметр и будет “пробегать” значения в этом интервале, отображающая точка будет перемещаться по сегменту кривой (рис. 10.2).
Параметрические полиномиальные поверхности
Параметрическая полиномиальная поверхность описывается уравнением вида
x(u,v) v(«.v) zG'.v)
Рис. 10.3. Участок поверхностиРис. 10.3. Участок поверхноститем применять их для формирования поверхности с аналогичными характеристиками.тем применять их для формирования поверхности с аналогичными характеристиками.Таким образом, для определения конкретной поверхности р(г/, v) необходимо задать 3(и+1 )(т+1) коэффициентов. Можно при анализе положить п=т, а параметры и и г изменять на интервале 0 < и, г < 1 и таким образом определить порцию поверхности (surface patch), как показано на рис. 10.3. Обращаю ваше внимание на то, что определенный таким способом участок поверхности можно рассматривать как предел, к которому стремится множество кривых, которые формируются, когда один из параметров и или v пробегает значения в своем интервале, в то время как другой сохраняет постоянное значение. В дальнейшем при обсуждении поверхностей будет использована следующая стратегия: мы будем определять полиномиальные кривые, а за
Рис. 10.3. Участок поверхности
Рис. 10.3. Участок поверхности
тем применять их для формирования поверхности с аналогичными характеристиками.
тем применять их для формирования поверхности с аналогичными характеристиками.
Общая характеристика полиномиальной параметрической формы представления
В компьютерной графике и прикладных системах автоматизации проектирования, которые строятся на базе систем компьютерной графики, кривые и поверхности используются довольно специфическим образом, в корне отличном от других применений. Существует множество доводов в пользу использования в этой области именно параметрической формы представления криволинейных объектов. Среди них упомянем основные:
возможность локального контроля формы объекта;
гладкость и непрерывность в математическом смысле;
возможность аналитического вычисления производных;
устойчивость к малым возмущениям;
возможность использовать относительно простые, а значит, высокоскоростные методы тонирования.
Понять смысл сказанного можно на простом примере. Предположим, требуется построить деревянную модель планера летательного аппарата. Каркас модели (авиаконструкторы говорят “набор”) можно создать следующим образом: сначала сделать из относительно коротких реек несколько поперечных сечений, а затем связать их стрингерами из длинных гибких и упругих реек (рис. 10.4). Чертежи поперечных сечений строятся либо по фотографиям реального аппарата, либо по эскизным кривым — наброскам, сделанным конструктором. Его типовое поперечное сечение (специалисты называют соответствующий конструктивный элемент набора шпангоутом) показано на рис. 10.5. Можно попробовать построить обобщенное уравнение кривой шпангоута, но, скорее всего, оно окажется слишком сложным и вряд ли нас устроит. При изготовлении шпангоута деревянная рейка может быть изогнута только до определенных пределов (если гнуть дальше, она просто сломается), причем изогнутая рейка имеет гладкую форму. Таким образом, кривая на рис. 10.5 представляет собой приближенный эскиз формы шпангоута, который реально можно изготовить из имеющихся у нас материалов. Скорее всего, реальная форма будет такой, как на рис. 10.6. На практике шпангоут придется изготовить из нескольких реек, каждая из которых становится, таким образом, отдельным сегментом кривой. Следовательно, гладким должен быть не только каждый сегмент по отдельности, но и в точках сопряжения (join points) сегментов должна быть обеспечена определенная степень гладкости кривой. Учтите, что именно гладкости в точках сопряжения
приходится уделять больше всего внимания при выборе способа аппроксимации. На рис. 10.7 показана непрерывная линия, первая производная которой не является гладкой функцией, — претерпевает разрыв в точке сопряжения. Общепринятое определение гладкости кривой связано с характеристиками функций первой производной по параметру и производных более высоких порядков. Для нас представляют интерес гладкие кривые, т.е. такие, функция первой производной которых непрерывна на всем интервале существования параметра. В некоторых случаях гладкой должна быть не только функция первой производной, но и производных более высокого порядка. Эти определения станут более понятны для вас после изучения материала раздела 10.3. Сейчас же отмечу, что для полиномиальной кривой
P<u) = ^ukck k=0
все производные до и-го порядка включительно являются гладкими и могут быть определены аналитически. Следовательно, единственное место, где могут возникать разрывы при аппроксимации произвольной гладкой кривой сегментами полиномиальных кривых — это точки сопряжения.
Рис. 10.5.Кривая поперечного сечения моделиРис. 10.5.Кривая поперечного сечения моделиРис. 10.4. Модель планераРис. 10.4. Модель планераАппроксимирующаяАппроксимирующаяРис. 10.7. Разрыв первой производной в точке сопряжения сегментовРис. 10.7. Разрыв первой производной в точке сопряжения сегментовЖелаемая кривая
Рис. 10.5.
Кривая поперечного сечения модели
Рис. 10.5.
Кривая поперечного сечения модели
Рис. 10.4. Модель планера
Рис. 10.4. Модель планера
Аппроксимирующая
Аппроксимирующая
Рис. 10.7. Разрыв первой производной в точке сопряжения сегментов
Рис. 10.7. Разрыв первой производной в точке сопряжения сегментов
Рис. 10.6. Приближенное представление кривой поперечного сечения
Процесс формировании кривой желательно организовать таким образом, чтобы каждый сегмент строился индивидуально, а не строить все сегменты единой глобальной вычисли
тельной процедурой. Причина, по которой такая организация предпочтительнее, заключается в том, что в таком режиме пользователю гораздо удобнее подбирать форму кривой на отдельных участках, следуя специфическим требованиям приложения. Внесение небольших из
менений в спецификацию кривой приводит только к локальным изменениям ее формы на том участке, с которым в текущий момент работает пользователь, и мало влияет на остальные участки. Такой способ реализует устойчивую стратегию конструирования, которая неформально может быть охарактеризована сле
дующим образом: малые вариации исходных параметров на входеР|
должны приводить к малым вариациям результата на выходе. Ро *'''''^Z* Рз
Работая с деревянными рейками, желательно иметь возмож- ° р*и^ ер
ность гнуть их таким образом, чтобы как можно точнее воспроиз
вести желаемую форму шпангоута. Более того, желательно свести Рис. 10.8. Сегмент кривой всю процедуру к выбору небольшого ансамбля опорных или кон-и контрольные точ-
трольных точек {control points'), которые будут полностью харак-км
теризовать форму сегмента кривой. На рис. 10.8 показан сегмент
кривой и ансамбль описывающих его контрольных точек. Обратите внимание на то, что через некоторые контрольные точки сегмент проходит, а некоторые располагаются вблизи действительной кривой. Как будет показано ниже, во многих задачах компьютерной графики и ав
томатизации проектирования нас вполне устраивает, если кривая проходит в малой окрестности заданных контрольных точек и при этом обеспечивается ее гладкость.
Общая характеристика полиномиальной параметрической формы... 425
Этот пример наглядно демонстрирует, почему в задачах компьютерной графики предпочтение отдается классу полиномиальных кривых. Даже наименование класса составных полиномиальных кривых— сплайн-кривые, или сплайны— произошло от английского наименования гибкой деревянной рейки или упругой стальной ленты, с помощью которой в кораблестроении вычерчивались гладкие контуры. Такая лента вставлялась между штифтами, соответствующими положению контрольных точек на кривой, и за счет упругости образовывала гладкую линию контура. Оказалось, что эта линия математически описывается сегментами полиномиальных кривых степени не выше третьей. За такими составными полиномиальными кривыми и закрепилось название сплайнов. Сплайны мы подробно рассмотрим в разделах 10.7 и 10.8.
Возвращаясь к задачам компьютерной графики, напомню, что помимо собственно математического описания кривых, нам понадобится разработать и методы их отображения. Математический аппарат, даже очень изящный, останется “вещью в себе”, если мы не сможем им воспользоваться для представления на экране кривых и поверхностей. Нам необходимы такие способы отображения криволинейных объектов, которые не превосходили бы по сложности аналогичные методы, которыми мы пользуемся для отображения прямолинейных и плоских объектов, и обладали теми же функциональными возможностями — раскрашивать и тонировать объекты, накладывать на них текстуры разного вида.
Параметрически заданные кубические кривые
Остановившись на классе полиномиальных кривых в параметрической форме, мы должны выбрать степень полинома для описания кривой. Если воспользоваться полиномом очень высокой степени, то при конструировании кривой определенной формы у нас будут в значительной степени “развязаны руки”, поскольку в нашем распоряжении будет больше коэффициентов, но процесс расчета координат точек на кривой потребует большего количества вычислений. Кроме того, при работе с полиномами высоких степеней возрастает опасность получить кривую волнистой формы. С другой стороны, выбор полинома слишком низкой степени может привести к тому, что в нашем распоряжении окажется слишком мало регулируемых параметров — коэффициентов полинома — и, следовательно, не удастся воспроизвести с требуемой точностью форму кривой. Выход можно найти в том, чтобы не описывать единым полиномом высокой степени всю кривую, а разбить ее на сегменты небольшой длины, которые можно описывать полиномами низкой степени. Хотя такой полином и обладает малым, количеством степеней свободы, их может оказаться вполне достаточно, чтобы воспроизвести форму кривой на отрезке небольшой длины. Основываясь на этих достаточно общих рассуждениях, многие конструкторы, не мудрствуя лукаво, приступая к формированию кривых, начинают с кубического полинома— начинать с квадратичной формы, если на то заранее нет веских оснований, все-таки несерьезно, а сразу браться за полином четвертой степени боязно, а вдруг третьей степени вполне хватит.
Описать кубическую полиномиальную кривую можно следующим образом, воспользовавшись матрицами-строками и матрицами-столбцами:
р(н) = с0 +с,и + с2и2 + с3м3 = £сУ = urc,
*=0
В этих выражениях с представляет матрицу коэффициентов полинома. Именно ее нам и потребуется вычислить по заданному ансамблю опорных точек. Ниже мы рассмотрим разные классы кубических кривых, которые отличаются характером сопоставления с опорными точками. Для каждого типа будет сформирована система из 12 уравнений с 12-ю неизвестными, но, поскольку параметрические функции для компонентов х, у и z независимы, эти 12 уравнений будут разделены на три группы по 4 уравнения с 4-мя неизвестными.
Вычисление значений коэффициентов определенного типа кубической кривой выполняется по заданному ансамблю опорных точек, соответствующих некоторым значениям независимого параметра и. Эти данные могут иметь форму ограничений, требующих, чтобы формируемая полиномиальная кривая проходила через некоторые из заданных точек и в окрестности других точек. Кроме того, эти данные накладывают и определенные условия на гладкость кривой, например, первая производная и производные более высоких порядков должны быть непрерывными в заданных точках сопряжения отдельных сегментов. Та или иная формулировка этих условий определяет класс формируемой кривой, причем кривые разного класса, сформированные на одном и том же ансамбле опорных точек, могут существенно отличаться.
Интерполяция
Первым мы рассмотрим класс кубических интерполяционных полиномов (interpolating polynomial). Хотя этот класс кривых очень редко используется в задачах компьютерной графики, их анализ поможет вам составить четкое представление о характеристиках полиномиальных кривых общего вида и о тех операциях, которые необходимо выполнять при формировании кривых других классов.
Пусть имеется четыре опорные точки в трехмерном пространстве: р0, Рь р2 и р3. Каждая точка представлена тройкой своих координат:
Наша задача — отыскать элементы матрицы коэффициентов с, такие, что полином р(м)=и7с будет проходить через заданные четыре опорные точки. Вывод соответствующих соотношений особого труда не представляет. Мы располагаем четырьмя трехмерными точками, через которые должна проходить формируемая кривая. Следовательно, можно составить 12 уравнений с 12 неизвестными— элементами матрицы с. Но сначала нужно решить, какому значению независимого параметра и соответствует каждая опорная точка. Не располагая никакой другой информацией, кроме координат точек и порядка их размещения вдоль формируемой кривой, будем считать, что значения ик (^=0,..,3) распределены равномерно на интервале [0, 1], т.е. w=0, 1/3, 2/3, 1 (напоминаю, мы договорились, что параметр и изменяется только на интервале [0, 1]). Соответствующие четыре уравнения в векторной форме имеют вид
ро=р(О) = со,
р3=р(1) = с0+с|+с2+с3.
Перепишем эти уравнения в матричной форме следующим образом: Р = Ас, где
Ро’
Р=Р1
р2
Рэ.
и
Проанализируем матрицу А. Если интерпретировать рис как матрицы-столбцы из 12 элементов, то правило умножения матриц соблюдено не будет. Но мы можем и по-другому интерпретировать матрицы рис — рассматривать их как матрицы-столбцы из четырех элементов, каждый из которых, в свою очередь, является матрицей-строкой. Тогда умножение элемента матрицы А, скаляра, на элемент матрицы с, трехмерную матрицу-строку, даст в результате трехмерную матрицу-строку, т.е. элемент того же вида, что и элементы матрицы-столбца р. Можно показать, что матрица А не является вырожденной и, следовательно, ее можно обратить и получить базисную интерполяционную матрицу {interpolating geometry matrix):
" 1
0
0
0
М;=А‘‘ =
-5.5
9
-4.5
1
9
-22.5
18
-4.5
-4.5
13.5
-13.5
4.5
Располагая значениями элементов М/, можно вычислить искомые значения коэффициентов:
с = М/р.
Теперь предположим, что кривая задана не четырьмя, а т опорными точками р0, ph.., рт. Такую кривую можно представить интерполяционным полиномом (w-l)-ro порядка и, используя описанную выше методику, вычислить Зхт коэффициентов су (/=0,..,m-l; j=x, у. z).
Р2Р2Рис. 10.9. Сопряжение интерполяционных сегментовРис. 10.9. Сопряжение интерполяционных сегментовНо можно поступить и по-другому — считать эту кривую состоящей из нескольких сегментов, каждый из которых является кубической кривой и задается очередной группой из четырех опорных точек. Непрерывность кривой можно обеспечить тем, что считать последнюю опорную точку предшествующей группы (последнюю точку сегмента) первой опорной
Р2
Р2
Рис. 10.9. Сопряжение интерполяционных сегментов
Рис. 10.9. Сопряжение интерполяционных сегментов
точкой следующей группы (первой точкой очередного сегмента) (рис. 10.9). Следовательно, для формирования первого сегмента будут использованы опорные точки р0. р,, р2, Рз, Для формирования второго — точки р3, р4, р5, р6 и т.д. Обращаю ваше внимание на то, что если
на каждом сегменте полагается, что параметр и изменяется в интервале [0, 1], то матрицы М/ для всех сегментов одинаковы. Хотя таким способом мы и получим непрерывную кривую, функции производных по параметру будут претерпевать разрывы в точках сопряжения.
Функции смешивания
Можно иначе подойти к анализу гладкости интерполяционных полиномиальных кривых. Перепишем выведенные в предыдущем разделе соотношения в слегка измененном виде. Подставив коэффициенты интерполяции в полином, получим
р(//)=и7с=и7М/р.
Это соотношение можно записать в виде
p(w) = b(w) zp,
где
b(w) = М/u
есть матрица-столбец из четырех полиномиальных функций смешивания {blending polynomials^;
b(«) =b(«) =b0{u) bt(u) Ьг(и) b^
b(«) =
b(«) =
В каждой функции смешивания полином является кубическим. Выразив p(z/) как сумму полиномов смешивания, получим
р(м) = 60(«)р0+61(и)р| +b2(u)p2*b3(u)p3 = ^Ь,{и)р, .
Из этого соотношения следует, что полиномиальные функции смешивания характеризуют вклад, который “вносит” каждая опорная точка, и таким образом позволяют оценить, насколько скажется на виде конечной кривой изменение положения той или иной опорной точки. Графики функций смешивания для кубического интерполяционного полинома показаны на рис. 10.10, а соответствующие аналитические выражения приведены ниже:
Поскольку все нули функций смешивания лежат в интервале [0, 1], то их значения могут существенно изменяться на этом интервале, а сами функции не являются монотонными. Эти характеристики функций смешивания следуют из того факта, что интерполяционная кривая долж-
на точно проходить через опорные точки, а не в ближайшей их окрестности. Еще более характерны такие особенности для интерполяционных полиномиальных кривых порядков выше четвертого. Плохая гладкость кривой, а также отсутствие непрерывности производных в точках сопряжения сегментов и объясняют, почему интерполяционные полиномиальные кривые редко используются в компьютерной графике. Но пользуясь той же методикой анализа, можно отыскать и тип представления кубической кривой, более подходящий для наших задач.
Рис. 10.10. Полиномиальные функции смешивания для случая кубической интерполяции
Порция кубической интерполяционной поверхности
Все, что было сказано о сегментах интерполяционных кривых, можно достаточно просто распространить и на трехмерный случай порции интерполяционной поверхности. Бикубическое уравнение порции поверхности можно записать в следующем виде:
p(M = ££«Vc, •
<•0 j-0
Здесь с,/ — трехкомпонентная матрица-столбец, элементами которой являются коэффициенты при одинаковых степенях независимой переменной в уравнениях для х, у и z компонент. Определим 4х4-матрицу таким образом, что ее элементами будут трехкомпонентные матрицы-столбцы:■
C-[c,J.
Тогда описать порцию поверхности можно следующим образом:
p(w, v) = u7Cv, где
Конкретная порция бикубической поверхности определяется 48 значениями элементов матрицы С — 16-ю трехмерными векторами.
Допустим, что имеется 16 трехмерных опорных точек р(/, i=0,..,3J=0,.-3- Эти точки можно использовать для определения порции интерполяционной поверхности (рис. 10.11). Будем считать, что эти данные используются для интерполирования с равным шагом по обоим
независимым параметрам и и v, которые принимают значения 0, 1/3, 2/3 и 1. Отсюда получим три набора из 16 уравнений с 16 неизвестными в каждом. Например, при w=v=0 получим три независимых уравнения:независимым параметрам и и v, которые принимают значения 0, 1/3, 2/3 и 1. Отсюда получим три набора из 16 уравнений с 16 неизвестными в каждом. Например, при w=v=0 получим три независимых уравнения:11Роо = [1 О О 0]СРоо = [1 О О 0]СООМы не будем выписывать и решать эти уравнения, а поступим проще. Если зафиксировать v=0, то, измеМы не будем выписывать и решать эти уравнения, а поступим проще. Если зафиксировать v=0, то, измеРис. 10.11. Порция интерполяционной поверхностиРис. 10.11. Порция интерполяционной поверхностиняя и, получим кривую, проходящую через роо, р|у, Р20, р30. Используя результаты, полученные в предыдущем разделе, можем записать для этой кривой следующееняя и, получим кривую, проходящую через роо, р|у, Р20, р30. Используя результаты, полученные в предыдущем разделе, можем записать для этой кривой следующеесоотношение:соотношение:РооРооp(«,0) = urM,p(«,0) = urM,РюР:оРюР:оurCurCООРзо
независимым параметрам и и v, которые принимают значения 0, 1/3, 2/3 и 1. Отсюда получим три набора из 16 уравнений с 16 неизвестными в каждом. Например, при w=v=0 получим три независимых уравнения:
независимым параметрам и и v, которые принимают значения 0, 1/3, 2/3 и 1. Отсюда получим три набора из 16 уравнений с 16 неизвестными в каждом. Например, при w=v=0 получим три независимых уравнения:
1
1
Роо = [1 О О 0]С
Роо = [1 О О 0]С
О
О
Мы не будем выписывать и решать эти уравнения, а поступим проще. Если зафиксировать v=0, то, изме
Мы не будем выписывать и решать эти уравнения, а поступим проще. Если зафиксировать v=0, то, изме
Рис. 10.11. Порция интерполяционной поверхности
Рис. 10.11. Порция интерполяционной поверхности
няя и, получим кривую, проходящую через роо, р|у, Р20, р30. Используя результаты, полученные в предыдущем разделе, можем записать для этой кривой следующее
няя и, получим кривую, проходящую через роо, р|у, Р20, р30. Используя результаты, полученные в предыдущем разделе, можем записать для этой кривой следующее
соотношение:
соотношение:
Роо
Роо
p(«,0) = urM,
p(«,0) = urM,
Рю
Р:о
Рю
Р:о
urC
urC
О
О
При значениях v=l/3, 2/3, 1 можно определить три другие интерполяционные кривые, каждую из которых можно описать тем же способом. Объединив уравнения для всех кривых, получим интересующую нас систему из 16 уравнений:
u7M/P - urCAr,
где А — матрица, обратная М/. Решением этого уравнения будет искомая матрица коэффициентов: С = М/РМ/.
Подставляя ее в уравнение поверхности, окончательно получим
p(w, v) = u7M/PM/v.
Этот результат можно интерпретировать по-разному. Из него следует, во-первых, что результаты, полученные при анализе интерполяционных кривых, можно распространить на соответствующие интерполяционные поверхности. Во-вторых, мы можем распространить методику использования полиномиальных функций смешивания на поверхности. Зная, что М/м описывает интерполяционную функцию смешивания, можно записать уравнение для порции поверхности в таком виде:
p(“’v>=ЕЕ wwpv •
Каждый член b,(u)bj(v) описывает в этом выражении порцию смешивания (blending patch). Поверхность формируется смешиванием 16 простых порций, для каждой из которых в качестве весового коэффициента выступают координаты определенной опорной точки. Основные свойства порций смешивания определяются теми же полиномами смешивания, которые мы анализировали в предыдущем разделе, и, следовательно, большинство характеристик поверхности аналогично характеристикам соответствующих кривых. В частности, порции смешивания немонотонны и не’отличаются гладкостью, поскольку нули функций b,(u)b^v) лежат в пределах еди-
ничного квадрата в пространстве {zz. v}. Поверхности, образованные кривыми в соотвегствии с этим методом, получили название поверхностей тензорного произведения (tensor-product surfaces). Порции бикубических поверхностей тензорного произведения образуют подмножество порций всех поверхностей, которые содержат члены обоих независимых параметров в степени, не превышающей трех. Они входят в группу так называемых разделяемых поверхностей (separable surfaces), поскольку позволяют работать с параметрами и и г независимо.
Эрмитова форма представления кривых и поверхностей
Методы, которые были рассмотрены применительно к интерполяционным кривым и поверхностям, мы используем в этом разделе для анализа кривых и поверхностей других типов. Как уже отмечалось выше, главное отличие между кривыми и поверхностями разных типов состоит в том, как соотносятся опорные точки и формируемая кривая или поверхность.
Форма Эрмита
В этом разделе будет рассмотрена форма представления кривых, предложенная Эрмитом (Hermite). Предположим, что анализ сегмента кривой мы начинаем, имея в своем распоряжении только точки7 ро и р3, и что по-прежнему сегменту соответствует интервал изменения параметра [0, 1], т.е. имеющиеся точки соответствуют значениям параметра z/=0 и i/= 1. Используя те же обозначения, что и раньше, можно записать два условия:
Р(0) = Ро = с0,
р(1) = Рз = Со+с|+с2+с3.
р'(и) =р'(и) =dx du dy du dz dudx du dy du dz du= ct + 2ис, +Зи2с3 .= ct + 2ис, +Зи2с3 .Рис. 10.12. К определению формы Эрмита для кубической кривойРис. 10.12. К определению формы Эрмита для кубической кривойДва других условия получим, зная значения производных функции в крайних точках сегмента и=0 и и=1. Производная кубического полинома есть квадратный полином
р'(и) =
р'(и) =
dx du dy du dz du
dx du dy du dz du
= ct + 2ис, +Зи2с3 .
= ct + 2ис, +Зи2с3 .
Рис. 10.12. К определению формы Эрмита для кубической кривой
Рис. 10.12. К определению формы Эрмита для кубической кривой
Обозначим известные значения производных в конечных точках р'о и р'3. Тогда два дополнительных условия примут вид (рис. 10.12)
р'о = р'(О) = с|, р'з = р'(1) = с1+2с2+3сз.
Эти же уравнения можно записать и в матричной форме:
Ро Рз Ро РзРо Рз Ро Рз1 1 о о1 1 о оО О1 11 О1 2О О1 11 О1 2ОО33
Ро Рз Ро Рз
Ро Рз Ро Рз
1 1 о о
1 1 о о
О О
1 1
1 О
1 2
О О
1 1
1 О
1 2
О
О
3
3
Обозначив через q матрицу имеющихся в нашем распоряжении данных
Ро Рз Ро Рз
можно записать решение уравнения в виде
c = M//q,
где М# называется базисной матрицей Эрмита {Hermite geometry matrix):
М =М =1О-321О-32ООО
М =
М =
1О-3
2
1О-3
2
О1о
3-2-1
-211
В результате получим представление полиномиальной кривой в форме Эрмита: р(и) = urMwq.
Мы будем использовать форму Эрмита для представления сегментов составной кривой, как показано на рис. 10.13. Точка сопряжения является общей для обоих сегментов, и, кроме того, производные к кривой в точке сопряжения для обоих сегментов также равны. В результа
те получаем составную кривую, непрерывную по первой производной на всем протяжении.
Рис. 10.13. Применение формы представления по Эрмиту к стыковке сегментовРис. 10.13. Применение формы представления по Эрмиту к стыковке сегментовВозможность получения более гладких кривых при использовании формы представления по Эрмиту можно обосновать математически следующим образом. Запишем полином в виде р(и) = b(w)rq,
Рис. 10.13. Применение формы представления по Эрмиту к стыковке сегментов
Рис. 10.13. Применение формы представления по Эрмиту к стыковке сегментов
где новые функции смешивания имеют вид
2и3-Зи2+1
b(u) = М^и =b(u) = М^и =-2и3+Зи2
b(u) = М^и =
b(u) = М^и =
и3 -2и2 -V и
Нули этих четырех полиномов расположены вне интервала [0, 1], а потому функции смешивания являются гораздо более гладкими, чем для интерполяционных полиномов (см. упр. 10.16).
Воспользовавшись представленными выше функциями смешивания, можно следующим образом определить порцию поверхности в форме Эрмита:
p{u,v) = '5L'5Lb,{u)bJ{v)qij, i-0 7=0
где Q=[q//] — набор данных, представляющих порцию поверхности аналогично тому, как q представляет сегмент кривой. Но пока что эти уравнения являются для нас чисто формальными выражениями. Совершенно неясно, как связаны между собой элементы Q и производные функции р(и, v). Четыре элемента Q представляют собой значения функции p(u, v) в угловых точках порции поверхности, а четыре других должны представлять производные к поверхности в этих угловых точках. В интерактивных приложениях пользователю жёлательно специфицировать не данные о производных, а координаты точек, и, следовательно, не сформулировав аналитические выражения для этих данных, мы не сможем получить производные.
Геометрическая и параметрическая непрерывность
Перед тем как мы перейдем к обсуждению форм представления Безье и сплайнов, кратко рассмотрим проблему непрерывности представления и его производных в общем виде. На рис. 10.14 показаны два последовательных сегмента составной параметрической кривой. Обозначим полином левого сегмента р(м), а полином правого — q(w). Сформулируем разные условия непрерывности, сопоставляя значения полиномов и их производных в точке сопряжения м=1 для р(и) и и=0 для q(w). Если желательно, чтобы составная кривая была непрерывной, необходимо в точке сопряжения обеспечить выполнение условия
P(D =P(D =P.O)Р/1)P.O)Р/1)= q(0) == q(0) ="^(0)"<7,(0)_^: (°)."^(0)"<7,(0)_^: (°).
P(D =
P(D =
P.O)
Р/1)
P.O)
Р/1)
= q(0) =
= q(0) =
"^(0)"<7,(0)_^: (°).
"^(0)"<7,(0)_^: (°).
В точке сопряжения значения всех трех параметрических компонент векторов р и q должны быть равны. Кривые, в которых такие условия удовлетворяются, назовем кривыми, обладающими параметрической непрерывностью (parametric continuity} класса С°.Переходя к анализу производных в точке сопряжения, можно сформулировать условие непрерывности по первой производной аналогично тому, как это делалось при обсуждении формы Эрмита:В точке сопряжения значения всех трех параметрических компонент векторов р и q должны быть равны. Кривые, в которых такие условия удовлетворяются, назовем кривыми, обладающими параметрической непрерывностью (parametric continuity} класса С°.Переходя к анализу производных в точке сопряжения, можно сформулировать условие непрерывности по первой производной аналогично тому, как это делалось при обсуждении формы Эрмита:p(1)-q(O)p(1)-q(O)р(0) р(и)р(0) р(и)• q( 1) qM• q( 1) qMРис. 10.14. Непрерывность составной кривой в точке сопряженияРис. 10.14. Непрерывность составной кривой в точке сопряжения
В точке сопряжения значения всех трех параметрических компонент векторов р и q должны быть равны. Кривые, в которых такие условия удовлетворяются, назовем кривыми, обладающими параметрической непрерывностью (parametric continuity} класса С°.
Переходя к анализу производных в точке сопряжения, можно сформулировать условие непрерывности по первой производной аналогично тому, как это делалось при обсуждении формы Эрмита:
В точке сопряжения значения всех трех параметрических компонент векторов р и q должны быть равны. Кривые, в которых такие условия удовлетворяются, назовем кривыми, обладающими параметрической непрерывностью (parametric continuity} класса С°.
Переходя к анализу производных в точке сопряжения, можно сформулировать условие непрерывности по первой производной аналогично тому, как это делалось при обсуждении формы Эрмита:
p(1)-q(O)
p(1)-q(O)
р(0) р(и)
р(0) р(и)
• q( 1) qM
• q( 1) qM
Рис. 10.14. Непрерывность составной кривой в точке сопряжения
Рис. 10.14. Непрерывность составной кривой в точке сопряжения
>:(!)'>:(!)''^(0)''^(0)'
>:(!)'
>:(!)'
'^(0)'
'^(0)'
p'(D = ^(D = q(0) = qAQ} .
.рХ.рХ.^(0)..^(0).
.рХ
.рХ
.^(0).
.^(0).
Кривые, в которых условия непрерывности удовлетворяются и для значения, и для первой производной, назовем кривыми, обладающими параметрической непрерывностью класса С1.
Теперь взглянем на проблему непрерывности с точки зрения геометрии. Производная к кривой, заданной параметрически, в некоторой точке есть вектор касательной к этой кривой, направленный в сторону увеличения значения параметра. Предположим, что в точке сопряжения вместо равенства значений компонент производной к сегментам мы потребуем только соблюдения пропорциональности этих компонент
pXD-tqXO),
с некоторым коэффициентом к. Если касательные к обоим сегментам пропорциональны, то соответствующие векторы параллельны (имеют одинаковое направление), но имеют разную длину. Будем говорить, что составная кривая, удовлетворяющая этим условиям, обладает геометрической непрерывностью (geometric continuity} класса G'. Если необходимо, чтобы касательные к сегментам в точке сопряжения были только пропорциональны, то от нас требуется соблюдение двух условий вместо трех. В результате появляется “лишняя” степень свободы, которую можно использовать для удовлетворения других условий. Эту идею можно распространить и на производные высших порядков, т.е. рассматривать классы параметрической непрерывности С" и геометрической непрерывности G".
Отметим, что форма кривой, обладающей геометрической непрерывностью класса G\ зависит от коэффициента пропорциональности длин касательных к сегментам в точке сопряжения. На рис. 10.15 показано, что форма сегментов кривых, совпадающих в конечных точках и имеющих в этих точках пропорциональные векторы касательных, довольно существенно от-
личается. Это свойство часто используется в графических программах вычерчивания, в которых пользователю предоставляется возможность регулировать значение коэффициента пропорциональности векторов касательных и таким способом настраивать . кличается. Это свойство часто используется в графических программах вычерчивания, в которых пользователю предоставляется возможность регулировать значение коэффициента пропорциональности векторов касательных и таким способом настраивать . кжелаемую форму вычерчиваемой кривой. В других приложениях, в частности связанных с анимацией, составные кривые задают траекторию движения объектов, и в таком случае геометрическая непрерывность классаG* оказывается явно недостаточной (см. упр. 10.11).желаемую форму вычерчиваемой кривой. В других приложениях, в частности связанных с анимацией, составные кривые задают траекторию движения объектов, и в таком случае геометрическая непрерывность классаG* оказывается явно недостаточной (см. упр. 10.11).Я'(0)Я'(0)q'd)q'd)P'Wq(u)P'Wq(u)P'(l)P'(l)
личается. Это свойство часто используется в графических программах вычерчивания, в которых пользователю предоставляется возможность регулировать значение коэффициента пропорциональности векторов касательных и таким способом настраивать . к
личается. Это свойство часто используется в графических программах вычерчивания, в которых пользователю предоставляется возможность регулировать значение коэффициента пропорциональности векторов касательных и таким способом настраивать . к
желаемую форму вычерчиваемой кривой. В других приложениях, в частности связанных с анимацией, составные кривые задают траекторию движения объектов, и в таком случае геометрическая непрерывность классаG* оказывается явно недостаточной (см. упр. 10.11).
желаемую форму вычерчиваемой кривой. В других приложениях, в частности связанных с анимацией, составные кривые задают траекторию движения объектов, и в таком случае геометрическая непрерывность классаG* оказывается явно недостаточной (см. упр. 10.11).
Я'(0)
Я'(0)
q'd)
q'd)
P'Wq(u)
P'Wq(u)
P'(l)
P'(l)
P(u)P(u)Puc. 10.15. Влияние длины векторов касательных на форму сегмента полиномиальной кривой
P(u)
P(u)
Кривые и поверхности в форме Безье
Сравнение кривых в форме Эрмита и в форме интерполяционного полинома чем-то напоминает сравнение яблока и апельсина. Обе формы представляют кубические полиномиальные кривые, но для их формирования используют разные по характеру наборы данных. Следовательно, нельзя сравнить кривые в этих формах, сформированные на одном и том же наборе данных. Попробуем использовать один и тот же ансамбль опорных точек и для определения интерполяционного полинома, и для косвенного задания производных в форме Эрмита. В результате получим кривую в форме Безье (Bezier), которая является очень хорошим приближением кривой в форме Эрмита и которую можно сравнивать с интерполяционным полиномом, сформированным на том же ансамбле опорных точек. Кроме того, поскольку определение кривой в форме Безье не требует задания производных, такая процедура идеально подходит для интерактивного построения криволинейных объектов в системах компьютерной графики и автоматизации проектирования.
L Кривые Безье
Рассмотрим ансамбль из четырех опорных точек р0, рь р2 и р3. Зададимся условием, что конечные точки формируемой кривой р(м) должны совпадать с опорными точками р0 и р3:
Ро = Р(О), Рз = Р(1).
Рис. 10.16. АппрокРис. 10.16. Аппроксимация векторов каса-симация векторов каса-Безье предложил использовать две другие опорные точки р( и р2 не для интерполяции, а для задания производных в крайних точках сегмента i/=0 и н=1. Мы воспользуемся для этого линейной аппроксимацией в пространстве параметра (рис. 10.16):
Рис. 10.16. Аппрок
Рис. 10.16. Аппрок
симация векторов каса-
симация векторов каса-
Применив эту аппроксимацию к касательным в двух крайних точках к па- р -« х х гтельных
раметрическои полиномиальной кривой p(w)=u с, получим два условия:
Зр,-Зро = С|,
Зр3-Зр2 = С]+2с2+Зс3.
Добавим их к уже имеющимся условиям совпадения кривой в конечных точках с р0 и р3:
Ро ~ <-о, Рз = с0+С]+с2+с3.
Итак, мы снова получили три набора по четыре уравнения относительно четырех неизвестных в каждом. Решая их по той же методике, что и в разделе 10.5.1, получим
с = Мдр,
где Mfl называется базисной матрицей Безье (Bezier geometry matrix^. Матрица Мд имеет вид
м„ =
" 1000"
-3 300
3-630
-1 3 -3 1
Следовательно, кубический полином Безье определяется соотношением
р(г/) = и7Мдр.
Эту формулу можно использовать точно так же, как и аналогичную формулу для составной кривой, сегменты которой являются интерполяционными полиномами. Если имеется ансамбль опорных точек р0,...,р„, то первую четверку р0, рь р2 и р3 будем использовать для определения первого сегмента составной кривой, конечную точку первого сегмента р3 и следующие три опорные точки р4, р5 и р6 — для определения второго сегмента и т.д. Очевидно, что составная кривая, построенная по методу Безье на произвольном ансамбле опорных точек, относится к классу С° параметрических непрерывных кривых, но требованиям класса С1 она не удовлетворяет, поскольку касательные справа и слева отточки сопряжения аппроксимируются по разным формулам.
Важные свойства кривых Безье станут более понятными после анализа функций смешивания для этого класса кривых (рис. 10.17). Можно представить кривую в виде
р(и) = b(w)7p,
где
(1-м)3
Ь(и) = МуЦ =Ь(и) = МуЦ =Зи(1-и)"
Ь(и) = МуЦ =
Ь(и) = МуЦ =
Зи2(1-«)
Эти четыре полинома являются частными случаями полиномов Бернштейна (Bernstein polynomials^-.
,/ \xd-k
Ьм(и^=—;-U (1-и) •
w k'.(d-ky V 7
Полиномы Бернштейна обладают рядом интересных свойств. Во-первых, все нули полинома располагаются либо в точках w=0, либо в точках w=l. Следовательно, каждая полиномиальная функция смешивания удовлетворяет условию 0 < ЬДи)при 0<м<1. Поскольку полиномиальные функции не имеют нулей на интервале представления параметра, все они достаточно гладкие. Несложно показать, что на этом интервале полиномы Бернштейна удовлетворяют условиям (см. упр. 10.4)
Ь,Ди)<\,
^Ь,Ди) = \.
/=0
9Иногда в литературе можно встретить название этой матрицы "базисная матрица кубической кривой Безье ". — Прим. ред.
Учитывая эти свойства полиномов Бернштейна, можно утверждать, что форма Безье кубической полиномиальной кривой является выпуклой суммой:
р(«) = ^^(и)р, .
Рис. 10.17. Полиномиальные функции смешивания для кривых в форме БезьеРис. 10.17. Полиномиальные функции смешивания для кривых в форме Безье
Рис. 10.17. Полиномиальные функции смешивания для кривых в форме Безье
Рис. 10.17. Полиномиальные функции смешивания для кривых в форме Безье
Рис. 10.18. Выпуклая оболочка и полиномиальная кривая БезьеРис. 10.18. Выпуклая оболочка и полиномиальная кривая Безье
Рис. 10.18. Выпуклая оболочка и полиномиальная кривая Безье
Рис. 10.18. Выпуклая оболочка и полиномиальная кривая Безье
Следовательно, кривая р(м) должна лежать внутри выпуклой многоугольной оболочки, образованной четырьмя заданными опорными точками, как показано на рис. 10.18. Таким образом, оказывается, что хотя полиномиальная кривая Безье и не проходит через все заданные опорные точки, она никогда не выходит за пределы области, ограниченной этими точками. Эти два свойства кривых Безье, к которым следует еще присовокупить и возможность работать при конструировании таких кривых только с координатами опорных точек, определили широкое распространение этого класса кривых в практических приложениях компьютерной графики и в системах автоматизации проектирования. Пользователь может задать расположение четырех опорных точек, получить эскиз кривой, построенной на их основе, а затем, перемещая опорные точки, “подгонять” форму кривой.
Порции поверхности в форме Безье
Порции поверхностей Безье {Bezier surface patches) можно сформировать с помощью функций смешивания. Если P-[p,J — массив опорных точек с размерами 4x4, то соответствующая порция поверхности в форме Безье описывается соотношением
p(u,v) = Y^bfu)bfv)pi/ = urMBPM>. "
<=0 7=0
Порция поверхности проходит через угловые точки р00, р03, р30 и р33 и не выходит за пределы выпуклого многогранника, вершинами которого являются опорные точки (рис. 10.19). Двенадцать опорных точек из 16 можно интерпретировать как данные, определяющие направление производных по разным параметрам в угловых точках формируемой порции поверхности.
Рассмотрим, например, угол порции поверхности, соответствующий значениям параметров u=v=0. Можно рассчитать функцию p(w, 0) и первые частные производные от р(м, v) в этой точке следующим образом:
P(0,0) = poo,
^(О,0) = 3(р10-р00), du
^(0,0) = 3(pl0-?„,), dv
^-(О,0) = 9(р00-р0|+р10-р|1).
dudv
Первые три соотношения являются очевидными расширениями граничных условий, которые были сформулированы при анализе кривых Безье. Четвертое соотношение можно рассматривать как меру оценки тенденции порции поверхности в этой угловой точке к изгибу, отклонению от плоской формы — кручению (hvisf). Рассматривая четырехугольник, вершинами которого являются роо, Рю, Рп и р0], придем к выводу, что кручение будет равно нулю только в том случае, если все точки лежат в одной плоскости. На ил. 8 цветной вклейки показано изображение рельефа, которое сформировано с использованием порций поверхностей Безье, построенных на основании массива данных об уровнях опорных точек рельефа.
Рис. 10.19. Порция поверхности БезьеРис. 10.19. Порция поверхности БезьеРис. 10.20. Кручение в угловой точке порции поверхности БезьеРис. 10.20. Кручение в угловой точке порции поверхности Безье
Рис. 10.19. Порция поверхности Безье
Рис. 10.19. Порция поверхности Безье
Рис. 10.20. Кручение в угловой точке порции поверхности Безье
Рис. 10.20. Кручение в угловой точке порции поверхности Безье
Кубические В-сплайны
Кубические кривые Безье и порции кубических поверхностей Безье довольно широко используются в задачах компьютерной графики и автоматизации проектирования. Но объекты этого класса имеют одно принципиальное ограничение. В точках сопряжения составных кривых или поверхностей обеспечивается выполнение условий параметрической непрерывности только класса С°. Если, например, использовать составную кривую в форме Безье для построения шпангоутов фюзеляжа планера (эту задачу мы рассматривали в разделе 10.2), вряд ли ее гладкость удовлетворит конструктора.
Создается впечатление, что мы достигли предела возможностей бикубического представления кривых и поверхностей и дальнейшие перспективы связаны либо с повышением порядка полиномов, либо с более частым размещением опорных точек. Но, оказывается, существует и третий путь совершенствования методики построения кубических кривых— нужно только отказаться от требования, чтобы формируемая кривая проходила через опорные точки, и согласиться, чтобы она проходила близко к ним. При этих условиях оказывается довольно просто обеспечить непрерывность не только самой составной кривой, но и ее первой и второй производных в точках сопряжения сегментов.
Кубические В-сплайны (пример)
В этом разделе мы рассмотрим один из частных случаев в В-сплайновой кривой и покажем, как обеспечить непрерывность класса С2 в точках сопряжения сегментов. В разделе 10.8
будет рассмотрен более общий подход к формированию сплайновых кривых, который включает кривые Безье как частный случай.
Рассмотрим четыре последовательные опорные точки {р,_2, p,-i, р„ p,+i}, “выдернутые” из достаточно длинной последовательности. Раньше мы формировали на основе четырех точек такую кубическую кривую, которая при изменении значения параметра и от 0 до 1 “пробегала” расстояние от р,_2 до р,+|, причем проходила через точки р,_2 и р/+ь Теперь же ос
тавим только первое условие — по мере того как параметр и изменяется от 0 до 1, мы должны сформировать участок кривой между промежуточными точками, соответствующими двум средним из заданного набора (рис. 10.21).
Рис. 10.21. Четыре опорные точки, определяющие сегмент В- сплайнаРис. 10.21. Четыре опорные точки, определяющие сегмент В- сплайнаПо этому же принципу при обработке набора {р,_3, р,_2, р,_ь р,} строится сегмент между р,_2 и p,_i, а при обработке набора {p,.h р„ р,+), р,+г} — между точками р, и р,+|. Пусть p(zz) — сегмент составной кривой между точками р,_] и р„ a q(w) — сегмент кривой слева от него, между точками р,_2 и р,_]. Можно сопоставить характеристики кривой в точке р(0) с характеристиками в точке q(l). Используя принятые ранее обозначения, будем искать матрицу М, которая обеспечит формирование искомого полинома в форме
Рис. 10.21. Четыре опорные точки, определяющие сегмент В- сплайна
Рис. 10.21. Четыре опорные точки, определяющие сегмент В- сплайна
p(zz) = и7Мр,
где р есть матрица опорных точек:
Р-2
₽=Р|. р,
_Р—I .
Аналогичное соотношение можно записать и для сегмента q(»):
q(zz) = u7Mq,
где
Р-з
L Р- J
В принципе можно было бы выписать набор ограничений, накладываемых на характеристики точки р(0) при их сопоставлении с характеристиками точки q( 1), и такие же ограничения на характеристики точки р(1) при их сопоставлении с начальной точкой сегмента справа от него. Например, ограничение
p(O) = q(D
обеспечивает непрерывность составной кривой в точке сопряжения, но не требует, чтобы составная кривая проходила через какую-либо опорную точку. Это ограничение представляет собой одно из уравнений системы, решением которой являются элементы матрицы М. Очевидно, что можно придумать довольно много разных ограничений и получить в результате избыточную систему уравнений. Выбирая то или иное множество уравнений из этой избыточной системы, можно вычислить разные значения элементов матрицы М.
Можно ограничить пространство перебора и вычислить элементы матрицы для наиболее распространенного сочетания ограничений. Этот вариант строится исходя из того, что
нам нужно использовать симметричную аппроксимацию в точке сопряжения сегментов. Следовательно, любая формулировка ограничений на характеристики в точке q(l) не может использовать опорную точку р,_3, поскольку она не может появиться в уравнении, которые мы выпишем для р(и). Точно так же, нельзя использовать р,+| в любом уравнении, составленном для характеристик точки р(0). Этим условиям симметричности будут соответствовать два уравнения:
р(0) = q(l) = 7(р,_2 + 4р,_, + р,), о
p'(0) = q'(l)=^(p, -р,_2).
Если выразить р(и) через массив коэффициентов с в виде
р(и) = u7c,
то сформулированные условия примут вид
с0 =7(Р,-2 +4р,_. +pj, о
с^Р.-р,-,).
Симметричные условия можно сформулировать для точки р(1):
р(1) = с0 + с, +с2 +с3 =7(р,ч +4р, +р,+1), О
р(1) = с, + 2с, + Зс3 = |(р,+1 -Р,_.)-
Теперь в нашем распоряжении имеется система четырех уравнений для коэффициентов с, которую можно решить, зная матрицу М$— базисную матрицу В-сплайна (B-spline geometry matrix):
1410"
Ms=l6Ms=l6-3 03 0
Ms=l
6
Ms=l
6
3 -6 3 0 "
-13-3 1
Рис. 10.22. Функции смешивания для кубических В-сплайновРис. 10.22. Функции смешивания для кубических В-сплайновЗначения элементов матрицы М5 определяют несколько интересных свойств полинома. Их несложно вывести, обратившись опять к полиномиальным функциям смешивания:
Рис. 10.22. Функции смешивания для кубических В-сплайнов
Рис. 10.22. Функции смешивания для кубических В-сплайнов
0-«)3
b(u) = М£и = ^b(u) = М£и = ^4-би2 + Зи31 + 3и + 3и2 - Зи3
b(u) = М£и = ^
b(u) = М£и = ^
Графики этих функций показаны на рис. 10.22. Как и в случае с формой Безье, полиномиальные функции смешивания обладают следующими свойствами:
Z^(«) = l
и 0 < b,(u) < 1 на интервале 0 < u < 1.
Следовательно, сформированный сегмент кривой должен лежать в границах, заданных выпуклой оболочкой из опорных точек, как показано на рис. 10.23. Обратите внимание, что сегмент перекрывает только часть диапазона изменения параметра в пределах оболочки. Мы формулировали ограничения таким образом, чтобы составная кривая принадлежала к классу непрерывности С1, но фактически она относится к классу непрерывности С2. Это несложно проверить, вычислив значение р"(и) в точках w=0 и w=l. Вы увидите, что эти значения одина
ковы, а следовательно, сегменты справа и слева от точки сопряжения имеют разные значения вторых производных. Это свойство сплайновых кривых делает их особенно привлекательны
ми для задач конструирования в интерактивном режиме.
лочка сегмента сплайновой кривойлочка сегмента сплайновой кривойЕсли по одному и тому же ансамблю опорных точек формировать сплайновую кривую, кривую Безье и составную интерполяционную кривую, то в первом случае приходится выполнить почти в три раза больше вычислений, чем в двух других. Это объясняется тем, что за один цикл вычислений при аппроксимации кривой сплайном формируется сегмент длиной, соответствующей расстоянию между соседними опорными точками, а во втором и третьем случаях длина сегмента в три раза больше.
лочка сегмента сплайновой кривой
лочка сегмента сплайновой кривой
В-сплайны и базисные функции
Попробуем взглянуть на формируемую кривую с несколько иной точки зрения — какое влияние оказывает на форму кривой каждая отдельная опорная точка. Каждая опорная точка принимается во внимание при формировании четырех последовательных сегментов составной В-сплайновой кривой. Это свойство гарантирует локальный характер кривых этого класса, т.е. изменение положения отдельной точки влияет на поведение составной кривой в достаточно малой окрестности этой точки. Рассмотрим опорную точку р,. На интервале между и=0 и и=\ координаты этой точки являются весовыми коэффициентами для полиномиальной функции смешивания Ьг^и). Они также учитываются и при формировании сегмента q(u) слева, где эти координаты являются весовыми коэффициентами функции смешивания б^и+1), — для этого сегмента нам придется сдвинуть значение параметра и на 1.
В\и) =В\и) =О^о(м + 2) 6t(« + l) Ь2(и) ^(«-1)ОО^о(м + 2) 6t(« + l) Ь2(и) ^(«-1)ООбщий взнос отдельной опорной точки можно выразить в виде 5,(и)р„ где В,(и) имеет вид
В\и) =
В\и) =
О
^о(м + 2) 6t(« + l) Ь2(и) ^(«-1)
О
О
^о(м + 2) 6t(« + l) Ь2(и) ^(«-1)
О
при и < i - 2, при I - 2 < и < I - 2, при i -1 < и < z, при i < и < i +1, при i +1 < и < z + 2, при и > i + 2.
График этой функции показан на рис. 10.24. Задавшись множеством опорных точек р0,..,рт, можно выразить составной сплайн в виде единого выражения:
т-1
р(и) = ^Я,(и)р,.
Из этого выражения следует, что каждый член множества функций B(u-i) является сдвинутым образом единственной функции и что такое множество образует базис для всех кубиче-
ских В-сплайновых кривых. Задавшись ансамблем опорных точек, мы формируем кусочнополиномиальную кривую p(zz) на всем интервале представления параметра в виде линейной комбинации отдельных базисных функций. На рис. 10.25 показаны кривая p(w) и базисные функции, которые входят в состав взвешенной суммы. В теории сплайнов общего вида, которую мы кратко рассмотрим в разделе 10.8, эта идея распространяется на полиномы более высокой степени и вариант аппроксимации, когда разные сегменты составной сплайн-кривой представлены полиномами разной степени.
/-2 /-1//+1 / + 2/-2 /-1//+1 / + 2Рис. 10.25. Аппроксимация кривой взвешенной суммой базисных функцийРис. 10.25. Аппроксимация кривой взвешенной суммой базисных функций
/-2 /-1//+1 / + 2
/-2 /-1//+1 / + 2
Рис. 10.25. Аппроксимация кривой взвешенной суммой базисных функций
Рис. 10.25. Аппроксимация кривой взвешенной суммой базисных функций
Сплайновые поверхности
В-сплайновая поверхность является таким же расширением В-сплайновых кривых, как поверхность Безье является расширением кривых Безье. Используя функции смешивания В- сплайновых кривых, получим уже знакомое вам описание порции поверхности:
P(“.v) = £5^,W/v)P,y .
/=0 ;=0
Отличие его от аналогичного соотношения для поверхности Безье заключается в том, что оно
справедливо только для порции, расположенной около центрального четырехугольника массива опорных точек (рис. 10.26). На расчет поверхности, соответствующей всему массиву опорных
точек, уйдет в 9 раз больше времени, чем в случае Безье- поверхности. Но зато на том же множестве опорных точек удается построить гораздо более плавную поверхность, чем при использовании формы Безье.
Рис. 10.26. Порция сплайновой поверхности
Обобщенные В-сплайны
Предположим, что имеется ансамбль опорных точек ро, -, Р„г В общем виде задача аппроксимации формулируется как поиск функции р(и)=[д-(г/) Xм) Xм)]определенной на интервале wmin < и < wmax, которая является достаточно гладкой и проходит в определенном смысле достаточно близко к опорным точкам. Предположим, что имеется множество значений {и*}, называемых узлами (knots), такое, что
Mmin м0 — М1 — ... < W„ — Wmax.
Последовательность и», мь..., и„ будем называть массивом узлов'2. При использовании аппроксимации сплайнами функция р(г/) имеет вид полинома степени d на интервале между соседними узлами:
d
p(u) = '^cjku1, ик<и<ик+1.
j-0
Следовательно, для того чтобы определить сплайн степени d, потребуется определить n(d+l) трехмерных коэффициентов с^. Необходимые для этого уравнения можно получить, рассматривая разного рода ограничения, связанные с непрерывностью функции и критерием близости к опорным точкам.
Например, если d=3, то мы имеем дело с кубическим полиномом на каждом интервале, т.е. при данном п нужно сформулировать 4п ограничений. Существует п-1 внутренний узел, и, формулируя ограничения, связанные с непрерывностью класса С2 в этих узлах, получим Зп-З уравнения. Если же требуется, чтобы функция еще и проходила через и+1 опорную точку, то общее число уравнений составит 4и-2. Остается сформулировать еще два ограничения (получить еще два уравнения). Их можно получить, задавшись, например, определенным направлением касательной к формируемой кривой в начальной и конечной точках. Такой подход к формированию сплайна является глобальным — нужно решить систему из 4п уравнений относительно 4п неизвестных, а значит, каждый полученный коэффициент будет зависеть от всех опорных точек. Поэтому, хотя такая методика определения коэффициентов сплайна и обеспечит получение гладкой кривой, проходящей через заданные опорные точки, она не очень хорошо согласуется со спецификой задач компьютерной графики и автоматизации проектирования.
Рекурсивно определенные В-сплайны
Подход, который выбран для формирования В-сплайнов, состоит в том, чтобы определять сплайн в терминах множества базисных функций (или, что то же самое, функций смешивания), каждая из которых отлична от нуля только на интервале в несколько узлов. Следовательно, можно записать функцию р(м) в виде
р(и) = У, В,Ди)р; ,
где каждая функция В,^и) есть полином степени d на интервале в несколько узлов и равна нулю за пределами интервала (ut min, ut max). Само название В-сплайн происходит от basis spline (базисный сплайн), т.е. отражает тот факт, что множество функций (B,j(u)) образуют базис для данной последовательности узлов и заданной степени полинома. Существует много способов определения базисных функций, но особое место принадлежит одному из них, который получил название метода рекурсивных функций Кокса-де Бура (Cox-deBoor recursion)'3;
_если uk<и $uk+t,
(О -в противном случае;
Вм =Ji^Bk.d^u)+-±^^^^^ ^k+d UkЩ+d+l UktX
12Во многих работах эта последовательность именуется вектором узлов (knot vector), но использование в данной книге такой терминологии может внести определенную путаницу, поскольку ранее мы договорились называть вектором направленный отрезок.
13Эта формула также известна как рекурсивная формула де Кастельо fdeCasteljau recursion).
Каждая функция из первого множества, Bto, постоянна на одном интервале и равна нулю за его пределами; каждая функция из второго множества, Вкх, линейна на двух интервалах и равна нулю за их пределами; каждая функция из третьего множества, Вкъ имеет вид квадратичной кривой на трех интервалах и равна нулю за их пределами и т.д. (рис. 10.27). В общем случае функция из множества Bkd имеет отличное от нуля значение на d+\ интервалах между ик и uk+d+x и представляет собой полином степени d на каждом из этих интервалов, причем в узлах обеспечивается параметрическая непрерывность класса С^”1. Сплайн-кривая, образованная как взвешенная сумма таких базисных функций, будет лежать внутри выпуклой многоугольной оболочки, поскольку для базисных функций выполняется условие
^В,Ди) = \, 0<ВДи)<\. 1=0
Но поскольку каждая функция Bid отлична от нуля только на d+\ интервалах, то каждая опорная точка оказывает влияние только на тот участок суммарной кривой, который лежит внутри оболочки, образованной d+1 опорной точкой.
Рис. 10.27. Три первые базисные функции Кокса-де Бура
Множество базисных функций В-сплайна определяется заданной степенью сплайна и массивом узлов. Учтите, что, поскольку для определения сплайна в диапазоне от и0 до ип,х необходимо располагать рекурсивными функциями, отличными от нуля в узлах от и0 до und, нам понадобятся d-\ “дополнительных” значений в узлах. Эти дополнительные значения определяются ограничениями, которые можно наложить на характеристики результирующей В- сплайновой кривой в начальной и конечной точках.
Обращаю также ваше внимание и на то, что мы не накладываем никаких ограничений на значения в узлах, кроме как ик < ик+х. Если считать, что результат деления 0/0 при вычислении по рекурсивным формулам равен 1, то мы можем иметь повторяющиеся, или кратные, узлы. При постоянном шаге между узлами сформированный В-сплайн называется равномерным (uniform spline), но можно обеспечить большую гибкость при формировании кривых, если позволить не только неравномерное размещение узлов, но и наличие кратных (ик = икч) узлов. Кратко проанализируем открывающиеся при этом возможности.
Равномерные В-сплайны
Рассмотрим последовательность равноотстоящих узлов {0, 1,2,..., п). Кубический В- сплаин, который был рассмотрен в разделе 10.7, можно построить на основе формул Кокса- де Бура на равноотстоящих узлах. Будем использовать нумерацию узлов, принятую в формулах Кокса-де Бура. Участок сплайн-кривой между узлами к и £+1 будем формировать по подмножеству опорных точек р*_ь р*, р*+1, р*+2- Следовательно, получим кривую, определенную только на интервале от w=l до и=п-\. Для опорных точек, расположенных так, как показано на рис. 10.28, мы построим кривую, которая не пересекает узлы. В некоторых случаях (один из них показан на рис. 10.29) удается использовать периодичность в расположении опорных точек и построить сплайн, который проходит через эти точки. Такой равномерный периодический В-сплайн (uniform periodic B-spline) обладает тем свойством, что каждая базисная функция представляет собой сдвинутый образ единственной функции.
Рк-1*Рк-1*Рк* еРк*1Рк* еРк*1• Рк +2
Рк-1*
Рк-1*
Рк* еРк*1
Рк* еРк*1
_1IIIIIL
Uk - 1 Uk Uk * 1 °к ♦ 2
Рис. 10.28. Равномерный В-сплайн
Рис. 10.29. Периодический равномерный В-сплайнРис. 10.29. Периодический равномерный В-сплайнНеравномерные В-сплайны
Рис. 10.29. Периодический равномерный В-сплайн
Рис. 10.29. Периодический равномерный В-сплайн
При повторении узлов (кратности узлов) проявляется эффект приближения сформированного В-сплайна к опорной точке, ассоциированной с этими узлами. Если кратность узла равна d+1, то В-сплайн степени d будет проходить через соответствующую опорную точку. Следовательно, один из методов решения проблемы недостатка данных для формирования сплайна состоит в том, чтобы повысить кратность узлов, соответствующих начальной и конечной точкам, и тем самым “заставить” результирующую кривую пройти через эти точки. При этом остальные, внутренние, узлы остаются равноотстоящими. Такие В-сплайны называются открытыми {open splines).
Для формирования кубических В-сплайнов часто используется массив узлов {0,0,0,0, 1,2,..., л-1, п, п, п, п}. Особый интерес представляет массив узлов {0,0,0,0, 1,1,1,1}, который превращает В-сплайн в кривую Безье. В общем случае можно делать кратными и внутренние узлы, а также размещать их неравномерно.
NURBS - неравномерный рациональный В-сплайн
Анализируя В-сплайны, мы полагали, что p(w) представляет собой массив триад [ х{и) у{и) z(w)]r. При работе в двухмерном пространстве триада заменяется парой [х(н) Xм)] \ н0 все выведенные соотношения остаются неизменными. Точно так же соотношения не изменятся и в том случае, если мы перейдем к четырехмерным В-сплайнам. Трехмерные опорные точки р,=[х; у, z,] можно следующим образом представить в пространстве однородных координат:
q, = w;
Идея состоит в том, чтобы использовать коэффициенты w, для увеличения или уменьшения “веса” конкретной опорной точки в ансамбле. Эти взвешенные опорные точки можно использовать при формировании четырехмерного В-сплайна. Первые три компонента полученного сплайна будут представлять собой обычное В-сплайновое представления взвешенных опорных точек:
q(«)= у{и)q(«)= у{и)z(«)z(«)=Х5<.^м)и;<р<- /=0=Х5<.^м)и;<р<- /=0х{и)
q(«)= у{и)
q(«)= у{и)
z(«)
z(«)
=Х5<.^м)и;<р<- /=0
=Х5<.^м)и;<р<- /=0
Компонент и1 представляет собой скалярный полиномиальный В-сплайн, сформированный по множеству значений весовых коэффициентов:
w(u) = ^Bld(u)w, .
/=0
При использовании четырех параметрических сплайнов в качестве составляющих набора однородных координат значение w может оказаться не равным 1, и, следовательно, нужно будет при переходе к трехмерному представлению точек использовать перспективное деление:
p(") = ^-q(«) =w(u)p(") = ^-q(«) =w(u)'LB.dWw.P. ^В.ДиХ /=0
p(") = ^-q(«) =w(u)
p(") = ^-q(«) =w(u)
Каждый компонент функции p(z/) представляет собой рациональную функцию параметра и, и поскольку мы не накладывали никаких ограничений на расположение узлов, то эта функция относится к классу неравномерных рациональных В-сплайнов (NURBS nonuniform rational B-spline).
NURBS-кривые сохраняют все свойства, присущие трехмерным В-сплайнам, в частности ограниченность выпуклой оболочкой и непрерывность, но дополнительно к ним обладают и еще двумя уникальными свойствами, которые представляют особый интерес для задач компьютерной графики и автоматизации проектирования.
Если по отношению к В-сплайновой кривой или поверхности применить аффинное преобразование, то получим ту же функцию, что и В-сплайн, сформированный по исходному ансамблю опорных точек, подвергшихся тому же преобразованию. Поскольку перспективное преобразование не относится к классу аффинных, большинство геометрических объектов, состоящих из В-сплайновых кривых, будет искажаться при их выполнении, но как раз NURBS- кривые будут обрабатываться при таких преобразованиях корректно.
Квадратичные поверхности обычно определяются аналитически в неявной форме. Используя нерациональные сплайны, можно только приближенно представить такие поверхности. Однако можно показать, что аналитические квадратичные кривые являются частным случаем квадратичных NURBS-кривых. Следовательно, можно использовать единый метод моделирования, основанный на формировании NURBS-кривых, для создания практически всех распространенных типов криволинейных геометрических объектов (см. упр. 10.14 и 10.15).
Построение кривых и поверхностей
Если уж в состав сцены включены объекты, состоящие из кривых и поверхностей, нам не обойтись без методов их воспроизведения. Существует несколько подходов к выполнению подобной процедуры. Первый подход— можно вычислить точки пересечения с таким объектом лучей, исходящих из центра проецирования и проходящих через определенные пиксели картинной плоскости. Однако вычисление пересечений с криволинейными объектами, если только они не представляют собой квадратичные поверхности (о них речь пойдет в разделе 10.11), требует решения нелинейных уравнений, что не так-то просто сделать, учитывая, что все вычисления следует выполнять в реальном масштабе времени. Второй подход состоит в том, чтобы вычислить массив вершин, принадлежащих криволинейному объекту, и построить на основе этого массива приближение криволинейного объекта множеством плоских из стандартного набора. Мы сосредоточим внимание именно на втором подходе и рассмотрим, как на его основе воспроизводить в графической системе параметрические полиномиальные кривые и поверхности.
Методы вычисления полиномов
Рассмотрим полиномиальную форму задания кривой на стандартном интервале:
р(и) = ^с/и', 0<и<1.
1=0
Можно вычислить значения р(г/) для некоторого множества значений параметра {и*} и затем использовать кусочно-линейную аппроксимацию кривой (в OpenGL это делается с помощью примитива типа GL_LINE_STRIP). Вместо того чтобы вычислять независимо каждый член, содержащий ик, их можно сгруппировать следующим образом:
p(w) = c0+i/(c,+w(c2+m(...+c„z/))).
После такой группировки для вычисления значения полинома p(Uk) потребуется только п умножений. Этот алгоритм известен под названием схемы Горнера {Horner's method). Типовой кубический полином р(и) группируется по этой схеме следующим образом:
р(н) = c0+w(ci+w(c2+wc3)).
Если отсчеты значения параметра {г/,} распределены равномерно на заданном интервале представления полинома, то для вычисления р(м*) можно использовать метод правых разностей (forward differences), который требует О{п) сложений и ни единого умножения. Этот метод реализуется следующей итерационной процедурой:'
Д(0)р(п*) = р(ик),
А(1)р(и*) = р(им)-р(“А
^'^(ик) = A^ptM^j-A'^ptz/*).
Если «k+t-u^h , то несложно показать, что для полинома р(м) степени п значение Д^р^*) будет постоянным при любом к. Из этого результата следует вычислительная схема, вариант которой для скалярного кубического полинома представлен на рис. 10.30:
р(и) = l+3u+2w2+w3 .
Для вычислений по этой схеме значения Д^'^по) требуется знать первые л+1 значений функции р{ик). Располагая значением ^р{и0), можно скопировать его в ячейки таблицы и работать дальше по схеме, представленной на рис. 10.31. Последующие значения р(ик) вычисляются по рекуррентной формуле, полученной перестановкой членов в приведенном выше соотношении:
Рис. 10.31. Вычислительная схема, использующая таблицу правых разностейРис. 10.31. Вычислительная схема, использующая таблицу правых разностей>012345>012345Этот метод довольно эффективен, но, к сожалению, его можно применять только на равномерной сетке, и он склонен к накоплению ошибок вычислений.
Рис. 10.31. Вычислительная схема, использующая таблицу правых разностей
Рис. 10.31. Вычислительная схема, использующая таблицу правых разностей
>012345
>012345
Рис 10.30. Построение таблицы правых разностей.
Рекурсивное разбиение кривых Безье
Пожалуй, наибольшее распространение в компьютерной графике получил метод рекурсивного разбиения полиномиальных кривых Безье. В основе метода лежит использование выпуклой многоугольной оболочки, причем в процессе вычислений по этому методу никогда не приходится в явном виде вычислять значения полиномов. Пусть имеется описание кубической кривой Безье (метод пригоден и для обработки кривых Безье более высокой степени). Известно, что кривая должна быть расположена в области, ограниченной выпуклой многоугольной оболочкой, построенной на опорных точках, как на вершинах. Разделим кривую на два сегмента l(w) и г(и). Каждый сегмент имеет область определения, равную половине исходного интервала. Поскольку исходная кривая кубическая, то и каждый из сегментов является кубической кривой. Учтите, что поскольку каждый сегмент имеет область определения, равную половине исходного интервала, то придется изменить масштаб представления параметра и для 1 и г таким образом, чтобы на
каждом сегменте и изменялся в диапазоне (0, 1). Сегмент 1(и) представляет левую половину р(и),
а сегмент r(u)— правую. Каждый из сегментов имеет четыре опорные точки, которые, во- первых, определяют его форму, а во-вторых, служат вершинами выпуклой многоугольной обо
лочки. Обозначим эти два множества опорных точек {10,1Ь 12, 13} и {r0, rh г2, г3}. Исходная кривая р(и) имеет ансамбль опорных точек (р0, рь р2, р3}. Все три ансамбля и кривая р(м), разбитая на сегменты 1(и) и г(м), показаны на рис. 10.32. Обратите внимание на то, что выпуклые оболочки
для I и г всегда будут лежать внутри выпуклой оболочки для р, что является следствием из свойст
ва уменьшения вариации {variation-diminishing property), которым обладают кривые Безье.
Рис. 10.32. Выпуклые оболочки и опорные точки кривой Безье и ее сегментовРис. 10.32. Выпуклые оболочки и опорные точки кривой Безье и ее сегментовРассмотрим левый сегмент. Можно выяснить, насколько многоугольник оболочки является выпуклым, измерив расстояние между точками I] и 12 и отрезком, связывающим 10 и 13. Если расстояние не превышает некоторой наперед заданной величины, то вместо криволинейного сегмента 1(и) можно провести прямую. В противном случае можно разделить сегмент l(w) еще на пару сегментов и проанализировать их выпуклость. В результате полу
Рис. 10.32. Выпуклые оболочки и опорные точки кривой Безье и ее сегментов
Рис. 10.32. Выпуклые оболочки и опорные точки кривой Безье и ее сегментов
чилась рекурсивная процедура, которая на первый взгляд не требует вычисления значений полинома. Но у читателей, естественно, должен возникнуть вопрос, а откуда мы возьмем данные о положении опорных точек (10, Ь, 12,13} и (г0, гь г2, г3} сегментов кривой после разбиения, которые одновременно являются и вершинами многоугольных выпуклых оболочек этих сегментов. Ниже будет показано, как отыскать эти вершины для левого сегмента !(?/), а про
цедура для правого сегмента будет симметричной (в смысле индексов точек). Начнем с представления кривой Безье, знакомого нам по разделу 10.6.1:
Ро
р(м) = игМвр(м) = игМвPiР2PiР2
р(м) = игМв
р(м) = игМв
Pi
Р2
Pi
Р2
Рз
где
10 0 0
мв =мв =-33-33о о
мв =
мв =
-3
3
-3
3
3 о
-13-3 1
Полином l(w) должен проходить через точки р(0) и р(1/2). Следовательно,
1(О) = 1о = ро,
1(1) = 1з=Р^ = |(Ро + Зр1+Зр2+р3).
При и=0 касательная к 1 должна повторять касательную к р, но поскольку параметр и для кривой 1 имеет измененный масштаб по сравнению с параметром if кривой р, то, подставляя и~=2и, придем к выводу, что отношение между производными должно быть скорректировано с учетом соотношения du~=2du. Следовательно, для производных в начальной точке сегмента справедливо соотношение
3
1'(0) =3(1,-1о) = р'(О) = —(р,-р0).
Рассуждая аналогично, выведем и соотношение для производных в средней точке p(z/~) (она же последняя точка сегмента 1(и)):
/ 1 А з
l'(D = (13 -12) = р' - = -(-р0 - Р| + Рз + Рз).
Р1+Р2Р1+Р2процесса разбиения кривой Безьепроцесса разбиения кривой БезьеВ результате получается четыре уравнения, которые можно решить аналитически. Но существует и более наглядная геометрическая интерпретация процедуры разбиения и поиска новых опорных точек (рис. 10.33). Она позволяет параллельно формировать опорные точки для левого и правого сегментов. Сначала выпишем соотношения для крайних опорных точек, которые должны совпадать в исходной кривой и сегментах 1 и г:
Р1+Р2
Р1+Р2
процесса разбиения кривой Безье
процесса разбиения кривой Безье
1о = Ро,
г3 = р3.
Опорные точки 1, и г2 определяются направлением касательных к начальной точке I и конечной точке г:
=~(Ро +Р|)»
Г2=^(Рз+Рз)-
Учет направления касательных в точке сопряжения сегментов определяет еще две опорные точки — Ь и iy
’^-(р.+рОь
Г| = 2р+рР1+Р-> }
И, наконец, для точки сопряжения сегментов получим 1з=г0=1(12+г,).
Преимущество такого метода определения опорных точек сегментов заключается в том, что он требует только операций суммирования и сдвига (для деления пополам), причем в единой процедуре определяются опорные точки обоих сегментов.
Построение других типов полиномиальных кривых методом разбиения
Подобрав подходящий ансамбль опорных точек, любую полиномиальную кривую можно представить не только в форме Безье, но и в форме интерполяционного полинома, В-сплайна и т.д. Эффективность метода разбиения кривых Безье привела к тому, что обычно стараются любые кривые преобразовать в форму Безье. Алгоритмы преобразования можно сформулировать, анализируя процесс формирования кривой. Рассмотрим кубическую кривую в форме Безье. Ее можно описать следующим образом с помощью базисной матрицы Безье Мй:
Р(») = и7Мйр,
где р есть матрица ансамбля опорных точек для формирования формы Безье. Ту же самую полиномиальную кривую можно представить и в виде
p(r/) = u7Mq,
где М есть базисная матрица другой формы кривой, a q — матрица соответствующего ансамбля опорных точек. Если полиномы идентичны (речь ведь идет об изменении только формы представления кривой), то, приравняв правые части соотношений, получим
Ч = М'Мвр.
В частности, для преобразования из формы интерполяционного полинома в форму Безье матрица пересчета должна иметь вид
Для выполнения преобразования из формы кубического В-сплайна в форму Безье необходимо использовать матрицу пересчета в виде
м;'м,=1 Ом;'м,=1 О14 10'0 4 2 00 2 4 00 14 114 10'0 4 2 00 2 4 00 14 1
м;'м,=1 О
м;'м,=1 О
14 10'
0 4 2 0
0 2 4 0
0 14 1
14 10'
0 4 2 0
0 2 4 0
0 14 1
На рис. 10.34 показаны четыре опорные точки и построенные по ним кривые в форме Безье, в форме интерполяционного полинома и в форме В-сплайна. Все три кривые сформированы средствами построения кривых Безье, имеющимися в составе графической системы OpenGL. Интерполяционная кривая и В-сплайн сформированы как кривые Безье с использованием матриц пересчета М/'М/ и M/'MS. Обратите внимание на то, что кривая в форме В-сплайна сформирована только на интервале между двумя средними опорными точками исходного ансамбля.
Рис. 10.34. Кубические полиномиальные кривые, сформированные как кривые Безье с преобразованием исходного ансамбля опорных точек: а — в форме Безье; б — в форме интерполяционного полинома; в — в форме В-сплайна.Рис. 10.34. Кубические полиномиальные кривые, сформированные как кривые Безье с преобразованием исходного ансамбля опорных точек: а — в форме Безье; б — в форме интерполяционного полинома; в — в форме В-сплайна.
Рис. 10.34. Кубические полиномиальные кривые, сформированные как кривые Безье с преобразованием исходного ансамбля опорных точек: а — в форме Безье; б — в форме интерполяционного полинома; в — в форме В-сплайна.
Рис. 10.34. Кубические полиномиальные кривые, сформированные как кривые Безье с преобразованием исходного ансамбля опорных точек: а — в форме Безье; б — в форме интерполяционного полинома; в — в форме В-сплайна.
Разбиение поверхности Безье
Описанный выше алгоритм разбиения можно распространить и на построение поверхностей в форме Безье. Порция поверхности Безье, представленная на рис. 10.35, сформирована по 16 опорным точкам. Каждые четыре точки в одном и том же столбце или строке задают сегмент кривой Безье, и, следовательно, к ним можно применить алгоритм разбиения. Но применительно к порции поверхности алгоритм должен разбивать ее не на две, а на четыре порции, поэтому модифицируем исходный алгоритм и преобразуем его в двухэтапный.
Сначала применим исходный вариант алгоритма к четырем сегментам, заданным шестнадцатью опорными точками, причем каждый сегмент соответствует определенному значению параметра и. В результате для значений и=0, 1/3, 2/3, 1 сформируются две группы по четыре опорные точки в каждой, причем средние точки будут принадлежать обеим группам. Таким образом, получим новые ансамбли по семь опорных точек вдоль каждого из четырех сегментов. На рис. 10.36 эти точки отмечены темными кружочками. Как видно на этом рисунке, все имеющиеся опорные точки (и прежние, и новые) можно разделить на три категории: исходные опорные точки, которые сохранились в новом ансамбле (они залиты серым цветом), новые опорные точки, которые появились в результате выполнения разбиения (они отмечены черными кружочками), и те точки исходного ансамбля, которые после разбиения отброшены
(они отмечены белыми кружочками).
РзоРзоРззРззРооРооРозРозРис. 10.35. Порция кубической поверхности БезьеРис. 10.35. Порция кубической поверхности БезьеПосле этого перейдем ко второму этапу. Выполним разбиение семи кривых, образованных новым ансамблем опорных точек, для семи фиксированных значений параметра г. Каждая кривая характеризуется четырьмя опорными точками, ко.мрые в результате применения алгоритма разбиения превращаются в семь (средняя точка используется совместно двумя новыми сегментами). Весь ансамбль опорных точек, образовавшийся после второго этапа выполнения алгоритма, показан на рис. 10.37. Раз-
Рзо
Рзо
Рзз
Рзз
Роо
Роо
Роз
Роз
Рис. 10.35. Порция кубической поверхности Безье
Рис. 10.35. Порция кубической поверхности Безье
делив этот ансамбль на четыре группы по 16 точек в каждой (12 средних точек попадают в две группы, а одна— центральная — во все четыре), получим четыре новые порции поверхности Безье (рис. 10.38).
Новые опорные точки, сформированные в результате разбиения О Исходные опорные точки, которые после разбиения отброшены
Исходные опорные точки, которые сохранились в новом ансамбле
Рис. 10.37. Опорные точки после второго этапа разбиения порции поверхностиРис. 10.37. Опорные точки после второго этапа разбиения порции поверхностиНовые опорные точки, сформированные в результате разбиения О Исходные опорные точки, которые после разбиения отброшены
Рис. 10.37. Опорные точки после второго этапа разбиения порции поверхности
Рис. 10.37. Опорные точки после второго этапа разбиения порции поверхности
Исходные опорные тачки, которые сохранились в новом ансамбле
Рис. 10.38. Квадрант порции поверхности, образовавшийся после разбиенияРис. 10.38. Квадрант порции поверхности, образовавшийся после разбиенияПри выполнении такого двухэтапного алгоритма разбиения анализ “плоскостности” образовавшейся новой порции поверхности будет более сложным, чем при работе с кривой. Поэтому во многих системах разработчики просто фиксируют количество циклов разбиения, позволяя пользователю настраивать его программно. Если от системы требуется особо высокое качество построения образа поверхности на экране, разбиение выполняется до тех пор, пока размеры проекции порции поверхности на картинную плоскость не окажутся сравнимы с размерами пикселя.
Рис. 10.38. Квадрант порции поверхности, образовавшийся после разбиения
Рис. 10.38. Квадрант порции поверхности, образовавшийся после разбиения
Пример: формирование изображения чайника
Анализ методов работы с поверхностями, заданными в параметрической форме, завершим рассмотрением примера использования алгоритма рекурсивного разбиения для формирования изображения объекта с явно выраженной криволинейностью формы. В качестве такового возьмем чайник, который проф. М. Ньюэлл (М. Newell) из университета штата Юта еще лет 20 назад начал использовать для тестирования эффективности различных алгоритмов построения изображения криволинейных объектов. Этот объект приобрел известность в кругах специалистов и даже получил имя собственное— чайник Юта {Utah teapot). Исходное описание поверхности состоит из опорных точек для построения 32 порций бикубической поверхности Безье. Опорные точки образуют массив из 306 элементов, пронумерованных от 1 до 306. Первые 12 порций образуют основной объем чайника, следующие четыре— ручку, а еще четыре— носик. Подробнее всего проработана крышка — на нее “ушло” восемь порций, а оставшиеся четыре образуют донышко. Все эти данные широко распространены среди специалистов и часто используются при проверке работоспособности алгоритмов и программ тонирования.
Мы будем использовать этот объект для демонстрации применения метода рекурсивного разбиения, пользуясь уже известными вам средствами графической сиг^мы OpenGL. Каждую порцию поверхности будем “пропускать” через п циклов разбиения и после этого формировать изображение по сформированному массиву вершин, используя либо отрезки прямых, либо многоугольники, проведенные через четыре угловые точки сформированной “микропорции”. Следовательно, формирование на экране изображения микропорции можно выполнить с помощью следующей функции:
void draw_patch(point3 p[4][4j) {
glBegin(GL_QUADS);
glVertex3fv(p[0][0]);
glVertex3fv(p[3] [ 0 ]);
glVertex3fv(p[3][3]);
glVertex3fv(p[0][3 ]);
glEnd();
}
Функцию разбиения порции образуем из функции разбиения сегмента кубической кривой Безье с, в которой используется ранее определенный тип данных point3.
void divide_curve(point3 c[4], point3 r[4], point3 1[4]) {
int i;
point3 t;
for(i=0;i<3;i++)
{
l[0][i]=c[0][i];
r[3][i]=c[3][i];
1[1] [i] = (c[l][i]+c[0][i])/2;
r[2][i]=(c[2][i]+c[3][i])/2;
t[i]=(l[l][i]+r[2][i])/2;
1 [ 2][i]=(t[i]+l[1][i])/2;
r[l][i]=(t[i]+r[2][i])/2;
1[3][i]=r[0][i]=(l[2][i]+r[l][i])/2;
}
}
Рекурсивную программу разбиения порции поверхности несложно построить в виде последовательности вызовов функции divide_curve () и предполагая, что в нашем распоряжении имеется функция транспонирования матрицы transpose(). Текст этой программы приведен ниже.
void divide_patch(point3 р[4][4], int n) {
point3 q[4][4], r[4][4], s[4][4], t[4][4];
point3 a[4][4], b[4][4];
int i,j, k;
if(n==0) draw_patch(p);
else
{
for(k=0; k<4; k++) divide_curve(p[k], a[k), b[k]);
transpose(a);
transpose(b);
for(k=0; k<4; k++)
{
divide_curve(a[k], q[k], r[k]);
divide_curve(b[k], s[k], t[k]);
}
divide_patch(q, n-1);
divide_patch(r, n-1);
divide_patch(s, n-1);
divide_patch(t, n-1);
}
}
Полный текст программы формирования тонированного изображения чайника Юта вы можете найти на ftp-сервере по адресу ftp.cs.unm.edu в каталоге pub/angel/BOOK. В текст программы включен и массив координат исходных опорных точек, описывающих тестовый объект.
Чайник как программный объект включен в библиотеки GLUT и aux. Но сейчас он нас интересует только как средство демонстрации работоспособности и практической реализации алгоритма рекурсивного разбиения. На рис. 10.39 показаны “проволочное” изображение чайника, полученное после выполнения трех циклов разбиения, и закрашенное изображение, сформированное методом заливки каждого многоугольника серым цветом постоянной интенсивности. Обратите внимание на то, что порции имеют различный размер и кривизну. Поэтому использовать одно и то же количество циклов разбиения для разных участков поверхности нерационально — это порождает множество маленьких многоугольников, в которых совершенно нет необходимости. Желательно так организовать процесс разбиения, чтобы для формирования участков с большей кривизной выполнялось больше циклов разбиения, а для относительно плоских — меньше.
Рис. 1039. Изображение чайника ЮтаРис. 1039. Изображение чайника Юта
Рис. 1039. Изображение чайника Юта
Рис. 1039. Изображение чайника Юта
Системные средства работы с кривыми Безье в OpenGL, которые мы подробно рассмотрим в разделе 10.12, позволяют формировать порции поверхностей при минимальном количестве вызовов функций API. Они выполняют рекурсивное разбиение значительно более эффективно, чем та “доморощенная” программа, которая была приведена выше.
Алгебраические поверхности
Хотя квадратичные кривые и поверхности можно рассматривать как частный случай NURBS-кривых и поверхностей, этот класс криволинейных объектов настолько часто используется в практике автоматизации проектирования и в задачах компьютерной графики, что имеет смысл выделить пару страниц для их анализа.
Квадратичные поверхности
Квадратичные поверхности (quadric surfaces) описываются алгебраическими уравнениями в неявном виде, в которых для каждого члена полинома x^zk выполняется условие i+j+k < 2. Любая квадратичная поверхность может быть описана уравнением вида
q(x, у, z) = a{ ix2<2awxy+a22y2+a33z2+2a23y::+^^ +b\x+b2y+b3z+c = 0 .
Этот класс поверхности включает эллипсоиды, параболоиды и гиперболоиды. Общее уравнение поверхности можно записать в виде квадратичной матричной формы (quadratic form), используя трехкомпонентную матрицу-столбец р=[х у г]7:
р7Ар+Ь7р+с = О,
Десять независимых коэффициентов в А, b и с определяют конкретный вид квадратичной поверхности. Однако для классификации можно воспользоваться последовательностью преобразований поворота и сдвига, в результате которой частная поверхность будет приведена к каноническому (стандартному) виду. В трехмерном пространстве такие преобразования можно представить в виде
р' = Mp+d.
Матрица М представляет собой матрицу поворота, которая преобразует А в диагональную матрицу D=MrAM. Например, уравнение эллипсоида в каноническом виде выглядит так:
а*! 'Х^+а^ту^+азз-2-^ = О,
Все коэффициенты в этом уравнении положительны. Обращаю ваше внимание на то, что поскольку при приведении уравнений поверхностей к каноническому виду используются аффинные преобразования, то свойства квадратичных поверхностей сохраняются такими преобразованиями, что хорошо сочетается со свойствами примитивов других типов.
Вычисление точек на квадратичной поверхности методом приведения лучей
Вычислить координаты точек на квадратичной поверхности в процессе формирования ее изображения можно с помощью метода приведения лучей. Представим в параметрической форме луч, исходящий из точки р0 в направлении d:
р = po+ad.
Подставляя его в уравнение квадратичной поверхности, получим скалярное уравнение относительно переменной а:
a2d/Ad+ad7(b+2Ap0)+p0,Ap0+b7d+c = 0.
Это уравнение может не иметь ни одного действительного корня, один или два действительных корня. Результат решения уравнения можно использовать либо для заполнения буфера кадра, либо как промежуточный результат при выполнении тонирования по методу трассировки лучей. Кроме того, можно к любой точке квадратичной поверхности применить стандартный алгоритм закрашивания, поскольку, располагая аналитическим выражением поверхности, несложно вычислить компоненты нормали:
=2Ар-Ь.
Такой метод построения может быть распространен на все виды алгебраических поверхностей. Пусть имеется уравнение поверхности в виде
q6^ = q^y, -) = о.
При конвейерной организации процесса тонирования будет последовательно выполняться приведение луча, исходящего из центра проецирования, ко всем пикселям картинной плоскости. Уравнение каждого луча можно записать в параметрическом виде:
p(a) = po+ad.
После подстановки этого выражения в уравнение для q получим неявное полиномиальное уравнение относительно неизвестной а:
^(р(а)) = 0.
Это уравнение можно решить подходящим численным методом или для квадратичных поверхностей — аналитически. Если в уравнении имеются члены вида х^с, то получим не более i+j+k точек пересечения. Отбор среди них единственной может привести к тому, что тонирование займет довольно много времени.
Кривые и поверхности в OpenGL
В составе OpenGL имеются средства поддержки работы с кривыми и поверхностями Безье — Безье-вычислитель (evaluator), которые позволяют вычислять значения полиномов Безье любого порядка. Безье-вычислитель не требует, чтобы опорные точки размещались с постоянным шагом, а потому позволяет преобразовывать в форму Безье кривые, определенные любым другим из описанных ранее способов. Безье-вычислитель можно использовать для работы с полиномами от одной, двух, трех и четырех переменных. При построении кривых Безье-вычислитель вычисляет значения полинома одной переменной, что необходимо при формировании карт цветов или траектории движения во временной области персонажей в анимационных приложениях. В библиотеке GLU Безье-вычислитель используется в четырехмерном варианте для поддержки методов обработки NURBS-кривых. Вариант Безье-вычислитель, работающий с полиномами двух переменных, используется в основном при формировании поверхностей. Помимо обработки кривых и поверхностей Безье-вычислитель в OpenGL обеспечивает вычисление цветов, нормалей и координат текстур.
Кривые Безье
Функция обработки полинома одной переменной настраивается в процессе инициализации OpenGL-программы посредством вызова функции
glMaplf(type, u_min, u_max, stride, order, point_array)
Аргумент type задает тип объекта, который будет представлен полиномом Безье. Можно назначать в качестве значения этого аргумента константы, задающие трех- и четырехмерные геометрические точки, цвет в формате RGBA, нормали, индексированные цвета и координаты текстур (от одно- до четырехмерных). Указатель на массив опорных точек полинома передается функции через аргумент point_array. Аргументы u_min и u_max определяют область существования параметра полинома. Аргумент stride представляет собой количество значений параметра между сегментами кривой. Например, для кубического В-сплайна приходятся три точки на каждый сегмент кривой. Значение аргумента order должно быть на единицу больше степени полинома. Для формирования кубической трехмерной кривой в форме В-сплайна, определенной на интервале (0, 1), функции glMaplf () следует передать такой набор аргументов:
point data[]={...}
glMaplf(GL_MAP_VERTEX_3, 0.0, 1.0, 3, 4, data);
Можно в одной и той же программе инициализировать несколько функций расчета полиномов, каждую со своим набором аргументов. Например, могут параллельно работать функции расчета полиномиальной кривой и нормали, аппроксимированной полиномом. После настройки функция активизируется посредством вызова
glEnable(type);
Если функция расчета активизирована, то можно получить от нее значение полинома, вызвав функцию
glEvalCoordlf(и);
Таким образом, обращение к glEvalCoordlf () может заменить обращение к функциям gIVertex (), glColor() и glNormal(). Все зависит от того, как она была настроена при инициализации. Пусть, например, функция расчета настроена на формирование кривой Безье на интервале (0, 10) по некоторому массиву опорных точек. Набор из 100 точек кривой, равномерно отстоящих на этом интервале, можно получить с помощью такого фрагмента программы:
glBegin(GL_LINE_STRIP)
for(i=0; i<100; i++) glEvalCoordlf( (float)i/100.0); glEnd();
Если значения параметра и распределены равномерно, то для вычисления точек на кривой следует использовать функции glMapGridlf () и glEvalMeshl(), например:
glMapGridlf(100,0.0, 10.0);
glEvalMeshl(GL_LINE, 0, 100);
После вызова glMapGridlf () устанавливается равномерная сетка в 100 отсчетов, а после вызова glEvalMeshl () будет сформирована кривая.
Поверхности Безье
Поверхности Безье формируются в OpenGL примерно по той же методике, что и кривые, только роль функции инициализации играет не glMapl*(), а д1Мар2*(), а для считывания результатов следует обращаться к функции glEvalCoord2* () вместо glEvalCoordl*(). В обеих функциях нужно специфицировать данные, относящиеся к двум независимым параметрам и и г. Например, функция glMap2f() имеет такой формат вызова:
glMap2f(type, u_min, u_max, u_stride, u_order, v_min, v_max, v_stride, v_order, point_array)
Настройка функции вычисления на работу с бикубической поверхностью Безье, определенной на области (0. 1)х(0, 1). выполняется таким вызовом glMap2f():
glMap2f(GL_MAP_VERTEX_3, 0.0, 1.0, 3, 4, 0.0, 1.0, 12, 4, data);
Для обоих независимых параметров нужно задать порядок полинома (аргументы u_order и v_order) и количество значений параметра между сегментами (аргументы u_stride и v_stride), что обеспечивает дополнительную гибкость при формировании поверхности. Обратите внимание на то, что значение v_stride для второго параметра равно 12, поскольку в массиве опорных точек data данные хранятся по строкам. Поэтому для перехода к следующему элементу той же строки нужно “перешагнуть” три числа в формате float, а для перехода к следующему элементу в этом же столбце нужно “перескочить” через 3x4=12 чисел в формате float. Способ вызова программы расчета зависит от того, какой результат мы хотим получить, — вывести на экран сеть или сформировать многоугольники для последующего раскрашивания. Если ставится задача сформировать на экране сеть, то соответствующий фрагмент программы должен выглядеть примерно так, как показано ниже:
for(3=0; j<100; j++) {
glBegin(GL_LINE_STRIP);
for(i=0; i<100; i++)
glEvalCoord2f( (float)i/100.0, (float)j/100.0);
glEnd();
glBegin(GL_LINE_STRIP);
for(i=0; i<100; i++)
glEvalCoord2f( (float)j/100.0, (float)i/100.0);
glEnd();
}
Если же желательно сформировать множество многоугольников, то фрагмент должен выглядеть так:
for(j=0; j<99; j++) {
glBegin(GL_QUAD_STRIP);
for(i=0; i<=100; i++)
{
glEvalCoord2f( (float)i/100.0, (float)j/100.0);
glEvalCoord2f( (float)(i+l)/100.0, (float)j/100.0);
}
glEnd();
}
Для работы на равномерной сетке параметров следует использовать функции glMapGrid2*() и glEvalMesh2(). Тогда в самое начало программы, в ту ее часть, которая отвечает за инициализацию, нужно включить такой фрагмент:
glMapGrid2f(100, 0.0, 1.0, 100, 0.0, 1.0);
В функции отображения display () нужно вызвать glEvalMesh2():
glEvalMesh2(GL_FILL,0,100,0,100);
Но существует еще одна проблема, на которую я хочу обратить ваше внимание. До тех пор, пока в программе не будет установлен режим учета освещения, сформированные многоугольники будут закрашены практически одинаково, и на экране на этом месте вы увидите темное пятно вместо выпуклой (или вогнутой) поверхности. Можно установить режим учета освещения способом, описанным в главе 6, но для работы алгоритмов тонирования необходима информация о направлении нормали в каждой обрабатываемой вершине. Эти нормали можно рассчитать и в прикладной программе, но лучше настроить OpenGL таким образом, чтобы система автоматически формировала их своими системными средствами. Для этого при настройке режима учета освещения нужно дополнительно вызвать функцию glEnable (), передав ей в качестве аргумента константу GL_AUTO_NORMAL:
glEnable(GL_AUTO_NORMAL);
Отображение чайника Юта
Для отображения на экране чайника Юта воспользуемся системными функциями обработки поверхностей Безье, имеющимися в составе библиотек OpenGL. Предположим, что информация о вершинах и порциях поверхности считана из файла в массив, объявленный следующим оператором:
GLfloat data[32][4][4];
Если планируется сформировать проволочную модель чайника, следует инициализировать сетку в пространстве параметров:
void myinit()
{
glEnable(GL_MAP2_VERTEX_3);
glMapGrid2f(20, 0.0, 1.0, 20, 0.0, 1.0);
Вычерчивание отрезков выполняется в функции отображения display (): for(k=0;k<32;k++) {
glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 12, 4,
&data[k][01(0][0]);
for (j=0; j <= 8; j++)
{
glBegin(GL_LINE_STRIP);
for (i=0; i <= 30; i++)
glEvalCoord2f((GLfloat)i/30.0, (GLfloat)j/8.0);
glEnd();
glBegin(GL_LINE_STRIP);
for (i=0; i <= 30; i++)
glEvalCoord2f((GLfloat)j/8.0, (GLfloat)i/30.0);
glEnd();
}
}
Обратите внимание на то. что для формирования на экране каждой порции поверхности нужно отдельно обращаться к функции glMap2f(). Если же планируется сформировать закрашенное изображение чайника, процедура инициализации будет выглядеть посложнее. В ней потребуется установить режим учета освещения и настроить свойства материала поверхности.
void myinit()
{
glEnable(GL_MAP2_VERTEX_3);
glEnable(GL_AUTO_NORMAL);
glMapGrid2f(8, 0.0, 1.0, 8, 0.0, 1.0);
glEnable(GL_DEPTH_TEST);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
glLightfv(GL_LIGHT0, GL_POSITION, position);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
Собственно отображение организуется двумя операторами в функции display (): for(k=0;k<32;k++) {
glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 12, 4,
&data[k)[0][0][0]);
glEvalMesh2(GL_FILL, 0,8,0,8);
}
Функции отображения NURBS-кривых и поверхностей
Функции вычисления полиномов можно использовать и на неравномерной сетке задания параметров при формировании четырехмерных кривых и поверхностей. Поскольку любой полиномиальный объект можно привести к форме Безье, подобрав подходящий ансамбль опорных точек, то в нашем распоряжении имеется полный арсенал средств, необходимых для определения NURBS-кривых и поверхностей. Причем в прикладной программе не нужно проходить через все этапы формирования таких объектов — в составе библиотеки утилит OpenGL, GLU имеется набор функций для работы с NURBS-объектами. Эти функции позволяют специфицировать в прикладной программе множество дополнительных параметров, настраивая которые прикладная программа может управлять режимом отображения объекта на экране. Для работы с NURBS- поверхностями имеется пять функций:
gluNewNurbsRenderer();
gluNurbsProperty();
gluBeginNurbsSurface();
gluNurbsSurface();
gluEndNurbsSurface();
Две первых функции формируют новый NURBS-объект и задают способ его отображения. Следующие три функции используются для формирования поверхности. При работе с NURBS-кривыми вместо них следует использовать gluBeginNurbsCurve(), gluNurbsCurve() и gluEndNurbsCurve().
В библиотеке GLU есть функции, которые позволяют выполнять операцию выреза
ния отверстий криволинейной формы вния отверстий криволинейной формы вNURBS-поверхностьNURBS-поверхностьРис. 10.40. NURBS-поверхность с прорезанными отверстиямиРис. 10.40. NURBS-поверхность с прорезанными отверстиямисформированной поверхности. "Вырезающие кривые” (Trimming curves) представляют собой замкнутые криволинейные контуры отверстий (рис. 10.40). В прикладной программе вырезающая NURBS-кривая формируется с помощью функции gluNurbsCurve(). Можно сформировать и кусочно-линейный контур отверстия с помощью функции gluPwlCurve(). Контуры отверстий формируются после вызова glNurbsSurface(), причем соответствую
ния отверстий криволинейной формы в
ния отверстий криволинейной формы в
NURBS-поверхность
NURBS-поверхность
Рис. 10.40. NURBS-поверхность с прорезанными отверстиями
Рис. 10.40. NURBS-поверхность с прорезанными отверстиями
щие вершины задаются в операторных скобках — между вызовами функций glBeginTrim() иglEndTrim().
Квадратичные поверхности
В составе библиотеки GLU OpenGL имеются функции формирования и отображения нескольких типов распространенных квадратичных поверхностей — дисков, цилиндров и сфер. В прикладной программе такие объекты могут обрабатываться так же, как и все прочие при-
митивы, — можно масштабировать, сдвигать, поворачивать, закрашивать их и накладывать на них текстуру. Функции построения квадратичных поверхностей автоматически формируют нормали и координаты текстуры. В OpenGL выполняется их аппроксимация многоугольниками, причем в прикладной программе задается, сколько многоугольников должно быть использовано для аппроксимации.
Предположим, что в изображении робота, которое мы рассматривали в главе 8, основание робота задумано сделать в форме цилиндра. Для этого сначала определим в программе новый квадратичный объект:
GLUquadricObj *р;
p=gluNewQuadric();
После этого в программе требуется задать параметры, определяющие вид объекта на экране. Если, например, планируется выводить проволочное изображение и текущий цвет, то следует вызвать функцию
gluQuadricDrawStyle(р, GLU_LINE);
Далее можно сформировать цилиндр, центр которого находится в начале координат, а образующая направлена вдоль оси у:
gluCylinder(р, BASE_RADIUS, BASE_RADIUS, BASE_HEIGHT, 5, 5);
Первые три аргумента задают радиусы нижнего и верхнего оснований и высоту (т.е. фактически формируется не цилиндр, а усеченный конус частного вида). Два последних параметра задают количество разбиений каждой окружности цилиндра (т.е. сечения при фиксированном значении координаты j) и количество слоев при разбиении по образующей в направлении оси дл
Резюме
Конечно, в этом учебном курсе мы поневоле были вынуждены обойтись крайне поверхностным изложением обширной темы использования кривых и поверхностей в системах компьютерной графики. Основное внимание было уделено тем математическим методам, которые реализованы в составе функций API типовой системы, в частности OpenGL. С этой точки зрения наиболее привлекательными выглядят криволинейные объекты в форме Безье. Алгоритм рекурсивного разбиения кривых и поверхностей в форме Безье позволяет организовать в прикладной программе их отображение с любой степенью точности.
Было также показано, что весьма интересные результаты можно получить в приложении, комбинируя методику интерактивного построения кривой в форме В-сплайна с методами ее отображения в форме Безье. В работах, перечисленных в следующем разделе, вы встретите множество вариаций сплайнов, применяемых в системах автоматизации проектирования объектов разных типов.
Квадратичные поверхности очень хорошо сочетаются с алгоритмами трассировки лучей, поскольку определение точки пересечения луча с поверхностью требует решения только квадратного уравнения относительно скалярной неизвестной. Анализ, лежит ли точка пересечения луча с плоскостью многоугольника, внутри этого многоугольника может потребовать больше операций, чем вычисление точки пересечения луча с квадратичной поверхностью. Поэтому во многих системах, реализующих алгоритм трассировки лучей, в набор объектов включаются только бесконечная плоскость, квадратичные поверхности и иногда выпуклые многоугольники.
Рекомендуемая литература
Прекрасное введение в геометрию кривых и поверхностей читатель найдет в книге Фарина (Farin) [Far88]. Введение к этой книге написано Безье, и в нем рассказано о том, как он и де Кастельо (de Casteljau) почти одновременно изобрели форму представления поверхностей, которая увековечила затем его имя. К сожалению, исследование де Кастельо не было опубликовано в доступной всем печати (это был внутренний отчет), и потому его имя очень мало известно в научных кругах. Самые разнообразные варианты сплайнов рассматриваются в книгах Роджерса (Rogers) [Rog90], Фоли (Foley) [Fol90], Бартельса (Bartels) [Ваг87] и Уатта (Watt) [Wat93].
В книге Фокса (Faux) [Fau80] обсуждается подход к построению кривых и поверхностей, не зависящий от частного вида системы координат.
Хотя книга, вышедшая под редакцией Гласснера (Glassner), [Gla89], в первую очередь касается проблематики трассировки лучей, помещенный в ней раздел, написанный Хейнесом (Haines), содержит очень интересный материал, касающийся работы с квадратичными и другими алгебраическими поверхностями.
Упражнения
Рассмотрим уравнение алгебраической поверхности Дх, у, г)=0, в котором каждый член может содержать х, у и : в степени не выше т. Каково максимально возможное количество членов в функции/?
Рассмотрим уравнения в явном виде )'=Дт) и -=g(x). Кривую какого типа они описывают?
Пусть имеется полином p(w) = ^qw*. Найдите другой полином q(1v) = ^ldkvl, такой, <*о<=о
что для каждой точки р^ в интервале (а, Ь) значения параметра и существовала бы равная ей точка qG) в интервале 0 < т < 1.
Покажите, что если каждой из четырех опорных точек кубической интерполяционной кривой соответствуют отличающиеся значения параметра и, то базисная интерполяционная матрица существует.
Покажите, что полиномы Бернштейна на интервале (0, 1) должны иметь значения меньше 1.
Покажите, что кубический сплайн имеет класс непрерывности С2.
В разделе 10.9 было показано, что кубическую полиномиальную кривую можно представить в форме Безье, подобрав соответствующим образом опорные точки, или, что то же самое, подобрав форму выпуклой оболочки. Покажите, как с учетом этого факта организовать вычисление промежуточных точек интерполяционной кубической кривой с помощью средств работы с кривыми Безье, которые имеются в составе OpenGL.
Покажите, как представить квадратичную поверхность в однородных координатах.
Предположим, что для формирования на экране изображения порции поверхности Безье используется алгоритм адаптивного рекурсивного разбиения. При этом каждая окончательная порция имеет разную кратность разбиения. Соблюдается ли при этом непрерывность кривых, ограничивающих стыкующиеся порции? Поясните свой ответ.
Разработайте OpenGL-программу, которая будет принимать от пользователя массив опорных точек и формировать на его основе интерполяционную кривую, В-сплаЙн и кривую Безье.
Предположим, что траектория перемещения некоторого персонажа анимационной программы описывается сплайном, причем в качестве параметра фигурирует время. Как скажется на характере движения персонажа изменение класса непрерывности траектории с G1 на С1?
Доработайте программу рисования, которая рассматривалась в главе 3, и включите в нее возможность построения кривых Безье. Программа должна предоставлять пользователю возможность настраивать положение опорных точек в интерактивном режиме.
Разработайте простой тест степени кривизны порции поверхности Безье.
Сформируйте открытый рациональный квадратичный В-сплайн с узлами {0, 0, 0, 0, 1, 1, 1, 1} и весами w0=w2=l и м’^м'.
Используя результаты упр. 10.14, покажите, что если и'=г/(1-г), 0 < г < 1, то получится кривая конического сечения.
Подсказка. Рассмотрите варианты /-<1/2 и г>1/2.
Найдите нули функций смешивания Эрмита. Почему из характера размещения нулей следует, что кривая Эрмита достаточно гладкая на интервале (0, 1)?
463463Какое отношение существует между опорными точками для порции поверхности Эрмита и производными в угловых точках порции?
463
463
Упражнения
ГЛАВА
Процедурные методы
Д
о сих пор мы рассматривали геометрические объекты, которые описывались своими поверхностями, причем эти поверхности, как правило, моделировались (или аппроксимировались) множеством плоских выпуклых многоугольников. Использование полигональных объектов в качестве основных “кирпичиков” диктуется в первую очередь тем, что современные графические системы эффективнее всего работают именно с ними. Успешное применение компьютерной графики для создания приложений самого разного назначения подтверждает целесообразность выбранной модели.
Но, тем не менее, хотя полигональная модель успешно справляется с формированием изображения даже в таких сложных приложениях, как системы автоматизации проектирования и симуляторы окружающей обстановки в разного рода динамических тренажерах, и пользователям, и разработчикам давно известно, что ее возможности отнюдь не безграничны. Нельзя с помощью полигональной модели представить такие физические объекты, как облака, дым или жидкую среду. Невозможно добавить в полигональную модель физические ограничения и воспроизведение сложного поведения объектов. Исследование методов решения подобного рода задач развивается в рамках другого направления компьютерной графики, получившего название процедурного .моделирования (procedural models). Отличительная черта этого направления — использование алгоритмических моделей для представления физических явлений, лежащих в основе задачи. При этом многоугольники формируются только в том случае, если в них возникает необходимость на стадии вывода изображения.
Особенности процедурных моделей
Если внимательно взглянуть на историю развития компьютерной графики, то видно, что на протяжении всего периода существования этого направления стремление получить более качественное изображение опережало возможности имеющихся аппаратных средств. Хотя на сегодняшнем оборудовании общего применения, доступном практически любому пользователю, можно обеспечить скорость формирования многоугольников до 1 миллиона в секунду, появились приложения, о которых раньше можно было прочитать только в фантастических романах, — симуляторы окружающей обстановки или приложения, имеющие дело с виртуальной реальностью, которые требуют воспроизведения свыше 100 миллионов многоугольников в секунду. Более того, по мере повышения скорости работы аппаратуры отображения
возрастает и объем соответствующей базы данных. В новейших приложениях он может превышать 1 миллиард многоугольников.
Однако в некоторых приложениях такие непомерные требования являются следствием использования существующих графических систем общего назначения, в которых реализована совершенно неподходящая для этих приложений парадигма моделирования и организации программного обеспечения. Исследователи и программисты, которые пошли нетрадиционным путем, не без основания предположили, что нет необходимости формировать так много элементарных многоугольников в описании изображения — достаточно ограничиться теми, которые, во-первых, видимы, а во-вторых, проектируются на область, не меньшую одного пикселя. Процедурные модели охватывают широкий спектр приложений. Эта модель предполагает алгоритмическое описание объектов, а многоугольники формируются только в том случае, если они необходимы для реализации процесса отображения.
В чем-то процедурные методы напоминают способы представления в компьютерах иррациональных чисел — корней квадратных, синусов, косинусов и т.п. Рассмотрим, например, три способа представления числа V2 . Его можно представить в виде константы
V2 =1.414... ,
ограничившись возможностями разрядной сетки компьютера. Есть и другой вариант, более абстрактный: считать, что V2 — это положительное число х, такое, что х2=2.
Но в компьютерах, как правило, V2 представляется как результат выполнения некоторого алгоритма, например расчета по методу Ньютона:
2
при начальном значении х0=1. С этой точки зрения иррациональное число V2 определено не своим значением, а алгоритмом расчета, т.е. определено программно. Аналогичный подход можно применить и в отношении некоторых объектов, с которыми приходится иметь дело в графических приложениях. Например, сферу, центр которой находится в начале координат, можно определить как математический объект, удовлетворяющий уравнению
х2+у2+?=г2.
Сферу можно определить и как результат выполнения алгоритма рекурсивного разбиения тетраэдра, который мы рассматривали в главе 6, или как результат соответствующей программы. Потенциальное достоинство такого подхода состоит в том, что изображение сферы будет содержать разное количество многоугольников, в зависимости от того, в каком масштабе она будет представлена на экране.
Есть и еще одна проблема, суть которой состоит в том, что полигональное представление объектов в системе компьютерной графики очень сложно увязать с физическими законами, моделируемыми прикладной программой. Хотя и можно построить и “оживить” полигональную модель какого-либо реального объекта, но значительно сложнее показать, что этот объект является сплошным, а следовательно, два таких объекта при столкновении не могут проникнуть друг в друга.
В этой главе мы рассмотрим два подхода к процедурному моделированию. Сначала рассмотрим систему частиц, подчиняющихся законам Ньютона. Будет показано, как можно смоделировать сложное поведение таких частиц, пользуясь математическим аппаратом решения дифференциальных уравнений. Вычисленное положение частицы будет использовано в графической системе в качестве информации для размещения стандартного геометрического объекта в пространстве отображаемой сцены.
Второй подход— алгоритмические модели — предполагает замену полигональной модели языковой, описательной моделью, подобно тому, как это делается в обычных языках и
языках программирования. Модели такого типа позволяют приближенно представить многие типы естественных объектов, опираясь на небольшое множество правил, которые предписывают формирование соответствующих графических объектов. В комбинации с геометрией фракталов такие модели обеспечивают формирование адекватного изображения, используя при этом минимально необходимое количество многоугольников.
Физические модели и система частиц
Одно из главных достоинств (а может, главных недостатков, это смотря откуда смотреть) технологии моделирования в компьютерной графике состоит в том, что модель можно формировать на основе любых принципов. Тот графический объект, который по заданию прикладной программы формирует графическая система, может не иметь никакого отношения к объектам реального мира. Эта гибкость позволяет математикам просматривать такие формы поверхностей, которые вы не встретите ни в каком, пусть даже трижды “затерянном” мире, инженерам — проектировать машины, которые невозможно создать из существующих материалов. Но если понадобится смоделировать поведение такого виртуального объекта в среде, подчиняющейся определенным физическим законам, и просмотреть поведение модели на экране ЭЛТ, то окажется, что эта задача на порядок сложнее задачи формирования объектов. Несложно заставить изображения нескольких объектов двигаться в пространстве сцены, но значительно труднее уследить за тем, не столкнулись ли при этом объекты, и отреагировать на это событие корректно с точки зрения законов физики. Для системы компьютерной графики ничего не стоит “запустить” мяч через закрытое окно, а вот показать, как стекло разлетелось от удара мячом, — задача на пару порядков сложнее. Даже воспроизвести отскок мяча от обычной стены и то не так просто.
В последнее время множество исследований в области компьютерной графики посвящается моделям, основанным на физических законах, т.е. моделям, в которых графические объекты подчиняются определенным, объективно существующим физическим законам. В рамках этого направления можно выделить два подхода. Первый во главу угла ставит физическую модель процесса, исследуемого или воспроизводимого в приложении, а результаты физического моделирования используются для управления графическими объектами. Например, если планируется “запустить" объект в среду, в которой есть множество других объектов, пусть и неподвижных, и показать, как будет двигаться этот объект, отскакивая от других, то, по крайней мере в принципе, можно воспользоваться аппаратом исследования динамики поведения объектов, имеющимся в теоретической механике, составить соответствующие уравнения и решить их. Но, к сожалению, этот подход слишком сложен для тех, кто только осваивает премудрости компьютерной графики, а потому мы не будем его рассматривать на страницах этой книги.
Другой подход состоит в том, чтобы комбинировать базовые физические законы с математическими ограничениями и таким образом моделировать динамику поведения объектов. Именно этому подходу мы и последуем, рассматривая поведение группы материальных частиц.
Система материальных частиц включает множество частиц, как правило, точечных, т.е. вся масса которых сосредоточена в точке, характеризующей текущее положение частицы. Динамика поведения частиц описывается системой дифференциальных уравнений. Системы таких идеальных частиц часто используются в качестве объекта исследования в самых разных областях. Например, в динамике жидкостей с помощью системы материальных частиц исследуется турбулентность потока. Вместо того чтобы решать уравнения в частных производных, можно смоделировать динамику системы, отслеживая поведение группы частиц, на которых действуют определенные силы и которые подчиняются определенным ограничениям.
Идеальные материальные частицы можно использовать и для моделирования явлений в физике твердого тела. В частности, при исследовании деформаций используется представление объекта в виде системы материальных частиц, связанных упругими связями. Под воздей-
ствием внешней силы частицы могут смещаться, и их распределение воспроизведет форму деформированного тела.
В графических приложениях модель поведения материальных частиц используется при исследовании таких явлений, как распространение огня и волн, поведение птичьих стай в полете. В каждом из такого рода приложений модель дает положение частиц в динамике, а графическая система может отображать их не только точками, но и объектами более сложной и наглядной формы.
В любом случае мы работаем с группой частиц, вся масса которых сосредоточена в точке. Поведение такой системы частиц описывается уравнениями, которые решаются численными методами, и в результате с определенным тактом на временной шкале мы получаем информацию о положении всех частиц. А далее уже дело фантазии разработчика программы, как представить частицу на экране — то ли в виде точки определенного цвета, то ли в виде какого-либо трафарета или даже объемной фигуры (например, маленьких колпачков — конусов).
Ньютоновы частицы
Рассмотрим множество частиц, подчиняющихся законам Ньютона. Мы остановились именно на этом классе физических объектов, поскольку, анализируя их поведение, можно получить интересные результаты на основании простых физических законов, известных каждому со школьной скамьи. Ньютоновы частицы подчиняются второму закону Ньютона, утверждающему, что произведение массы частицы (т) на ее ускорение (а) равно сумме всех сил (f), действующих на частицу:
та = С.
Обращаю ваше внимание на то, что ускорение и сила являются векторами, как правило, трехмерными. Из второго закона Ньютона следует, что текущее состояние идеальной частицы с точечной .массой (частица, вся масса которой сосредоточена в точке) полностью характеризуется ее положением и скоростью. Следовательно, в трехмерном пространстве идеальная частица имеет шесть степеней свободы, а система из п частиц имеет 6п переменных состояния — положение и скорости всех частиц системы. В некоторой системе координат состояние /-й частицы описывается двумя трехмерными матрицами-столбцами: матрицей положения
и матрицей скорости dx
Гл,1 * . dv ^' — V, = — . ' V dt
-"'-I ck dt
Учитывая, что ускорение— это производная функции скорости, а скорость— производная функции положения, можно выразить второй закон Ньютона для материальной частицы в виде системы из шести дифференциальных уравнений первого порядка:
Р, =v„
V =— f,(r).
Следовательно, динамика системы из и материальных частиц с точечной массой описывается системой би обычных дифференциальных уравнений.
Помимо текущего состояния, каждая частица характеризуется набором атрибутов, к которым относятся ее масса т, и набор свойств, способные нести информацию о поведении частицы и способах ее визуализации. Например, некоторые атрибуты задают цвет частицы при отображении ее на экране, форму ее образа и свойства поверхности этого образа. Обращаю ваше внимание на то, что хотя в физической модели частица есть идеальный объект нулевого размера, на экране ее образ может иметь вполне различимые размеры и форму, причем и то и другое задается в прикладной программе. Например, в одном приложении частицами моделируются отдельные личности в толпе, в другом — молекулы, участвующие в процессе химического синтеза, а в третьем — кусочки ткани, образующие в совокупности развевающееся на ветру полотнище. В любом случае физическая модель системы частиц описывает положение, скорость и ускорение каждой из них, а располагая этой информацией, можно соответствующим образом представить моделируемый объект на экране.
Множество сил, действующих на частицы, {f,}, определяет поведение системы. Эти силы могут порождаться взаимодействием частиц, которое, в свою очередь, зависит от их текущего состояния, а значит, изменяется во времени. В модель можно включить основные физические явления, которые приводят к возникновению этих сил, например упругость пружины, или некоторые физические ограничения. Можно также моделировать в системе и внешние силы, например воздействие поля гравитации. Тщательно продумав модель сил, действующих на систему, можно воспроизвести в ней те или иные варианты развития событий.
Состояние системы в динамике вычисляется численными методами решения дифференциальных уравнений, хорошо известными специалистам. При этом весь интервал наблюдения разбивается на такты, и на каждом такте вычисляются силы, действующие на п частиц (силы задаются функциями, определенными пользователем), и новое состояние частиц, которое является следствием приложения этих сил. Последнее как раз и вычисляется методами численного интегрирования дифференциальных уравнений. Конечная процедура каждого цикла — отображение нового состояния частиц на экране с учетом атрибутов соответствующих графических объектов. Следовательно, фрагмент программы на псевдокоде, реализующей один цикл вычислений, будет иметь примерно такой вид:
float time, delta;
float state[6n], force[3n); state=get_initial_state();
for(time=tO; time<time_final; time+=delta) {
/* Вычисление сил */
force=force_function(state, time);
/* Стандартная процедура интегрирования системы дифференциальных уравнений */ state=ode(force, state, time, delta);
/* Отображение результатов */ render(state, time);
}
Основной компонент программы, на который программист тратит больше всего сил при разработке конкретного приложения, — функции вычисления сил, действующих на частицы.
Несвязанные частицы
Частицы могут взаимодействовать как друг с другом в рамках системы, так и с окружающим систему миром, причем каждый вид взаимодействия порождает свои силы, воздействующие на элементы системы. Если силы, действующие на определенную частицу, не зависят от других частиц системы, то силу, приложенную к z-й частице, можно представить в виде уравнения
f,= f,(P„ V,).
Самый простой случай — воздействие на каждую частицу только постоянной силы гравитации:
f,= g
Если сила гравитации направлена вниз, то
О
g = "8
О
где g— положительное число, а каждая частица движется по дуге параболы. Если в уравнение добавить член, пропорциональный скорости, то в модель будут включены силы трения. Если в модели менять на протяжении “жизненного цикла"’ каждой частицы один из ее атрибутов, например цвет, причем протяженность жизненного цикла выбирать случайно, получим на экране имитатор пламени. Рассуждая с более общей точки зрения, внешние силы в системе независимых частиц прикладываются к каждой из них независимо от прочих. Если позволить частицам медленно двигаться в некотором направлении со случайными вариациями, причем представлять частицу на экране графическим объектом достаточно внушительных, но также случайно выбранных размеров, то получим графическую модель группы облаков, медленно плывущих по экрану.
Силы упругости
При моделировании системы из п независимых частиц оценка объема вычислений, необходимых для определения сил, имеет вид О^п\ В более общем случае, когда нужно учитывать взаимное влияние частиц друг на друга, эта оценка возрастает и имеет вид O(n'Y При доста-
Рис. 11.1. Сеть частицРис. 11.1. Сеть частицточно большом п этот процесс может серьезно замедлить работу программы. Но в большинстве практических приложений область взаимовлияния можно свести к ограниченной окрестности частицы, т.е. принимать во внимание только ближайшие частицы.
Рис. 11.1. Сеть частиц
Рис. 11.1. Сеть частиц
Рассмотрим пример моделирования с помощью системы частиц изменения во времени формы поверхности куска ткани (полотнища, развевающегося на ветру, или занавеса). Положение каждой частицы системы можно рассматривать как вершину (узел) не
которой сети из четырехугольных ячеек (рис. 11.1). Форма сети изменяется во времени как из-за действия внешних сил (силы ветра или силы тяжести), так и вследствие сил, стремящихся сохранить расстояние между соседними частицами. Приближенно их можно представить как силы, действующие только на ближайших соседей каждой конкретной части
цы. Так, если р;/ — положение частицы, находящейся на пересечении z-й строки и у-го
столбца сети, то вычисление сил, действующих на частицу ру, потребует учета сил, связывающих ее с частицами, находящимися в узлах р,,, ,, р,_( ^ р,/+), р/7-|. Таким образом, оценка объема вычислений будет линейной — О(п\
Один из наиболее простых, а потому и распространенных методов моделирования взаимного влияния частиц в подобных приложениях — включение между соседними частицами упругих “пружин”. Рассмотрим две соседние частицы, расположенные в точках р и q и связанные пружиной (рис. 11.2).
Обозначим силу, с которой частица q действует на р, через
f. Частица р при этом действует на частицу q с силой -f. Когда qe• Р
пружина не деформирована, ее длина равна s. В отсутствие
внешних сил именно на таком расстоянии будут находиться Рис. 11.2. Частицы, связаннее частицы по истечении достаточно продолжительного пе- ные пружиной риода времени. Если пружина растягивается, то сила упруго
сти действует в направлении d=p-q, т.е. вдоль прямой, соединяющей точки, в которых находятся частицы. Эта сила подчиняется закону Гука (Hook's law):
Рис. 11.3. Вычисление силы демпфирования пружиныРис. 11.3. Вычисление силы демпфирования пружиныгде k, — постоянная, характеризующая упругость пружины. Из этого закона следует, что чем сильнее растягивается пружина, т.е. чем дальше друг от друга отстоят частицы, тем большая сила взаимного притяжения действует на них. И наоборот, если сжимать пружину, появляется сила, стремящаяся оттолкнуть частицы друг от друга. Но в формулировке закона Гука не учитывается фактор демпфирования (или трения) в системе. Система, не обладающая демпфированием, будет колебаться неограниченное время после выхода из состояния покоя под действием внешних сил. Поэтому в закон Гука нужно включить демпфирующий член — “успокоитель”. Сила демпфирования действует в том же направлении, что и сила упругости, но ее величина зависит от скорости взаимного движения частиц. Коэффициент пропорциональности между скоростью и силой демпфирования зависит от величины проекции вектора скорости на вектор, связы
Рис. 11.3. Вычисление силы демпфирования пружины
Рис. 11.3. Вычисление силы демпфирования пружины
вающий текущие положения частиц (рис. 11.3). Математически динамический закон Гука формулируется следующим образом:
7 “ d d
где kd — постоянная демпфирования, a
d=p-q.
Описанная таким уравнением система точечных масс и пружин, выведенная однажды из состояния равновесия, со временем вновь вернется в него.
Четыре изображения, представленные на ил. 16 цветной вклейки, показывают, как с помощью сетки из точечных масс и пружин моделируется воздействие ветра на кузов автомобиля. После вычисления положения узлов сетки на каждом шаге моделирования на получившуюся поверхность накладывается текстура. При этом используются методы, описанные в главе 9.
Силы взаимного притяжения и отталкивания
В то время как силы упругости стремятся сохранить устойчивое (недеформированное) состояние системы частиц, силы взаимного притяжения стремятся собрать все частицы вместе в одной точке, а силы взаимного отталкивания, наоборот, — рассеять по всему свету. В модели силы взаимного отталкивания можно использовать для распределения частиц по некоторой поверхности или, если частицы представляют в физической модели некоторый объемный объект, удерживать такие объекты от столкновений. Без учета сил взаимного притяжения не обойтись в модели солнечной системы, как, впрочем, и в любой другой, описывающей поведение объектов в космосе. Уравнения для сил притяжения и отталкивания, по сути, одни и те же, исключая только знак.
Для пары частиц, размещенных в точках р и q, сила отталкивания действует в направлении d=p-q и обратно пропорциональна расстоянию между частицами. Математически это выглядит следующим образом:
Рис. 11.4. Разделение пространства .модели на ячейкиРис. 11.4. Разделение пространства .модели на ячейки
Рис. 11.4. Разделение пространства .модели на ячейки
Рис. 11.4. Разделение пространства .модели на ячейки
Изменив знак выражения и заменив коэффициент кг отношением (mam^lg, получим уравнение для силы притяжения, действующей между двумя частицами массой ти и mh. В этом уравнении g— гравитационная постоянная.
В общем случае, когда на каждую частицу действуют силы притяжения от всех других (/7-1) частиц системы, оценка объема вычислений сил притяжения и отталкивания имеет вид О(п"\ Модель, состоящая из сети частиц, связанных силами упругости, воспроизводит поведение объектов, которые стремятся сохранить свою топологию и взаимное положение компонентов. Модель, в которой главенствующее положение занимают частицы, находящиеся под воздействием сил притяжения и отталкивания, характеризуется хаотичным изменением взаимного положения компонентов в системе. Поэтому снизить объем вычислений в такой модели, уйти от оценки вида О(и") очень сложно. Один из подходов состоит в кластеризации пространства модели — разделения его на отдельные трехмерные ячейки, каждая из которых может содержать сколь угодно много частиц, а может не содержать и
Размеры ячеек выбираются с учетом характера взаимодействия — обратной пропорциональности между величиной силы и квадратом расстояния. При оптимально выбранных размерах ячеек для вычисления сил притяжения и отталкивания, которые действуют на частицу, находящуюся в некоторой ячейке, достаточно анализировать только частицы, находящиеся в этой же ячейке и в соседних с ней. Если такая кластеризация пространства модели допустима, оценка сложности вычислений будет иметь вид <Хп\ а не О^п‘\ Но, естественно, кластеризация требует накладных расходов. Частицы могут переходить из одной ячейки в другую, а потому время от времени необходимо выполнять их сортировку. Сложность состоит в том, что приходится изобретать подходящую структуру данных для
описания ячеек и хранения информации о частицах.
Другой подход состоит в том, что взаимодействие частиц друг с другом заменяется учетом взаимодействия частица и силового поля. Например, если необходимо определить силу гравитации, действующую на частицу, находящуюся на поверхности Земли, используется значение гравитационного поля Земли, а не закон притяжения точечных масс, открытый Ньютоном. Правда, положение усложняется, если нужно принимать во внимание еще и влияние Луны.
Решение системы уравнений
Вернемся вновь к системе уравнений, описывающих состояние множества из п материальных частиц с точечной массой. Если ограничиться учетом только самых главных силовых факторов, воздействующих на такую систему, то ее состояние описывается системой из би обычных дифференциальных уравнений в виде
u = g(u.z),
где и представляет массив из би компонентов позиций и скоростей и частиц, a g включает все внешние силы, действующие на частицы. Следовательно, если имеются две частицы а и Ь, связанные силами упругости без демпфирования, то получим
uf=[w0 их Из и3 и4 и5 и6 и7 «8 Wg и10 ии]
= £аЛ а> а. ах dx a. bx bx b. bx bx b. J
При заданных внешних силах g и состоянии системы частиц и в момент времени / можно вычислить
gr=^ и3 и4 и5 ~kdx -kdx -kd. и9 и|0 uH kdx kdx kd. J.
Здесь к — постоянная упругости пружины, a d^ dv, d. — компоненты нормированного вектора между а и Ь. Следовательно, сначала необходимо вычислить
«о -«6
Программа решения обычных дифференциальных уравнений требует, чтобы мы вычисляли функцию g для экстраполяции состояния и на будущее. Можно разработать набор таких программ, основываясь на теореме Тейлора. Простейший метод этой группы известен как .метод Эйлера. Предположим, что интегрируется выражение
u = g(u,z),
на коротком промежутке времени А:
| ш7т = и(/ + A)-u(z) = | g(u,x)Jx.
Если h мало, можно заменить значение g на интервале [/, Z+A] значением g в момент Z; следовательно,
u(z+A) = u(z)+Ag(u(z), Z).
Из этого выражения следует, что для приближенного вычисления значения u(Z+A) используется значение производной в момент времени / (рис. 11.5).
Это выражение учитывает два первых члена разложения функции в ряд Тейлора; мы можем записать его в виде
u(z + A) = u(z) +Au(z) + o(A2) = u(z) +Ag(u(z),z) + o(A2).
Из этого выражения видно, что ошибка аппроксимации пропорциональна квадрату шага интегрирования.
Реализовать этот метод не представляет особого труда. Нужно вычислить значения сил (как внешних, так и сил взаимного влияния частиц системы друг на друга) в момент времени Z, вычислить массив обобщенных сил g, умножить его на А и добавить к массиву теку-
щего состояния. По этому методу в итерационном цикле вычисляются состояния системы и в последующие моменты времени /+2Л, /+3/?,... . Основные вычисления — определение массива обобщенных сил в каждый момент времени.
Рис. 11.5. Приближенное решение дифференциального уравнения по методу Эйлера
Применение метода Эйлера требует тщательного выбора шага интегрирования, поскольку от него зависит точность результата и устойчивость итерационной вычислительной процедуры. Точность вычисления пропорциональна квадрату шага интегрирования. Следовательно, для повышения точности нужно уменьшать шаг, что увеличивает количество циклов итерации и соответственно время решения. Но еще более серьезные опасения вызывает проблема устойчивости вычислительной процедуры. При переходе к очередному циклу у нас появляются ошибки, источниками которых являются, во-первых, использование для приближения только первых членов разложения в ряд Тейлора, а во-вторых, ограниченная разрядность компьютера. Эти ошибки могут либо компенсировать друг друга, либо, наоборот, суммироваться и, следовательно, накапливаться. Такое поведение вычислительного алгоритма принято называть вычислительной неустойчивостью. К счастью, в большинстве задач, связанных с анализом состояния системы материальных точек, используются достаточно простые модели сил, а потому при правильном выборе шага интегрирования неустойчивость не отмечается. Потенциально наиболее подверженной вычислительной нестабильности оказывается модель, в которой учитываются силы упругого взаимодействия. Дело в том, что при определенных значениях коэффициентов жесткости, входящих в уравнения Гука, даже точное решение имеет колебательный характер, а потому наложение на него ошибок численного интегрирования делает вычислительный алгоритм склонным к “самовозбуждению”.
Существуют два подхода к решению проблемы вычислительной неустойчивости. Один состоит в том, чтобы отказаться от использования метода Эйлера. Анализ таких методов выходит за рамки нашего курса, и мы отсылаем читателей к специальной литературе по численным методам. Другой подход состоит в том, чтобы сохранить в общих чертах метод Эйлера, но внести в него некоторые изменения, уменьшающие накопление ошибок при переходе к очередному циклу. Мы кратко остановимся на одном из методов такой модификации, который даст читателем общее представление о целом семействе подобных методов.
Предположим, что, как и раньше, мы начинаем с интегрирования дифференциальных уравнений на коротком интервале, чтобы получить
u(/ + 6) = u(/) + |, g(u,x)Jx.
Но на этот раз будем использовать при интегрировании усредненное значение на интервале [/, t+hy
p''g(u,T)dT = ^(g(u(z),z) + g(u(z + /z)^^.
Теперь появляется другая проблема — в нашем распоряжении нет значения g(u(Z+/z), t+h), мы располагаем только значением g(u(Z), z). Для приближенного вычисления g(u(Z+A), Z+Л) воспользуемся все тем же методом Эйлера:
g(u(z+/z), Z+A) = g(u(z)+/zg(u(Z), /), t+h).
Этот метод известен под названием усовершенствованного .метода Эйлера {improved Euler method) или метода Рунге-Кутта второго порядка {Runge-Kutta method of order 2). Обратите внимание на то, что в одном и том же цикле (при переходе от t к t+h) значение g нужно вычислять дважды. Однако, если проанализировать ошибку согласно теореме Тейлора, то окажется, что она имеет порядок O{h3). Следовательно, хотя на каждом шаге приходится делать больше вычислений, размер шага можно увеличить (а количество циклов соответственно уменьшить) и в итоге даже добиться более высокой производительности. Можно и далее повышать порядок формул Рунге-Кутта, увеличивая тем самым точность вычислений на каждом шаге. Наиболее распространенным в настоящее время для решения обычных дифференциальных уравнений является метод Рунге-Кутта четвертого порядка, который предполагает четырехкратное уточнение значения g в течение одного шага интегрирования, но обеспечивает при этом точность порядка O{h4). В последнее время разработаны методы этой же группы, которые автоматически подстраивают интервал интегрирования в процессе решения таким образом, чтобы обеспечивалась устойчивость итерационного процесса.
Ограничения
В некоторых приложениях недостаточно сформировать множество частиц, задать их исходное состояние, смоделировать внешнее воздействие и затем смотреть, как ведет себя система “в свободном полете”. Особенно это касается приложений, в которых отдельные объекты могут сталкиваться. Хотя поведение объекта, который отскакивает от стены, также подчиняется законам Ньютона, построить систему моделирования соударений на таком принципе не удается (по крайней мере на существующих аппаратных средствах общего назначения) из-за большого количества необходимых вычислений. Вместо этого гораздо эффективнее ввести в модель ряд ограничений на состояния объектов, например запретить объектам “проникать” друг в друга. Эти ограничения хранятся и обрабатываются отдельно от общих правил поведения частиц, описываемых дифференциальными уравнениями.
Применительно к элементам системы частиц можно выделить две группы ограничений. Жесткие ограничения {hard constraints) должны соблюдаться в обязательном порядке. Например, мяч должен обязательно отскочить от стены. Ни при каких обстоятельствах он не может “пройти сквозь стену” и выскочить с другой стороны. Мягкие ограничения {soft constraints) не являются обязательными — их соблюдение скорее желательно. Например, можно пожелать, чтобы частицы по возможности не приближались друг к другу на расстояние, меньшее заданного, как это реализуется в сетке частиц.
Столкновения
Хотя жесткие ограничения общего вида довольно сложно встроить в физическую модель, существуют отдельные классы ограничений, которые можно реализовать на уровне модели системы точечных масс. К таковым, в частности, относятся ограничения, связанные со столкновениями. Задача моделирования столкновений распадается на две подзадачи: обнаружение столкновения и реакция на столкновение. Предположим, что в системе частиц с точечной массой между частицами действуют силы отталкивания, а потому частицы друг с дру-
гом не сталкиваются. Возможно только столкновение частиц с внешними геометрическими объектами. Если в системе имеется п частиц и система погружена в среду из т многоугольников, то на каждом шаге расчета состояния системы нужно проверять возможность столкновения любой частицы с любым многоугольником.
Рис. 11.6. Частица, “пронзившая”Рис. 11.6. Частица, “пронзившая”
Рис. 11.6. Частица, “пронзившая”
Рис. 11.6. Частица, “пронзившая”
ппРис. 11.8. Положение частицы после столкновенияРис. 11.8. Положение частицы после столкновения
п
п
Рис. 11.8. Положение частицы после столкновения
Рис. 11.8. Положение частицы после столкновения
многоугольник
Пусть какая-либо частица пронизала многоугольник, как показано на рис. 11.6. Обнаружить сам факт столкновения частицы с многоугольником можно, подставив координаты положения частицы в уравнение плоскости многоугольника. Если временной шаг интегрирования уравнений состояния частиц достаточно мал, то, полагая, что скорость частицы на последовательных шагах изменяется незначительно, можно с помощью линейной интерполяции восстановить момент времени, когда в действительности произошло столкновение.
То, что происходит с частицей после столкновения, во многом напоминает то, что происходит со световым пучком после отражения. Если имеет место неупругое столкновение (inelastic collision), то частица не теряет энергии и ее скорость (точнее, модуль скорости) остается неизменной. Но направление движения изменится в соответствии с законом идеального зеркального отражения. Следовательно, если известны нормаль п к поверхности в точке столкновения s и предыдущая позиция частицы р, то можно вычислить угол идеального зеркального отражения по методике, описанной в главе б (рис. 11.7):
r=(2(p-s)-n)n-(p-s).
Частица “пролетит” вдоль вектора отражения такое же расстояние, как и то, которое она “пролетит”, пронзив многоугольник, если бы столкновение не было обнаружено (рис. 11.8).
Направление движения изменится, а модуль вектора скорости останется прежним. Следовательно, та компонента вектора скорости, которая направлена вдоль плоскости многоугольника, остается неизменной, а компонента, параллельная нормали, меняет знак на противоположный.
Более сложным является механизм моделирования упругого столкновения (elastic collision), при котором частицы теряют часть своей энергии. Коэффициент восстановления (coefficient of restitution) частицы — это та часть нормальной ком-
поненты вектора скорости, которая сохраняется после столкновения. Следовательно, угол отражения вычисляется как и для неупругого столкновения, но нормальная компонента вектора скорости уменьшается в соответствии со значением коэффициента восстановления.
Сложнее всего обнаружить в модели столкновения сам факт столкновения. В игровых программах вполне достаточно приблизительно определить момент столкновения, и в таком случае можно заменить сложную модель геометрического объекта, состоящую из множества многоугольников, на один простой объект, например ограничивающий прямоугольник или параллелепипед. Обнаружить столкновение с таким каноническим объектом значительно проще.
Обращаю ваше внимание на то, что работа с точечными частицами позволяет избежать вычислений, которые обязательно пришлось бы выполнять, если бы анализировалось столкновение объекта определенной пространственной формы. Задача обнаружения столкновений во многом напоминает задачу отсечения одного объекта другим — хотя сами по себе формулы и не сложны, но объем вычислений оказывается значительным и на него уходит довольно много времени. Кроме того, при работе с объектами конечной формы нужно учитывать силы инерции, что повышает количество дифференциальных уравнений, описывающих состояние системы1. В компьютерной графике чаще всего используется приближенное решение системы уравнений, описывающей состояние системы, а потом на место идеальных частиц с точечной массой в изображение помещается какой-либо геометрический объект со своей формой и атрибутами.
Существует еще один вариант жестких ограничений, который часто включается в физическую модель, — учет сил контактного взаимодействия {contact forces). Предположим, что в модели имеется частица, на которую действует сила, толкающая ее параллельно какой-либо поверхности (рис. 11.9).
Рис. 11.9. Силы контактного взаимодействия
С одной стороны, под действием этой силы частица не может “проникнуть” в поверхность, а с другой — не должна и отрываться от нее. В такой системе следует учитывать только компоненту вектора суммарных сил, касательную к поверхности. Можно пойти дальше и включить в физическую модель еще и учет сил трения при движении вдоль поверхности.
Частицы внутри сферической оболочки
Прежде чем перейти к анализу мягких ограничений, остановимся на время еще на одном жестком ограничении. Начнем с группы частиц, заключенных в сферическую оболочку радиуса г, центр которой находится в начале координат. Предположим, что на частицы действуют силы взаимного отталкивания, величина которых обратно пропорциональна расстоянию между ними. Соударение частиц с поверхностью оболочки носит характер упругого столкновения. Пусть р,(/) и v,(/) — положение и скорость /-й частицы в момент времени t. Расстояние dn между /-й иу-й частицами равно
'Здесь вряд ли .можно согласиться с автором, поскольку учет инерциальных свойств объектов никак не связан с их размерами. а только с массой. От размеров же может зависеть сила сопротивления среды, в которой перемещается моделируемый объект. — Прим. ред.
а отталкивающая сила, действующая на /-ю частицу со стороныу-й частицы вдоль прямой, их соединяющей, равна
f„="kf„="k1 Р, "Р,<; |р.-р,|1 Р, "Р,<; |р.-р,|-^Л(р,-р,)--^Л(р,-р,)-
f„="k
f„="k
1 Р, "Р,<; |р.-р,|
1 Р, "Р,<; |р.-р,|
-^Л(р,-р,)-
-^Л(р,-р,)-
Начав с некоторого случайного положения частиц, можно интегрировать систему дифференциальных уравнений, описывающих состояния частиц, применяя численные методы и соответственно подобрав шаг интегрирования. Все будет идти прекрасно до тех пор, пока какая- либо из частиц не попытается проникнуть сквозь сферическую оболочку.
Решение, полученное на очередном шаге интегрирования системы уравнений, необходимо проанализировать на предмет того, не оказалась ли какая-либо из частиц вне сферической оболочки. Пусть положение некоторой частицы в момент времени / было р, а после следующего шага интегрирования, в момент t+h, стало q. Соответственно скорость частицы в момент / обозначим v, а в момент t+h — и. Если q находится внутри сферы, то эта точка сохраняется в качестве текущего положения частицы. Но если точка q вышла за пределы сферы, то дальше выполняется следующая процедура. Сначала отыскивается точка а пересечения сферы, которая лежит на траектории от точки р до точки q (рис. 11.10). В этой точке должны выполняться соотношения
а = (1 —€х)р + aq,
IP + a(q-p)| = г.
Отсюда следует простое квадратное уравнение относительно неизвестной а. Вычислив а, получим координаты точки пересечения траектории частицы со сферой:
а = р + a(p-q).
Эту точку в дальнейшем будем рассматривать как точку столкновения частицы с оболочкой. Для определения траектории отраженной частицы нам понадобится знать направление нормали к поверхности в точке отражения. Поскольку поверхность сферическая, вектор нормали несложно вычислить по формуле
п = -а/г.
Направление отражения вычисляется после этого по формуле (рис. 11.11)
г = -(р-а) + 2(п • (р-а))п.
Рис. 11.10. Пересечение траектории частицы со сферойРис. 11.10. Пересечение траектории частицы со сферойРис. 11.11. Отражение частицы от сферической оболочкиРис. 11.11. Отражение частицы от сферической оболочки
Рис. 11.10. Пересечение траектории частицы со сферой
Рис. 11.10. Пересечение траектории частицы со сферой
Рис. 11.11. Отражение частицы от сферической оболочки
Рис. 11.11. Отражение частицы от сферической оболочки
Следовательно, в момент времени t+h частица будет в точке а+ar. Для дальнейших вычислений потребуется также определить и новый вектор скорости vq частицы после отражения от сферической оболочки. Компонента вектора скорости вдоль направления нормали в точке столкновения равна n v,,. Нам нужно изменить направление этой компоненты на противоположное, оставив другие компоненты без изменений. Текст программы, реализующей
описанную модель системы частиц, заключенных в сферическую оболочку, вы найдете в файле bounce.с. В упражнениях в конце главы вам будет предложено самостоятельно разработать другие варианты подобного рода ограничений.
Мягкие ограничения
Включение в физическую модель большинства видов жестких ограничений связано с серьезным увеличением сложности, а значит, и времени вычислений. Если, например, требуется, чтобы скорость всех частиц не превосходила некоторую максимальную или чтобы сумма энергии всех частиц не превышала некоторую константу, то для решения такой задачи в реальном масштабе времени потребуется очень мощный процессор, поскольку обычных дифференциальных уравнений для описания состояния системы будет недостаточно.
Но во многих ситуациях можно получить удовлетворительное решение, используя мягкие ограничения в виде минимизации некоторого функционала качества системы. Если, например, желательно, чтобы частица, занимающая в текущем состоянии положение р, не удалялась слишком далеко от точки р0, то можно включить в модель функцию штрафа (penalty function) |р—Ро|2. Чем меньшее значение этой функции будет обеспечено в текущем состоянии, тем точнее соблюдает система сформулированное таким образом ограничение. Эта функция является одним из видов энергетической функции (energy function) системы, величина который является оценкой энергии определенного вида, накопленной в системе. Физически это может быть потенциальная или кинетическая энергия. При использовании энергетической функции в качестве меры состояния системы в модели может решаться задача минимизации или максимизации этой оценки, но описание соответствующих математических методов выходит за рамки данной книги.
На ил. 13 цветной вклейки показан результат использования модели системы частиц для визуализации данных научных исследований. В этом исследовании необходимо было построить поверхность уровня определенной функции, основываясь на большом массиве трехмерных данных. В модель были “впрыснуты” частицы, обладающие замечательным свойством, — они стремятся расположиться только на поверхности заданного уровня (изоповерхности). Функция штрафа заставляет частицы стремиться занять место на этой поверхности, а силы взаимного отталкивания побуждают их более или менее равномерно распространиться по всей поверхности, а не собираться в отдельных ее областях. Информацию такого типа можно отобразить и с помощью другого метода, который будет рассмотрен в главе 12.
Языковые модели
В главе 8 было показано, как с помощью графов двух видов — деревьев и ориентированных ациклических графов — представлять иерархические отношения меду компонентами сложной системы. В этом разделе будет рассказано, как использовать для представления таких отношений языковые средства и создавать языковые иерархические модели. Такая методика не только предлагает альтернативный путь описания отношений, но и логически приводит к процедурным методам определения объектов.
До сих пор при формировании геометрических объектов мы повсеместно пользовались фиксированной последовательностью операций: обрабатывали модель, а далее формировали все примитивы, независимо от того, будут они видимы или нет, а если будут видимы, то какую часть экрана займут и насколько будут заметны их детали. Вместо этого предлагается определять объекты процедурами или алгоритмами, которые будут формировать примитивы, а не примитивами как таковыми. В результате собственно формирование примитивов — геометрических объектов — откладывается до того этапа процесса, когда наступит очередь отображения. На этом этапе мы будем располагать полной информацией о том, следует ли во-
обще формировать конкретный примитив, попадет ли он в кадр, не окажется ли так, что при заданных параметрах визуализации, в частности проективного преобразования, размеры примитива окажутся меньше размера пикселя и образ примитива просто физически не сможет появиться в изображении. Достоинством такой модели является и возможность встроить в нее генератор случайных чисел и внести “приятное разнообразие” в форму аналогичных объектов на экране — они не будут выглядеть как оловянные солдатики, отлитые по одной форме. Очень удобно применять эту методику при воспроизведении в сцене объектов реального мира — растений или участков земной поверхности.
Даже не очень наблюдательный человек скажет вам, что не встречал в своей жизни двух абсолютно одинаковых деревьев. Придумано множество методов, позволяющих создавать при каждом очередном обращении объект, внешне отличающийся от любого из предыдущих, хотя между ними и есть очевидное структурное и типологическое сходство, которое определяется набором достаточно ясных правил. Ниже мы рассмотрим, как можно использовать древовидные структуры данных для формирования объектов, которые выглядят на экране как растения.
В информатике древовидные структуры используются повсеместно. Классический пример — грамматический разбор скобочных форм. В основном грамматический разбор в компьютерных и естественных языках выполняется по одной схеме. Разбор предложения компьютерного языка — программы — позволяет уяснить связь между отдельными ее компонентами, а разбор предложения естественного языка позволяет понять связь между отдельными членами предложения (кто, кого и когда). Дерево, представляющее структуру проанализированного предложения, описывает синтаксис этого предложения. Интерпретация отдельных элементов дерева— слов— придает предложению определенный смысл, т.е. определяет семантику предложения.
Если нас интересует только синтаксис языка, то существует прямая корреляция между правилами языка и формой дерева, которое представляет предложение на этом языке. Эту идею можно распространить и на иерархию объектов в графической системе — в той части, которая относится к отношениям между набором правил и древовидной моделью. Подобные системы известны под названием древовидных грамматик (tree grammars). Такая грамматика определяется множеством символов и множеством правил подстановки или порождающих правил (productions), которые четко предписывают, как можно заменить определенный символом другим символов или комбинацией других символов. Типичное правило представляется в форме
А —> ВС,
В -э АВА.
Располагая набором порождающих правил, можно сформировать бесконечное множество строк символов. В общем случае по отношению к некоторому символу в определенной ситуации можно применить более чем одно правило, а потому при случайном выборе фактически применяемого правила можно при каждом новом обращении к программе формировать отличающийся вариант строки. Программа может быть написана таким образом, что не только сформирует новую строку, но и, получив строку в качестве аргумента, сможет проанализировать, является ли она (строка) членом множества строк, сформированных в соответствии с определенным набором правил. Следовательно, можно будет иметь набор правил формирования объектов определенного класса (скажем, деревьев или кустиков) и отдельно от него программу, которая сможет определить, к какому классу принадлежит тот или иной объект сцены, основываясь на информации о разных грамматиках, порождающих формы объектов.
Символы преобразуются в графические объекты в результате интерпретации сформированных строк, причем для этого могут быть использованы разные способы. Один из подходов базируется на графической системе черепашьей графики" (turtle graphics) (см. упр. 2.4). В
2В отечественной литературе по отношению к этому способу формирования штриховых графических объектов иногда используется термин "паучья графика ”. — Прим ред.
этой графической системе образ объекта формируется в результате манипуляций с перемещением графического курсора— черепахи, который оставляет след на поле изображения. Есть три главные команды управления черепахой — ее можно передвинуть вперед на 1 единицу, повернуть вправо или влево. Угол поворота фиксирован (но в каждой частной системе может иметь место свое значение — например,
30°, 45°, 90° и т.п.). Обозначим три базовые операцииЛ
символами F,Rn L. Любая строка таких символов име- / \
ет простую и очевидную графическую интерпретацию.
Если, например, в системе используется угол поворота ^мс- Н12. Правило формирования 120°, то интерпретация строки FRFRFR приведет к кривой Коха формированию равностороннего треугольника. Доба
вим в набор символов еще два: [— для обозначения операции записи состояния черепахи (ее положения и ориентации) в стек, а 7 — для операции восстановления состояния, извлеченного из стека. Пусть графическая система настроена на угол поворота 60°. Рассмотрим порождающее правило
F-) FLFRRFLF,
графическая интерпретация которого показана на рис. 11.12. Если применить это правило повторно ко всем символам F в новой строке, то получим кривую, показанную на рис. 11.13, а, после повторного применения к этой кривой — замкнутую кривую, показанную на рис. 11.13, б. Эти кривые известны в научном мире под именами собственными кривой Коха {Koch curve) и снежинки Коха {Koch snowflake) и являются примерами так называемых заполняющих кривых {space-filling/\
curves)-, при рекурсивном применении правила кривая растяги-у ~7
вается все больше и больше, но при этом нигде не пересекает /\ /\ /\
саму себя. Если на каждом шаге рекурсии уменьшать масштаб условной единицы длины при геометрической интерпретации,°)
то кривая, несмотря на увеличение суммарной длины траектории, будет все время “вписываться” в определенную прямоугольную оболочку..
Включение в правило операторов записи в стек и извлеченияуу
из стека позволяет формировать побочные ответвления образа._)С.
Рассмотрим правилоуу
FF\RF\F\LF\F,
б)б)Рис. 11.13. Заполняющие кривые: а — кривая Коха; б — снежинка КохаРис. 11.13. Заполняющие кривые: а — кривая Коха; б — снежинка Кохадля которого установлен угол поворота 27° (рис. 11.14). Учтите, что отрисовка кривой начинается с нижней точки, а угол измеряется относительно текущего направления “вперед”.
б)
б)
Рис. 11.13. Заполняющие кривые: а — кривая Коха; б — снежинка Коха
Рис. 11.13. Заполняющие кривые: а — кривая Коха; б — снежинка Коха
После применения правила к единственному линейному сегменту получим образ, представленный на рис. 11.14. Далее можно поступать по-разному. Например, применить правило повторно ко всем символам F в последовательности. В этом случае
получится образ, показанный на рис. 11.15. Одновременно можно изменить масштаб единичного шага черепахи и таким образом уменьшить длину последующих отростков. После нескольких итераций получим нечто, напоминающее сухой кустарник с массой колючек (какой- нибудь саксаул). Все, кажется, прекрасно, но если с помощью этого правила попытаться изобразить на экране заросли саксаула, то получатся не хаотические дебри, а выстроившиеся, как на параде, “ветвистые витязи”, ибо “все равны, как на подбор”.
Рис. 11.14. Кривая, фор- Рис 11.15. Повторное Рис. 11.16. Включение элемента ау- мируемая правиломприменение правилачайности в повторное приме-
F -> F/RF/F/LF/FF -* F/RF/F/LF/Fнекие правила F -* F/RF/F/LF/F
Можно устранить это однообразие, так милое людям в погонах, введя случайность в отбор тех символов F, по отношению к которым применяется правило на очередном шаге итерации. В результате с помощью одного и того же правила можно получить разные варианты образа саксаула (рис. 11.16). Если ввести в набор еше несколько порождающих правил и случайно выбирать, какое из них применять на очередном цикле итерации, то можно сформировать на экране довольно правдоподобное изображение лесных дебрей. Незначительно модифицировав правила, можно и “приклеить” листочки на концы ветвей — саксаул превратится в сакуру.
Привлекательность этой стратегии состоит в том, что обходясь минимумом средств — буквально двумя-тремя правилами и несколькими параметрами, — мы получаем возможность определить целый класс объектов подобной и в то же время разнообразной формы. Предположим, что для оживления пейзажа или для создания “среды обитания" персонажей в сцену требуется включить деревья. Можно, конечно, просто сформировать в программе такие объекты, используя привычную методику их описания в виде множества примитивов — отрезков, многоугольников и канонических кривых. В некоторых случаях деревьев может потребоваться не единицы, а десятки (а может, и сотни). Но даже не это самое неприятное. Как правило, одна и та же сцена “по ходу действия” может рассматриваться в разном ракурсе и в разном масштабе, причем только малая часть из того множества деревьев, на формирование которого в программе ушло так много труда, появится в каждом конкретном изображении — виде сцены. Остальные будут либо вне поля зрения, либо иметь такой мелкий масштаб, что ни о какой проработке деталей образа и говорить не приходится. В результате складывается впечатление, что громадная работа по формированию деревьев ушла впустую. Совсем по-другому складывается ситуация, если для формирования образов деревьев использовать описанный процедурный метод. В программе присутствует только алгоритм формирования обобщенного дерева, а собственно создание образа происходит только тогда, когда такой образ действительно должен присутствовать в изображении. Причем степенью детализации образа, которая зависит от его масштаба на экране, также можно управлять программно.
Грамматику формирования экранных образов можно описать непосредственно в терминах графических форм и аффинных преобразований, в результате чего будет создана грамматика формы {shape grammar). Вернемся вновь к узору Серпинского. Каждый цикл разбиения можно описать в терминах трех аффинных преобразований, каждое из которых уменьшает вдвое масштаб исходного треугольника и размещает его уменьшенную копию в разных точках, как показано на рис. 11.17. Эти правила можно применять в случайном порядке или все три параллельно, но в любом случае, в конце концов, получим уже знакомый узор.
Итак, мы познакомили вас с тремя процедурными методами, которые позволяют формиИтак, мы познакомили вас с тремя процедурными методами, которые позволяют формировать как модели реальных физических объектов, так ских объектов. Кривые Коха и узор Серпинского дают вам представление еще об одном аспекте процесса формирования графических образов— методе, который можно применять рекурсивно и который на каждом очередном цикле детализирует ранее созданный образ, сохраняя характерные признаки формы исходного объекта. Этот метод положен в основание одного из направлений современной геометрии — геометрию фракталов.
Итак, мы познакомили вас с тремя процедурными методами, которые позволяют форми
Итак, мы познакомили вас с тремя процедурными методами, которые позволяют форми
Рис. 11.17. Три правила, используемые при рекурсивном построении узора Серпинского
и модели воображаемых математиче-и модели воображаемых математиче-11.7. Рекурсивные методы и фракталы
и модели воображаемых математиче-
и модели воображаемых математиче-
Языковые процедурные модели — это только один из возможных подходов к формированию сложных графических объектов с помощью простой программы. Другой подход базируется на геометрии фракталов {fractal geometry^ и использует внутреннюю симметрию, присущую многим объектам реального мира. Геометрия фракталов разработана Мандельбротом (Mandelbrot), который положил начало новому направлению в математике. Исследования в этом направлении привели к созданию методов, позволяющих описать очень интересные явления, неподвластные средствам традиционной геометрии. Специалисты по компьютерной графике использовали идеи геометрии фракталов не только для создания графических объектов весьма прихотливой формы, но и для моделирования многих физических явлений, которые другими методами представить не удается. Графические объекты, созданные с помощью методов геометрии фракталов, получили название графталов {graftals\
1L7.L Масштаб и длина
Стройное здание геометрии фракталов зиждется на двух устоях:
зависимость между геометрией формы и масштабом;
внутренняя симметрия (самосимметрия).
Чтобы понять смысл этих формулировок, попытаемся ответить на вопрос, который, собственно, и породил это направление: “Что такое длина береговой линии?” Будем считать, что перед нами на столе разложена географическая карта. Поскольку береговая линия извилиста и крайне нерегулярна, нам придется взять сантиметровую ленту (например, такую, которой в докомпьютерные времена пользовались портные), выложить ее вдоль изображения береговой линии на карте и затем, используя масштаб, обозначенный на карте, пересчитать измеренную длину3. Однако, если вы потом найдете другую карту того же побережья, более крупномасштабную, и повторите эксперимент, то скорее всего и результат получите другой — больший. Дело в том, что на крупномасштабной карте форма береговой линии станет более изрезанной — появятся мелкие бухты и мысы, которые на мелкомасштабной карте просто не отражены. Еще более под-
Для подобных измерений чаще всего используется специальный механический прибор — курвиметр. — Прим. ред.
робно будет воспроизведена форма береговой линии на топографической карте. В принципе, этот процесс можно продолжать до бесконечности, например перейти на молекулярный уровень, и каждый раз мы будем видеть все новые и новые детали общей картины.
Если мы все-таки хотим получить полезную информацию из проведенных измерений, то сначала нужно согласовать приемлемый масштаб карты или приемлемую единицу разрешения при измерении. Аналогичная проблема существует и в компьютерной графике при работе с перспективными видами, поскольку степень детализации отдельных объектов зависит от того, на каком расстоянии мы их видим.
Понять математическую суть проблемы можно, воспользовавшись рекурсивным применением кривой Коха, описанной в предыдущем разделе. В этой кривой каждый линейный сегмент длиной в 1 единицу заменяется на очередном цикле четырьмя сегментами, каждый из которых имеет длину 1/3 единицы (рис. 11.18). Следовательно, на каждом цикле длина траектории вдоль кривой между одними и теми же точками увеличивается с коэффициентом 4/3. Анализ предела, к которому стремится длина кривой при бесконечном количестве итераций, обязательно подведет нас к вопросу о размерности. Кривую нельзя считать обычной одномерной линией, так как в пределе она имеет бесконечную длину и бесконечное количество разрывов первой производной. Но она и не является двухмерным объектом, поскольку не заполняет собой некоторую область на плоскости. Эту проблему можно разрешить, введя понятие дробной размерности (fractional dimension).
Рис. 11.18. Удлинение кривой КохаРис. 11.18. Удлинение кривой Коха
Рис. 11.18. Удлинение кривой Коха
Рис. 11.18. Удлинение кривой Коха
Размерность фрактала
Рис. 11.20. Разбиение объектов при h=l/3Рис. 11.20. Разбиение объектов при h=l/3Рассмотрим линейный сегмент единичной длины, единичный квадрат и единичный куб (рис. 11.19). При любой приемлемой формулировке понятия размерность линейный сегмент, квадрат и куб должны оставаться одно-, двух- и трехмерными объектами соответственно. Предположим, в нашем распоряжении есть измеритель с разрешением Л, где h=\!n— наименьшая единица длины, воспринимаемая прибором. Положим, что п — целое число. Разделим объекты на подобные в терминах значения И, как показано на рис. 11.20.
Рис. 11.20. Разбиение объектов при h=l/3
Рис. 11.20. Разбиение объектов при h=l/3
Рис. 11.19. Линейный сегмент, квадрат и куб
Линейный сегмент разобьем на к=п идентичных сегментов, квадрат— на к=п" более мелких квадратов, а куб — на к=п более мелких кубиков. В любом случае можно утверждать, что новый объект сформирован в результате сжатия исходного с коэффициентом h и подстановкой на место исходного к раз. Пусть d — размерность любого из этих объектов. В процессе разбиения сохраняется сумма всех компонентов, и математически для любого объекта это выражается равенством
^кп"* =\.
Решая это уравнение относительно d, получим выражение для размерности фрактала (fractal dimension):
1пЛ In л
Другими словами, размерность фрактала некоторого объекта есть мера оценки количества подобных объектов, формируемых в одном цикле разбиения. Рассмотрим теперь кривую Коха. При ее разбиении создается четыре подобных объекта, каждый из которых втрое короче. Соответствующее уравнение для размерности фрактала имеет вид
In 4
d= — = 1.26186.
1пЗ
Повторим аналогичные рассуждения в отношении узора Серпинского. Один цикл разбиения представлен графически на рис. 11.21. В каждом цикле стороны треугольника делятся пополам и формируется объект, в который включаются три из четырех образовавшихся треугольников. Следовательно, получим
В обоих случаях видно, что в результате разбиения формируется объект, который занимает больше пространства, чем кривая, но меньше, чем площадь области, ограниченной исходным объектом. Можно сформировать и трехмерный аналог узора Серпинского. Исходным объектом в этом случае будет тетраэдр. В процессе разбиения его грани делятся как объекты двухмерного узора (рис. 11.22). В сформированном объекте остаются четыре малых тетраэдра, а область в середине исходного тетраэдра удаляется. В результате получим значение размерности фрактала трехмерного узора Серпинского:
Обратите также внимание на то, что объем объекта после каждого цикла разбиения уменьшается, а площадь поверхности увеличивается. Рассмотрим еще один объект— куб, который разбивается на втрое меньшие кубики, из которых 20 сохраняются в новом объекте, а 7 (шесть в центре каждой грани и один в центре куба) — удаляются (рис. 11.23). Для этого объекта размерность фрактала равна
Рис. 11.23. Разбиение кубаРис. 11.23. Разбиение кубаРис. 11.22. Разбиение элемента трехмерного узора СерпинскогоРис. 11.22. Разбиение элемента трехмерного узора СерпинскогоЭти конструкции мы рассматривали здесь только для иллюстрации некоторых положений геометрии фракталов, хотя всякого рода эксперименты с ними весьма интересны и поучительны. При моделировании явлений реального мира используются несколько другие объекты, в которые, помимо прочего, добавлен феномен случайности.
Рис. 11.23. Разбиение куба
Рис. 11.23. Разбиение куба
Рис. 11.22. Разбиение элемента трехмерного узора Серпинского
Рис. 11.22. Разбиение элемента трехмерного узора Серпинского
Рис. 11.21. Разбиение
элемента узора Серпинского
Разбиение в средней точке и броуновское движение
Фрактальные кривые имеют размерность в интервале 1 < d < 2. Кривые с меньшим значением размерности фрактала выглядят более гладкими, а по мере увеличения размерности кривая становится все более извилистой. Аналогичное утверждение справедливо и в отношении поверхностей: фрактальная размерность таких объектов лежит в интервале 2<(/<3. В компьютерной графике существует множество приложений, в которых желательно сформировать объект, имеющий характерные признаки своего класса и в то же время не дублирующие форму своих “собратьев”. Более того, иногда желательно управлять степенью “резкости” или “мягкости” контура объекта. Например, силуэт горы на экране должен выглядеть более резким, чем линия горизонта в пустынном ландшафте. Следовательно, если и тот и другой создаются с применением технологии фракталов, то в первом случае объект должен иметь более высокую размерность, чем во втором. Кроме того, формирование объекта должно учитывать и требования к разрешению. Степень “проработки” деталей ландшафта очень сильно сказывается на скорости формирования изображения, что существенно для приложений, от которых требуется работа в реальном масштабе времени, например симуляторов.
Случайное движение частиц жидкости известно в физике под названием броуновского движения. Моделирование такого движения демонстрирует весьма интересный подход, который с успехом можно использовать при формировании кривых и поверхностей, имеющих довольно естественный вид. Физики моделируют броуновское движение с помощью ломаной линии, в которой каждая следующая точка излома отстоит на случайно выбранном расстоянии от предыдущей, а ориентация очередного сегмента также случайна. При воспроизведении реального физического процесса очень существенную роль играет настройка функции распределения генератора случайных чисел, принимающего участие в построении траектории. В компьютерной графике мы меньше озабочены обеспечением адекватности модели и физического явления, а значительно важнее для нас скорость вычислений и возможность формировать кривые с настраиваемыми характеристиками “резкости” или “мягкости”. Поэтому термин броуновское движение мы будем далее использовать для обозначения любых случайных траекторий, может быть, даже и не имеющих отношения к движению частиц жид
кости или газа.
Конечно, формировать траекторию броуновского движения можно и с помощью последовательности отрезков ломаной, задавая для каждого сегмента случайные значения длины и ориентации, но более эффективно воспользоваться рекурсивной процедурой. Рассмотрим линейный отрезок— сегмент траектории, показанный на рис. 11.24,а. Найдем его среднюю точку и разобьем сегмент на два, причем точку стыковки сегментов сдвинем относительно
средней точки по нормали к исходному отрезку на nj»q случайно выбранное расстояние (рис. 11.24,6). По
вторим этот процесс несколько раз, применяя его по отношению ко всем сегментам, построенным в пре
//дыдущем цикле. В результате будут построены
Р*кривые, примерно такие, как на рис. 11.25. Харак
теристики генератора случайных чисел, в частно- б)сти среднеквадратичное отклонение, следует регу-
Рис. 11.24. Смещение средней точки: а — исходный линейный сегмент; б — сегмент после разбиенияРис. 11.24. Смещение средней точки: а — исходный линейный сегмент; б — сегмент после разбиениялировать на каждом цикле, уменьшая его примерно в 2 раза, поскольку длины сегментов на каждом цикле также уменьшаются примерно вдвое. Можно усложнить алгоритм и включить случайный
Рис. 11.24. Смещение средней точки: а — исходный линейный сегмент; б — сегмент после разбиения
Рис. 11.24. Смещение средней точки: а — исходный линейный сегмент; б — сегмент после разбиения
фактор не только в расстояние смещения, но и в его направление. Если отклонение всегда
положительно, то будет построена случайная линия горизонта, а если воспользоваться ге
нератором гауссовского центрированного шума со среднеквадратичным отклонением,
пропорциональным /2(2-</), где / — длина разделяемого сегмента, то результирующая кривая будет иметь размерность d. Значение <7=1,5 соответствует траектории идеального броуновского движения.
Рис. 11.25. Фрактальные кривые, состоящие из 1, 2, 4 и 16 сегментов
Формирование изображения горы с помощью фракталов
В задачах компьютерной графики фрактальная технология получила наибольшее распространение при формировании деталей ландшафта, в частности гор. Для этого используется описанный выше метод рекурсивного разбиения в средней точке, но в качестве исходного объекта берется тетраэдр. Рассмотрим одну грань тетраэдра (рис. 11.26). Сначала определяются средние точки каждого из трех ребер грани. Затем каждая средняя точка смещается случайным образом. В результате формируется четыре новые треугольные грани. Управляя характеристиками генератора случайных чисел, задающего смешение, можно добиваться нужной степени мягкости или резкости рельефа горы. Обращаю ваше внимание на то, что к выбору характеристик генератора нужно отнестись очень внимательно, иначе результат не оправдает ваших ожиданий. Подробно об этом вы сможете узнать в специальной литературе, которая будет рекомендована для самостоятельного изучения в конце этой главы.
Рис. 11.26. Разбиение в средней точке граней тетраэдра
Алгоритм разбиения в средней точке можно применять по отношению к любой полигональной сети. Можно начинать с плоской сети из четырехугольных ячеек в плоскости х, г, а далее разбивать каждый четырехугольник на четыре более мелких и смещать все вершины случайным образом вверх (в направлении оси у). На рис. 11.27 показано, как выглядит промежуточный результат этого процесса.
б)
Рис. 11.27. Ландшафт, сформированный с помощью фракталов: а— сеть; б— разбиение сети со смещением вершин
Множество Мандельброта
Ни одно обсуждение темы фракталов, даже на самом элементарном уровне, не обходится без упоминания .множества Мандельброта ^Mandelbrot set). Хотя алгоритм формирования множества Мандельброта довольно прост, при этом формируются образы бесконечно сложной формы. Это множество является прекрасным примером использования таблицы соответствия цветов при генерации изображения. В дальнейшем изложении мы полагаем, что читатели знакомы хотя бы на самом элементарном уровне с арифметикой комплексных чисел.
Обозначим точку на комплексной плоскости как
Z = x+iy,
где х— действительная часть, а у— мнимая часть комплексного числа z (рис. 11.28). Если Z| = Xi+i^i и z2 = х2"Ну2 — два комплексных числа, то операции их сложения и умножения определены следующим образом:
z^Zz^+^+iOi+yz),
Z|Z2 = х^-ууг+Кхо^ХзУд-
Число i—.мнимая единица— обладает свойством i2= —1.У
Комплексное число z имеет модуль (или абсолютную величи-<1
ну), которая определяется следующим образом:• z = х + ;к
। ,2 _ 2 . 2
|z| - X +у .
Функция w = F(z) отображает одну точку комплексной► х
плоскости на другую. С помощью такой функции можно определить рекуррентную процедуру построения некоторой траектории на комплексной плоскости (годографа комплексного вектора):
z^' /'(ZAрис. 11.28. Комплексная плос-
где Zo = с — заданная исходная точка траектории. Если нанес- кость ти на комплексную плоскость точки z*, то, как видно на
рис. 11.29, существует несколько вариантов “развития событий”, в зависимости от вида функции Р1гк) и начальных условий — положения точки с. В одном случае по мере возрастания к точки стремятся разойтись все дальше и дальше, в другом — картина периодически повторяется, а в третьем — точки стремятся к некоторой фиксированной точке сходимости.
Рис. 11.29. Последовательность точек, формируемая рекуррентной функцией отображенияРис. 11.29. Последовательность точек, формируемая рекуррентной функцией отображенияРис. 11.30. Множество Мандельброта: а — черно-белая раскраска; б — цветная раскраскаРис. 11.30. Множество Мандельброта: а — черно-белая раскраска; б — цветная раскраска
Рис. 11.29. Последовательность точек, формируемая рекуррентной функцией отображения
Рис. 11.29. Последовательность точек, формируемая рекуррентной функцией отображения
Рис. 11.30. Множество Мандельброта: а — черно-белая раскраска; б — цветная раскраска
Рис. 11.30. Множество Мандельброта: а — черно-белая раскраска; б — цветная раскраска
Рассмотрим, например, функцию
Хы = z*2,
где Zq = с. Если с лежит вне окружности единичного радиуса, то последовательность {z*} расходится; если с лежит внутри окружности единичного радиуса, то последовательность {z*} сходится к началу координат комплексной плоскости; если же |с| = 1, то каждое последующее
значение zk лежит на окружности единичного радиуса.
Более интересный результат дает функция
zM = z/+c
при начальных условиях Zq = 0+Ю. Точка с принадлежит .множеству Мандельброта {Mandelbrot set) тогда и только тогда, когда все точки порождаемой ею рекуррентной последовательности являются конечными. Следовательно, комплексную плоскость можно разделить на две зоны, одна из которых представляет собой совокупность точек, принадлежащих множеству Мандельброта. Выделив на комплексной плоскости некоторый прямоугольник, можно графически выделить определенным цветом (например, черным) точки, принадлежащие множеству Мандельброта, а белым — остальные (рис. 11.30, а). Но на границе раздела двух областей картина оказывается довольно сложной, поэтому приходится увеличивать коэффициент масштабирования, чтобы рассмотреть подробности.
Вычисление точек множества Мандельброта может занять довольно много времени, но существует несколько специальных приемов, позволяющих ускорить процесс. Область с центром в точке с = -0.5+i0.0 представляет наибольший интерес, хотя, возможно, вам и захочется изменить размер и положение окна просмотра.
Как правило, уже посЛе нескольких итераций можно довольно уверенно предсказать, уйдут ли точки рекуррентной последовательности в бесконечность или будут стремиться к точке сходимости. Например, если |z*| > 4, то по мере возрастания номера итерации точки все больше расходятся и можно прекращать процесс. Сложнее предсказать поведение последова-
тельности, когда исходная точка находится вблизи границы зоны раздела. Поэтому обычно используется аппроксимация множества, причем выполняется она следующим образом. Мы фиксируем максимальное количество итераций. Если при данном с последовательность точек, сформированная на заданном количестве итераций, обнаруживает тенденцию к расхождению, последовательность считается расходящейся и точка с окрашивается белым цветом. Если после выполнения заданного количества итераций значение |z*| не превышает некоторого порога, считается, что точка с принадлежит множеству Мандельброта и на изображении комплексной плоскости она окрашивается в черный цвет. Можно присваивать цвет точке с и соответственно значению |z*| после выполнения последней итерации.
На ftp-сервере по адресу ftp.cs.unm.edu в каталоге /pub/angel/ВООК находится программа Mandelbrot.с, которая формирует множество Мандельброта по этому приближенному алгоритму. Пользователь этой программы имеет возможность устанавливать размеры и положение обозреваемой зоны комплексной плоскости, а также максимальное количество итераций для анализа расходимости рекуррентной последовательности. Комплексные числа гк масштабируются таким образом, чтобы их абсолютная величина находилась в интервале от О до 255. Программа формирует однобайтовый массив image размером n х m и заполняет его значениями гь полученными после выполнения заданного количества итераций. Каждый элемент массива определяет затем цвет соответствующего пикселя картинной плоскости. Процесс отображения включает две стадии. На первой стадии формируется карта цветов, это происходит на этапе инициализации программы. Программа демонстрирует пользователю представление значений яркости в диапазоне (0.0, 1.0). Пользователь определяет карту соответствия интенсивности красного цвета таким образом, что минимальной яркости (0.0) будет соответствовать отсутствие красной составляющей в сумме цветов, а максимальной яркости (1.0)— насыщенный красный цвет. Между этими крайними значениями выполняется линейная интерполяция. Для синей составляющей формируется обратное соответствие — нулевой яркости соответствует насыщенный синий цвет, а максимальной яркости — отсутствие синей составляющей. Зеленая составляющая выбирается случайным образом. Такой способ установки значения зеленой составляющей позволяет лучше передать в изображении области с малым перепадом интенсивности (рис. 11.30,6). Программный код заполнения карты соответствия цветов приведен ниже.
for(i = 0;i<256;i++) {
redmap[i]=i/255.;
greenmap[i]=rand()%RAND_MAX;
bluemap[i]=1.0-i/255.;
}
glPixelMapfv(GL_PIXEL_MAP_I TO_R, 256, redmap);
glPixelMapfv(GL_PIXEL_MAP_I-TO_G, 256, greenmap); glPixelMapfv(GLJ4XEL_MAP_ljr°_B, 256, bluemap);
Если посчитаете нужным, можете самостоятельно включить в программу другой алгоритм формирования карты соответствия цветов. Функция отображения включает всего две операции — очистку экрана и передачу в буфер кадра значения кодов засветки из массива image.
void display() {
glClear(GL_COLOR_BUFFER BIT);
glDrawPixels(n,m,GL_COLOR_INDEX, GL_UNSIGNED_BYTE, image);
}
Резюме
В этой главе мы кратко познакомились с двумя новыми подходами к моделированию — физической моделью системы материальных частиц и языковыми методами моделирования графических образов. В обоих случаях важное место отводится технологии рекурсивного выполнения процедур, которая позволяет формировать графические образы с контролируемой степенью проработки деталей.
Основное достоинство процедурных методов формирования графических объектов — возможность управлять процессом создания примитивов для отображения конкретного набора объектов из всех имеющихся в сцене с учетом их характеристик именно в данной ситуации. Процедурные методы хорошо сочетаются с объектно-ориентированным подходом к созданию моделей реальных объектов и явлений. Поэтому следует ожидать интенсивное развитие этих методов уже в самом ближайшем будущем.
Объединение в единой системе информации о физической природе явлений с технологией компьютерной графики порождает совершенно новые технологии создания таких популярных в последнее время приложений, как компьютерная анимация в самых различных областях — от видеофильмов до научных исследований. Та система материальных частиц, о моделировании которой шла речь в этой главе, — лишь пример использования физических моделей в задачах компьютерной графики. Она позволила продемонстрировать, насколько широкими возможностями обладает подобная технология.
Те технологии, с которыми вы познакомились в данной главе, будут использованы в главе 12 при построении системы визуализации результатов научных исследований.
Рекомендуемая литература
Системы материальных частиц впервые были внедрены в практику компьютерной графики Ривсом (Reeves) [Ree83]. С тех пор они стали использоваться довольно широко для исследования и визуализации самых различных физических и иных явлений— см. [Rey87], [WU94, а]. В данной книге мы следовали подходу, предложенному Уиткином (Witkin) [Wit94, Ь].
Существует огромный массив специальной литературы, посвященной фракталам и связанным с ними методам. Впервые методику формирования рельефа с помощью геометрии фракталов описал Фурнье (Fournier) в статье [Fou82]. Более глубокое освещение теории фракталов можно найти в книгах Мандельброта (Mandelbrot) [Мап82] и Петгена (Peitgen) [Pei88]. Имеется также довольно много работ, посвященных использованию грамматик графов в самых разных формах, — [Pru90, Smi84, Ып68]. В работах Хилла (Hill) [HU90] и Прузинкевича (Prusinkiewicz) [Рги90] описаны интересные кривые заполнения пространства. Система итерационных функций Барнсли (Barnsley), описанная в работе [Ваг93], демонстрирует подход к использованию свойства самосимметрии фракталов в задаче сжатия изображения.
Упражнения
Разработайте набор порождающих правил для процедуры формирования узора Серпинского, которая начинается с единственного равностороннего треугольника.
Как определить размерность фрактала береговой линии? Как можно удостовериться в том, что береговая линия в действительности обладает свойствами фрактала?
491491Преобразуйте программу разбиения тетраэдра для аппроксимации сферы, которую мы рассматривали в главе б, в программу формирования графического образа возвышенности с помощью технологии фракталов.
491
491
Упражнения
Бинарное дерево, которое используется в задачах поиска, обычно описывается связным списком. Напишите OpenGL-программу, которая по этому списку сможет сформировать на экране изображение графа.
Разработайте программу моделирования состояния системы материальных частиц, соединенных упругими связями. Программа должна формировать динамическое изображение сети из четырехугольных ячеек, представляющей текущее состояние системы частиц. Включите в программу средства, позволяющие пользователю восстановить исходное состояние системы.
Добавьте в программу, разработанную при выполнении предыдущего упражнения, возможность моделирования внешних сил. Сформируйте с ее помощью изображение развевающегося на ветру флага.
Разработайте программы фрактализации сети. Для задания исходного положения вершин сети постарайтесь использовать реальные данные.
Разработайте программу, которая, получив на входе описания двух многоугольников с равным количеством вершин, покажет последовательность фаз преобразования одного многоугольника в другой.
Разработайте физическую модель системы материальных частиц, которая будет имитировать искры при электросварке.
Доработайте модель, разработанную при выполнении предыдущего упражнения, таким образом, чтобы она позволяла имитировать взрыв полигонального объекта и разлет осколков.
Разработайте программу, которая будет формировать изображение облаков, используя механизм альфа-канала (глава 9), алгоритм рекурсивной аппроксимации сферы (глава 6) и технологию фракталов.
Разработайте программу, которая будет формировать изображение поверхности виртуальной планеты с помощью технологии фракталов. На изображении должны быть отчетливо видны океаны и континенты.
ГЛАВА
12
Визуализация данных научных исследований
В
заключительной главе будет показано, как использовать все описанные ранее технологии в одном из самых сложных приложений компьютерной графики — системах визуализации результатов научных исследований. В предыдущих главах был описан богатый инструментарий, которым располагает современная компьютерная графика, — средства формирования геометрических объектов, средства управления их раскраской, анимацией, изменением ориентации и точки наблюдения. Средства работы с внутренними буферами графической системы позволяют выполнять маскирование, удаление невидимых линий и поверхностей, комбинировать несколько изображений в одном. Применение этого богатого арсенала позволяет выбрать наиболее подходящий для конкретного случая метод визуализации имеющегося набора данных. О том, как это делается, и будет рассказано в данной главе.
Данные+геометрия
В любой вычислительной системе данные— это, прежде вс^го, числа. В чистом виде данные невозможно представить пользователю иначе, как в виде последовательности цифр, и нет для пользователя более нудной и неблагодарной работы, чем пытаться извлечь полезную информацию из мириады цифр. А ведь именно так использовались компьютеры еще не так уж давно — всего 30-40 лет назад. Собственно и компьютерная графика начала интенсивно развиваться в период, когда объем такой числовой информации стал настолько велик, что дальнейшее повышение мощности вычислительной техники теряло всякий смысл, — пользователи не успевали “переварить” все, что насчитали компьютеры. Графическое представление данных позволяет наглядно представить огромные массивы числовой информации, выявить в этих массивах наиболее интересные фрагменты и сосредоточить именно на них дальнейшие исследования. Формирование графического представления и составляет суть процесса визуализации данных. Рассмотрим знакомое со школьной скамьи представление числовых данных в виде двухмерных графиков. Сначала необходимо преобразовать имеющийся массив чисел в линии, прямоугольники, маркеры и другие графические объекты. В научных приложениях данные могут поступать из множества источников — это могут быть результа-
ты измерений, математического моделирования, имитационного моделирования и т.д. Проще всего определить понятие визуализация научных данных следующим образом: преобразование данных в графические образы посредством технологии компьютерной графики.
Чаще всего перед исследователем стоит задача оценки распределения некоторой характеристики процесса по объему, причем данные для этой задачи представляют собой либо массив значений, полученных в отдельный момент процесса, либо совокупность массивов, соответствующих множеству моментов протекания процесса. Мы рассмотрим три вида подобных задач:
скалярная визуализация — исследуемая характеристика представляет собой скалярную величину, например плотность излучения;
векторная визуализация — исследуемая характеристика представляет собой векторную величину, например скорость течения жидкости в каждой точке потока;
тензорная визуализация — исследуемая характеристика в каждой точке пространства имеет вид матрицы, например напряжения в механической конструкции.
На пути решения задачи оценки распределения нам предстоит преодолеть довольно много сложностей, главная из которых — большой объем массива первичных (“сырых”) данных. Данные считываются в точках интересующего нас объема с определенным пространственным шагом, кроме того, поскольку большинство процессов динамические, нужно повторять процесс съема первичных данных еще и с некотором тактом по времени. Например, при накоплении данных магниторезонансного исследования в биологии и медицине они собираются в 512 х 512 х 200 точках. Примерно такой же объем первичных данных дают и другие новейшие методики медицинских исследований. Следовательно, любая (даже самая простая на первый взгляд), операция при выполнении ее на таком множестве данных перестает быть “проходной" и ей нужно уделять достаточно серьезное внимание. Вторая сложность состоит в том, что первичные данные представляют собой просто числа и им еще предстоит стать графическими объектами, которые система сможет предъявить пользователю. Третья сложность состоит в том, что во многих случаях нам придется иметь дело с многомерными выборками, т.е. состояние исследуемой системы в данной точке «и в данный момент времени определяется не одним, а множеством чисел. Если, например, регистрируется процесс течения жидкости, то в каждой точке потока измеряются компоненты трехмерного вектора скорости. Если измеряются напряжения или деформации в точке твердого тела, то в каждой точке нужно регистрировать матрицу данных размером 3x3. Стратегия визуализации и определяет, как эти данные трансформируются в графические объекты, и набор их отображаемых атрибутов.
Поля превышений и линии уровня
При выполнении различных исследований очень часто возникает необходимость наглядно представить те или иные математические функции. Способ визуализации функций во многом определяется формой ее задания — явной, неявной, параметрической — и количеством независимых переменных. В этом разделе мы рассмотрим методы визуализации функций двух видов. Сначала остановимся на функциях двух переменных, заданных в явном виде:
= =Л^у\
Функция такого вида описывает одну или несколько поверхностей в трехмерном пространстве. Часто их называют полями превышений (height fields), поскольку значение зависимой переменной z графически представляется как высота соответствующей точки над уровнем координатной плоскости ху. В такой форме представляются данные топографической съемки, причем в качестве независимых переменных х и у выступают географические координаты
точки на местности (долгота и широта), а в качестве значения z — высота над уровнем моря. При визуализации функции / формируется изображение рельефа местности. Такого же рода изображение формируется и при отображении двухмерных данных {-, х}, которые изменяются во времени. Заменив у переменной времени /, можно наглядно, в виде поверхности, представить характер изменения данных во времени. Задав прямоугольную область изменения х и у, получим представление функции/в виде порции поверхности, как показано на рис. 12.1.
Далее рассмотрим неявно заданные функции двух переменных вида
g(^, У) = с,
где с— константа. Такое уравнение описывает одну или несколько двухмерных кривых на плоскости х,у. Может случиться и так, что пары значений х,у, которая удовлетворяла бы этому соотношению, и не существует. Как отмечалось в главе 10, хотя функция такого вида иногда может быть представлена аналитически, но общего метода выражения одной переменной через другую не существует. Знакомые всем примеры неявно заданных функций двух переменных — обобщенное уравнение квадратичной кривой (кривой конического сечения):
ax2+bxy+cy2+dx+ey+d = 0
и уравнение плоскости
ax+by+cz+d = 0.
Более сложный пример — уравнение овалов Кассини {Ovals of Cassini):
g(x,y) = (x2+y2+a2)2-4a2x2-b4.
В зависимости от значений коэффициентов а и b это уравнение описывает одну или две замкнутые кривые.
Методы визуализации обоих видов функции во многом связаны, поскольку при заданном значении с функция g описывает кривую, которая соответствует линии пересечения поверхности / плоскостью z = с, как показано на рис. 12.2. Такие кривые часто называются линиями уровня с {contour curved.
Рис. 12.1. Порция поверхности Z = f(X, у)Рис. 12.1. Порция поверхности Z = f(X, у)Рис. 12.2. Порция поверхности с нанесенными линиями уровняРис. 12.2. Порция поверхности с нанесенными линиями уровня
Рис. 12.1. Порция поверхности Z = f(X, у)
Рис. 12.1. Порция поверхности Z = f(X, у)
Рис. 12.2. Порция поверхности с нанесенными линиями уровня
Рис. 12.2. Порция поверхности с нанесенными линиями уровня
Сети
Самый общий способ визуализации функции вида Дх, у)— отображение поверхности. Процедура построения поверхности, которую мы рассматривали в главе 10, начиналась с определения множества опорных точек. Если значения функции известны только в отдельных точках (т.е. отсутствует аналитическое описание функции), то в нашем распоряжении массив выборок или результатов измерения:
2и=Лхьу,).
*В топографии и картографии линии уровня принято называть горизонталями. — Прим. ред.
Можно предполагать, что точки выборки расположены с постоянным шагом на плоскости ху:
х, = х0+/Ах, i = 0,..., N,
» ~ ^о^/ДУ» j = 0,...,M,
где Ах и Ду — шаг между выборками в направлении х и у. Если известен аналитический вид функции/, то значения в массиве формируются вычислением значения функции в точках х„ у. Но гораздо чаще приходится иметь дело с результатами измерений.
Проще всего сформировать поверхности, воспользовавшись форматом описания сети, состоящей либо из треугольных, либо из четырехугольных ячеек. Взяв четыре выборки zM, z^Vr г,+1/+] и zll+v расположенные в соседних элементах массива, можно сформировать либо одну четырехугольную, либо две треугольные ячейки сети. Таким образом, массив данных позволяет сформировать сеть, состоящую либо из NM четырехугольных ячеек, либо из 2NM треугольных. Разработать соответствующую программу в системе OpenGL не представляет большого труда. На рис. 12.3 показана сеть из четырехугольных ячеек, построенная по данным топографической съемки участка острова Гонолулу в группе Гавайских островов.
Рис. 12.3. Сеть, построенная по данным топографической съемки участка острова Гонолулу
Остановимся на некоторых аспектах формирования и модификации такой сети в OpenGL- программе. Во-первых, если использовать для формирования сети весь набор имеющихся данных, то в ней окажется очень много ячеек маленького размера. В результате плотность линий в изображении будет очень большой, что может привести к появлению муара. Следовательно, нужно каким-то образом отобрать из всего массива некоторый подмассив. Очевидное решение — брать не все выборки подряд, а, например, каждую k-ю, задавшись определенным значением к. Другой вариант — выполнить усреднение соседних выборок на какой-то базе усреднения. В любом случае должен быть сформирован массив меньшей размерности, что соответственно уменьшит размерность сети. Во-вторых, при формировании изображения, показанного на рис. 12.3, черным цветом вычерчивались линии контура ячеек, а белым цветом заливались многоугольники ячеек. Линии контура ячеек позволяют передать в изображении характер рельефа, а вычерчивать и заливать многоугольники нужно для того, чтобы удалить невидимые наблюдателю участки сети. Можно было бы воспользоваться алгоритмом удаления невидимых поверхностей, который имеется в составе системных средств OpenGL. Но в данном случае этого делать не стоит, поскольку данные, описывающие рельеф, уже упорядочены. Если формировать ячейки сети в порядке от более удаленных к ближним, то при заливке многоугольников, ближе расположенных к наблюдателю, автоматически удалятся контуры закрываемых ими ранее сформированных ячеек, которые таким образом становятся невидимыми для наблюдателя. Если в графической системе стандартные средства удаления невидимых поверхностей реализованы про-
граммно, то отказ от их использования в данном случае позволит повысить скорость формирования изображения. Данные, организованные таким образом, получили название множества данных размерностью 2.5 (2.5 dimensional data sets'), поскольку, хотя они и представляют точки трехмерного пространства, специфическая организация данных позволяет использовать эффективные алгоритмы их отображения. Но учтите, подобная структура данных не позволяет отображать трехмерные поверхности произвольного вида.
При формировании изображения, представленного на рис. 12.3, использован еще один специфический прием, который вы сможете в дальнейшем применять и в собственной практике. Одновременное вычерчивание многоугольника и контура ячейки выполняется следующим фрагментом программы:
glColor3f(1.0, 1.0, 1.0);
glBegin(GL_QUADS)
glVertex3i(i, z[i][j],j);
glVertex3i(i+l, z[i+l][j],j);
glVertex3i(i+1, z[i+1][j+1],j+1);
glVertex3i(i, z[i][j+1],j+1);
glEnd();
glColor3f(0.0, 0.0, 0.0);
glBegin(GL_LINE_LOOP)
glVertex3i(i, z[i][j],j);
glVertex3i(i+l, z[i+l][j],j);
glVertex3i(i+l, z[i+l ] [j+1],j+1);
glVertex3i(i, z [ i][j+1],j+1);
glEnd();
Следовательно, и контур ячейки, и многоугольник лежат в одной и той же плоскости. Хотя контур ячейки вычерчивается и после отображения многоугольника, неточность вычислений может привести к тому, что часть линий контура окажется перекрытой заливкой соседнего многоугольника, частично использующего те же вершины. В OpenGL отсутствует режим совмещенного отображения контура многоугольника и его внутренней области разными цветами. Но тот же эффект можно получить, воспользовавшись режимом смещения многоугольника (polygon offset mode). Для этого следует предварительно установить параметры смещения: glEnable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(1.0, 0.5);
Первый параметр функции glPolygonOf f set() задает смещение, а второй — зависимое от реализации значение масштабного коэффициента. В результате изображение контура многоугольника будет несколько придвинуто к наблюдателю по сравнению с изображением внутренней области многоугольника, а потому обязательно будет видимо.
Изображение сети можно модифицировать множеством способов, добиваясь того или иного изобразительного эффекта. Если в изображение сети включены только многоугольники (т.е. не формируется изображение контуров ячеек), то можно включить в сцену источники света и выполнить тонирование. В результате получим полутоновое изображение рельефа, хорошо передающее его объемность. Управляя положением источников света, можно даже моделировать вид рельефа в разное время суток. Для вычисления нормалей, которые необходимы для выполнения тонирования по описанным в главе 6 алгоритмам, используются координаты вершин ячеек сети. Еще более интересный эффект дает наложение правильно выбранной текстуры.
Если количество исходных данных достаточно велико, то при их отображении часто используется какой-либо алгоритм сглаживания или сплайн-аппроксимации из тех, что были описаны в главе 10.
Вычерчивание линий уровня
Трехмерное изображение поверхности, представленной в виде сети, очень наглядно, но, к сожалению, не дает возможности количественно оценить значения функции в отдельных точках или в окрестности точки. Кроме того, часть поверхности бывает скрыта от наблюдателя (находится на “обратном склоне”). Оценить поведение исследуемой функции количественно позволит ее графическое представление на двухмерной плоскости линиями уровня — кривыми, соответствующими фиксированному значению функции.
На раннем этапе развития компьютерной графики, когда отсутствовали растровые системы отображения и не было достаточно развитого программного обеспечения для формирования трехмерного изображения, представление функции линиями уровня было основным. Этот метод пришел в компьютерную графику из топографии, где именно таким способом на топографической карте представляется рельеф земной поверхности. Этот метод достаточно распространен до сих пор, хотя сейчас он используется в качестве дополнения к трехмерному изображению рельефа.
Если функция /задана аналитически, то для построения линии уровня необходимо найти решение уравнения
Лх,у") = с
при определенном значении параметра с. Чаще всего на одном изображении выводится несколько линий уровня, соответствующих разным значениям с. Таким образом, все сводится к решению уравнения, заданного в неявной форме:
g(x,D =J(x,y)-c = 0.
Например, если
Му^хЧу1,
то при положительном с каждая линия уровня есть окружность радиуса 4с .
Если в нашем распоряжении имеется не аналитическая формула для/^у), а массив выборок, то мы сталкиваемся с аналогичной проблемой, но решать ее необходимо на основе аппроксимации массива выборок. К слову сказать, даже при аналитическом задании функции Дх,у^ получить аналитическое решение уравнения^, уУ-с = 0 удается чрезвычайно редко. На практике чаше всего в этом случае формируется все тот же массив выборок УнА' а Уже п0 не" му строятся линии уровня. Эту задачу мы будем решать с помощью метода маркированных квадратов, который является частным случаем метода маркированных кубиков (этот метод будет подробно рассмотрен в разделе 12.4).
Метод маркированных квадратов
Предположим, что имеется массив {А,у ^ХпУу)} выборок функции fix, у\ сформированный на регулярной сетке (или решетке) по независимым переменным х и у:
X, = x0+z'Ax, / = 0, 1,..., N-\,
У) = у0+/Ду, j = °, 1 v, М-\,
где Дх и Ду— шаг между выборками в направлении х и у. Постоянный шаг выборок нам нужен только для упрощения рассуждений. Массив выборок может быть получен и путем непосредственного измерения некоторой физической величины с постоянным шагом по пространству и во времени, а если речь идет об интенсивности какого-либо излучения, то шаг определяется расположением элементарных ячеек в матрице приемника излучения.
Предположим, что необходимо найти точки на кривой, неявно заданной уравнением z =jVx,y^ при z = с. Для определенного значения с функция у(х, у) может вообще не иметь линии уровня, иметь единственную линию или множество линий. Стратегия построения кусочно-линейной аппроксимации линии уровня состоит в следующем.
Процесс начинается с некоторой прямоугольной ячейки, определенной четырьмя узлами сетки выборок (хьу(\ (x,+],yj), (jc,+b-y/+1), (хьу^\ как показано на рис. 12.4. В общем случае может оказаться так, что линия уровня проходит через ячейку, хотя ни одно из значений функции в углах ячеек (соответствующих узлах сети) не будет равно с.
Рассмотрим простой случай, когда одно из значений в углах ячейки, скажем fM, оказывается больше с, а значения в других углах — меньше с:
Л>с»
< с»
f^\,i+\ <
Лн <с-
Эту ситуацию можно схематически представить либо так, как на рис. 12.5,а, где соотношения для каждого узла выведены в виде надписей, либо так, как на рис. 12.5,6, где используется раскраска (маркировка) углов ячейки: белым цветом маркируется угол, в котором значение больше заданного, а черным — углы, в которых значения .меньше заданного. Очевидно, что в этом случае кривая линии уровня должна пересечь два ребра ячейки, прилегающих к “белому” углу. Это же должно произойти и в том случае, когда значение в одном углу ячейки будет меньше с, а в соседних с ним — больше. Такая ситуация показана на рис. 12.6,а.
К. У/ + 1)К. У/ + 1)(х„ У/)(х„ У/)'„о'„оРис. 12.5. Маркировка углов ячейкиРис. 12.5. Маркировка углов ячейки(X, * 1, у,)(X, * 1, у,)Рис. 12.4. Прямоугольная ячейкаРис. 12.4. Прямоугольная ячейка
К. У/ + 1)
К. У/ + 1)
(х„ У/)
(х„ У/)
'„о
'„о
Рис. 12.5. Маркировка углов ячейки
Рис. 12.5. Маркировка углов ячейки
(X, * 1, у,)
(X, * 1, у,)
Рис. 12.4. Прямоугольная ячейка
Рис. 12.4. Прямоугольная ячейка
На рис. 12.6,а показано единственное пересечение ячейки линией уровня. Но возможен вариант, когда линия уровня пересекает ребро ячейки три раза (рис. 12.6,6), а в общем случае — любое нечетное число раз. Но мы всегда будем интерпретировать переход линии уровня через ребро ячейки как однократное, следуя одному из принципов Питера: если нечто можно объяснить по-разному, то самое простое объяснение будет и самым вероятным.
Рис. 12.6. Пересечение ячейки линией уровня: а — однократное; б — многократноеРис. 12.6. Пересечение ячейки линией уровня: а — однократное; б — многократное
Рис. 12.6. Пересечение ячейки линией уровня: а — однократное; б — многократное
Рис. 12.6. Пересечение ячейки линией уровня: а — однократное; б — многократное
Если мы сможем оценить, в каких именно точках ребер линия уровня пересекает ячейку, то сможем соединить эти точки отрезком прямой и далее сразу вывести его на экран, поскольку анализ отдельных ячеек выполняется независимо. Но где установить точки пересечения? Тут возможны две стратегии. Можно просто установить точку пересечения на середине ребра между “белой” и “черной” вершинами. Но если значение а функции в вершине (х„ уд лишь незначительно больше с, а значение b в соседней вершине (x,+ l,yz) значительно меньше с, то вполне резонно именно в таком отношении разделить ребро точкой пересечения. По такому принципу интерполируется положение точки пересечения с ребром. Рассмотрим, например, две соседние вершины ячейки, имеющие противоположную маркировку, т.е. значение функции в одной вершине больше с, а в другой — меньше с.
Дх„ у,) = а,а>с,
Кх,^уд = Ь,Ь<с.
Если шаг независимой переменной х равен Ах, то можно вычислить положение точки пересечения, линейно интерполируя перепад между значениями а и b на интервале Дх (рис. 12.7).
Рис. 12.7. Определение точки пересечения линии уровня с ребром ячейки
В результате получим:
alkx х = х, +.
а-Ь
Точку пересечения на втором ребре найдем аналогично, а затем соединим эти точки отрезком и получим один из сегментов кусочно-линейной аппроксимации линии уровня.
До сих пор мы анализировали только один из возможных вариантов маркировки вершин ячейки— одна вершина окрашена в белый цвет, а остальные— в черный. Всего возможно 24 = 16 вариантов маркировки. Все они представлены на рис. 12.8. Там же вы увидите и совместимый с такой маркировкой вариант пересечения ячейки линией уровня. Внимательный анализ этих вариантов показывает, что в них имеются два типа симметрии. Один тип — симметрия относительно поворота. Все варианты, которые могут быть преобразованы к единому путем поворота, как, например, варианты 1 и 2, характеризуются тем, что линия уровня “отсекает” один из углов ячейки. Существует еще один тип симметрии, который образуется, если инвертировать маркировку вершин, — белые сделать черными, а черные — белыми, как, например, в вариантах 0 и 15. Если принять во внимание подобные типы симметрии, то 16 возможных вариантов сводятся к четырем. Эти варианты показаны на рис. 12.9. Следовательно, нам нужно разработать программу, которая сможет вычерчивать сегмент линии уровня в этих четырех случаях, и с ее помощью можно будет отобразить все 16 возможных вариантов.
Первый вариант тривиальный, поскольку в этом случае линия уровня не пересекает ячейку. Второй вариант мы уже рассмотрели чуть выше. Третий вариант также довольно прост— нужно вычертить сегмент, который пересекает ячейку от одного ребра до противоположного.
Рис. 12.8. 16 вариантов пересечения ячейки линией контура
Последний, четвертый, вариант наиболее сложен и наиболее интересен — его можно интерпретировать по-разному (рис. 12.10), и нам предстоит выбрать одну из возможных интерпретаций. При отсутствии дополнительной информации нельзя отдать предпочтение ни одному из представленных вариантов, а потому следует выбрать любой из них наудачу, случайно. Можно и заранее выбрать один способ интерпретации, а о другом “забыть навсегда”. Но на рис. 12.11 показано, что результаты могут существенно отличаться, если предпочесть один вариант другому.
Чтобы разрешить неопределенность, придется выполнить дополнительные расчеты. Разделим “подозрительную” ячейку на четыре части (рис. 12.12) и рассчитаем значение функции в центральной точке. Если функция задана аналитически, это делается очень просто, а если в
нашем распоряжении только экспериментальные данные, то можно получить “воображаемое” значение, интерполируя по какому-либо закону уже имеющиеся данные в вершинах исходной ячейки. Скорее всего, что ситуация в новых ячейках будет однозначной. Если это не так, то “подозрительную” четвертушку придется опять разделить.
Рис. 12.10. Неоднозначная интерпретация маркировки вершин ячейкиРис. 12.10. Неоднозначная интерпретация маркировки вершин ячейкиРис. 12.11. Пример разной интерпретации одного и того же варианта маркировки вершин ячейкиРис. 12.11. Пример разной интерпретации одного и того же варианта маркировки вершин ячейкиРис. 12.12. Разбиение ячейкиРис. 12.12. Разбиение ячейки
Рис. 12.10. Неоднозначная интерпретация маркировки вершин ячейки
Рис. 12.10. Неоднозначная интерпретация маркировки вершин ячейки
Рис. 12.11. Пример разной интерпретации одного и того же варианта маркировки вершин ячейки
Рис. 12.11. Пример разной интерпретации одного и того же варианта маркировки вершин ячейки
Рис. 12.12. Разбиение ячейки
Рис. 12.12. Разбиение ячейки
Программный код, реализующий этот метод, весьма незамысловат. В программе организуется цикл по всем ячейкам. Функция се11(), текст которой приведен ниже, определяет тип ячейки — один из шестнадцати. Аргументы функции — а, Ь, с и d — значения выборок, соответствующих вершинам ячейки.
int cell(double a, double b, double с , double d)
/* THRESHOLD = значение уровня для формируемой линии */ int п=0;
if(a>THRESHOLD) п+=1;
if(b>THRESHOLD) п+=8;
if(OTHRESHOLD) п+=4;
if(d>THRESHOLD) n+=2;
return n;
Теперь нужно отнести ячейку к одному из четырех уникальных типов. Это выполняется оператором логического анализа switch ():
switch(n)
{
case 1: case 2: case 4: case 7:
case 8: case 11: case 13: case 14:
/* Линия уровня отсекает один угол */
draw_one(num, i,j,a,b,c,d);
break;
case 3: case 6: case 9: case 12:
/* Линия уровня пересекает ячейку от одного ребра до противоположного */ draw_adjacent(num,i,j,a,b,c,d);
break;
case 5: case 10:
/* Неоднозначный вариант */ draw_opposite(num, i,j,a,b,c,d); break;
case 0: case 15:
/* Линия не пересекает ячейку */ break;
}
Теперь разработаем три функции, которые будут вычерчивать сегмент, — каждая в своем варианте. Например, функция draw_one() будет вычерчивать сегмент, проходящий между двумя соседними ребрами ячейки. Переменные s_x и s_y — размеры ячейки в направлении осей х и у соответственно, а переменные ох и оу — координаты левого нижнего угла ячейки (рис. 12.13). Программный код этой
функции представлен ниже.(ох, оу)(ох + $„ оу)
Рис. 12.13. Вычерчивание линейного сегмента
void draw_one(int num, int i, int j, double a, double b, double c, double d)
{
/* num = тип ячейки */
/* i, j = индексы в массиве ячеек */
/* a, b, с, d = значения функции в четырех вершинах ячейки */
/* s_x, s_y = длины ребер ячейки */
/* Здесь необходимо вычислить координаты левого
нижнего угла ячейки по значениям индексов i и j */
switch(num) /* тип варианта */
case 1: case 14: xl=ox; yl=oy+s_y; x2=ox+s_x; y2=oy;
break;
/* Другие варианты */
}
/* Вычертить сегмент */
glBegin(GL_LINES);
glVertex2d(xl, yl);
glVertex2d(x2, y2);
glEnd();
}
Полный текст программы вы найдете в файле contour.с, который находится на ftp-cep вере по адресу ftp.cs.unm.edu в каталоге /pub/angel/ВООК. На рис. 12.14 показаны две ли нии уровня для функции овалов Кассини
Дх, у) = ^х~+у‘+а"У-4а"х‘-Ь ,
рассчитанные при значениях я=0,49 и Ь=0,5. В том изображении, которое показано на рисун
ке слева, использовался простейший вариант но, как сглаживается линия уровня, если применяется линейная интерполяция. Функция была представлена массивом выборок размером 50 x 50. На рис. 12.15 показаны линии уровня (топографические горизонтали), построенные по данным топографической съемки острова Гонолулу в группе Гавайских островов.
деления ребра ячейки пополам, а справа показа-деления ребра ячейки пополам, а справа показа-Рис. 12.15. Топографические горизонтали на карте острова ГонолулуРис. 12.15. Топографические горизонтали на карте острова ГонолулуРис. 12.14. Линии уровня овала Кассини: а — построенная с помощью алгоритма деления ребра пополам; б — построенная с помощью интерполяции выборокРис. 12.14. Линии уровня овала Кассини: а — построенная с помощью алгоритма деления ребра пополам; б — построенная с помощью интерполяции выборокСуществуют и другие методы построения линий уровня. Один из них состоит в том, что сначала отыскивается любая ячейка, через которую проходит линия, а затем отслеживается ее траектория, целенаправленным переходом из текущей ячейки в ту, куда “ушла” линия конту-
деления ребра ячейки пополам, а справа показа-
деления ребра ячейки пополам, а справа показа-
Рис. 12.15. Топографические горизонтали на карте острова Гонолулу
Рис. 12.15. Топографические горизонтали на карте острова Гонолулу
Рис. 12.14. Линии уровня овала Кассини: а — построенная с помощью алгоритма деления ребра пополам; б — построенная с помощью интерполяции выборок
Рис. 12.14. Линии уровня овала Кассини: а — построенная с помощью алгоритма деления ребра пополам; б — построенная с помощью интерполяции выборок
ра. Этот метод кажется более производительным, поскольку анализируются не все ячейки подряд, а только те из них, через которые обязательно проходит линия уровня (раз уж она “нырнула” в ячейку, то обязательно должна из нее и “вынырнуть”), но он таит опасность упустить один из контуров линии уровня. Кроме того, достоинство рассмотренного выше метода в том, что анализ одной ячейки не зависит от другой, а следовательно, при наличии соответствующих средств ячейки можно анализировать параллельно. Кроме того, рассмотренный метод без особого труда переносится в трехмерное пространство, о чем вы узнаете в разделе 12.4. С некоторыми вариантами этого метода вы сможете познакомиться и в литературе, рекомендуемой в конце главы для самостоятельной проработки.
Визуализация поверхностей и скалярных полей
Задача отображения значения функции двух переменных и линий уровня, которую мы рассматривали в предыдущем разделе, можно расширить на случай трех переменных. Рассмотрим функцию/, определенную в пространстве трех независимых переменных. В каждой точке пространства функция характеризуется некоторым скалярным значением^, у, г), и мы говорим, что функция / определяет скалярное поле {scalar field). Например, функция / может характеризовать плотность вещества в каждой точке пространства или интенсивность поглощения рентгеновских лучей тканями тела человека, которая измеряется методами компьютерной томографии, или коэффициент прозрачности в каждой точке стеклянной отливки. Визуализация скалярного поля — задача намного более сложная, чем визуализация функции двух переменных, и на то есть две основные причины. Во-первых, скалярное поле характеризуется намного большим массивом данных, следовательно, для выполнения даже рутинных операций вроде чтения файла или канонических преобразований приходится изобретать специальные ускоренные методы. Во-вторых, при визуализации функции двух переменных можно использовать третью координату трехмерного пространства для представления значения функции. В результате получаем наглядное изображение поведения функции на области изменения независимых переменных в виде рельефа. При работе с функцией трех переменных руки у нас связаны — все три координаты трехмерного пространства уже заняты тремя независимыми переменными. Тем не менее можно распространить рассмотренные ранее методы и на визуализацию скалярных полей.
Объемное множество данных
Как и в рассмотренной задаче визуализации функции двух переменных, будем считать, что в нашем распоряжении имеется массив значений функции, сформированный либо по результатам измерений реального процесса, либо вычислением аналитически заданной функции в точках {х„ур zkf Такой массив будем называть объемным множеством данных {volumetric data set).
Как и прежде, будем считать, что выборки сняты на равномерной сетке по координатам х,у, z, но на сей раз трехмерной (рис. 12.16). Предположение о равномерности сетки не является принципиальным, но оно упростит понимание сути предлагаемого метода. Итак, имеем
fijk ~ f^if Ур ~кУ
где
х, = х0+/Дх,
У) = У^^
2, = z0+kAz.
Каждое значение fjk можно считать результатом усреднения скалярного поля в правильном параллелепипеде со сторонами Дх, Ду, Дд, центр которого находится в точке {х„ у„ zk). Такой
элементарный параллелепипед будем называть векселем (voxel) по аналогии с пикселем —
элементарной областью двухмерной плоскости.
Трехмерный массив значений скалярного поля, соответствующих вокселям, называется структурированным множеством данных (structured data set), поскольку при его хранении нет
Рис. 12.16. Объемное множество данныхРис. 12.16. Объемное множество данныхсмысла хранить еще и информацию о размещении каждой выборки в пространстве. Термины структурированное множество данных и множество вокселей (set of voxels) зачастую используют как синонимы.
Рис. 12.16. Объемное множество данных
Рис. 12.16. Объемное множество данных
Данные, снятые в произвольных точках пространства, должны храниться совместно с координатами соответствующих точек, а потому их принято называть неструктурированным множеством данных. Визуализация неструктурированного множества данных выполняется несколько сложнее, но, в конце концов, все сводится к тем же самым методам. Поэтому эту проблему мы специально исследовать не будем.
Способов наглядного представления скалярного поля можно придумать даже больше, чем способов отображения функции двух переменных. Однако из
всего множества наибольшую популярность завоевали два — непосредственное отображение объема и построение изоповерхностей. При непосредственном отображении объема в формировании изображения “принимает участие” каждый воксель, а при построении изоповерхностей в конкретном изображении используется только подмножество вокселей. Для функции Дх, у. л) изоповерхность определяется уравнением в неявной форме:
Дх, у, л) = с.
Значение константы с — уровень изоповерхности. Наша задача — отыскать приемлемый способ аппроксимации изоповерхности по заданному множеству вокселей.
Визуализация функций, заданных в неявной форме
Задача визуализации изоповерхностей функции трех переменных является естественным расширением задачи построения линий уровня функции двух переменных. Рассмотрим функцию трех переменных, заданную в неявной форме:
g(x, у, л) - О,
где функция g представляет собой некоторое аналитическое выражение. Множество точек, удовлетворяющих этому уравнению, образуют одну или несколько трехмерных поверхностей. Общеизвестными примерами являются сфера, плоскость, квадратичные поверхности, тор радиуса г, имеющий радиус поперечного сечения а:
(х* + у" + л" - г" - а )" - 4а (г* - л") = 0.
Хотя функция g и позволяет выяснить, принадлежит ли точка (х, у, л) заданной поверхности, при построении поверхности она может играть только вспомогательную роль. Нам придется самостоятельно изобрести метод построения поверхности.
Один из способов основывается на упрощенной форме трассировки лучей, который получил название приведения луча (ray casting). На рис. 12.17 показаны наблюдатель, поверхность, соответствующая функции, и картинная плоскость. Любой проецирующий луч можно описать параметрическим уравнением
р(0 = ро + /d.
Перепишем это уравнение в виде системы уравнений для компонентов:
х(0 = х0 + td„
ЛО=уо +tdy, z^ = z0 + td..
Подставив их в неявное уравнение поверхности, получим скалярное уравнение относительно неизвестной /:
g(xo + tdx, у о + tdy, Zq + td.) = м(/) = 0.
Решением уравнения является точка пересечения проецирующего луча с поверхностью, причем решение может быть не единственным. Последнее означает, что луч пересекает поверхность многократно. Если g— простая функция, например уравнение квадратичной поверхности или тора, то уравнение «(/) можно решить аналитически. Если g представляет квадратичную поверхность, то и является квадратным уравнением относительно неизвестной / и имеет не больше двух действительных корней, которые находятся по известному алгебраическому выражению, знакомому всем со школьной парты. Если же функция g имеет более сложный вид, например полинома более высокой степени, или включает тригонометрические или экспоненциальные функции, то решать уравнение придется численными методами.
Рис. 12.17. Приведение луча при отображении поверхности, заданной функцией в неявном виде
Вычислив координаты точки (точек) пересечения проецирующего луча с поверхностью, мы можем применить к этой поверхности одну из моделей закрашивания. Нужные для этого компоненты вектора нормали можно найти с помощью частных производных функции g в точке пересечения:
^g(x,y,z)дх
n= dg^y.z) ду
dg(x,y,z)
Обычно при отображении таких поверхностей нас не очень заботит проблема глобального освещения. Следовательно, нет необходимости вычислять параметры теней, т.е. опреде-
пять, освещается ли данная точка поверхности от определенного источника, или параметры отраженных лучей. Для сцен, состоящих из простых квадратичных поверхностей, приведение лучей является не только способом отображения, но и позволяет проанализировать видимость поверхности и часто используется в сочетании с моделями, созданными в рамках конструктивной геометрии тел. Но для функций, более сложных, чем квадратичные, объем вычислений, требуемых для определения точки пересечения проецирующего луча с поверхностью, становится основным лимитирующим фактором, и приходится искать альтернативные методы отображения.
Сформулируем более общую постановку задачи — отображать не поверхности, а объемы, т.е. рассматривать не поверхность, описываемую уравнением g^x, у, z) = 0, а скалярное поле Дх, у, z), определенное в каждой точке пространства. Если нас интересует только определенное значение функции /, скажем значение с, то в этом случае мы имеем дело с задачей отображения изоповерхности скалярного поля:
gU, у, z) -Дх, у, z) - с = 0.
Иногда вполне достаточно сформировать изображение одной изоповерхности. В частности, это относится к приложениям компьютерной томографии, где пользователя интересуют области с определенным уровнем поглощения тканями рентгеновских лучей. Но существуют и приложения, в которых необходимо выводить на экран изображения нескольких изоповерхностей.
Ниже мы рассмотрим метод построения изоповерхности скалярного поля, заданного объемным множеством данных — выборок, измеренных на равномерной сетке. Этот метод получил название метода маркированных кубиков и является расширением метода маркированного квадрата на трехмерное пространство независимых переменных.
Изоповерхности и метод маркированных кубиков
Пусть имеется массив выборок \Дчк\ скалярного поля Дх, у, z)— множество вокселей. Требуется по этим выборкам построить изоповерхность в форме полигональной сети. Для заданного значения с может существовать единственная изоповерхность, ни одной изоповерхности или несколько. Учитывая, что графическая система лучше всего справляется с задачей соображения трехмерных треугольников, разработаем метод, который позволит отыскать вершины изоповерхности и аппроксимировать ее множеством треугольников. Этот метод получил наименование метода маркированных кубиков.
Будем считать, что выборки сняты на равномерной сетке, узлы которой совпадают с центрами вокселей. Если это не так, то всегда можно использовать ту или иную процедуру ин
терполяции и получить из исходного массива неструктурированных данных множество во
кселей (см. упр. 12.7). Восемь соседних узлов пространственной сетки образуют одну ячейку в форме кубика (рис. 12.18). Вексель (ij, к) в углу этой ячейки имеет значение /ик. Теперь на основе имеющихся значений вокселей следует проанализировать, проходит ли изоповерхность через данную ячейку, и если проходит, то отыскать координаты вершин соответствующего участка.
Для данного значения уровня изоповерхности с можно промаркировать черным и белым цветом вершины ячейки, в зависимости от того, превышает
значение соответствующего векселя с или нет. Су
ществует 28=25б возможных вариантов маркировки отдельной ячейки. Но приняв во
внимание имеющуюся симметрию отдельных вариантов, все разнообразие сводится к
14 уникальным случаям (рис. 12.19). Точки пересечения поверхности с ребрами ячейки можно вычислить, используя тот же метод интерполяции, который применялся при обработке квадратных ячеек в методе маркированных квадратов (см. раздел 12.2). После определения координат точек пересечения формируется участок изоповерхности в виде одного или нескольких треугольников (рис. 12.20).
Рис. 12.20. Элементарные участки изоповерхности в ячейках векселей
Как и квадратные ячейки, которые раньше использовались для формирования линий уровня, кубические ячейки вокселей можно обрабатывать независимо друг от друга. Значение каждого внутреннего вокселя (т.е. вокселя, не расположенного по краям области существования скалярного поля) влияет на расположение участков изоповерхности в восьми соседних ячейках. Самый простой алгоритм — обрабатывать ячейки последовательно, строка за строкой, слой за слоем.
После обработки очередной ячейки сформированный в ней участок изоповерхности в виде одного или нескольких треугольников передается в конвейер визуализации графической системы. Поскольку этот алгоритм довольно просто можно распараллелить, метод маркированных квадратиков довольно широко используется для визуализации трехмерных данных. Этот метод одновременно служит и для сжатия информации, имеющейся в исходном массиве вокселей, и для моделирования. И имитационные программы, и программы отображения способны формировать множества вокселей объемом от 107 до 109 выборок. При таком объеме данных даже простые операции (считывание данных с диска, масштабирование или иные подобные преобра-
зования) становятся весьма обременительными для системы в смысле как временных, так и аппаратных ресурсов. Но в большинстве приложений после анализа множества вокселей и построения изоповерхности объем данных сокращается до 103-104 трехмерных треугольников. С таким количеством графических объектов современная система компьютерной графики справляется без особого труда. Построенную поверхность можно в реальном времени поворачивать, масштабировать, тонировать и т.д. В общем случае только небольшая часть вокселей принимает участие в формировании определенной изоповерхности, что и объясняет высокий коэффициент сжатия информации.
Изображение омара на ил. 12 цветной вклейки сформировано описанным методом. Изображение изоповерхности тонировано, что позволило наглядно передать мельчайшие детали ее рельефа.
о)б)в)о)б)в)Рис. 12.21. Проблема неоднозначности при анализе маркированной пространственной ячейки: а — .маркированная ячейка; бив — два варианта интерпретацииРис. 12.21. Проблема неоднозначности при анализе маркированной пространственной ячейки: а — .маркированная ячейка; бив — два варианта интерпретацииПри работе с маркированными кубиками возникает та же проблема неоднозначности, которую мы рассматривали выше применительно к маркированным квадратам. Неоднозначность появляется в том случае, когда воксели с одинаковой маркировкой расположены по диагонали грани ячейки. Рассмотрим вариант маркировки ячейки, показанный на рис. 12.21,а. На рис. 12.21, б,в показаны возможные варианты интерпретации такой маркировки. Совершенно очевидно, что эти варианты приводят к разному виду изоповерхности в районе этой ячейки. Неверный выбор проявится в виде разрыва изоповерхности, которая в других местах будет выглядеть довольно гладкой. Удовлетворительное решение этой задачи, которое одинаково хорошо срабатывало
о)б)в)
о)б)в)
Рис. 12.21. Проблема неоднозначности при анализе маркированной пространственной ячейки: а — .маркированная ячейка; бив — два варианта интерпретации
Рис. 12.21. Проблема неоднозначности при анализе маркированной пространственной ячейки: а — .маркированная ячейка; бив — два варианта интерпретации
бы во всех возможных случаях, до сих пор не найдено. Как и при работе с маркированными квадратами, для ее решения требуется дополнительная информация, которая отсутствует в исходном наборе выборок.
Непосредственное отображение объема
Представление скалярного поля набором изоповерхностей имеет недостаток, который является “продолжением достоинств” этой методики, — при построении изоповерхностей значительная часть информации, содержащейся во множестве вокселей, утрачивается (страницей выше мы отмечали это как достоинство метода, поскольку оно позволяет “сжимать информацию”). Следовательно, не исключается возможность того, что наиболее интересная часть информации ускользнет от внимательного глаза исследователя только потому, что соответствующий участок поля попал “между” изоповерхностями. Метод непосредственного отображения объема (direct volume rendering) позволяет сформировать изображение, в котором “принимают участие” все воксели исходного множества. Эта методика является развитием методов создания совмещенного изображения, о которых шла речь в главе 9. Поскольку, как правило, воксели образуют регулярную пространственную сетку, то при известном положении наблюдателя всегда можно отыскать такой порядок просмотра вокселей, который позволит формировать изображение либо от ближайших объектов к более дальним, либо в обратном порядке — от более дальних к ближайшим.
В самых первых разработках методов этого типа воксель рассматривался как маленький кубик, который может быть либо совершенно прозрачным, либо абсолютно непрозрачным (поглощающим свет). Если изображение формировалось в порядке от ближних объектов к дальним, то прослеживалась траектория лучей, проходящих через каждый пиксель картинной плоскости, пока на пути луча не возникал непрозрачный воксель, соответствующий
пиксель окрашивался в черный цвет. Если на пути луча нигде не встречался непрозрачный воксель, пиксель окрашивался в белый цвет. Если же изображение формировалось в порядке от дальних объектов к ближним, то алгоритм “маляра” закрашивал на экране только области, соответствующие непрозрачным вокселям. В любом варианте качество сформированного изображения было весьма далеко от идеального. Существенное повышение качества было достигнуто только после применения палитры цветов и варьируемого коэффициента прозрачности.
Управление цветом и коэффициентом прозрачности
Описание методов непосредственного отображения объемов начнем с анализа методов управления цветом и коэффициентом прозрачности вокселей. В качестве примера рассмотрим данные, полученные при компьютерной томографии головы человека. Значению меры поглощения рентгеновского излучения поставим в соответствие определенные цвета. Мягкие ткани, которые характеризуются низкой поглощающей способностью, могут быть окрашены в красный цвет, ткани со средней поглощающей способностью — в синий, а ткани с высоким
поглощением — в белый. Ткани, прозрачные для рентгеновского излучения, окрашиваются в
черный цвет. Чаще всего соответствие цветов значениям интенсивности скалярного поля ус танавливается в результате анализа гистограммы распределения интенсивностей. Гистограм' ма, представленная на рис. 12.22, имеет че
тыре пика. Каждому из пиковых значений п
Интенсивность поляИнтенсивность поляИнтенсивность поляИнтенсивность поляРис. 12.23. Кривые интенсивности основных цветов для цветового представления интенсивности скалярного поляРис. 12.23. Кривые интенсивности основных цветов для цветового представления интенсивности скалярного поляможно назначить определенный цвет и, используя механизм индексирования цветов, сформировать таблицу соответствия цвета. Интенсивность каждого из основных цветов — красного, синего и зеленого — может линейно изменяться в каком-либо интервале значений плотности скалярного поля, как показано на рис. 12.23. Если выборки скалярного поля получены с помощью компьютерной томографии головы, то, скорее всего, костям черепа будут соответствовать значения интенсивности более низкого пика в левой части гистограммы и им мы назначим белый цвет, а пустым пространствам — значения/крайнего правого пика, и им мы назначим черный цвет.
Интенсивность поля
Интенсивность поля
Интенсивность поля
Интенсивность поля
Рис. 12.23. Кривые интенсивности основных цветов для цветового представления интенсивности скалярного поля
Рис. 12.23. Кривые интенсивности основных цветов для цветового представления интенсивности скалярного поля
Рис. 12.22. Гистограмма данных, полученных при компьютерной томографии
Значения коэффициентов прозрачности присваиваются исходя из того, какие воксели желательно подчеркнуть в изображении поля. Если желательно выделить в изображении ткани мозга, а кости черепа сделать “прозрачными”, то следует назначить наибольшее значение коэффициента прозрачности именно значениям интенсивности, характерным для костных тканей. В принципе назначение цветов и коэффициентов прозрачности выполняется на основе методов, исследуемых в теории распознавания образов, а потому в данной книге мы не будем в них углубляться. В графических системах разработчики выходят из положения следующим образом — в распоряжение пользователя предоставляются средства ручной настройки таблиц соответствия цветов и коэффициента прозрачности. Это позволяет ему на свой вкус и на основании собственного опыта настраивать систему отображения. Нас же больше интересует, как сформировать изображение после того, как подобная настройка уже каким-то способом выполнена.
Отображение скалярного поля с помощью отпечатков
После того как со значениями интенсивности поля будут ассоциированы цвета и коэффициенты прозрачности, нам нужно ассоциировать с каждым вокселем геометрический объект определенной формы и далее применить методы совмещения изображений, описанные в главе 9. Один из них основывается на методике отображения объектов от дальнего к ближнему (алгоритм “маляра”). Рассмотрим группу вокселей, показанную на рис. 12.24. Обычно, когда мы говорим ближний, имеется в виду ближний по отношению к наблюдателю. Для трехмерного скалярного поля, если уж мы расположили наблюдателя относительно осей этого поля, термин ближний будет определять порядок обработки массива вокселей. Эффективный механизм хранения данных о множестве вокселей, который позволяет изменять порядок обработки в зависимости от положения наблюдателя, предоставляет в наше распоряжение 8-арные деревья (octrees). Их мы рассматривали в главе 8. Порядок обхода такого 8-арного дерева определяется размещением наблюдателя.
Проще всего сформировать изображение, наглядно передающее информацию о форме скалярного поля, с помощью отпечатков (footprint). Каждый воксель представляется в виде простой геометрической фигуры, и эти фигуры проецируются на картинную плоскость. На рис. 12.25 показаны воксель в виде сферы и его отпечаток на картинной плоскости. Обращаю ваше внимание на то, что если используется параллельная проекция и все воксели представлены одинаковыми геометрическими объектами, то их отпечатки будут отличаться только цветом и прозрачностью. Следовательно, можно обойтись без проецирования каждого вокселя, а заменить эту операцию наложением растровых образов отпечатков на изображение, формируемое в буфере кадра.
Форма отпечатка выбирается исходя из требований теории дискретных выборок, подобных тем, которые мы рассматривали в главе 9. Если процесс выборки данных считать идеальным, то каждый отпечаток должен иметь вид трехмерной функции sine. Использование гексагональных или эллипсоидных отпечатков основано на аппроксимации вокселей параллелепипедами или эллипсоидами. Лучший результат можно получить, если использовать аппроксимацию отпечатками в виде функции Гаусса, которые получаются при проецировании трехмерного геометрического объекта в форме функции sine.
Ключевым при формировании изображения, состоящего из отпечатков отдельных вокселей, является метод наложения отпечатков друг на друга с учетом их прозрачности. Поскольку исходные данные представляют собой выборки на регулярной сетке, они изначально отсортированы по расстоянию до наблюдателя или до картинной плоскости. Воксели можно обрабатывать в порядке от дальних к ближним, добавляя “взнос” каждого в формируемое изображение посредством наложения его отпечатка. Сначала формируется фоновое изображение, а затем в работу включается механизм альфа-смешивания отпечатков. Рассмотрим формулу смешивания полупрозрачных пикселя-источника и пикселя-приемника:
С^ = (1-а5)С<, + a5C„
(V^l-uOoj + a,.
Если обработка вокселей идет в порядке от дальних к ближним и пиксель-источник непрозрачен (a, = 1), то пиксель-приемник также становится непрозрачным и ему присваивается цвет пикселя-источника. Если же пиксель-источник прозрачен, то цвет и коэффициент прозрачности пикселя-приемника сохраняются. Для реализации такого метода формирования изображения можно воспользоваться функцией смешивания OpenGL, заменив значение цвета С каждого вокселя значением аС и используя в качестве коэффициентов смешивания пикселя-источника и пикселя-приемника соответственно значения 1 и l-av Основное преимущество метода формирования изображения от дальних вокселей к ближним состоит в том, что можно обрабатывать данные послойно, как бы накладывая друг на друга двухмерные изображения. При этом даже нет нужды держать весь массив вокселей в оперативной памяти в процессе формирования изображения. Правда, “эта медаль” имеет и обратную сторону — при послойной обработке утрачивается внутренняя связь между соседними вокселями, расположенными в разных слоях, а учет этих связей потенциально мог бы несколько ускорить процесс. Если, например, ближайшие к наблюдателю слои непрозрачны, то только они и будут присутствовать в окончательном изображении, а время, затраченное на обработку дальних слоев, оказывается потраченным впустую. .
Трассировка лучей в скалярном поле
Альтернативой описанному методу является формирование изображения в порядке от ближних вокселей к дальним посредством трассировки лучей (рис. 12.26). Пользуясь теми же формулами смешивания, можно определить момент, когда трассируемый луч “упрется” в непрозрачный воксель, и прекратить после этого дальнейшую трассировку данного луча. Недостаток алгоритма в том, что трассировка каждого луча требует просмотра массива вокселей “в глубину”, а следовательно, приходится держать весь массив исходных данных в оперативной памяти.
Выбор между методом обработки вокселей в порядке от дальних к ближним или от ближних к дальним во многом напоминает выбор между методами отображения, опирающимися на пространство изображения или на пространство объектов. Фактически мы просто добавили в этот процесс учет прозрачности. Следовательно, алгоритм трассировки лучей в объеме скалярного поля способен на основании того же массива данных создавать изображение, в котором наглядно передается объемность. Но за это приходится платить увеличением коли-
чества вычислительных операций, поскольку при любом изменении параметров наблюдателя необходимо полностью повторить все расчеты. Поэтому такой алгоритм не применяется в приложениях, предполагающих интенсивный диалог с пользователем.
Рис. 12.26. Приведение лучей: а — трехмерный вид; б — вид сверхуРис. 12.26. Приведение лучей: а — трехмерный вид; б — вид сверху
Рис. 12.26. Приведение лучей: а — трехмерный вид; б — вид сверху
Рис. 12.26. Приведение лучей: а — трехмерный вид; б — вид сверху
Рис 12.27. Формирование многоугольников в трехмерной памяти текстурРис 12.27. Формирование многоугольников в трехмерной памяти текстур
Рис 12.27. Формирование многоугольников в трехмерной памяти текстур
Рис 12.27. Формирование многоугольников в трехмерной памяти текстур
Наложение текстуры на объем
Технология наложения текстур играет весьма важную роль в приложениях, использующих анимацию, а потому современные графические системы комплектуются специальной памятью довольно большого объема для хранения и обработки текстур. Если наложение текстуры
выполняется в основном на аппаратном уровне, то это практически не сказывается на скоро-
сти формирования окончательного изображения. Если же речь идет о программной реализации этого процесса, то в версию OpenGL 1.2 включены средства поддержки наложения трехмерных текстур.
Предположим, что в графической системе достаточно памяти для работы с текстурами, что позволяет разместить в ней весь массив данных скалярного поля. В таком случае можно определить множество плоскостей, параллельных картинной и рассекающих объем скалярного поля, и отобразить координаты текстуры на мировые координаты таким образом, что эти плоскости образуют множество параллельных многоугольников (рис. 12.27). После этого можно накладывать текстуры на образовавшиеся многоугольники. Поскольку количество многоугольников невелико— не более нескольких сотен, то процесс наложения текстур
на них не слишком загрузит специализированные аппаратные средства. В отличие от других
методов визуализации скалярных полей, метод с применением наложения текстур реализуется достаточно быстро, и его можно использовать в приложениях, ориентированных на интенсивный диалог с пользователем.
Визуализация векторных полей
Векторное поле отличается от скалярного тем, что в каждой его точке определено не отдельное значение, а «-мерный вектор. В результате имеем трехмерный объем, в каждой точке которого определена «-мерная вектор-функция f(x, у, z). Чаще всего исследуются трехмерные векторные поля, в которых каждая точка характеризуется трехмерным вектором. Примером может служить задача исследования распределения скоростей в потоке жидкости. При этом в каждой точке потока измеряется скорость — трехмерный вектор.
В двухмерном случае можно рассматривать вектор-функцию f(x, у\ определенную на некоторой поверхности. Например, спутниковые системы наблюдений измеряют набор параметров в каждой точке земной поверхности. В частности, многоканальный спектроанализатор Landsat Multispectral Sensor фиксирует интенсивность излучения в 13 спектральных диапазонах. В этом случае аргументами вектор-функции f являются две координаты точки на поверхности Земли. Такие данные, как правило, наглядно представляются в виде цветного двухмерного изображения. Значения данных сопоставляются с определенным цветом, по которому исследователь может оценить информацию, содержащуюся в данных измерений. Методику цветового кодирования мы рассмотрим в разделе 12.6.3.
Для визуализации трехмерных векторных полей используются две технологии: значение поля в каждой точке передается либо цветом, либо с помощью геометрических объектов.
Отрезки переменной длины
Будем считать, что векторное поле определено в объеме, имеющем форму параллелепипеда. В каждой точке этого объема известно значение трехмерного вектора данных.
Наиболее очевидным решением кажется представление вектора в виде трехмерного короткого отрезка, ориентация и длина которого пропорциональны соответствующим характеристикам вектора. Эти отрезки получили название hedgehog (в дословном переводе— колючки, иголки ежа). Сформировать изображение, состоящее из таких отрезков, не представляет особого труда, если обратить внимание на некоторые нюансы процесса.
Во-первых, поскольку трехмерные отрезки произвольной ориентации проецируются на двухмерную картинную плоскость, то длина штрихов в сформированном изображении зависит не только от исходных данных, но и от ориентации картинной плоскости (т.е. от положения наблюдателя). С этой проблемой можно справиться, применив цветовое кодирование. Например, можно сопоставить осям х, у, z цвета вершин RGB-куба и соответственно закрашивать проекции штрихов. Кроме того, графическая система должна позволить пользователю легко менять направление визирования объема и рассматривать его с разных точек зрения.
Во-вторых, если формировать отрезки в каждой точке объема, то штрихи в результирующем изображении наложатся друг на друга и разобраться в этой картинке будет невозможно. Эту проблему можно решить, либо выбирая для отображения точки пространства с каким-то шагом (например, выбирая каждую к-ю точку), либо отбирая только те точки, в которых модуль вектор-функции превышает определенный порог.
И в-третьих, нужно продумать, как масштабировать модуль отображаемой векгор- функции. Здесь потребуется отыскать “золотую середину” — штрихи на экране не должны быть ни слишком короткими, ни слишком длинными. Если в графической системе имеется достаточно памяти для хранения всего массива данных, решить эту проблему не представляет особого труда, но она может доставить много хлопот, если весь массив не вмещается в имеющуюся память, а требуется визуализация динамического процесса в реальном масштабе времени.
На рис. 12.28 показано, как выглядит на экране изображение векторного поля тока крови в сонной артерии человека. Исходные данные были собраны посредством измерения в точках трехмерного объема. В большинстве точек модуль измеренного вектора мал, поскольку через них не проходят кровеносные сосуды. Поэтому пороговая селекция позволяет исключить эти точки из результирующего изображения. Кроме того, для отображения выбираются не все точки объема, а каждая пятая.
Рис. 12.28. Изображение векторного поля тока крови в сонной артерии человека, сформированное с помощью отрезков регулируемой длины
Бусинки
Рис. 12.29. Использование бусинок в форме конуса для визуализации векторного поля потока жидкостиРис. 12.29. Использование бусинок в форме конуса для визуализации векторного поля потока жидкостиМожно представлять векторы в каждой точке поля не в виде штрихов, а небольшими объемными геометрическими объектами — бусинками {glyphs). Как правило, форма бусинок выбирается такой, чтобы с ее помощью можно было наглядно передать ориентацию вектора, а модуль вектора отображается масштабом изображения бусинки. Примером может быть конус — колпачок (рис. 12.29).
Рис. 12.29. Использование бусинок в форме конуса для визуализации векторного поля потока жидкости
Рис. 12.29. Использование бусинок в форме конуса для визуализации векторного поля потока жидкости
Центр основания конуса помещается в точку выборки данных, а ориентация оси конуса соответствует ориентации измеренного вектора. Довольно часто в качестве бусинок используются разные варианты изображения стрелок.
Цвет
Цвет при визуализации векторных полей используется либо как основное средство наглядного представления, либо как дополнение к другим средствам — отрезкам или бусинкам. Чаще всего с помощью цвета передается информация о модуле вектор-функции в той или иной точке поля, но иногда он используется и для того, чтобы привлечь внимание наблюда-
0
0.0
1.0
0.0
1
0.0
0.9
0.1
255
1.0
1.0
1.0
Индекс Красный Зеленый СинийИндекс Красный Зеленый СинийРис. 12.30. Заполнение таблицы соответствия цветовРис. 12.30. Заполнение таблицы соответствия цветовтеля к определенным данным. Можно использовать и кодирование компонент трехмерной вектор-функции тремя основными цветами и таким образом передать цветом ориентацию вектора. Правда, как показывает практика, наблюдателю довольно сложно интерпретировать такое представление ориентации.
Индекс Красный Зеленый Синий
Индекс Красный Зеленый Синий
Рис. 12.30. Заполнение таблицы соответствия цветов
Рис. 12.30. Заполнение таблицы соответствия цветов
При использовании индексируемых цветов нам потребуется сначала сформировать таблицу соответствия цветов. Индексом таблицы может служить значение модуля вектор-функции в заданном интервале. Затем каждому значению индекса нужно присвоить определенные значения интенсивности основных цветов (рис. 12.30).
Проще всего при назначении цветов отдельным строкам таблицы соответствия воспользоваться термальной шкалой. Человеку свойственно ассоциировать холодные тона — синий и зеленый — с данными, не несущими особой информации, а теплые тона — красный, желтый и белый — с данными, представляющими определенный интерес. Следовательно, если характер исследуемого процесса таков, что на векторы большей длины следует обратить особое
внимание исследователя, то имеет смысл в начальных строках таблицы разместить максимально интенсивный холодный цвет— зеленый, а по мере возрастания значения индекса переходить к голубому, синему, фиолетовому, красному, желтому и завершить белым. Такая функция распределения цветов наглядно представляется цветовым кубом. Нужно стремиться по возможности назначать цвета, соответствующие ребрам цветового куба. На рис. 12.31 показана последовательность обхода ребер цветового куба при заполнении таблицы, а на рис. 12.32— функции соответствия интенсивности отдельных цветовых компонентов и модуля отображаемой вектор-функции.
Рис. 12.32. Соответствие интенсивности отдельных цветовых компонентов и модуля отображаемой вектор-функцииРис. 12.32. Соответствие интенсивности отдельных цветовых компонентов и модуля отображаемой вектор-функцииРис. 12.31. Последовательность назначения цветов при формировании термальной цветовой шкалыРис. 12.31. Последовательность назначения цветов при формировании термальной цветовой шкалыЕсли при визуализации векторного поля используются бусинки, то их можно раскрасить с помощью такой таблицы соответствия цветов, что позволит более наглядно представить наблюдателю характер исследуемого поля.
Рис. 12.32. Соответствие интенсивности отдельных цветовых компонентов и модуля отображаемой вектор-функции
Рис. 12.32. Соответствие интенсивности отдельных цветовых компонентов и модуля отображаемой вектор-функции
Рис. 12.31. Последовательность назначения цветов при формировании термальной цветовой шкалы
Рис. 12.31. Последовательность назначения цветов при формировании термальной цветовой шкалы
Треки частиц и линии потока
Одно из наиболее распространенных приложений методов визуализации векторных полей — визуализация разного рода потоков частиц в жидкостях и газах. В таких приложениях исходный массив данных представляет собой выборки компонент вектора скорости частиц в каждой точке исследуемого объема, причем это может быть единственный массив, характе-
ризующий поле скоростей в определенный момент времени, либо несколько массивов данных, снятых в разные моменты.
Как правило, при исследовании потоков пользователя интересует не столько распределение скоростей, сколько траектории частиц в таком потоке. Эту информацию можно наглядно представить двумя способами.
Треки частиц {particle track) представляют собой траектории частиц, перемещающихся в потоке жидкости. Следовательно, для визуализации трека нужно поместить частицу в виртуальный объем и использовать имеющиеся данные наблюдений для трассировки ее траектории по мере протекания вещества (жидкости или газа) через этот объем. В общем случае для решения этой задачи потребуются массивы данных, снятые в разные моменты времени, но если поток является стационарным, можно обойтись и данными о векторном поле скоростей, зафиксированными в какой-то один момент времени.
Линия потока {streamline) представляет собой кривую, полученную интегрированием скоростей при заданных начальных условиях. В стационарном потоке частицы будут перемещаться вдоль этих кривых. Технология отображения треков частиц и линий потока одна и та же, а отличие существует только в алгоритме обработки данных первичных измерений.
Предположим, что в нашем распоряжении имеется векторное поле распределения скоростей в определенном объеме, снятое в момент времени t. Выберем произвольную точку в этом объеме:
л(/) уи) Ж)
В исходном массиве данных имеются компоненты вектора скорости в этой точке:
Если поместить мысленно в эту точку материальную частицу, то через небольшой интервал времени, т.е. в момент t + h, эта частица будет примерно в точке
р(/ + h) = р(/) + hv{t),
как показано на рис. 12.33. Эта точка— следующая на траектории движения частицы. Если
поток стационарный, то эта точка также принадлежит линии потока р(/).
Новая точка будет лежать в одной из ячеек, образованных
сеткой дискретизации первичных измерений. Следующую точку траектории частицы можно вычислить по той же формуле, но подставив в нее данные из элемента исходного массива, ближайшего к только что сформированной точке, либо воспользовавшись билинейной интерполяцией данных первичных измерений.
Если известно, что поток нестационарный, то интервал И выбирается равным интервалу съема первичных данных и для вычисления следующей точки траектории берутся данные из очередного массива либо, если интервал между очередными
Рис. 12.33. Вычисление точек измерениями не равен А, значение в момент t+h формируется
линии потокалинии потокас помощью линейной интерполяции.
линии потока
линии потока
Визуализация тензорных полей
Данные, характеризующие состояние тензорного поля в каждой точке объема, также являются распределенными в некоторой окрестности этой точки. Например, можно измерять напряжения в каждой точке конструкции, которые вызываются внешними силами. Напряжение в точке (х, у, -) описывается матрицей размера 3x3 (тензором ранга 2):
о.о.М = тххМ = тххТ.по.Т.по.т_, т.хт_, т.хтд. тх. охтд. тх. ох
о.
о.
М = тхх
М = тхх
Т.п
о.
Т.п
о.
т_, т.х
т_, т.х
тд. тх. ох
тд. тх. ох
Диагональные члены матрицы представляют нормальные напряжения (силы, действующие на единицу площади), которые либо сжимают, либо растягивают материал по нормали к граням элементарного куба в данной точке. Члены матрицы, лежащие по обе стороны от диагонали, представляют силы, которые стремятся “перекосить” элементарный куб, превратить его в параллелепипед, как показано на рис. 12.34.
Рис. 12.34. Напряжения, действующие на элементарный куб конструкции: а — элементарный куб; б — нормальные напряжения; в — скашивающие напряженияРис. 12.34. Напряжения, действующие на элементарный куб конструкции: а — элементарный куб; б — нормальные напряжения; в — скашивающие напряжения
Рис. 12.34. Напряжения, действующие на элементарный куб конструкции: а — элементарный куб; б — нормальные напряжения; в — скашивающие напряжения
Рис. 12.34. Напряжения, действующие на элементарный куб конструкции: а — элементарный куб; б — нормальные напряжения; в — скашивающие напряжения
Замена матрицы 3x3 матрицей 1x9 (тензором ранга 1) приведет к утере части информации, заключенной во внутренней структуре массива 3x3; следовательно, для визуализации такого тензорного поля нельзя применить те методы, которые мы использовали для визуализации векторных полей. Поле напряжений в конструкции можно наглядно отобразить, например, с помощью маленьких кубиков, каждый из которых “перекошен” под воздействием локальных напряжений в соответствии со значениями элементов матрицы напряжений 3x3. Другой метод — отобразить на экране свойства локальных матриц напряжения в каждой точке объема. Если, например, матрица симметрическая — именно так обстоит дело в большинстве случаев, — то ее можно разложить на собственные числа и собственные векторы, т.е. можно представить матрицу М в виде
М = V'AV,
где матрица Л является диагональной, а V есть матрица вращения, т.е. NT = V"'. Три диагональных элемента матрицы Л — Ль Л2 и Л3 — это собственные числа матрицы М, а столбцы матрицы V — ее собственные векторы. Собственные числа являются одной из главных характеристик матрицы.
С тремя собственными числами матрицы можно ассоциировать эллипсоид. Изоповерхности квадратичной формы хуМх сами по себе являются квадратичными, поскольку
Подстановка у = Vx вместо х изменяет вид квадратичной формы:
x/Mx = yrV'MVy = у;Ау = ^Л.,у|2 .
z=0
Если все три собственных числа матрицы положительны, то квадратичная поверхность является эллипсоидом и матрица V поворачивает исходный фрейм таким образом, что его оси становятся параллельны главным осям эллипсоида. Собственные числа характеризуют длины трех главных осей эллипсоида. Поскольку форма эллипсоида однозначно соответствует виду матрицы тензора, то для визуализации такого тензорного поля удобно использовать бусинки в форме эллипсоида, которые наглядно представят информацию о характеристиках матрицы напряжения в каждой точке объема. Собственные векторы определяют ориентацию эллипсоида в пространстве.
Резюме
В этой главе мы сосредоточили внимание на задачах визуализации больших массивов данных, получаемых при исследованиях в самых разных областях науки и техники. Наглядное представление таких данных требует применения всего арсенала средств компьютерной графики, рассмотренных в предыдущих главах этой книги.
Пользуясь средствами таких API, как OpenGL, не так уж сложно сформировать изображения поверхностей и линий уровня при визуализации функций двух переменных.
Данные, характеризующие скалярное поле в определенном объеме, можно наглядно представить и с помощью изоповерхностей или тонированием объема. В первом случае на экран выводятся трехмерные поверхности, объединяющие точки объема с равными значениями характеристической скалярной величины. Такие трехмерные поверхности отображаются с помощью полигональной сети, которая эффективно отображается существующими аппаратными и программными средствами стандартных систем компьютерной графики. При тонировании объема скалярного поля характеристики цвета и прозрачности отдельных вокселей ставятся в соответствие величине напряженности скалярного поля, но такой метод затрудняет организацию интерактивного режима просмотра, поскольку сопряжен с большим числом вычислений.
Методы визуализации векторных полей играют большую роль при отображении потоков жидкостей и газов. В общих чертах были описаны разнообразные методы представления векторов отрезками переменной длины, простейшими геометрическими телами — бусинками, линиями потока и треками частиц.
Наибольшие сложности вызывает визуализация тензорных полей, причем эти сложности являются следствием ограниченных возможностей объемной графики для представления многокомпонентных тензоров состояния.
Рекомендуемая литература
Метод маркированных квадратов и кубиков был разработан и внедрен в практику наглядного представления результатов научных исследований Лоренсоном (Lorenson) и Клайном (Kline) в работе [Lor87], В дальнейшем метод неоднократно модифицировался различными исследователями. Проблемы неоднозначности интерпретации результатов маркировки обсуждаются в работе [Van94J. О первых работах, посвященных визуализации данных о распределении скалярных полей в трехмерном объеме, сообщалось в конце 70-х годов в статьях Германа (Herman) [Нег79] и Фуча (Fuchs) [Fuc77]. Метод трассировки лучей при визуализации скалярных полей впервые описан Левоем (Levoy) в работе [Lev88]. Другие методы рас-
смотрены в работах [Wes90], [Wit94,a] и [Сго97]. Достаточно много информации о современных методах визуализации читатель найдет в работах [Gal95, Nie97] и особенно на страницах журнала IEEE Transactions on Visualization and Computer Graphics.
Упражнения
Разработайте программу отображения массива значений {/,} в виде
мозаики разноцветных прямоугольников;
сети прямоугольных ячеек;
линий уровня.
Модифицируйте программу отображения сети таким образом, чтобы она формировала тонированную поверхность.
Доработайте программу отображения сети и включите в нее возможность наложения текстуры на сформированное изображение поверхности.
Используйте при формировании изображения сети полиномиальные поверхности третьего порядка, описанные в главе 9.
Сформируйте таблицу соответствия цветов, которая позволит передать разными цветами 16 вариантов маркировки прямоугольной ячейки при прохождении через нее линии уровня.
Существует 256 вариантов маркировки элементарного куба при пересечении его изоповерхностью. После учета симметрии это множество вариантов сводится к 14 уникальным. Сколько вариантов из исходного множества соответствует каждому из уникальных?
Реализуйте программно метод маркированного куба. В вариантах маркировки, допускающих неоднозначную интерпретацию, используйте интерпретацию по собственному выбору.
Трехмерные данные можно наглядно представить с помощью локальных нормалей к каждой ячейке или группе соседних ячеек. Разработайте программу, которая будет по заданному множеству линий уровня формировать нормаль к участку поверхности, проходящему через заданную ячейку.
Имеется массив значений функции в восьми вершинах правильного параллелепипеда. Разработайте метод трилинейной интерполяции, который позволит получить значение функции в любой внутренней точке объема, ограниченного параллелепипедом, подобно тому, как метод билинейной интерполяции позволяет получить значение функции внутри прямоугольной области.
Разработайте метод построения изоповерхности, аналогичный методу маркированного квадрата, но в качестве элементарного объема используйте тетраэдр.
Разработайте программу визуализации потока жидкости, которая будет выводить на экран траектории линий потока в виде трубок разного цвета с наложением теней.
Разработайте программу формирования собственных чисел и собственных векторов матрицы 3 х 3 и отображения их в виде эллипсоида.
521521Разработайте программу визуализации скалярного поля, использующую в качестве отображаемого примитива только точки. Плотность точек в определенной области должна соответствовать усредненному значению напряженности поля.
521
521
Упражнения
ПРИЛОЖЕНИЕ
А
Демонстрационные программы
В
приложении А представлены исходные тексты некоторых программ, которые кратко рассматривались в разных главах этой книги. Эти и другие программы можно также загрузить с ftp-сервера ftp.cs.unm.edu из каталога /pub/angel/ВООК. Информацию об изменениях, внесенных в тексты программ, можно получить на Web-сервере http//ww.cs.unm.edu/~angel. Компиляцию можно выполнять в большинстве реализаций операционной системы UNIX, а соответствующие файлы управления компиляцией можно загрузить с того же сервера. Тексты других учебных программ, в том числе и тех, которые входят в состав руководства OpenGL Programmer’s Guide, а также библиотеки GLUT для разных версий операционных систем можно загрузить с Web-сервера SGI (http://ww.sgi.com/software/ opengl) или с Web-сервера ассоциации пользователей OpenGL (http: //ww.opengl.org).
Версии OpenGL существуют для всех типов графических станций Silicon Graphics и для операционных систем семейства Microsoft Windows (98 и NT). Информация о версиях OpenGL для других операционных систем представлена на Web-сервере ассоциации пользователей OpenGL по адресу http://ww.opengl.org. Существует также свободно распространяемый комплект API, полностью совместимый с OpenGL, — программный пакет MESA, который можно загрузить с Web-сервера http://ww.ssec.wisc.edu/~brianp/Mesa.html, Пакет работает под управлением большинства существующих операционных систем, в том числе и Linux. Существует также коммерческая версия пакета для Linux.
Приведенные ниже программы взаимодействуют с операционной системой через библиотеку GLUT, которая доступна по сети Internet. Имена функций соответствуют руководствам OpenGL Programmer's Guide и GLUT Users Guide. Поскольку некоторые фрагменты “кочуют” из одной программы в другую, мы снабдили комментариями только первые версии этих фрагментов.
Мы отбирали для включения в книгу программы, руководствуясь, прежде всего, их доступностью и читабельностью программного кода, принося подчас в жертву эффективность и производительность программы. В процессе самостоятельной работы вы сможете усовершенствовать их и добиться большей эффективности.
В данное приложение включены следующие программы:
программа формирования 5000 точек двухмерного узора Серпинского (глава 2);
программа формирования двухмерного узора Серпинского, использующая рекурсивный алгоритм (глава 2);
программа формирования точек трехмерного узора Серпинского (глава 2);
программа формирования трехмерного узора Серпинского, использующая рекурсивный алгоритм (глава 2);
программа, демонстрирующая методику использования библиотеки GLUT для взаимодействия с операционной системой (глава 3);
программа рисования (глава 3);
программа формирования изображения вращающегося квадрата, использующая одинарную и двойную буферизацию (глава 3);
программа формирования изображения вращающегося куба (глава 4);
программа формирования изображения вращающегося куба, использующая массив вершин (глава 4);
программа формирования изображения вращающегося куба, использующая для управления вращением виртуальный трекбол (глава 4);
программа, демонстрирующая управление положением наблюдателя (глава 5);
■ программа аппроксимации сферы методом рекурсивного разбиения тетраэдра (глава 6).
А.1. Двухмерный узор Серпинского
/* Построение точек двухмерного узора Серпинского способом деления пополам отрезков между произвольно выбранными вершинами */
^include <GL/glut.h>
/* Оператор включения файла <glut.h> зависит от размещения файлов библиотек на конкретном компьютере */
/* Включение файла glut.h автоматически включает в программу gl.h и glu.h */
void myinit()
{
/* Атрибуты */
/* Цвет фона - белый */
glClearColor(1.0, 1.0, 1.0, 1.0);
/* Цвет переднего плана - красный */ glColor3f(1.0, 0.0, 0.0);
/* Установка параметров отображения */
/* Окно 500 х 500 */
/* Начало экранных координат в левом нижнем углу */
glMatrixMode(GL_PROJECTION);
glLoad!dentity();
gluOrtho2D(0.0, 500.0, 0.0, 500.0);
glMatrixMode(GL_MODELVIEW);
}
/* */
void display() {
/* Определение типа данных, представляющего в программе */
/* точку двухмерного пространства */
typedef GLfloat point2[2];
/* Треугольник */
point2 vertices[3]={{0.0,0.0},
{250.0,500.0},
{500.0,0.0}};
int i, j, k;
int rand(); /* Стандартный генератор случайных чисел */
point2 р ={75.0,50.0}; /* Произвольно выбранная исходная */ /* точка внутри треугольника */
glClear(GL_COLOR_BUFFER_BIT); /* Очистить окно */
/* Вычислить и вывести на экран 5000 точек узора */
for( k=0; k<5000; k++) {
j=rand()%3; /* Выбрать случайно одну из вершин */
/* Вычислить точку на полпути между выбранной */
/* вершиной и прежней точкой */
р[0] = (p[0]+vertices[j][0])/2.0;
р[1] = (p[l]+vertices[j][l])/2.0;
/* Вычертить новую точку */
glBegin(GL_POINTS);
glVertex2fv(p);
glEnd();
}
glFlush(); /* Очистить буферы */
}
/* */
void main(int argc, char** argv)
{
/* Стандартная процедура инициализации библиотеки GLUT */ glutlnit(&argc,argv);
/* Дублируются */
/* установки по умолчанию. */
/* Эти операторы можно опустить. */ glut!nitDisplayMode(GLUT_SINGLE | GLUT_RGB); glutlnitWindowSize(500,500); /* Окно 500 х 500 пикселей */ glut!nitWindowPosition(0,0); /* Начало координат */ glutCreateWindow("Sierpinski Gasket"); /* Заголовок окна */ /* Регистрация функции отображения. */ /* Функция будет вызвана при открытии окна. */ glutDisplayFunc(display);
myinit(); /* Установка атрибутов */
glutMainLoop(); /* Запустить цикл обработки событий */ }
/*Конец файла*/
А.2. Рекурсивный алгоритм построения узора Серпинского
/* Построение двухмерного узора Серпинского рекурсивным разбиением треугольника */
/* Количество циклов задается в командной строке при вызове программы */
#include <GL/glut.h>
typedef float point2[2];
/* Исходный треугольник */
point2 v[]={{-1.0, -0.58}, {1.0, -0.58}, {0.0, 1.15}};
int n;
/* */
void triangle( point2 a, point2 b, point2 c)
/* Отобразить треугольник */
{
glBegin(GL_TRIANGLES);
glVertex2fv(a);
glVertex2fv(b);
glVertex2fv(c);
glEnd();
}
/* */
void divide_triangle(point2 a, point2 b, point2 c, int m) {
/* Разбиение треугольника */
point2 vO, vl, v2;
int j;
if(m>0)
{
for(j=0; j<2; j++) vO[j]=(a[j]+b[j])/2;
for(j=0; j<2; j++) vl[j]=(a[j]+c[j])/2;
for(j=0; j<2; j++) v2[j]=(b[j]+c[j])/2;
divide_triangle(a, vO, vl, m-1);
divide_triangle(c, vl, v2, m-1);
divide_triangle(b, v2, vO, m-1);
}
else(triangle(a,b,c)); /* Вычертить треугольник */
}
/* */
void display()
{
glClear(GL_COLOR_BUFFER_BIT);
divide_triangle(v[O], v[l], v[2], n);
glFlush();
}
/* */
void myinit()
{
glMatrixMode(GL_PROJECTION);
glLoad!dentity();
gluOrtho2D(-2.0, 2.0, -2.0, 2.0);
glMatrixMode(GL_MODELVIEW);
glClearColor (1.0, 1.0, 1.0, 1.0);
glColor3f(0.0,0.0,0.0);
}
/* */
void main(int argc, char **argv)
{
n=atoi(argv[1]);
glutlnit(&argc, argv);
glut!nitDisplayMode(GLUT_SINGLE | GLUT_RGB );
glut!nitWindowSize(500, 500);
glutCreateWindow("Sierpinski Gasket");
glutDisplayFunc(display);
myinit();
glutMainLoop();
}
/*Конец файла*/
A.3. Трехмерный узор Серпинского
/* Программа формирования трехмерного узора Серпинского */
#include <GL/gl.h>
#include <GL/glu.h>
^include <GL/glx.h>
#include <GL/glut.h>
/* Определение типа данных, представляющего точку в трехмерном пространстве */
typedef struct { float x,y,z;} point3;
/* Исходный тетраэдр */
points vertices[4]={{0,0,0},{250,500,100}, {500,250,250},{250,100,250}};
int j;
points new, old={250,100,250};
/* */
void clear()
{
glClear(GL_COLOR_BUFFER_BIT);
}
/* */
/* Вычисление и вывод на экран новой точки узора */ void displayf)
{
long rand();
int i;
j=rand()%4; /* Выбор вершины случайным образом */
/* Вычисление координат точки, стоящей на середине пути между вершиной и прежней точкой узора */ new.x = (old.x+vertices[j].х)/2;
new.y = (old.y+vertices[j].у)/2;
new.z = (old.z+vertices[j].z)/2;
/* Вывод точки на экран */ glBegin(GL_POINTS);
glColorSf(1.0-new.z/250.,new.z/250.,0.);
glVertexSf(new.x, new.y,new.z);
glEnd();
/* Заменить прежнюю точку новой */ old.x=new.x;
old.y=new.y;
old.z=new.z;
glFlush();
/**/
void mouse(int btn, int state, int x, int y) {
if(btn==GLUT_LEFT_BUTTON&state==GLUT_DOWN) glutldleFunc(display);
if(btn==GLUT_MIDDLE_BUTTON&state==GLUT_DOWN) glutldleFunc(NULL);
if(btn==GLUT_RIGHT_BUTTON&state==GLUT_DOWN) exit();
}
/**/
int main(int argc, char** argv)
{
glutlnit(&argc,argv);
glutlnitDisplayMode (GLUT_SINGLE | GLUT_RGB);
glutInitWindowSize(500,500);
glutlnitWindowPosition(0,0);
glutCreateWindow("Sierpinski Gasket");
glutldleFunc (display);
glutMouseFunc (mouse);
glClearColor(1.0, 1.0, 1.0, 0.0); /* Цвет фона - белый */
glColor3f(1.0, 0.0, 0.0);
glMatrixMode(GL_PROJECTION);
glLoad!dentity();
glOrtho(0.0, 500.0, 0.0, 500.0, -500.0, 500.0);
glMatrixMode(GL_MODELVIEW);
glutDisplayFunc(clear);
glutMainLoop();
}
/*Конец файла*/
A.4. Рекурсивный алгоритм построения трехмерного узора Серпинского
/* Построение трехмерного узора Серпинского рекурсивным разбиением тетраэдра */
/* Количество циклов задается в командной строке
при вызове программы */
#include <stdlib.h>
#include <GL/glut.h>
typedef float point3[3];
/* Исходный тетраэдр */
point3 v[]={{0.0, 0.0, 1.0}, {0.0, 0.942809, -0.33333}, {-0.816497, -0.471405, -0.333333}, {0.816497, -0.471405, -0.333333}};
static GLfloat theta[] = {0.0,0.0,0.0};
int n;
/* */
/* Отображение треугольника.
В режиме проволочного изображения вычерчивается контур из трех отрезков.
Для закрашивания фиксированным цветом используется нормаль в одной точке.
Для закрашивания изменяющимся цветом используются
три нормали в вершинах. */
void triangle( point3 a, point3 b, point3 c)
{
glBegin(GL_POLYGON);
glNormal3fv(a);
glVertex3fv(a);
glVertex3fv(b);
glVertex3fv(c);
glEnd();
}
/**/
/* Разбиение треугольника. */
void divide triangle(point3 a, point3 b, point3 c, int m)
{"
point3 vl, v2, v3;
int j;
if(m>0)
{
for(j=0; j<3; j++) vl[j]=(a[j]+b[j])/2;
for(j=0; j<3; j++) v2[j]=(a[j)+c[j])/2;
for(j=0; j<3; j++) v3[j]=(b[j]+c[j])/2;
divide_triangle(a, vl, v2, m-1);
divide_triangle(c, v2, v3, m-1);
divide triangle(b, v3, vl, m-1);
}"
else(triangle(a,b,c)); /* Вычертить треугольник */
/**/
/* Применение алгоритма разбиения треугольника
к граням тетраэдра */
void tetrahedron( int m)
{
glColor3f(1.0,0.0,0.0);
divide triangle(v[O], v[l], v[2], m);
glColor3f(0.0,1.0,0.0);
divide_triangle(v[3], v[2], v[l], m);
glColor3f(0.0,0.0,1.0);
divide triangle(v[0], v[3], v[l), m);
glColor3f(0.0,0.0,0.0);
divide triangle(v[0], v[2], v[3J, m);
}"
/* */
void display()
{•
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
tetrahedron(n);
glFlush();
}
/* */
void myReshape(int w, int h) {
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (w <= h)
glOrtho(-2.0, 2.0, -2.0*(GLfloat)h/(GLfloat)w, 2.0*(GLfloat)h/(GLfloat)w, -10.0, 10.0);
else
glOrtho(-2.0*(GLfloat)w/(GLfloat)h,
2.0 * (GLfloat) w/ (GLf loat) h,
-2.0, 2.0, -10.0, 10.0)?
glMatrixMode(GL_MODELVIEW);
glutPostRedisplay();
}
/* */
void main(int argc, char **argv) {
n=atoi(argv[l]);
glutlnit(&argc, argv);
glutlnitDisplayMode(GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH); glutInitWindowSize(500, 500);
glutCreateWindow("3D Gasket"); glutReshapeFunc(myReshape); glutDisplayFunc(display);
glEnable(GL_DEPTH_TEST);
glClearColor (1.0, 1.0, 1.0, 1.0); glutMainLoop();
Конец файла
A.5. Программа вычерчивания квадрата
/* Программа демонстрирует методику использования функций из библиотеки GLUT для взаимодействия с операционной системой управления окнами */
/* Программа открывает окно, очищает его - устанавливает черный цвет фона, а затем вычерчивает квадрат в том месте, где пользователь щелкнет левой кнопкой мыши. Щелчок правой кнопкой мыши завершает выполнение программы. При перемещении окна и изменении его размеров программа очищает окно. */
^include <GL/glut.h>
/* Глобальные переменные */
GLsizei wh = 500, ww = 500; /* исходные размеры окна */
GLfloat size =3.0;/* половина длины стороны квадрата */
void drawSquare(int х, int у) {
y=wh-y;
glColor3ub( (char) rand()%256,
(char) rand()%256, (char) rand()%256);
glBegin(GL_POLYGON);
glVertex2f(x+size, y+size);
glVertex2f(x-size, y+size);
glVertex2f(x-size, y-size);
glVertex2f(x+size, y-size);
glEnd();
glFlush();
/* Перерисовка изображения в окне.
Программа вызывается при изменении положения или размеров окна. */
void myReshape(GLsizei w, GLsizei h)
{
/* Настройка рамки отсечения */
glMatrixMode(GL_PROJECTION); glLoad!dentity();
glOrtho(0.0, (GLdouble)w, 0.0, (GLdouble)h, -1.0, 1.0); glMatrixMode(GL_MODELVIEW);
glLoad!dentity();
/* Настройка параметров видового окна и очистка окна */ glviewport(0,0,w,h);
glClearColor (0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT); glFlush();
/* Установка глобальных переменных размеров окна. Переменные будут использоваться подпрограммой отображения */
ww = w; wh = h;
}
/**/
void myinit() {
g1Viewport(0,0,ww,wh);
/* Установка размеров отсекающей рамки в соответствии с размерами окна. Такая настройка позволяет избежать масштабирования координат объектов при изменении размеров окна. */
glMatrixMode(GL_PROJECTION); glLoad!dentity();
glOrtho(0.0, (GLdouble)ww , 0.0, (GLdouble)wh , -1.0, 1.0);
/* Установка черного цвета фона и очистка окна */ glClearColor (0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT); glFlush();
/* Регистрация функции обработки события изменения размеров окна */
glutReshapeFunc(myReshape);
}
/* */
void mouse(int btn, int state, int x, int y) {
if(btn==GLUT_RIGHT_BUTTON && state==GLUT_DOWN) exit();
/* */
/* Функция отображения.
Библиотека GLUT 3.0 требует, чтобы такая функция была обязательно определена в программе.*/
void display()
{}
/* */
int main(int argc, char** argv)
{
glutlnit(&argc,argv);
glutlnitDisplayMode (GLUT_SINGLE | GLUT_RGB);
glutCreateWindow("square"7;
myinit ();
glutReshapeFunc (myReshape);
glutMouseFunc (mouse);
glutMotionFunc(drawSquare);
glutDisplayFunc(display);
glutMainLoop();
}
/* Конец файла */
A.6. Программа рисования
/* Простая программа, формирующая на экране текст, отрезки прямых, треугольники, прямоугольники и точки */
«define NULL 0
«define LINE 1
«define RECTANGLE 2
«define TRIANGLE 3
«define POINTS 4
«define TEXT 5
«include <GL/glut.h>
void mouse(int, int, int, int);
void key(unsigned char, int, int);
void display();
void drawSquare(int, int);
void myReshape(GLsizei, GLsizei);
void myinit();
void screen_box(int, int, int);
void right_menu(int);
void middle_menu(int);
void color_menu(int);
void pixel_menu(int);
void filljnenu(int);
int pick(int, int);
/* Глобальные переменные */
GLsizei wh = 500, ww = 500; /* исходные размеры окна */
GLfloat size =3.0;/* половина длины стороны квадрата */
int draw_mode = 0;/* режим рисования */
int rx, ry;/* позиция растра */
GLfloat г = 1.0, g = 1.0, b = 1.0; /* цвет переднего плана */ int fill = 0; /* флаг заливки */
/**/
void drawSquare(int x, int y) {
y=wh-y;
glColor3ub( (char) rand()%256, (char) rand()%256, (char) rand()%256);
glBegin(GL_POLYGON);
glVertex2f(x+size, y+size);
glVertex2f(x-size, y+size);
glVertex2f(x-size, y-size);
glVertex2f(x+size, y-size);
glEnd();
}
/* */
/* Программа перерисовки при изменении размеров или положения окна */
void myReshape(GLsizei w, GLsizei h) {
/* adjust clipping box */ Настройка рамки отсечения */
glMatrixMode(GL_PROJECTION);
glLoadIdentity(7;
glOrtho(0.0, (GLdouble)w, 0.0, (GLdouble)h, -1.0, 1.0); glMatrixMode(GL_MODELVIEW);
glLoadldentityO;
/* Настройка видового окна и его очистка */
glviewport(0,0,w,h);
glClearColor (0.8, 0.8, 0.8, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
display();~
glFlush();
/* Установка глобальных размеров окна.
Эти данные потребуются программе вывода изображения */
ww = w;
wh = h;
}
/**/
void myinit()
{
glviewport(0,0,ww,wh);
/* Установка размеров отсекающей рамки в соответствии с размерами окна. Такая настройка позволяет избежать масштабирования координат объектов при изменении размеров окна. */
glMatrixMode(GL_PROJECTION);
glLoad!dentity();
glOrtho(0.0, (GLdouble)ww , 0.0,
(GLdouble)wh , -1.0, 1.0);
/* Установка черного цвета фона и очистка окна */
glClearColor (0.8, 0.8, 0.8, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glFlush();
}
/**/
void mouse(int btn, int state, int x, int y) {
static int count;
int where;
static int xp[2],yp[2];
if(btn==GLUT_LEFT_BUTTON && state==GLUT_DOWN) {
glPushAttrib(GL_ALL_ATTRIB_BITS);
where = pick(x,y);
glColor3f(r, g, b);
if(where != 0) {
count = 0;
draw_mode = where;
}
else switch(draw_mode)
{
case(LINE):
if(count==0)
(
count++;
xp[O] = x;
yp[O] = y;
else
{
glBegin(GL_LINES);
glVertex2i(x,wh-y);
glVertex2i(xp[0],wh-yp[O]);
glEnd();
draw_mode=0;
count=0;
}
break;
case(RECTANGLE): if(count == 0)
{
count++;
xp[0] = x;
yp[0] = y;
} else
{
if(fill) glBegin(GL_POLYGON);
else glBegin(GL_LINE_LOOP);
glVertex2i(x,wh-y);
glVertex2i(x,wh-yp[0]);
glVertex2i(xp[0],wh-yp[0]);
glVertex2i(xp[0],wh-y);
glEnd();
draw_mode=0;
count=0;
} break;
case (TRIANGLE): switch(count) {
case(0):
count++;
xp[0] = x;
yp[0] = y;
break;
case(l):
count++;
xp[l] = x;
yp[i] = y;
break;
case(2):
if(fill) glBegin(GL_POLYGON);
else glBegin(GL_LINE_LOOP);
glVertex2i(xp[0],wh-yp[O]);
glVertex2i(xp[1],wh-yp[1]); glVertex2i(x,wh-y);
glEnd();
draw_mode=0;
count=0;
}
break;
case(POINTS):
{
drawSquare(x,y);
count++;
}
break;
case(TEXT):
{
rx=x;
ry=wh-y;
glRasterPos2i(rx,ry);
count=0;
}
}
glPopAttrib();
glFlush();
}
}
/* */
int pick(int x, int y)
{
у = wh - y;
if(y < wh-ww/10) return 0;
else if(x < ww/10) return LINE;
else if(x < ww/5) return RECTANGLE;
else if(x < 3*ww/10) return TRIANGLE;
else if(x < 2*ww/5) return POINTS;
else if(x < ww/2) return TEXT;
else return 0;
}
/* */
void screen_box(int x, int y, int s )
{
glBegin(GL_QUADS);
glVertex2i(x, y);
glVertex2i(x+s, y);
glVertex2i(x+s, y+s); glVertex2i(x, y+s);
glEnd();
/* */
void right_menu(int id)
{
if(id == 1) exit();
else display();
}
/**/
void middle_menu(int id)
{}
/* */
void color menu(int id)
{"
if(id == 1) {r = 1.0; g = 0.0; b = 0.0;}
else if(id==2){r=0.0;g=1.0;b=0.0;}
else if(id==3){r=0.0;g=0.0;b=1.0;}
else if (id==4){r=0.0;g=1.0;b=1.0;}
else if(id==5){r=1.0;g=0.0;b=1.0;}
else if(id==6){r=1.0;g=1.0;b=0.0;}
else if(id==7){r=1.0;g=1.0;b=1.0;}
else if(id==8){r=0.0;g=0.0;b=0.0;}
}
/**/
void pixel_menu(int id)
{
if (id == 1) size = 2 * size;
else if (size > 1) size = size/2;
}
/**/
void fill_menu(int id)
{
if (id == 1) fill = 1;
else fill = 0;
}
/**/
void key(unsigned char k, int xx, int yy) {
if(draw_mode!=TEXT) return;
glColor3f(0.0,0.0,0.0);
glRasterPos2i(rx,ry);
glutBitmapCharacter(GLUT_BITMAP_9_BY_15, k);
/*glutStrokeCharacter(GLUT_STROKE_ROMAN,i); */ rx+=glutBitmapWidth(GLUT_BITMAP_9_BY_15, к);
}
/**/
void display()
{
int shift=0;
glPushAttrib(GL_ALL_ATTRIB_BITS);
glClearColor (0.8, 0.8, 0.8, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(1.0, 1.0, 1.0);
screen_box(0,wh-ww/10,ww/10);
glColor3f(1.0, 0.0, 0.0);
screen_box(ww/10,wh-ww/10,ww/10);
glColor3f(0.0, 1.0, 0.0);
screen_box(ww/5,wh-ww/10,ww/10);
glColor3f(0.0, 0.0, 1.0);
screen_box(3*ww/10,wh-ww/10,ww/10);
glColor3f(1.0, 1.0, 0.0);
screen_box(2 *ww/5,wh-ww/10,ww/10);
glColor3f(0.0, 0.0, 0.0);
glBegin(GL_LINES);
glVertex2i(wh/40,wh-ww/20);
glVertex2i(wh/40+ww/20,wh-ww/20);
glEnd();
glBegin(GL_TRIANGLES);
glVertex2i(ww/5+ww/40,wh-ww/10+ww/40);
glVertex2i(ww/5+ww/20,wh-ww/40);
glVertex2i(ww/5+3*ww/40,wh-ww/10+ww/40);
glEnd();
glPointSize(3.0);
glBegin(GL_POINTS);
glVertex2i(3*ww/10+ww/20, wh-ww/20);
glEnd();
glRasterPos2i(2*ww/5,wh-ww/20);
glutBitmapCharacter(GLUT_BITMAP_9_BY_15, ' A');
shift=glutBitmapWidth(GLUT_BITMAP_9_BY_15, 'A');
glRasterPos2i(2*ww/5+shift,wh-ww/20);
glutBitmapCharacter(GLUT_BITMAP_9_BY_15, ' В');
shift+=glutBitmapWidth(GLUT_BITMAP_9_BY_15, 'В');
glRasterPos2i(2*ww/5+shift,wh-ww/20);
glutBitmapCharacter(GLUT_BITMAP_9_BY_15, 'C');
glFlush();
glPopAttrib();
int main(int argc, char** argv) {
int c_menu, p_menu, f_menu;
glutlnit(&argc,argv);
glutlnitDisplayMode (GLUT_SINGLE | GLUT_RGB);
glutCreateWindow("square"J;
glutDisplayFunc(display);
c_menu = glutCreateMenu(color_menu);
glutAddMenuEntry("Red", 1);
glutAddMenuEntry("Green", 2);
glutAddMenuEntry("Blue",3);
glutAddMenuEntry("Cyan", 4);
glutAddMenuEntry("Magenta", 5);
glutAddMenuEntry("Yellow",6);
glutAddMenuEntry("White", 7);
glutAddMenuEntry("Black",8);
p_menu = glutCreateMenu(pixel_menu);
glutAddMenuEntry("increase pixel size", 1);
glutAddMenuEntry("decrease pixel size", 2);
f_menu = glutCreateMenu(fill_menu);
glutAddMenuEntry("fill on", 1);
glutAddMenuEntry("fill off", 2);
glutCreateMenu(right_menu);
glutAddMenuEntry("quit",1);
glutAddMenuEntry("clear",2);
glutAttachMenu(GLUT_RIGHT_BUTTON);
glutCreateMenu(middle_menu);
glutAddSubMenu("Colors", c_menu);
glutAddSubMenu("Pixel Size", p_menu);
glutAddSubMenu("Fill", f_menu);
glutAttachMenu(GLUT_MIDDLE_BUTTON);
myinit ();
glutReshapeFunc (myReshape);
glutKeyboardFunc(key);
glutMouseFunc (mouse);
glutMainLoop();
}/*}/*Конец файлаКонец файла
}
/*
}
/*
Конец файла
Конец файла
A.7. Программа отображения с двойной буферизацией /*
* single double.с
* Программа демонстрирует методику использования одинарной и
* двойной буферизации при создании динамического изображения.
* Щелчок левой кнопкой мыши запускает движение квадрата по
* экрану, а щелчок средней кнопкой прекращает движение.
*/
^include <GL/glut.h>
^include <stdlib.h>
static GLfloat spin = 0.0;
int singleb, doubleb;
/* */
void displayd()
{
glClear (GL_COLOR_BUFFER_BIT);
glRectf (-25.0, -25.0, 25.0, 25.0);
glutSwapBuffers ();
}
/* */
void displays()
{
glClear (GL COLOR BUFFER BIT);
glRectf (-25.0, -25.0, 25.0, 25.0);
glFlush();
}
/* */
void spinDisplay ()
{
spin = spin + 2.0;
if (spin > 360.0) spin = spin - 360.0;
glutSetWindow(singleb);
glLoadIdentity();
glRotatef (spin, 0.0, 0.0, 1.0);
glutPostRedisplay();
glutSetWindow(doubleb);
glLoadIdentity();
glRotatef (spin, 0.0, 0.0, 1.0);
glutPostRedisplay();
}
/*-— */
void myinit ()
{
glClearColor (0.0, 0.0, 0.0, 1.0);
glColor3f (1.0, 1.0, 1.0);
glShadeModel (GL FLAT);
}
/* */
void mouse(int btn, int state, int x, int y)
if(btn==GLUT_LEFT_BUTTON && state==GLUT_DOWN) glutldleFunc(spinDisplay);
if(btn==GLUT MIDDLE BUTTON && state==GLUT DOWN) glutldleFunc(NULL);
}
/**/
void myReshape(int w, int h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (w <= h)
glOrtho (-50.0, 50.0, -50.0*(GLfloat)h/(GLfloat)w, 50.0*(GLfloat)h/(GLfloat)w, -1.0, 1.0); else
glOrtho (-50.0*(GLfloat)w/(GLfloat)h,
50.0*(GLfloat)w/(GLfloat)h, -50.0, 50.0, -1.0, 1.0);
glMatrixMode(GL_MODELVIEW);
glLoadldentity 7);
}
/* Основной цикл */
int main(int argc, char** argv)
{
glutlnit(iargc,argv);
glutlnitDisplayMode (GLUT_SINGLE | GLUT_RGB);
singleb=glutCreateWindow("single buffered");
myinit ();
glutDisplayFunc(displays);
glutReshapeFunc (myReshape);
glutldleFunc (spinDisplay);
glutMouseFunc (mouse);
glutlnitDisplayMode (GLUT_DOUBLE | GLUT_RGB);
doubleb=glutCreatewindow("double buffered");
myinit ();
glutDisplayFunc(displayd);
glutReshapeFunc (myReshape);
glutldleFunc (spinDisplay);
glutMouseFunc (mouse);
glutMainLoop();
}
/*Конец файла*/
А.8. Программа отображения вращающегося куба
/* Вращающийся куб.
* При раскраске граней используется линейная
* интерполяция цвета
*/
/* Программа демонстрирует использование преобразований
* однородных координат и структуру данных, описывающую
* топологию пространственного объекта. Подробно эта
* структура данных рассматривается в главе 4.
*/
/* С каждой вершиной ассоциирован вектор нормали и цвет. */
/* Центр куба находится в начале координат, а потому
* ненормированные компоненты векторов нормалей
* совпадают с координатами соответствующих вершин
*/
#include <stdlib.h>
^include <GL/glut.h>
GLfloat vertices[][3] = {{-1.0,-1.0,-1.0},{1.0,-1.0,-1.0}, {1.0,1.0,-1.0}, {-1.0,1.0,-1.0}, {-1.0,-1.0,1.0}, {1.0,-1.0,1.0}, {1.0,1.0,1.0}, {-1.0,1.0,1.0}};
GLfloat normals[][3] = {{-1.0,-1.0,-1.0},{1.0,-1.0,-1.0}, {1.0,1.0,-1.0}, {-1.0,1.0,-1.0}, {-1.0,-1.0,1.0}, {1.0,-1.0,1.0}, {1.0,1.0,1.0}, {-1.0,1.0,1.0}};
GLfloat colors[][3] = {{0.0,0.0,0.0},{1.0,0.0,0.0}, {1.0,1.0,0.0}, {0.0,1.0,0.0}, {0.0,0.0,1.0}, {1.0,0.0,1.0}, {1.0,1.0,1.0}, {0.0,1.0,1.0}};
/* */
void polygon(int a, int b, int c , int d) {
/* Вычертить многоугольник, используя список вершин */
glBegin(GL_POLYGON);
glColor3fv(colors[a]);
glNormal3fv(normals[a]);
glVertex3fv(vertices[a]);
glColor3fv(colors[b]);
glNorma!3fv(normals[b]);
glVertex3fv(vertices[b]);
glColor3fv(colors[c]);
glNormal3fv(normals[c]);
glVertex3fv(vertices[c]);
glColor3fv(colors[d]);
glNormal3fv(normals[d]);
glVertex3fv(vertices[d]); glEnd();
}
/* */
void colorcube()
{
/* Связь вершин с гранями */
polygon(0,3,2,1);
polygon(2,3,7,6);
polygon(0,4,7,3);
polygon(1,2,6,5);
polygon(4,5,6,7);
polygon(0,1,5,4);
}
static GLfloat theta[] = {0.0,0.0,0.0};
static GLint axis = 2;
/* */
void display()
{
/* Программа отображения очищает буфер кадра и z-буфер,
* поворачивает куб, вычерчивает куб в новой
* ориентации и переключает буферы.
*/
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoad!dentity();
glRotatef(theta[0], 1.0, 0.0, 0.0);
glRotatef(theta[l], 0.0, 1.0, 0.0);
glRotatef(theta[2], 0.0, 0.0, 1.0);
colorcube();
glFlush();
glutSwapBuffers();
}
/* */
void spinCube() {
/* Программа обработки простоя.
* Программа поворачивает куб на 2 градуса
* вокруг заданной оси.
*/
theta[axis] += 2.0;
if( theta[axis] > 360.0 ) theta[axis] -= 360.0;
/* display(); */ glutPostRedisplay();
}
/* */
void mouse(int btn, int state, int x, int y) {
/* Программа обработки событий мыши.
* Программа выбирает ось, вокруг которой
* выполняется поворот.
*/
if(btn==GLUT_LEFT_BUTTON && state == GLUT_DOWN) axis = 0;
if(btn==GLUT_MIDDLE_BUTTON && state == GLUT_DOWN) axis = 1;
if(btn==GLUT_RIGHT_BUTTON && state == GLUT_DOWN) axis = 2; }
/**/.
void myReshape(int w, int h) {
glviewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (w <= h)
glOrtho(-2.0, 2.0, -2.0*(GLfloat)h/(GLfloat)w, 2.0*(GLfloat)h/(GLfloat)w, -10.0, 10.0);
else
glOrtho(-2.0 *(GLfloat)w/(GLfloat)h,
2.0 *(GLfloat)w/(GLfloat)h,
-2.0, 2.0, -10.0, 10.0);
glMatrixMode(GL MODELVIEW);
}"
/* */
void main(int argc, char **argv) {
glutlnit(iargc, argv);
/* Задается режим двойной буферизации и обработки z-буфера */
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); glutInitWindowSize(500, 500);
glutCreateWindow("colorcube");
glutReshapeFunc(myReshape);
glutDisplayFunc(display);
glutldleFunc(spinCube);
glutMouseFunc(mouse);
glEnable(GL_DEPTH_TEST); /* Разрешается режим удаления */
/* невидимых поверхностей */ glutMainLoop();
}
/*Конец файла*/
А.9. Вращение куба с использованием массива вершин
/* Вращающийся куб.
При раскраске граней используется линейная
интерполяция цвета
*/
/* Программа демонстрирует использование преобразований
однородных координат и структуру данных, описывающую
топологию пространственного объекта. Подробно эта
структура данных рассматривается в главе 4.
*/
/* С каждой вершиной ассоциирован вектор нормали и цвет. */
/* Центр куба находится в начале координат, а потому
ненормированные компоненты векторов нормалей
совпадают с координатами соответствующих вершин
*/
^include <stdlib.h>
^include <GL/glut.h>
GLfloat vertices[] = {-1.0,-1.0,-1.0,1.0,-1.0,-1.0, 1.0,1.0,-1.0, -1.0,1.0,-1.0, -1.0,-1.0,1.0, 1.0,-1.0,1.0, 1.0,1.0,1.0, -1.0,1.0,1.0};
GLfloat normals[] = {-1.0,-1.0,-1.0,1.0,-1.0,-1.0, 1.0,1.0,-1.0, -1.0,1.0,-1.0, -1.0,-1.0,1.0, 1.0,-1.0,1.0, 1.0,1.0,1.0, -1.0,1.0,1.0};
GLfloat colors[] = {0.0,0.0,0.0,1.0,0.0,0.0,
.0,1.0,0.0, 0.0,1.0,0.0, 0.0,0.0,1.0, 1.0,0.0,1.0, 1.0,1.0,1.0, 0.0,1.0,1.0};
GLubyte cubeindices[]={0,3,2,1,2,3,7,6,0, 4,7,3,1,2,6,5,4,5,6,7,0,1,5,4};
static GLfloat theta[] * {0.0,0.0,0.0};
static GLint axis = 2;
/* */
void display() {
/* Программа отображения очищает буфер кадра и z-буфер,
* поворачивает куб, вычерчивает куб в новой
* ориентации и переключает буферы.
*/
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
glRotatef(theta[0], 1.0, 0.0, 0.0);
glRotatef(theta[l], 0.0, 1.0, 0.0);
glRotatef(theta[2], 0.0, 0.0, 1.0);
glDrawElements(GL_QUADS, 24, GL_UNSIGNED_BYTE, cubeindices);
glBegin(GL_LINES);
glVertex3f(0.0,0.0,0.0);
glVertex3f(1.0,1.0,1.0);
glEnd();
glFlush();
glutSwapBuffers();
}
/* */
void spinCube()
{
/* Программа обработки простоя.
* Программа поворачивает куб на 2 градуса
* вокруг заданной оси.
*/
theta[axis] += 2.0;
if( theta[axis] > 360.0 ) theta[axis] -= 360.0; glutPostRedisplay();
}
/* */
void mouse(int btn, int state, int x, int y)
{
/* Программа обработки событий мыши.
* Программа выбирает ось, вокруг которой
* выполняется поворот.
*/
if(btn==GLUT_LEFT_BUTTON && state == GLUT_DOWN) axis = 0;
if(btn==GLUT_MIDDLE_BUTTON && state == GLUT_DOWN) axis = 1;
if(btn==GLUT_RIGHT_BUTTON && state == GLUT_DOWN) axis = 2;
}
/* */
void myReshape(int w, int h)
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoad!dentity();
if (w <= h)
glOrtho(-2.0, 2.0, -2.0*(GLfloat)h/(GLfloat)w,
2.0*(GLfloat)h/(GLfloat)w, -10.0, 10.0);
else
glOrtho(-2.0 *(GLfloat)w/(GLfloat)h,
.0 *(GLfloat)w/(GLfloat)h,
-2.0, 2.0, -10.0, 10.0);
glMatrixMode(GL_MODELVIEW);
}
/**/
void main(int argc, char **argv)
{
/* Задается режим двойной буферизации и обработки z-буфера */
glutlnit(&argc, argv);
glut!nitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glut!nitWindowSize(500, 500);
glutCreateWindow("colorcube");
glutReshapeFunc(myReshape);
glutDisplayFunc(display);
glutldleFunc(spinCube);
glutMouseFunc(mouse);
glEnable(GL_DEPTH_TEST); /* Разрешается режим удаления */
/* невидимых поверхностей */ glEnableClientState(GL_COLOR_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, vertices);
glColorPointer(3,GL_FLOAT, 0, colors);
glNormalPointer(GL_FLOAT,0, normals);
glColor3f(l.0,1.0,1.0);
glutMainLoop();
}
/*Конец файла*/
АЛО. Вращающийся куб, управляемый трекболом
/* Программа демонстрирует использование трекбола для управления вращением куба */
^include <math.h>
#include <GL/glut.h>
^define bool int
Idefine false 0
^define true 1
int winWidth, winHeight;
float angle = 0.0, axis[3], trans[3];
bool trackingMouse = false;
bool redrawContinue = false;
booltrackballMove = false;
/**/
/* Вычерчивание куба */
GLfloat vertices[][3] = {
{-1.0,-1.0,-1.0},{1.0,-1.0,-1.0},
{1.0,1.0,-1.0}, {-1.0,1.0,-1.0},
{-1.0,-1.0,1.0}, {1.0,-1.0,1.0},
{1.0,1.0,1.0}, {-1.0,1.0,1.0} };
GLfloat colors[][3] = {
{0.0,0.0,0.0},{1.0,0.0,0.0}, {1.0,1.0,0.0}, {0.0,1.0,0.0}, {0.0,0.0,1.0}, {1.0,0.0,1.0}, {1.0,1.0,1.0}, {0.0,1.0,1.0} };
/**/
void polygon(int a, int b, int c , int d, int face) {
/* Вычерчивание многоугольника с помощью списка вершин */
glBegin(GL_POLYGON);
glColor3fv(colors[a]);
glVertex3fv(vertices[a]);
glColor3fv(colors[b]);
glVertex3fv(vertices[b]);
glColor3fv(colors[c]);
glVertex3fv(vertices[c]);
glColor3fv(colors[d]);
glVertex3fv(vertices[d]);
glEnd();
}
/**/
void colorcubef) {
/* Связь вершин с гранями */
polygon(1,0,3,2,0);
polygon(3,7,6,2,1);
polygon(7,3,0,4,2);
polygon(2,6,5,1,3);
polygon(4,5,6,7,4);
polygon(5,4,0,l,5);
}
/**/
/*
** Управление движением с помощью трекбола.
*/
float lastPos[3] = {0.0F, 0.0F, 0.0F};
int curx, cury;
int startX, startY;
/**/
void trackball_ptov(int x, int y, int width, int height, float v[3])
{
float d, a;
/* Проекция x,y на полусферу */
v[0] = (2.0F*x - width) / width;
v[l] = (height - 2.0F*y) / height;
d = (float) sqrt(v[0]*v[0] + v[l]*v[l]);
v[2] = (float) cos((M_PI/2.0F) * ((d < 1.0F) ? d : 1.0F));
a = 1.0F / (float) sqrt(v[0]*v[0] + v[l]*v[l] + v[2]*v[2]);
v[0] *= a;
v[l] *= a;
v[2] *= a;
}
/* */
void mouseMotion(int x, int y)
{
float curPos[3], dx, dy, dz;
trackball_ptov(x, y, winWidth, winHeight, curPos);
if(trackingMouse)
{
dx = curPos[0] - lastPos[0];
dy = curPosfl] - lastPos[l];
dz = curPos[2] - lastPos[2];
if (dx || dy || dz) {
angle = 90.OF * sqrt(dx*dx + dy*dy + dz*dz);
axis[0]=lastPos[l]*curPos[2]-lastPos[2]*curPos[l];
axis[l]=lastPos[2]*curPos[0]-lastPos[0]*curPos[2];
axis[2]=lastPos[0]*curPos[l]-lastPos[l]*curPos(0];
lastPos[0] = curPos[0];
lastPos[l] = curPos(l);
lastPos[2] = curPos[2];
glutPostRedisplay();
/* */
void startMotion(int x, int y) {
trackingMouse = true;
redrawContinue = false;
startX = x; startY = y; curx = x; cury = y;
trackball_ptov(x, y, winWidth, winHeight, lastPos); trackballMove=true;
}
/* */
void stopMotion(int x, int y) {
trackingMouse = false;
if (startX != x || startY != y) { redrawContinue = true;
}
else {
angle = 0.0F;
redrawContinue = false;
trackballMove = false;
}
}
/* */
void display()
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
/* Преобразование вида */
if (trackballMove) {
glRotatef(angle, axis[0], axis[l], axis[2]);
}
colorcube();
glutSwapBuffers();
}
/* */
void mouseButton(int button, int state, int x, int y) {
if(button==GLUT_RIGHT_BUTTON) exit();
if(button==GLUT_LEFT_BUTTON)
switch(state)
{
case GLUT_DOWN: y=winHeight-y; startMotion( x,y); break;
case GLUT_UP:
stopMotion( x,y);
break;
}
}
/**/
void myReshape(int w, int h)
{
glViewport(0, 0, w, h);
winWidth = w;
winHeight = h;
}
/**/
void spinCube()
{
if (redrawContinue) glutPostRedisplay();
}
/**/
void main(int argc, char **argv)
{
glutlnit(&argc, argv);
glutlnitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glut!nitWindowSize(500, 500);
glutCreateWindow("colorcube");
glutReshapeFunc(myReshape);
glutDisplayFunc(display);
glutldleFunc(spinCube);
glutMouseFunc(mouseButton);
glutMotionFunc(mouseMotion);
glEnable(GL_DEPTH_TEST);
glMatrixMode(GL_PROJECTION);
glLoad!dentity();
glOrtho(-2.0, 2.0, -2.0, 2.0, -2.0, 2.0);
glMatrixMode(GL_MODELVIEW);
glutMainLoop();
}
/*Конец файла*/
A. 11. Изменение положения наблюдателя
/* Вращение куба с изменением положения наблюдателя.
Программа рассматривалась в главе 5.
Структура данных, описывающая куб, и программа
отображения те же, что и в предыдущем примере.
*/
^include <stdlib.h>
^include <GL/glut.h>
GLfloat vertices[][3] = {{-1.0,-1.0,-1.0},{1.0,-1.0,-1.0}, {1.0,1.0,-1.0}, {-1.0,1.0,-1.0}, {-1.0,-1.0,1.0}, {1.0,-1.0,1.0}, {1.0,1.0,1.0}, {-1.0,1.0,1.0}};
GLfloat normals[][3] = {{-1.0,-1.0,-1.0},{1.0,-1.0,-1.0}, {1.0,1.0,-1.0}, {-1.0,1.0,-1.0}, {-1.0,-1.0,1.0}, {1.0,-1.0,1.0}, {1.0,1.0,1.0}, {-1.0,1.0,1.0}};
GLfloat colors[][3] = {{0.0,0.0,0.0},{1.0,0.0,0.0}, {1.0,1.0,0.0}, {0.0,1.0,0.0}, {0.0,0.0,1.0}, {1.0,0.0,1.0}, {1.0,1.0,1.0}, {0.0,1.0,1.0}};
/* */
void polygon(int a, int b, int c , int d) {
glBegin(GL_POLYGON);
glColor3fv(colors[a]);
glNormal3fv(normals[a]);
glVertex3fv(vertices[a]);
glColor3fv(colors[b]);
glNormal3fv(normals[b]);
glVertex3fv(vertices[b]);
glColor3fv(colors[c]);
glNormal3fv(normals[c]);
glVertex3fv(vertices[c]);
glColor3fv(colors[d]);
glNormal3fv(normals[d]);
glVertex3fv(vertices[d]);
glEnd();
}
/* */
void colorcube()
{
polygonfO,3,2,1);
polygon(2,3,7,6);
polygonfO,4,7,3);
polygonfl,2,6,5);
polygon(4,5,6,7);
polygon(0,1,5,4);
}
static GLfloat theta[] = {0.0,0.0,0.0};
static GLint axis = 2;
static GLdouble viewer[]= {0.0, 0.0, 5.0}; /* Исходное положение наблюдателя */
/* */
void display()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
/* Обновить положение наблюдателя */
glLoadldentity();
gluLookAt(viewer[0],viewer[l],viewer[2], 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
/* Поворот куба */
glRotatef(theta[0], 1.0, 0.0, 0.0);
glRotatef(theta[l], 0.0, 1.0, 0.0);
glRotatef(theta[2], 0.0, 0.0, 1.0);
colorcube();
glFlush();
glutSwapBuffers();
}
/* */
void mouse(int btn, int state, int x, int y)
{
if(btn==GLUT_LEFT BUTTON && state == GLUT DOWN) axis = 0;
if(btn==GLUT MIDDLE BUTTON && state == GLUT DOWN) axis = 1;
if(btn==GLU<RIGHT_BUTTON && state == GLUT_DOWN) axis = 2;
theta[axis] += 2.0;
if( thetafaxis] > 360.0 ) theta[axis] -= 360.0;
display();
}
/* */
void keys(unsigned char key, int x, int y)
{
/* Положением наблюдателя управляют
клавиши х, X, у, Y, z, Z */
if(key == 'х') viewer[0]-= 1.0;
if(key == 'X') viewer[0]+= 1.0;
555555A.ll. Изменение положения наблюдателя
555
555
if(key == 'у') viewer[l]-= 1.0;
if(key == 'Y') viewer[l]+= 1.0;
if(key == 'z') viewer[2]-= 1.0;
if(key == ' Z') viewer[2]+= 1.0; display();
}
/**/
void myReshape(int w, int h)
{
glviewport(0, 0, w, h);
/* Режим перспективной проекции */
glMatrixMode(GL_PROJECTION);
glLoad!dentity();
if(w<=h) glFrustum(-2.0, 2.0, -2.0*(GLfloat)h/(GLfloat)w, 2.0*(GLfloat)h/(GLfloat)w, 2.0, 20.0);
else glFrustum(-2.0, 2.0, -2.0*(GLfloat)w/(GLfloat)h, 2.0*(GLfloat)w/(GLfloat)h, 2.0, 20.0);
/* Можно использовать и функцию gluPerspective() */
/* gluPerspective(45.0, w/h, 1.0, 10.0); */
glMatrixMode(GL_MODELVIEW);
}
/* */
void main(int argc, char **argv)
{
glutlnit(&argc, argv);
glut!nitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glut!nitWindowSize(500, 500);
glutCreateWindow("colorcube");
glutReshapeFunc(myReshape);
glutDisplayFunc(display);
glutMouseFunc(mouse);
glutKeyboardFunc(keys);
glEnable(GL_DEPTH_TEST);
glutMainLoop();
}
/*Конец файла*/
A. 12. Построение сферы
/* Построение сферы рекурсивным разбиением тетраэдра. Программа подробно рассматривалась в главе 6. */
/* Режим 0 - каркасное изображение,
Режим 1 - постоянная закраска граней,
Режим 3 - закраска граней с интерполяцией цвета */
^include <stdlib.h>
^include <GL/glut.h>
typedef float point[4];
/* Исходный тетраэдр */
point3 v[]={{0.0, 0.0, 1.0}, {0.0, 0.942809, -0.33333}, {-0.816497, -0.471405, -0.333333}, {0.816497, -0.471405, -0.333333}};
static GLfloat theta[] = {0.0,0.0,0.0};
int n;
int mode;
/**/
void triangle( point3 a, point3 b, point3 c)
/* Отображение одного треугольника */
{
if (mode==0) glBegin(GL_LINE_LOOP);
else glBegin(GL_POLYGON);
if(mode==l) glNorma!3fv(a);
if(mode==2) glNormal3fv(a);
glVertex3fv(a);
if(mode==2) glNormal3fv(b);
glVertex3fv(b);
if(mode==2) glNormal3fv(c);
glVertex3fv(c);
glEnd();
/* */
void normal(point3 p)
{
/* Нормализация вектора */
double sqrt();
float d =0.0;
int i;
for(i=0; i<3; i++) d+=p[i]*p[i];
d=sqrt(d);
if(d>0.0) for(i=0; i<3; i++) p[i]/=d;
}
/* */
void divide_triangle(point3 a, point3 b, point3 c, int m) {
/* Разбиение треугольника */
points vl, v2, v3;
int j;
if(m>0)
{
for(j=0; j<3; j++) vl[j]=a[j]+b[j];
normal(vl);
for(j=0; j<3; j++) v2 [j]=a[j]+c[j];
normal(v2);
for(j=0; j<3; j++) v3[j]=b[j]+c[j ];
normal(v3);
divide_triangle(a, vl, v2, m-1);
divide_triangle(c, v2, v3, m-1);
divide_triangle(b, v3, vl, m-1);
divide_triangle(vl, v3, v2, m-1);
}
else(triangle(a,b,c)); /* draw triangle at end of recursion */ }
/* */
void tetrahedron( int m)
{
/* Разбиение граней тетраэдра с помощью программы
разбиения треугольников */
divide_triangle(v[O], v[l], v[2], m);
divide_triangle(v[3], v[2], v[l], m);
divide_triangle(v[O], v[3], v[l], m);
divide_triangle(v[O], v[2), v[3], m);
/* */
void display() {
/* Отображение объекта в трех видах */
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoad!dentity();
mode=0;
tetrahedron(n);
mode=l;
glTranslatef(-2.0, 0.0,0.0);
tetrahedron(n);
mode=2;
glTranslatef( 4.0, 0.0,0.0);
tetrahedron(n);
glFlush();
/* */
void myReshape(int w, int h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoad!dentity(T;
if (w <= h)
glOrtho(-4.0, 4.0, -4.0*(GLfloat)h/(GLfloat)w, 4.0*(GLfloat)h/(GLfloat)w, -10.0, 10.0);
else
glOrtho(-4.0 *(GLfloat)w/(GLfloat)h,
4.0*(GLfloat)w/(GLfloat)h, -4.0, 4.0, -10.0, 10.0);
glMatrixMode(GL_MODELVIEW);
display();
}
/**/
void myinit()
{
GLfloat mat_specular[]={1.0, 1.0, 1.0, 1.0};
GLfloat mat_diffuse[]={1.0, 1.0, 1.0, 1.0};
GLfloat mat_ambient[]={1.0, 1.0, 1.0, 1.0};
GLfloat mat_shininess={100.0};
GLfloat light_ambient[]={0.0, 0.0, 0.0, 1.0};
GLfloat light_diffuse[]={1.0, 1.0, 1.0, 1.0};
GLfloat light_specular[]={1.0, 1.0, 1.0, 1.0};
/* Установка компонентов источника 0 */
glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
/* Определение свойств материала */
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_AMBIENT, mat ambient);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat^diffuse); glMaterialf(GL_FRONT, GL_SHININESS, mat_shininess);
glEnable(GL_SMOOTH); /* режим плавного закрашивания */ glEnable(GL_LIGHTING); /* учитывать источники света */
glEnable(GL_LIGHT0); /* включить источник 0 */ glEnable(GL_DEPTH_TEST); /* разрешить работу с z-буфером */
glClearColor (1.0, 1.0, 1.0, 1.0);
glColor3f (0.0, 0.0, 0.0);
/* */
void main(int argc, char **argv) {
n=atoi(argv[1]);
glutlnit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize(500, 500);
glutCreateWindow("sphere");
myinit();
glutReshapeFunc(myReshape);
glutDisplayFunc(display);
glutMainLoop();
}
/*Конец файла*/
ПРИЛОЖЕНИЕ
Б
Абстрактные пространства в компьютерной графике
М
атематическое обеспечение систем компьютерной графики — это совокупность методов представления и манипулирования множеством геометрических элементов, главными из которых являются точки и отрезки прямых. Эти методы базируются на свойствах нескольких типов абстрактных пространств. В приложение Б мы включили материал об основных свойствах тех типов пространств, которые наиболее широко используются в средствах формирования и трансформации геометрических объектов в системах компьютерной графики, — линейного (векторного), аффинного и евклидового. Линейное (векторное) пространство содержит объекты только двух типов: скаляры (действительные числа) и векторы. В аффинном пространстве к ним добавляется третий тип объектов — точки. В евклидовом пространстве вводится концепция расстояния.
В математическом обеспечении компьютерной графики понятие вектор трактуется как направленный отрезок прямой, представленный совокупностью п чисел. В приложении В будут представлены основные сведения из матричной алгебры — базового математического инструмента, с помощью которого можно выполнять различные преобразования векторов. В данном приложении основное внимание будет сосредоточено на главных свойствах трех перечисленных типов пространств и имеющихся в них правилах манипулирования объектами. Вероятно, наиболее удобно считать эти объекты (скаляры, векторы и точки) абстрактными типами данных, а аксиомы, характеризующие свойства пространств, как множество операций, допустимых при работе с этими типами данных.
Б.1. Скаляры
Множество вещественных чисел и операций над ними представляет собой скалярное поле. Рассмотрим множество 5 скалярных элементов, которые обозначим строчными буквами греческого алфавита а, 0, ... . Над скалярами определены две основные операции — сложение, которое обозначается знаком (+), и умножение, которое обозначается знаком (•)’. Следова-
1 Иногда, если это не .может быть истолковано неоднозначно, мы будем обозначать операцию умножения в виде «Р вместо а • Р
тельно, если Va,P G 5, то a+P G S, a • Р G S. Эти операции обладают свойствами ассоциативности, коммутативности и дистрибутивности2, т.е. если Va,P,y G 5, то'
a + P = P + a.
a-P = p-a.
а + (Р + у) = (а + р) + у.
а(Ру) = (а-Р)у,
a(P + y) = (aP) + (a-y).
Существуют два специальных скаляра (универсальные тождественные элементы) — числа 0 и 1, таких, что прибавление первого и умножение на второе не меняют ни одного числа, т.е. для Va G 5
a + 0 = 0 + a = a, al = la = a.
Каждый элемент а скалярного множества имеет соответствующие ему аддитивный инверсный элемент, который обозначается -а, и мультипликативный инверсный (обратный) элемент, который обозначается а"1, такие, что
a+ (-«) = О, act"1 =1.
-a.a*' 6 S.
Множество действительных чисел в совокупности с определенными выше операциями сложения и умножения образуют действительное скалярное поле. Скалярные поля других типов могут быть образованы множеством комплексных чисел в совокупности с операциями комплексного сложения и комплексного умножения и множеством рациональных функций (отношений двух полиномов).
Б.2. Векторное пространство
Векторное пространство помимо скаляров содержит и элементы другого типа — векторы. Над векторами определены две операции: сложение вектора с вектором и умножение вектора на скаляр. Пусть и, v, и- — векторы, определенные в векторном пространстве V. Операция сложения векторов определяется как замкнутая (z/+v G Г, Xiu,v G П, коммутативная (zz+r = v+u^ и ассоциативная (г/+(г+и’) = (z/+r)+ir). В векторном пространстве существует специальный вектор — нуль-вектор, который обозначается 0, такой, что, если Xiи G V, то
и + 0 = и.
Каждый вектор и имеет соответствующий ему аддитивный инверсный вектор, который обозначается -и,такой, что
и + ^-и^О.
Операция умножения вектора на скаляр определена таким образом, что для любого скаляра a и любого вектора и произведение аи есть также вектор в пространстве Г. Операция умножения вектора на скаляр подчиняется дистрибутивному закону, т.е.
a(w + v) = azz + av,
(а + Р)и = «z/+Pm.
"Иногда говорят, что эти операции подчиняются ассоциативному, коммутативному и дистрибутивному законам апгебры — Прим, ред
Примерами векторных пространств являются пространство геометрических векторов (направленных отрезков прямой) и пространство алгебраических векторов — совокупностей п действительных чисел. Рассмотрим множество направленных отрезков, которые графически представлены на рис. Б.1. Если скаляры в векторном пространстве есть действительные числа, то операция умножения вектора на скаляр изменяет модуль (абсолютную величину или длину) вектора, но не меняет его ориентацию (рис. Б.2).
Рис. Б. 1. Направленные от-резки прямойРис. Б. 1. Направленные от-резки прямойРис. Б.2. Умножение векто-Рис. Б.2. Умножение векто-Рис. Б.З. Правило аю-Рис. Б.З. Правило аю-ра на скалярра на скаляржения векторовжения векторов
Рис. Б. 1. Направленные от-резки прямой
Рис. Б. 1. Направленные от-резки прямой
Рис. Б.2. Умножение векто-
Рис. Б.2. Умножение векто-
Рис. Б.З. Правило аю-
Рис. Б.З. Правило аю-
ра на скаляр
ра на скаляр
жения векторов
жения векторов
Сложение вектора с вектором определяется в векторном пространстве правилом сочленения начала с концом (head-to-tail rule), которое имеет довольно наглядное представление в пространстве направленных отрезков. Вектор суммы u+v образуется после сочленения начала вектора и с концом вектора г (рис. Б.З). Можете самостоятельно проверить, что все правила, определенные на пространстве векторов, удовлетворяются и для суммы векторов.
Второй пример векторного пространства— это пространство алгебраических векторов, т.е. совокупностей п скаляров. Как правило, скалярами являются действительные или комплексные числа. В таком пространстве вектор определятся как
V = (Vt.V,v„).
Операции умножения вектора на скаляр и сложения векторов определяются соотношениями
m + p = (u1j/,,...,z/„) + (vi,v2 V„) = (m, + vM иг + \\. ..., u„*v„y
av = (avpav2av„).
Такое векторное поостранство принято обозначать R", и для манипуляций с векторами в нем используется математический аппарат матричной алгебры, который будет кратко описан в приложении В.
Базовой концепцией векторного пространства является линейная независимость векторов. Линейной комбинацией п векторов мь и,..., и„ называется вектор вида
и = a,zz, +a2w2 + ... + a„u„.
Если соотношение
a1z/1 + a2z/2 +... + a„z/„ = О
удовлетворяется только единственным множеством скаляров
a, =а, = ...«„ =0.
563563то векторы и^, и,..., и„ являются линейно-независимыми. Наибольшее количество линейнонезависимых векторов, которое может быть найдено в данном векторном пространстве, определяет размерность этого пространства. В zz-мерном векторном пространстве любое мно-
563
563
Б.2. Векторное пространство
жество п линейно-независимых векторов образует базис этого пространства. Если Г]. г2, ..., г„ есть базис пространства Г, то произвольный вектор v можно единственным способом представить в виде линейной комбинации векторов базиса:
v = 0lv14-0:v2+... + 0„t>„.
Множество скаляров {0,} называется разложением вектора г по базису Гь т2, ..., г„ (часто также используется термин представление вектора г в базисе г,, г2, ..., г„). Если множество v'h г'2, ..., г'„ представляет собой другой базис в этом же пространстве (количество векторов в базисе для данного пространства фиксировано), то существует разложение вектора г по этому базису:
v = 0X + P?< + ...+0j/.
Разложения вектора по разным базисам «-мерного векторного пространства связаны матрицей М размера п х п, такой, что
"РЯГР/
р;р2
. =м . .
1РЯ LPJ
Свойства матриц М мы рассмотрим в приложении В. Такая матрица позволяет изменять представление вектора с помощью линейного преобразования, которое реализуется с помощью только вычислительных операций над скалярами в процессе умножения матриц. Обобщая, можно утверждать, что если известен базис (или базисы) векторного пространства, то далее все манипуляции происходят только с разложениями векторов по базису (или по базисам) этого пространства. Если скаляры в данном пространстве являются вещественными числами, то все операции над «-мерными векторами (наборами п чисел) можно свести к операциям матричной алгебры.
Б.З. Аффинное пространство
Строго говоря, в векторном пространстве отсутствуют такие понятия, как положение и расстояние. Если в качестве векторного пространства для решения геометрических задач ис-
Рис. Б.4. Идентичные свободные векторыРис. Б.4. Идентичные свободные векторыпользовать пространство отрезков прямых, то мы встретимся со множеством сложностей, поскольку векторы в нем являются свободными, т.е. как и физические векторные величины, имеют модуль (длину) и направление, но не имеют точки приложения. Векторы, показанные на рис. Б.4, являются в векторном пространстве идентичными объектами.
Рис. Б.4. Идентичные свободные векторы
Рис. Б.4. Идентичные свободные векторы
Система базисных векторов, которая позволяет представить любой вектор в данном пространстве в виде набора коэффициентов разложения по этому базису, образует систему координат пространства. На рис. Б.5,а показаны три базисных вектора, исходящие из определенной точки пространства, называемой началом этой системы координат. Те векторы, которые показаны на рис. Б.5,6, идентичны базисным векторам, поскольку в векторном пространстве все векторы являются свободными. В результате в
векторном пространстве отсутствует возможность определить эту особую точку, к которой должны быть “привязаны” базисные векторы.
a)a)Рис. Б. 5. Система координат: а — базисные векторы,“привязанные” к точке начала координат;
a)
a)
б — свободные базисные векторы
Эта проблема разрешается вводом в векторное пространство еще одного типа объектов — точки. Такое “дополненное” векторное пространство получило название аффинного. В аффинном пространстве определено множество точек ^Р, Q, R,...), и единственная операция над ними— вычитание точек, результатом которой является вектор. Если Р и Q— две произвольные точки, то результат операции вычитания
v = P-Q
есть вектор в пространстве V. Следовательно, для любого вектора г и любой точки Р можно отыскать такую точку Q , для которой будет справедливо приведенное выше соотношение. Эта операция, производная от базовой операции вычитания точек, называется операцией сложения вектора и точки
Q = v + P.
Следствием из правила сложения векторов является утверждение, что для любых трех точек Р, Q, R выполняется соотношение
kP-QW-R>kp-Ry
Если отображать вектор Р-Q в виде отрезка прямой, проведенного от точки Q к точке Р, причем для обозначения направления отрезка использовать стрелки, то правило сложения векторов может быть наглядно представлено так, как на рис. Б.б.
Геометрические объекты в аффинном пространстве обладают множеством интересных свойств. Пожалуй, наиболее важное из них состоит в том, что если использовать для представления объектов фрейм, а не систему координат, то можно единообразно представлять и векторы, и точки. Фрейм включает набор базисных векторов vb г2,..., v„ и точку начала координат Ро (точку отсчета), из которой исходят эти базисные векторы. Если в аффинном пространстве определен фрейм, то любой вектор можно представить в этом фрейме единственным образом:
Рис. Б.б. Наглядное представление операции сложения векторов в аффинном пространствеРис. Б.б. Наглядное представление операции сложения векторов в аффинном пространствеv = a1v1+a2v2+... + a„v„.
Рис. Б.б. Наглядное представление операции сложения векторов в аффинном пространстве
Рис. Б.б. Наглядное представление операции сложения векторов в аффинном пространстве
Любая точка также может быть единственным образом представлена в заданном фрейме:
Р = ^ + Pi vi +p2v2 + - + 0„ г„-
Два множества из п скаляров, {ah ..., ct„} и {0Н ..., Р„} — соответственно представление вектора и точки в аффинном пространстве относительно заданного фрейма.
Если точка отсчета фрейма не изменяется, то следует учитывать только изменение системы базисных векторов. Но в задачах компьютерной графики нам чаще всего приходится иметь дело с изменением всех компонентов фрейма и представлением одних и тех же геометрических объектов в разных фреймах. Например, чаще всего геометрический объект определяется в некотором “физическом” фрейме. В терминах этого же фрейма определяется и положение камеры (или, что то же самое, — наблюдателя), а уже в процессе формирования изображения появляется необходимость сформировать представление объекта по отношению к фрейму камеры. Точка отсчета этого фрейма, как правило, совпадает с центром проецирования камеры.
Б.4. Евклидово пространство
Хотя аффинное пространство и содержит все элементы, необходимые для построения геометрических моделей, в нем отсутствует понятие расстояния между двумя точками, а соответственно и понятие длины вектора. Аффинное пространство, дополненное понятием расстояния (меры длины), превращается в евклидово.
Евклидово пространство Е содержит скаляры (а, 0, у,...) и векторы (z/, v, ir,...) причем скаляры представляют собой действительные числа. В евклидовом пространстве вводится новая операция — скалярное произведение, операндами которой являются векторы, а результатом — скаляр. Операция скалярного произведения обладает следующими свойствами: для любых трех векторов и, г, и- и двух скаляров а, 0 выполняются соотношения
uv = VU.
(«И +0v)- И’ = (XU • И’ + 0Г- IV.
v- v > 0. если I’ * 0.
0 0 = 0.
Если
и • v = 0,
то векторы и и г ортогональны. Модуль (длина) вектора в евклидовом пространстве определяется соотношением
|v| = V^.
Поскольку евклидово пространство унаследовало от аффинного, кроме векторов и скаляров, еще и точки, то в нем можно определить и понятие расстояния между точками Р и Q, как производное от длины вектора P-Q :
Операция скалярного произведения дает нам и меру угла между двумя векторами:
и- v = |z/||v|cos9.
Несложно показать, что определенное по этой формуле значение cos0 лежит в интервале (-1, +1) и равно нулю в том случае, если векторы ортогональны. Если векторы параллельны (и = av), то |cos0| = 1.
Б.5. Проекции вектора
Из свойства ортогональности двух векторов следует несколько важных для практики понятий. Понятие проекции возникло при решении задачи определения кратчайшего расстояния ме
Рис. Б. 7. Проекция векторана другой векторРис. Б. 7. Проекция векторана другой векторжду точкой и прямой (или плоскостью). Пусть заданы два непараллельных вектора г и и-. Один из них, н-, можно представить в виде суммы двух векторов, один из которых параллелен г, а другой — ортогонален ему (рис. Б.7):
Рис. Б. 7. Проекция векторана другой вектор
Рис. Б. 7. Проекция векторана другой вектор
tv = av + u.
Вектор аг, первое слагаемое, параллелен г, и вектор и, второе слагаемое, — ортогонален г. Следовательно,
и • v = О,
wv = av-v + uv = av-v.
Тогда
v- v
Вектор av называется проекцией вектора iv на вектор г. Второе слагаемое разложения вектора и- определяется соотношением
v и = VVV. v-v
Полученный результат можно распространить на разложение вектора по ортогональным векторам произвольного базиса.
Б.6. Ортогонализация Грама-Шмидта
Если в /т-мерном пространстве определено множество векторов базиса ab а2> •••, а„. то в нем можно определить другой базис, b\,b2, ...,Ьт который будет ортонормированным, т.е. каждый вектор этого базиса имеет единичную длину и ортогонален всем другим векторам базиса. Математически это выражается следующим образом:
[ 1, если / = /.
[0 в противном случае.
Следовательно, при использовании ортогональной (декартовой) системы координат общность рассуждений не теряется.
Будем искать вектор вида
b2 = а2 + а^,
который можно сделать ортогональным ЬИ выбрав соответствующим образом значение а. Рассмотрим скалярное произведение
Ь2 • bx = 0 = a2 bv+abx bv
Из этого соотношения следует, что
а. Ь.
а = —=—L,
bx Ьх
Ьз =Оз —=—-Ь..
-- bxbx *
567567Таким образом, мы построили ортогональный вектор, удалив компоненту, параллельную Ьх, т.е. проекцию а2 на Ьх.
567
567
Б. 6. Ортогонализация Грама-Шмидта
Обобщенный итерационный цикл состоит в том, чтобы найти вектор
Х-1
^ =X а А *
который будет ортогонален векторам Ьь ..., Ьк_}. Существует к-А условий ортогональности, которые позволяют нам отыскать коэффициенты а;.
Каждый вектор можно нормализовать либо перед завершением процедуры, заменив Ь, частным ЬДЬД либо, что более эффективно, нормализуя каждый вектор Ь, сразу же после его определения.
Б.7. Рекомендуемая литература
Имеется масса прекрасной учебной литературы по линейной алгебре и векторным пространствам. Для тех, кто занимается компьютерной графикой, я бы рекомендовал сначала познакомиться с основными концепциями векторного пространства, а затем с линейной алгеброй, которую следует рассматривать в качестве математического инструментария для манипулирования объектами обобщенного векторного пространства. К сожалению, в большинстве книг по линейной алгебре основное внимание уделяется только «-мерным евклидовым пространствам R". Я бы рекомендовал в первую очередь познакомиться с книгами Бойера (Bowyer) и Вудворка (Woodwark) [Bow83] и Банхофа (Banchoff) и Вернера (Werner) [Вап83].
Для знакомства с аффинным пространством я рекомендую книгу Фоли (Foley) [Ро190Д в которой основной акцент сделан на геометрических свойствах.
Упражнения
Б.1. Докажите, что множество комплексных чисел образует скалярное поле. Какой вид в этом поле будут иметь универсальные тождественные элементы для операций сложения и умножения?
Б.2. Докажите, что множество рациональных функций образует скалярное поле.
Б.З. Докажите, что множество рациональных функций с действительными коэффициентами образует векторное пространство.
Б.4. Докажите, что количество элементов базиса определенного векторного пространства является фиксированным.
Б.5. Рассмотрите множество из п действительных функций {/(*)}, /=1,..., «. Покажите, как сформировать векторное пространство с этими элементами. Дайте определение базису и размерности этого пространства.
Б.6. Покажите, что множество полиномов степени не выше п образует «-мерное векторное пространство.
Б.7. Дайте определение операциям сложения векторов и умножения вектора на скаляр в «-мерном евклидовом пространстве R". Что представляет собой операция скалярного произведения в R"?
Б.8. Предположим, что в евклидовом пространстве R3 заданы три вектора. Как проверить, образуют ли они базис этого пространства?
Б.9. В евклидовом пространстве R3 заданы три вектора: (1, 0, 0), (1, 1,0) и (1, 1, 1). Покажите, что эти векторы линейно-независимы. Постройте на этих векторах ортогональный базис, начав с (1,0, 0).
ПРИЛОЖЕНИЕ
Матрицы
О
сновная роль матриц в математическом обеспечении систем компьютерной графики состоит в выполнении операций преобразования геометрических объектов при изменении систем координат или фреймов. Термин вектор в матричной алгебре имеет трактовку, несколько отличную от той, которая используется в векторном анализе и линейной алгебре. Чтобы не вносить излишней путаницы, при описании операций матричной алгебры мы будем пользоваться вместо общепринятого термина вектор терминами матрица-строка и .матрица- столбец. Вектором же будем называть направленный отрезок прямой и иногда, как в приложении Б, абстрактный тип данных, представляющий элемент векторного пространства.
В этом приложении собраны основные сведения из матричной алгебры, которые необходимы для понимания сути операций преобразования геометрических объектов. Практически везде будут использоваться матрицы размера 4x4. Поэтому мы опустили в этом приложении те разделы матричной алгебры, которые относятся к операциям с матрицами общего вида, в частности методы обращения произвольных квадратных матриц. В большинстве современных графических систем обращение матриц размера 4x4 выполняется системными аппаратными или программными средствами.
Основные определения
Матрицей размера пхт называется массив скаляров, организованный в виде таблицы из п строк и т столбцов. Часто параметры п и т называют размерностями матрицы по строкам и столбцам. Если т=п, то матрица называется квадратной размера (или порядка) п. В качестве скаляров мы будем использовать только действительные числа, хотя большинство представленных результатов в равной мере справедливы и в отношении комплексных чисел. Элементы матрицы А представляют собой скаляры {<?,,}, /=1,..., л,у-1,..., т. Запишем матрицу А в терминах ее элементов в виде
А=Ы-
Операция транспонирования матрицы А размером п х т формирует новую матрицу размером тх п, в которой строки исходной матрицы заменяются столбцами с тем же номером. Полученная в результате матрица называется транспонированной и обозначается А7:
А'
Специально выделяются матрицы, состоящие из одной строки (матрица размера 1 х л?) и одного столбца (матрица размера и х 1). Первая называется матрицей-строкой, а вторая — матрицей-столбцом. Матрицы-столбцы в дальнейшем будем обозначать строчными буквами:
Ь = [М
В результате транспонирования матрицы-строки образуется матрица-столбец, которую будем обозначать Ъ1.
Операции над матрицами
Существуют три базовые операции над матрицами: умножение матрицы на скаляр, сложение матрицы с матрицей и перемножение двух матриц. Мы полагаем, что скаляры представляют собой действительные числа, но это не обязательно. Главное, чтобы типы элементов матрицы и скалярного множителя были совместимы.
Операция умножения матрицы на скаляр определена для матриц А любого размера. Матрица результата образуется в результате поэлементного перемножения исходной матрицы на скалярный множитель а:
аА=[аа,у].
Операция сложения матриц определена только для пар матриц равного размера. Элементы новой матрицы— суммы матриц— являются суммами одноименных элементов матриц- слагаемых:
С = А + В=[а,у +ьД
Операция умножения матрицы А размера п х / на матрицу В размера / х т формирует матрицу произведения размером п х т:
С = АВ=[е„], где
си=^аА-
Операция перемножения имеет смысл только для квадратных матриц-сомножителей равного размера или для прямоугольных матриц, у которых количество столбцов матрицы множимого А равно количеству строк матрицы множителя В. Будем говорить, что матрица А умножается справа на матрицу В или что матрица В умножается слева на матрицу А.
Операция умножения матрицы на скаляр подчиняется простым правилам, которые справедливы для любых матриц А и скаляров а и 0:
а(0А) = (а0)А,
с/0А = 0аА.
Эти правила вытекают из того, что операция умножения матрицы на скаляр сводится к операциям над скалярными элементами матрицы и скалярным множителем. Операция сложения матриц является коммутативной, т.е. для любых матриц А и В размера п х т выполняется соотношение А + В = В + А.
Эта операция обладает также свойством ассоциативности, т.е. для любых трех матриц А, В и С размера п х т справедливо равенство
А + (В + С) = (А + В)+С.
Свойством ассоциативности обладает и операция умножения матрицы на матрицу:
А(ВС) = (АВ)С,
но эта операция не является коммутативной. При этом не только отличаются произведения, т.е. АВ * ВА, но и вполне вероятно, что одно из произведений существует, а второе — нет. Из этого утверждения следует, что в графических приложениях, где матрицы представляют преобразования объектов (сдвиг, поворот и т.д.), порядок выполнения отдельных преобразований имеет существенное значение. Поворот, за которым последует сдвиг, даст результат, совершенно отличный от того, который получится при выполнении сдвига, а затем поворота.
Единичная матрица I представляет собой квадратную матрицу, элементы главной диагонали которой равны 1, а остальные элементы равны 0:
, г 1Р’ если i = j.
I = а . а. = (
L J[0 в противном случае.
Если матрицы А. В и I имеют одинаковые размеры, то выполняются соотношения
AI = A, IB = В
Матрицы-строки и матрицы-столбцы
Особый интерес для задач компьютерной графики представляют матрицы-строки размера 1 х и и матрицы-столбцы размера п х 1. Вектор и точку трехмерного пространства1 по отношению к произвольному фрейму можно представить в виде матрицы-столбца:
Р = у .
Для обозначения матриц-столбцов будем в дальнейшем использовать строчные буквы. Результатом транспонирования матрицы р будет матрица-строка
Р7 = [* У с].
Поскольку произведение матрицы размера п х / на матрицу размера 1хт представляет собой матрицу размера п х т, то произведение квадратной матрицы размера п на матрицу- столбец размера их 1 даст новую матрицу-столбец размера пх 1. Стандартный способ геометрических преобразований в графических системах основан на использовании матриц- столбцов размером 2, 3 или 4 для представления точек или векторов и квадратных матриц для представления операторов преобразований. Следовательно, выражение
р' = Ар
описывает отдельное преобразование точки (или вектора), а выражение
р' = АВСр
описывает последовательность или суперпозицию преобразований. Обращаю ваше внимание на то, что поскольку операция умножения матриц обладает свойством ассоциативности, в этом выражении нет необходимости использовать скобки.
571571* При использовании однородных координат, которые мы рассматривали в главе 4. векторы и точки представляются по-разному.
571
571
Матрицы-строки и матрицы-столбцы
В некоторых книгах по компьютерной графике авторы предпочитают описывать точки и векторы матрицами-строками. В таком случае, учитывая, что транспонирование произведения есть произведение транспонированных сомножителей в обратном порядке, т.е.
(АВ)Г =ВГАГ,
придем к выводу, что суперпозиция трех преобразований может быть записана для матрицы строки в следующем виде:
рг =p/C7B'A,
Достоинство такой формы описания в том, что матрицы отдельных преобразований в комбинированном преобразовании перечисляются слева направо в том порядке, в котором элементарные преобразования реализуются в системе, т.е. сначала преобразование С, затем В, а потом А. Но в большинстве серьезных научных, технических и математических работ используется традиционная форма представления точек и векторов в виде матриц-столбцов. Поэтому и в данной книге мы отдали предпочтение этой форме. Хотя принципиального значения выбор той или иной формы и не имеет, при освоении нового API обязательно обратите внимание, какая именно форма представления в нем использована, поскольку от этого зависит не только порядок перечисления матриц в составном выражении, но и сами матрицы преобразований должны быть транспонированы.
ВЛ. Ранг матрицы
В математическом обеспечении систем компьютерной графики матрицы используются в первую очередь для представления точек, векторов и геометрических преобразований этих объектов. Часто возникает задача определить, является ли преобразование, описанное квадратной матрицей, обратимым. Так, если имеется преобразование
q = Ap,
желательно знать, существует ли квадратная матрица В, такая, что
p = Bq.
Подставляя исходное выражение для q, получим
р = Bq = ВАр = 1р = р,
из чего следует, что,
BA = I.
Если матрица В существует, то она называется обратной матрице А, а матрица А является невырожденной {неособенной). Матрица А, для которой не существует обратная, называется вырожденной {особенной). Матрица, обратная А, обозначается А"1.
Фундаментальное утверждение относительно обратных матриц гласит следующее:
Матрица, обратная заданной квадратной матрице, существует тогда и только тогда, когда определитель этой матрицы не равен нулю.
Вычисление определителя матрицы А (он обозначается |А|) для матриц порядка более высокого, чем второй, требует практически столько же вычислительных операций, сколько и вычислений обратной матрицы. Оценка сложности вычислений для матрицы порядка п имеет вид О{п). Для матриц второго, третьего и четвертого порядков, которые преимущественно используются в математическом обеспечении систем компьютерной графики, можно довольно быстро вычислить определитель, воспользовавшись правилом Крамера. Получив значение
определителя, можно выполнить обращение матрицы. Но обращение матриц преобразования можно провести и на основе геометрических соображений. Например, матрица, обратная матрице сдвига, задает сдвиг на то же расстояние, но в обратном направлении. Подобные варианты мы рассматривали в главе 4.
Для прямоугольных матриц общего вида особое значение имеет понятие ранга .матрицы. Квадратную матрицу можно рассматривать как матрицу-строку, элементами которой являются матрицы-столбцы, или как матрицу-столбец, элементами которой являются матрицы- строки. В терминах векторного пространства, которое мы рассматривали в приложении Б, строки матрицы размера п х т есть элементы евклидова пространства R"', в то время как столбцы есть элементы пространства R". Можно проанализировать, сколько строк (или столбцов) матрицы являются линейно-независимыми. Ранг матрицы по строкам (по столбцам) есть максимальное количество линейно-независимых строк (столбцов). Следовательно, для любой матрицы размера п х п ранг по строкам равен рангу по столбцам и матрица является неособенной тогда и только тогда, когда ее ранг равен п. Таким образом, справедливо утверждение, что матрицу можно обратить в том и только в том случае, если ее строки (и столбцы) являются линейно-независимыми.
Изменение представления
С помощью матриц можно представить изменение базиса любого множества векторов, удовлетворяющих правилам, сформулированным в Приложении Б. Предположим, что имеется л-мерное векторное пространство. Пусть {z/h z/2,..., и,д и {vb г2,..., v„) —два базиса в этом векторном пространстве. Тогда некоторый вектор г можно представить в виде
v = а,их + а2и, + ... + a„z/„ или в виде
v = P,vl+P2v2+... + P„v„.
Следовательно, (а,, а2,..., а„) и (Рь 02,..., 0») — два представления вектора г, причем каждое из них можно выразить в виде матрицы-столбца размера п х 1. Но работая с таким представлением, нужно очень внимательно относиться к обозначениям, которые должны однозначно отображать разницу в представлениях. Представление v можно записать либо в виде
v = [a, a, ... a,,]7,
либо в виде-
Разница в обозначениях соответствует разнице базисов, в которых представляется вектор.
Теперь рассмотрим, каким образом можно перейти от представления v к представлению V1. Векторы базиса {гь г2, ..., г,,} можно выразить в базисе {z/b z/2, •••, и,^. Следовательно, существует множество скаляров у,;, таких, что
и, = 7,^ + 7,2^ + - + 7„Л,,/ = 1 п.
Это выражение для всех и, можно записать в матричной форме следующим образом:
М2
где матрица А является квадратной матрицей порядка и:
Оба представления — и v. и v' — можно выразить через матрицы-столбцы
Г v = а . где а =[«,]• Можно следующим образом определить Ь: Ь = [Р,] и выразить V* в виде
Матрица А связывает два базиса, и прямой подстановкой получим
Ьг = а'А.
Таким образом, матрица А позволяет выполнять преобразование представления векторов в одном базисе в представление в другом базисе.
Векторное произведение
Для двух непараллельных векторов » и v в трехмерном пространстве векторное произведение определяет третий вектор и, ортогональный двум первым. Независимо от формы представления векторов, справедливо равенство
w и = w-v = 0.
Одну компоненту вектора и- можно установить произвольно, поскольку для нас имеет значение направление этого вектора, а не его длина. В результате остаются три условия, связывающие три компоненты вектора и\ Если в определенной системе координат вектор и имеет компоненты ah a2, a3, а вектор г— компоненты рь Р2, р3, то в этой системе координат операция векторного произведения определяется следующим образом:
w = uxv =w = uxv =a Д - аД а3Р, -аД аД - аД
w = uxv =
w = uxv =
Обращаю ваше внимание на то, что вектор и' полностью определяется векторами и и г; их представление мы используем только тогда, когда необходимо вычислить компоненты вектора w в конкретной системе координат. В правосторонней системе координат векторное произведение х х у ортов осей х и у есть вектор, параллельный орту третьей оси z.
Рекомендуемая литература
Наиболее распространенными учебниками по линейной алгебре и теории матриц являются книги Штранга (Strang) [Str93], Банхофа (Banchoff) и Вернера (Werner) [Вап83]. Также обратите внимание на книгу Роджерса (Rogers) и Адамса (Adams) [Rog90] и выпуски серии Graphics Gems [Gra90—Gra92, Gra94, Gra95].
Дискуссия о том, какая форма представления векторов предпочтительнее — матрицами- строками или матрицами-столбцами, — имеет давнюю историю. В первых книгах по компьютерной графике, в частности в книге Ньюмена (Newman) и Спрулла (Sproull) [New73], использовались матрицы-строки. Но в современной литературе четко прослеживается тенденция использования матриц-столбцов (см., например [Fol90]\ хотя в некоторых книгах, например в [Wat93], авторы по-прежнему отдают предпочтение матрицам-строкам. При работе с конкретным API отнюдь не всегда можно выяснить из документации, какое именно представление использовано, поскольку квадратная матрица в программе часто имеет вид линейного массива из и2 элементов. Некоторые API, в частности OpenGL, позволяют выполнять только умножение справа системной матрицы на матрицу, определенную в прикладной программе. В других API, например в PHIGS, поддерживается как умножение справа, так и умножение слева.
Упражнения
Рассмотрите два базиса в трехмерном пространстве R3—{(1,0,0), (1,1,0), (1, 1, 1)} и {(1, 0, 0), (0, 1,0), (0, 0, 1)}. Определите матрицы, преобразующие представление вектора в одном базисе в представление в другом. Покажите, что эти матрицы взаимно обратные.
Рассмотрим векторное пространство полиномов степени не выше второй. Покажите, что множества полиномов {1, х, х2} и {1, 1+х, 1+х+х"} являются базисами этого пространства, отыщите представление 1+2х+Зх2 в обоих базисах. Определите матрицу преобразования из одного базиса в другой.
Пусть i, j и к — единичные векторы, параллельные осям координат х, у и с в трехмерном евклидовом пространстве R3. Покажите, что векторное произведение любой пары векторов «х vзадается матрицей
к
"з
V3
В.4.В.4.Покажите, что в трехмерном евклидовом пространстве R3 выполняется соотноше
В.4.
В.4.
ние |и х v| = |w||v|sin0, где 6 — угол между векторами и и v.
Литература
Ado85Аке88Аке93Ang90ANSI85ANSI88Арр68Агп96Вап83Ваг93Ваг83Ваг 87BU76ВП77Во^83Вге65Вге87Саг 78С as 96Cat 75Ado85Аке88Аке93Ang90ANSI85ANSI88Арр68Агп96Вап83Ваг93Ваг83Ваг 87BU76ВП77Во^83Вге65Вге87Саг 78С as 96Cat 75Adobe Systems Incorporated. PostScript Language Reference Manual.— Addison- Wesley, Reading, MA, 1985.
Ado85
Аке88
Аке93
Ang90
ANSI85
ANSI88
Арр68
Агп96
Вап83
Ваг93
Ваг83
Ваг 87
BU76
ВП77
Во^83
Вге65
Вге87
Саг 78
С as 96
Cat 75
Ado85
Аке88
Аке93
Ang90
ANSI85
ANSI88
Арр68
Агп96
Вап83
Ваг93
Ваг83
Ваг 87
BU76
ВП77
Во^83
Вге65
Вге87
Саг 78
С as 96
Cat 75
Akeley K., Jermoluk T. High Performance Polygon Rendering. Computer Graphics, 22(4), 1988, p. 239-246.
Akeley K. Reality Engine Graphics. Computer Graphics, 1993, p. 109-116.
Angel E. Computer Graphics. — Addison-Wesley, Reading, MA, 1990.
American National Standards Institute (ANSI), American National Standard for Information Processing Systems-Computer Graphics-Graphical Kernel System (GKS) Functional Description. — ANSI, X3.124-1985, ANSI, New York, 1985.
American National Standards Institute (ANSI). American National Standard for Information Processing Systems—Programmer's Hierarchical Interactive Graphics System (PHIGS). — ANSI, X3.144-1988, ANSI, New York, 1988.
Appel A. Some Techniques for Shading Machine Renderings of Solids. Spring Joint Computer Conference, 1968, p. 37^-5.
Arnold K., Gosling J. The Java Programming Language. — Addison-Wesley, Reading, MA, 1996.
Banchoff T., Werner J. Linear Algebra Through Geometry. — Springer-Verlag, New York, 1983.
Barnsley M. Fractals Everywhere, Second Edition. — Academic Press, San Diego, CA, 1993.
Barsky B. A., Beatty C. Local Control of Bias and Tension in Beta-Splines. ACM Transactions on Graphics, 2(2), 1983, p. 109-134.
Bartels, Beatty C., Barsky B. A. An Introduction to Splines for use in Computer Graphics and Geometric Modeling. — Morgan-Kaufman, Los Altos, CA, 1987.
Blinn J. F., Newell M. E. Texture and Reflection in Computer Generated Images. — CACM, 19(10), 1976, p. 542-547.
Blinn J. F. Models of Light Reflection for Computer-Synthesized Pictures. Computer Graphics, 11(2), 1977, p. 192-198.
Bowyer A., Woodwark J. A Programmer's Geometry. — Butterworth, 1983.
Bresenham J. E. Algorithm for Computer Control of a Digital Plotter. IBM Systems Journal, January, 1965, p. 25-30.
Bresenham J. E. Ambiguities in Incremental Line Rastering. IEEE Computer Graphics and Applications, May, 1987, p. 31-43.
Carlbom I., Paciorek J. Geometric Projection and Viewing Transformations. Computing Surveys, 1(4), 1978, p. 465-502.
Castleman К. C. Digital Image Processing. — Prentice-Hall, Englewood Cliffs, NJ, 1996.
Catmull E. A Hidden-Surface Algorithm with Antialiasing. Computer Graphics, 12(3), 1975, p. 6-11.
Cha98Clu82Coh85Coh88Coo82Coo87Cro81Der88Der89Dre88Ebe94End84Eng68Far88Fau80Fo!90Fol94Fos96Fou82Gla89Gla95Cha98Clu82Coh85Coh88Coo82Coo87Cro81Der88Der89Dre88Ebe94End84Eng68Far88Fau80Fo!90Fol94Fos96Fou82Gla89Gla95Chan P., Lee R. The Java Class Libraries: Java.Applet, Java.Awt, Java.Beans (Vol 2). — Addison-Wesley, Reading, MA, 1998.
Cha98
Clu82
Coh85
Coh88
Coo82
Coo87
Cro81
Der88
Der89
Dre88
Ebe94
End84
Eng68
Far88
Fau80
Fo!90
Fol94
Fos96
Fou82
Gla89
Gla95
Cha98
Clu82
Coh85
Coh88
Coo82
Coo87
Cro81
Der88
Der89
Dre88
Ebe94
End84
Eng68
Far88
Fau80
Fo!90
Fol94
Fos96
Fou82
Gla89
Gla95
Clark J. E. The Geometry Engine: A VLSI Geometry System for Graphics. Computer Graphics, 16, 1982, p. 127-133.
Cohen M. F., Greenberg D. P. The Hemi-Cube: A Radiosity Solution for Complex Environments. Computer Graphics, 19(3), 1985, p. 31-Л0.
Cohen M. F., Chen S. E., Wallace J. R., Greenberg D. P. A Progressive Refinement Approach to Fast Rasiosity Image Generation. Computer Graphics, 22(4), 1988, p. 75-84.
Cook R. L., Torrance К. E. A Reflectance Model for Computer Graphics. ACM Transactions on Graphics, 1(1), 1982, p. 7-24.
Cook R. L., Carpenter L., Catmull E. The Reyes Image Rendering Architecture. Computer Graphics, 21(4), July, 1987, p. 95-102.
Crow F. C. A Comparison of Antialiasing Techniques. IEEE Computer Graphics and Applications, 1(1), 1981, p. 40-Л8.
DeRose T. D. A Coordinate Free Approach to Geometric Programming. — SIGGRAPH Course Notes, SIGGRAPH, 1988.
DeRose T. D. A Coordinate Free Approach to Geometric Programming. Theory and Practice of Geometric Modeling. W. Strasser and H.P. Seidel (Eds.)— SpringerVerlag, Berlin, 1989.
Drebin R. A., Carpenter L., Hanrahan P. Volume Rendering. Computer Graphics, 22(4), 1988, p. 65-74.
Ebert D. S. (Ed) Texturing and Modeling: A Procedural Approach. — Academic Press, New York, 1994.
Enderle G., Kansy K., Pfaff G. Computer Graphics Programming: GKS-The Graphics Standard.— Springer-Verlag, Berlin, 1984. (Имеется русский перевод: Эндерле Г., Канси К., Пфафф Г. Программные средства машинной графики. Международный стандарт GKS. — М.: Радио и связь, 1988.)
Engelbart D. С., English W. К. A Research Center for Augmenting Human Intellect. Fall Joint Computer Conference. — Thompson Books, Washington, DC, 1968.
Farin G. Curves and Surfaces for Computer Aided Geometric Design. — Academic Press, New York, 1988.
Faux I. D., Pratt M. J. Computational Geometry for Design and Manufacturing. — Halsted, Chichester, England, 1980. (Имеется русский перевод: ФоксА., Пратт М. Вычислительная геометрия. Применение в проектировании и на производстве. — М.: Мир, 1982.)
Foley J. D., van Dam A., Feiner S. К., Hughes J. F. Computer Graphics, Second Edition.— Addison-Wesley, Reading, MA, 1990 (C Version 1996). (Имеется русский перевод первого издания: Фоли Дж., ван Дэм А. Основы интерактивной машинной графики: В 2-х книгах. — М.: Мир, 1985.)
Foley J. D., van Dam A., Feiner S. К., Hughes J. F., Phillips R. Introduction to Computer Graphics. — Addison-Wesley, Reading, MA, 1994.
Fosner R. OpenGL Programming for Windows 95 and Windows NT. — Addison- Wesley, Reading, MA, 1996.
Fournier A., Fussell D., Carpenter L. Computer Rendering of Stochastic Models. CACM, 25(6), 1982, p. 3 71-3 84.
Glassner A. S. (Ed.) An Introduction to Ray Tracing. — Academic Press, New York, 1989.
Glassner A. S. Principles of Digital Image Synthesis. — Morgan-Kaufmann, New York, 1995.
Gol83Gon87Gor84Gou71Gra90Gra91Gra92Gra94Gra95Hal89Har96Hea94Hec84Hek86HU90Hop83Hop911SO88Jar76Joy88Kaj86Kil94,aKil94,bКП96Knu87Kov97Gol83Gon87Gor84Gou71Gra90Gra91Gra92Gra94Gra95Hal89Har96Hea94Hec84Hek86HU90Hop83Hop911SO88Jar76Joy88Kaj86Kil94,aKil94,bКП96Knu87Kov97Goldberg A., Robson D. Smalltalk-80: The Language and Its Implementation. — Addison-Wesley, Reading, MA, 1983.
Gol83
Gon87
Gor84
Gou71
Gra90
Gra91
Gra92
Gra94
Gra95
Hal89
Har96
Hea94
Hec84
Hek86
HU90
Hop83
Hop91
1SO88
Jar76
Joy88
Kaj86
Kil94,a
Kil94,b
КП96
Knu87
Kov97
Gol83
Gon87
Gor84
Gou71
Gra90
Gra91
Gra92
Gra94
Gra95
Hal89
Har96
Hea94
Hec84
Hek86
HU90
Hop83
Hop91
1SO88
Jar76
Joy88
Kaj86
Kil94,a
Kil94,b
КП96
Knu87
Kov97
Gonzalez R., Wintz P. Digital Image Processing, Second Edition. — Addison-Wesley, Reading, MA, 1987.
Goral С. M., Torrance К. E., Greenberg D. P., Battaile B. Modeling the Interaction of Light Between Diffuse Surfaces. Computer Graphics (SIGGRAPH 84), 18(3), 1984, p. 213-222.
Gouraud H. Computer Display of Curved Surfaces. IEEE Trans. Computers, C-20, 1971, p. 623-628.
Graphics Gems I, Glassner A. S. (Ed.) — Academic Press, San Diego, CA, 1990.
Graphics Gems II, Arvo J. (Ed.) — Academic Press, San Diego, CA, 1991.
Graphics Gems III, Kirk D. (Ed.) —Academic Press, San Diego, CA, 1992.
Graphics Gems IV, Heckbert P. (Ed.) — Academic Press, San Diego, CA, 1994.
Graphics Gems V, Paeth A. (Ed.) — Academic Press, San Diego, CA, 1995.
Hall R. Illumination and Color in Computer Generated Imagery. — Springer-Verlag, New York, 1989.
Hartman J., Wernecke J. The VRML 2.0 Handbook. — Addison-Wesley, Reading, MA, 1996.
Hearn D., Baker M. P. Computer Graphics, Second Edition. — Prentice-Hall, Englewood Cliffs, NJ, 1994.
Heckbert P. S., Hanrahan P. Beam Tracing Polygonal Objects. Computer Graphics, 18(3), 1984, p. 119-127.
Heckbert P. S. Survey of Texture Mapping. IEEE Computer Graphics and Applications, 6(11), 1986, p. 56-67.
Hill F. S. Computer Graphics. — MacMillan, New York, 1990.
Hopgood F. R. A., Duce D. A., Gallop J. A., Sutcliffe D. C. Introduction to the Graphical Kernel System: GKS. — Academic Press, London, 1983.
Hopgood F. R. A., Duce D. A. A Primer for PH1GS. — John Wiley Sons, Chichester, England, 1991.
International Standards Organization, International Standard Information Processing Systems—Computer Graphics—Graphical Kernel System for Three Dimensions (GKS- 3D), ISO Document Number 8805:1988(E). — American National Standards Institute, New York, 1988.
Jarivs J. F., Judice C.N., Ninke W. H. A Survey of Techniques for the Image Display of Continuous Tone Pictures on Bilevel Displays. Computer Graphics and Image Processing, 5(1), 1976, p. 13-40.
Joy К. 1., Grant C. W., Max N. L., Hatfield L. Computer Graphics: Image Synthesis. — Computer Society Press, Washington, DC, 1988.
Kajiya J. T. The Rendering Equation. Computer Graphics, 20(4), 1986, p. 143-150.
Kilgard M. J. OpenGL and X, Part 3: Integrated OpenGL with Motif. The X Journal, SIGS Publications, July/August 1994.
Kilgard M. J. An OpenGL Toolkit. The X Journal, SIGS Publications, November/December 1994.
Kilgard M. OpenGL Programming for the X Windows System. — Addison-Wesley, Reading, MA, 1996.
Knuth D. E. Digital Halftones by Dot Diffusion. ACM Transactions on Graphics, 6(40), 1987, p. 245-273.
Kovatch P. J. The Awesome Power of Direct3D/DirectX. — Manning Publications Company, 1997.
Las87 Lasseter J. Principles of Traditional Animation Applied to 3D Computer Animation. Computer Graphics, 21(4), 1987, p. 33—44.
Lev88 Levoy M. Display of Surface from Volume Data. IEEE Computer Graphics and Applications, 8(3), 1988, p. 29-37.
Lia84Liang Y., Barsky B. A New Concept and Method for Line Clipping. ACM
Transactions on Graphics, 3(1), 1984, p. 1-22.
Lin68Lindenmayer A. Mathematical Models for Cellular Interactions in Biology.
J. Theoretical Biology, 18, 1968, p. 280-315.
Lor87 Lorensen W. E., Cline H. E. Marching Cubes: A High Resolution 3D Surface Construction Algorithm. Computer Graphics, 21(4), 1987, p. 163-169.
Mag85 Magnenat-Thalmann N., Thalmann D. Computer Animation: Theory and Practice. — Springer-Verlag, Tokyo, 1985.
Man82 Mandelbrot B. The Fractal Geometry of Nature. — Freeman Press, New York, 1982.
Mat95The Math Works. Student Edition of MatLab Version 4, Users Guide. — Prentice-Hall,
Englewood Cliffs, NJ, 1995.
Max51 Maxwell E. A. General Homogeneous Coordinates in Space of Three Dimensions.— Cambridge University Press, Cambridge, England, 1951.
New73 Newman W. M., Sproull R. F. Principles of Interactive Computer Graphics.— McGraw-Hill, New York, 1973. (Имеется русский перевод: Ньюмен У., Спрулл Р. Основы интерактивной машинной графики. — М.: Мир, 1976.)
Nie94 Nielson J. Usability Engineering. — Academic Press, New York, 1994.
Ope93,a OpenGL Architecture Review Board. OpenGL Programming Guide. ■— Addison- Wesley, Reading, MA, 1993.
Ope93,b OpenGL Architecture Review Board. OpenGL Reference Manual. — Addison-Wesley, Reading, MA, 1993.
OSF89 Open Software Foundation. OSF/Motif Style Guide. — Prentice-Hall, Englewood Cliffs, NJ, 1989.
Ost94 Osterhout J. Tel and the Tk Toolkit. — Addision-Wesley, Reading, MA, 1994.
Pap81 Papert S. LOGO: A Language for Learning. — Creative Computer Press, Middletown, NJ, 1981.
Pav95 Pavlidis T. Interactive Computer Graphics in X. — PWS Publishing, 1995.
Per89Perlin K., Hoffert E. Hypertexture. Computer Graphics, 23(3), 1989, p. 253-262.
PH1G88PHIGS+ Committee. PHIGS+ Functional Description, Revision 3.0. Computer
Graphics, 22(3), July, 1988, p. 125-218.
Pho75 Phong В. T. Illumination for Computer Generated Scenes. Communications of the ACM, 18(6), 1975, p. 311-317.
Pie88 Peitgen H. O., Saupe S. (Ed.) The Science of Fractal Images. — Springer-Verlag, New York, 1988.
Pik84 Pike R., Guibas L., Ingalls D. Bitmap Graphics. Computer Graphics, 18(3), 1984, p. 135-160.
Por84 Porter T., Duff T. Compositing Digital Images. Computer Graphics, 18(3), 1984, p. 253-259.
Pra78 Pratt W. K. Digital Image Processing. — Wiley, New York, 1978.
Pn<90 Prusinkiewicz P., Lindenmayer A. The Algorithmic Beauty of Plants. — SpringerVerlag, Berlin, 1990.
Rie81 Riesenfeld R. F. Homogeneous Coordinates and Projective Planes in Computer Graphics. IEEE Computer Graphics and Applications, 1(1), 1981, p. 50-56.
Rev83 Reeves W. T. Particle Systems - A Technique for Modeling a Class of Fuzzy Obejcts. Computer Graphics, 17(3), 1983, p. 359-376.
Rey87 Reynolds C.W. Flocks, Herds, and Schools: A Distributed Behavioral Model. Computer Graphics, 21(4), 1987, p. 25-34.
Rog85Rogers D. F. Procedural Elements for Computer Graphics, Second Edition. —
McGraw-Hill, New York, 1998.
Rog90Rogers D. F., Adams J. A. Mathematical Elements for Computer Graphics. —
McGraw-Hill, New York, 1990. (Имеется русский первод: Роджерс Д., Адамс Дж. Математические основы машинной графики. — М.: Мир, 2001.)
Rus97 Rush К., Deering М., Sowizral Н. A. The Java 3D API Specification.— Addison- Wesley, Reading, MA, 1997.
Sch88 Schiefler R. W., Gettys J., Newman R. X-Window System. — Digital Press, 1988.
Schi97 Schneiderman B. Designing the User Interface: Strategies for Effective HumanComputer Interaction, Third Edition. — Addison-Wesley, Reading, MA, 1997.
Sch85 Shoemaker K. Animating Rotation with Quaterion Curves. Computer Graphics, 19(3), 1985, p.245-254.
Schr98 Schroeder W., Martin K., Lorensen B. The Visualization Toolkit. — Prentice-Hall, 1998.
Seg92 Segal M., Akeley K. The OpenGL Graphics System: A Specification, Version 1.0. — Silicon Graphics, 1992.
Sie81 Siegel R., Howell J. Thermal Radiation Heat Transfer. — Hemisphere, Washington, DC, 1981.
SH89 Sillion F. X., Puech C. A General Two-Pass Method Integrating Specular and Diffuse Reflection. Computer Graphics, 22(3), 1989, p. 335-344.
Smi84 Smith A. R. Plants, Fractals and Formal Languages. Computer Graphics, 18(3), 1984, p. 1-10.
Str93 Strang G. Introduction to Linear Alegbra. — Wellesley-Cambridge Press, 1993.
Sut63 Sutherland I. E. Sketchpad, A Man-Machine Graphical Communication System, SJCC, 329. — Spartan Books, Baltimore, MD, 1963
Sut74,a Sutherland I. E., Hodgeman G. W. Reentrant Polygon Clipping. Communications of the ACM, 17, 1974, p. 32-42.
Sut74,b Sutherland 1. E., Sproull R. F., Schumacker R. A. A Characterization of Ten Hidden- Surface Algorithms. Computer Surveys, 6(1), 1974, p. 1-55.
Tor67 Torrance К. E., Sparrow E. M. Theory for Off-Specular Reflection from Roughened Surfaces. J. Optical Society of America, 57(9), 1967, p. 1105-1114.
Tuj83 Tufte E. R. The Visual Display of Quantitative Information. — Graphics Press, Cheshire, CN, 1983.
Tuf90Tufte E. R. Envisioning Information. — Graphics Press, Cheshire, CN, 1990.
Tuj97Tufte E. R. Visual Explanations. — Graphics Press, Cheshire, CN, 1997.
Ups89 Upstill S. The Renderman Companion: A Programmer's Guide to Realistic Computer Graphics. — Addison-Wesley, Reading, MA, 1989.
Van94 Van Gelder A., Wilhelms J. Topological Considerations in Isosurface Generation. ACM Transactions on Graphics, 13(4), 1994, p. 337-375.
Wat92 Watt A., Watt M. Advanced Animation and Rendering Techniques. — Addision- Wesley, Wokingham, England, 1992.
Wat93 Watt A. 3D Computer Graphics, Second Edition.— Addison-Wesley, Wokingham, England, 1993.
Wer94Wernecke J. The Inventor Mentor. — Addison-Wesley, Reading, MA, 1994.
lVes90Westover L. Footprint Evaluation for Volume Rendering. Computer Graphics, 24(4),
1990, p. 367-376.
Whi80 Whitted T. An Improved Illumination Model for Shaded Display. Communications of ACM, 23(6), 1980,p. 343-348.
W’it94,a Witkin A. P., Heckbert P. S. Using Particles to Sample and Control Implicit Surfaces. Computer Graphics, 28(3), 1994, p. 269-277.
H’it94,b Witken A. (Ed.). An Introduction to Physically Based Modelling.— Course Notes, SIGGRAPH 94.
Ho!9i Wolfram S. Mathematica. — Addison-Wesley, Reading, MA, 1991.
Wri97 Wright R. S. Jr., Sweet M. OpenGL Superbible.— Wite Group Press, Corte Madera, CA, 1997.
Wys282 Wyszecki G., Stiles W. S. Color Science. — Wiley, New York, 1982.
Предметный указатель
D
Direct 3D. графическая система, 373
F
FTP (File Transfer Protocol), протокол передачи файлов. 370
G
GKS (Graphical Kernel System). 108: 216
GLU (GL Utility) library'. 88
GLUT (GL Utility toolkit). 84; 88; 104
H
HTML (Hypertext Markup Language), язык разметки гипертекста. 370
I
Internet. 369
Internet Explorer, 29
M
Macintosh. 104
Microsoft Windows, 84
N
Netscape. 29
NTSC (National Television Systems Committee), 327'
О
Open Inventor, надстройка над OpenGL. 361;
371
OpenGL. 26
API. 44
атрибуты. 72
буферы, 395
визуализация. 211-16
кривые. 456
операции над пискелями, 397
поверхности, 457
положение камеры. 211-16 цвет. 77
Р
PHIGS (Programmer’s Hierarchical Interactive Graphics System). 44: 108; 216; 348
PostScript, 70
R
Renderman, 47; 373
s
Sketchpad, проект. 103
T
TCP/IP (Transmission Control Protocol/lnternet Protocol). 369
V
VRML (Virtual Reality Modeling Language), язык моделирования виртуальной реальности. 44; 371
w
World Wide Web, 370
z
z-буфер. 227
A
Абстрактный тип данных. 150; 166
Автоматизация проектирования. 27
Аддитивная цветовая модель. 74
Алгебра множеств. 362
Альфа-канал. 78; 400
Анимация. 135-39; 352
Аплет. 372
Архитектура
графических систем. 47-52 конвейерная. 49
Атрибуты
размер точки. 80
цвет отображения точек. 80
цвет чистого экрана, 80
Аффинное преобразование. 164 пространство. 150 сложение. 150; 153
Б
Базис. 157
Базисная функция сплайна. 442
Бернштейна полином. 436
Биювая плоскость. 376
Буфер. 376-77
глубины (z). 395; 403
источник. 391
кадра. 29
глубина. 30 полноцветный. 30 рабочий. 137 фоновый. 137
маски. 395
накопитель. 395; 406
приемник. 391
рабочий. 395 фоновый. 395 цвета. 395
В
Ввода устройства. 105-12 джойстик. 107 клавиатура. 105 логические. 105; 108
мышь, 106
планшет. 106
спейсбол, 108
трекбол, 106
виртуальный, 199
указания. 105
физические, 105
Вектор. 148
вертикали вида. 216
вертикали картинной плоскости, 217
градиента. 262
компоненты. 157
координаты. 157
нормаль к картинной плоскости. 216
нормаль к поверхности. 261
нулевой длины. 151
половинного направления. 264
сложение с вектором. 152
сложение с точкой. 152
сочленение начала с концом, 148
Векторное
произведение. 154
пространство, 150
Векторный дисплей. 31
Вершина. 34
список. 171
Видовое окно, 86; 293
Визуализация. 80
данных научных исследований. 493-520
зона видимости, 81
перспектива. 210
двухточечная. 54; 211
одноточечная, 54; 211
точка схода. 54; 211
трехточечная. 211
прямоугольник видимости. 81
функции OpenGL. 64
Виртуальная реальность. 28
Вокссль. 506
Восстановление непрерывной функции. 413
Выпуклая оболочка. 154
Выпуклость. 153
Г
Гамма-коррекция. 330
Геометрическая оптика. 35
Геометрические преобразования. 50
Геометрический процессор, 50
Геометрия, 171
Геометрия фракталов. 483
Гистограмма распределения яркости. 399
Гладкость кривой. 425
Глаз человека, 38
Глубина резкости. 41
Горнера схема вычисления полинома, 447
Грамматика формы. 482
Граф. 340
Графика, инвариантная к устройствам. 62
Графический сервер. 112: 369
Гуро (Gouraud Н.). 268
д
Двойная буферизация. 137:395
Деление
перспективное, 293
Дерево
4-арное. 367
8-арное. 367
БРП (бинарное, разделения пространства), 365
динамический узел, 351
обход, 343; 363
Дискретизация в пространственной области, 324
Дисплейная память. 114; 326
Дисплейный
процессор. 48: 114
список. 115: 369
файл. 113
Диффузная поверхность. 37
Диффузное отражение, 250: 257
идеальное. 257
3
Зависимые переменные. 420
Закрашивание. 247; 289
метод Гуро, 268
метод Фонга. 269
многоугольников, 266
плоское, 266
Зеркальное отражение. 250
Зона видимости. 224; 288
каноническая, 231
пирамида, 224
И
Изоповерхность, 506
Интерполяция
билинейная, 173; 269
вдоль строк растра, 173
Интерфейс прикладного программирования. 44; 63
К
Камера
спецификация параметров. 45: 211-16
фрейм.212
Камера-обскура, 40
Карта микрорельефа. 377
Карта отражения. 388
Карта среды. 388
Картинная плоскость. 42; 206
Касательная плоскость. 262
Кассини овалы. 495
Кватернионы, 202
Класс параметрической непрерывности. 434
Клиент/сервер. архитектура, 112: 369
Кокса-де Бура рекурсивные функции, 443
Компьютерная графика. 25
области применения. 26
Конвейерная архитектура. 49
Конструктивная геометрия тел (КГТ). 157: 362
Коха снежинка. 99: 481
Коэффициент
поглощения. 400
прозрачности. 401
резкости бликов. 259; 276
смешивания. 402
формы, 282
Кривая
В-сплайн. 438
NURBS (неравномерный рациональный).
446: 460
базисная матрица. 440
неравномерный. 445
открытый. 445
равномерный. 444
функции смешивания, 440
геометрическая непрерывность. 434
заполнения пространства. 99
заполняющая, 481
параметрическая непрерывность. 434
полиномиальная. 72
форма Безье, 435: 445; 456
разбиение, 448
форма Эрмита, 432
Кривизна, 420
л
Ламберта закон. 257
Линейная фильтрация. 407
Линейная функция. 177
Линия потока. 518
Линия уровня. 495
Локатор. 109
Луч. 36
трассировка, 38; 279-282
м
Максвелла треугольник. 101
Мандельброта множество. 488
Маркированный квадрат. 498
Матрица
вида. 83; 167: 292
масштабирования, 183
перспективной нормализации, 238
поворота. 184
проецирования. 83: 231
сдвига. 183
скоса. 185
Меню. 127-128
Метаметрическая пара, 75
Метод анализа излучательности. 282
Многоугольник. 67
выпуклый. 68
закрашивание внутренней области, 321
общего вида. 319
плоский, 68
простой. 68
разбиение, 319
самопересекающийся. 318
тест на четность-нечетность, 318
тест охватывания. 318
Моделирование. 288; 337-51
Модуль вектора, 151
Морфинг. 353
н
Наблюдатель. 34
Найквиста теорема о выборках, 411; 413
Наложение
изображения окружающих предметов, 388
микрорельефа, 377; 390
проективной текстуры, 377-388
Направление проецирования, 206
Направляющие
косинусы, 191
углы, 191
Независимые переменные, 420
Нелицевые грани. 307
Немедленное отображение, режим. 73; 114
Нормализация проецирования. 230: 304
Нормаль, вектор, 261
Нуль-вектор. 151
Ньютона второй закон. 468
О
Обратная задача кинематики, 352
Объектно-ориентированное программирование.
355
Объемное множество данных. 505
Овалы Кассини. 495
Однородные координаты. 162
преобразования. 182
Ориентированный ациклический граф, 341
Ортогональность. 154
Освещение
глобальное. 248
локальное. 248
Отображение. 326-31
от ближнего к дальнему. 310
от дальнего к ближнему, 310
Отображение с сохранением, режим, 115
Отсечение. 49: 50; 289: 293
алгоритм Коэна-Сазерленда. 294; 303
алгоритм Лианга-Барского. 296; 303
алгоритм Сазерленда-Ходжмена, 298
в трехмерном пространстве. 303
конвейер. 300
многоугольников. 298
отрезков. 294
пирамида видимости, 224
рамка, 50; 81
п
Первичные цвета. 39
Пересылка битового блока. 140: 391
Перспективное деление. 223; 231
Перспективные сокращения, 210
Пиксель. 29: 376
Пирамида видимости, 224
Плоскость, 155
проекции, 42
светоприемника, 46
Поверхность
алгебраическая. 421; 454
внутренняя сторона, 170: 275
идеально отражающая, 250
квадратичная. 421; 454: 460
Ламбертова. 257
неявная форма представления, 421
порция. 424
разделяемая. 432
тензорного произведения. 432
форма Безье, 437; 457
разбиение. 451
форма Эрмита, 433
шероховатая, 257
Поворот
вокруг произвольной оси, 188
матрица. 184
фиксированная точка. 187
Поглощение света, 78; 400
Позиция растра, 71; 395
Позиция растра, параметр состояния, 120
Показания. 109
Поле зрения. 41
Полигональная сеть, 100
Полиномиальные кривые, 72
Полное внутреннее отражение, 265
Полутона, 330
Поля превышений, 494
Порция смешивания. 431
Порядок полинома, 423
Правило правой руки, 171
Преломление, 37; 250
Преломления показатель, 265
Преобразование. 176
аффинное. 176-182
в OpenGL. 193
изометрическое, 180
масштабирование. 181
матрица масштабирования, 183
матрица сдвига. 182
нормализация, 289
обобщенное, 182
отражение, 181
перспективное, 221
перспективной нормализации, 236; 289; 305;
311
поворот, 179; 187-193
сдвиг. 179
скос, 185
суперпозиция, 186
экземпляра, 189; 338
Примитивы, 66-72
замкнутая ломанная, 67
кривая, 72
ломанная линия, 67
многоугольник, 67
отрезок. 67
полоса
из треугольников. 69; 267; 319
из четырехугольников. 69; 267
розетка из треугольников, 70; 319
точка. 60
треугольник. 69
четырехугольник. 69
Проективное преобразование. 51; 220
Проекция
аксонометрическая. 208
диметричсская. 208
изометрическая, 208
триметрическая. 208
косоугольная, 209: 233: 304
ортогональная. 82; 208
параллельная, 208-210
перспективная, 210; 221; 236
плоская геометрическая, 206
Проецирующий луч, 42; 206
Прозрачность, 78
Прямая, 152
неявная форма. 421
Прямоугольная оболочка, 301; 310
Псевдоцвет, 398
Р
Разбиение рекурсивное. 270
Разрешающая способность. 39
Рассеивающая поверхность, 37
Растр. 29
позиция. 395
прогрессивная развертка. 32
чересстрочная развертка. 32
Растровое преобразование. 30; 51; 289; 313
алгоритм Брезенхэма, 315
алгоритм ЦДА, 314
Регенерация изображения, 31
Режим побитовой записи. 393
Режим работа с матрицами. 83
Режимы ввода
выборок, 111
обработки событий, 111; 121-27
по запросу. НО
Резиновая линия, 140
Рекурсивная формула де Кастельо, 443
Рекурсивное разбиение, 90
Рефракция, 37
с
Сазерленд A. (Sutherland I.). 27; 53: 103
САПР. 27
СБИС. 49
Свертка. 408
Свет. 35-36
диффузное отражение, 250; 257
зеркальное отражение, 250: 258
источник. 250
прожектор. 253: 274
точечный. 252
удаленный, 254
монохраматический, 36
преломление, 250: 265
фоновый. 252: 257
функция излучения. 251
Световое перо, 107
Связывание атрибутов, 73
Сглаживание усреднением по области, 325
Серпинского узор. 56; 89-96; 482
Сигнал синхронизации, 109
Символ, 338
Симулятор. 28; 219
Синтезированная камера, 42
Система координат, 62; 158
мировая, 62: 292
наблюдателя. 292
нормализованная устройства отображения, 293;321
объекта. 292
окна OpenGL. 231
отсечения, 292
правосторонняя. 155
прикладная. 62
текстуры, 378
экрана, 231; 293
Скаляр. 148
Скалярное поле. 505
Скалярное произведение, 154
Снелля закон. 265
Событие. 121-27
изменение размеров окна, 124
очередь. 111
Соотношение сторон. 85
Стек. 116; 196; 345
Степень полинома, 423
Степень свободы, 108; 177; 468
Стереопара, 395
Структурированное множество данных, 506
т
Тексель. 378
Текст, 70; 117-121: 396
Тени. 240-242
Термальная шкала. 398
Тип объекта
GLLINELOOP. 67
GLLINESTRIP, 67
GLLINES. 67
GL POINTS. 60
GLPOLYGON. 69
GLQUADSTRIP. 69
GLQUADS, 69
GL TRIANGLE FAN, 69
GL TRIANGLE STR1P. 69
GL TR1ANGLES, 69
Тонирование, 47
глобальное, 278
локальное, 247
Топология, 171
Точка, 148
вычитание точек, 151
опорная полиномиальной кривой, 425
привязки вида. 216
сложение с вектором. 152
сопряжения полиномиальных сегментов, 424
Трассировка лучей, метод. 38; 279-82
Треки частиц. 518
У
Угол
азимута. 220
возвышения. 220
зрения. 41; 224
крен, 220
кручения. 220
отражения, 263
падения, 263
половинного направления, 264
рыскание, 220
тангаж, 220
Эйлера, 201
Удаление невидимых поверхностей, 95; 227;
289; 305
алгоритм z-буфера, 308; 320
алгоритм маляра, 310
алгоритм построчного сканирования. 312
анализ пространства изображения, 306
анализ пространства объектов. 305
отбраковка нелицевых граней. 307 оценка сложности, 306 сортировка по глубине. 310
Узел дерева динамический. 351 корневой. 340 терминальный, 340 Указание объектов. 129 Универсальный локатор ресурса (Uniform
Resource Locator— URL), 370 Уравнение
в неявной форме. 262: 421
в параметрической форме. 262; 296: 422 в явной форме. 420
Уровень изоповерхности, 506: 508 Устройства указания. 33
Ф
Физические модели. 467-472 Фиксированная точка преобразования. 180 Фокусное расстояние. 41: 45: 53 Фонг (Phong ВТ.). 248 метод закрашивания. 269 модель отражения. 255: 260
Фракталы. 98: 483-90
графтал. 483
Фрейм. 159
камеры.167; 206
мировой. 167: 338 модели. 338
Функция OpenGL glAccum(). 409 glBegin(). 59: 66 glBeginTrim(). 460 glBitmapO, 396 glBlendFunc(). 402 glCallList(). 116 glCallLists(). 120 glClear(). 137; 228: 406 glClearColor(). 78: 80 glColor3f(). 77: 80 glColorMaterial(). 276 glColorPointer(). 175 glCopyPixels(). 398 glCullFace(). 308 glDepthFunc(). 309 glDepthMask(). 403 glDrawElements(). 175: 176 glDrawPixelsQ. 398 glEnable(). 96: 228; 331: 384: 402 glEnableClientState(). 175 glEnd(). 59: 66
glEndList(). 115; 119: 121
glEndTrim(). 460
glEvalCoordlf(), 457
glEvalCoord2*(). 457
glEvalMeshl(). 457
glEvalMesh2(), 458
glFlush(). 61
glFrustum(). 225; 229; 409
glGenListsQ. 117: 119
glHint(). 387
gllndexi(). 79
glLightf(). 273: 274
glLightfv(). 273
glLightModcl(). 275
glListBase(). 120
glLoadIdentity(). 195
glLoadMatrix(). 169: 220
glLoadMatrixf(). 195; 198
glLogicOp(). 394
glMaplf(). 456
glMap2f(). 457: 459
glMapGridl f(). 457
glMapGrid2*(). 458
glMaterialf(). 275
glMaterialfvQ. 275
glMatrixModc(). 83: 193: 195: 346
glMultMatrixf(). 195
glNewList(). 115: 119: 121
glNormal3f(). 263
glNormal3fv(). 263
glOrtho(). 82: 86; 214: 226; 231: 407
glPersective(). 407
glPixelMap(). 398
glPointQ. 99
glPointSize(). 80
glPolygonModc(). 69
glPolygonOffset(). 497
glPopAttrib(). 117: 347
glPopMatrix(), 117: 120: 196; 346; 360
glPushAttrib(). 117: 347
glPushMatrix(). 117; 120: 196; 346; 350: 360
glRasterPos*(). 120: 395
glRasterPos3f(). 395
glReadPixels(). 398
glRotatef(). 195
glScalef(). 195
gfShadcModeK). 267: 268
glTexCoord2f(). 384
glTexEnv(). 387
giTexGenfv(). 389
glTc.\Image2D(). 383
glTe.xParameter(). 385
glTranslatcO, 118
glTranslatef(). 119: 195 gluBeginNurbsCurve(), 460 gluBeginNurbsSurface(). 460 gluBuild2DMipmaps(). 386 gluCylinder(), 461 gluEndNurbsCurve(). 460 gluEndNurbsSurface(), 460gluLookAt(). 219; 228: 229 gluNe\\NurbsRenderer(), 460 gluNe\vQuadric(). 461 gluNe\vTess(). 319 gluNurbsCurve(). 460 gluNurbsProperty(). 460 gluNurbsSurface(), 460 gluOrtho2D(). 82 gluPerspective(). 225 gluPwlCurve(). 460 gluQuadricDrawStyle(). 461 glutBitmapCharacter(). 71; 120 glutBitmapWidth(). 121 glutCreateWindow(). 85: 127 glutDisplayFunc(). 88: 126 glutldleFunc(), 136 glutlnit(). 85glutlnitDisplayMode(). 85; 96: 127; 137; 228 glutMotionFunc(). 126 glutMouscFunc(). 122 glutPostDisplayO, 126 glutPostRedisplayQ. 136 glutSetColor(). 79 glutSetWindow(). 127 glutStrokeCharacter(), 120 glutSwapBufters(). 137 glVertex(), 212 gIVertex*, 59 glVertexPointer(). 175 glVie\vport(). 86 gMultMatrixf(). 198 ввода информации. 65 визуализации. 64 геометрические преобразования. 64 задания атрибутов. 64 построения примитива. 64Функция с обратным вызовом, 112 движение мыши. 126 отображение. 126 перерисовка, 124 простой. 127XХарактеристический код области. 294 Хроматические координаты. 328glTranslatef(). 119: 195 gluBeginNurbsCurve(), 460 gluBeginNurbsSurface(). 460 gluBuild2DMipmaps(). 386 gluCylinder(), 461 gluEndNurbsCurve(). 460 gluEndNurbsSurface(), 460gluLookAt(). 219; 228: 229 gluNe\\NurbsRenderer(), 460 gluNe\vQuadric(). 461 gluNe\vTess(). 319 gluNurbsCurve(). 460 gluNurbsProperty(). 460 gluNurbsSurface(), 460 gluOrtho2D(). 82 gluPerspective(). 225 gluPwlCurve(). 460 gluQuadricDrawStyle(). 461 glutBitmapCharacter(). 71; 120 glutBitmapWidth(). 121 glutCreateWindow(). 85: 127 glutDisplayFunc(). 88: 126 glutldleFunc(), 136 glutlnit(). 85glutlnitDisplayMode(). 85; 96: 127; 137; 228 glutMotionFunc(). 126 glutMouscFunc(). 122 glutPostDisplayO, 126 glutPostRedisplayQ. 136 glutSetColor(). 79 glutSetWindow(). 127 glutStrokeCharacter(), 120 glutSwapBufters(). 137 glVertex(), 212 gIVertex*, 59 glVertexPointer(). 175 glVie\vport(). 86 gMultMatrixf(). 198 ввода информации. 65 визуализации. 64 геометрические преобразования. 64 задания атрибутов. 64 построения примитива. 64Функция с обратным вызовом, 112 движение мыши. 126 отображение. 126 перерисовка, 124 простой. 127XХарактеристический код области. 294 Хроматические координаты. 328
glTranslatef(). 119: 195 gluBeginNurbsCurve(), 460 gluBeginNurbsSurface(). 460 gluBuild2DMipmaps(). 386 gluCylinder(), 461 gluEndNurbsCurve(). 460 gluEndNurbsSurface(), 460
gluLookAt(). 219; 228: 229 gluNe\\NurbsRenderer(), 460 gluNe\vQuadric(). 461 gluNe\vTess(). 319 gluNurbsCurve(). 460 gluNurbsProperty(). 460 gluNurbsSurface(), 460 gluOrtho2D(). 82 gluPerspective(). 225 gluPwlCurve(). 460 gluQuadricDrawStyle(). 461 glutBitmapCharacter(). 71; 120 glutBitmapWidth(). 121 glutCreateWindow(). 85: 127 glutDisplayFunc(). 88: 126 glutldleFunc(), 136 glutlnit(). 85
glutlnitDisplayMode(). 85; 96: 127; 137; 228 glutMotionFunc(). 126 glutMouscFunc(). 122 glutPostDisplayO, 126 glutPostRedisplayQ. 136 glutSetColor(). 79 glutSetWindow(). 127 glutStrokeCharacter(), 120 glutSwapBufters(). 137 glVertex(), 212 gIVertex*, 59 glVertexPointer(). 175 glVie\vport(). 86 gMultMatrixf(). 198 ввода информации. 65 визуализации. 64 геометрические преобразования. 64 задания атрибутов. 64 построения примитива. 64
Функция с обратным вызовом, 112 движение мыши. 126 отображение. 126 перерисовка, 124 простой. 127
X
Характеристический код области. 294 Хроматические координаты. 328
glTranslatef(). 119: 195 gluBeginNurbsCurve(), 460 gluBeginNurbsSurface(). 460 gluBuild2DMipmaps(). 386 gluCylinder(), 461 gluEndNurbsCurve(). 460 gluEndNurbsSurface(), 460
gluLookAt(). 219; 228: 229 gluNe\\NurbsRenderer(), 460 gluNe\vQuadric(). 461 gluNe\vTess(). 319 gluNurbsCurve(). 460 gluNurbsProperty(). 460 gluNurbsSurface(), 460 gluOrtho2D(). 82 gluPerspective(). 225 gluPwlCurve(). 460 gluQuadricDrawStyle(). 461 glutBitmapCharacter(). 71; 120 glutBitmapWidth(). 121 glutCreateWindow(). 85: 127 glutDisplayFunc(). 88: 126 glutldleFunc(), 136 glutlnit(). 85
glutlnitDisplayMode(). 85; 96: 127; 137; 228 glutMotionFunc(). 126 glutMouscFunc(). 122 glutPostDisplayO, 126 glutPostRedisplayQ. 136 glutSetColor(). 79 glutSetWindow(). 127 glutStrokeCharacter(), 120 glutSwapBufters(). 137 glVertex(), 212 gIVertex*, 59 glVertexPointer(). 175 glVie\vport(). 86 gMultMatrixf(). 198 ввода информации. 65 визуализации. 64 геометрические преобразования. 64 задания атрибутов. 64 построения примитива. 64
Функция с обратным вызовом, 112 движение мыши. 126 отображение. 126 перерисовка, 124 простой. 127
X
Характеристический код области. 294 Хроматические координаты. 328
ц
Цвет. 73-80
RGB. 30: 77
RGBA. 78
гамма. 76
дополняющие цвета (CMY), 76
индексируемый, 78; 516
палитра. 78
первичный. 39
смешивание. 401
таблица соотвегствия. 79: 398; 516
цветовой куб. 76
четырехкомпонентная система, 78
ЦДА (цифровой дифференциальный анализатор). 314
Центр проецирования, 42
ч
Чайник Юта. 452: 458
Черепашья графика. 99: 480
Численное интегрирование
Рунге-Кутта метод, 475
Эйлера метод. 473
ш
Шрифт. 70
растровый. 71: 396
штриховой. 70: 396
э
Эйлера углы. 201
Экземпляр. 189: 338
ЭЛТ. 31
теневая маска, 32
триада. 32
Я
Язык
C++. 355
HTML. 370
JAVA. 372
JAVA-3D, 372: 373
Renderman Shading Language, 373
VRML. 371
Яркость. 39
Научно-популярное изданиеЭдвард Эйнджел
Интерактивная компьютерная графика.Вводный курс на базе OpenGL,2 изд.
Литературный редактор И.А. Попова
Верстка М.А. Удалов
Художественный редактор С. А. Чернокозинский
Технический редактор Г.Н. Горобец
Корректоры Л.А. Гордиенко, Т.А. Корзун,
Л. В. Коровкина, О. В. Мишутина
Издательский дом “Вильямс”.101509, Москва, ул. Лесная, д. 43, стр. 1.Изд. лиц. ЛР № 090230 от 23.06.99Госкомитета РФ по печати.
Подписано в печать 09.10.2001. Формат 70x100/16.Гарнитура Times. Печать офсетная.Усл. печ. л. 47,73. Уч.-изд. л. 39,93.
Тираж 5000 экз. Заказ № 1875.
Отпечатано с диапозитивов в ФГУП “Печатный двор”Министерства РФ по делам печати,телерадиовещания и средств массовых коммуникаций.197110, Санкт-Петербург, Чкаловский пр., 15.
Ил. 1 Трехмерный узор (бернинского, рассчитанный но точкам. (С любезного разрешения университета Нью Мексико.)
Ил. 2 Трехмерный узор (бернинского, рассчитанный методом рекурсивного разбиения. (С любезного разрешения университета Нью-Мексико.)
Ил. 3 Хронометрический вид здания. (С любезного разрешения архитектора Ричарда Нордхауза (Richard Nodrhaus), Альбукерк, шт. Нью-Мексико.)
Ил. 4 Перспективный вид интерьера здания. (С любезного разрешения архитектора Ричарда Нордхауза (Richard Nodrhaus), Альбукерк, шт. Нью-Мексико.)
Ил. 5 Тень, отбрасываемая кубом на горизонтальную плоскость. (С любезного разрешения университета Нью- Мексико; авторы Филип Экенрот (Philip Eckenroth), Майкл Тайнинг (Michael Tipping), Мерили Падилла (Marilee Padilla), Джой Мадригал (Joey Madrigal).)
Ил. 6 Цветовой куб в системе RGB. (С любезного разрешения университета Нью-Мексико.)
Ил. 7 Модель распределения температуры воды в океане во время урагана “Эль-Ниньо”.
При построении изображения карты использовано наложение текстуры на поверхность материков (С любезного разрешшi ия Нацио! 1ал ьной лаборатории в Лос-Аламосе; автор Аллен Мак Ферсон (Allen McPherson).)
Ил. 8 Пространственное изображение рельефа о. Гонолулу в группе Гавайских островов, полученное с помощью поверхности в форме Безье.
(С любезного разрешения университета Ныо-Мексико и Национальной лаборатории Сандиа; автор Брайан Уайли (Brian Wylie).)
Ил. 9 Модель динамики жидкой мл мы в мантии Земли.
В изображении для окрашивания изотермальных поверхнос тей использованы псевдоцвета.
(С любезного разрешения Национальной лаборатории в Лос-Аламосе; автор Джемс Пейнтер (James Painter).)
Ил. 10 Результат тонирования по методу трассировки лучей.
(С любезного разрешения Национальной лаборатории в Лос- Аламосе и университета Нью-Мексико; автор Патрик Мак Кормик (Patrick McCormick.)
Ил. 11 Изображения чайников Юта, в которых использованы материалы с разными свойствами.
(С любезного разрешения Silicon Graphics, Inc.)
Ил. 12 Изоповерхности, сформированные методом маркированного куба на основании набора данных о строении омара (120x 120x34 вокселей). (С любезного разрешения университета Нью-Мексико и Национальной лаборатории Сандиа; ав тор Патриция Кроссно (Patricia Crossno).)
Ил. 13 Изоповерхность, сформирование с помощью модели системы 40000 материальных частиц на основании набора данных о строении омара.
(С любезного разрешения университета Ныо- Мсксико и Национальной лаборатории Сандиа; автор Патриция Кроссно (Patricia Crossno).)
Ил. 14 Кадр из игровой программы Geri's Game, в котором на стеклышках очков персонажа видно отражение окружающего пейзажа и преломление световых лучей. (С любезного разрешения Pixar Animaiion Studios.)
Ил. 15 Карта отражения среды, которая была использована для наложения при формировании изображения стеклышек очков персонажа игровой программы Geri's Game. (С любезного разрешения Pixar Animation Studios.)
в) моделирование воздействия ветра па сетку частицг) поверхность, “сдутая" ветром с автомобиля
Ил. 16 Моделирование системы частиц (С любезного разрешения Lexus and Rhythm and Hues.)
Изибра - ( пня пр. и iан к-нпыс н оближм , взяты из компьютерного фильма "Маска Kj^noi! смерти", который создали студен ты yuHBcpcmeia Нью Мексико Макс Ха- -триг (Max Hazelngg) Крипофер Джор |.ш (Christopher Jordm), Uh зад Морани (Shu ad Morani). Джим llp-й т (Jim Pi- (I) X ui ( манер (Hal Smyer) и Хью Уокер (Нш Walker) < щ hi i были смоле in рованы с помощью пак га Мауа Д1Я io- ниро ани ш по и. овален нак т Bine Moon Rendering Tool* I la lai а. пока занна i на первой и пос к шей страницах обложки, шшь на и I с« ми комн I г, в которых разво- рачив !■ гс ц и т и фи тьмаИзибра - ( пня пр. и iан к-нпыс н оближм , взяты из компьютерного фильма "Маска Kj^noi! смерти", который создали студен ты yuHBcpcmeia Нью Мексико Макс Ха- -триг (Max Hazelngg) Крипофер Джор |.ш (Christopher Jordm), Uh зад Морани (Shu ad Morani). Джим llp-й т (Jim Pi- (I) X ui ( манер (Hal Smyer) и Хью Уокер (Нш Walker) < щ hi i были смоле in рованы с помощью пак га Мауа Д1Я io- ниро ани ш по и. овален нак т Bine Moon Rendering Tool* I la lai а. пока занна i на первой и пос к шей страницах обложки, шшь на и I с« ми комн I г, в которых разво- рачив !■ гс ц и т и фи тьма1ЬЯМС”1ЬЯМС”| ПОДДЕРЖКА| ПОДДЕРЖКАWEBWEB
Изибра - ( пня пр. и iан к-нпыс н оближм , взяты из компьютерного фильма "Маска Kj^noi! смерти", который создали студен ты yuHBcpcmeia Нью Мексико Макс Ха- -триг (Max Hazelngg) Крипофер Джор |.ш (Christopher Jordm), Uh зад Морани (Shu ad Morani). Джим llp-й т (Jim Pi- (I) X ui ( манер (Hal Smyer) и Хью Уокер (Нш Walker) < щ hi i были смоле in рованы с помощью пак га Мауа Д1Я io- ниро ани ш по и. овален нак т Bine Moon Rendering Tool* I la lai а. пока занна i на первой и пос к шей страницах обложки, шшь на и I с« ми комн I г, в которых разво- рачив !■ гс ц и т и фи тьма
Изибра - ( пня пр. и iан к-нпыс н оближм , взяты из компьютерного фильма "Маска Kj^noi! смерти", который создали студен ты yuHBcpcmeia Нью Мексико Макс Ха- -триг (Max Hazelngg) Крипофер Джор |.ш (Christopher Jordm), Uh зад Морани (Shu ad Morani). Джим llp-й т (Jim Pi- (I) X ui ( манер (Hal Smyer) и Хью Уокер (Нш Walker) < щ hi i были смоле in рованы с помощью пак га Мауа Д1Я io- ниро ани ш по и. овален нак т Bine Moon Rendering Tool* I la lai а. пока занна i на первой и пос к шей страницах обложки, шшь на и I с« ми комн I г, в которых разво- рачив !■ гс ц и т и фи тьма
1ЬЯМС”
1ЬЯМС”
| ПОДДЕРЖКА
| ПОДДЕРЖКА
WEB
WEB
Интерактивнаяграфика Вводный курс на базе OpenGL
Данная книга написана с учетом того, что студенты значительно быстрее усваивают основы компьютерной графики, если сразу же включаются в разработку про1рамм. Такой подход с самых первых страниц книги вовлекает читателя в процесс разработки графических программ. В каждой главе представлены нетривиальные приложения, на практике демонстрирующие применение рассмотренных методов. Книга включает все основные разделы компьютерной графики — архитектуру графических систем, геометрические преобразования, взаимодействие света и материала, тонирование изображения, графическое моделирование, построение кривых и криволинейных поверхностей, наложение текстур, особенности современных аппаратных средств гра-
фических систем.
Весь практический материал, приведенный в книге, базируется на графической системе OpenGL, с которой читатель познакомится уже в первых главах. Эта система доступна тем, кто работает в операционной среде Microsoft Windows (как 98. так и NT), а пользователей компьютеров Apple фирма-изготовитель известила о том, что OpenGL будет включена в качестве базового компонента в будущие версии операционной системы. Пользовательский интерфейс OpenGL и знание основных методик программирования на языке С позволят читателям создавать собственные достаточно сложные графические приложения, использующие технологию визуализации трехмерных объектов и обработку событий для управления ходом выполнения программы.
Во второе издание книги внесены значительные изменения и дополнения. В частности, существенно дополнен материал, касающийся построения иерархических моделей, использования в компьютерной графике объектно- ориентированного подхода, новых методов визуализации и взаимодействия пользователя с программой. В книге освещены новые возможности, которые предоставляет пользователям версия системы OpenGL 1.1, в том числе и работа с массивами вершин.
Дополнительны!! материал к этой книге, включая тексты программ и ссылки на ресурсы, касающиеся OpenGL, читатель найдет на Web-узле по адресу http://www.awl.com/ cseng/angel.
Эдвард Эйнджел — профессор по специальностям "Информатика" и "Электротехника и вычислительная техника" университета Нью-Мексико, в котором он также является председателем совета отделения и консультирует аспирантов. В университете Нью-Мексико он был первым, кого удостоили Президентского отличия воспитателю юношества (Presidential Teaching Fellow). Эйнджел получил степень бакалавра в Калифорнийском технологическом институте а степень доктора философии — в университете Южной Ка лифорнии.
11 Такое предположение не является необходимым условием для вывода алгоритма. Если для представления конечных точек используется формат с фиксированной точкой и если в этом же формате выполняются все арифметические операции, то алгоритм будет иметь точно такой же вид, но точность преобразования повысится.
2При изложении этого материала мы не будем пользоваться аппаратом однородных координат. Об использовании этого аппарата мы поговорим в разделе 10.8, когда будем обсуждать кривые в форме рациональных В-сплайнов на неравномерной сетке (NURBS-сплайнов).
тей их последователей упоминается 15 уникальных вариантов, но в приведенном ими наборе два варианта
все-таки симметричны.
Interactive Computer
GraphicsGraphicsSECOND EDITIONSECOND EDITION
Graphics
Graphics
SECOND EDITION
SECOND EDITION
A top-down approach with OpenGL
Edward Angel
ADDISON-WESLEY
Reading, Massachusetts • Menlo Park, California • New York • Harlow, England Don Miles, Ontario • Sydney • Mexico City • Madrid • Amsterdam
Второе издание Интерактивнаякомпьютерная графика
Вводный курс на базе
ОрепОГ
Эдвард Эйнджел
Издательский дом “Вильямс" Москва ♦ Санкт-Петербург ♦ Киев 2001
ББК 32.973.26-018.2.75
Э64
УДК 681.3.07
Издательский дом “Вильямс”
Перевод с английского и редакция канд.техн.наук В.Т. Тертыишого
По общим вопросам обращайтесь в Издательский дом "Вильямс"по адресу: info@vvilliamspublishing.com. http://vvwvv.vvilliamspublishing.com
Эйнджел, Эдвард.
Э64 Интерактивная компьютерная графика. Вводный курс на базе OpenGL, 2 изд.: Пер. с англ. — М.: Издательский дом “Вильямс”, 2001. — 592 с.: ил. — Парал. тит. англ.
ISBN 5-8459-0209-6 (рус.)
Книга представляет собой вводный курс компьютерной графики, в котором основной упор сделан на вопросах прикладного программирования. Она включает описание структуры графических систем и обсуждение основных концепций формирования изображений трехмерных объектов и сцен. Рассматривается взаимодействие освещения и материалов, приводятся основные сведения о методах тонирования освещенных поверхностей, принципах иерархической организации графических моделей и новых возможностях современных аппаратных графических средств. В книгу включены те разделы линейной алгебры и геометрии, которые необходимы для понимания основ компьютерной графики. Обсуждаются методы построения кривых и поверхностей, языковые модели, фракталы и системы частиц, а также методика применения графических средств для визуализации результатов научных расчетов. Весь теоретический материал в книге иллюстрируется программами на OpenGL.
Книга адресована в основном студентам старших курсов и аспирантам первого года обучения, специализирующимся в области информатики и вычислительной техники, но будет также полезна и многим профессионалам.
ББК 32.973.26-018.2.75
Все названия программных продуктов являются зарегистрированными торговыми марками соответствующих фирм.
Никакая часть настоящего издания ни в каких целях не может быть воспроизведена в какой бы то ни было форме и какими бы то ни было средствами, будь то электронные или механические, включая фотокопирование и запись на магнитный носитель, если на это нет письменного разрешения издательства Addison-Wesley Publishing Company, Inc.
Authorized translation from the English language edition published by Addison-Wesley Publishing Company. Inc, Copyright © 2000
All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system. without permission from the Publisher.
Russian language edition published by Williams Publishing House according to the Agreement with R&I Enterprises International, Copyright © 2001
ISBN 5-8459-0209-6 (рус.)© Издательский дом "Вильямс". 2001
ISBN 0-201-38597-Х (англ.)© Addison-Wesley Publishing Company. Inc. 2000
Оглавление
Предисловие18