Text
                    ВТОРОЕ ИЗДАНИЕ
М
\
ная компьютеона
ВВОДНЫЙ КУРС НА БА3
OpenGL
Эдвард Эйнджел


SECOND EDITION Interactive Computer Graphics A top-down approach with OpenGL™ Edward Angel ADDISON-WESLEY Reading, Massachusetts • Menlo Park, California • Л/ew York • Harlow, England Don Miles, Ontario • Sydney • Mexico City • Madrid • Amsterdam
Второе издание Интерактивная компьютерная графика Вводный курс на базе OpenGL™ Эдвард Эйнджел Издательский дом "Вильяме" Москва ¦ Санкт-Петербург ¦ Киев 2001
ББК 32.973.26-018.2.75 Э64 УДК 681.3.07 Издательский дом "Вильяме" Перевод с английского и редакция канд.техн.наук ВТ. Тертыишого По общим вопросам обращайтесь в Издательский дом "Вильяме" по адресу: info@vvilliamspublishing.com, http://\vw\v.williamspublishing.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 Глава 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 Программирование на языке С с применением OpenGL 19 На кого рассчитана эта книга 20 Структура книги 20 Изменения, внесенные во второе издание 21 Дополнительные источники 23 Благодарности 23 Глава 1 Графические системы и модели 25 1.1. Области применения компьютерной графики 26 1.1.1. Отображение информации 26 1.1.2. Проектирование 27 1.1.3. Моделирование 28 1.1.4. Интерфейс пользователя 28 1.2. Графическая система 29 1.2.1. Пиксели и буфер кадра 29 1.2.2. Устройства вывода изображений 31 1.2.3. Устройства ввода 33 1.3. Изображение: физическое и синтезируемое 33 1.3.1. Объекты и наблюдатели 3 3 1.3.2. Свет и изображение 35 1.3.3. Трассировка лучей 36 1.4. Глаз человека 38 1.5. Камера-обскура 40 1.6. Моделирование камеры 41 1.7. Интерфейс программиста 43 1.7.1. Интерфейс прикладного профаммирования 44 1.7.2. Парадигма "моделирование — тонирование" 47 1.8. Архитектура графических систем 47 1.8.1. Дисплейные процессоры 48 1.8.2. Конвейерная архитектура 49 1.8.3. Геометрические преобразования 50 б Содержание
1.8.4. Отсечение 50 1.8.5. Проективное преобразование 51 1.8.6. Растровое преобразование 51 1.8.7. Производительность работы геометрического конвейера 51 1.9. Резюме 52 1.10. Рекомендуемая литература 52 Упражнения 53 Глава 2 Графическое программирование 55 2.1. Узор Серпинского 56 2.1.1. Перьевой плоттер 57 2.1.2. Системы координат 62 2.2. Прикладной интерфейс OpenGL 63 2.2.1. Графические функции 64 2.2.2. Интерфейс OpenGL 65 2.3. Примитивы и атрибуты 66 2.3.1. Многоугольники 67 2.3.2. Типы многоугольников в OpenGL 69 2.3.3. Текст 70 2.3.4. Криволинейные объекты 72 2.3.5. Атрибуты 72 2.4. Цвет 73 2.4.1. Цветовая система RGB 77 2.4.2. Индексируемый цвет 78 2.4.3. Настройка атрибута цвета 80 2.5. Визуализация 80 2.5.1. Визуализация двухмерных объектов 81 2.5.2. Ортогональная проекция 82 2.5.3. Матричный режим проецирования 83 2.6. Функции управления 84 2.6.1. Взаимодействие с подсистемой окон 84 2.6.2. Соотношение сторон и видовые окна 85 2.6.3. Функции main(), display() и myinit() 87 2.6.4. Структура программы 88 2.7. Программа Gasket 89 2.8. Многоугольники и рекурсия 90 2.9. Трехмерный узор Серпинского 92 2.9.1. Использование трехмерных точек 92 2.9.2. Использование многоугольников в трехмерном пространстве 94 2.9.3. Удаление невидимых поверхностей 95 2.10. Резюме 96 2.11. Рекомендуемая литература 97 Упражнения 98 Содержание 7
Глава 3 Ввод и взаимодействие с пользователем 103 3.1. Интерактивная компьютерная графика 103 3.2. Устройства ввода 105 3.2.1. Физические устройства ввода 105 3.2.2. Логические устройства 108 3.2.3. Показания и синхронизация 109 3.2.4. Режимы ввода 110 3.3. Клиенты и серверы 112 3.4. Дисплейный файл 113 3.4.1. Формирование дисплейного списка и преобразование его в изображение 115 3.4.2. Дисплейные списки и формирование текста 117 3.4.3. Шрифты библиотеки GLUT 120 3.5. Программирование ввода, управляемого событиями 121 3.5.1. Использование устройств указания 121 3.5.2. События окна 124 3.5.3. События клавиатуры 126 3.5.4. Функции отображения и простоя 126 3.5.5. Управление окнами 127 3.6. Меню 127 3.7. Указание объектов 129 3.8. Простая программа рисования 130 3.9. Интерактивные программы анимации 135 3.9.1. Вращающийся квадрат 135 3.9.2. Двойная буферизация 137 3.9.3. Проблемы с буферизацией 138 3.10. Разработка интерактивных графических программ 139 3.10.1. Инструментальные средства, экранные элементы управления и буфер кадра 139 3.11. Резюме 141 3.12. Рекомендуемая литература 142 Упражнения 142 Глава 4 Объекты и геометрические преобразования 147 4.1. Скаляры, точки и векторы 148 4.1.1. Геометрическое определение базовых типов 148 4.1.2. Математическое определение: векторное и аффинное пространства 150 4.1.3. Информационное определение 150 4.1.4. Геометрические абстрактные типы данных 151 4.1.5. Прямые 152 4.1.6. Аффинное сложение 153 8 Содержание
4.1.7. Выпуклость 153 4.1.8. Скалярное и векторное произведение 154 4.1.9. Плоскости 155 4.2. Трехмерные примитивы 156 4.3. Системы координат и фреймы 157 4.3.1. Замена систем координат 159 4.3.2. Пример изменения представления 161 4.3.3. Однородные координаты 162 4.3.4. Пример перехода из одного фрейма в другой 164 4.3.5. Фреймы и абстрактные типы данных 166 4.3.6. Фреймы в OpenGL 167 4.4. Модель разноцветного куба 169 4.4.1. Моделирование куба 170 4.4.2. Внешние и внутренние грани 170 4.4.3. Структура данных для представления объектов 171 4.4.4. Цвет куба 172 4.4.5. Билинейная интерполяция 173 4.4.6. Массивы вершин 174 4.5. Аффинные преобразования 176 4.6. Поворот, сдвиг и масштабирование 178 4.6.1. Плоскопараллельное смещение 179 4.6.2. Поворот 179 4.6.3. Масштабирование 181 4.7. Преобразования в однородных координатах 182 4.7.1. Сдвиг 182 4.7.2. Масштабирование 183 4.7.3. Поворот 184 4.7.4. Скос 185 4.8. Суперпозиция преобразований 186 4.8.1. Поворот вокруг произвольной фиксированной точки 187 4.8.2. Поворот вокруг произвольной оси 188 4.8.3. Преобразование экземпляра 189 4.8.4. Поворот вокруг произвольной оси 190 4.9. Матрицы преобразований в OpenGL 193 4.9.1. Текущая матрица преобразования 194 4.9.2. Поворот, сдвиг и масштабирование 195 4.9.3. Поворот вокруг фиксированной точки средствами OpenGL 195 4.9.4. Последовательность выполнения преобразований 196 4.9.5. Вращение куба 196 4.9.6. Загрузка матриц и использование стека матриц 198 4.10. Взаимодействие пользователя с трехмерными графическими приложениями 198 4.10.1. Использование областей экрана 199 4.10.2. Виртуальный трекбол 199 4.10.3. Плавное вращение 201 Содержание
4.11. Резюме 202 4.12. Рекомендуемая литература 203 Упражнения 203 Глава 5 Визуализация 205 5.1. Классическая и компьютерная визуализация 205 5.1.1. Визуализация в классической графике 207 5.1.2. Ортографические проекции 208 5.1.3. Аксонометрические проекции 208 5.1.4. Косоугольные проекции 209 5.1.5. Визуализация с учетом перспективы 210 5.2. Размещение камеры 211 5.2.1. Настройка положения фрейма камеры 212 5.2.2. Задание ориентации камеры 216 5.3. Проецирование 220 5.3.1. Перспективные проекции 221 5.3.2. Ортогональная проекция 223 5.4. Проективные преобразования в OpenGL 224 5.4.1. Перспективные преобразования в OpenGL 224 5.4.2. Параллельное проецирование в OpenGL 226 5.5. Удаление невидимых поверхностей 227 5.6. Путешествие с камерой по сцене 228 5.7. Матрицы параллельного проецирования 230 5.7.1. Нормализация проецирования 230 5.7.2. Матрицы ортогонального проективного преобразования 231 5.7.3. Косоугольная проекция 233 5.8. Матрицы перспективного проецирования 236 5.8.1. Перспективная нормализация 236 5.8.2. Перспективное преобразование в OpenGL 238 5.9. Проецирование и формирование теней 240 5.10. Резюме 242 5.11. Рекомендуемая литература 243 Упражнения 244 Глава 6 Закрашивание 247 6.1. Свет и материя 248 6.2. Источники света 250 6.2.1. Цвет излучения 251 6.2.2. Фоновое освещение 252 6.2.3. Точечный источник света 252 6.2.4. Прожекторы 253 10 Содержание
6.2.5. Удаленный источник света 254 6.3. Модель отражения Фонга 255 6.3.1. Отражение фонового света 257 6.3.2. Диффузное отражение 257 6.3.3. Зеркальное отражение 258 6.4. Вычисление векторов 261 6.4.1. Нормаль к поверхности 261 6.4.2. Угол отражения 263 6.4.3. Вектор половинного направления 264 6.4.4. Преломление света 265 6.5. Закрашивание многоугольников 266 6.5.1. Плоское закрашивание 266 6.5.2. Интерполяционное закрашивание и закрашивание по методу Гуро 268 6.5.3. Закрашивание по методу Фонга 269 6.6. Аппроксимация сферической поверхности рекурсивным разбиением 270 6.7. Описание источников света в OpenGL 273 6.8. Спецификация материалов в OpenGL 275 6.9. Закрашивание модели сферы 276 6.10. Глобальное тонирование 278 6.10.1. Трассировка лучей 279 6.10.2. Метод анализа излучательности 282 6.11. Резюме 283 6.12. Рекомендуемая литература 284 Упражнения 284 Глава 7 Алгоритмы формирования изображения 287 7.1. Четыре основные задачи 288 7.1.1. Моделирование 288 7.1.2. Геометрическая обработка 289 7.1.3. Растровое преобразование 289 7.1.4. Отображение 290 7.1.5. Базовые стратегии реализации 290 7.2. Реализация геометрических преобразований 292 7.3. Отсечение отрезков 294 7.3.1. Алгоритм Коэна-Сазерленда 294 7.3.2. Алгоритм Лианга-Барского 296 7.4. Отсечение многоугольников 298 7.5. Отсечение примитивов других типов 301 7.5.1. Прямоугольные оболочки 301 7.5.2. Кривые, поверхности и надписи 302 7.5.3. Отсечение в буфере кадра 302 7.6. Отсечение в трехмерном пространстве 303 Содержание 11
7.7. Удаление невидимых поверхностей 305 7.7.1. Удаление нелицевых граней 307 7.7.2. Алгоритм z-буфера 308 7.7.3. Сортировка по глубине 310 7.7.4. Алгоритм построчного сканирования 312 7.8. Растровое преобразование 313 7.9. Алгоритм Брезенхэма 315 7.10. Растровое преобразование многоугольников 317 7.10.1. Тест принадлежности внутренней области 318 7.10.2. Обработка многоугольников общего вида в OpenGL 319 7.10.3. Растровое преобразование с использованием z-буфера 320 7.10.4. Заполнение внутренней области и сортировка 321 7.10.5. Заливка 322 7.10.6. Алгоритмы заполнения построчным сканированием 322 7.10.7. Особые случаи 324 7.11. Сглаживание ступенек на изображении линий 324 7.12. Отображение информации 326 7.12.1. Цветовые системы 327 7.12.2. Гамма-коррекция 330 7.12.3. Формирование полутонов 330 7.13. Резюме 331 7.14. Рекомендуемая литература 332 Упражнения 333 Глава 8 Иерархические графические модели 337 8.1. Символы и экземпляры 338 8.2. Иерархические модели 339 8.3. Модель руки робота 341 8.4. Обход деревьев 344 8.4.1. Алгоритм обхода с использованием стека 345 8.5. Обход древовидных структур 348 8.6. Анимация 352 8.7. Графические объекты 353 8.7.1. Методы, атрибуты и сообщения 354 8.7.2. Объект cube 355 8.7.3. Объекты и иерархия 358 8.7.4. Геометрические объекты 358 8.8. Граф сцены 359 8.9. Другие типы древовидных структур 361 8.9.1. Деревья в конструктивной геометрии тел 361 8.9.2. Бинарные деревья разделения пространства 364 8.9.3. 4-арное и 8-арное деревья 366 12 Содержание
8.10. Графикой Web 368 8.10.1. Сети и протоколы 368 8.10.2. Гипермедиа и HTML 370 8.10.3. Базы данных и VRML 371 8.10.4. Java и аплеты 372 8.11. Резюме 372 8.12. Рекомендуемая литература 373 Упражнения 373 Глава 9 Операции с изображением на уровне растрового представления 375 9.1. Буферы и наложение 376 9.2. Наложение проективных текстур 377 9.2.1. Наложение двухмерных проективных текстур 378 9.2.2. Проективное наложение текстуры в системе OpenGL 383 9.2.3. Генерация образцов текстур 387 9.3. Наложение изображения окружающих предметов 388 9.4. Наложение микрорельефа 390 9.5. Запись в буферы 391 9.5.1. Режимы записи битовых блоков 392 9.5.2. Режим записи XOR 393 9.6. Операции с пикселями в OpenGL 394 9.6.1. Буферы OpenGL 395 9.6.2. Использование растровых образов символов шрифта 396 9.6.3. Пиксели и изображения 397 9.6.4. Таблицы соответствия цветов 398 9.6.5. Использование буферов в процедуре указания объекта 400 9.7. Технология комбинирования изображений 400 9.7.1. Поглощение света и смешивание изображений 400 9.7.2. Смешивание изображений 402 9.7.3. Смешивание изображений в OpenGL 402 9.7.4. Сглаживание погрешностей дискретизации 403 9.7.5. Тонирование сцен со множеством полупрозрачных объектов 405 9.7.6. Эффект тумана и создание иллюзии глубины пространства 406 9.8. Использование буфера-накопителя 406 9.9. Дискретизация изображения 409 9.9.1. Теория квантования по независимым переменным 410 9.9.2. Восстановление непрерывной функции по дискретным выборкам 413 9.9.3. Квантование по уровню 415 9.10. Резюме 416 9.11. Рекомендуемая литература 417 Упражнения 417 Содержание 13
Глава 10 Кривые и криволинейные поверхности 419 10.1. Представление кривых линий и поверхностей 419 10.1.1. Представление в явной форме 420 10.1.2. Неявная форма представления 421 10.1.3. Параметрическая форма представления 422 10.1.4. Параметрические полиномиальные кривые 423 10.1.5. Параметрические полиномиальные поверхности 423 10.2. Общая характеристика полиномиальной параметрической формы представления 424 10.3. Параметрически заданные кубические кривые 426 10.4. Интерполяция 427 10.4.1. Функции смешивания 429 10.4.2. Порция кубической интерполяционной поверхности 430 10.5. Эрмитова форма представления кривых и поверхноаей 432 10.5.1. Форма Эрмита 432 10.5.2. Геометрическая и параметрическая непрерывность 434 10.6. Кривые и поверхности в форме Безье 435 10.6.1. Кривые Безье 435 10.6.2. Порции поверхности в форме Безье 437 10.7. Кубические В-сплойны 438 10.7.1. Кубические В-сплайны 438 10.7.2. В-сплайны и базисные функции 441 10.7.3. Сплайновые поверхности 442 10.8. Обобщенные В-сплайны 442 10.8.1. Рекурсивно определенные В-сплайны 443 10.8.2. Равномерные В-сплайны 444 10.8.3. Неравномерные В-сплайны 445 10.8.4. NURBS — неравномерный рациональный В-сплайн 445 10.9. Построение кривых и поверхностей 446 10.9.1. Методы вычисления полиномов 447 10.9.2. Рекурсивное разбиение кривых Безье 448 10.9.3. Построение других типов полиномиальных кривых методом разбиения 450 10.9.4. Разбиение поверхности Безье 451 10.10. Пример: формирование изображения чайника 452 10.11. Алгебраические поверхности 454 10.11.1. Квадратичные поверхности 454 10.11.2. Вычисление точек на квадратичной поверхности методом приведения лучей 455 10.12. Кривые и поверхности в OpenGL 456 10.12.1. Кривые Безье 456 10.12.2. Поверхности Безье 457 10.12.3. Отображение чайника Юта 458 14 Содержание
10.12.4. Функции отображения NURBS-кривых и поверхностей 460 10.12.5. Квадратичные поверхности 460 10.13. Резюме 461 10.14. Рекомендуемая литература 462 Упражнения 462 Глава 11 Процедурные методы 465 11.1. Особенности процедурных моделей 465 11.2. Физические модели и система частиц 467 11.3. Ньютоновы частицы 468 11.3.1. Несвязанные частицы 470 11.3.2. Силы упругости 470 11.3.3. Силы взаимного притяжения и отталкивания 472 11.4. Решение системы уравнений 473 11.5. Ограничения 475 11.5.1. Столкновения 475 11.5.2. Частицы внутри сферической оболочки 477 11.5.3. Мягкие ограничения 479 11.6. Языковые модели 479 11.7. Рекурсивные методы и фракталы 483 11.7.1. Масштаб и длина 483 11.7.2. Размерность фрактала 484 11.7.3. Разбиение в средней точке и броуновское движение . 486 11.7.4. Формирование изображения горы с помощью фракталов 487 11.8. Множество Мандельброта 488 11.9. Резюме 491 11.10. Рекомендуемая литература 491 Упражнения 491 Глава 12 Визуализация данных научных исследований 493 12.1. Данные+геометрия 493 12.2. Поля превышений и линии уровня 494 12.2.1. Сети 495 12.2.2. Вычерчивание линий уровня 498 12.2.3. Метод маркированных квадратов 498 12.3. Визуализация поверхностей и скалярных полей 505 12.3.1. Объемное множество данных 505 12.3.2. Визуализация функций, заданных в неявной форме 506 12.4. Изоповерхности и метод маркированных кубиков 508 12.5. Непосредственное отображение объема 510 12.5.1. Управление цветом и коэффициентом прозрачности 511 Содержание 15
12.5.2. Отображение скалярного поля с помощью отпечатков 12.5.3. Трассировка лучей в скалярном поле 12.5.4. Наложение текстуры на объем 12.6. Визуализация векторных полей 12.6.1. Отрезки переменной длины 12.6.2. Бусинки 12.6.3. Цвет 12.6.4. Треки частиц и линии потока 12.7. Визуализация тензорных полей 12.8. Резюме 12.9. Рекомендуемая литература Упражнения Приложение А Демонстрационные программы А.1. Двухмерный узор Серпинского А.2. Рекурсивный алгоритм построения узора Серпинского А.З. Трехмерный узор Серпинского А.4. Рекурсивный алгоритм построения трехмерного узора Серпинского А.5. Программа вычерчивания квадрата А.6. Программа рисования А.7. Программа отображения с двойной буферизацией А.8. Программа отображения вращающегося куба А.9. Вращение куба с использованием массива вершин А. 10. Вращающийся куб, управляемый трекболом А.11. Изменение положения наблюдателя А. 12. Построение сферы Приложение Б Абстрактные пространства в компьютерной графике Б.1. Скаляры Б.2. Векторное пространство Б.З. Аффинное пространство Б.4. Евклидово пространство Б.5. Проекции вектора Б.6. Ортогонализация Грама-Шмидта Б.7. Рекомендуемая литература Упражнения 512 513 514 515 515 516 516 517 519 520 520 521 523 524 526 528 529 532 534 541 544 547 549 554 556 561 561 562 564 566 567 567 568 568 16 Содержание
Приложение В Матрицы 569 8.1. Основные определения 569 8.2. Операции над матрицами 570 8.3. Матрицы-строки и матрицы-столбцы 571 8.4. Ранг матрицы 572 8.5. Изменение представления 573 8.6. Векторное произведение 574 8.7. Рекомендуемая литература 575 Упражнения 575 Литература 577 Содержание 17
Предисловие Книга, которую вы держите в руках, представляет собой вводный курс компьютерной графики, в котором основной акцент сделан на вопросах прикладного программирова- программирования. В первом издании, которое вышло в свет в 1997 году, я отмечал, что за семь лет, прошедших после публикации моей предыдущей книги по компьютерной графике, в этой об- области произошли разительные изменения — она развивалась со скоростью, превзошедшей самые смелые ожидания специалистов, в том числе и мои собственные. За последние три го- года эти темпы не только не уменьшились, а даже возросли. Полнометражные кинофильмы, в производстве которых использована компьютерная анимация, имеют не только громадный зрительский, но и коммерческий успех. Включение в кинофильмы эффектных сцен, сделан- сделанных с помощью компьютера, стало в последние годы не исключением, а нормой, и подчас та- такие сцены неотличимы от снятых на "натуре". Особый интерес к графическим приложениям вызывает их применение в среде Internet. За последние годы не только возросли функциональные возможности средств компью- компьютерной графики, но и значительно снизилась стоимость графических рабочих станций, при- причем это характерно для установок всех классов, как простейших, так и профессиональных. В течение нескольких лет стоимость графической станции, способной формировать около од- одного миллиона трехмерных многоугольников в секунду с учетом эффектов освещения и на- наложения текстуры, понизилась со $100 000 до нескольких тысяч. Значительно более доступ- доступными по цене стали и специализированные графические платы для персональных компьюте- компьютеров. При цене в несколько сотен долларов эти платы обеспечивают почти такие же возможности, как и графические рабочие станции. В области программного обеспечения также произошли серьезные изменения. OpenGL стала своего рода стандартным интерфейсом для программистов как при написании прикладных программ, так и при разработке про- программных продуктов высокого класса— от интерактивных игр до систем визуализации ре- результатов научных исследований. Нисходящий подход Отмеченные достижения еще более упрочили мою веру в преимущество нисходящего (сверху вниз) подхода при изложении вводного курса компьютерной графики. Хотя на фа- факультетах информатики и вычислительной техники многих вузов читают несколько курсов лекций, имеющих отношение к этой дисциплине, большинство студентов предпочитают про- прослушать только какой-либо один из них. Обычно такой курс включается в учебную програм- программу после прослушивания курсов по основам программирования, структурам данных, теории алгоритмов, технологии программирования и базовых математических курсов. Сам предмет компьютерной графики позволяет преподавателю так организовать этот курс, что он будет не только познавательным, но и увлекательным для слушателей. При пла- планировании занятий я стараюсь предоставить студентам возможность как можно раньше при- приступить к программированию задач трехмерной графики, а анализ алгоритмов нижнего уров-
ня, таких как вычерчивание линий и заливка многоугольников, откладываю на более позднее время. Я возвращаюсь к ним тогда, когда студенты уже могут самостоятельно программиро- программировать построение графических изображений. Джон Кемени (John Kemeny), один из пионеров преподавания этой дисциплины в США, использовал для обоснования такой концепции преподавания понятную многим аналогию с вождением автомобиля. Для того чтобы сесть за руль и стронуться с места, совсем не обяза- обязательно знать, что происходит под капотом, но тот, кто не знаком с правилами движения и ос- основными приемами управления автомобилем, обречен сидеть на заднем сиденье, а не на мес- месте водителя. Кемени различает три подхода к обучению. Первый — алгоритмический — обу- обучать сначала тому, как функционирует автомобиль в целом и отдельные его агрегаты: двигатель, трансмиссия, топливная система и т.д. Второй подход — потребительский — на- нанять шофера, устроиться на заднем сиденье и созерцательно глядеть на мир за окном. Третий подход— это подход программиста, который я и отстаиваю в этой книге,— научиться во- водить автомобиль и ориентироваться в обстановке на дороге, пренебрегая детальным знанием о том, что происходит под капотом. Овладевший этими знаниями, всегда сможет добраться до места назначения (если, конечно, автомобиль будет исправен — но это уже другая песня). Те, кто давно делает бизнес на прокате автомобилей, выразили эту мысль простой формулой: "Предоставьте нам усадить вас на место водителя". Программирование на языке С с применением OpenGL В прежние времена наиболее серьезные сложности при чтении курса компьютерной гра- графики и подготовке учебников по этой дисциплине были связаны с отсутствием общепринятой графической библиотеки или интерфейса прикладного программирования (API — Application Programming Interface). Из-за этого приходилось приобретать специализированные средства по довольно высокой цене, которые были недоступны большинству студентов вне пределов вуза, страдали отсутствием общности и были довольно сложны в освоении. Появление OpenGL в значительной мере сняло эти вопросы, в чем на собственном опыте убедились те, кто ранее использовал в практике преподавания как другие пакеты API (такие как GKS и PUIGS), так и "доморощенное" программное обеспечение. На сегодняшний день графическая система OpenGL поддерживается большинством производителей рабочих графических стан- станций, а независимые разработчики создали средства ее поддержки для большинства сущест- существующих аппаратных и программных платформ. Эта система доступна тем, кто работает в операционной среде Microsoft Windows (как 98, так и NT), а пользователей компьютеров Ap- Apple фирма-изготовитель известила о том, что OpenGL будет включена в качестве базового компонента в будущие версии операционной системы. Свободно распространяются исходные программные коды системы Mesa — пакета API на базе OpenGL, — которые можно компи- компилировать в большинстве операционных систем, в том числе и Linux. Конечно, содержание курса компьютерной графики выходит далеко за пределы простого руководства по применению конкретного пакета API, но наличие хорошего базового пакета значительно упрощает как преподавание, так и усвоение студентами ключевых тем этой дис- дисциплины — создание изображений трехмерных объектов, закрашивание, графические систе- системы с архитектурой "клиент/сервер", моделирование и создание прикладных графических сис- систем. Я полагаю, что широкие функциональные возможности OpenGL и тщательно продуман- продуманная структура этой системы послужат хорошим фундаментом для изложения как теоретических, так и практических аспектов этого предмета, в том числе и новейших идей, таких как наложение текстур и их комбинирование (эти функции не поддерживались преж- прежними пакетами API). Я перешел на использование OpenGL в своей преподавательской практике примерно 5 лет назад, и уже первые результаты меня изумили — в середине первого семестра каждый сту- Предисловие 19
дент мог написать довольно сложную программу построения изображения трехмерных объ- объектов, в которой требуется знание не только математики трехмерного моделирования, но и умение работать с событиями. За предыдущие 15 лет преподавания я и близко никогда не подходил к подобным результатам. Опыт преподавания курса на новой базе подвел меня к мысли полностью переделать написанный ранее учебник. Настоящая книга представляет собой учебник по компьютерной графике, а не руково- руководство пользователя по работе с OpenGL. Поэтому я не считал необходимым освещать в ней все тонкости использования этого языка, а описывал только то, что имеет непосредственное отношение к теоретическим и практическим задачам, рассматриваемым в книге. Я старался таким образом использовать OpenGL для иллюстрации тех или иных идей, чтобы те читате- читатели, которые пользуются другим пакетом API, могли без особого труда воспринимать изла- излагаемый материал. Помимо этого я использую в книге язык С. У читателей может возникнуть вполне резон- резонный вопрос: "Почему С, а не C++, Java или любой другой объектно-ориентированный язык программирования?" Мой выбор обоснован двумя соображениями. Во-первых, система OpenGL не относится к объектно-ориентированным системам программирования, а потому использование C++ или Java не внесет ничего особенного в большинство излагаемых тем. Исключение составляет тема структуры библиотеки промежуточных программ между OpenGL и пользователем, но я решил вообще не касаться ее, поскольку это сделает книгу ме- менее доступной для студентов, владеющих навыками программирования, но незнакомых с ме- методикой объектно-ориентированного программирования. На кого рассчитана эта книга В первую очередь я адресую эту книгу студентам старших курсов и аспирантам первого года обучения, специализирующимся в области информатики и вычислительной техники, а также студентам других специальностей, имеющим достаточный опыт программирования. Книга будет полезна и многим профессионалам. Я провел около сотни краткосрочных курсов по компьютерной графике для профессионалов, и опыт преподавания для такой аудитории оказал немалое влияние на отбор материала для этой книги. Необходимым условием для успешного усвоения материала книги является достаточно глубокие знания языка программирования С, знакомство с основными идеями объектно- ориентированного программирования и хотя бы начальные познания в области линейной ал- алгебры и тригонометрии. Я полагаю, что математическая подготовка студентов и аспирантов, специализирующихся в области информатики и вычислительной техники, играет очень важ- важную роль в изучении большинства дисциплин. Поэтому я постарался включить в материал книги те разделы линейной алгебры и геометрии, которые необходимы для понимания основ компьютерной графики. Этот материал я выделил в два отдельных приложения. Структура книги Предлагаемая вашему вниманию книга состоит из 12 глав. В главе 1 представлен обзор методов формирования изображений оптическими приборами, где читатель сразу же позна- познакомится с основными концепциями создания изображений трехмерных объектов. В главе 2 читатель знакомится с методикой программирования с использованием OpenGL. Хотя первая программа, рассматриваемая в этой главе (а в каждой главе рассматривается одна или не- несколько законченных программ), имеет дело с двухмерными объектами, они "встраиваются" в трехмерную среду. В главе 3 обсуждаются современные тенденции создания интерактивных графических систем типа "клиент/сервер" и методика разработки графических программ, управляемых событиями. В главах 4 и 5 основное внимание уделено концепциям формирова- 20 Предисловие
ния изображений трехмерных объектов и сцен; в главе 4 речь идет о математическом аппара- аппарате описания трехмерных объектов, а в главе 5 рассматриваются методы их отображения. В главе 6 представлен вводный материал о взаимодействии освещения и материалов и о мето- методах закрашивания освещенных поверхностей. Материал первых шести глав следует изучать в той последовательности, в которой он изложен в данной книге. Это должно занять примерно 10 недель при 15-недельном семестре. Материал следующих пяти глав можно изучать в произвольном порядке. Эти главы не имеют столь жесткой структуры изложения, как предыдущие, и читатель может просмот- просмотреть их и получить общее представление или выбрать отдельные темы, и изучить их более детально, чем остальные. В главе 7 представлен обзор методов закрашивания поверхно- поверхностей, используемых в компьютерной графике. Читатель имеет возможность подробно по- познакомиться с одним-двумя алгоритмами выполнения каждого из основных этапов процес- процесса построения изображения. Глава 8 включает несколько тем, касающихся иерархической организации графических моделей и использования объектно-ориентированного подхода. Рассмотренные в ней темы охватывают обширный материал от создания моделей, инкап- инкапсулирующих отношения между компонентами, до использования графики в Internet. В гла- главе 9 рассматриваются новые возможности современных аппаратных графических средств и методы поддержки этих средств, реализованные в OpenGL. Эти методы предполагают ин- интенсивное использование разнообразных буферов. Заключительные разделы этой главы посвящены обсуждению вопросов квантования графического изображения и минимизации возникающих при этом искажений. В главе 10 рассматриваются кривые и поверхности. Процедурные методы построения геометрических моделей на базе многоугольников описаны в главе 11. В этой же главе чита- читатель познакомится с языковыми моделями, фракталами и системами частиц. В главе 12 речь идет о применении графических средств при визуализации результатов научных расчетов. Эта область применения машинной графики позволяет продемонстрировать весь рассмот- рассмотренный в предшествующих главах арсенал методов и средств. Программы, приведенные в основном тексте книги, собраны отдельно в приложении А. Тексты программ доступны и в электронном виде. Изменения, внесенные во второе издание Реакция читателей на появление первого издания этой книги была исключительно пози- позитивной. Особой похвалы удостоились описание методики программирования задач компью- компьютерной графики на OpenGL и принятый в книге нисходящий подход к изложению материала. Но, тем не менее, я включил в настоящее издание много нового материала, а прежний значи- значительно переработал. Внесенные изменения я бы разделил на три категории. Во-первых, я переработал изложе- изложение некоторых математических вопросов (надеюсь, это пошло на пользу полноте и ясности материала) и добавил новые примеры. Во-вторых, я значительно дополнил материал о совре- современных областях применения компьютерной графики, в особенности в научных исследова- исследованиях. В-третьих, в книгу включено значительно больше практических программ на базе OpenGL, причем в них использованы расширения, появившиеся в OpenGL 1.1, касающиеся, в частности, массивов вершин. В результате мне пришлось значительно переработать про- программы, приведенные во второй половине книге. Я добавил в главу 2 два новых примера. Первый относится к использованию рекурсии при построении узора Серпинского и не только демонстрирует, как организуется рекурсия в OpenGL, но и показывает, какие интересные программы могут создавать студенты, даже не изучив досконально все возможности OpenGL. Второй пример демонстрирует нюансы по- построения трехмерного узора Серпинского, и при создании этой программы студенты знако- Предисловие 21
мятся с элементарными приемами создания изображений трехмерных объектов, не требую- требующими использования сложных преобразований координат. Большая часть материала, отно- относящегося к свойствам многоугольников, была перенесена в главу 7. В главе 3 существенной переработке подверглась программа вычерчивания. Из нее удале- удалены часы, поскольку для их реализации использовались функции, специфичные для операци- операционной системы UNIX, но вопросы использования временной синхронизации в этой главе по- прежнему обсуждаются. В главу включен новый пример, демонстрирующий вращение квад- квадрата на экране, который иллюстрирует описанные в основном тексте методы анимации и применение двойной буферизации. В главе 4 более детально описаны абстрактные геометрические объекты и дополнены примерами, в которых показано, как выполняется изменение координат и фреймов. Показано также, как использовать при формировании геометрических моделей массивы вершин — но- новинку версии OpenGL 1.1. Еще один новый пример демонстрирует использование виртуаль- виртуального трекбола для интерактивной реализации преобразований координат в трехмерном про- пространстве. Я надеюсь, что изложение математических основ в главе 5 стало более доступным для студентов по сравнению с первым изданием. Я включил в эту главу также и пример форми- формирования теней, в котором использованы матрицы проецирования. Глава 6 осталась практиче- практически в том же виде, что и в первом издании. Глава 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 собран материал о визуализации пространственных объектов, разбросанный в первом издании по трем главам. Такая концентрация материала позволяет связно описать ме- методы визуализации различных объектов, начиная с контуров и сеток и заканчивая скалярны- скалярными, векторными и тензорными полями. Материал о векторных и тензорных полях, представ- представленный в этой главе, а также примеры в прежнем издании отсутствовали. 22 Предисловие
Дополнительные источники Текущую информацию о дополнениях к этой книге вы можете получить на Web-сервере издательства Addison-Wesley по адресу http://wvw.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 благосклонно предоставили мне необходимое программное обеспечение, и я смог протестировать все про- программы на разных платформах. Предисловие 23
Множество других людей оказали мне очень существенную помощь в подготовке этой книги. Я благодарен Гонсало Картагенова (Gonzalo Cartagenova), Тому Коделу (Tom Caudell), Кетти Коллинз (Kathi Collins), Кетлин Дениэлсон (Kathleen Danielson), Роджеру Эриху (Roger Enrich), Чаку Хансену (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 Carl- 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), подготовила рисунки к предыдущему изданию, многие из которых остались и в этом издании. Со свойственной ей рассудительно- рассудительностью она не стала возражать против использования для этой работы нашего единственного ноутбука и тем самым сохранила мир и сердечность наших отношений, за что, среди прочего, заслуживает дополнительных тысяч слов благодарности. 24 Предисловие
ГЛАВА 1 Графические системы и модели Совершенно очевидно, что в новом тысячелетии информационные и коммуникацион- коммуникационные технологии будут играть важнейшую роль во всех сферах жизни человечества. В таких областях, как кинематография, издательское и банковское дело, в образователь- образовательных учреждениях, внедрение этих технологий уже произвело поистине революционный пере- переворот. Интеграция в единой системе компьютерной графики отдельных компьютеров, сетей и систем машинного зрения открыла новые пути отображения информации, "проникновения" в виртуальный мир и организации взаимодействия человека и машины. Компьютерная графика (computer graphics) — это область информатики (науки о ком- компьютерах — computer sciences), в сферу интересов которой входят все аспекты формирования изображений с помощью компьютеров. Эта область начала развиваться около 40 лет назад. В те годы удавалось добиться отображения нескольких десятков отрезков на экране электрон- электронно-лучевой трубки (ЭЛТ), а современные системы машинной графики позволяют создавать изображения, практически неотличимые по качеству от фотографических снимков. Обще- Общепринятой практикой стало сейчас обучение пилотов с помощью систем моделирования ре- реальной ситуации, как она видится из кабины самолета во время пилотирования, создание изо- изображений виртуального динамического мира во всем его многообразии в реальном масштабе времени. На экран выходят полнометражные кинофильмы, в которых нет ни одного кадра, снятого "на натуре" или в павильоне киностудии, а все действие "разворачивается" в памяти компьютера. В этой главе мы начнем нашу "экскурсию" в мир компьютерной графики с краткого об- обсуждения областей ее применения. Затем будет дан обзор графических систем и способов формирования изображений. На протяжении всей книги я буду постоянно обращать ваше внимание на тесную связь методов компьютерной графики с "традиционными" способами формирования изображений, такими как рисование "от руки" или фотография. Вы увидите, насколько такие аналогии помогают при создании прикладных графических программ, биб- библиотек и структурном синтезе графических систем. В процессе изложения материала в данной книге широко используется одна из сущест- существующих на сегодняшний день программных графических систем — OpenGL, которая в по-
следнее время рассматривается многими разработчиками как своего рода стандарт для созда- создания графических приложений. Изучение OpenGL не представляет никакой сложности для ма- мало-мальски опытного программиста, и в то же время эта система располагает всем набором средств, характерных для большинства современных графических систем. При изложении материала мы будем использовать нисходящий подход (сверху вниз). Это означает, что я предлагаю вам как можно раньше приступить к разработке практических программ, способ- способных создавать графические картинки, пусть поначалу и несложные. И уже после того как вы добьетесь появления картинки на экране с помощью созданных программ, мы обсудим, как выполняются отдельные графические функции библиотеки и каким образом аппаратно реали- реализуется формирование изображения на экране. Материал этой главы должен дать вам общее представление о том, чего можно достичь, создавая графические программы. 1.1. Области применения компьютерной графики Развитие компьютерной графики определяется двумя факторами: реальными потребно- потребностями потенциальных пользователей и достижениями в области аппаратного и программного обеспечения. Хотя компьютерная графика используется в самых различных сферах жизни со- современного общества, можно выделить четыре главные области ее применения. 1. Отображение информации. 2. Проектирование. 3. Моделирование. 4. Пользовательский интерфейс. Хотя во многих практических приложениях можно обнаружить характерные признаки двух или более перечисленных групп, развитие каждой из этих групп шло своим путем. 1.1.1. Отображение информации Классические графические технологии развивались как средство передачи информации в человеческом обществе. Хотя аналогичную роль играет и язык (как в устной, так и в пись- письменной форме), зрительная система человека обладает гораздо большими возможностями (специалист по информатике сказал бы "большей пропускной способностью"), поскольку вы- выполняет функции и обработки данных, и распознавания образов. Еще 4000 лет назад древние вавилоняне использовали графические планы при строительстве каменных сооружений. Древние греки еще в конце первого тысячелетия до н.э. были способны преподносить свои архитектурные идеи в графическом виде, хотя соответствующие математические методы появились только в эпоху Ренессанса. Сегодня подобного рода информация создается архи- архитекторами, конструкторами и чертежниками с помощью компьютерных систем. На протяжении многих столетий картографы и астрономы вычерчивали карты, чтобы представить информацию о расположении небесных тел и географических областей. Нет смысла говорить о том, какое значение имеют такие карты сегодня не только для навигации на Земле и в Космосе, но и для решения повседневных задач человечества с помощью геоин- геоинформационных систем. Сейчас любую карту можно в считанные минуты получить и обрабо- обработать с помощью Internet. За последние 100 лет статистики использовали самые разные технологии для представле- представления в графическом виде первичных данных и результатов их статистической обработки. Та- Такая форма представления множества собранных данных является наиболее информативной. Сегодня и в этой области не обойтись без компьютеров, которые не только обрабатывают со- собранные данные, но и формируют соответствующие графики, используя самые разнообраз- разнообразные способы их представления, в том числе и с применением цвета. Только такая форма 26 Глава 1. Графические системы и модели
представления позволяет человеку без труда интерпретировать информацию, содержащуюся в гигабайтах собранных первичных данных. Множество важных и интересных проблем анализа данных ставит и медицина. Новые технологии визуализации состояния человеческого организма, такие как компьютерная томо- томография, магниторезонансное обследование, ультразвуковое зондирование и позитронно- эмиссионная томография, позволяют получать трехмерную информацию, которая может быть впоследствии обработана вычислительными методами. Хотя сами первичные данные формируются специальной медицинской аппаратурой, последующая компьютерная обработ- обработка и созданное цветное изображение позволяют специалистам достаточно просто интерпре- интерпретировать их. С появлением суперкомпьютеров стало возможным исследовать проблемы, ранее отне- отнесенные к классу неразрешимых. В области визуализации результатов экспериментов и науч- научных расчетов средства компьютерной графики являются мощным инструментом для пра- правильной интерпретации огромных массивов первичных данных. Исследования в таких облас- областях, как течение жидкостей, молекулярная биология и математика, не обходятся без преобразования первичных данных в зримые геометрические образы, что помогает лучше понять суть происходящих процессов. Например, на ил. 9 цветной вклейки представлено изо- изображение динамики движения жидкости в мантии Земли. Исходные данные были получены в результате математического моделирования процесса, а затем использовались различные ме- методы визуализации этих данных, описанные в главах 8, 9 и 12 настоящей книги. 1.1.2. Проектирование Проектирование является одной из основных стадий создания изделий и сооружений в технике и строительстве. Задавшись спецификацией основных характеристик разрабатывае- разрабатываемого изделия, конструктор ищет решение, оптимальное с точки зрения затрат и технических параметров. Процесс проектирования по самой своей природе является итеративным — очень редко бывает так, что заданные характеристики допускают только один вариант реше- решения. Как правило, исходная формулировка задачи проектирования оказывается недоопреде- ленной, т.е. допускает множество решений, или переопределенной, другими словами, в таком виде задача оказывается неразрешимой. В первом случае конструктору или проектировщику нужно перебрать множество вариантов перед тем, как остановиться на одном из них, причем часто получается так, что очередной вариант появляется в результате анализа и устранения недостатков предыдущего. Во втором случае приходится добиваться изменения исходной формулировки задачи, что также включает поиск вариантов, наиболее близких по характери- характеристикам к заданной спецификации. Достоинства парадигмы взаимодействия конструктора с изображением проектируемой конструкции на экране ЭЛТ впервые подметил Айвен Сазерленд (Ivan Sutherland) еще лет со- сорок назад. Сегодня уже ни у кого не вызывает сомнения прогрессивность применения средств графического взаимодействия конструктора и компьютера в системах автоматизации проек- проектирования (САПР). Такие системы применяются в самых разнообразных отраслях техники — от проектирования микросхем со сверхвысокой степенью интеграции (СБИС) до автомоби- автомобилей, самолетов и космических аппаратов. Не менее широкое применение такого рода системы нашли в архитектурном проектировании и строительстве. В различных областях проектиро- проектирования графические средства используются по-разному. Например, при проектировании СБИС интерфейс между конструктором и компьютером (т.е. программами автоматического синтеза электронных схем) реализуется посредством меню и пиктограмм. После того как таким спо- способом будет создан вариант проектируемой конструкции, система анализирует ее параметры и представляет характеристики изделия также в графическом виде. Не менее широкие воз- возможности открывают САПР и перед архитекторами. На ил. 3 и 4 цветной вклейки показаны 1.1. Области применения компьютерной графики 27
два вида спроектированного здания, созданных с помощью САПР. Эти рисунки демонстри- демонстрируют, какими возможностями обладают современные САПР при отображении одного и того же объекта на разных стадиях проектирования. 1.1.3. Моделирование Как только графические системы стали обладать достаточной производительностью для создания сложных динамических изображений, возникла идея применить их в качестве сред- средства моделирования реальной обстановки (симулятора) на разного рода тренажерах. Первы- Первыми такие системы освоили авиаторы и использовали для обучения пилотов на земле. Это по- позволило значительно снизить стоимость обучения, гарантируя при этом его высокое качество и безопасность. Использование в современных системах специальных БИС позволило на- настолько снизить стоимость подобного рода устройств, что они дошли до уровня детских иг- игрушек. Например, с помощью средств компьютерной графики возможно создагие графиче- графической модели робота, которая используется при планировании технологических процессов и подготовке программ управления роботами, работающими в окружения множества других элементов производственной системы. В телевидении, кинематографии и рекламном деле в последнее время также широко ис- используются средства компьютерной графики, позволяющие создавать динамические изобра- изображения, практически неотличимые от снятых "на натуре". Именно их мы часто видим на экра- экранах телевизоров и в кинотеатрах. Подобного рода изображения (правда, статические) заполо- заполонили и страницы популярных периодических изданий. Стоимость создания полнометражного кинофильма с помощью компьютера сравнима со стоимостью съемки такого же фильма на "натуре" и в павильонах студии, но при этом можно создавать такие спецэффекты и трюки, которые недоступны для "живой" съемки. В главах 6 и 9 будут рассмотрены различные мето- методы моделирования освещения, которые используются при построении изображений, близких по качеству к фотографиям (для краткости будем их впредь именовать фотореалистически- фотореалистическими). Ил. 14 на цветной вклейке демонстрирует возможность создания компьютерными сред- средствами изображения, которое невозможно получить иными средствами, хотя в нем полно- полностью соблюдаются все законы оптики. Изображения на ил. 10 и 11 цветной вклейки также созданы компьютером с использованием современных методов тонирования. В последнее время появилась еще одна область применения средств компьютерной графики, которая получила наименование формирование виртуальной реальности (VR— virtual reality). В VR-системах человек-наблюдатель пользуется специальным шлемом с парой миниатюрных дисплеев, на экранах которых формируются разные изображения для правого и левого глаза. В результате создается стереоэффект. Кроме того, положение и ориентация головы наблюдателя постоянно анализируются и соответственно изменяется изображение на экранах дисплеев. Если добавить к этому еще и средства "влияния" на эту среду вроде перчаток с силомоментными дат- датчиками, а также звуковое сопровождение, то создается полная иллюзия погружения в виртуаль- виртуальную среду. Наблюдатель чувствует себя участником происходящего. Это уже не просто развле- развлечение, а инструмент для практического применения. Например, с помощью такой системы хи- хирург может отработать методику проведения операции, астронавт может подготовиться к выходу в открытый космос и проведению ремонтных работ. 1.1.4. Интерфейс пользователя В последнее время визуальная парадигма стала доминирующей в сфере взаимодействия пользователя с компьютером. Визуальный метод предполагает использование различного ро- рода окон, пиктограмм, меню и устройств указания, таких как мышь. С точки зрения пользова- пользователя оконные операционные системы — X Window, Microsoft Windows и операционная сис- система Macintosh — отличаются только деталями. Сейчас уже миллионы людей пользуются ус- 28 Глава 1. Графические системы и модели
лугами сети Internet. Доступ к этой сети немыслим без графических программ-броузеров, та- таких как Netscape и Internet Explorer, которые используют, по сути, одни и те же графические средства интерфейса. Мы настолько к ним привыкли, что часто и не задумываемся, что эти средства также относятся к инструментам компьютерной графики. Хотя мы уже и привыкли к такому стилю в организации пользовательского интерфейса в большинстве графических рабочих станций, развитие компьютерной графики позволяет ис- использовать и другие формы интерактивного взаимодействия. 1.2. Графическая система Система компьютерной графики является прежде всего вычислительной системой и, как таковая, включает все компоненты вычислительной системы общего назначения. Нач- Начнем наш обзор с блок-схемы, представленной на рис. 1.1, на которой показаны основные компоненты системы: ¦ процессор; ¦ память; ¦ буфер кадра; ¦ устройства вывода; ¦ устройства ввода. Рис. 1.1. Структура графической системы Эта модель имеет достаточно общий характер и отображает структуру и графической ра- рабочей станции, и персонального компьютера, и графического терминала большой вычисли- вычислительной системы, работающей в режиме разделения машинного времени, и интеллектуальной системы формирования изображений. Хотя все компоненты представленной блок-схемы при- присутствуют и в стандартном компьютере (кроме, возможно, буфера кадра), именно специали- специализация каждого компонента в соответствии с требованиями задач компьютерной графики и делает систему графической. 1.2.1. Пиксели и буфер кадра В настоящее время практически все графические системы используют растровый прин- принцип создания изображения. Суть его заключается в том, что изображение рассматривается как массив — растр — простейших элементов, или пикселей (pixels). Каждый пиксель 1.2. Графическая система 29
имеет четко заданное положение на экране (рис. 1.2). Массив кодов, определяющих за- засветку пикселей на экране, хранится в отдельной области памяти, которая называется бу- буфером кадра (frame buffer). В системах особо высокого качества для буфера кадра исполь- используются специальные типы микросхем — видеопамять с произвольным доступом (VRAM — video random-access memory) или микросхемы динамической памяти с произвольным дос- доступом (DRAM — dynamic random-access memory), которые позволяют быстро вывести со- содержимое буфера на экран. Глубина (depth) буфера кадра характеризует количество бит информации, определяющих засветку каждого отдельного пикселя, в частности количество цветов, которое может быть представлено на экране данной системы. Например, буфер глубиной 1 бит позволяет выводить только двухградационное изображение, а буфер глуби- глубиной 8 бит может выводить изображение, состоящее из элементов 28 = 256 цветов. Современные полноиветные (full-color) системы характеризуются глубиной буфера 24 бита (а иногда и больше). Такие системы способны создавать по-настоящему фотореалистиче- фотореалистическое изображение. Иногда их называют системами с правильной цветопередачей (true- color), или RGB-системами, поскольку в кодировке засветки каждого пикселя можно вы- выделить отдельные группы битов, характеризующие интенсивность засветки по каждому из основных цветов,— красному (red), зеленому (green) и синему (blue). В более простых системах буфер кадра выделяется в основной памяти компьютера. Размер буфера кадра определяет, в конце концов, одну из главных характеристик графической системы — раз- разрешающую способность (или разрешение). Рис. 1.2. Пиксели: а — изображение кота Йети; б — деталь изображения (глаз кота), на которой различимы отдельные пиксели В простых системах, как правило, используется единственный процессор, на который воз- возлагается решение как "обычных" задач, так и задач компьютерной графики. Основные гра- графические функции, в принципе, сводятся к преобразованию описания графического примити- примитива (отрезка прямой, окружности или многоугольника), сформированного прикладной про- программой, в коды засветки определенных пикселей в буфере кадра. Процесс преобразования описания графического примитива в коды засветки пикселей получил наименование растро- растрового преобразования (rasterization) или сканирующего преобразования (scan conversion). В современных высокопроизводительных графических системах для такого преобразования ис- используются специализированные процессоры, причем не один, а несколько, каждый из кото- которых выполняет свой набор графических функций. 30 Глава 1. Графические системы и модели
1.2.2. Устройства вывода изображений Доминирующее положение среди устройств вывода изображений занимают электронно- электроннолучевые трубки (ЭЛТ). В упрощенном виде конструкция ЭЛТ представлена на рис. 1.3. При попадании сфокусированного электронного луча на люминофор, покрывающий экран трубки, излучается свет. Направление электронного луча и, следовательно, положение точки засветки экрана управляются двумя парами отклоняющих пластин. Значения координат точки засвет- засветки, формируемые компьютером, преобразуются с помощью цифро-аналоговых преобразова- преобразователей в управляющие напряжения, которые подаются на пластины, отклоняющие луч по осям х ну. В результате на экране появляется засвеченное пятно, положение которого соответству- соответствует заданным координатам. Электронная пушка Фокусирующая система Рис. 1.3. Электронно-лучевая трубка Если сигналы, подаваемые на отклоняющие пластины, изменяются с постоянной скоро- скоростью, то луч вычертит на экране прямую линию — вектор. Такие устройства получили на- наименования ЭЛТ с произвольным отклонением {random-scan CRT) или векторные ЭЛТ (calligraphic CRT), поскольку в них луч может перемещаться по произвольной траектории1. Если электронный луч заперт (его интенсивность равна нулю), то можно переместить его в новое положение, не оставляя следа на экране. Именно по такому принципу работали многие системы отображения, созданные в 60-х годах, которые затем были вытеснены современны- современными растровыми системами. Сформированное на экране изображение остается видимым в течение очень короткого промежутка времени — это определяется характеристиками люминофора, покрывающего эк- экран, так называемого времени послесвечения. Как правило, время послесвечения не превыша- превышает нескольких миллисекунд. Исключение составляют специальные запоминающие ЭЛТ, ко- которые сохраняют на экране сформированное изображение около часа. При использовании обычных ЭЛТ изображение должно обновляться (регенерироваться) не реже 50 раз в секунду. В растровых системах луч всегда перемещается по одной и той же траектории, а изо- изображение создается за счет изменения интенсивности луча синхронно со считыванием кодов засветки пикселей из буфера кадра. За время одного цикла регенерации изображения полно- полностью считывается содержимое буфера кадра. Частота регенерации выбирается таким обра- Строго говоря, любая ЭЛТ как электронный прибор допускает произвольное отклонение луча. Вектор- Векторным или растровым может быть все устройство — дисплей. — объединяющее в себе ЭЛТ. отклоняющую систему и генератор отклоняющих сигналов. Поэтому в отечественной литературе используются термины "векторный дисплей" и "растровый дисплей". — Прим. ред. 1.2. Графическая система 31
зом, чтобы человеческий глаз не замечал мелькания изображения вследствие конечного вре- времени послесвечения люминофора. Существуют два способа формирования растра на экране. Первый получил наименование прогрессивной развертки {noninterlaced) — в течение каждо- каждого очередного периода кадровой развертки луч последовательно проходит по всем строкам разложения. Частота кадровой развертки соответствует частоте регенерации и составляет 50- 85 Гц. Другой принцип — чересстрочная {interlaced) развертка — предполагает, что в од- одном кадре луч проходит по всем четным строкам разложения, а в следующем — по всем не- нечетным. Чересстрочная развертка используется в обычных телевизионных системах. При этом, хотя частота кадровой развертки составляет 60 Гц (в Европе — 50 Гц), изображение на экране полностью обновляется только 30 раз в секунду. Для наблюдателя, который сидит достаточно близко к экрану, разница между прогрессивной и чересстрочной разверткой весь- весьма заметна. Дисплеи с прогрессивной разверткой занимают в настоящее время доминирую- доминирующее положение, хотя для их нормальной работы и приходится использовать в качестве буфе- буфера кадра высокоскоростные модули памяти. В цветных ЭЛТ экран покрывается точками трех разных типов люминофора— одни под воздействием электронного луча излучают красный свет, другие — зеленый, а третьи — си- синий. Точки люминофоров разного цвета размещены триадами. Большинство цветных ЭЛТ имеет три электронные пушки (соответственно трем цветам точек люминофора). Между от- отклоняющей системой и экраном в такой цветной ЭЛТ располагается теневая маска — метал- металлический экран со множеством отверстий соответственно триадам точек люминофора (рис. 1.4). Теневая маска обеспечивает попадание луча, испускаемого определенной пушкой, только наточки люминофора "своего" цвета в соответствующей триаде. Рис. 1.4. Цветная ЭЛТ с теневой маской Несмотря на то что ЭЛТ являются наиболее распространенным средством создания изображений в системах компьютерной графики, в последнее время интенсивно разраба- разрабатываются приборы, основанные на других физических принципах. Но и в них используется тот же растровый способ создания изображения. В портативных компьютерах используют- используются .жидко-кристаллические дисплеи {LEDs — liquid-crystal displays). Такие приборы также требуют регенерации изображения, а устройства создания "твердых" копий на бумажных носителях хотя и не требуют регенерации, но используют все тот же растровый принцип разложения изображения. 32 Глава 1. Графические системы и модели
1.2.3. Устройства ввода В большинстве графических систем в качестве хотя бы одного из возможных устройств ввода используется обычная алфавитно-цифровая клавиатура. Но более специфическими уст- устройствами, предназначенными для ввода именно графической информации, являются мышь, джойстик (joystick) и планшет. Каждое из этих устройств способно передавать в систему ин- информацию о положении и каждое оснащено, как минимум, парой кнопок, формирующих управляющие сигналы. По отношению ко всем подобным устройствам применяется термин устройство указания {pointing devices). Мы рассмотрим подробно их конструкцию и харак- характеристики в главе 3. 1.3. Изображение: физическое и синтезируемое При изучении дисциплины "компьютерная графика" специалисты по педагогике рекомен- рекомендуют начинать с обсуждения методов формирования растрового изображения простых двух- двухмерных геометрических примитивов (например, точек, отрезков прямых и многоугольников) в буфере кадра. Такой подход хорош, если задаться целью научить студентов формировать простые штриховые изображения, например чертежи. Но для работы с современными графи- графическими системами нужно в первую очередь иметь представление о том, какими возможно- возможностями обладают применяемые в них аппаратные и программные средства в части создания реалистических изображений трехмерных объектов. Эта задача предполагает знакомство с физическими принципами формирования изображений, основанными на законах, описываю- описывающих взаимодействие света с веществом. Поэтому я предпочитаю начинать изложение курса компьютерной графики именно с таких основополагающих принципов. Изображение, формируемое компьютером, по самой своей природе является искусственно синтезируемым, поскольку зачастую оно не существует физически. Но при этом используют- используются те же физические законы, которые действуют и в отношении изображений физических объектов, воспринимаемых зрением человека. Поэтому я и начну изложение с рассмотрения принципов создания изображений в оптических системах, таких как фотокамера или глаз че- человека. Мы сформируем модель процесса формирования изображения, которая в дальнейшем позволит воспроизвести этот процесс программными средствами. В этой главе я буду использовать математику только там, где без нее совершенно невоз- невозможно обойтись. Главное для нас сейчас — познакомиться с принципами создания изобра- изображения и представить себе архитектуру вычислительной системы, которая могла бы их реали- реализовать. Все детали процесса, как математические, так и технические, будут изложены в по- последующих главах. 1.3.1. Объекты и наблюдатели Нам выпало жить в мире трехмерных объектов. Обычно мы характеризуем положение не- некоторой точки на объекте в терминах подходящей относительной системы координат. Мы можем измерять расстояние между точками и таким образом расстояние между объектами. Именно желание концептуально систематизировать такие простые идеи, как измерение раз- размеров и расстояний, и привело к развитию многих областей математики, в частности геомет- геометрии и тригонометрии. Часто мы стараемся представить свое представление о пространствен- пространственных отношениях между объектами в форме чертежей или изображений, например карт, тех- технических чертежей, рисунков или фотографий. Точно так же изобретение многих физических приборов, включая фотокамеры, микроскопы и телескопы, было тесно связано с желанием визуализировать пространственные отношения между объектами. Следовательно, существует фундаментальная связь между физикой и математикой описания процесса формирования 7.5. Изображение: физическое и синтезируемое 33
изображения. Именно эту связь мы и будем использовать в процессе разработки методов соз- создания изображений с помощью компьютеров. В любом процессе формирования изображений — физическом или формальном (математическом) — присутствуют две сущности: объект и наблюдатель. Объект сущест- существует в пространстве независимо от процесса создания изображения и соответственно от наблюдателя. В компьютерной графике мы будем иметь дело не с реальным физическим объектом, а с воображаемым, синтезированным компьютерной программой. Такой вооб- воображаемый объект формируется через спецификацию положения в пространстве разнооб- разнообразных геометрических примитивов — точек, отрезков прямых или многоугольников. В большинстве графических систем для описания или аппроксимации объектов оказывается достаточно множества описаний точек в пространстве, или вершин (vertices). Например, отрезок прямой характеризуется двумя вершинами, многоугольник — упорядоченным спи- списком вершин, а сфера— двумя вершинами, одна из которых соответствует центру, а дру- другая — любой точке на поверхности сферы. Одна из главных функций САПР — обеспечить пользователю возможность формировать такую синтетическую модель проектируемого из- изделия и окружающей это изделие среды. В главе 2 будет показано, как формировать моде- модели простых объектов с помощью OpenGL, а в главе 8 — как описать пространственные от- отношения между объектами. Любая система отображения должна обладать средствами формирования изображений наблюдаемых объектов. Для этого нам понадобится некто (или нечто), "рассматривающий" эти объекты. Этим некто или нечто может быть человек, фотокамера или дигитайзер. Именно наблюдатель формирует изображение объектов. Если наблюдателем является человек, то изображение формируется на сетчатке глаза, а в фотокамере — на поверхности фотопленки. Часто понятие объекта и его изображения смешиваются. Мы обычно смотрим на объект с определенной точки и забываем, что другие наблюдатели, рассматривая его из другой точки, могут получить совершенно другое изображение того же самого объекта. На рис. 1.5,а пока- показано изображение сцены, как ее видит наблюдатель А. В этой сцене присутствуют два других наблюдателя — В и С. Для наблюдателя А они являются объектами сцены. На рис. 1.5,6,в по- показано, как увидят эту же сцену наблюдатели В и С. 6) в) Рис. 1.5. Как видят изображение одной и топ же сцены три разных наблюдателя: а — наблюдатель А; б — наблюдатель В; в — на- наблюдатель С На рис. 1.6 показано, как формируется изображение здания на пленке фотокамеры. Хотя и наблюдатель, и наблюдаемый объект существуют в одном и том же трехмерном мире, соз- созданное на плоскости пленки изображение является двухмерным. Суть процесса формирова- формирования изображения и состоит в том, чтобы, зная положение (и характеристики) наблюдателя и положение объекта, описать получаемое при этом двухмерное изображение. Именно это мы и рассмотрим детально в последующих разделах. 34 Глава 1. Графические системы и модели
Рис. 1.6. Создание изображения в фотокамере 1.3.2. Свет и изображение В упрощенном описании процесса формирования изображения были опущены многие важные детали. В частности, ничего не было сказано об источниках света и об их влиянии на создание изображения. Совершенно очевидно, что без источника света объект просто- напросто погрузится в "тьму кромешную" и ни о каком его изображении просто не может быть речи (хотя сам объект и будет реально существовать). Точно так же мы ничего не сказа- сказали и о цвете или о том, как влияет на изображение объекта текстура его поверхности. Анализ физики процесса начнем с простейшего случая, представленного схематически на рис. 1.7. На нем показана сцена, в которой присутствуют физический объект, наблюдатель (фотокамера) и источник света. Свет, излучаемый источником, по-разному взаимодействует с разными участками поверхности объекта, и часть отраженной световой энергии попадает в объектив фотокамеры. Количество энергии, попавшей в фотокамеру, зависит от характера взаимодействия между падающим светом и поверхностью (точнее, разными участками поверхности). Думаю, читателям известно, что свет— это элек- электромагнитное излучение в определенном диапазоне длин волн или частот.2 В полном спектре электромаг- электромагнитного излучения (рис. 1.8) диапазон видимого света располагается между диапазонами радиоволн и инфра- инфракрасного (теплового) излучения. Видимый свет имеет длину волны в диапазоне от 350 до 780 нанометров (нм). Каждый источник света характеризуется цветом, т.е. спектральным составом излучения. Например, лазер формирует монохроматическое излучение, т.е. излуче- излучение одной длины волны, а в излучении, формируемом лампой накаливания, присутствует множество состав- составляющих частот. В компьютерной графике, к счастью, практически никогда не приходится иметь дело с волновой природой света. Вместо этого мы будем следовать традиционному подходу, который вполне корректен при достаточно высоком уровне интенсивности света, когда его волновая природа не является суще- существенным фактором. В геометрической оптике принято моделировать источники света в пред- Рис. /. 7. Фотокамера и источник света ' Соотношение между частотой f и длиной волны Я имеет вид fA = с, где с — скорость света. 1.3. Изображение: физическое и синтезируемое 35
положении, что интенсивность излучаемой ими световой энергии постоянна. Свет распростра- распространяется в однородной среде по прямой от источников к тем объектам, с которыми он взаимодей- взаимодействует. Идеальный точечный источник света излучает из заданной точки в пространстве свет равной интенсивности во всех направлениях. Реальные источники имеют конечные геометриче- геометрические размеры излучающей поверхности и характеризуются пространственной диаграммой ин- интенсивности, поскольку предположение о равномерности излучения по всем направлениям в та- таком случае уже не отражает физики процесса. Кроме того, диаграмма интенсивности может из- изменяться для разных длин волн. Пока что мы будем рассматривать только точечные источники света, а моделирование реальных источников отложим до главы 6. Радиоволны | Видимый свет i Инфракрасное (тепловое) излучение 350 A(nm) 780 Рис. 1.8. Спектр электромагнитных волн Другая существенная характеристика источника света— спектр излучения. В этой главе мы рассмотрим только источники монохроматического излучения, которые имеют единст- единственную спектральную составляющую. Поскольку в отношении разных световых частот мож- можно применять принцип суперпозиции, это предположение не влияет на корректность полу- полученных результатов и позволяет распространить их на полихромные источники. Но такое предположение позволит нам сейчас ограничиться только одной характеристикой источни- источника — его светимостью (brightness), в то время как при работе с полихромными источниками нужно вводить в рассмотрение еще и параметр цветовой тон (hue). Здесь есть определенная аналогия с отличием черно-белого телевидения от цветного. 1.3.3. Трассировка лучей Начнем создание модели процесса формирования изображения с исследования траекто- траектории световых лучей, испускаемых источником. Рассмотрим сцену, ..редставленную на рис. 1.9, в которой присутствует только один точечный источник света. Наблюдатель в сцене нужен по той простой причине, что нас, в конце концов, интересует свет, который достигнет глаз наблюдателя. Наблюдателем может быть и фотокамера, как показано на рис. 1.10. Луч представляет собой прямую, ограниченную с одной стороны, т.е. исходящую из определен- определенной точки и уходящую в бесконечность. Поскольку свет распространяется по прямой, то его можно анализировать в терминах геометрических лучей, исходящих из точечного источника во всех направлениях. Часть этих лучей и вносит свой вклад в образование изображения на пленке в фотокамере. Например, если источник видим из камеры, то некоторые лучи прямо попадают через объектив на пленку. Большая часть испускаемых источником лучей уходит в бесконечность, не попадая прямо ни в объектив камеры, ни на один из "пассивных" объектов сцены. Эти лучи не вносят никакого вклада в создание изображения, хотя их и могут видеть другие наблюдатели. Оставшиеся лучи падают на объекты сцены и освещают их. Лучи, па- падающие на объект, могут взаимодействовать с ним по-разному. Например, если поверхность объекта зеркальная, то отраженные лучи могут (в зависимости от ориентации поверхности) 36 Глава 1. Графические системы и модели
достичь объектива и внести определенный вклад в изображение. Другие поверхности, кото- которые называются рассеивающими, или диффузными (diffuse surfaces), рассеивают падающий на них свет во всех направлениях. Если поверхность прозрачная, то световой луч от источни- источника может пройти через нее, но при этом возникает его преломление (или рефракция) и час- частичное отражение. В результате преломленный и отраженный лучи потом могут взаимодей- взаимодействовать с другими объектами и, возможно, попасть в объектив камеры. Как обычно, большая часть этих лучей потом уходит в бесконечность. На рис. 1.10 представлены шесть вариантов взаимодействия лучей, испускаемых источником, с окружающей средой. Рис. 1.9. Сцена с единственным точечным источником света Рис. 1.10. Трассировка лучей. Луч А попадает непосредственно в объектив камеры. Луч В уходит в бесконечность, не встретив на своем пути никаких препятствий. Луч С отразкается от зеркальной поверхности и попадает в объектив камеры. Луч D попадает на диффузную по- поверхность, и в результате образуется бесконечное множество других лучей, распростра- распространяющихся во всех направлениях, часть из которых, вполне вероятно, и попадет в объектив камеры. Луч Е попадает на частично прозрачную поверхность и в результате расщепляется на преломленный луч и отраженный. Луч F, отраженный от зеркальной поверхности, попа- попадает на другой объект, который может его поглотить 1.3. Изображение: физическое и синтезируемое 37
Трассировка лучей — это метод моделирования процесса формирования изображения, осно- основанный на анализе описанных оптических явлений. Этот метод в последнее время все шире применяется в компьютерной графике. Метод трассировки световых лучей можно применять для моделирования оптических эффектов в сколь угодно сложной среде — все зависит только от производительности программы и компьютера. Хотя эта модель и достаточно точно описы- описывает реальные физические процессы, соответствующие алгоритмы требуют довольно большого объема вычислений при реализации на компьютере. Можно, однако, в дальнейшем упростить модель, что облегчит ее алгоритмическую реализацию. С физической точки зрения, если на- наблюдатель улавливает излучение, исходящее от некоторого объекта, для него не имеет значения, каким образом образовалось это излучение — в результате преобразования одного вида энергии в другой (физический источник света) или в результате отражения падающего света. Если, на- например, зеркало отражает свет от источника в объектив камеры, то для камеры оно является та- таким же источником. Если мы видим какой-либо объект, то он или освешен светом от какого- либо источника, или сам является источником, или и то и другое. Глядя на объект, мы не можем сказать категорически, какая комбинация этих вариантов имеет место в данной ситуации. Предположим, что все объекты имеют одинаковую яркость, — сценарий, который трудно реализовать в реальном мире, но который очень часто является вполне резонным при размеще- размещении источников в среде. С точки зрения наблюдателя красный треугольник имеет одинаковую яркость красного света в каждой точке, и его нельзя отличить от равномерно излучающего ис- источника того же цвета и формы. Задавшись таким предположением, можно освободиться от собственно физических источников света и с помощью простых тригонометрических методов "просчитать" изображение. В главе 6 будут рассмотрены модели, включающие сложные источ- источники света и учитывающие свойства материалов реальных объектов. В следующих разделах мы рассмотрим две физические системы создания изображения — глаз человека и камеру-обскуру (или камеру с точечным отверстием). Глаз человека— это исключительно сложная оптическая система, но она подчиняется тем же физическим зако- законам, что и прочие. Мы рассмотрим ее не только в качестве примера реальной оптической системы, но и потому, что правильное представление о ее характеристиках поможет в даль- дальнейшем анализировать возможности компьютерных графических систем. Камера-обскура — это одна из простейших оптических систем, анализ которой поможет уяснить принципы ра- работы и других, более сложных оптических приборов. Мы будем эмулировать характеристики такой камеры при построении модели процесса формирования изображения. 1.4. Глаз человека Хотя глаза и являются очень сложной биологической системой, они обладают всеми ком- компонентами искусственно созданных оптических систем, таких как фотокамера и микроскоп. Структура глаза схематически показана на рис. 1.11. Свет по- попадает в глаз через роговицу и хрусталик (своего рода линзу). Роговица— это прозрачная субстанция, предохраняющая хрусталик. Радужная оболочка глаза играет роль диафрагмы, регулируя количество пропускаемого внутрь света. Хрусталик формирует изображение на двухмерной поверхности сетчат- сетчатки — внутренней поверхности глазного яблока. Расположен- Расположенные на сетчатке фоторецепторные клетки — палочки и кол- колбочки — играют роль приемников света, которые восприни- воспринимают электромагнитные колебания в диапазоне длин волн от Нерв ^^шшяяв^ 350 до 780 нм. _ . ,, „ Палочки являются высокочувствительными приемниками Рис 1.11. Строение глаза чело- „ _ ,. J _ г излучения и работают в условиях слабого освещения 38 Глава 1. Графические системы и модели
(ночью), а колбочки несут свою "вахту" при обычном, дневном освещении. Размеры палочек и колбочек в совокупности с оптическими свойствами хрусталика и роговицы определяют разрешающую способность глаза. Этот параметр характеризует способность глаза, как, впрочем, и любой другой оптической системы, различать мельчайшие видимые объекты. Ес- Если говорить техническим языком, то разрешающая способность характеризует минимальное расстояние между точками, различимое данной системой. Чувствительные клетки глаза неодинаково реагируют на электромагнитные колебания раз- разной длины волны. Существуют три типа колбочек и один тип палочек. В то время как интенсив- интенсивность есть мера энергии света, воздействующего на глаз, яркость — это мера восприятия этого воздействия. Глаз человека по-разному воспринимает монохроматический красный цвет и мо- монохроматический зеленый цвет равной интенсивности, поскольку чувствительность колбочек разного типа отличается. На рис. 1.12 приведена интегральная кривая спектральной чувстви- чувствительности глаза, известная как стандартная кривая Ме.ждународной светотехнической комис- комиссии (CIE — Commision Internationale de L'Eclairage). Из нее видно, что глаз наиболее чувствите- чувствителен к зеленому цвету, а наименее — к чисто красному и синему. Эта кривая очень близка к кри- кривым спектральной чувствительности светоприемников камер черно-белого телевидения, и по ней сверяют свою продукцию изготовители черно-белых фотопленок. Яркость — это интегральная мера нашего восприятия поступившего света. Возможность цветового восприятия по- появляется у человека благодаря наличию "узкополосных" приемников излучения — колбочек трех типов. Каждый тип характеризуется своей кривой спектральной чувствительно- чувствительности, причем диапазон длин волн для каждого типа сущест- существенно уже полного спектра световых волн (рис. 1.13). Мак- Максимум чувствительности на одной из этих кривых приходит- приходится на синий цвет, на другой — на зеленый, на третьей — на желтый.3 Одним из следствий раскрытия механизма цвето- восприятия глаза явилось то, что по этому же принципу вы- выделения основных, или первичных, цветов строятся и техни- технические системы приема цветных изображений — телевизи- телевизионные и фотографические. Эти первичные цвета можно использовать для приближенного представления любого "промежуточного" цвета. Обсуждение проблемы цветовосприятия и создания цветных изо- изображений мы отложим до главы 2. 350 Л(пт) 780 Рис. 1.12. Интегральная кривая спектральной чувстви- чувствительности глаза человека (стандартная кривая CIE) 6) в] О X X Рис. 1.13. Кривые чувствительности колбочек 3 Кривую, имеющую максимум интенсивности на длине волны желтого цвета, часто называют красной кривой. Это сделано только для терминологического единообразия с трехцветными системами в фотогра- фотографии и на телевидении. Главное же состоит в том, что во всех трехцветных системах — и технических, и биологических — используются три типа чувствительных элементов, обладающих цветовой избирательно- избирательностью. 1.4. Глаз человека 39
Хотя начальная фаза обработки изображения, появившегося на внутренней поверхности сетчатки глаза, во многом походит на то, что происходит в технических системах, далее все идет совершенно по-иному (по крайней мере, по сравнению с существующим на сегодняш- сегодняшний день техническими системами). Нервные клетки связаны с палочками и колбочками ис- исключительно сложным образом, и этот симбиоз в чем-то напоминает чрезвычайно сложный сигнальный процессор (или скорее, наоборот, создатели сигнальных процессоров пытаются воспроизвести то, что происходит в зрительной системе человека). Окончательная обработка происходит в коре головного мозга, где выполняются такие сложные функции, как распозна- распознавание образов. Мы не будем вдаваться в обсуждение этих вопросов, поскольку они далеко выходят за рамки проблематики компьютерной графики. В дальнейшем нас будет интересо- интересовать только восприятие изображения на уровне первичных сенсоров — палочек и колбочек. 1.5. Камера-обскура Камера-обскура, схематически показанная на рис. 1.14, позволяет простейшим образом описать процесс формирования изображения с помощью геометрической модели. Она пред- представляет собой светонепроницаемый ящик, в центре одной из стенок которого "проколото" малюсенькое отверстие (в идеале это должна быть геометрическая точка, имеющая беско- бесконечно малый размерL. На внутренней стороне противоположной стенки ящика закрепляется фоточувствительная пластина. Совместим с этим отверстием начало прямоугольной системы координат. Ось - этой системы координат направим нормально к плоскости светочувстви- светочувствительной пластины. Обозначим через d расстояние по нормали между отверстием и светочув- светочувствительной пластиной. Рассматривая вид камеры сбоку (рис. 1.15), несложно вычислить ко- координаты (х, у, г) точки пересечения светового луча, прошедшего через отверстие, с плоско- плоскостью z = -d светочувствительной пластины. Используя подобие двух треугольников на рис. 1.15, получим, что световой луч, исходящий из точки с координатой у на объекте, попа- попадет на световую пластину в точке с координатой ур\ У,=~ У z/d Анализируя таким же образом вид камеры сверху, получим и значение координаты: х =—¦ z/d Рис. 1.14. Камера-обскура 4 Отсюда и название этого прибора на английском языке — pinhole camera, т.е. камера с отверстием, проколотым иглой. — Прим. перев. 40 Глава 1. Графические системы и модели
Точка (xf>, ур, -d) называется проекцией точки (х, у, г). В нашей идеализированной модели цвет точки на плоскости светочувствительной пластины будет таким же, как и цвет точки {х, у, z). Поле, или угол зрения, камеры — это угол, характеризующий наибольший объект, изображение которого умещается на светочувствительной пластине, полностью занимающей заднюю стенку камеры. Этот параметр можно вычислить на основании геометрической мо- модели, изображенной на рис. 1.1 б5. Если обозначить через h высоту светочувствительной пла- пластины, то угол зрения 6 будет равен 9 = 2 arctg—. 2d (УР< - ) t 1 — -. 2i» Рис. 1.15. Камера-обскура (вид сбоку) Рис. 1.16. Угол зрения камеры-обскуры Идеальная камера-обскура будет иметь бесконечную глубину резкости, т.е. все видимые точки изображения будут в фокусе. Изображением точки будет опять же точка. Камера- обскура имеет два недостатка. Во-первых, бесконечно малый размер отверстия (в идеале че- через это отверстие должен пройти только один луч, исходящий из некоторой точки на объек- объекте) приводит к тому, что на светочувствительную пластину попадает очень мало света. Во- вторых, угол зрения камеры определенного размера никак не может быть изменен. В принципе, современный фотоаппарат отличается от камеры-обскуры только тем, что вместо точечного отверстия в нем используется система линз (объектив). Но это позволяет устранить оба отмеченных недостатка. Во-первых, объектив позволяет "собрать" значительно больше света — чем больше апертура объектива, тем больше световой энергии проходит через него. Во-вторых, угол зрения фотокамеры определяется фокусным расстоянием используемого объектива, а его можно изменять, меняя объективы либо применяя объектив с регулируемым фокусным расстояни- расстоянием. Современные объективы позволяют увеличить угол зрения почти до 180°. Но, к сожалению (а фотохудожники говорят "к счастью"), объективы не обеспечивают бесконечной глубины резкости. В данной главе мы будем рассматривать только идеальную модель камеры-обскуры, для которой фокусным расстоянием является расстояние d от передней до задней стенки камеры. В компьютерной графике также в большинстве случаев не моделируется эффект ограничен- ограниченной глубины резкости. 1.6. Моделирование камеры Рассматриваемые модели оптических систем формирования изображения непосредствен- непосредственно подводят нас к фундаментальным концепциям современной трехмерной компьютерной графики. Посмотрим, каким образом можно алгоритмически синтезировать изображение, по- ¦ Если рассматривать эту задачу в трехмерной, а не в двухмерной постановке, то следует вместо пара- параметра h подставить размер светочувствительной пластины по диагонали. См. упр. 1 в конце данной главы. 1.6. Моделирование камеры 41
добное тому, которое реально создается в оптической системе. Эта парадигма получила в ли- литературе название модель синтезированной камеры {synthetic-camera model). Рассмотрим систему формирования изображения, представленную на рис. 1.17. На ней мы снова видим объект и наблюдателя. В данном случае наблюдателем является камера с регулируемым рас- расстоянием между объективом и плоскостью светоприемника (пластины или пленки). Наша цель — эмулировать процесс создания изображения на плоскости светоприемника. Рис. 1.17. Система формирования изо- изображения Во-первых, отметим, что спецификация (формальное описание) объекта не зависит от спецификации наблюдателя. Следовательно, при организации графической библиотеки мож- можно отделить функции описания объекта от функций описания наблюдателя. Во-вторых, параметры изображения можно вычислить, используя простейшие тригоно- тригонометрические соотношения. Рассмотрим плоскость светоприемника камеры и объект (рис. 1.18). Схема в левой части рисунка абсолютно аналогична той, которую мы рассматри- рассматривали при анализе камеры-обскуры. В правой части рисунка приведена схема, в которой плос- плоскость изображения передвинута и расположена перед объективом камеры, что соответствует размещению элементов сцены на рис. 1.19. Для построения изображения некоторой точки объекта мы проводим проецирующий луч от этой точки к центру объектива — центру про- проецирования (center of projection). Обратите внимание на то, что все проецирующие лучи схо- сходятся в центре проецирования. В нашей синтезированной камере плоскость светоприемника, которую мы передвинули и разместили перед объективом, называется картинной плоско- плоскостью, или плоскостью проекции {projection plane). Изображение точки размешается в точке пересечения проецирующего луча с картинной плоскостью. В главе 5 этот процесс рассмат- рассматривается подробно с использованием соответствующего математического аппарата. Камера о) б) Рис. 1.18. Схема формирования изображения: а — изображение формируется на задней поверхности камеры; б — изображение формируется перед объективом камеры 42 Глава 1. Графические системы и модели
Рис. 1.19. Синтезированная камера Следует принять во внимание и конечные размеры изображения. Как мы видели, из-за ог- ограниченности угла зрения камеры-обскуры на плоскость светоприемника попадает изображе- изображение отнюдь не всех объектов. В нашей синтезированной камере аналогичное ограничение можно смоделировать, разместив на плоскости проекции отсекающий прямоугольник (clipping rectangle), или отсекающую рамку (clipping window) (рис. 1.20). Этот прямоуголь- прямоугольник действительно играет роль рамки окна, через которое наблюдатель, находящийся в цен- центре проекции, смотрит на окружающий мир. Задав положение центра проекции, положение и ориентацию плоскости проекции и размеры отсекающего прямоугольника, можно однознач- однозначно определить, какие объекты появятся на сформированном изображении. а) 6) Рис. 1.20. Отсечение: а — исходное положение рамки; б смещена рамка 1.7. Интерфейс программиста Существует множество способов взаимодействия пользователя с графической систе- системой. В современных системах автоматизации проектирования пользователь создает изо- изображение, взаимодействуя с системой отображения посредством устройств ввода ин- информации, таких как мышь и клавиатура. В типичной системе построения чертежей (рис. 1.21) для представления возможных операций используются меню и панели инст- 1.7. Интерфейс программиста 43
рументов. Щелкая на тех или иных элементах графического интерфейса, пользователь активизирует соответствующие функции и формирует изображение, не прибегая к соб- собственно программированию. Рис. 1.21. Графический интерфейс программы вычерчивания Естественно, перед этим кто-то должен разработать программы этого приложения, и мно- многие из тех, кто держит в руках эту книгу, будут самостоятельно создавать такого рода про- программы (и даже получать от этого удовольствие), несмотря на наличие на рынке огромного количества программных продуктов аналогичного назначения. 1.7.1. Интерфейс прикладного программирования Интерфейс между прикладной программой и графической системой — это множество функций, которые в совокупности образуют графическую библиотеку. Спецификация этих функций и есть то, что мы называем интерфейсом прикладного программирования (API — application programmer's interface). Модель системы прикладного программирования показана схематически на рис. 1.22. Для программиста, занимающегося разработкой прикладной про- программы, существует только API, и он избавлен, таким образом, от необходимости вникать в подробности работы аппаратуры и программной реализации функций графической библиоте- библиотеки. С точки зрения прикладного программиста те функции, к которым он обращается через API, должны соответствовать концептуальной модели описания изображения. Основой для этого часто является описанная выше модель синтезированной камеры. Она используется во множестве разнообразных API, таких как OpenGL, PHIGS, Direct3D, VRML и JAVA-3D. Клавиатура Прикладная программа Мышь ЭЛТ-монитор Рис 1.22. Структура прикладной графической системы 44 Глава 1. Графические системы и модели
Следуя концепции модели синтезированной камеры, в составе API должны присутство- присутствовать функции, которые позволяли бы описывать: ¦ объекты; ¦ наблюдателя; ¦ источники света; ¦ свойства материалов объектов. Для описания объектов чаще всего используются массивы вершин. Для простых геомет- геометрических объектов — отрезков прямых, прямоугольников и многоугольников — существует достаточно очевидное соответствие между списком вершин и формой объектов, основанное на простых математических соотношениях. Более сложные объекты могут быть по-разному определены с помощью списка вершин. Например, круг можно определить тремя точками (вершинами) на окружности или центром и одной точкой на окружности. В большинстве API в распоряжение пользователя предоставляется практически один и тот же набор примитивов. Такие примитивы обычно довольно быстро отображаются аппа- аппаратными средствами. Типовой набор включает точки, отрезки прямых, многоугольники и иногда текст. В OpenGL примитивы описываются списком вершин. Ниже приведен фраг- фрагмент программного кода на OpenGL, в котором с помощью вызова пяти функций API постро- построен многоугольник с тремя вершинами: glBegin(GL_POLYGON); glVertex3f@.0, 0.0, 0.0); glVertex3f@.0, 1.0, 0.0); glVertex3f@.0, 0.0, 1.0); glEnd( ); Обращаю ваше внимание на то, что, добавив в список дополнительные вершины, можно описать произвольный многоугольник. Изменив параметр типа GL_POLYGON, можно на тех же самых вершинах построить иной примитив. Например, примитив типа GL LINE_STRIP соот- соответствует двум связанным отрезкам прямых, проходящих через те же точки, а примитив типа GL_POINTS сформирует изображения трех точек. Некоторые API позволяют пользователю работать напрямую с буфером кадра— считы- считывать и записывать коды засветки отдельных пикселей. Иногда в число примитивов включа- включаются отрезки кривых и участки поверхностей, хотя чаще такие объекты приходится аппрок- аппроксимировать более простыми примитивами, причем эта задача возлагается на саму приклад- прикладную программу. В OpenGL поддерживается доступ к отдельным пикселям буфера кадра, а также создание криволинейных отрезков и участков поверхностей. Описать наблюдателя или камеру можно разными способами. Доступные на сегодняшний день графические API отличаются как гибкостью, которую они обеспечивают при выборе па- параметров камеры, так и количеством имеющихся в распоряжении пользователя методов ее описания. Для камеры, представленной на рис. 1.23, существует четыре типа параметров, од- однозначно определяющих характеристики создаваемого ею изображения. 1. Положение камеры задается положением центра проекции (ЦП). 2. Ориентация. Расположив центр проекции в определенной точке пространства, можно совместить с ним начало локальной системы координат камеры и вращать ее относи- относительно осей этой системы координат, изменяя таким образом ориентацию объектива. 3. Фокусное расстояние объектива камеры фактически определяет размер изображения на плоскости проекции. 1.7. Интерфейс программиста 45
4. Плоскость светоприемника. Задняя стенка камеры имеет конечные размеры в высоту и ширину. Некоторые API позволяют настраивать ориентацию плоскости светоприем- светоприемника независимо от ориентации объектива. Такую спецификацию можно сформировать разными способами. Один из них состоит в том, что положение и ориентация камеры задаются набором координатных преобразований. Эти координатные преобразования используются для вычисления положения точек объектов, заданных вершинами, в системе координат камеры. Такой подход очень удобен и с точки зрения его программной реализации, и с точки зрения гибкости графической системы, по- поскольку позволяет довольно просто формировать различные виды объекта, "перемещая" ка- камеру. Именно такой подход мы и будем использовать в дальнейшем, начиная с главы 5. Необходимость настройки значений множества параметров также представляет определен- определенную сложность. Частично причины этой проблемы кроются в самой модели синтезированной камеры. В классических методах визуализации, применяемых, в частности, в архитектурном проектировании, акцент делается именно на взаимосвязи наблюдателя и среды, в то время как модель предполагает независимость наблюдателя и наблюдаемых объектов. Так, классическая двухточечная перспектива куба на рис. 1.24 формируется именно вследствие учета взаимосвязи наблюдателя и плоскостей граней куба (см. упр. 1.6). API OpenGL позволяет создавать преобра- преобразования самого разного вида и обеспечивает программисту возможность использовать некото- некоторые нетрадиционные функции. Рассмотрим, например, обращение к двум функциям: gluLookAt(cop_x, cop_y, cop_z, at_x, at_y, at_z,...)J glPerspective(field_of_view, . . .); Вызов первой функции ориентирует камеру в направлении указанной точки пространства, а вызов второй — устанавливает параметры объектива для формирования изображения с уче- учетом перспективы. Но ни одна из существующих графических библиотек, построенных на ос- основе концепции синтезированной камеры — OpenGL, PH1GS, VRML, — не имеет в своем со- составе функций для задания взаимосвязей между камерой и объектом. Рис. 1.23. Параметры спе- спецификации камеры Рис. 1.24. Двухточечная перспектива куба Источник света характеризуется положением, интенсивностью и цветом излучения и его направленностью. В составе большинства API имеются функции для задания такого рода па- параметров, причем в сцене может присутствовать несколько источников света с разными ха- характеристиками. Существуют и функции спецификации оптических свойств материалов по- поверхностей объектов. Эти функции вызываются на этапе создания моделей объектов. Набор программируемых свойств источников света и материалов определяется принятой в данном API моделью взаимодействия света с окружающей средой. Подробно такие модели рассмат- рассматриваются в главе 6. 46 Глава 1. Графические системы и модели
1.7.2. Парадигма "моделирование-тонирование" В процессе формирования изображения можно следовать и другому подходу: во многих САПР-приложениях и программах создания фотореалистических изображений, например для включения в кинофильм, моделирование сцены и ее тонирование (rendering) представляют со- собой две последовательные стадии процесса (рис. 1.25). Хотя решаемые задачи и не отличаются от тех, которые мы рассматривали выше, такая организация позволяет разделить программные и аппаратные средства реализации этих двух стадий. Например, рассмотрим процесс создания од- одного кадра мультипликационного фильма. Сначала определяются компоновка кадра и располо- расположение в нем персонажей и объектов фона. Этот процесс требует активного участия художника- мультипликатора, но на первом этапе воспроизводить все детали изображения, связанные с оп- оптическими эффектами, нет необходимости. Следовательно, этот этап предпочтительнее выпол- выполнять на специализированной рабочей станции, располагающей средствами активного диалога с пользователем. После того как сцена будет скомпонована, можно приступать к ее раскрашива- раскрашиванию. На этой стадии используется все многообразие средств моделирования оптических эффек- эффектов — освещения, фактуры поверхностей объектов и т.п. Этот этап значительно меньше связан с вмешательством пользователя, но требует больших объемов вычислений, а потому для его реализации имеет смысл использовать высокопроизводительные компьютеры. Не только аппа- аппаратные, но и программные средства реализации этих этапов разительно отличаются, а потому грамотное разделение процесса может существенно повысить его эффективность. Моделирование Промежуточный файл Закрашивание Рис. 1.25. Двухэтапный процесс "моделирование-то- "моделирование-тонирование" Связь между компонентами системы, которые отвечают за моделирование и тонирование, осуществляется с помощью промежуточного файла. В структуре такого файла предусматривается возможность хранения как описания объектов сцены, созданных в результате моделирования, так и источников света, информации о положении наблюдателя, свойствах материалов и т.п. Этот под- подход реализован в формате Renderman Interface, который позволяет передать информацию о модели в текстовом формате. У этого подхода есть и другое немалое достоинство — он позволяет оптими- оптимизировать структуру и функциональные возможности того компонента системы, который отвечает за моделирование, в соответствии со спецификой конкретного приложения. При этом второй ком- компонент может быть использован без изменений для совершенно разных графических приложений. Возможен и другой вариант, когда промежуточный файл может обрабатываться разными про- программами тонирования, которые имеют разные функциональные возможности и работают на раз- разных моделях компьютеров. В принципе, по крайней мере теоретически, при таком подходе можно вообще не использовать программу моделирования, а описать всю сцену в текстовом виде с помо- помощью обычного текстового редактора. Но, конечно, на практике удобнее использовать интерактив- интерактивные программы моделирования, в которых применяется все та же модель синтезированной камеры для создания изображений, хоть и не раскрашенных. 1.8. Архитектура графических систем В схеме, приведенной на рис. 1.22, по одну сторону API располагается прикладная про- программа, а по другую — комбинация программных и аппаратных средств, которая реапизует функции, специфицированные в API, и формирует изображение на экране. В течение послед- 1.8. Архитектура графических систем 47
них 40 лет было предложено множество вариантов структурной организации средств, реали- реализации функций графического API. В первых графических системах использовались вычислительные машины общего назна- назначения со стандартной архитектурой, предложенной еще фон Нейманом. В таких вычисли- вычислительных машинах был один процессор, который в каждый момент времени "исполнял" одну инструкцию. В простейшем виде такая структура представлена на рис. 1.26. В качестве уст- устройства отображения использовалась ЭЛТ с необходимым аппаратным обрамлением — все вместе это именовалось дисплеем. Дисплей формировал на экране отрезки прямых между двумя заданными точками или дуги по трем точкам. Главный (и единственный в такой систе- системе) компьютер выполнял прикладную программу и вычислял координаты характеристиче- характеристических точек примитивов в системе координат экрана. Эта информация передавалась в дисплей с достаточно высокой скоростью, чтобы избежать мелькания изображения на экране. В те не столь уж далекие времена скорость вычислений была настолько низкой, что вывод на экран даже пары сотен отрезков требовал использования практически всех вычислительных ресур- ресурсов машины. Главный компьютер Цифро-аналоговый преобразователь Рис. 1.26. Структура ранних графических систем 1.8.1. Дисплейные процессоры Первые попытки создания специализированных графических систем в первую очередь преследовали цель освободить главный компьютер от рутинных операций регенерации изо- изображения на экране дисплея. В состав дисплея включили "собственный" дисплейный процес- процессор (рис. 1.27), который взял на себя задачу регенерации. Дисплейный процессор чаще всего имел ту же архитектуру, что и процессор общего назначения, но дополнительно в его систему команд были включены и инструкции формирования графических примитивов. Главное пре- преимущество такой структурной организации всей системы состояло в том, что от главного компьютера требовалось только сформировать описание изображения и передать его в виде дисплейного файла в дисплейный процессор. Последний сохранял дисплейный файл в собст- собственной памяти и считывал его с частотой регенерации, освобождая таким образом главный компьютер от рутинной работы. Этот вариант архитектуры в общих чертах воспроизведен в современных графических системах со звучным названием "клиент/сервер", которые рас- рассмотрены в главе 3. Главный компьютер Рис. 1.27. Структура графической системы с дис- дисплейным процессором 48 Глава 1. Графические системы и модели
1.8.2. Конвейерная архитектура Совершенствование архитектуры графических систем шло параллельно с совершенство- совершенствованием аппаратного обеспечения рабочих станций. В обеих областях существенный прогресс наметился после появления на рынке специализированных интегральных микросхем сверх- сверхбольшой степени интеграции (СБИС). Кроме того, радикальное снижение стоимости инте- интегральных запоминающих схем привело к повсеместному переходу на растровый принцип создания изображения в системах компьютерной графики. С точки зрения задач компьютерной графики наибольшее значение имеет возможность реа- реализовать в специализированных СБИС конвейерный принцип обработки информации. Схема на рис. 1.28 иллюстрирует этот принцип на примере простейших арифметических вычислений. Рис. 1.28. Арифметический конвейер Эта простейшая конвейерная структура состоит из сумматора и умножителя. Пусть необхо- необходимо вычислить значения выражений вида а+(Ьхс), что требует одной операции сложения и од- одной операции умножения на каждое очередное выражение. Если вычисляется одно выражение, то время вычислений в такой структуре будет таким же, как и в единственном процессоре, ис- использующем электронику с теми же динамическими характеристиками. Но если нужно выпол- выполнить подряд несколько однотипных операций с разными значениям операндов a, b и с, то умно- умножитель, выполнив умножение пары операндов первого набора, передаст результат на сумматор, и пока последний будет выполнять сложение, успеет выполнить умножение второй пары опе- операндов. В результате время выполнения всей операции сократится вдвое (если, конечно, умно- умножитель и сумматор выполняют свою работу с одинаковой скоростью). Таким образом, инте- интегральная пропускная способность системы возрастает (в данном случае удваивается). По этому же принципу можно создать и конвейеры с более сложной структурой, которые позволяют еще больше повысить пропускную способность специализированного вычислителя. Естественно, применять подобный конвейер имеет смысл только в том случае, если необходимо многократно выполнять вычисления по одним и тем же формулам с разными данными. Но в за- задачах компьютерной графики мы имеем именно такой случай — нужно многократно обрабаты- обрабатывать по одним и тем же формулам список вершин, характеризующих отображаемые объекты. Предположим, что имеется множество вершин, определяющих графические примитивы, из которых формируется изображение. Поскольку все объекты представлены в терминах ко- координат положения точек в пространстве, можно рассматривать множество типов примити- примитивов и вершин как геометрические данные. Сложная сцена описывается тысячами, если не миллионами, вершин. Все их нужно обработать по одному алгоритму и в результате сформи- сформировать в буфере кадра описание растра. Если рассматривать этот процесс в терминах геомет- геометрических операций с исходными данными, то можно представить его в виде схемы рис. 1.29. На этой схеме показаны четыре основных этапа процесса: ¦ геометрическое преобразование; ¦ отсечение; ¦ проективное преобразование; ¦ растровое преобразование. 1.8. Архитектура графических систем 49
Вершины—»- Геометрическое 1^ Отсечение |—>¦":Пр™™^:Ц_^^?<Юр<*<»^Л^ п н преобразование! I преобразование!преобразование! Рис. 1.29. Структура геометрического конвейера В последующих разделах мы обсудим детали реализации каждого из этих этапов. Но главный вывод, который следует из всего изложенного, состоит в том, что если для всех объ- объектов сцены выполняются единообразные преобразования, то быстрее всего можно реализо- реализовать этот процесс с помощью специализированного процессора с конвейерной архитектурой. 1.8.3. Геометрические преобразования Большинство этапов обработки графической информации можно описать в форме гео- геометрических преобразований представления объектов сцены в разных системах координат. Например, рассматривая модель синтезированной камеры, приходим к выводу, что основная часть процесса визуализации представляет собой преобразование представления объектов из базовой (мировой) системы координат в систему координат камеры. Другой пример — стадия размещения изображения на поле экрана ЭЛТ или другого устройства вывода. Внутреннее представление геометрических объектов — будь то в системе координат камеры или в любой другой подходящей системе координат, используемой в графическом API, — должно быть преобразовано на этой стадии в представление в системе координат устройства отображения. Каждое такое преобразование можно представить в матричной форме, причем последова- последовательные преобразования выражаются перемножением {конкатенацией — concatenating) со- соответствующих матриц элементарных преобразований. В результате формируется матрица комплексного преобразования. В главе 4 будет подробно рассмотрен математический аппарат описания геометрических преобразований. Поскольку произведение двух матриц есть также матрица, этот математический метод является прекрасным кандидатом для реализации в форме конвейерных вычислений. Кроме того, поскольку матрицы преобразований в компью- компьютерной графике имеют небольшие размеры Dx4), сам процесс умножения матриц также можно распараллелить в блоке, который выполняет эту операцию. 1.8.4. Отсечение Вторая важная операция в графическом конвейере— отсечение (clipping). Необходи- Необходимость в ней возникает по той простой причине, что имеющиеся в нашем распоряжении сред- средства отображения сами по себе имеют конечные размеры. Сетчатка глаза человека имеет ог- ограниченный угол зрения примерно 90°, а наша синтезированная камера (как, впрочем, и ре- реальный фотоаппарат) — ограниченное поле "фотоприемника". В реальной фотокамере поле (угол) зрения можно настраивать, подбирая объективы с разным фокусным расстоянием (или перестраивая фокусное расстояние объектива-трансфокатора). Этот процесс в синтезируемой камере моделируется изменением размеров (а иногда и положения) отсекающей прямоуголь- прямоугольной рамки {clipping rectangle) на плоскости проекции (см. рис. 1.20). Объекты, проекция ко- которых попадает во внутреннюю область отсекающей рамки, "участвуют" в формировании изображения. Те объекты, проекции которых пересекают отсекающую рамку, будут частично видимы. Отсечение выполняется на разных этапах формирования изображения. Отсечение геомет- геометрических примитивов можно выполнить, анализируя только координаты, и, следовательно, этот процесс несложно встроить в геометрический конвейер. Сам процесс отсечения можно детализировать и разбить на последовательность элементарных операций, также поддающих- поддающихся распараллеливанию, а следовательно, совместимых с принципом конвейерной обработки (см. упр. 1.4 и 1.5). Эффективные алгоритмы отсечения будут рассмотрены в главе 7. 50 Глава 1. Графические системы и модели
1.8.5. Проективное преобразование Как правило, при обработке геометрической информации трехмерное описание объектов стараются сохранить как можно дольше по мере продвижения "по конвейеру". Но после стадий геометрических преобразований и отсечения неизбежно наступает момент, когда те объекты, которые попадают в поле видимости, нужно преобразовать из трехмерной формы в двухмер- двухмерную. Существует множество видов проективного преобразования, которые мы рассмотрим в главе 5. Некоторые из них позволяют использовать математический аппарат операций с матри- матрицами размером 4x4 и, следовательно, могут быть реализованы в том же самом конвейере. 1.8.6. Растровое преобразование Последний этап процесса — преобразование описания двухмерных объектов в коды засветки пикселей в буфере кадра. Как выполняется эта операция для стандартных примитивов — линий и многоугольников, — будет подробно рассмотрено в главе 7. Поскольку регенерация изображения выполняется аппаратно, этот процесс практически скрыт от прикладного программиста и можно считать, что последняя операция геометрического конвейера — это растровое преобразование. 1.8.7. Производительность работы геометрического конвейера В рассматриваемой структуре обработки геометрической информации используются опе- операции двух типов. На начальных стадиях выполняются операции со значениями координат вершин, представленными в форме чисел с плавающей точкой. Эти операции идеально под- подходят для реализации в конвейерной вычислительной структуре. Разработанный фирмой Sili- Silicon Graphics геометрический процессор на базе СБИС стал базовым элементом большинства графических рабочих станций. Для операций с 4х4-матрицами преобразования в нем исполь- используются специальные микросхемы арифметических расширителей, подобные Intel i860. В ре- результате программа перемножения двух матриц сводится к единственной инструкции. В гра- графических рабочих станциях и графических платах для профессиональных ПК используются специализированные СБИС, которые берут на себя выполнение большинства необходимых преобразований и выполняют их аппаратно с огромной скоростью. Хотя в подавляющем большинстве профессиональных графических рабочих станций ис- используется конвейерный принцип обработки, следует учитывать, что по мере добавления в конвейер новых операций время реакции системы увеличивается, и нужно найти оптимальное сочетание времени реакции и функциональных возможностей системы. Все стадии обработки после растрового преобразования требуют выполнения побитовых операций на уровне содержимого буфера кадра. Характер этих операций существенно отличает- отличается от характера вычислений с матрицами, выполняемых при геометрических и проективных преобразованиях. Чаще всего при работе с буфером кадра приходится выполнять перемещение содержимого участка памяти. Общая производительность системы определяется скоростью "перемещения" геометрических примитивов по конвейеру и количеством пикселей, которые могут быть изменены в буфере кадра за определенное время. Поэтому наиболее производитель- производительные графические рабочие станции используют СБИС с конвейерной архитектурой для выполне- выполнения преобразований трехмерных и двухмерных геометрических объектов и параллельные бито- битовые процессоры на конечной стадии обработки изображения в растровой форме. Конвейерная архитектура занимает доминирующее положение среди существующих на сегодняшний день структур аппаратных средств графических систем, в особенности тех из них, которые должны формировать динамические изображения в реальном масштабе време- времени. Но следует отметить, что сам по себе принцип распараллеливания вычислений годится и для организации программного обеспечения при реализации высокопроизводительных гра- графических пакетов API. Причем в обоих случаях — аппаратной и программной реализации — используются возможности, предоставляемые все той же моделью синтезированной камеры. 1.8. Архитектура графических систем 51
1.9. Резюме В этой главе описаны основные стадии разработки графических приложений, как они представляются при использовании нисходящего подхода к проектированию и обучению. Имея перед собой общую картину, читатель сможет приступить к разработке простейших графических программ уже при изучении материала следующей главы. Мы делаем акцент на том, что в компьютерной графике главную роль играют методы, ос- основанные на тех же физических принципах формирования изображения, которые действуют и в реальном мире, в частности в оптических системах, таких как фотокамера или глаз чело- человека. В главе 2 будут рассмотрены вопросы ввода цвета в изображение, и вы увидите, что по- понимание основных принципов функционирования зрительной системы человека позволяет искусственно создавать изображения, воспринимаемые наблюдателем как реальные. В этой главе мы рассмотрели в общих чертах три различные парадигмы формирования изображения с помощью компьютера. Описанная модель синтезируемой камеры имеет два очень важных для практики следствия. Во-первых, она предполагает независимость изобра- изображаемых объектов и наблюдателя, что влияет на способ организации библиотеки графических функций. Во-вторых, описанная модель открывает путь к использованию конвейерных прин- принципов построения системы компьютерной графики. В этой главе мы также познакомили читателей с идеей трассировки лучей при построении изображения сцены. Эта парадигма играет существенную роль в понимании процессов взаимо- взаимодействия света и материала, на которой основан процесс формирования физического изображе- изображения. Метод трассировки лучей требует альтернативного варианта организации системы компь- компьютерной графики, и мы попытались сделать набросок такого варианта системы. Однако уже по- поверхностный анализ показывает, что на этом пути нас ожидают весьма серьезные трудности, поскольку реально только малая часть всех лучей участвует в формировании изображений, а время, ушедшее на анализ большей их части, оказывается потраченным впустую. В главе 6 мы вернемся к этому методу и расскажем о способах повышения его эффективности. Далее в этой главе было показано, какое важное значение имеет парадигма "моделиро- "моделирование-тонирование". В современной графической системе с конвейерной организацией в 1 секунду формируются миллионы отрезков прямых или многоугольников с разрешением до 1280x1024 пикселей. Такая система успевает выполнять в реальном масштабе времени удале- удаление невидимых поверхностей и тонирование примитивов, но в последней процедуре прихо- приходится использовать упрощенные алгоритмы, поскольку на большее не хватает производи- производительности аппаратных средств. Если стоит цель сформировать картинку, не уступающую по качеству той, которая получается при съемке кинофильма "на натуре", необходимо добиться разрешения порядка 4000x6000 пикселей и учитывать характеристики источников света и материалов отображаемых объектов, причем все расчеты должны выполняться с такой ско- скоростью, чтобы воспроизводить динамическую сцену в реальном масштабе времени. Несмотря на непрерывное возрастание производительности аппаратных и программных средств, ис- используемых в системах компьютерной графики, принцип разделения фаз моделирования и тонирования будет и в будущем сохранять свою актуальность. В этой же главе мы затронули и аспекты реализации прикладной части системы. Было по- показано, что API OpenGL, поддерживаемый на большинстве существующих аппаратных и про- программных платформ, является мощным средством изучения принципов формирования изо- изображения и построения прикладных систем. 1.10. Рекомендуемая литература На сегодняшний день опубликовано множество прекрасных книг по компьютерной гра- графике. Первое место в этом ряду занимает книга Ньюмена (Newman) и Спрулла (Sproull) [New73J, в которой впервые с современных позиций была описана модель синтезированной 52 Глава 1. Графические системы и модели
камеры. В следующем десятилетии ни одна работа по компьютерной графике не обходилась без ссылки на книги Фоли (Foley) и соавторов [Fol90, Fol94]. Среди других книг, на которые я хотел бы обратить внимание читателей, работы Херна (Неагп) и Бейкера (Baker) [Hea94] и Хилла (Hill) [HU90]. В своей работе Фоли, так же, как, впрочем, и Херн с Бейкером, исполь- использовали API PHIGS, а материал книги Анджела [Ang90] базируется на API GKS. Я рекомендую всем читателям регулярно просматривать журналы Computer Graphics, ко- который ежеквартально выпускается SIGGRAPH (Группа по компьютерной графике в составе Ассоциации по вычислительной технике —Association for Computing Machinery's Special In- 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 [Sut63J, явилась, возможно, тем ростком, из которого выросло дерево современной компьютерной графики. Именно Сазерленд первым осознал, какие широкие перспективы открывает воз- возможность взаимодействия пользователя с компьютером через систему отображения на осно- основе ЭЛТ. Видеофильм о его оригинальной работе до сих пор пользуется популярностью. В книгах Тафта (Tufte) [TuJ83, Tuf?0, Tuf97] описана история развития компьютерной графики. В статье Карлбома (Carlbom) и Пасьорека (Paciorek) [Car78] обсуждается связь ме- между классической теорией зрения, которая используется испокон веков в архитектуре, и ме- методами визуализации, используемыми в компьютерных приложениях. Существует огромное множество книг, описывающих зрительную систему человека. Кни- Книга Пратта (Pratt) [Pra78] посвящена принципам создания растровых изображений. Я бы сове- советовал также познакомиться с работами Гласснера (Glassner) [Gla95], Визецкого (Wyszecki) и Стайлса (Stiles) [Wys82] и Холла (Hall) [Hal89]. Упражнения 1.1. Фокусное расстояние линзы в фотокамере— это расстояние между центром линзы и той точкой, в которой сходятся параллельные лучи после их преломления линзой. В ка- камере-обскуре фокусное расстояние измеряется как расстояние между передней и задней стенками камеры. Размер кадра на 35-миллиметровой пленке примерно равен 24x36 мм. Будем считать, что глаз человека характеризуется углом зрения 90°. Каково должно быть фокусное расстояние линзы в фотокамере, использующей 35-миллиметровую пленку, чтобы изображение на ней было подобно тому, которое видит глаз человека? 1.2. В компьютерной графике криволинейные поверхности, например сферическая, ап- аппроксимируются объектами, состоящими из множества плоских многоугольников (многогранниками). Первым объектом такого рода являет тетраэдр, четыре грани которого являются треугольниками. Отыщите координаты всех его вершин, пола- полагая, что начало системы координат находится в центре аппроксимируемой сферы, а одна из вершин располагается на оси>>. Разработайте алгоритм, позволяющий полу- получать все более точную аппроксимацию сферической поверхности правильными многогранниками, используя последовательное деление граней тетраэдра. 1.3. Рассмотрите отсечение линейного отрезка, заданного крайними точками. Покажите, что, для того чтобы определить, рассекается ли отрезок прямоугольной рамкой, весь он видим или весь невидим, достаточно знать только положение его крайних точек. Упражнения 5 3
1.4. Покажите, что отсечение прямолинейного отрезка верхней стороной отсекающей рамки может быть выполнено независимо от отсечения прочими сторонами рамки. На основании полученных результатов покажите, как можно распараллелить про- процесс отсечения в системе с конвейерной архитектурой, имеющей четыре независи- независимо работающих блока. 1.5. Распространите методы, использованные при выполнении упр. 1.3 и 1.4, на трех- трехмерный случай. 1.6. Рассмотрите перспективные изображения куба, представленные на рис. 1.30. Изо- Изображение, представленное в левой части рисунка, называется одноточечной пер- перспективой (one-point perspective), поскольку параллельные линии — ребра верхней грани — на изображении пересекаются в одной точке схода (vanishing point). В правой части рисунка показана двухточечная перспектива (two-point perspective) того же куба. Подумайте над тем, какие отношения существуют между наблюдате- наблюдателем (например, камерой) и объектом в обоих случаях. Рис. 1.30. Перспективные изображения куба 1.7. Память, используемая для хранения растрового представления изображения, долж- должна иметь быстродействие, достаточное для того, чтобы выведенное на экран изо- изображение не мелькало. В типовой графической рабочей станции разрешение экрана составляет 1280x1024 пикселей. Если изображение регенерируется 72 раза в секун- секунду, каково должно быть быстродействие памяти буфера кадра? Иными словами, сколько времени должно занимать считывание из буфера кода светимости одного пикселя? Каково будет значение этого параметра для дисплея с разрешением 480x640 пикселей при частоте регенерации 60 Гц и чересстрочной развертке. 1.8. В течение одной секунды кинозрителю последовательно "предъявляется" 24 кадра кинофильма. В принципе, эта частота не позволяет избежать мелькания изображе- изображения на экране. Каким образом удается избежать мелькания в кинопроекторе? (Подсказка: зрителю кажется, что он видит 48 кадров в секунду.) 1.9. Подумайте над тем, как организовать двухмерный графический пакет API для опре- определенного класса приложений, например проектирования СБИС. Перечислите все примитивы и атрибуты, которые необходимо включить в состав такого API. 1.10. Можно придумать такую конструкцию цветной ЭЛТ, в которой будет только одна электронная пушка и соответственно отпадет необходимость в теневой маске. Единственный электронный луч будет включаться и выключаться в нужные момен- моменты времени и таким образом возбуждать свечение того люминофора, который ну- нужен. Почему управлять такой ЭЛТ сложнее, чем ЭЛТ с теневой маской? 1.11. В типовой ЭЛТ с теневой маской для получения изображения с плавными цветопе- реходами размер пикселя должен быть примерно втрое больше размера триады. Предположим, что монитор должен иметь разрешение 1280x1024 пикселей, диа- диаметр экрана ЭЛТ равен 50 см, а глубина ЭЛТ— 25 см. Рассчитайте расстояние ме- между отверстиями в теневой маске. 54 Глава 1. Графические системы и модели
ГЛАВА 2 Графическое программирование Используемый в этой книге подход к изложению проблематики компьютерной графики ориентирован на программирование, и, следуя ему, я стараюсь вовлечь читателя в процесс программирования как можно раньше. Поэтому в данной главе будет описан минимальный набор функций в составе графического API, который достаточен для програм- программирования довольно широкого круга задач двух- и трехмерной графики и поможет читателям усвоить основные концепции построения графических программ. Двухмерная графика в данной книге рассматривается как частный случай трехмерной графики. Это позволяет нам начать практическую работу еще до детального знакомства с концепциями построения изображений трехмерных объектов. Те программы работы с двух- двухмерными объектами, которые читатели создадут с нашей помощью в процессе изучения ма- материала этой главы, затем без всяких изменений можно будет включать и в трехмерную гра- графическую систему. Изучая материал данной главы, мы будем анализировать разные аспекты достаточно простой и в то же время весьма информативной задачи — построения так называемого узора Серпинского (Sierpinski gasket). На примере этой задачи будет показано, как с по- помощью довольно небольшого набора функций можно строить сложные узоры. Как и бы- было обещано ранее, в качестве графического API будет использоваться OpenGL, но обсу- обсуждение основных концепций выходит далеко за рамки этого конкретного пакета и при- ложимо к большинству существующих графических систем, в том числе PHIGS (Programmer's Hierarchical Graphics System) и GKS (Graphical Kernel System). Те функ- функциональные возможности, с которыми читатель познакомится в этой главе, вполне дос- достаточны для разработки довольно сложных программ построения изображения двухмер- двухмерных объектов, которые, правда, не оснащены средствами взаимодействия с пользовате- пользователем. В конце этой главы читатели познакомятся с простой программой построения изображения трехмерных объектов.
2.1. Узор Серпинского Для демонстрации принципов программирования задач двухмерной графики мы будем использовать в качестве примера узор Серпинского. История исследования этого узора дос- достаточно длинная, а сам узор представляет особый интерес в такой области, как геометрия фракталов. Узор Серпинского является случайным объектом, определенным рекурсивно, но в пределе его форма стремится к детерминированному объекту. Предположим, что в исходном состоянии у нас есть три точки на плоскости, причем их положение описано координатами в некоторой подходящей системе координат1: {хь у{), (х2, уг) и (х2, уз). Далее выполняется следующая процедура. 1. Случайно выбирается некоторая точка внутри треугольника, образованного заданными вершинами. 2. Случайно выбирается одна из трех вершин. 3. Выбирается точка, равноотстоящая от первой выбранной точки и от выбранной вершины. 4. Новая точка включается в изображение и помечается маркером, например кружком. 5. Исходная точка заменяется этой новой точкой. 6. Повторяется вся процедура, начиная с шага 2. Таким образом, каждая новая точка, созданная на шаге 3, вклю- включается в изображение и выводится на устройство отображения. Весь описанный процесс проиллюстрирован на рис. 2.1, где р0— исход- исходная точка (см. шаг 1), a pi и р2— две точки, сформированные в процессе двух последовательных циклов описанного алгоритма. Прежде чем приступить к разработке программы, весьма желатель- желательно представить себе, какой вид будет иметь изображение, полученное в результате применения этого алгоритма. Попробуйте нарисовать его Рис. 2.1. Формирование вручную на листе бумаге — результат вас скорее всего удивит. узора Серпинского Ниже представлен возможный вариант графической программы построения узора. main( ) { initialize_the_system(); for(some_number_of_points) { pt = generate_a_point(); display_the_point(pt); } cleanup(); } Хотя в окончательном виде наша программа на OpenGL будет иметь несколько другой вид, но останется такой же простой. Процесс разработки программы будет прослежен по- поэтапно. Сначала сконцентрируем внимание на двух основных этапах — формирование точки и вывод ее на экран. Нужно ответить на два вопроса: "Как представить точки в пространст- пространстве?" и "Следует ли использовать двух-, трехмерное или другое представление?" В главе 4 мы рассмотрим эту же задачу в более общей постановке с использованием координатных фреймов. 56 Глава 2. Графическое программирование
2.1.1. Перьевой плоттер Большинство графических систем первого поколения были двухмерными. В них исполь- использовалась концепция модели перьевого плоттера (pen-plotter modelJ. Такое устройство фор- формирует изображение на бумаге за счет перемещения пера, закрепленного на двух подвижных направляющих (рис. 2.2). Одна направляющая перемещается в продольном направлении (вдоль оси у), а другая — в попе- поперечном (вдоль оси х). Специальный механизм поднимает и опускает перо. В опущенном состоянии перо при перемеще- перемещении оставляет след на бумаге — этот след и формирует изо- изображение. Такого рода устройства и поныне используются в рцс 22 Перьевой тоттер графических системах при построении технических черте- чертежей. Для программного управления графопостроителями применяются разнообразные пакеты API — такие как LOGO, GKS и PostScript. Хотя эти па- пакеты и отличаются друг от друга функциональными возможностями, но во всех используется та же самая идея формирования на носителе следа подвижного пера, т.е. фактически воспро- воспроизведения процесса ручного вычерчивания с помощью карандаша. Пользователь при этом имеет дело с участком плоскости, ограниченным размерами планшета. Поведение такой графической системы описывается двумя основными графическими функциями: moveto(x,y); lineto(x,y); При выполнении функции moveto() перо приподымается и затем перемещается в точку с координатами (х, у), не оставляя следа на носителе (при этом траектория перемещения не имеет значения — главное, что заканчиваться она должна в точке с заданными координата- координатами). При выполнении функции lineto() перо опускается и затем перемещается по прямой из текущего положения в точку с заданными координатами (х,у), оставляя след на носителе. Ес- Если добавить к этим функциям еще и функции настройки (выбор пера подходящего цвета и толщины), то получим законченную двухмерную графическую систему. Ниже приведен фрагмент программы вычерчивания несложного изображения с помощью такой системы: moveto@, 0); lineto(l, 0); lineto(l, 1); lineto@, 1); lineto@, 0); Эта программа формирует изображение, представленное на рис. 2.3,а. Добавив в про- программу еще несколько операторов, получим при ее выполнении изображение куба в изомет- изометрической проекции (рис. 2.3,6). moveto@, 1); lineto@.5, 1.866); linetoA.5, 1.866); linetoA.5, 0.866); lineto(l, 0); moveto(l, 1); lineto(l,5, 1.866); 2B me не столь давние времена в русскоязычной литературе использовался термин "графопостро- "графопостроитель ". — Прим. перев. 2.1. Узор Серпинского 57
а) 6) Рис. 2.3. Результат выполнения простой программы вычерчивания: а — квадрат; б — изометрическая проекция куба По этому принципу построены некоторые приложения, например системы компоновки страниц в полиграфии. Язык описания страниц PostScript является стандартным средством управ- управления принтерами, в котором реализована эта же идея. Но нас значительно больше интересует изо- изображение трехмерных объектов. Модель перьево- перьевого плоттера не очень "вписывается" в процесс формирования изображений трехмерных объек- объектов. Если, например, использовать эту модель для построения изображения трехмерного объекта на двухмерном планшете (то ли вручную, то ли с по- помощью компьютера), то в первую очередь нужно решить, где на планшете должна располагаться двухмерная точка, соответствующая опреде- определенной точке на изображаемом трехмерном объекте. Такие двухмерные точки являются, как было показано в главе 1, проекциями точек трехмерного пространства. Математически про- процесс формирования проекций описывается тригонометрическими соотношениями. Мы про- проанализируем эти соотношения в главе 5, но предпочтительнее использовать такой пакет API, который позволял бы пользователю формулировать свою задачу в терминах трехмерного пространства и не выполнять в прикладной программе никаких тригонометрических преоб- преобразований. Все операции, связанные с проективным преобразованием, должны взять на себя функции API. Такой подход очень удобен для пользователей, поскольку избавляет их от не- необходимости создавать доморощенные программы выполнения таких преобразований в со- составе своей прикладной системы. Пакет API, созданный профессионалами, решит эту задачу лучше и эффективнее. Решение двухмерных задач, таких как построение узора Серпинского, можно рассматри- рассматривать как частный случай работы в трехмерном пространстве. С точки зрения математики двухмерную плоскость или простую двухмерную криволинейную поверхность можно считать подпространством трехмерного пространства. Следовательно, выражения, справедливые для трехмерного пространства, будут справедливы и для его подпространства. Для простоты будем считать планшет бесконечным и совместим его поверхность с плос- плоскостью z=0. В таком случае точки р=(дг, у, 0) на планшете можно рассматривать или как точки трехмерного пространства, или как точки р-(х,у) двухмерного подпространства — плоскости планшета. OpenGL, как и большинство других трехмерных графических систем, позволяет пользователю использовать любое из описанных представлений, причем внутреннее пред- представление не зависит от той формы "внешнего" представления, которую выбрал для себя пользователь. Точку можно представить разными способами, но самый простой — рассмат- рассматривать ее как триаду в трехмерном пространстве: Р = Сейчас мы не будем обращать внимание на то, в какой системе координат задан компонент р. В этой книге мы чаще всего будем использовать термин вершина (vertex), а не точка {point). Вершина характеризует определенное положение в пространстве, причем в компью- компьютерной графике используются двух-, трех- и четырехмерные пространства. С точки зрения графической системы вершина представляет собой атомарный графический объект, который соответствует простейшему геометрическому объекту — точке. Двумя вершинами определя- 58 Глава 2. Графическое программирование
ется отрезок прямой — второй по простоте графический объект, тремя — треугольник и ок- окружность, четырьмя — четырехугольник и т.д. В OpenGL имеется множество форм представления объектов, из которых пользователь может выбирать ту, которая наилучшим образом соответствует специфике определенной за- задачи. Обобщенная форма представления вершины в OpenGL имеет вид glVertex* Здесь * можно заменить двумя или тремя символами в формате nt или ntv, где п задает количество размерностей B, 3 или 4), t описывает тип данных: целый (integer) — i, с пла- плавающей точкой одинарной точности (float) —f или с плавающей точкой удвоенной точности (double) — d. Третий символ V, если он присутствует в определении вершины, означает, что переменные (значения координат) задаются указателем на массив, а не списком аргументов. В дальнейшем в каждом частном случае мы будем выбирать именно ту форму представления вершины, которая наиболее подходит для данного случая. Необходимую дополнительную информацию о формах представления объектов в OpenGL читатель найдет в руководстве пользователя OpenGL Reference Manual [Ope97b]. Еще раз обращаю ваше внимание на то, что при любом внешнем представлении объекта (зададите вы вершину как двухмерный объ- объект или как трехмерный) его внутреннее представление будет одним и тем же. В главе 4 мы рассмотрим четырехмерное внутреннее представление, но сейчас вам нет нужды беспокоить- беспокоиться об этих деталях реализации. При программировании на языке OpenGL мы часто будем пользоваться базовыми типами переменных — GLf loat и GLint, — а не основными типами переменных языка С, такими как float и int. Эти типы определены в соответствующем заголовочном файле с помощью ди- директивы #def ine, например, так: #define GLfloat float Использование специализированных типов переменных в OpenGL обеспечивает дополни- дополнительную гибкость, поскольку позволяет переопределять эти типы (например, заменить фор- формат числа с одинарной точностью на формат с удвоенной точностью) и изменять таким обра- образом точность вычислений, не изменяя текст прикладной программы. Вернемся к функции определения вершины. Форма glVertex2i(GLint xi, GLint yi) подходит для представления вершины на двухмерной плоскости, причем координатами яв- являются целые числа. Представление glVertex3f(GLfloat х, GLfloat у, GLfloat z) задает вершину в трехмерном пространстве, причем значения координат будут представлены вещественными числами одинарной точности с плавающей точкой. Можно использовать для задания координат и массив. Сначала нужно определить массив значений координат: GLfloat vertex[3]; а затем задать вершину в приведенном ниже формате: glVertex3fv(vertex); Набор вершин позволяет описать разнообразные геометрические объекты, причем для каждого вида объектов требуется определенное количество вершин. Язык OpenGL позволяет сгруппировать любое количество вершин в описании объекта с помощью пары связанных функций glBegin() и glEnd(). Аргумент функции glBegin() задает тип геометрического объекта, который определяется следующим далее набором вершин. Например, отрезок пря- прямой задается следующим фрагментом программы: 2.1. Узор Серпинского 59
glBegin(GL_LINES); glVertex2f(xl,yl); glVertex2f(x2,y2); glEnd(); Те же вершины позволяют определить и пару геометрических объектов — точек: glBegin(GL_POINTS); glVertex2f(xl,yl); glVertex2f(x2,y2); glEnd(); Теперь можно приступить и к разработке программы, формирующей узор Серпинского. Будем считать, что все точки узора должны располагаться внутри единичного квадрата, ле- левый нижний угол которого находится в точке @, 0), — это обычный вариант, который при желании легко можно изменить. Сначала задумайтесь над тем, как представлять геометрические данные в программе. При работе в трехмерном пространстве можно использовать базовое представление в форме трех раздельных переменных x,y,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; 60 Глава 2. Графическое программирование
• triangle[j][l])/2; /* Вывод на экран новой точки */ glBegin(GL_POINTS); glVertex2fv(p); glEnd(); glFlush(); Функция rand() в этой программе — стандартный генератор случайных чисел. Для фор- формирования случайной последовательности, элементами которой могут быть только три целых числа 0, 1 и 2, используется операция вычисления остатка при целочисленном делении. При небольшом количестве итераций статистические характеристики генератора случайных чисел большого значения не имеют, а потому в программе можно использовать любой другой гене- генератор, а не только тот, который вызывается стандартной функцией rand (). Функция glFlush() обеспечивает вывод формируемых точек на экран сразу же по окон- окончании вычислений их координат. Если не обращаться к этой функции, точки все равно поя- появятся на экране, но при этом будет ощутима задержка вывода, особенно при наличии сетево- сетевого окружения. Эту программу в таком виде еще нельзя считать завершенной. На рис. 2.4 по- показано, как будет выглядеть сформированный ею узор. .р% •\ /г Л /.¦> i\ Ъ( Д ?* Л^. -Vf :f.4 /t А м л-лл-л-1 i.A4: А. с- Рис. 2.^. Узор Серпинского, сформированный из 5000 случайных точек Нам осталось разобраться с некоторыми нюансами формируемого узора и, соответствен- соответственно, программы: ¦ Как задать цвет узора? ¦ В какой части экрана появится изображение? ¦ Как управлять размером изображения? ¦ Как выделить область экрана — видовое окно — для формируемого изображения? 2.1. Узор Серпинского 61
¦ Какая часть нашего "бесконечного" планшета появится на экране? ¦ Как долго созданное изображение будет оставаться на экране? Хотя получить ответы на перечисленные вопросы и весьма важно, на первый взгляд ка- кажется, что они стоят как-то в стороне от рассматриваемых сейчас базовых концепций. Как вы увидите далее, основной текст тех фрагментов программы, которые мы разработаем в про- процессе поиска ответов на эти вопросы, практически без изменений войдет и в другие програм- программы формирования изображений. Следовательно, затратив сейчас некоторое время на поиск ответов, мы будем вознаграждены в дальнейшем. 2.1.2. Системы координат Сейчас вы скорее всего гадаете, каким образом можно интерпретировать сформирован- сформированные программой значения координат — переменных х, у и z — в спецификации вершин. В каких линейных единицах они выражены — футах, метрах, микронах? Где находится точка отсчета, от которой откладываются соответствующие смещения вдоль осей? В любом случае простейший ответ будет таков — все зависит от программиста, т.е. от вас. При работе с ранними системами компьютерной графики требовалось, чтобы в приклад- прикладной программе вся геометрическая информация задавалась в терминах системы координат экрана используемого устройства отображения3. Если бы такой способ описания распростра- распространялся и на современные прикладные программы высокого уровня, нам пришлось бы говорить о точках в терминах пикселей экрана или сантиметров от угла экрана. Но этот метод кажется совершенно абсурдным для описания изображений, в которых расстояния измеряются в све- световых годах (при отображении данных, с которыми имеет дело астрономия), или таких, где расстояния измеряются микронами (при выводе изображений топологии интегральных мик- микросхем). Одно из достоинств современных графических программ — возможность использо- использования в них любых единиц измерения, которые сочтет необходимым выбрать пользователь, руководствуясь только спецификой решаемой задачи. Концепция графики, инвариантной к устройствам {device-independent graphics), освободила прикладных программистов от необ- необходимости учета деталей конструкции устройств ввода-вывода графической информации. По отношению к применяемой пользователем системе координат используется термин мировая система координат {world coordinate system) или прикладная система координат {problem coordinate system). Пользователь может использовать любой диапазон значений координат, причем единственным (и очень незначительным) ограничением является диапазон представ- представления вещественных чисел в современных компьютерах в формате с плавающей точкой. Система отсчета положения на экране конкретного устройства отображения ранее назы- называлась координатами физического устройства {physical-device coordinates), или просто ко- координатами устройства {device coordinates). Для растровых устройств, каковыми являются подавляющее большинство современных дисплеев, мы используем термин координаты рас- растра {raster coordinates), или координаты экрана {screen coordinates). Координаты растра все- всегда представляются целыми числами, поскольку пиксели занимают совершенно определенное положение в узлах фиксированной сетки (или, если выразиться более профессиональным языком, пиксели по самой своей природе являются дискретными объектами и, следовательно, адресуются целыми числами). Между координатами некоторой точки в системе координат прикладной программы и в системе координат растра существует определенное соответствие, как показано на рис. 2.5. Выполнение преобразования из системы координат прикладной программы в координаты растра выполняется графической системой, и программист беспокоиться об этом не должен. 3 Даже в современных системах некоторые команды нижнего уровня требуют задания положения в дюймах, причем диапазон допустимых значений соответствует реапьным размерам экрана. 62 Глава 2. Графическое программирование
Как будет показано в последующих разделах, в прикладной программе следует специфициро- специфицировать только несколько параметров, необходимых для выполнения такого преобразования, — область отображаемого мирового пространства и размер поля экрана. 1х у ) 1 min' ' min' Координаты прикладной Координаты растра программы Рис. 2.5. Отношение между координатами при- прикладной программы и координатами растра 2.2. Прикладной интерфейс OpenGL Поскольку функцию, выполняющую основной алгоритм построения узора, мы уже разработали, то теперь рассмотрим вспомогательные операции, которые позволяют управлять видом объектов на экране. Нам понадобится также управлять ходом выполне- выполнения программы и взаимодействовать с операционной системой. Сначала более детально рассмотрим API OpenGL — программный инструмент, которым нам предстоит пользо- пользоваться. Поскольку внутреннее представление вершин не зависит от того, определили мы их как двух- или трехмерные объекты, то все, что будет изложено в этом разделе, в рав- равной степени относится и к трехмерным задачам графики. Естественно, работа в трех- трехмерном пространстве предоставляет прикладному программисту гораздо более широкие возможности, но ведь мы находимся только в самом начале пути, и у нас еще все впере- впереди. Основная задача этой главы — познакомить читателей с методами спецификации графических примитивов, а о методах взаимодействия пользователя с системой мы по- поговорим позже, в главе 3. Структура OpenGL аналогична структуре большинства других графических API, в том числе PHIGS и GKS. Следовательно, все усилия, которые придется приложить при изуче- изучении OpenGL, будут оплачены сторицей, так как одновременно вы постигаете и возможно- возможности других аналогичных систем. По сравнению с другими API изучать OpenGL гораздо проще, но это не означает, что функциональные возможности его менее широкие, чем у "конкурентов". OpenGL поддерживает работу как с двухмерными, так и с трехмерными примитивами, что вы увидите в программах, представленных в главах 2-6. В этом пакете реализованы и современные технологии тонирования, что будет продемонстрировано в программах, описанных в главах 8-12. Поскольку основная цель этой книги — изучение методов решения задач, применяемых в компьютерной графике, то и OpenGL мы будем использовать именно в качестве вспомога- вспомогательного средства для достижения этой цели. Следовательно, вы не найдете в этой книге ис- исчерпывающего описания всех доступных функций OpenGL и многих деталей, но, тем не ме- менее, все представленные программы носят законченный вид. Более детальные сведения об источниках дополнительной информации, касающейся как OpenGL, так и других графиче- графических API, вы найдете в обзоре рекомендуемой литературы в конце главы. 2.2. Прикладной интерфейс OpenGL 63
2.2.1. Графические функции При знакомстве читателей с графическими пакетами мы будем использовать подход, ставший уже классическим в кибернетике, — концепцию черного ящика (black box). Этим термином в технике принято называть устройства и системы, функциональные возможности которых описываются только соответствием между определенной входной и выходной ин- информацией, а о внутреннем механизме работы никакой информации не имеется. Таким обра- образом, мы будем считать графическую систему подобным черным ящиком, входом которого являются различные функции, вызываемые из прикладной программы, информация, переда- передаваемая с помощью устройств ввода, таких как мышь или клавиатура, и сообщения, переда- передаваемые операционной системой. Выходом системы являются в первую очередь графические образы, формируемые на экране. На данном этапе входом для черного яшика будут только вызовы функций, а выходом — примитивы, отображаемые на экране ЭЛТ (рис. 2.6). Анало- Аналогичный подход можно использовать и при изучении других графических API. Вызовы функций Выход Прикладная I ^" Графическая I ^ Устройства I программа 1^ система |^ ввода-вывода [ Данные »»——««w—л Вход """'""" "*""г""" Рис. 2.6. Графическая система как черный ящик Описывать возможности API мы будем через функции его библиотеки. В состав мощного пакета API может входить несколько сотен функций, а потому желательно сразу же разделить их на категории. Мы рассмотрим функции шести категорий. ¦ Функции описания примитивов {primitive functions) определяют объекты нижнего уровня иерархии — примитивы, — которые способна отображать графическая систе- система. В большинстве графических API имеются такие примитивы, как точки, отрезки прямых линий, многоугольники, пиксели, текст и различного рода криволинейные от- отрезки и участки криволинейных поверхностей. ¦ Если с помощью примитивов определяется, что появится на экране, то с помощью ат- атрибутов определяется, как будут выглядеть отображаемые объекты, т.е. атрибуты оп- определяют способ вывода объектов на экран. Функции задания атрибутов (Attribute functions) позволяют прикладному программисту выполнять широкий круг операций настройки изображения — от выбора цвета до указания образца заливки внутренней области многоугольника или шрифта для надписей на графике. ¦ Нужно задать параметры используемой модели синтезированной камеры, с помощью которой создается изображение. Как было сказано в главе 1, от прикладного програм- программиста требуется выбрать положение и ориентацию камеры в мировой системе коорди- координат и параметры объектива, в частности фокусное расстояние. Зная эти параметры системы, он сможет не только правильно построить изображение, но и отсечь те объ- объекты, которые оказываются вне поля зрения. Последнее позволяет не тратить времени впустую. Функции визуализации (viewing functions) позволяют задать разнообразные виды, хотя разные типы API существенно отличаются возможностями манипулирова- манипулирования видами. ¦ Одна из наиболее интересных возможностей хорошего графического пакета — набор функций геометрических преобразований (transformation functions), которые позволя- позволяют пользователю выполнять различные преобразования объектов — поворот, плоско- плоскопараллельный перенос, масштабирование и т.п. Такого рода операции мы часто будем использовать при описании методов визуализации (глава 5) и моделирования (глава 8). 64 Глава 2. Графическое программирование
Для создания интерактивных приложений нужно, чтобы в составе API имелись и функции ввода графической информации {input functions). Эти функции играют роль промежуточного звена между устройствами ввода, такими как клавиатура, мышь, планшеты разного рода, дигитайзеры, и прикладной программой. В главе 3 читатели смогут познакомиться с функциями, использующими три разных режима ввода, и с различными устройствами ввода. В реальных приложениях программисту приходится думать и об управлении средой функционирования программы, особенно в случае, когда речь идет о многопроцессор- многопроцессорной системе, многооконной операционной среде или сетевой среде со множеством пользователей. Для управления процессом выполнения программы в состав API, как правило, включаются специальные управляющие функции (control functions). Они по- позволяют прикладной программе взаимодействовать с операционной системой, ини- инициализировать приложение и обрабатывать ошибки других графических функций. 2.2.2. Интерфейс 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 2.2. Прикладной интерфейс OpenGL 65
Для улучшения "читабельности" текста программы в OpenGL предлагается использовать макросы. В частности, в заголовочных файлах (h-файлах) определены макросы GL_FILL и GL_POINTS. В большинстве версий для включения заголовочных файлов glut.h, gl.h и glu.h достаточно вставить в текст программы директиву linclude <GL/glut.h> или #include <glut.h> 2.3. Примитивы и атрибуты Среди специалистов в области компьютерной графики не прекращаются дебаты вокруг вопроса, носящего, казалось бы, совершенно схоластический характер: "Что считать прими- примитивом?" и "Какие примитивы следует поддерживать в составе API?" Скорее всего спорщики никогда не придут к единому мнению. Минималисты считают, что примитивами должны быть только такие объекты, которые в большинстве существующих графических систем формируются аппаратно. Кроме того, набор примитивов должен обладать свойством ортого- ортогональности, т.е. ни один из элементов этого набора нельзя было бы получить из других. Ми- Минимальные системы обычно поддерживают набор, состоящий из отрезка прямой, много- многоугольника и некоторой формы текста (последовательности символов). Все элементы набора могут быть эффективно сформированы аппаратно. В другом лагере— максималистов — придерживаются мнения, что в набор следует включить и такие объекты, как окружности, кривые разного рода, поверхности и сплошные тела. В качестве основного аргумента при этом выдвигается забота о пользователе, которого следует избавить от самостоятельного формирования таких объектов, ставших неотъемлемой частью большинства развитых прило- приложений. Но поскольку возможностью поддерживать на аппаратном уровне работу с большим набором довольно сложных примитивов обладают только немногие модели графических станций, программу, использующую такой большой набор, нельзя будет переносить с одной платформы на другую. В этом споре разработчики OpenGL заняли промежуточную позицию (как правило, самую удобную в споре). В базовой библиотеке используется небольшой набор примитивов, а в до- дополнительной библиотеке GLU содержатся функции построения более сложных объектов, которые с точки зрения прикладного программиста также являются примитивами. Базовые примитивы OpenGL специфицируются набором точек в пространстве (вершин). Формат определения объектов представлен ниже. glBegin(<ivw7>); glVertex*(...)? glVertex*(...); glEnd(); Значение <тий> определяет вид объекта и несет OpenGL информацию о том, каким обра- образом нужно интерпретировать следующий далее список вершин. Между вызовами функций glBegin() и glEnd() могут быть включены операторы вызовов других функций OpenGL. На- Например, здесь могут стоять операторы вызова функций задания атрибутов или вычисления значений координат вершин. Основное концептуальное отличие примитивов разного типа со- состоит в том, имеют ли они внутреннюю область. Все примитивы, кроме точек, определяются либо в терминах набора вершин, либо в терминах отрезков прямых линий (не следует путать отрезок прямой линии, который имеет ограниченные размеры, и прямую, которая представ- представляет собой геометрический объект бесконечной протяженности). Естественно, что отдельный 66 Глава 2. Графическое программирование
отрезок4 задается парой вершин, но отрезок играет настолько большую роль в описании про- прочих объектов, что его можно рассматривать как базовый примитив. Отрезки можно исполь- использовать для аппроксимации кривых линий или с помощью отрезков соединять соседние точки на графике функции. Отрезками отображаются и ребра замкнутых геометрических объектов, которые имеют внутреннюю область, например многоугольников. В OpenGL есть несколько способов формирования отрезков (рис. 2.8). Pi. Ро» P7# Pa * Рб • Рз • Рд •Ps GL_POINTS P7^ Pa Рб - Рз С GLJ.INES Pa Р7"~ЛРз Ро / ) Рд Р7^Л Рб GL_UNE_STRIP pv Ро/ \ GL. Ра Рб .LINE. "^ рз \ )Р4 Л .LOOP Рис. 2.8. Типы точек и отрезков Отрезки (GL_LINES) Тип GL LINES задает формирование отрезка по двум вершинам, которые рассматриваются как его начальная и конечная точки. Обратите внимание на то, что построенные таким обра- образом отрезки, как правило, не стыкуются друг с другом в крайних точках. Ломаные линии (GLLINESTRIP) Если необходимо сформировать ломаную линию (polyline) — последовательность стыкую- стыкующихся отрезков5, — то используется тип GL LINE_STRIP. Чаще всего ломаные линии использу- используются для аппроксимации кривых. Если желательно получить замкнутый контур, то можно либо указать в качестве конечной и начальной в списке одну и ту же вершину, либо использовать специальный тип, определяющий именно замкнутую ломаную, — GL_LINE_LOOP. В этом случае OpenGL самостоятельно свяжет отрезком последнюю вершину в списке с первой. 2.3.1. Многоугольники Отрезки и ломаные можно использовать для моделирования ребер более сложных объектов, но примитив типа замкнутая ломаная не имеет одного важного свойства — внутренней облас- области. Подобным свойством в OpenGL обладают примитивы другого типа— многоугольники (polygon) (рис. 2.9). литя по форме замкнутая ломаная ничем не отличается от многоугольника с совпадающими вершинами, она не имеет внутренней области6. Многоугольники играют особую роль в компьютерной фафике, поскольку в растровых системах изображение таких примитивов формируется очень быстро. С помощью многоугольников аппроксимируют криволинейные по- поверхности. Производительность фафических систем принято сейчас оценивать в количестве многоугольников, выводимых на экран в течение одной секунды. Изображение прямоугольника на экране может выглядеть по-разному. Можно выводить только контур, заливать внутреннюю область одним цветом или заполнять определенным рисунком (образцом — pattern), причем в 4 В дальнейшем мы не будем каждый раз оговаривать, что отрезок является прямолинейным. Только в случае, если рассматривается отрезок линии другого типа, например гиперболы uiu параболы, будет указы- указываться тип линии. — Прим. ред. ' Часто в литературе, особенно переводной, используется и термин полилиния, который является капь- кой с английского polyline. — Прим. перев. 6В некоторых графических системах, в частности в GKS. вместо термина многоугольник используется термин заполняемая область (fill area). 2.3. Примитивы и атрибуты 67
залитом многоугольнике ребра можно и не отображать (рис. 2.10). Хотя контур многоугольника определяется очень легко упорядоченным списком его вершин, неправильное (неоднозначное) определение его внутренней области может привести к некорректному ее заполнению. Для кор- корректного отображения внутренней области многоугольника он должен обладать тремя свойст- свойствами — быть простым, выпуклым и плоским. Рис. 2.9. Примитивы, имеющие внут- внутреннюю область Рис. 2.10. Способы отображения многоугольников Если в двухмерном многоугольнике нет пересекающихся ребер, то он называется простым. Как видно на рис. 2.11, простой многоугольник имеет однозначно определенную внутреннюю область. Выяснить, является ли многоугольник простым, можно по положению его вершин, но алгоритм проверки потребует множества вычислений (см. упр. 2.14). По- Поэтому в большинстве графических API предполагается, что такая проверка выполняется в прикладной программе. Вопрос, как должна поступить гра- графическая система в случае, если заданный для отображения многоугольник не является простым, будет рассмотрен в главе 7. С точки зрения алгоритмов заливки внутренней области недостаточ- недостаточно, чтобы многоугольник был простым. В некоторых API гарантируется правильное выполнение заливки только в случае, если многоугольник является выпуклым. Говорят, что геометрический объект является вы- выпуклым (convex), если все точки отрезка, соединяющего две любые точки внутренней области объекта, также принадлежат внутренней области (рис. 2.12). Хотя в этой главе мы будем иметь дело только с двухмерны- двухмерными (плоскими) объектами, это определение справедливо и для трехмер- трехмерных объектов. В число выпуклых объектов входят треугольники, тетра- тетраэдры, прямоугольники, круги, сферы и параллелеп»'"~цы (рис. 2.12). Су- Рис 2.11. Много- ществует множество алгоритмов проверки выпуклости объектов (см. угольники: уПр 2.20). Но следует отметить, что проверка выпуклости, как и провер- проверит простои, ка Пр0СТ0ТЫ многоугольника, требует достаточно сложных вычислений стой и' как пРавило> возлагается на прикладную программу. А Рис 2.12. К определению по- понятия ^выпуклости" Рис. 2.13. Выпуклые геометрические объекты 68 Глава 2. Графическое программирование
При работе с многоугольниками в трехмерном пространстве появляются дополнительные сложности. В этом случае многоугольник не обязательно является плоским, т.е. все его вер- вершины не обязательно лежат в одной плоскости. Одна из геометрических теорем утверждает, что любые три неколлинеарные точки (т.е. точки, не лежащие на одной прямой) однозначно определяют плоскость, в которой лежит треугольник, построенный на этих трех вершинах. Этот вывод используется в большинстве графических систем. Следовательно, если использо- использовать для аппроксимации криволинейных поверхностей только треугольники, то графическая система будет иметь дело исключительно с плоскими многоугольниками, которые всегда то- тонируются корректно. Кроме того, заливка треугольных областей выполняется аппаратно или программно значительно быстрее, чем заливка многоугольников другой формы. 2.3.2. Типы многоугольников в OpenGL Теперь вернемся к типам объектов в OpenGL. Существует несколько типов плоских объ- объектов, имеющих внутреннюю область (рис. 2.14). Р2 Р2 Р2 Р2 Pi. * *Рз Ро» »Рд Ро/ \рд Poi^^^7P4 Р° т Р7 Ps Рб Рб Рб Рб GL_POINTS GLPOLYGON GL_QUADS GL_TRIANGLES Рис. 2.14. Типы многоугольников в OpenGL Многоугольники (GLPOLYGON) Ребра объектов этого типа совпадают с отрезками замкнутой ломаной, построенной на том же наборе вершин. Внутренняя область объекта заполняется в соответствии с заданными значениями атрибутов заливки. Сами по себе ребра имеют конечную толщину. В большинст- большинстве графических систем можно либо заливать внутреннюю область определенным цветом (заполнять определенным образцом), либо вычерчивать ребра линиями определенной толщи- толщины, но нельзя совмещать эти два способа отображения. В OpenGL можно использовать функ- функцию glPolygonMode() для отображения ребер и тем самым отменять используемый по умол- умолчанию режим заливки. Если же желательно сформировать совмещенное изображение много- многоугольника в обоих режимах, придется фактически создать два объекта на одном и том же наборе вершин. Первый объект — это стандартный многоугольник, а второй — либо много- многоугольник, сформированный функцией glPolygonMode(), либо замкнутая ломаная. Треугольники и четырехугольники (GLTRIANGLES, GLQUADS) Для формирования треугольников и четырехугольников в OpenGL используются специ- специальные примитивы. Треугольник определяется тремя вершинами, а четырехугольник — че- четырьмя. Эти объекты тонируются быстрее, чем такие же по форме объекты типа GL_POLYGON. Полосы (GL_TRIANGLE_STRIP, GL_QUAD_STRIP, GL_TRIANGLE_FAN) Объекты этих типов представляют собой группу треугольников или четырехугольни- четырехугольников, в которых отдельные фигуры совместно используют некоторые вершины. В треуголь- треугольной полосе — объекте типа GL_TRIANGLE_STRIP — каждая последующая вершина в списке комбинируется с двумя предыдущими и определяет таким образом очередную треугольную ячейку полосы (рис. 2.15). В полосе, состоящей из четырехугольных ячеек — объекте типа 2.3. Примитивы и атрибуты 69
GL QUAD_STRIP, — каждая очередная пара вершин в списке комбинируется с предыдущей па- парой и задает очередную четырехугольную ячейку. Объект типа GL TRIANGLE_FAN — розет- розетка— строится на основе одной фиксированной вершины (центра), которая идет первой в списке вершин. Следующие две вершины в списке определяют первый треугольный "лепесток", а затем каждая очередная вершина вместе с предыдущей в списке и центральной определяет следующий "лепесток". Pi Рз Р5 Р7 Pi Рз Р5 Р7 Pi Рз Рб Р7 .' .' .' .' /\/\/\/ / / / / Ро Ра Ра Рб Ро Ра Рд Рб Ро Ра Рд Рб GL_POINTS GL_TRIANGLE_STRIP GL_QUAD_STRIP Рис. 2.15. Полосы из треугольников и четырехугольников 2.3.3. Текст Очень редко в графическое изображение не включаются поясняющие надписи. Хотя в не- неграфических приложениях вывод информации в текстовом виде используется повсеместно, в графических вывод текста вызывает определенные сложности. В неграфических приложени- приложениях мы обычно имеем дело с небольшим набором символов, отображаемых единообразно7. В графических приложениях текст, как правило, формируется из символов разного размера, на- начертания, цвета и т.д. В распоряжение прикладного программиста также предоставляется множество шрифтов, определяющих форму символов на экране, таких как Times, Computer Modern или Helvetica. Существуют два типа шрифтов — штриховой и растровый. Штриховой шрифт формиру- формируется по тому же принципу, что и прочие графические примитивы (рис. 2.16). Начертание символа определяется вершинами соответствующих прямолинейных и криволинейных отрез- отрезков. Если символ формируется из замкнутых контуров, то его внутреннюю область можно за- залить. Преимущество использования штриховых шрифтов заключается в том, что с ними можно обращаться в графической системе точно так же, как с любыми другими графически- графическими объектами. Несомненным достоинством штриховых шрифтов является и сохранение на- начертания во всех деталях при выполнении геометрических преобразований — масштабирова- масштабировании или повороте. Поэтому определение символов набора выполняется только для одного, базового, размера, а все прочие получаются после выполнения стандартных геометрических преобразований — масштабирования и поворота. Но следует отметить, что исходное описание шрифта из 128 или /~* i 256 символов может быть довольно сложным и занимать много мес- >^ О ГП О U ТС Г та в памяти. Стандартные шрифты PostScript создаются из отрезков ^^ . # полиномиальных кривых и демонстрируют все достоинства и недос- СЗ ГО О П IС S татки штриховых шрифтов. Существуют различные модификации * шрифтов PostScript для приложений с высоким и низким разрешени- Рис. 2.16. Штриховой ем- Д°вольно часто разработчики пытаются справиться с проблемой шрифт медленной обработки таких объектов в процессе тонирования изо- изображения, возлагая значительную часть связанных с этим операций на аппаратное обеспечение принтеров. Эта стратегия тесно связана с принципами организа- организации систем "клиент/сервер" и будет рассмотрена в главе 3. Любой пользователь современного текстового редактора скажет, что это утверждение, мягко гово- говоря, не совсем справедливо. — Прим. ред. 70 Глава 2. Графическое программирование
Растровый шрифт определяется значительно проще, а отображается быстрее (рис. 2.17). Символы шрифта определяются на прямоугольной области и представляют собой блоки би- битов (bit blocks). Каждый блок задает определенный символ в виде образа из нулей и единиц, которые соответствуют засвеченным и незасвеченным точкам растра. При отображении оп- определенный таким способом символ помещается в буфер кадра с помощью операции поби- побитового переноса (bitblt), которая выполняется очень быстро. Мы рассмотрим эту операцию подробно в главе 9. В составе OpenGL есть функции, которые позволяют прикладной про- программе напрямую манипулировать содержимым буфера кадра. Увеличить размер символов растрового шрифта можно только дублированием пикселей, что при большом увеличении приводит к формированию символов довольно "грубой" формы (рис. 2.18). Выполнять какие-либо геометрические преобразования растрового шрифта бес- бессмысленно. Кроме того, поскольку растровые шрифты, как правило, хранятся в постоянной памяти, они не переносятся с компьютера на компьютер. Рис. 2.17. Растровый шрифт ft Рис. 2.18. Изменения размера растрового шрифта Символы как штриховых, так и растровых шрифтов формируются из других примитивов, а потому в составе основной библиотеки OpenGL нет специального примитива для формиро- формирования текста. Но в составе дополнительной библиотеки GLUT имеется несколько наборов символов (как штриховых, так и растровых), которые определены программно, а следова- следовательно, являются переносимыми. Например, для включения в изображение символа растро- растрового шрифта размером 8x13 пикселей нужно вызвать функцию glutBitmapCharacter(): glutBitmapCharacter(GLUT_BITMAP_8_BY_13, с) Второй аргумент этой функции, с, представляет собой ASCII-код выводимого символа. Символ размещается в текущей позиции растра на поле экрана — это один из параметров, характеризующих текущее состояние графической системы. Изменение значения текущей позиции растра выполняется с помощью разных модификаций функции glRasterPos*. Мы еще вернемся к методам вывода текста на экран в главе 3, где речь пойдет об использова- использовании дисплейных списков. 2.3. Примитивы и атрибуты 71
2.3.4. Криволинейные объекты Все ранее рассмотренные примитивы полностью определялись набором вершин. Все они, за исключением точки, состояли из прямолинейных отрезков или использовали такие отрезки для определения границ некоторой области, которая заливалась определенным цветом или заполнялась образцом. В этом разделе будут рассмотрены два подхода к формированию рас- расширенного набора примитивов. Первый подход предполагает использование ранее определенных примитивов для ап- аппроксимации кривых и поверхностей. Если, например, нам нужен круг, то его можно аппрок- аппроксимировать правильным многоугольником с п вершинами. Точно так же сфера аппроксими- аппроксимируется правильным многогранником. В обшем случае любую криволинейную поверхность можно аппроксимировать сеткой из выпуклых многоугольников (мозаикой— tessellation), которая формируется либо на стадии тонирования, либо в прикладной программе. Другой подход, который мы подробно рассмотрим в главе 10, состоит в том, что создает- создается математическое описание криволинейного объекта, а затем разрабатываются графические функции реализации этого математического описания. Математический аппарат описания та- таких объектов, как квадратичные поверхности и параметрические полиномиальные кривые, достаточно хорошо разработан, и их можно строить, базируясь на наборе характеристических точек. Например, сферу можно построить, зная всего две точки — центр сферы и произволь- произвольную точку на ее поверхности. Кубическую полиномиальную кривую можно построить по че- четырем точкам, находящимся на этой кривой. В большинстве графических систем используются оба подхода. В OpenGL можно приме- применять функции аппроксимации наиболее часто используемых криволинейных поверхностей из библиотеки утилит GLU, но можно разработать и собственные функции построения разного рода специфических криволинейных объектов. Мы будем использовать эти возможности OpenGL при работе с параметрическими полиномиальными кривыми и поверхностями. 2.3.5. Атрибуты В современных системах компьютерной графики принято делить используемую инфор- информацию на две категории: информация, определяющая форму объекта, и информация, харак- характеризующая вид объекта на экране. Красная сплошная линия и зеленая штриховая линия формируются на основании одного и того же типа примитива, но выглядят совершенно по- разному. Атрибут — это любое свойство, которое определяет способ отображения заданного графического примитива. Чаще всего используются такие атрибуты, как цвет, толщина линий и образец заливки многоугольников. Рис. 2.19 поясняет физический смысл некоторых атри- атрибутов линий и многоугольников. f ' '* а) 6) Рис. 2.19. Атрибуты линий и многоугольников 72 Глава 2. Графическое программирование
Атрибуты ассоциируются или связываются с примитивами на разных этапах конвейерно- конвейерного процесса моделирования и тонирования. Связывание может не быть постоянным. В этой главе мы будем рассматривать только режим немедленного отображения {immediate-mode), в котором примитивы не сохраняются в памяти системы, а сразу же после формирования пере- передаются на отображение. Значения многих атрибутов входят в состав параметров, характери- характеризующих текущее состояние всей системы. При формировании любого примитива в програм- программе к нему применяются текущие значения атрибутов, приложимых к данному типу объектов, и соответственно формируется изображение объекта на экране. Поскольку примитив не со- сохраняется в памяти системы, то после очистки экрана он утрачивается. В главе 3 мы рассмот- рассмотрим дисплейный список (или дисплейный файл), в котором сохраняется информация о сфор- сформированных графических объектах. Такой режим работы системы позволяет повторно ис- использовать однажды сформированные объекты при создании нового изображения8. К каждому типу объектов приложим свой набор атрибутов. Например, на внешний вид точ- точки влияют только два атрибута— цвет и размер точки. На внешний вид отрезка влияют цвет, толщина линии и тип линии (сплошная, штриховая или пунктирная). Внешний вид объектов, имеющих внутреннюю область, определяется более широким набором атрибутов, посколь- поскольку требуется специфицировать множество параметров заливки. Можно заливать внут- внутреннюю область одним цветом или опреде- определенным образцом, можно задать режим ото- отображения многоугольника— с заполнением или контурный. Контур многоугольника мо- может выводиться одним цветом, а внутренняя область заливаться другим. В графических системах, поддерживающих работу со штриховыми наборами символов, Computer Graphics с Computer Graphics § Q_ О 6 Computer Graphics существуют еще и атриоуты, определяющие начертание таких символов. Как проявляются некоторые из них, продемонстрировано на рис. 2.20. Атрибуты этой группы позволяют регулировать ориентацию текстовой строки, соотношение между высотой и шириной сим- символов, наклон символов, вид начертания (полу- (полужирный, курсив, подчеркнутый) и т.д. О. Е о U Computer Graphics № SDjLjdDJQ J9jndtUCQ з с О -1 Q о Рис. 2.20. Атрибуты текста, использующего штриховой шрифт 2.4. Цвет Способность воспринимать цвет является одним из наиболее интересных свойств рецепторной системы человека, а возможность воспроизводить цвет — одним из главных достоинств современ- современных систем компьютерной фафики. Исчерпывающий анализ возможности человека воспринимать цвет требует значительно более глубокого изложения анатомии, физиологии и психологии, чем тот, который мы можем себе позволить в данной книге. Но, тем не менее, я постараюсь расширить представленную в главе 1 модель зрительной системы человека и получить на ее основе приемле- приемлемую для использования в компьютерной графике модель цветовосприятия. НВ OpenGL упор сделан именно на режиме немедленного отображения и использовании конвейерной ор- организации процесса, которая позволяет создавать динамические графические приложения, активно рабо- работающие с пользователем. В этом состоит одно из основных оппичип OpenGL от таких графических сис- систем, как PHIGS, в которых используется парадигма баз данных. В базе данных сохраняется информация о геометрических объектах, которую при желании можно использовать повторно. 2.4. Цвет 73
350 780 Как уже говорилось, видимый свет представляет собой электромагнитные колебания в диа- диапазоне длин волн между 350 и 780 нм. Колебания с более короткой длиной волны воспринима- воспринимаются как более близкие к синему цвету, а по мере увеличения длины цвет воспринимается как все более близкий к красному. Промежуточное положение между синей и красной частями спектра занимает зеленый цвет. Практи- Практически все источники света излучают колебания в достаточно ши- широком диапазоне частот. Исключение составляют источники коге- когерентных колебаний — лазеры. Каждый источник света характери- характеризуется функцией спектрального распределения С(Х), которая в графическом виде представлена на рис. 2.21. Аргументом функции является длина волны X, а значением функции — мощность излу- излучения соответствующей частоты. Функция С(Х) учитывает объективные физические свойства излучения, но никак не отражает способность зрительной системы человека воспринимать цвет этого излучения. Для того чтобы это различие стало более понятным, рассмотрим одну из ключевых задач использования цвета в компьютерной графике — задачу соответствия цветов. Предполо- Предположим, что нам нужно воспроизвести на экране цветовой эффект, создаваемый в реальном мире излучением, имеющим функцию распределения С(л). Должны ли мы для этого воспроизвести относительную мощность С для каждого значения Л? Монитор, который обладал бы подобной возможностью во всем диапазоне видимых цветов, пока что не создан. Но, к счастью, нам и не нужен такой монитор, поскольку зрительная система человека вполне удовлетворительно опи- описывается моделью, основанной на трех первичных цветах. Рис. 2.21. Функция спек- спектрального распределе- распределения светового излучения - хх- Рис. 2.22. Наложение первичных цветов Представление цвета в компьютерной фафике основано на теории трех первичных состав- составляющих (three-color theory)9. Аддитивная цветовая модель (additive color model) базируется на предположении, что любой цвет можно рассматривать как взвешенную сумму трех основных цве- 9Исключение составляют приложения для полиграфии, в которых используется четырехцветная обра- обработка. Но это связано не столько с фундаментальными концепциями теории, сколько с практическими ас- аспектами использования полиграфического оборудования. 74 Глава 2. Графическое программирование
тов. Достаточно близкая и понятная аналогия — использование трех прожекторов основных цве- цветов с регулируемой интенсивностью излучения каждого (рис. 2.22). Все прожекторы направлены в одну и ту же область темного экрана. Наши глаза воспринимают отраженный световой поток, ко- который представляет собой наложение трех первичных, но видит его как один смешанный цвет. При использовании такой модели можно, варьируя интенсивность излучения каждого из первичных прожекторов, добиться желаемого цвета. Хотя с помощью этой модели нельзя до- добиться абсолютно точного воспроизведения всех цветов, но если в качестве первичных ис- использовать красный, зеленый и синий, такое воспроизведение будет довольно близким. Фор- Формально этот процесс можно описать следующим образом. Пусть С— это тот цвет, который требуется воспроизвести, a R, G и В представляют наши три прожектора. Цветовое соответ- соответствие при этом выражается соотношением где 7*1, 7*2 и Г3 представляют интенсивность излучения соответствующих прожекторов. Синте- Синтезируемый цвет можно охарактеризовать триадой G*1, 7*2, Т3). Естественно, что точного соответствия между непрерывной функцией С(к) и наборами триад (Т|, 7*2, Т3) добиться не удастся, но именно так действует зрительная система человека. В ней существуют три типа цветовых рецепторов — колбочек. Колбочки типа /, / е {1, 2, 3}, имеют кривую чувствительности S,(k) и, когда на них попадает световой поток с функцией распределения С(Х), посылают в мозг сигнал А, = где интегрирование выполняется по всему диапазону длин волн видимого света. Таким обра- образом мозг получает информацию о цвете в виде триад (А], А2, А3), а не в виде отсчетов непре- непрерывной функции С(к). Подводя итог сказанному, получим основополагающий принцип трехкомпонентной тео- теории цветовое приятия: Если два цвета характеризуются одним и тем же набором первичных составляю- составляющих, они визуально неразличимы. Два цвета, которые воспринимаются глазом одинаково, образуют метаметрическую пару (metameric pairs). Такие цвета имеют те же самые значения интенсивности первичных со- составляющих, хотя их функции распределения С(Л) могут и не совпадать. Для построения со- соответствующих цветов достаточно воспроизвести в системе компьютерной графики интен- интенсивности первичных составляющих. Существует множество нюансов теории цветовосприятия, на которых мы в данной книге останавливаться не будем. Наибольшее внимание обычно уделяется выбору набора первич- первичных составляющих и ограничениям, проистекающим из физических принципов работы ре- реальных приборов. Первичные цвета, воспринимаемые рецепторами глаза, не совпадают ни с теми, которые используются в цветных ЭЛТ или принтерах, ни с теми, которые используются в цветных фото- и кинопленках. Кроме того, интенсивности первичных составляющих, кото- которые используются для получения соответствующих цветов на экране ЭЛТ, должны быть обя- обязательно положительны, поскольку по самой своей физической природе этот прибор позво- позволяет только добавлять какой-либо цвет, но никак не может "вычесть" его. Существует и пре- предел интенсивности свечения как излучаемого данным физическим прибором, так и воспринимаемого рецепторами зрительной системы. Следовательно, хотя математически мы и можем соотнести интенсивности первичных компонентов одного набора с аналогичными значениями другого набора, физически воспроизвести их не всегда возможно. Диапазон цве- 2.4. Цвет 75
с Зеленый Бирюзовый Синий У / i Белый / Черный / Желтый Красный __ / ¦ Фиолетовый тов, который можно воспроизвести в данной системе при заданном наборе первичных цветов, называется цветовой гаммой (color gamut). Аддитивная модель подходит для таких средств формирования цветного изображения, как ЭЛТ, прозрачные экраны и позитивные (слайдовые) фотопленки. В них в качестве первичных используются красный, зеленый и синий цвета. Цвет отдельной точки изображения можно охарактеризо- охарактеризовать положением в цветовом кубе, который изобра- изображен на рис. 2.23 и на ил. 6 цветной вклейки. Вдоль осей координатной системы такого цветового куба откладываются интенсивности первичных компо- компонентов. В нормализованном цветовом кубе любой цвет можно представить определенной точкой внут- внутри такого единичного куба. Вершины куба соответ- соответствуют черному цвету (интенсивность всех первич- первичных компонентов равна 0), красному, зеленому и си- синему цветам (два других первичных компонента имеют интенсивность 0), бирюзовому (сумма зеле- зеленого и синего), фиолетовому (сумма красного и си- синего) и желтому (сумма красного и зеленого) цве- цветам, а вершина, которая отстоит от начала системы координат куба по главной диагонали, соответствует белому цвету. Все цвета, располагающиеся вдоль главной диагонали, имеют равные соотношения первичных компонентов и проявляются в изображении в виде оттенков серого. С помощью такого цветового куба можно сравнивать системы воспроизведения цветов, построенные на разных наборах первичных компонентов. При работе с ЭЛТ цвет формируется в результате добавления первичных компонентов. В издательских системах используется иная модель, основанная на вычитании первичных ком- компонентов. В этом случае носитель изображения в исходном состоянии является белым (например, чистый лист бумаги). Окрашенные пигменты как бы удаляют определенную со- составляющую из белого цвета, отражаемого поверхностью носителя. Если считать, что по- поверхность засвечивается чисто белым цветом, то некоторая точка этой поверхности будет восприниматься красной в том случае, если все компоненты, кроме красного, будут поглоще- поглощены наложенными на эту точку пигментами. В таких системах первичные пигменты имеют дополняющий цвет. Это так называемая система CMY (по первым буквам английских назва- названий дополняющих цветов — cyan, magenta, yellow (рис. 2.24)). В данной книге мы не будем в дальнейшем использовать систему дополняющих цветов и сконцентрируемся только на адди- аддитивном методе формирования цвета. Рис. 2.23. Цветовой куб Синий Желтый Фиолетовый Зеленый Бирюзовый Бирюзовый Белый Зеленый Синий Рис. 2.24. Формирование цвета: а — наложение первичных цветов; б — вычитание первичных цветов 76 Глава 2. Графическое программирование
2.4.1. Цветовая система RGB В этом разделе мы рассмотрим, как выполняются манипуляции с цветом в графических системах с точки зрения программиста, т.е. какие функции для этого имеются в составе API. Существуют два довольно сильно отличающихся подхода к программным манипуляциям с цветом. Мы будем рассматривать только цветовую модель RGB, поскольку она играет ре- решающую роль в дальнейшем обсуждении вопросов моделирования освещения и реализации тонирования объектов сцены. По сравнению с моделью цветовых индексов, которая будет рассмотрена в следующем разделе, RGB-модель более сложная в аппаратной реализации, по- поскольку требует большего объема памяти, но в связи с тем, что стоимость памяти непрерывно снижается, это уже можно не считать ее существенным недостатком. В современных графи- графических API стало возможным обеспечить правильную цветопередачу, причем аппаратно уда- удается использовать алгоритмы аппроксимации, позволяющие достаточно приблизиться к пол- полноценной RGB-модели отображения. Концептуально в RGB-системе наложения цветов должны существовать три раздельных буфера кадра— по одному на каждый из первичных цветов. В каждом буфере за определен- определенным пикселем на экране закреплен фиксированный адрес в памяти (рис. 2.25). В типовой сис- системе, имеющей разрешение 1280x1024 пикселей, каждый из них описывается тремя байтами B4 битами): по одному байту соответственно на красный, зеленый и синий цвета. В результа- результате буфер кадра должен иметь объем порядка 3 Мбайт, причем весь буфер должен считывать- ся с частотой регенерации. До последнего времени необходимость в таком буфере являлась одним из наиболее существенных препятствий во внедрении графических систем с правиль- правильной цветопередачей. Буфер кадра Рис. 2.25. Цветовая система RGB Программисту желательно было бы иметь возможность задавать цвет в виде определен- определенного числа и не думать о том, каким образом соответствующая информация будет храниться в буфере кадра. Если считать, что на каждый цвет отводится по 1 байту, то в распоряжении программиста есть 224 возможных цветов (иногда говорят, что имеется 16 М цветов, понимая под М число 10242). В других графических системах используется 12 и более битов на каж- каждый цвет, но существуют и системы с 4 битами на цвет. Поскольку все графические API же- желательно делать аппаратно-независимыми, то программисту нужно позволить специфициро- специфицировать цвет независимо от объема памяти, отводимой в буфере кадра на каждый пиксель. Все остальные операции, связанные с приведением кода цвета к формату конкретной системы отображения, должны взять на себя драйверы и аппаратные средства. Наиболее естественным было бы воспользоваться моделью цветового куба и специфицировать цвет в виде трех дей- действительных чисел в диапазоне от 0 до 1.0. В OpenGL эта идея реализуется следующим обра- образом. Для того чтобы задать красный цвет, вызывается функция glColor3f (): glColor3fA.0, 0.0, 0.0); 2.4. Цвет 77
В результате выполнения этой функции устанавливается значение текущего атрибута цвета, со- соответствующее красному цвету. Поскольку атрибут цвета является одним из параметров текущего состояния системы, все последующие графические объекты будут отображаться красным цветом до тех пор, пока значение атрибута не изменится. Суффикс 3f в имени функции играет ту же роль, что и суффиксы в именах функций семейства glVertex*, и означает, что используется трехцветная (RGB) модель формирования цвета, а значения компонентов — аргументов функции — это веще- вещественные числа в формате float, как он определен в языке С. Если для задания компонентов будут использованы числа в формате integer или byte, то максимальное значение выбранного типа бу- будет соответствовать максимальному значению интенсивности первичного компонента, а мини- минимальное — полностью отключенному первичному компоненту. В дальнейшем мы рассмотрим четырехкомпонентную (RGBA) систему представления цвета. Четвертый компонент в такой системе (А-компонент) называется альфа-каналом {alpha channel), но сохраняется в буфере кадра, как и значения RGB-компонентов. В главе 9 будут рассмотрены различные варианты использования альфа-канала, которые позволяют, например, создать эффект тумана или комбинированного (смешанного) изображения. Значе- Значение альфа-составляющей в OpenGL трактуется как значение параметров прозрачности {transparency) или поглощения {opacity). Прозрачность и поглощение — это два взаимно до- дополняющих параметра, характеризующих оптические свойства среды. Поглощающий объект не пропускает свет через себя, а прозрачный объект пропускает весь падающий на него све- световой поток. При определенных значениях этого параметра объект может быть абсолютно черным телом, т.е. полностью поглощать световой поток, либо абсолютно прозрачным. Одна из первых операций, которые выполнятся в графической программе, — это очистка экрана, точнее той его части, в которой будет формироваться графическое изображение, — видового окна. При использовании четырехкомпонентной (RGBA) цветовой системы можно создать эффект взаимного наложения текущего видового окна с тем, которое "расположено" под ним. Это выполняется с помощью параметра поглощения окна при выполнении операции его очистки. Вызов функции glClearColorA.0, 1.0, 1.0, 1.0); настраивает белый цвет заполнения, поскольку первые три аргумента — цветовые компоненты RGB — равны 1.0, и режим непрозрачности, поскольку альфа-компонент также равен 1.0. Те- Теперь после вызова функции glClear() окно окрасится в белый цвет и станет непрозрачным. 2.4.2. Индексируемый цвет Во множестве графических систем используется буфер кадра с ограниченной глубиной. Например, в системе может использоваться буфер, рассчитанный на разрешение 1280x1024 пикселей, но каждый пиксель имеет только 8-битовый код засветки. Можно было бы разделить эти 8 бит на более мелкие группы и назначить по одной такой группе на каждый из первичных цветов — красный, зеленый и синий. Но такая технология, во-первых, слишком ограничивает возможности цветопередачи в системе, а во-вторых, разбиение на очень мелкие группы битов очень негативно сказывается на скорости работы буфера кадра. Вместо этого можно последовать аналогии с художником, который пишет картину. Он может воспроизвести на картине сколько угодно цветов, смешивая на палитре краски всего из нескольких тюбиков. Будем говорить, что художник имеет потенциально неограниченную палитру цветов, но в каждый момент времени он работает только с некоторыми из них, ко- которые уже смешаны (подготовлены) на палитре. Возвращаясь к нашей компьютерной модели, покажем, что, выбирая для определенного приложения ограниченный набор цветов из большого множества (нашу палитру), можно в подавляющем большинстве случаев создавать изображение достаточно высокого качества. 78 Глава 2. Графическое программирование
Индекс 0 2l'-l Красный 0 2m-1 0 Зеленый 0 0 2m-l Синий 0 0 0 Код пикселя при этом интерпретируется не как абсолютный код цвета, а как индекс в списке или таблице. Предположим, что каждый пиксель характеризуется кодом из к бит, т.е. имеет зна- значение индекса в диапазоне от 0 до 2*'. Будем также считать, что аппаратные средства отображения по- позволяют задавать каждый из основных цветов с точностью т бит, т.е. можно получить 2т градаций интенсивности любого из основных цветов. Следо- Следовательно, потенциально наш дисплей может вос- воспроизвести любой из 23т цветов, но в буфере кадра можно задать только 2* из них. Эти значения мож- можно "пропустить" через определенную пользовате- пользователем таблицу соответствия цветов (color-lookup table), которая имеет размер 2*x3/w (рис. 2.26). В **с. 2.26. Таблица соответствия цветов прикладной программе заполняется 2* строк табли- таблицы желаемыми кодами цветов, которые представляют собой набор из трех чисел, соответст- соответствующих интенсивности первичных компонентов (каждое имеет размер /я бит). Создав такую таблицу, пользователь может специфицировать любой из перечисленных в ней цветов его ин- индексом в таблице (рис. 2.27). При к=т=% (это стандартный вариант системы отображения) поль- пользователь имеет в своем распоряжении набор, включающий любые 256 цветов из общего числа 16 М. Таблица из 256 элементов и образует палитру пользователя данного приложения. тбит m бит тбит Буфер кадра Рис. 2.27. Индексация цвета В режиме индексированного цвета текущий цвет выбирается специальной функцией, ар- аргументом которой является значение индекса. В OpenGL эта операция выполняется функцией gllndexi(). Заполнение и модификация строк в таблице соответствия цветов требует взаи- взаимодействия с подсистемой управления окнами, которая в каждой операционной системе мо- может быть реализована по-своему. (Этот вопрос обсуждается в главе 3.) Основная сложность состоит в том, что в некоторых операционных системах и комплектах аппаратуры системы отображения поддерживается ограниченное количество цветов и подсистема управления ок- окнами располагает единственной таблицей соответствия цветов для всех окон. В других опе- операционных системах для каждого окна можно сформировать отдельную таблицу. Библиотека GLUT позволяет прикладному программисту организовать отдельную таблицу для каждого окна и заполнять ее, вызывая функцию glutSetColor(): void glutSetColor(int, GLfloat red, GLfloat green, GLfloat blue); Управление цветом с помощью индексации в таблице соответствия представляет в на- настоящее время интерес разве что с точки зрения исторической, поскольку снижение стоимо- стоимости памяти свело на нет основное преимущество этого метода. В то же время ограниченное количество цветов в одном приложении не позволяет создавать изображения высокого каче- качества, особенно динамические. Поэтому в дальнейшем будем полагать, если не оговорено про- противное, что используется полноцветная RGB-система. 2.4. Цвет 79
2.4.3. Настройка атрибута цвета В программе формирования узора Серпинского, которую мы будем в разных вариан- вариантах рассматривать далее в этой главе, используется полноцветная RGB-система управле- управления цветом. В этой программе необходимо установить значения трех атрибутов. Сначала настраивается цвет чистого экрана {clear color), что выполняется вызовом функции glClearColor(): glClearColorA.0, 1.0, 1.0, 1.0); Этот атрибут играет в OpenGL ту же роль, что и знакомый большинству программистов цвет фона {background color). Настройка цвета отображения точек осуществляется функ- функцией glColor3f (). Приведенный ниже оператор устанавливает текущим красный цвет: glColor3fA.0, 0.0, 0.0); Можно настроить и размер примитива "точка" при отображении его на экране. Для этого в OpenGL имеется функция glPointSize(). Приведенный ниже оператор устанавливает раз- размер примитива "точка" на экране равным двум пикселям: glPointSizeB.0); Обратите внимание на то, что атрибуты, определяющие линейные размеры графических элементов на экране, например размер точки или толщина линии, задаются в пикселях. Сле- Следовательно, при выполнении одной и той же программы на компьютерах, оборудованных мониторами с разными размерами экрана и разным разрешением, получится несколько отли- отличающееся в деталях изображение. В некоторых графических API все размеры задаются в единицах, независимых от используемой аппаратуры, но и в этом случае не всегда удается получить изображение одинакового качества на разных платформах. Разработчики OpenGL выбрали компромиссный подход, более практический с их точки зрения. 2.5. Визуализация Итак, в предыдущих разделах вы познакомились с теми графическими примитивами, из которых может состоять изображение, и с тем, как с помощью атрибутов задать их внешний вид на экране. Но до сих пор мы не рассматривали методы, позволяющие точно задать, какие из этих объектов должны появиться на экране. Точно так же, как фотограф по-разному ком- компонует кадр, изменяя объектив и точку съемки, программист должен настроить параметры визуализации в своей прикладной программе. В основе описанной в главе 1 модели синтезированной камеры лежит концепция незави- независимости параметров отображаемых объектов и параметров камеры. После того как в про- программе будут построены модели сцены и камеры, можно приступать к формированию изо- изображения этих объектов с помощью этой камеры. В фотографии камера формирует изобра- изображение, экспонируя фотопленку. Компьютерная система формирует изображение, выполняя последовательность операций, заложенную в конвейер визуализации (viewing pipeline). При- Прикладная программа должна заботиться только о спецификации параметров объектов и каме- камеры, так же, как и фотограф-любитель не думает о том, как работает затвор фотоаппарата или как изменяется химический состав фотоэмульсии под воздействием света. Существуют установленные по умолчанию параметры формирования изображения в ком- компьютере, что аналогично установленной на штативе фотокамере со штатным объективом. Но такая фиксированная камера вынуждает фотографа компоновать кадр, переставляя объекты съемки. Если нужно снять слона, то его придется втащить в павильон и заставить стоять на приличном удалении от камеры. А какого-нибудь паучка вообще не удастся снять, поскольку это не позволит сделать штатный объектив. Поэтому профессиональные фотографы пользу- 80 Глава 2. Графическое программирование
ются портативными камерами с набором сменных объективов или специальным объективом с переменным фокусным расстоянием — трансфокатором. Тот же подход реализуется и в системах компьютерной графики. 2.5.1. Визуализация двухмерных объектов Визуализация двухмерных объектов сводится к выбору прямоугольной области двухмер- двухмерного пространства и переносу ее содержимого на экран, как показано на рис. 2.28. Отобра- Отображаемая область пространства называется прямоугольником видимости (viewing rectangle), или прямоугольником отсечения (clipping rectangle). Объекты, попавшие внутрь этого прямо- прямоугольника, переносятся на экран, а не попавшие в него — отсекаются и не отображаются. Объекты, которые пересекаются контуром прямоугольника отсечения, будут частично види- видимыми. Какими должны быть размеры окна и где его расположить на экране — это отдельный разговор, который мы продолжим в разделе 2.6. а) 6) Рис. 2.28. Визуализация двухмерных объектов: а — объекты до отсечения; б — изображение после выполнения отсечения Не забывайте, что двухмерная графика является частным случаем трехмерной, а следова- следовательно, прямоугольник видимости лежит на плоскости z = О в трехмерной зоне видимости (viewing volume), как показано на рис. 2.29. Если зона видимости не задана явно, то в OpenGL используется установленная по умолчанию зона в виде куба видимости 2x2x2 с началом ко- координат в центре куба. В терминах двухмерной плоскости левый нижний угол прямоугольни- прямоугольника видимости имеет координаты (-1.0, -1.0), а правый верхний — координаты A.0, 1.0). Прямоугольник видимости 2=0 Рис. 2.29. Зона видимости 2.5. Визуализация 81
2.5.2. Ортогональная проекция Описанный двухмерный вид является частным случаем ортогональной проекции {orthographic projection), которую мы подробно рассмотрим в главе 5. При простом орто- ортогональном проецировании точка (xfy, z) на объекте проецируется в точку (х,у, 0) на плос- плоскости проекции (рис. 2.30). Поскольку двухмерное пространство есть плоскость z - 0, то никакого эффекта проецирования мы не заметим. Однако, что не менее важно, при форми- формировании ортогональной проекции двухмерных объектов можно использовать тот же меха- механизм, что и при проецировании трехмерных объектов. В OpenGL ортогональная проекция, характеризуемая параллелепипедом видимости, задается функцией glOrtho(), объявлен- объявленной следующим образом: void glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far); Рис. 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 будет рассмотрено, как организовать в программе перемещение камеры и фор- формирование более сложных видов. 82 Глава 2. Графическое программирование
Z у/ и L щ 1 II f 1 X / > X / (правый, верхний, дальний) (левый, нижний, ближний) Рис. 2.31. Размещение камеры при настройке по умолчанию параметров параллелепипеда види- видимости при ортогональном проецировании. Показаны соответствие аргументов вызова функции glOrthof) и положения вершин параллелепипеда 2.5.3. Матричный режим проецирования В системах компьютерной графики, использующих конвейерный принцип обработки, же- желательно, чтобы все операции формирования изображения выполнялись единообразно — в форме перемножения матриц. Ряд матриц преобразования являются переменными состояния системы OpenGL и действуют до тех пор, пока их принудительно не изменят. Из них наи- наибольшее значение для нас сейчас имеют матрицы вида {model-view) и проецирования (или проективного преобразования). В начальном состоянии обе эти матрицы имеют вид единич- единичных. В главе 4 читатели смогут познакомиться с имеющимися в составе OpenGL средствами выполнения операций с матрицами. Обычно этим матрицам присваиваются значения, кото- которые отличают их от единичных. Специальные функции позволяют манипулировать любыми типами матриц. Выбор конкретного типа матрицы осуществляется настройкой переменной режима работы с матрицами (matrix mode). Эта переменная, как и ряд других, характеризу- характеризует текущее состояние графической системы. По умолчанию переменной присваивается зна- значение, соответствующее работе с матрицей вида (матрицы model-view). Поэтому для настрой- настройки типа проецирования нужно первым делом изменить состояние переменной режима работы с матрицами. Ниже приведен типовой фрагмент программы, в котором выполняется настрой- настройка двухмерного прямоугольника видимости. glMatrixMode(GL_PROJECTION); glLoadldentityO; gluOrtho2D@.0, 500.0, 0.0, 500.0); glMatrixMode(GL_MODELVIEW); Значения аргументов функции gluOrtho2D() задают прямоугольник видимости размером 500x500, левый нижний угол которого находится в начале двухмерной системы координат. Последний оператор фрагмента восстанавливается режим работы с матрицей вида. В слож- сложной программе считается хорошим тоном восстанавливать значения параметров текущего со- 2.5. Визуализация 83
стояния после выполнения каких-либо манипуляций. Если следовать этому правилу, то при разработке любой подпрограммы всегда можно будет считать, что текущим является режим работы с матрицей вида. 2.6. Функции управления Мы рассмотрели практически все вопросы, необходимые для разработки программы по- построения на экране узора Серпинского. Осталось невыясненным единственное — как графи- графическая система взаимодействует с подсистемой окон и операционной системой. Не углубля- углубляясь в детали определенных платформ, таких как система X Window под UNIX или Microsoft Windows, сразу же отметим, что интерфейс между графической системой и конкретной опе- операционной системой может быть достаточно сложным. В деталях этот интерфейс очень тесно связан со спецификой определенной операционной системы, и их обсуждение далеко уведет нас от главной задачи — исследования методов компьютерной графики. Поэтому мы ограничимся минимальным набором операций, которые необходимы для нормальной работы прикладной графической системы. Большинство из них располагает ком- комплектом версий библиотек, предназначенных для работы с определенными операционными системами. В OpenGL эта роль принадлежит библиотеке GLUT (GL Utility Toolkit). Именно ее функции учитывают все особенности определенной операционной системы. Таким обра- образом, основной набор функций API можно считать независящим от используемой платформы. Практически вся настройка сводится к добавлению нового пути в набор путей поиска биб- библиотек. В данной главе и в главе 3 при описании средств взаимодействия современных гра- графических систем с операционной средой мы будем опираться на функции библиотеки GLUT. 2.6.1. Взаимодействие с подсистемой окон Термин окно (window) используется в информатике и программировании очень часто и имеет множество значений. В компьютерной графике термин окно, или окно экрана (screen window), означает прямоугольную область, на которой формируется изображение. Чаще всего это экран ЭЛТ, хотя в данном случае это и непринципиально. Окно характеризуется своими размерами (высотой и шириной), и поскольку в пределах окна выводится содержимое буфера кадра, положение на поле окна измеряется в оконных (window coordinates) или экранных ко- координатах (screen coordinates), причем в качестве единиц измерения используются пиксели10. Современная операционная среда позволяет выводить на один и тот же экран множество окон. Каждое из них может принадлежать разным приложениям или даже в пределах одного приложения выполнять совершенно разные функции. Мы в дальнейшем будем использовать термин подсистема окон, подразумевая под ним многооконную среду, поддерживаемую в та- таких операционных системах, как X Window или Microsoft Windows. Положение внутри этого окна отсчитывается от его базовой точки — обычно один из углов прямоугольной рамки ок- окна. В большинстве случаев им является левый нижний угол, который имеет оконные коорди- координаты @, 0), но учтите, что в некоторых операционных системах это соглашение не выполня- выполняется. Поскольку в растровом дисплее развертка выполняется по той же траектории, что и в коммерческом телевидении (слева направо и сверху вниз), то с этой точки зрения желательно было бы выбирать в качестве начала координат левый верхний угол. В OpenGL команды под- подразумевают, что начало оконной системы координат находится в левом нижнем углу, хотя информация, получаемая от подсистемы окон, в частности о положении указателя мыши, часто выражается в иной системе координат, которая имеет начало отсчета в левом верхнем IOB OpenGL система оконных координат трехмерная, а система экранных координат — двухмерная. В обеих системах в качестве единицы измерения применяются пиксели, но третий компонент в системе окон- оконных координат позволяет хранить информацию о глубине. 84 Глава 2. Графическое программирование
углу. Таким образом, приходится выполнять дополнительное преобразование данных, посту- поступающих от операционной системы. Обращаю ваше внимание и еще на одно обстоятельство. Хотя весь экран дисплея имеет определенное стандартное разрешение, скажем 1280x1024 пикселей, окно приложения может иметь любые размеры (но, конечно, не больше, чем размер полного экрана). Таким образом, буфер кадра приложения должен иметь разрешение соответственно размеру окна. Если ис- используется окно размером 300x400 пикселей, то нужно считать, что и буфер кадра имеет раз- разрешение 300x400, хотя при этом используется только часть памяти, отведенной для буфера. Перед тем как окно приложения будет открыто на экране, должен состояться сеанс связи между подсистемой окон и OpenGL. В составе библиотеки GLUT эта операция возлагается на функцию glutlnit(): void glutlnit(int *argcp, char **argv); Функция имеет два аргумента, через которые можно передать аргументы командной стро- строки, как в стандартной С-функции main(). После выполнения glutlnit() можно открыть окно OpenGL-программы, вызвав функцию glutCreateWindow(): int glutCreateWindow(char *title); Аргумент title этой функции — надпись, которая будет выведена в строке заголовка окна. При создании окна устанавливаются его параметры, назначенные графической системой по умолчанию, — размер, положение на экране и режим RGB управления цветом. Для на- настройки иных значений параметров нужно перед вызовом glutCreateWindow() обратиться к соответствующим функциям их установки. Например, в следующем фрагменте программы устанавливается размер окна 480x640 пикселей, а само окно размещается в левом верхнем углу экрана". glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE); glutInitWindowSizeD80, 640); glutlnitWindowPosition@,0); Обратите внимание на то, что константы задания отдельных режимов объединяются в ар- аргументе функции glutInitDisplayMode() операцией побитового ИЛИ. Константа GLUT_RGB задает RGB-режим. Если предполагается использовать режим индексированных цветов, то вместо константы GLUT_RGB следует передать GLUT_INDEX. Вторая константа GLUT_DEPTH зада- задает использование буфера глубины при выполнении удаления невидимых поверхностей. Тре- Третья константа GLUT_DOUBLE задает двойную буферизацию (одиночная буферизация задается константой GLUT_SINGLE). Режим, заданный по умолчанию, который вполне достаточен для той задачи, которую мы будем решать в этой главе, — цвет RGB, отсутствие удаления невидимых поверхностей и одиночная буферизация. Эти настройки можно и не устанавливать в явном виде, но включе- включение их в программу делает ее текст более понятным для тех, кто только осваивает методику программирования на OpenGL. 2.6.2. Соотношение сторон и видовые окна Сам по себе параметр соотношение сторон {aspect ratio) применим к любому прямо- прямоугольнику и означает отношение его ширины к высоте. Существующая в графической системе независимость между объектами, визуализацией и настройкой окна приложения В таких системах, как X Window, запрашивается формирование окна с определенными свойствами. Но переданные значения свойств могут быть переопределены операционной системой, если они не соответст- соответствуют функциональным возможностям установленной версии. 2.6. Функции управления 85
может привести к нежелательным побочным эффектам, в частности искажению пропор- пропорций между продольными и поперечными размерами объектов на экране. Соотношение сторон прямоугольника видимости задается аргументами функции glOrtho() и может отличаться от соотношения сторон окна, которое задается аргументами функции glutInitWindowSize(). Именно такой случай продемонстрирован на рис. 2.32, и вы ви- видите, к каким искажениям пропорций в изображении объектов на экране это привело. Следует оговориться, что "тайной пружиной" такого искажения явилось не только несо- несоответствие между соотношениями сторон, но и назначенный по умолчанию режим ото- отображения, при котором все содержимое прямоугольника видимости должно быть пере- перенесено в заданное окно на экране. Чтобы избежать таких искажений, следует отменить этот режим — использовать концепцию видового окна. Видовое окно (viewport) — это прямоугольная область в пределах окна на экране (окно в окне). По умолчанию видовое окно занимает всю область экрана, запрошенную у операционной системы, но можно ус- установить и другие его размеры, вызывав функцию glViewport(): void glViewport(GLint х, GLint у, GLsizei w, GLsizei h); Первые два аргумента этой функции — х и у — положение левого нижнего угла прямо- прямоугольника видового окна относительно левого нижнего угла выделенной области экрана. Следующие аргументы — w и h — задают высоту и ширину видового окна. Все параметры задаются как целые числа и измеряются в пикселях. Соответствие между отсекающей рамкой (прямоугольником видимости) и видовым окном на экране иллюстрируется на рис. 2.33. При заданных размерах окна приложения таким способом можно в прикладной программе устра- устранить искажение пропорций изображения. а) б) Рис. 2.32. Искажение пропорций при несоответствии со- соотношения сторон прямоугольника видимости и окна приложения на экране: а — прямоугольник видимости; б — окно на экране Параметры видового окна также характеризуют текущее состояние графической систе- системы. Если изменить их между последовательными циклами тонирования изображения или циклами повторения вывода изображений одних и тех же объектов, то можно получить эффект появления нескольких изображений на поле окна, причем управлять можно не только положением видового окна, но и соотношением его сторон (результаты очень впе- впечатляют). В главе 3 мы еще вернемся к тому, как можно изменять размеры и форму окна приложения на экране. 86 Глава 2. Графическое программирование
-Видовое окно ¦Графическое окно Отсекающая рамка Рис. 2.33. Соответствие между отсекающей рамкой и видовым окном на экране 2.6.3. Функции main(), display() и myinit() В принципе, можно было бы скомбинировать приведенный выше фрагмент инициализа- инициализации окна и текст программы, приведенный в разделе 2.1, и получить завершенную OpenGL- программу формирования узора Серпинского. Но, к сожалению, не так просто все делается в современных операционных системах. Нам предстоит решить еще две проблемы: первая из них характерна для всех графических систем, а вторая касается еще одного аспекта взаимо- взаимодействия с операционной системой. В режиме немедленного графического вывода примитив выводится на экран, как только он формируется графической системой. При этом графическая система использует текущие значения атрибутов, характеризующие состояние системы. При выполнении нашей простой программы, в которой отсутствует взаимодействие с пользователем, изображение выводится на экран, и на этом процесс завершается. После завершения работы программы операционная система сразу же закрывает окно приложения, и пользователь вряд ли успеет заметить, что же там нарисовала программа. Самое простое решение этой проблемы — вставить в про- программу выдержку времени с помощью стандартной функции sleep(). Но даже в тривиальном приложении следует поискать более интересное решение. В главе 3 будет рассмотрен меха- механизм обработки событий, который предоставляет прикладному программисту значительно более широкие возможности контролировать процесс выполнения программы. А сейчас мы воспользуемся функцией glutMainLoop() из библиотеки GLUT. Определена эта функция сле- следующим образом: void glutMainLoop(void) Обращение к этой функции приведет к тому, что программа "войдет" в цикл обработки событий. Пока в очереди событий не будет заявки на обработку, программа будет находиться в состоянии ожидания и пользователь сможет рассматривать созданное изображение. Так бу- будет продолжаться до тех пор, пока пользователь не нажмет какую-либо клавишу. Собственно вывод графической информации выполняется с помощью функции отобра- отображения с обратным вызовом {display callback). Эта функция задается еще одной функцией из библиотеки GLUT, определение которой имеет вид void glutDisplayFunc(void (*func)(void)); Здесь аргумент f unc задает имя функции, которая будет вызываться операционной систе- системой, как только она придет к выводу, что окно приложения OpenGL должно быть обновлено. Одно из таких событий — открытие окна. Поскольку разрабатываемая нами программа не 2.6. Функции управления 87
реагирует на действия пользователя, то все процедуры формирования изображения должны быть сосредоточены в теле функции, имя которой передается в качестве аргумента glutDisplayFunc(). Эта функция будет выполнена однократно сразу же после открытия окна и выведет изображение узора. Но функция формирования изображения может вызываться не только при открытии окна, но и при его перемещении или закрытии окна другого приложения, частично или полностью закрывающего окно OpenGL-программы. Для большинства неинтерактивных графических приложений вполне подходит та про- программа main ( ), которая приведена в листинге 2.1. Листинг 2.1. Функция main() Iinclude <GL/glut.h> void raain(int argc, char **argv) { glutlnit(&argc,argv); glutlnitDisplayMode (GLUT_SINGLE | GLUT_RGB); glutlnitWindowSizeE00,500); glutInitWindowPosition@,0); glutCreateWindow("Simple OpenGL example"); glutDisplayFunc(display); myinitj); glutMainLoop(); _} Для начальной установки переменных текущего состояния, отвечающих за настройку па- параметров визуализации и атрибутов, используется функция myinit( )l2. Эти параметры пред- предпочтительнее устанавливать один раз, независимо от функции формирования изображения. Стандартный заголовочный файл библиотеки GLUT включается в текст программы перед определениями любых функций. В большинстве систем программирования на языке С для этого используется директива компилятора iinclude <GL/glut.h> При этом одновременно в текст программы включаются и заголовочные файлы стандарт- стандартной библиотеки gl.h и библиотеки утилит OpenGL glu.h. В этих файлах находятся и макро- макроопределения таких констант, как GL_LINES и GL_RGB. 2.6.4. Структура программы Все программы, которые мы будем рассматривать в данной книге, имеют такую же струк- структуру, что и программа построения узора Серпинского. Во всех этих программах будет ис- использоваться библиотека GLUT. Функция main() включает вызовы функций из библиотеки GLUT, которые устанавливают параметры окна (или окон) приложения и обеспечивают под- поддержку со стороны операционной системы процесса отображения. В функции main() также объявляется имя функции отображения с обратным вызовом, которая, собственно, и фор- формирует изображение. Помимо нее, в программе могут существовать и другие функции с об- обратным вызовом, которые необходимы для организации взаимодействия с пользователем. В 12 Я надеюсь, что у читателей не возникнет путаница из-за того, что я использую те же имена функ- функций, которые упоминаются в руководстве OpenGL Programmer's Guide [Ope97,a] или в документации на биб- библиотеку GLUT [Kil94,a]. 88 Глава 2. Графическое программирование
функции myinit() настраиваются значения атрибутов и опций прикладной программы, для чего используются функции из библиотек GL и GLU. Хотя эти настройки можно выполнять и в функции main(), текст программы будет понятнее и его будет легче сопровождать, если об- обращения к функциям из библиотеки GLUT вынести в отдельный блок (в данном случае — функцию main()). Как правило, в программах OpenGL практически вся процедура формиро- формирования изображения выполняется в функции отображения с обратным вызовом, которую чаще всего именуют display (). 2.7. Программа Gasket В дополнение к функции main(), представленной в листинге 2.1, в программу Gasket формирования узора Серпинского нужно включить функции myinit() (листинг 2.2) и display () (листинг 2.3). Эта программа будет выводить точки красного цвета на белом фоне. При формировании изображения используются двухмерная система координат и прямо- прямоугольник (квадрат) видимости размером 500x500, причем начало координат располагается в левом нижнем углу этого квадрата. Листинг 2.2. Функция инициализации myinit() void myinit(void) { /* Атрибуты */ glClearColorA.0, 1.0, 1.0, 0.0); /* белый фон */ glColor3fA.0, 0.0, 0.0); /* вывод красным цветом */ /* Настройка параметров визуализации */ glMatrixMode(GL_PROJECTION); gluLoadIdentity(); gluOrtho2D@.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 p ={75.0,50.0}; /* Произвольная точка внутри треугольника */ glClear(GL_COLOR_BUFFER_BIT); /* Очистка окна */ 2.7. Программа Gasket 89
/* Вычисление и вывод на экран 5000 новых точек */ for( k=0; k<5000; j=rand()%3; /* Выбрать вершину случайным образом */ /* Вычислить координаты точки, лежащей посередине между вершиной и предыдущей точкой */ р[0] = (p[0]+vertices[j][0])/2.0; = (p[l]+vertices[j][l])/2.0; /* Вывести точку на экран */ glBegin(GL_POINTS); glVertex2fv(p); glEnd(); glFlush(); В теле функции формирования изображения определены произвольный треугольник (массив из трех вершин) и произвольная точка внутри этого треугольника. Далее следует цикл, в котором формируется фиксированное количество (в данном случае 5000) точек. В конце процедуры вызывается OpenGL-функция glFlush(); тем самым система прину- принуждается выводить сформированные точки на экран как можно скорее. Полный листинг этой программы читатель найдет в приложении А. 2.8. Многоугольники и рекурсия Результат работы описанной выше программы (см. рис. 2.4) демонстрирует весьма инте- интересную графическую структуру. Чем больше точек будет формировать эта программа, тем менее случайный характер будет носить изображение. Анализируя эту структуру, приходим к выводу, что независимо от количества сформированных точек, они никогда не появляются в середине некоторых треугольных областей. Если провести отрезки между средними точками прилежащих сторон исходного треугольника, то он будет разбит на четыре треугольника, причем в средний точки узора никогда не попадут (рис. 2.34). В других трех треугольниках (исключая центральный) можно повторить описанную процедуру разбиения. Возвра- Возвращаясь к узору Серпинского (см. рис. 2.4), вновь отметим, что и в средних треугольниках второго уровня разбиения точки узора не появляются. Эти рассуждения лежат в основе второго варианта алго- алгоритма построения узора Серпинского — варианта рекурсив- рекурсивного разбиения многоугольников. Этот алгоритм не требует генератора случайных чисел. Одно из достоинств второго ва- Рис 2.34. Один цикл разбиения рианта алгоритма состоит в том, что он позволяет использо- треугольника г г г вать заливку многоугольников на экране. Стратегия построе- построения узора довольно очевидна. Исходный треугольник делится на четыре, соединяя попарно середины прилежащих сторон, а затем средний треугольник изымается из дальнейших операций. Цикл повторяется с каждым из оставшихся трех тре- треугольников до тех пор, пока размеры сформированных треугольников не станут достаточно малы — сравнимы с размерами пикселя. 90 Глава 2. Графическое программирование
Этот процесс реализуется с помощью рекурсивно вызываемой функции, т.е. функции, вызы- вызывающей саму себя. Начинаем с построения исходного треугольника по трем заданным вершинам: void triangle( point2 a, point2 b, point2 с) { glBegin(GLJTRIANGLES); glVertex2fv(a); glVertex2fv(b); glVertex2fv(c); glEnd(); } Пусть вершины треугольника будут представлены элементами массива point2 v[3]; тогда средние точки треугольника также образуют массив ш[3], элементы которого вычис- вычисляются в приведенном ниже фрагменте программы: for(j=0; j<2; j++) m[0] for(j=0; j<2; j++ for(j=0; j<2; j++) m[2][j]=(v[l][j]+v[2][j])/2.0; Имея в своем распоряжении эти шесть точек, можно использовать функцию triangle () для формирования трех треугольников: вершины первого— (v[0], m[0], m[l]), второго — (v[2], m[l], m[2]), третьего—(v[l], m[2], m[0]). Нам не нужно выводить изображения этих треугольников на экран — они потребуются для выполнения дальнейшего деления. Та- Таким образом, процесс становится рекурсивным. Определим рекурсивную функцию di- vide_triangle(point2 a, point2 b, point2 c, int k), которая будет выводить изображе- изображение треугольника на экран только тогда, когда аргумент к будет равен нулю. В противном случае функция будет выполнять разбиение треугольника, заданного аргументами a, b и с, и уменьшать значение к. Ниже приведен текст этой функции: void divide_triangle(point2 a, point2 b, point2 с, 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]= /* Разбиение всех треугольников, кроме среднего */ 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)); /* Вывод треугольника на экран и завершение работы */ 2.8. Многоугольники и рекурсия 91
При такой организации программы функция display() становится тривиальной. В ней используется объявленное глобальным в функции main() значение переменной п, которое за- задает количество циклов рекурсии13, и вызывается функция divide_triangle(). void display(void) { glClear(GL_COLOR_BUFFER_BIT); divide_triangle(v[O], v[l], v[2], n); glFlush(); A aa aa AA AA AAAAAAAA Л A^ _ AAAAAA A ^ .A A A A А Л A A AA AAA AAA AAAAAAAA Л A A AAA Оставшаяся часть программы та же, что и в предыдущем варианте, за исключением того, что в ней считывается значение переменной п. Изображение, которое сформируется после пяти циклов выполнения рекурсивной проце- процедуры, представлено на рис. 2.35. Полный текст программы приведен в при- приложении А. Рис. 2.35. Изображение, которое сформируется после пяти циклов рекурсивного разбиения треугольников Рис. 2.36. Тетраэдр 2.9. Трехмерный узор Серпинского Я неоднократно подчеркивал, что двухмерная графика— это только частный случай трехмерной, но мы до сих пор не рассматривали програм- программы, в которых использовалось бы представление трехмерных объектов. В этом разделе будет показано, как преобразовать рассмотренную выше про- программу построения узора Серпинского в трехмерную. В этом разделе мы воспользуемся обоими методами формирования узора — и точечным, и ме- методом рекурсивного разбиения. В обоих случаях все начинается с про- пространственного аналога треугольника — тетраэдра (рис. 2.36). 2.9.1. Использование трехмерных точек Поскольку тетраэдр является выпуклым геометрическим телом, то средняя точка отрез- отрезка, проведенного между некоторой вершиной и любой точкой внутри тетраэдра, также ле- лежит внутри тетраэдра. Следовательно, можно использовать процедуру, подобную описан- описанной ранее, но на сей раз вместо трех вершин треугольника включить в рассмотрение четы- четыре вершины тетраэдра. Обратите внимание на то, что, до тех пор пока любые три вершины не будут коллинеарны (т.е. лежать на одной прямой), можно случайным образом выбирать любую из четырех вершин тетраэдра и это не повлияет на характер сформированного в ре- результате изображения. 13Обращаю ваше внимание на то, что, работая с OpenGL, нам часто придется передавать данные в функции с обратным вызовом через глобальные переменные. Хотя желательно избегать использования гло- глобальных переменных, но при обращении к функция» с обратным вызовом у нас просто нет иного выхода. 92 Глава 2. Графическое программирование
Первым делом нужно внести определенную коррекцию в функцию 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] = (p[0]+vertices[j][O])/2.O; р[1] = (p[l]+vertices[j][l])/2.0; р[2] = (p[2]+vertices[j][2J)/2.0; /* Вывести точку на экран */ glBegin(GL_POINTS); glColor3f(p[0]/250.0,p[l]/250.0,р[2]/250.0); glVertex3fv( p ); 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.9. Трехмерный узор Серпинского 93
На рис. 2.37 и ил. 1 на цветной вклейки по- показано, что если сформировать достаточно большое количество точек, то изображение будет походить на исходный тетраэдр, из ко- которого "вынут" меньший тетраэдр в центре. Рис. 2.37. Трехмерный узор Серпинского 2.9.2. Использование многоугольников в трехмерном пространстве Теперь рассмотрим, каким образом в трехмерном пространстве можно реализовать второй подход к построению узора. Гранями тетраэдра являются четыре треугольника, заданные че- четырьмя вершинами. Для каждой из четырех граней можно применить алгоритм разбиения, точ- точно такой же, как тот, который применялся к единственному треугольнику при работе в двухмер- двухмерном пространстве. В результате текст новой программы в значительной мере воспроизводит текст программы, формирующей двухмерный узор по тому же принципу. Единственное отличие в базовой функции разбиения — использование трехмерных точек вместо двухмерных: void triangle( point3 a, point3 b; point3 с) { glBegin(GL_POLYGON); glVertex3fv(a); glVertex3fv(b); glVertex3fv(c); glEnd(); Функция divide_triangle() выполняет те же операции, но над трехмерными точками: void divide_triangle(point3 a, point3 b, point3 c, int k) point3 ab, ac, be; int j; if (k>0) for(j=0; j<3; for(j=0; j<3; for(j=0; j<3; divide_triangle(a ab[j]= ac[j]= bc[j]=(b[j]+c[j])/2; ab, ac, k-1); divide_triangle(c, ac, be, k-1); divide_triangle(b, be, ab, k-1); 94 Глава 2. Графическое программирование
else(triangle(a,b,c)); /* Вывод треугольника на экран и завершение работы */ } Для разделения четырех граней тетраэдра нужно применить эту функцию к каждой из них: void tetrahedron( int n ) glColor3fA.0,0.0,0.0); divide triangle(v[O], v[l], v[2], k); glColor3f@.0,1.0,0.0); divide triangle(v[3], v[2], v[l], k); glColor3f@.0,0.0,1.0); divide_triangle(v[0], v[3], v[l], k); glColor3f@.0,0.0,0.0); divide_triangle(v[0], v[2], v[3], k); } Здесь переменная п по-прежнему представляет количество циклов выполнения рекурсив- рекурсивной процедуры. При выводе на экран элементарных треугольников каждой грани им следует задать свое значение атрибута цвета, тогда изображение трехмерной структуры будет лучше восприниматься пользователем. Но перед нами стоит еще одна проблема, которую следует решить, если уж мы хотим получить нормальное изображение. 2.9.3. Удаление невидимых поверхностей При выполнении программы, рассмотренной в предыдущем разделе, результат не очень вас впечатлит. Программа будет выводить на экран треугольники в том порядке, в каком они формируются в процессе выполнения деления граней. Этот порядок не имеет отношения к расположению граней в пространстве, каждый треугольник будет заливаться своим цветом, причем те треугольники, которые формируются позже, перекроют нарисованные раньше. Совсем другая картинка открылась бы нашему взору, если бы узор создавался из реальных маленьких непрозрачных треугольников. В этом случае были бы видны только те из них, ко- которые ближе к наблюдателю. На рис. 2.38 схематически показано, в чем состоит суть про- проблемы невидимых поверхностей. Наблюдатель видит прежде всего ближайший к нему уча- участок А, а что касается остальных, то треугольник В оказывается вообще невидим, а в тре- треугольнике С видна только та часть, которая не перекрывается участком А. Даже не вдаваясь в детали алгоритмов анализа невидимых поверхностей, можно сразу заметить, что решающее значение имеет взаимное положение наблюдателя и отобра- отображаемых участков поверхностей, в данном случае— тре- треугольников. Алгоритмы упорядочения объектов в порядке их близости к наблюдателю называются алгоритмами анализа видимости поверхностей (visible-surface algorithms) или ал- алгоритмами удаления невидимых поверхностей (hidden- surface-removal algorithms) — все зависит от точки зрения. Эти алгоритмы мы подробно рассмотрим в главах 4 и 7. В данной программе будет использован алгоритм удаления невидимых поверхностей, получивший в литературе название алгоритма z-буфера (z-buffer algorithm), который поддержива- Рис' 2'38' Проблема удаления _ _;¦ J, невидимых поверхностей ется в OpenGL. Существуют средства управления этим алго- 2.9. Трехмерный узор Серпинского 95
ритмом, позволяющие легко отключать или включать его. В функции main() нужно запросить дополнительную память для работы этого алгоритма — z-буфер или буфер глубины. Это выпол- выполняется спецификацией константы GLUT_DEPTH при вызове функции glutInitDisplayMode(): 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. Трехмерный узор Серпинского после выполнения пяти циклов рекурсивной про- процедуры 2.10. Резюме В этой главе читатели познакомились с основными характеристиками API OpenGL и с тем, как в этой системе реализуются базовые концепции, представленные в главе 1. Хотя пер- первое из рассмотренных в этой главе приложений и является двухмерным, мы все время обра- обращали ваше внимание на то, что двухмерная графика в OpenGL — это только частный случай трехмерной. Было показано, как с минимальными усилиями распространить результаты, по- полученные для двухмерного случая, на трехмерный вариант постановки задачи. В качестве учебной задачи была рассмотрена нетривиальная задача построения узора Серпинского с помощью алгоритмов двух типов. Некоторые математические вопросы, свя- связанные с этой задачей, мы предлагаем вам проанализировать самостоятельно в упражнениях 96 Глава 2. Графическое программирование
после основного текста главы. Более пространное вступление в теорию фракталов читатель найдет в главе 11, где эта теория будет использована для формирования реалистически вы- выглядящих объектов случайной формы. Литературные источники, которые упоминаются в за- заключительном разделе главы, содержат много других примеров интересных кривых и по- поверхностей, которые можно реализовать в довольно простых программах. Обзор развития графических API показал, насколько важно как можно раньше начинать работать с задачами компьютерной графики в трехмерной постановке. Много лет концепту- концептуальная модель перьевого плоттера использовалась в качестве базовой при создании графиче- графических API, в частности Postscript. Разработка международных стандартов для графических API началась в 1970-х годах, и первым результатом в этом направлении было принятие в 1984 году Международной организацией по стандартизации (International Standards Organiza- Organization — ISO) стандарта GKS. Однако основанный на модели перьевого плоттера стандарт GKS распространялся только на двухмерные графические приложения и имел ограниченное при- применение в индустрии автоматизированного проектирования. Хотя позднее он был расширен и охватывал трехмерные приложения (стандарт GK.S-3D), ограничения, связанные с ориента- ориентацией на указанную концептуальную модель, все же остались. API PHIGS и PHIGS+, поначалу применявшиеся только в САПР, изначально создавались как трехмерные на основе модели синтезированной камеры. Парадигма "моделирование-тонирование" реализована в интерфейсе Renderman Interface. Хотя его спецификация охватывает только интерфейс между средствами моделирования и тонирования, этот программный продукт использует высококачественный алгоритм тониро- тонирования на основе трассировки лучей и наряду с другими широко используется при создании мультипликационных фильмов. Графическая система OpenGL появилась как развитие GL API, основанного на модели синтезированной камеры в приложении к конвейерной архитектуре. Пакет GL был разрабо- разработан фирмой Silicon Graphics для выпускаемых ею рабочих графических станций, в которых реализована конвейерная архитектура обработки на базе специализированных СБИС. Поэто- Поэтому, хотя между PHIGS и GL есть много общего, пакет GL был разработан специально для по- получения с высокой скоростью тонированных изображений, близких к реальным. Система OpenGL появилась в ответ на спрос со стороны прикладных программистов, убедившихся в достоинствах программирования с использованием GL и пожелавших реализовать эти прин- принципы на других платформах. При этом оказалось необходимым изъять из пакета функции, входящие в компетенцию операционных систем, и сосредоточить основное внимание на эф- эффективных методах моделирования и тонирования. Приведенные в этой главе примеры и программы касаются прежде всего описания и ото- отображения геометрических объектов. Если рассматривать материал данной главы с точки зре- зрения парадигмы "моделирование-тонирование"', то основное внимание в нем было уделено вопросам моделирования. Но созданные простейшие модели абсолютно неструктурированы. Объекты представлены только набором вершин и атрибутов. В главе 8 будет рассмотрен ие- иерархический подход к моделированию, который позволяет представить в модели отношения между объектами. Но, тем не менее, и на этой базе можно создавать довольно впечатляющие графические программы. 2.11. Рекомендуемая литература Узор Серпинского является отличным примером для ввода читателя в мистический мир геометрии фракталов, которая в разных аспектах обсуждается в работах [Bar93, HH90, Мап82. РгиЩ. Пакеты API, построенные на базе модели перьевого плоттера, используются в Postscript [Ado85] и LOGO [Pap81]. LOGO поддерживает так называемую "черепашью", или "паучью" 2.11. Рекомендуемая литература 97
графику (turtle graphics). Этот пакет несложен в изучении и пригоден для построения разного рода двухмерных математических кривых. О нем еще пойдет речь в главе 8 (см. упр. 2.4). Международные и национальные графические стандарты описаны в целом ряде технических документов: [ANS185] — GKS, [ISO88] — GKS-3D, [ANS188] —PH1GS, [PHIG89] — 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 [Ope97,a] и справочник OpenGL Reference Manual [Ope97,b]. Существует и формальная спецификация OpenGL— [Seg92]. Во втором издании руководства программиста Programmer's Guide описана библиотека GLUT, разработанная Марком Килгардом (Mark Kilgard) [Kil94,b]. В это руководство включено множество приме- примеров использования функций OpenGL. Библиотека GLUT была разработана для операционной системы X Window [KH96J, но ее новые версии применимы и в операционных системах Win- Windows 98 и NT. В первой редакции руководства для интерфейса с X Window использован бо- более простой инструментальный пакет aux. Конвертирование программ, написанных с ориен- ориентацией на aux в программы, работающие с библиотекой GLUT, не представляет труда. Много информации об OpenGL и примеры программ можно найти и в Internet. Достаточно много адресов соответствующих страниц вы найдете в самом начале приложения А. Упражнения 2.1. Небольшая модификация алгоритма формирования узора Серпинского с помощью рекурсивного разбиения треугольников позволяет создавать с помощью треуголь- треугольников изображения фрактальных гор {fractal mountains) в компьютерных мульт- мультфильмах. После определения средних точек на каждой стороне треугольника нужно слегка исказить эти значения случайным образом, а затем выполнять разбиение. Разработайте программу, которая будет формировать такие треугольники без за- заливки. Позже можно будет распространить этот алгоритм и на трехмерный случай и добавить тонирование. После нескольких циклов разбиения получится достаточно детальное изображение пирамидальной горы с неровным рельефом. 2.2. Узор Серпинского, сгенерированный в предыдущем упражнении, демонстрирует геометрические объекты, которые изучаются в геометрии фракталов (подробно она будет рассмотрена в главе 8). Предположим, что формируется узор из идеаль- идеальных математических линий, т.е. двухмерных многообразий, имеющих длину, но не имеющих толщину. Какая часть области исходного треугольника окажется в преде- пределе не заполнена этими линиями после того, как центральный треугольник при каж- каждом разбиении изымается из дальнейшего процесса? Рассмотрите также значение периметра треугольников, оставшихся после удаления центрального. К какому зна- значению в пределе стремится суммарный периметр всех оставшихся треугольников? 2.3. На самом нижнем уровне обработки мы манипулируем с битами в буфере кадра. В составе OpenGL имеются команды работы с пикселями, позволяющие напрямую обращаться к буферу кадра. Вы можете поэкспериментировать с простыми растро- 98 Глава 2. Графическое программирование
выми алгоритмами формирования отрезков или окружностей, используя в качестве базовой OpenGL-функцию glPoint(). Разработайте библиотеку подобных функций формирования примитивов, которая позволит создать буфер кадра в памяти. Ядро такой библиотеки должны составить функции WritePixel() и ReadPixel (). В со- составе библиотеки также должны быть функции заполнения и отображения буфера кадра и запуска программы пользователя, которая будет записывать и считывать коды засветки отдельных пикселей. 2.4. " Черепашья " графика (turtle graphics) — это метод построения штриховых изо- изображений, альтернативный позиционному. В основе этого метода лежит идея вос- воспроизведения следа черепахи, ползущей по экрану с закрепленным в ее центре пе- пером (перо способно подниматься и опускаться). Текущее состояние черепахи опи- описывается триадой (л-, у, 9)— положением центра и углом ориентации. В типовом API, поддерживающем такой режим, есть следующие функции: init(x,y,theta); /* Установка положения и ориентации черепахи */ forward(distance); right(angle); left(angle); pen(up_down); Реализуйте библиотеку функций черепашьей графики на базе OpenGL. 2.5. Используя библиотеку, разработанную при выполнении предыдущего упражнения. напишите программу построения узора Серпинского и фрактального изображения горы (алгоритмы построения такого изображения рассматриваются в упр. 2.1 и 2.2). 2.6. Уже не одно столетие математиков интересуют свойства и методы построения кри- кривых заполнения пространства (space-filling curves). В пределе такие кривые беско- бесконечны, но, во-первых, пространство заполнения ограничено конечным прямоуголь- прямоугольником, а во-вторых, кривая не должна быть самопересекающейся. Многие из таких кривых могут быть сгенерированы с помощью итеративной процедуры. Рассмотри- Рассмотрите "правило", представленное на рис. 2.40, которое заменяет одиночный отрезок че- четырьмя более короткими. Разработайте программу, которая начинает работу с тре- треугольника, а затем итерационно применяет сформулированное правило замены ко всем отрезкам. Объект, который сформирует подобная программа, называется сне- снежинкой Коха (Koch snowflake). Другие типы кривых заполнения пространства опи- описаны в книгах [НИ90, Ваг93]. 111 1 Рис. 2.40. Формирование снежинки Коха 2.7. Изображение простейшего лабиринта можно построить, начав с прямоугольного мас- массива ячеек. Каждая ячейка имеет четыре стороны. В каждом цикле итерационной процедуры удаляется одна из сторон ячейки, но только не та, что лежит на периметре ограничивающего прямоугольника. Процедура продолжается до тех пор, пока все ячейки не станут связаны "проемами" в стенах. Затем создается вход и выход из лаби- лабиринта, для чего нужно удалить "наружную" стену в двух ячейках, примыкающих к ог- ограничивающему прямоугольнику. Простой лабиринт такого типа показан на рис. 2.41. Разработайте OpenGL-программу, которая принимает в качестве аргументов построе- построения лабиринта размером Л'хЛ/значения двух целых чисел Л; и А/. Упражнения 99
Рис. 2.41. Про- Простои ла- лабиринт 2.8. Подумайте, как адаптировать цветовую модель RGB, используе- используемую в OpenGL, для представления модели цветов с вычитанием основных компонентов. 2.9. В этой главе было показано, что базовой операцией графической системы является отображение точки (х,у), которая лежит во внутренней области отсекающего прямоугольника, в точку (дгЛ, yv), которая принадлежит видовому окну на экране. Предположим, что отсекающий прямоугольник и рамка видового окна определены вызовами функций OpenGL: glViewport(u, v, w , h); glu0rtho2D(x_min, xjnax, yjnin, yjnax); Отыщите математическое соотношение, связывающее пары координат (х, у) с па- парами (xs,ys). 2.10. Во многих графических API используется относительный способ задания положе- положения. В подобных API для вычерчивания отрезков и многоугольников имеются функции move_rel(x,y); line_rel(x,y); Функция move_rel() перемещает курсор на заданное расстояние в новое положе- положение. Текущая позиция курсора является внутренней переменной состояния систе- системы. Функция line_rel() не только перемещает курсор, но и вычерчивает отрезок с заданными проекциями на оси координат, начиная с предыдущей позиции кур- курсора. Каковы преимущества и недостатки относительного задания параметров графических элементов по сравнению с заданием в абсолютных значениях, как это делается в OpenGL? Каким образом реализовать относительный способ в рамках API OpenGL? 2.11. В разделе 2.3 при анализе положения точки относительно контура многоугольника были проигнорированы проблемы, которые могут возникнуть, если одна или не- несколько сторон многоугольника окажутся параллельными линиям растра. В чем, по- вашему, суть этих проблем? Как с ними справиться? 2.12. С практической точки зрения анализ каждой точки некоторой области на принад- принадлежность внутренней или внешней области многоугольника крайне неэффективен. Разработайте общую стратегию, которой нужно следовать, чтобы избежать анализа всего массива точек. 2.13. В разделе 2.3 было сказано, что в OpenGL многоугольник задается списком его вершин. Как можно в рамках системы OpenGL задать многоугольник его ребрами? 2.14. Разработайте тест простоты многоугольника. 2.15. На рис. 2.42 показано множество многоугольни- многоугольников, образующее полигональную сеть {polygonal mesh). Эти многоугольники имеют совместные грани и вершины. Разработайте один или не- несколько вариантов структур данных, описываю- описывающих такую сеть. "Хорошая" структура данных должна включать информацию о совместном ис- использовании вершин и ребер. В рамках системы OpenGL отыщите эффективный метод отобра- отображения сети, представленной такой структурой. Лс ш Полигональная сеть 100 Глава 2. Графическое программирование
2.16. В системе OpenGL с каждой вершиной можно связать некоторый цвет. Если вер- вершины отрезка имеют разные цвета, OpenGL при вычерчивании отрезка плавно из- изменяет (интерполирует) цвет линии по мере перемещения от начальной вершины к конечной. Точно так же вычерчиваются и ребра многоугольников. Воспользуйтесь этим свойством OpenGL и разработайте программу вычерчивания треугольника Максвелла {Maxwell triangle) — равностороннего треугольника, вершины которого имеют красный, зеленый и синий цвета. Как соотносятся треугольник Максвелла и цветовой куб? 2.17. Включив в модель описание некоторых физических явлений, можно получить до- довольно интересные изобразительные эффекты. Смоделируйте прыгающий шарик в двухмерном пространстве, используя явления гравитации и упругого столкновения с поверхностью. Сам шарик можно представить в виде замкнутого многоугольника с достаточно большим количеством сторон, чтобы он выглядел на экране как гладкий. 2.18. В развитие предыдущего упражнения подумайте о том, как смоделировать игру в бильярд (это задача посложнее, чем простой "попрыгунчик"). Придется просчиты- просчитывать взаимодействие множества шаров со столом и друг с другом. Подсказка. Начните с двух шаров и подумайте над тем, как выяснить, столкнутся ли они. 2.19. В сообщениях о некоторых дисплеях на ЭЛТ анонсируется, что они способны вос- воспроизводить любые четыре из 64 цветов. Какую информацию о буфере кадра и ка- качестве дисплея несет такое сообщение? 2.20. Разработайте программу анализа выпуклости двухмерного многоугольника. 2.21. Для многих новичков, только приступивших к работе с OpenGL, большую слож- сложность представляет множество вариантов базовых функций, как, например, glVer- tex*(). В таких языках, как C++, можно использовать одно и то же имя функции при работе с разными типами аргументов — заботу о выборе подходящего варианта реализации функции берет на себя компилятор. Разработайте библиотеку функций C++, которые должны выполнять роль посредника между прикладной программой и OpenGL и таким образом облегчить жизнь прикладному программисту — позво- позволить ему пользоваться единственным именем функции и неявно специфицировать ее вариант через типы аргументов. Упражнения 101
ГЛАВА 3 Ввод и взаимодействие с пользователем В данной главе мы рассмотрим интерактивные графические программы. В этот класс входит огромное количество самых разнообразных приложений — от систем проекти- проектирования до программ управления большими системами, в которых используется графи- графический интерфейс, систем виртуальной реальности и компьютерных игр. Обсуждение этой темы я разделил на три части. Сначала я познакомлю читателей с устрой- устройствами ввода, которые используются одной из взаимодействующих "сторон", — пользователем графической программы. Устройства ввода мы рассмотрим с двух точек зрения: как способ описания физических устройств их реальными характеристиками и как способ представления этих устройств в прикладной программе. Затем мы рассмотрим сетевую среду и графические приложения типа "клиент/сервер", работающие в такой среде. Идеи организации подобных приложений мы будем использовать для создания подсистемы ввода информации, управляемой событиями. В последней части главы будет рассмотрена интерактивная графическая программа, изменяющая изображение в соответствии с командами пользователя. Эта программа продемон- продемонстрирует возможности организации эффективного взаимодействия человека и компьютера. 3.1. Интерактивная компьютерная графика Одно из основных достоинств компьютерной технологии состоит в том, что она позволя- позволяет пользователю оперативно взаимодействовать с компьютером в процессе решения опреде- определенной проблемы. При этом со стороны компьютера роль средства "общения" предоставля- предоставляется дисплею, на экране которого компьютер демонстрирует результат своей работы. Отсчет современной эры интерактивной компьютерной графики следует вести от проекта Sketch- 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. Начнем изложение материала этой главы с описания нескольких типов устройств ввода и способов организации взаимодействия с пользователем на их базе. Затем будет рассказано о том, как эти устройства можно применять в сетевых системах класса "клиент/сервер". После 104 Глава 3. Ввод и взаимодействие с пользователем
этого будут описаны пакет API, обеспечивающий минимально необходимый набор функций взаимодействия, и простая графическая программа на его основе. 3.2. Устройства ввода Устройства ввода можно рассматривать с двух точек зрения. Прежде всего, их можно анализировать как физические устройства, например клавиатуру или мышь, и рассматривать физические принципы работы. Конечно же, программист должен иметь определенное пред- представление о том, как работают устройства, с которыми он сталкивается практически ежеми- ежеминутно. Однако с точки зрения прикладного программирования нежелательно использовать в программе конкретные характеристики физического устройства определенного типа. Следует скорее рассматривать такое устройство как логическое, свойства которого можно специфици- специфицировать в терминах функций, которые оно выполняет в прикладной программе. Логическое устройство характеризуется интерфейсом достаточно высокого уровня с программой поль- пользователя, а не физическими параметрами. Термин "логическое устройство" знаком всем про- программистам, работающим с языками высокого уровня. Например, в языке С ввод и вывод данных осуществляется с помощью функций printf (), scanf (), getchar() и putchar(), ар- аргументами которых являются переменные стандартных типов языка. При выводе строки с помощью функции printf () в качестве физического устройства используется дисплей тер- терминала, принтер или файл на диске. Выводимые данные могут быть использованы как вход- входные для другой программы. Нюансы форматирования, характерные для конкретного устрой- устройства, при этом никак не заботят прикладного программиста. В компьютерной графике работа с логическими устройствами ввода организуется не- несколько сложнее, поскольку формат входной информации может сильно отличаться от про- простой последовательности битов или символов, которой вполне достаточно для неграфических данных. Например, можно использовать мышь — физическое устройство — либо для указа- указания позиции на экране ЭЛТ, либо для выбора определенного пункта меню. .В первом случае в прикладную программу возвращается пара значений (х, у) в некоторой системе координат, а во втором — программа получает целое число, которое однозначно идентифицирует выбран- выбранный пункт меню. Такое отделение физического устройства от логического позволяет про- программе использовать одно и то же физическое устройство совершенно по-разному. Кроме то- того, такая программа способна нормально работать без всякой коррекции даже в том случае, когда мышь заменена другим физическим устройством, например планшетом или трекболом. 3.2.1. Физические устройства ввода Особенности конструкции каждого устройства позволяют ему специализироваться на вы- выполнении определенного круга задач. Мы будем придерживаться того же подхода, который использован в большинстве литературных источников по машинной графике, — разделение на две большие группы: устройства указания и клавиатуры. С помощью устройства указания {pointing device) пользователь может указать позицию на экране. Практически все устройства этой группы оснащены парой или несколькими кнопками, которые позволяют сформировать и передать в компьютер какие-либо сигналы или прерывания. Клавиатура (keyboard device) почти всегда представляет собой панель, на которой установлено множество (как правило, свыше сотни) кнопок. Различные варианты устройств этой группы объединяет то, что все они передают в компьютер коды символов1. 'Как правило, при этом используется формат кодов символов ASCII (Американский стандартный код для обмена информацией — American Standard Code for Information Interchange), рекомендованный Американ- Американским институтом стандартов. По ничто не мешает использовать и любую другую кодировку, если того требует специфика приложения. 3.2. Устройства ввода 105
Мышь (рис. 3.1) и трекбол (рис. 3.2) похожи не только по назначению, но часто и по кон- конструкции. Если перевернуть типичную механическую мышь кверху "брюшком", то она будет очень похожа на трекбол. В обоих устройствах вращение шарика преобразуется с помощью пары преобразователей в сигналы, передаваемые в компьютер. Преобразователи измеряют вращение относительно двух взаимно перпендикулярных осей. Рис. 3.1. Мышь Рис. 3.2. Трекбол Существует очень много модификаций устройств этих групп. В некоторых их них исполь- используются оптические, а не механические чувствительные элементы для измерения перемеще- перемещения. Оптическая мышь измеряет расстояние, подсчитывая штрихи на специальной подложке. Маленькие трекболы (trackball) широко используются в портативных компьютерах, где их встраивают прямо в клавиатуру. В некоторые клавиатуры встраиваются приборы, чувствительные к давлению, которые выполняют те же функции, что и мышь или трекбол, но при этом в них отсутствуют под- подвижные элементы. Преобразователи в таких устройствах измеряют величину давления на небольшой выпуклый набалдашник, размещенный между двумя кнопками в средней части клавиатуры. Выходные сигналы мыши или трекбола можно рассматривать как две независимые вели- величины и преобразовать их в координаты положения на двухмерной- плоскости экрана или в ка- какой-либо другой системе координат. Считанные с устройства значения можно сразу же ис- использовать для управления специальной отметкой (маркером, курсором) на экране, но в та- таком режиме подобные устройства используются очень редко. Совсем не обязательно, чтобы формируемые мышью или трекболом сигналы интерпре- интерпретировались как расстояния. Драйвер устройства и прикладная программа могут трактовать их и как значения двух независимых скоростей (см. упр. 3.4). Затем программа может ин- интегрировать последовательность этих значений и получить абсолютные координаты в двухмерной системе. Таким образом, по мере движения мыши по какой-либо поверхности интеграл от скорости дает значения (х, у), которые служат для отображения маркера на эк- экране (рис. 3.3). Интегрируя смещения трекбола (рассматривая их как значения скоростей), можно использовать его как устройство ввода с переменной чувствительностью. Неболь- Небольшие отклонения шара трекбола от заданного "нулевого" положения приводят к медленно- медленному смещению маркера на экране, а большие — к его быстрому движению. При использо- использовании мыши или трекбола в обычном режиме мы имеем дело фактически с относитель- относительным положением устройства. Если переместить указатель каким-либо способом в другое место, не вращая при этом шарик мыши или трекбола, то дальнейшие сигналы будут сме- смещать указатель относительно новой позиции. Абсолютные координаты устройства не счи- тываются прикладной программой. При решении некоторых задач, например при вводе в компьютер графиков, прикладная программа нуждается не в относительных, а в абсолютных координатах устройства ввода. Такую возможность обеспечивают разного рода планшеты (Data tablets). В типичном план- планшете применяется ортогональная сетка проводов, расположенных под его поверхностью 106 Глава 3. Ввод и взаимодействие с пользователем
(рис. 3.4). Положение щупа (stylus) определяется посредством электромагнитного взаимодей- взаимодействия сигналов, проходящих от проводов к щупу. Иногда в качестве планшета используются чувствительные к прикосновению прозрачные экраны, которые наносятся на поверхность ЭЛТ. Небольшие экраны такого типа размещаются иногда на клавиатуре портативных ком- компьютеров. Чувствительные к прикосновению панели можно использовать в режимах абсо- абсолютных и относительных отсчетов. Рис. 3.3. Управление положением указателя мыши Рис. 3.4. Планшет Пожалуй, самую длинную историю имеет использование в компьютерной графике устройст- устройства, получившего при рождении имя световое перо (lightpen). Впервые оно появилось еще в про- проекте Sketchpad. Световое перо содержит фоточувствительный элемент (рис. 3.5), который при приближении к экрану "чувствует" излучение, порождаемое при столкновении электронов с люминофорным покрытием экрана. Если мощность светового импульса превышает определен- определенный порог, фоточувствительный элемент формирует импульс, который передается в компьютер. Анализируя смещение этого импульса относительно начала цикла регенерации, компьютер мо- может четко определить координаты той точки экрана, возбуждение которой "засветило" фото- фотоэлемент (см упр. 3.19). Таким образом, в распоряжении пользователя оказывается устройство непосредственного указания, работающее напрямую с изображением на экране. В те далекие времена еще не была изобретена мышь, но сейчас эти простые и надежные устройства повсеме- повсеместно вытеснили экзотические световые перья. Кроме всего прочего, в графической системе, ра- работающей со световым пером, нужно предпринимать специальные меры, чтобы иметь возмож- возможность считывать координаты точек на темных участках экрана. Рис. 3.5. Световое перо Рис. 3.6. Джойстик Еще одно устройство, которое заслуживает упоминания, — это джойстик (Joystick) (рис. 3.6). Перемещение джойстика в двух взаимно перпендикулярных направлениях воспри- воспринимается преобразователями, интерпретируется как составляющие вектора скорости, интег- интегрируется, а полученные значения используются для управления положением маркера на экра- экране. Интегрирование выполняется таким образом, что неподвижный джойстик в каком-либо промежуточном положении не изменяет положение маркера, а чем дальше джойстик откло- отклонен от начального положения, тем быстрее маркер перемещается по экрану. Таким образом. 3.2. Устройства ввода 107
джойстик играет роль устройства ввода с переменной чувствительностью. Другое преимуще- преимущество джойстика — возможность имитации силовой обратной связи с помощью разного рода пружин. При этом пользователь чувствует, что чем дальше отклонен джойстик, тем большее усилие требуется для его дальнейшего движения. Это очень помогает в работе с разного рода симуляторами и играми. В трехмерной графической системе весьма соблазнительно использовать и трехмерные устройства ввода. Хотя и существуют различные конструкции таких устройств, они все еще не получили широкого распространения, поскольку проигрывают популярным двух- двухмерным устройствам как по стоимости, так и по техническим характеристикам. Пространственный шар (спейсбол — spaceball) очень похож на джойстик, но отличается от него тем, что на рукоятке закреплен шар (рис. 3.7), причем рукоятка в этой конструкции неподвижна. Шар имеет датчики давления, которые измеряют усилие, прикладываемое пользователем. Шар может измерять не только составляющие усилия в трех основных на- направлениях (сверху вниз, от себя или на себя, влево — вправо), но и вращение относитель- относительно трех осей. Таким образом, это устройство способно передавать в компьютер шесть не- независимых параметров (говорят, что устройство имеет шесть степеней свободы), характе- характеризующих как плоскопараллельное смещение, так и вращение. Такого рода устройство можно использовать, например, при настройке положения и ориентации камеры в нашей модели получения изображения. Существуют и другие трехмерные системы измерения и ввода, использующие самые современные технологии, на- например лазерные. В системах виртуальной реальности ис- используются еще более экзотические устройства, позволяю- позволяющие динамически отслеживать положение и ориентацию пользователя. Для приложений, связанных с современной ро- робототехникой и моделированием виртуальной реальности, иногда требуются устройства, обладающие еще большим Рис. 3.7. Пространственный числом степеней свободы, чем упомянутый спейсбол. В по- шар (спеис о.у следнее время появилось множество сообщений о новых раз- разработках в этом направлении, в частности об изобретении "очувствленных" перчаток, кото- которые способны улавливать движения отдельных органов подвижности человека. В тех программах, которые будут рассматриваться в этой книге, мы не будем использо- использовать никакие экзотические устройства, а ограничимся известными всем мышью и клавиату- клавиатурой. Но это отнюдь не значит, что пакет API ограничивает возможность ввода графической информации только двухмерными данными. 3.2.2. Логические устройства Теперь вернемся к рассмотрению устройств ввода с точки зрения возможности их связи с прикладной программой, т.е. будем рассматривать их как логические устройства. Функцио- Функционирование устройства ввода можно описать двумя характеристиками: ¦ какую измерительную информацию передает устройство в прикладную программу; ¦ когда устройство посылает эту информацию. В некоторых API, таких как PHIGS и GKS, рассматривается шесть классов логических устройств ввода. Поскольку в современных операционных системах функцию ввода можно полностью отделить от конструктивных особенностей того или иного прибора, в OpenGL такой подход не применяется. Тем не менее мы кратко познакомим читателей с этими ше- шестью классами, поскольку они демонстрируют, какой широкий спектр форм ввода инфор- информации имеется в распоряжении разработчика прикладной графической системы. Кроме то- 108 Глава 3. Ввод и взаимодействие с пользователем
го, этот перечень поможет нам в дальнейшем проанализировать, как аналогичные функции реализуются в OpenGL. 1. Строковое устройство. Это логическое устройство ввода, способное передавать в прикладную программу текстовую (символьную) информацию в виде строк (после- (последовательностей символов) в определенном формате — чаше всего в виде кодов ASCII. Обычно логические устройства этого типа конструктивно оформляются в виде клавиа- клавиатуры. В таком случае используемая терминология полностью совместима с применяе- применяемой в описании большинства существующих операционных систем. В OpenGL не де- делается различия между логическими строковыми устройствами и физическими уст- устройствами типа клавиатуры. 2. Локатор. Устройство типа локатор передает в прикладную программу информацию о положении в мировой системе координат. Обычно такое логическое устройство кон- конструктивно оформляется в виде устройства указания — мыши или трекбола. В OpenGL мы будем использовать устройства указания именно таким образом, хотя для этого и придется выполнять в прикладной программе промежуточные преобразования из системы координат экрана в мировую систему координат. 3. Указатель. Указатель возвращает в прикладную программу идентификатор указанно- указанного объекта. Обычно в качестве логического устройства этого типа используется та же самая мышь или трекбол, но оно имеет другой интерфейс с прикладной программой. При описании OpenGL будем именовать процессом выбора {selection) процедуру ука- указания какого-либо объекта на экране. 4. Устройство выбора. Устройство такого типа позволяет пользователю выбрать одну из дискретных (т.е. перечислимых) опций. В OpenGL в качестве логического устрой- устройства такого типа используются элементы графического интерфейса, поддерживаемые операционной системой, — меню, полосы прокрутки, экранные кнопки и т.д. Напри- Например, меню, включающее п пунктов, играет роль устройства выбора, позволяющего пользователю выбрать одну из п альтернатив. 5. Циферблат. Это устройство позволяет пользователю вводить значения непрерывных вели- величин. Роль циферблата опять же выполняют различные элементы графического интерфейса, поддерживаемые операционной системой, — шкалы, регуляторы с движками и т.п. 6. Росчерк. Логическое устройство, которое получило столь экзотическое название (в оригинале — stroke), возвращает массив позиций. Фактически его представляет неко- некоторая процедура— например, нажать кнопку мыши, переместить мышь, отпустить кнопку мыши. В результате в прикладную программу должен быть передан массив ко- координат траектории перемещения мыши. 3.2.3. Показания и синхронизация Информацию, передаваемую от физического или логического устройства в прикладную программу, можно условно разделить на две категории: показания устройства и сигнал син- синхронизации от устройства. Показание устройства — это то значение, которое устройство пе- передает в прикладную программу. Сигнал синхронизации — это физический сигнал от устрой- устройства, которым пользователь извещает программу о завершении некоторого этапа процесса ввода (или всего процесса). Например, показания клавиатуры — это строка символов, соот- соответствующая нажатым алфавитно-цифровым клавишам, а сигнал синхронизации формирует- формируется после нажатия клавиши <Enter> или <Retum>. Для устройства типа локатор показаниями являются данные о положении устройства, а сигнал синхронизации формируется после нажа- нажатия определенной кнопки на устройстве указания. 3.2. Устройства ввода 109
Помимо основной информации, которую пользователь вводит явно, показания могут со- содержать и дополнительную информацию, например о текущем состоянии устройства. Напри- Например, устройство типа указатель возвращает в качестве основного компонента показаний идентификатор того объекта, на который указывает пользователь. Если физическим устрой- устройством этого типа является мышь, то сигнал синхронизации вырабатывается после нажатия кнопки мыши (как правило, левой). При разработке прикладной программы следует преду- предусматривать и такой вариант, когда пользователь нажал кнопку мыши, не указывая при этом ни на какой объект. Если показания будут состоять только из идентификатора объекта, то программист в этом случае столкнется с довольно сложной проблемой анализа некорректно- некорректности показаний. Разрешить ее будет значительно проще, если в показания включить и еще один компонент, который будет нести информацию о том, указывает ли устройство в момент формирования сигнала синхронизации на какой-либо объект или нет, не вышел ли маркер устройства за пределы выделенного приложению окна и т.п. 3.2.4. Режимы ввода Помимо существующего множества типов логических устройств ввода, показания от этих устройств могут передаваться тремя разными способами. Эти способы (или режимы) отра- отражают характер взаимодействия процесса съема показаний (или применительно к работе поль- пользователя — ввода показаний) и формирования сигнала синхронизации. Обычно инициализа- инициализация устройства ввода запускает и процесс съема показаний. Для инициализации может по- потребоваться вызов в явном виде определенной функции API или она может выполняться автоматически. В любом случае, как только будет запущен процесс съема показаний, соот- соответствующая информация начинает поступать в буфер, хотя последний в это время и недос- недоступен для прикладной программы. Например, положение мыши отслеживается непрерывно соответствующими средствами операционной системы, независимо от того, нуждается ли прикладная программа в этой информации или нет. В режиме работы по запросу с синхронизацией от пользователя (request mode) показа- показания устройства ввода не передаются в прикладную программу до тех пор, пока устройство не сформирует сигнал синхронизации. Этот режим ввода является стандартным для неграфиче- неграфических приложений, например типичной программы на С, которая требует ввода символов. В таких приложениях, встретив вызов функции ввода символов (например, scanf ()), програм- программа приостанавливает выполнение и ожидает, пока пользователь не введет последователь- последовательность символов, пользуясь клавиатурой терминала. При этом в последовательности могут быть и символы забоя, а сама последовательность может иметь произвольную длину. По мере ввода данные помещаются в буфер клавиатуры, но содержимое последнего передается при- прикладной программе только после того, как будет нажата определенная клавиша (чаще всего <Enter>), которая сформирует сигнал синхронизации. Логическое устройство, например ло- локатор, можно перемещать в требуемую позицию, а затем, нажав кнопку на этом устройстве, сформировать сигнал синхронизации. В результате в прикладную программу поступят коор- координаты положения устройства именно в тот момент, когда была нажата кнопка. Схематиче- Схематически связь между сигналом синхронизации и показаниями в режиме работы по запросу пока- показана на рис. 3.8. Запрос Процесс * I ^^ Процесс съема 1^ ч синхронизации I ^*"u показаний 1 ^»'v Программа I Сигнал ¦ i ' • ' i \ъ mf Показания синхронизации Рис. 3.8. Режим работы по запросу с синхронизацией от пользователя 110 Глава 3. Ввод и взаимодействие с пользователем
В режиме выборок (sample-mode) ввод выполняется немедленно после поступления за- запроса со стороны прикладной программы. Как только в программе будет вызвана соответст- соответствующая функция, показания от устройства ввода передаются в программу. Таким образом, от устройства не требуется формирования какого-либо сигнала синхронизации (рис. 3.9). При работе в режиме выборок в программу поступают показания, введенные пользователем как раз перед , Выборка вызовом функции считывания показаний, — код пР°чвсс сь*м<3 г^ Поогоамма I ^J показаний I ^. npw'H°*rtMU t последней клавиши, нажатой на клавиатуре, ИЛИ .д | Показания „, ,,,„ , .„„,,. ,| положение указателя локатора. При работе через функции API с устройствами Рис. 3.9. Режим выборок ввода в обоих описанных режимах нужно заранее указать в программе, от какого именно устройства следует ввести информацию. Для работы с устройствами используются функции API, подобные приведенным ниже: request_locator(device_id, bmeasure); sample_locator(device_id, Smeasure); В качестве аргументов функций используются идентификатор конкретного устройства и адрес буфера приема информации. Совершенно очевидно, что в обоих режимах игнорируется информация от всех прочих устройств, кроме специфицированного в вызове функции. Такая идеология организации свойственна тем приложениям, в которых ведущую роль играет про- программа, — именно программа определяет, с каким устройством ввода на данном этапе дол- должен работать пользователь. Но если мы хотим отдать "пальму первенства" пользователю, оба эти режима ввода не подходят. Например, симулятор полета оснащен несколькими устройст- устройствами ввода—джойстиком, шкалами, кнопками и переключателями, причем именно пользо- пользователь-пилот выбирает, какое из устройств нужно использовать в данной ситуации. Разрабо- Разработать программу, которая обращалась бы ко всем этим устройствам в режиме запроса или вы- выборки, практически невозможно, поскольку заранее неизвестно, какое из них понадобится пилоту в той или иной ситуации. Рассуждая на обобщенном уровне, приходим к заключению, что эти режимы не удовлетворяют требованиям современных систем активного взаимодейст- взаимодействия пользователя с компьютером. Третий режим — режим ввода, синхронизируемый событиями (или короче, режим об- обработки событий — event mode), позволяет реализовать обслуживание множества одно- одновременно работающих устройств ввода. Описание этого режима я разбил на три стадии. Сначала я покажу, как режим обработки событий можно описать в терминах других режи- режимов в пределах парадигмы "показания-синхронизация". Затем будут рассмотрены основ- основные концепции архитектуры "клиент/сервер", в которой режим обработки событий играет главенствующую роль. Третья стадия — описание методики использования библиотеки GLUT при создании программ на OpenGL, активно взаимодействующих с пользователем. На этой стадии мы проанализируем демонстрационную программу, в которой применяется подобный интерфейс. Предположим, что наше приложение работает в операционной среде, располагающей множеством устройств ввода, каждое из которых имеет свои средства формирования сигна- сигналов синхронизации и съема показаний. Каждый раз, когда устройство формирует сигнал син- синхронизации, возникает (иногда говорят возбуждается) событие (event). При этом показания устройства вместе с его идентификатором помещаются в очередь событий (event queue). Очередь событий заполняется операционной системой без всякого вмешательства со стороны прикладных программ и независимо от того, как прикладная программа собирается реагиро- реагировать на возникшие события. Один из способов обработки событий в прикладной программе схематически показан на рис. 3.10. Прикладная программа может проанализировать инфор- информацию о событии, которое стоит в очереди первым на обработку, или, если очередь пуста, 3.2. Устройства ввода 111
перейти в состояние ожидания возникновения события. После извлечения из очереди инфор- информации о событии прикладная программа определяет его тип и решает, что делать дальше. Именно такой метод используется в API графических систем GKS и PHIGS. Ожидание Процесс 1 ^^ Процесс съема I Очередь Программа Событие синхронизации I показаний I событий тшя ятшш I Сигнал «„„„„ц,...!!,!,! т.,-! Показания я шт t синхронизации Рис. 3.10. Резким ввода с обработкой событий Другой подход— связать с каждым типом событий функцию с обратным вызовом {callback). Мы будем следовать именно такому подходу, поскольку он используется в подав- подавляющем большинстве современных операционных систем и доказал свою эффективность в системах с архитектурой "клиент/сервер". 3.3. Клиенты и серверы До сих пор, рассматривая процесс ввода информации, мы отстранялись от всех прочих про- процессов, происходящих в операционной среде. Графическая система представлялась этаким мо- монолитным блоком, который имеет ограниченные возможности коммуникации с внешним ми- миром, помимо тех устройств ввода, управление которыми тщательно спланировано в прикладной программе, и, конечно же, графического дисплея. Эта картина разительно изменилась с появле- появлением сетевых многопользовательских систем. Теперь даже автономная система, рассчитанная на работу с отдельным пользователем, организуется таким образом, что ее программное обеспе- обеспечение имеет ярко выраженное деление на компоненты клиентов и серверов, как в сетевой среде. Чтобы графические приложения могли получить широкое распространение в современ- современной компьютерной индустрии, они должны обладать способностью эффективно работать в распределенной вычислительной среде, организуемой на базе локальных (а теперь уже и гло- глобальных) вычислительных сетей. В такой среде основными структурными компонентами сис- системы — ее строительными блоками — являются серверы и клиенты. Серверы и клиенты мо- могут быть распределены между разными компонентами аппаратной части системы, как это по- показано схематически на рис. 3.11, а могут "сосуществовать" и на одном и том же компьютере. Многим хорошо знаком сервер печати, который позволяет всем пользователям сети пользо- пользоваться единственным устройством высококачественной печати. Другие, может быть, менее распространенные примеры — вычислительный сервер, реализованный на суперкомпьютере, доступный прикладным программам, выполняемым на отдельных, менее мощных компьюте- компьютерах рабочих станций, файл-сервер, позволяющий множеству пользователей совместно ис- использовать (а иногда и пополнять) информацию из определенных файлов, и терминальные серверы, которые заняты обслуживанием запросов, поступающих по телефонным каналам. Те пользователи и прикладные программы, которые пользуются услугами таких серверов, назы- называются клиентами или клиентскими программами. В этой ясной и четкой структуре рабочим станциям трудно сразу отвести определенную роль. В принципе, рабочая станция может быть и клиентом, и сервером, и даже более, парал- параллельно выполнять и клиентские программы, и серверные. Представленная здесь модель реализована операционной системой X Window. В даль- дальнейшем мы будем широко пользоваться терминологией, заимствованной из документации этой системы, которая сейчас повсеместно используется и в других операционных системах и хорошо согласуется с графическими приложениями. Рабочая станция, оснащенная растровым дисплеем, клавиатурой, устройством указа- указания, таким как мышь, является графическим сервером. Такой сервер может взять на себя 112 Глава 3. Ввод и взаимодействие с пользователем
оказание "услуг" по выводу информации на экран дисплея или по вводу данных с помо- помощью клавиатуры и устройства указания. На эти услуги может рассчитывать любой кли- клиент, подключенный к сети. Графический сервер Рабочая станция Рабочая станция Сервер-печати Вычислительный сервер Рис. 3.11. Сетевая вычислительная среда Графический сервер Те прикладные программы на OpenGL, которые мы рассматривали ранее (и будем рас- рассматривать впредь), являются, по сути, клиентскими программами, пользующимися "услуга- "услугами" графического сервера. Внутри автономно функционирующего компьютера это разделе- разделение между прикладной программой и специальными графическими средствами кажется не столь уж и существенным, но в сетевой среде прикладная клиентская программа (в том числе и графическая) может выполняться на одном компьютере, а пользователь будет сидеть за эк- экраном совершенно другого компьютера — графического сервера. 3.4. Дисплейный файл С помощью дисплейных файлов (или дисплейных списков) организуется эффективное взаимодействие между графическими клиентами и серверами в сетевой среде. История появ- появления дисплейных файлов уходит корнями в "седую древность" (конечно, по масштабам компьютерной эры). Как уже упоминалось в главе 1, в структуре ранних графических систем использовался компьютер общего назначения (сейчас для такого компьютера используется термин главный компьютер, или хост, а тогда подавляющее большинство компьютеров были "главными"), связанный через блоки цифро-аналоговых преобразователей с ЭЛТ (рис. 3.12). Компьютер передавал на преобразователи графические данные (координаты последователь- последовательных точек вычерчиваемых на экране линий) с частотой регенерации изображения2. В те вре- времена (начало 1960-х годов) вычислительная мощность компьютеров была невелика (по со- современным меркам), а сами компьютеры дороги, а потому использовать графические прило- приложения могли позволить себе только весьма состоятельные клиенты. 'Эта частота зависит от времени послесвечения люминофорного покрытия экрана ЭЛТ, и в ранних систе- системах ее стремились снизить, используя специальные трубки. В современных системах частота регенерации ле- лежит в пределах от 50 до 85 Гц или уменьшена наполовину, если исполыуется чересстрочная развертка. 3.4. Дисплейный файл 113
Главный компьютер Рис. 3.12. Простейший вариант архитектуры графической системы Решение этой проблемы нашли, воспользовавшись со- советом Юлия Цезаря, — "Разделяй и властвуй" (в этом ми- мире новое — это хорошо забытое старое). Были разработа- разработаны специализированные компьютеры — дисплейные про- процессоры, — организация которых схематически представ- представлена на рис. 3.13. Набор команд дисплейного процессора был ограничен, причем большинство команд было ориен- ориентировано на вычерчивание графических примитивов на экране ЭЛТ. Прикладная программа выполнялась на глав- главном компьютере и результатом ее выполнения был дис- дисплейный файл — программа построения изображения в терминах команд дисплейного про- процессора. Этот дисплейный файл сохранялся в дисплейной памяти (функционально— это аналог современного буфера кадра, хотя в нем хранились не коды засветки пикселей, а ко- команды, но описывали они единственный кадр изображения). Если в приложении не использо- использовался активный диалог с пользователем, то, сформировав дисплейный файл, главный компь- компьютер мог заняться чем-нибудь более важным (на то он и главный), а черную работу по реге- регенерации изображения брал на себя дисплейный процессор. Поначалу дисплейный процессор был довольно примитивным, но потом в его состав начали включать специализированные аппаратные блоки выполнения процедур, требовавших большого объема вычислений, напри- например тонирования. В результате через десяток лет вычислительная мощность специализиро- специализированного дисплейного процессора превысила вычислительную мощность того компьютера, который когда-то "гордился" тем, что он главный. Главный компьютер Рис. 3.13. Архитектура графической системы с ис- использованием дисплейного процессора Теперь то, что раньше именовалось дисплейным процессором, называется графически.» сервером, а прикладная программа, ранее выполнявшаяся на главном компьютере, стала кли- клиентской программой. Основной проблемой теперь является не обеспечение регенерации изо- изображения с необходимой частотой, а перегрузка сети при передаче информации между кли- клиентами и серверами. Попутно отмечу, что в высококачественных графических серверах ис- используются специализированные аппаратные средства (как для формирования изображения, так и для взаимодействия с пользователем). Организовать работу с дисплейным файлом можно двумя способами. Можно переслать на графический сервер полное описание изображения — координаты вершин, атрибуты, типы примитивов и дополнительную информацию о визуализации сцены. Напомню, что мы дого- договорились использовать в работе режим немедленного отображения {immediate mode), при котором как только программа выполняет оператор формирования примитива, последний пе- передается на сервер для отображения и в памяти о нем не остается никакой информации . Для ^Изображение примитива сохраняется в буфере кадра и используется для регенерации, но изображение и описание примитива — вещи разные. 114 Глава 3. Ввод и взаимодействие с пользователем
повторного отображения примитива (того же самого, в том же месте экрана) после очистки поля изображения или после внесения некоторых корректив в модель сцены придется заново его сформировать и переслать описание на сервер. Если приложение предназначено для ра- работы со сложными графическими объектами (например, моделью здания), причем пользова- пользователь активно "вмешивается" в процесс, объем информации, передаваемой от клиента серверу, может оказаться достаточно большим и привести к "заторам" в сети. Второй способ предполагает использование дисплейного файла (теперь чаще использует- используется термин дисплейный список, поскольку этот файл на диске не сохраняется). Такой способ часто называют режимом отображения с сохранением (retained-mode), поскольку при рабо- работе в этом режиме объект определяется однократно, а его описание помещается в дисплейный список, который сохраняется на сервере. Вывести его на экран можно вызовом специальной функции со стороны программы-клиента. В результате, во-первых, снижается объем инфор- информации, передаваемой от клиента серверу, во-вторых, в дисплейном списке описания объектов представлены таким образом, что появляется возможность использовать для их отображения специализированные аппаратные средства, которыми оснащен графический сервер. Оказыва- Оказывается, что оптимальная структура графической системы должна включать высокопроизводи- высокопроизводительный компьютер общего назначения, способный быстро выполнять обработку числовой информации (на него возлагается выполнение прикладной программы), и специализирован- специализированный графический компьютер, оснащенный аппаратными средствами формирования изобра- изображений, в том числе и средствами тонирования с учетом освещения и т.д. Короче говоря, от чего в начале 1970-х годов начали уходить, к тому сейчас опять вернулись, правда, в новом исполнении. В концепции использования дисплейного списка имеются и определенные недостатки. Для хранения списка требуется память на сервере и, кроме того, на его формирование и обработку тратится определенное время. Но эти дополнительные затраты окупаются высо- высокой эффективностью специализированной аппаратуры преобразования дисплейного списка в изображение. 3.4.1. Формирование дисплейного списка и преобразование его в изображение Для работы с дисплейными списками необходимо иметь в своем распоряжении средства создания списка и включения в него нужной информации. Механизм доступа к элементам списка должен быть достаточно гибким и обеспечивать прикладной программе значительную свободу. В составе OpenGL имеется небольшой набор функций манипулирования содержи- содержимым дисплейного списка, и ниже на примерах будет показано, как ими пользоваться4. Дисплейные списки формируются по той же схеме, что и графические примитивы. Спи- Список открывается функцией glNewList(), а закрывается функцией glEndList(). Операторы между этими двумя вызовами формируют содержимое списка. Каждый дисплейный список имеет уникальный идентификатор — целое число, которое определяется с помощью дирек- директивы макроопределения #def ine языка С. Например, приведенный ниже фрагмент програм- программы формирует дисплейный список, в котором имеется один объект — красный квадрат. Этот фрагмент практически идентичен фрагменту, приведенному в главе 2, но на сей раз информа- информация не отправляется сразу же на отображение, а помещается в список. tdefine BOX I /* Или любое другое ранее не использованное целое число */ 4 В пакете PHIGS имеются структуры ^structures,/, а в пакете GKS— сегменты ^segments/ причем и те и другие поддерживают многие из характеристик дисплейных файлов OpenGL. 3.4. Дисплейный файл 115
glNewList(BOX, GL_COMPILE); glBegin(GL_POLYGON); glColor3fA.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= I ; i<5; glLoadIdentity(); gluOrtho2D(-2.0*i, 2.0*i, -2.0*i, 2.0*i); glCallList(BOX); } При каждом вызове функции glCallList() квадрат будет повторно выводиться на экран с учетом новых параметров отсекающего прямоугольника. В последующих главах я познакомлю вас с матрицами преобразований, которые позволяют использовать дисплейный список для моделирования динамических сцен. Но учтите, поскольку имеется возможность манипулировать дисплейным списком, нужно быть предельно вниматель- внимательным при программировании такого рода процедур, иначе потом могут появиться совершенно нежелательные и часто непредсказуемые эффекты. Например, в приведенном выше дисплейном списке текущий цвет изменяется на красный. После этого все объекты будут выводиться на эк- экран красным цветом до тех пор, пока в каком-либо другом списке не встретится функция, уста- устанавливающая другой текущий цвет. Проще всего "предохраниться" от подобного эффекта с по- помощью стека матриц и атрибутов, который поддерживается пакетом OpenGL. Стек— это структура данных, в которой элемент, включенный в структуру последним, извлекается из нее первым. Таким образом, можно поместить в вершину соответствующего стека (иногда говорят "затолкать в стек") текущее значение используемого атрибута или матрицы, а затем восстано- восстановить его, извлекая из стека это значение (одновременно оно удаляется из стека). Поэтому стан- стандартная последовательность операций, обеспечивающая сохранение-восстановление текущих параметров состояния, предусматривает сначала сохранение их в соответствующих стеках, за- 116 Глава 3. Ввод и взаимодействие с пользователем
тем изменение текущего значения, заполнение дисплейного списка примитивами, а затем вос- восстановление значений параметров— извлечение их из стеков. Поэтому в начале процедуры формирования стека вы часто увидите вызовы функций glPushAttrib(GL_ALL_ATTRIB_BITS); glPushMatrix(); В конце процедуры в таком случае должны присутствовать вызовы функций glPopAttrib(); glPopMatrix(); При описании методов построения иерархических графических моделей в главе 8 мы бу- будем очень часто обращаться к стекам матриц и атрибутов. В составе OpenGL есть еще несколько функций, облегчающих работу с дисплейным спи- списком. Когда приходится работать со множеством дисплейных списков, то для формирования последовательных значений идентификаторов списков можно воспользоваться функцией glGenLists(<количество>), которая возвращает первое целочисленное значение в последо- последовательности из <количество>, еще не использованное в программе в качестве идентификато- идентификатора. Функция glCallLists() позволяет запустить процесс отображения сразу нескольких дис- дисплейных списков. Очень хорошим примером использования возможностей, которые откры- открывают дисплейные списки, является формирование текста. В следующем разделе будет подробно описана технология формирования текста с помощью средств OpenGL, но вы мо- можете при чтении этой главы и опустить этот материал, а вернуться к нему позже. Однако хочу обратить ваше внимание на то, что описанная методика демонстрирует, какими гибкими воз- возможностями обладает API и как это облегчает прикладному программисту решение множест- множества проблем. К теме дисплейных списков мы вернемся еще раз в главе 8, когда будем обсуж- обсуждать использование графических приложений в сети World Wide Web. 3.4.2. Дисплейные списки и формирование текста В главе 2 были описаны два способа формирования текстовых символов — растровый и штриховой. Независимо от того, какой из этих методов выбран, нам понадобится определен- определенная программа, описывающая весь набор символов определенного шрифта. Предположим, что мы остановились на растровом шрифте, в котором каждый символ должен быть описан матрицей битов размером 12x10. В результате для хранения каждого символа нужно выде- выделить память объемом 15 байт. Проще всего формировать изображение строки, отсылая на сервер матрицу битов каждого очередного символа в порядке их по- появления в строке. Таким образом, придется отсылать по 15 байт на Qi Input Output каждый символ строки. Если же сформировать штриховой шрифт, состоящий из отрезков, то на каждый символ "уйдет" разное коли- количество отрезков и соответственно потребуется разный объем памя- g. ти. Можно пойти дальше и сформировать шрифт из многоугольни- многоугольников с заливкой, как показано на рис. 3.14. Для буквы "I" много труда не потребуется, но чтобы получить плавное изображение буквы "О", РиСщ 3t4- Штриховые символы: а — мно- придется аппроксимировать ее контур множеством коротких отрез- r r r j г г г гоугольники с за- ков. В среднем на полный комплект оукв такого шрифта потребует- чивкой- б укруп- ся гораздо больше памяти, чем 15 байт на символ. Если приложение пенное изображе- должно выводить на экран достаточно длинный текст, то на сервер ние контуров придется пересылать очень много информации, что приведет к зна- значительной загрузке сети. Все, что говорилось выше, должно подвести вас к заключению о том, что такая техноло- технология вывода текста в приложении "клиент/сервер" неэффективна. Более рационально исполь- 3.4. Дисплейный файл 117
зовать дисплейные списки (по одному на каждый символ) и сохранить таким образом описа- описание шрифта на сервере. Это очень похоже на использование стандартных растровых матриц символов в алфавитно-цифровых терминалах. Весь набор символов сохраняется в ПЗУ тер- терминала, а затем каждый символ выводится на экран при получении его ASCII-кода— одного байта. Отличие заключается только в качестве получаемого изображения и количестве шриф- шрифтов. Ничто не мешает нам определить столько шрифтов, сколько сможет хранить память сер- сервера, а затем использовать описание каждого символа, составленного из отрезков, как любой другой графический объект, и применять по отношению к нему любые преобразования — масштабировать, поворачивать или смещать. Таким образом, основная идея — передать на сервер описание всех символов шрифта, а затем запрашивать отображение нужных символов, передавая всего один байт, — демон- демонстрирует, насколько эффективным может оказаться использование дисплейных списков в OpenGL. Процедура, по сути, та же, что и при работе с растровыми шрифтами. Сначала нужно сформировать множество из 96 воспроизводимых ASCII-символов или расширенный набор из 256 символов, если предполагается использовать приложение не только в англоязычных странах. Определим функцию OurFont(char с), которая будет вычерчивать любой ASCII-символ с. Эта функция в общих чертах имеет следующий вид: void OurFont(char с) { switch(c) { case 'a': break; case 'A': break; В каждом блоке case нужно позаботиться о размещении символа на экране — каждый очередной символ должен выводиться на определенном расстоянии справа (а иногда и слева) от предыдущего символа. Для перемещения изображения можно воспользоваться функцией glTranslate(). Пусть, например, у нас есть описание буквы "О" в виде дисплейного списка и желательно вписать ее изображение в единичный квадрат. Соответствующий фрагмент функ- функции OurFont() будет выглядеть следующим образом: case 'О': glTranslatef@.5, 0.5, 0.0); /* Передвинуть в центр */ glBegin(GL_QUAD_STRIP); for (i=0; i<=12; i++) /* 12 вершин */ { angle = 3.14159/6.0*i; /* 30 градусов в радианах */ glVertex2f@.4*cos(angle), 0.4*sin(angle)); glVertex2f@.5*cos(angle), 0.5*sin(angle)); } glEnd(); glTranslatef@.5, -0.5, 0.0); /* Передвинуть в правый нижний угол */ break; 118 Глава 3. Ввод и взаимодействие с пользователем
В этом фрагменте окружность (точнее, две концентрические окружности) аппроксимируется 12 четырехугольниками (полосой из 12 участков). Каждый прямоугольник будет залит в соответст- соответствии с текущими значениями атрибутов. Поскольку мы еще не рассматривали методы выполнения геометрических преобразований — это материал главы 4, — придется объяснить суть используе- используемых в этом фрагменте формул. Все преобразования выполняются с двухмерными объектами. Сле- Следовательно, каждый из них определен на плоскости z=0 и можно использовать любую систему ко- координат для его описания. Предполагается, что каждый объект (символ шрифта, включая и меж- межсимвольный интервал) вписан в прямоугольник5. Обычно отображение строки символов начинается с левого нижнего угла первого из них. Следующий символ размешается таким образом, что его левый нижний угол совпадает с правым нижним углом предыдущего символа. Первый вызов функции glTranslatef () в приведенном фрагменте задает смещение в центр прямоугольника, который в данном случае совпадает с центром окружности. Можно считать, что функция glTranslatef () сдвигает начало теку- текущей системы координат, т.е. системы, в которой будут отсчи- тываться координаты всех последующих графических объек- объектов. Затем формируются вершины, расположенные вдоль двух концентрических окружностей, одна из которых имеет радиус 0.5, а вторая — 0.4 (рис. 3.15). После того как таким спосо- способом будут сформированы 12 четырехугольников, начало сис- системы координат переносится в правый нижний угол. В резуль- результате все готово к вычерчиванию следующего символа, начи- начиная с правого нижнего угла только что вычерченного. Обратите внимание— в этом примере нам не пришлось об- обращаться к стекам матриц и атрибутов. Другие символы фор- формируются по этой же схеме. Хотя приведенный фрагмент программы выглядит и не очень элегантно, его эффективность не должна нас особенно заботить, поскольку символы формируются только один раз в течение сеанса работы приложения. Сформи- Сформированные символы пересылаются на сервер в виде скомпилированного дисплейного списка. Предположим, что нам необходимо сформировать расширенный шрифт из 256 символов. Соответствующая программа, в которой используется функция OurFont(), будет выглядеть следующим образом: base = glGenListsB56); /* Возвращает индекс первого из 256 доступных идентификаторов */ for(i=0; i<256; GL_COMPILE); Рис. 3.15. Вычерчивание бук- вы "® " glNewList(base OurFont(i); glEndList(); Когда понадобится использовать этот список для вычерчивания отдельных символов, можно будет не смещать каждый последующий вызов относительно base, а просто устано- установить базу смещения: glListBase(base); 5В принципе, каждый символ может иметь свое соотношение сторон и, следовательно, может быть вписан в прямоугольник своего уникального размера. Чтобы не усложнять задачу, будем считать наш шрифт моноширинным, т.е. таким, все символы которого имеют одинаковые размеры. Именно таким шриф- шрифтом набраны тексты программ в этой книге. 3.4. Дисплейный файл 119
Собственно вывод некоторой текстовой строки производится самим сервером при вызове функции glCallLists (): char *text string? glCallLists( (GLint) strlen(text_string), GL_BYTE, text_string); В этом вызове используется функция strlen() из стандартной библиотеки языка С, которая определяет длину строки text string. Первый аргумент функции glCallLists() задает коли- количество отображаемых дисплейных списков. Третий аргумент — указатель на массив, элементы которого имеют тип, заданный вторым аргументом. Идентификатор к-го дисплейного списка, который подлежит отображению, формируется как сумма базы списка, установленной функцией glListBase() и значением к-го символа в строке (массиве символов text string). 3.4.3. Шрифты библиотеки GLUT Конечно, любой программист предпочитает использовать уже разработанные ранее шрифты, а не заниматься созданием собственных, если в этом нет особой необходимости. В состав библиотеки GLUT входит несколько растровых и штриховых шрифтов6. Для того чтобы вывести с их помощью текст на экран, не обязательно использовать механизм рабо- работы с дисплейными списками, но в последнем примере этой главы мы все-таки создадим дисплейный список, который будет содержать один из шрифтов библиотеки 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*(). Как правило, после вы- 6 Можно использовать и шрифты, поддерживаемые операционной системой. 120 Глава 3. Ввод и взаимодействие с пользователем
вода образа очередного символа функцией glutBitmapCharacter() прикладная программа смещает позицию растра на один символ вправо. Это изменение не влияет на последующее тонирование геометрических примитивов. Если символы имеют разную ширину, нужно вос- воспользоваться функцией glutBitmapWidth(font, char), которая возвращает ширину заданно- заданного символа указанного шрифта. Таким образом, фрагмент прикладной программы, который организует вывод символа некоторой надписи, обычно имеет вид glRasterPos2i(rx, ry); glutBitmapCharacter(GLUT BITMAP_8_BY_13, k); rx+=glutBitmapWidth(GLUT~BITMAP_8_BY_13, k); Далее в этой главе будет рассмотрена программа, в которой надпись выводится растро- растровым шрифтом с помощью дисплейного списка. Организация прикладной программы на базе использования дисплейных списков требу- требует особого внимания при программировании, и мы еще вернемся к этому вопросу в главе 8, когда будем рассматривать иерархические графические модели. Но сейчас нас больше ин- интересует ясность и наглядность программы, хотя это и может пойти в ущерб ее производи- производительности. Поэтому в большинстве примеров в последующих главах дисплейные списки применяться не будут. В принципе, большую часть приведенных текстов программ доста- достаточно легко использовать и применительно к дисплейным спискам. Для этого требуется заключить фрагменты, формирующие изображение, в "процедурные скобки"— вызовы функций glNewList() и glEndList(). 3.5. Программирование ввода, управляемого событиями В этом разделе на множестве несложных примеров мы рассмотрим механизм обработки событий при вводе информации пользователем в прикладную программу. В основу про- программной реализации этого механизма положены функции с обратным вызовом, упомянутые в разделе 3.2. Будут рассмотрены типы событий, распознаваемые операционной системой, и для тех из них, которые представляют интерес для графических приложений, разработаны функции с обратным вызовом, отвечающие за реакцию приложения на эти события. 3.5.1. Использование устройств указания Начнем обсуждение механизма обработки событий с модификации функции main() в программе построения узора Серпинского, рассмотренной в главе 2. В первой ее версии для вывода окна приложения на экран были использованы функции из библиотеки GLUT, а затем с помощью функции glutMainLoop() организован цикл ожидания событий. В теле цикла не было запрограммировано никаких операций. Даже прекращение работы программы выпол- выполнялось внешним по отношению к этому циклу механизмом. Этот недостаток первой версии программы был преодолен за счет использования устройства указания, которое позволяло прекратить выполнение программы, вызвав стандартную функцию прерывания выполнения exit() при нажатии определенной кнопки мыши. Мы будем рассматривать только те события, которые распознаются библиотекой GLUT, хотя операционные системы, подобные X Window, распознают гораздо более широкий класс событий. Но в библиотеку GLUT включены средства распознавания только тех событий, ко- которые являются общими для большинства существующих операционных систем и представ- представляют интерес для разработчиков графических приложений. С устройством указания, в каче- качестве которого чаще всего используется мышь, ассоциируются события двух типов. Событие типа перемещение возбуждается в том случае, если мышь перемещается при нажатой кнопке (любой из имеющихся на устройстве). Если мышь перемещается, но при этом никакая кнопка 3.5. Программирование ввода, управляемого событиями 121
не удерживается в нажатом состоянии, это событие рассматривается как пассивное переме- перемещение. Положение мыши — показание этого устройства ввода — становится доступным при- прикладной программе после возникновения события типа перемещение. Другой тип события — событие мыши — возбуждается в случае, если пользователь нажал или отпустил любую из кнопок. Пока кнопка удерживается нажатой, никакие события не возбуждаются (если, конеч- конечно, мышь не перемешается) до тех пор, пока кнопка не будет отпущена7. Передаваемая при этом информация — показания устройства — включает идентификатор кнопки, послужив- послужившей причиной возникновения события, ее состояние после возникновения события (нажата или отпущена) и положение маркера мыши в координатах экрана. Регистрация в операцион- операционной системе функции мыши с обратным вызовом обычно выполняется в теле функции 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() новой программы будет выглядеть практически так же, как и аналогич- аналогичная функция в предыдущем примере8. int main(int argc, char **argv) glutlnit(&argc,argv); glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); glutCreateWindow("square"); myinit(); 7 В некоторых операционных системах нажатие и отпускание кнопки рассматривается как единое событие. 8 Мы будем использовать соглашение об именовании функций с обратным вызовом, регламентированное в руководстве OpenGL Programmer's Guide [Ope97,a]. 122 Глава 3. Ввод и взаимодействие с пользователем
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 && state==GLUT_DOWN) drawSquare(x,y); if(btn==GLUT_MIDDLE_BUTTON && state==GLUT_DOWN) exit(); } Поскольку графические примитивы формируются только в функции drawSquare(), значе- значения атрибутов можно устанавливать в любом месте программы. Мы для этого будем исполь- использовать функцию myinit(). Нам потребуются три глобальные переменные. Размеры окна могут изменяться в процессе работы программы, а потому текущие значения соответствующих параметров должны быть доступны всем компонентам программы. В частности, они понадобятся функции перерисовки и функции вычерчивания квадратиков drav;Square(). Если бы нужно было вычерчивать квад- квадратики переменного размера, то соответствующий параметр также следовало бы сделать гло- глобально доступным. Программа инициализации устанавливает размеры отсекающего прямо- прямоугольника равными размерам окна приложения, которое будет сформировано в main(), a размеры видового окна задает таким образом, чтобы оно занимало всю область окна прило- приложения. Цвет фона окна — черный. Обращаю ваше внимание на то, что установку параметров окна приложения и видового окна в данном случае можно и не включать в программу, по- поскольку мы фактически дублируем установки по умолчанию. Но я специально включаю в программу соответствующие операторы, чтобы потом можно было сравнить этот вариант программы с тем, который будет создан в следующем разделе. /* Глобальные переменные */ GLsizei wh = 500, ww = 500; /* Исходные размеры окна */ GLfloat size =3.0; /* Половина длины стороны квадратика */ void myinit(void) { /* Установка параметров визуализации */ glviewport@,0,ww,wh); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D@.0, (GLdouble)ww, 0.0, (GLdouble)wh); 3.5. Программирование ввода, управляемого событиями 123
glMatrixMode(GL_MODELVIEW); /* Очистить окно, цвет фона - черный */ glClearColor @.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); glFlush(); } При разработке программы вычерчивания квадратиков нужно учитывать, что те коорди- координаты положения маркера мыши, которые передаются операционной системой при возбужде- возбуждении события мыши, определены в системе координат окна приложения, начало которой на- находится в его верхнем левом углу. Следовательно, придется сначала скорректировать значе- значение координаты у, используя для этого текущее значение высоты окна (глобальную переменную wh). Для вычерчивания будем использовать случайно выбранный цвет, восполь- воспользовавшись для этого стандартным генератором случайных чисел — функцией rand(). void drawSquare(int x, 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(); } Остается только вставить в текст программы необходимые директивы # include, и полу- получим программу, которая будет вполне удовлетворительно работать до тех пор, пока пользова- пользователь не попытается изменить размеры окна приложения. 3.5.2. События окна Большинство современных операционных систем позволяет пользователю изменять раз- размеры окон приложений и перемещать их по поверхности экрана. Эта процедура, как правило, выполняется с помощью мыши путем перетаскивания какого-либо из углов окна. При этом операционная система возбуждает событие, которое относится к группе события окна (window event). В прикладной программе должна присутствовать функция, которая будет реа- реагировать на это событие9. Продумывая реакцию приложения на изменение размеров окна, следует обратить внимание на следующие вопросы. 1. Следует ли перерисовать все объекты, которые были вычерчены в области окна перед тем, как его размеры изменились? 2. Как следует отреагировать на изменение соотношения сторон окна приложения? 3. Нужно ли после этого изменять размеры или атрибуты новых примитивов? 9 Существует функция перерисовки с обратным вызовом, которая вызывается по умолчанию, но она может выполнять совсем не те операции, которые желательны для данного приложения. 124 Глава 3. Ввод и взаимодействие с пользователем
Однозначных ответов на эти вопросы не существует — здесь все зависит от специфики кон- конкретного приложения. Если на экране изображена реальная сцена, то, скорее всего, следует сохра- сохранить нормальное соотношение сторон, чтобы изображение не оказалось искаженным. Но при этом может оказаться так, что часть области окна будет не использована или часть сцены не "впишется" в окно измененного размера. Если при изменении размеров окна нужно воспроизвести в нем все ранее вычерченные объекты, то необходим какой-то механизм сохранения информации об этих объектах и их повторной перерисовки. Чаще всего это выполняется в теле единственной функции, например функции display (), которую мы уже использовали в программе из главы 2. Эта функция регистрируется в качестве функции отображения с обратным вызовом. Но в данном примере этот механизм не подходит, поскольку было решено, что программа будет вычерчивать квадратики только в ответ на действия пользователя в интерактивном режиме. В этой программе все квадратики должны иметь один и тот же размер, который не зави- зависит от размеров и формы (соотношения сторон) окна приложения. При каждом изменении размеров окна приложения оно очищается, и мы фактически начинаем "с чистого листа". В качестве показаний событие перерисовки передает в прикладную программу высоту и шири- ширину нового окна. Эти параметры используются для переопределения прямоугольника отсече- отсечения с помощью функции gluOrtho2D() и нового видового окна с тем же соотношением сто- сторон. Затем окно очищается, причем в качестве цвета фона устанавливается черный цвет. Та- Таким образом, функция перерисовки с обратным вызовом будет иметь вид void myReshape(GLsizei w, GLsizei h) /* Настроить прямоугольник отсечения */ glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D@.0, (GLdouble)w, 0.0, (GLdouble)h); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); /* Настроить параметры видового окна и очистить его */ glViewport@,0,w,h); glClearColor @.0, 0.0, 0.0, 0.0); glClear(GL_COLOR BUFFER_BIT); glFlush(); } Полный текст программы читатель найдет в приложении А. Но предлагаемый вариант функции перерисовки не единственно возможный. Можно, на- например, изменять размеры вычерчиваемых в дальнейшем квадратиков в соответствии с новыми размерами окна приложения. Помимо события изменения размеров окна приложения, сущест- существуют и другие, связанные с перекомпоновкой окон на экране, — перемещение окна без измене- изменения его размеров, перевод окна "на передний план" и т.д. Для всех таких событий, если в при- приложении планируется реагировать на них каким-то образом, нужно предусмотреть свои функ- функции обработки с обратным вызовом, аналогичные myReshape(), или можно "положиться" на те функции по умолчанию, которыми обеспечивает приложение библиотека GLUT. В разработанную программу можно ввести и еще одно усовершенствование — пусть она вычерчивает квадратики, пока пользователь будет перемещать мышь, удерживая кноп- кнопку нажатой. В этом случае необходимо определить в программе соответствующую функ- 3.5. Программирование ввода, управляемого событиями 125
цию с обратным вызовом и зарегистрировать ее в операционной системе с помощью функ- функции glutMotionFunc(). В нашей программе уже есть такая функция — drawSquare(), а по- потому остается просто зарегистрировать ее в качестве функции с обратным вызовом: glutMotionFunc(drawSquare); Теперь можно отслеживать на экране траекторию движения мыши, которая будет обрисо- обрисовываться прямоугольной кистью. 3.5.3. События клавиатуры В качестве одного из устройств ввода очень часто в графических приложениях использу- используется и клавиатура. События клавиатуры возбуждаются в том случае, если на клавиатуре на- нажата одна из клавиш в тот момент, когда маркер мыши на экране находится в области окна приложения. В некоторых операционных системах событие клавиатуры возбуждается и при отпускании клавиши, но в библиотеке GLUT на этот случай функция регистрации обратного вызова не предусмотрена. При возбуждении события операционная система передает в при- прикладную программу ASCII-код нажатой клавиши и координату положения маркера мыши в окне приложения. Регистрация функции обработки события клавиатуры выполняется функ- функцией glutKeyboardFunc(): glutKeyboardFunc(keyboard); Если, например, в приложении с помощью клавиатуры планируется только сигнализиро- сигнализировать программе о завершении работы, то функция обработки события клавиатуры будет иметь вид void keyboard(unsigned char key, int x, int y) { if(key=='q' || key == 'Q') exit(); 3.5.4. Функции отображения и простоя Из еще не рассмотренных функций с обратным вызовом две заслуживают особого внимания — это функция отображения и функция простоя {idle). Функция отображения упоминалась в главе 2. Она регистрируется в операционной системе с помощью функции glutDisplayFunc() из библиотеки типов GLUT: glutDisplayFunc(display); Функция, указанная в glutDisplayFunc(), будет вызываться каждый раз, когда потребу- потребуется перерисовка изображения. Одна из таких ситуаций— инициализация окна приложения. Поскольку заранее известно, что событие отображения возбуждается, когда открывается окно приложения, то в эту функцию желательно включить все операции вывода, не связанные с взаимодействием с пользователем. Исполнительная система GLUT требует, чтобы в каждом графическом приложении была определена функция отображения с обратным вызовом. Функцию отображения можно использовать и в другом контексте, например при анима- анимации, когда некоторые параметры, определяемые в приложении, изменяются. Библиотеку GLUT можно использовать для того, чтобы открыть несколько окон. В таком случае в число компонентов текущего состояния приложения включается и идентификатор текущего окна, и можно выводить объекты в разные окна, изменяя этот параметр. Можно также "свернуть" окно в пиктограмму {iconify). Следовательно, интерактивные приложения и программы ани- анимации могут вызывать функцию отображения в разных ситуациях. Удобно выполнять эту процедуру косвенно через функцию glutPostDisplay() из библиотеки GLUT. Эта функция 126 Глава 3. Ввод и взаимодействие с пользователем
включает определенную логику и предотвращает попытку вывода на экран, который не мо- может быть реализован, например, если окно свернуто в пиктограмму. Функция простоя с обратным вызовом (idle callback) вызывается в том случае, если про- программа не получает сообщений о каких-либо событиях. По умолчанию эта функция ничего не выполняет— ее тело не содержит никаких операторов, но программист может реализовать в этой функции какие-либо полезные для приложения операции. Типичный пример использова- использования такой функции — формирование графических примитивов, которые выводятся на экран, пока в окружающем мире ничего не меняется (см. упр. 3.2). Функции отображения и простоя будут использованы в программе, которую мы рассмотрим в последних разделах этой главы. В процессе выполнения можно переназначать функции с обратным выводом для обработ- обработки определенных событий. Можно вообще заблокировать обработку какого-либо события, передав в качестве аргумента соответствующей функции регистрации NULL. 3.5.5. Управление окнами Библиотека GLUT поддерживает как работу с несколькими окнами в одном приложении, так и с дочерними окнами в пределах одного главного окна приложения. В приложении можно соз- создать второе окно верхнего уровня (с заголовком second window) следующим оператором: id=glutCreateWindow("second window"); Значение переменной id можно в дальнейшем использовать для установки этого окна в каче- качестве текущего и перенаправления в него всех последующих операторов графического вывода: glutSetWindow(id); Во втором окне можно установить другие настройки параметров, обратившись к функции glutInitDisplayMode() перед вызовом glutCreateWindow(). Более того, каждое окно может иметь свой набор функций с обратным вызовом, поскольку функции регистрации имеют силу только по отношению к текущему окну. 3.6. Меню В прикладной программе с помощью графических примитивов и функций обработки со- событий мыши можно создать собственные средства графического интерфейса. Например, можно сформировать ползунковый регулятор, изображенный на рис. 3.16. Для этого потребу- потребуется сформировать на экране изображения прямоугольников и вывести текст надписей шка- шкалы, а затем, обрабатывая события мыши, получать информацию о желаемом перемещении ползунка регулятора. Конечно, программирование такого элемента управления — задача до- довольно утомительная, особенно если попытаться воспроизводить разного рода объемные эф- эффекты изображения отдельных деталей механизма (если хочется, чтобы регулятор выглядел на экране, "как живой"). В большинстве операционных сис- систем имеется свой набор элементов управления для графиче- графического интерфейса, но поскольку в этой книге мы не "привязываемся" к определенной операционной системе, то обсуждать специфику их использования не будем. Но, к сча- счастью, в библиотеке GLUT имеются функции поддержки ра- Рис. 3.16. Ползунковый регу- боты с всплывающими меню (pop-up menus), которые можно ля тор использовать в приложениях, нуждающихся в интенсивном диалоге с пользователем. Работа с меню в прикладной программе включает несколько достаточно простых опера- операций. Сначала нужно определить в программе пункты меню. Нужно связать меню с опреде- определенной кнопкой мыши. И последнее — нужно определить функцию с обратным вызовом для 3.6. Меню 127
обработки выбора каждого пункта меню. Ниже мы продемонстрируем, как это делается, на примере меню из трех пунктов. Выбор первого пункта будет завершать выполнение прило- приложения, в выбор второго и третьего — изменять размеры квадратиков, вычерчиваемых функ- функцией drawSquare(). Функцию обработки меню с обратным вызовом назовем demo_menu(). В функцию main() поместим вызовы функций, которые формируют меню и связывают его с правой кнопкой мыши: glutCreateMenu(demojnenu); glutAddMenuEntry("quit",1); glutAddMenuEntry("increase square size", 2); glutAddMenuEntry("decrease square size", 3); glutAttachMenu(GLUT_RIGHT_BUTTON); Функция glutAddMenuEntry() формирует очередной пункт меню и ее второй аргумент — идентификатор, который будет передаваться функции обработки с обратным вызовом при выборе этого пункта. Текст функции обработки меню с обратным вызовом приведен ниже. void demojnenu(int id) { if(id == 1) exit( ); else if (id == 2) size = 2 * size; else if (size > 1) size = size/2; glutPostRedisplay( ); } Обращение к функции glutPostResdisplay() запрашивает перерисовку изображения, кото- которое выполняется той функцией отображения, которая зарегистрирована функцией glutDis- playFunc(). В результате после завершения выполнения demojnenu () на экране восстановит- восстановится картинка без изображения меню. Библиотека GLUT поддерживает работу и с иерархическими меню (рис. 3.17). Пусть, на- например, главное меню состоит только из двух пунктов. При выборе первого пункта выполне- выполнение программы завершается, а при выборе второго на экран выводится подменю. Это подме- подменю содержит два пункта, позволяющих увеличивать или уменьшать размеры квадратиков, формируемых функцией drawSquare(). Для организации такого иерархического меню в функцию main() следует включить следующий фрагмент: subjnenu = glutCreateMenu(sizejnenu); glutAddMenuEntry("increase square size", 2); glutAddMenuEntry("decrease square size", 3); glutCreateMenu(topjnenu); glutAddMenuEntry("quit",1); glutAddSubMenu("Resize", subjnenu); glutAttachMenu(GLUT_RIGHT_BUTTON); Функции обработки size_menu() и top_menu() я предлагаю читателям разработать самостоятельно (см. упр. 3.5). quit resize \ increase square size decrease square size Рис. 3.17. Иерархическое меню 128 Глава 3. Ввод и взаимодействие с пользователем
3.7. Указание объектов Указание объектов {picking) — это одна из операций ввода, которая позволяет пользо- пользователю передать прикладной программе информацию о выборе того или иного из изобра- изображенных на экране объектов. Хотя при реализации этой операции и используется то же са- самое физическое устройство, что и для операции локализации позиции на экране, в инфор- информационном плане они значительно отличаются. В графических системах с растровым способом создания отображения реализация операции указания объектов связана со значи- значительными сложностями. Но так было не всегда. В системах с векторным способом вычерчивания дисплейный про- процессор получал сигнал указания от светового пера в тот момент, когда на экране физически вычерчивался выбранный объект, а потому селекция выполнялась по времени — достаточно было зафиксировать адрес объекта в дисплейном файле в момент получения прерывания от светового пера. Одна из причин, усложняющих выполнение операции указания в современных систе- системах, — конвейерный принцип обработки информации. Примитивы, определенные в пользо- пользовательской программе, затем проходят через несколько стадий преобразования, пока не будет сформировано их растровое разложение в буфере кадра. Хотя практически все преобразова- преобразования обратимы в математическом смысле, эту обратимость сложно реализовать, поскольку преобразования выполняются аппаратно. Поэтому преобразование информации о положении на экране в информацию о графическом примитиве не может быть сведена к простейшим вы- вычислениям. Кроме того, существует и проблема потенциальной неоднозначности такого пре- преобразования (см. упр. 3.11 и 3.12). Существуют два главных способа преодоления этих сложностей. Первый способ — се- лектирование объектов— предполагает настройку прямоугольника отсечения небольшого размера и видового окна так, что с их помощью можно отобрать то подмножество объектов, которые попадают в окрестность маркера устройства указания. Эти отобранные примитивы включаются в специальный список селекции (hit list), который далее анализируется приклад- прикладной программой. Альтернативный подход — использовать при анализе прямоугольные оболочки объектов (bounding rectangles или extents). Прямоугольная оболочка (или просто оболочка) — это наи- наименьший прямоугольник со сторонами, параллельными осям координат, который может быть описан вокруг объекта. Отобрать среди оболочек всех объектов те, которые содержат точку, указанную пользователем, не так сложно. Если в прикладной программе используется доста- достаточно простая структура данных для сопоставления объектов в локальных или мировых сис- системах координат с их оболочками в системе координат окна, то выбор соответствующих объ- объектов можно возложить на прикладную программу. Мы продемонстрируем этот подход на практике в программе, которая будет рассмотрена в следующем разделе. Значительно сложнее реализовать операцию указания объектов в том случае, если в гра- графической системе используется иерархическая организация отображаемых объектов. В этом случае каждый отдельный объект является членом какого-либо подмножества объектов. Ко- Когда такой объект "захватывается" устройством, в прикладную программу нужно переслать список всех объектов этого подмножества. Как правило, в графических API не специфицируется, как близко должна находиться ука- указанная на экране точка к объекту, чтобы его можно было считать выбранным. Одна из при- причин такого "упущения" — стремление предоставить программистам самостоятельно реализо- реализовать подходящий способ отбора. Если, например, использовать подход, основанный на при- применении оболочек, то можно указывать любую точку на поле объекта. Следует учитывать, что и пользователи при выполнении операции указания также далеко не всегда располагают маркер устройства точно на объекте, особенно если это контурный объект. 3.7. Указание объектов 129
3.8. Простая программа рисования Все, что было сказано выше о функциях обработки с обратным вызовом, дисплейных спи- списках и разработке интерактивных графических программ, мы продемонстрируем теперь на примере реальной программы. Программы рисования обладают множеством функциональ- функциональных возможностей развитых программ автоматизированного проектирования. Практически всякая программа рисования должна иметь следующий набор функций. ¦ Работа с геометрическими объектами, такими как отрезки прямых и многоугольники. Полагая, что объекты описываются множеством вершин, программа должна позволять вводить эти вершины в интерактивном режиме. ¦ Возможность манипулировать пикселями и таким образом формировать изображение непосредственно в буфере кадра. ¦ Управление атрибутами — цветом, типом линий и образцом заполнения. ¦ Поддержка операций с меню для управления режимом работы приложения. ¦ Корректное отслеживание перемещения окна приложения и изменение его размеров. Всеми этими возможностями обладает программа, которую мы сейчас рассмотрим. На рис. 3.18 показано, как выглядит окно программы сразу после начала сеанса. Пять прямоугольников в верхней части окна — это кнопки выбора одного из режимов рисования: Отрезок, Прямоугольник, Треугольник, Пиксель и Текст. Режим выбирается щелчком на поле соответствующего прямоугольника левой кнопкой мыши. После того как будет выбран режим Отрезок, следующими двумя щелчками левой кнопкой мыши пользователь может указать положение конечных точек отрезка. При этом указатель мыши должен находиться в рабочей области окна — вне зоны кнопок режима. После задания второй крайней точки про- программа выводит соответствующее изображение отрезка, причем он вычерчивается текущим цветом. По этой же схеме формируются изображения прямоугольников и треугольников. При вычерчивании прямоугольника задаются две точки на противоположных углах по диагонали, а при вычерчивании треугольника — три точки вершин. Новый режим можно выбрать после вычерчивания любого очередного объекта. В режиме Пиксель последовательные щелчки ле- левой кнопкой мыши задают положение малень- маленьких квадратиков, размер которых регулируется, вс : , , начиная с двух пикселей, причем квадратики ,ч ,,.-, , , ч> заливаются случайно выбранным цветом. В ре- , . . ', . , J-,'. >Л-,*:\.". жиме Текст пользователь вводит текст с кла- ,../;., виатуры, который отображается, начиная с по- последней указанной в рабочем поле точки. Два меню вызываются на экран после : ,, щелчка средней и правой кнопками мыши , . . ,. | (рис. 3.19). То меню, которое вызывается пра- , . , .ч ,&. вой кнопкой, позволяет пользователю очи- _ у. , ,,, ', стить экран (команда Clear) или прекратить .; '* выполнение программы (команда Exit). Меню, .,,.,." 1 . С,, которое вызывается средней кнопкой, имеет >,•."• •• >\- • v,V.. иерархическую структуру и включает три .., ; ,,yi . .. ,, , ,, ин »- г . /™. подменю. При выборе пункта Color в подме- Iw^- чиЧ^-"--' t,v > ¦«-,-• (• . . ню основного меню можно указать новый те- 4isV%t s / ?"> I'. . - ,, кущий цвет. Выбор пункта Fill в основном ме- меню позволяет задать в подменю режим запол- Рис. 3.18. Окно программы рисования в начале цешя ( Fj|| } или контурного изоб. сеанса v J ' JV сеанса 130 Глава 3. Ввод и взаимодействие с пользователем
ражения (пункт Fill off) прямоугольников и треугольников. Последний пункт основного ме- меню, Pixel size, позволяет указать в подменю увеличение размеров формируемых квадратиков (пункт increase pixel size) или их уменьшение (пункт decrease pixel size). Правая кнопка tm Средняя кнопка ¦»> quit clear Colors Pixel size Fill Red Green Blue Cyan Magenta Yellow White Black increase pix decrease pi> Fill on Fill off si size eel size Рис. 3.19. Структура меню программы рисования Самое сложное при разработке программы — решить, как распределить задачи между отдельными компонентами (функциями). То решение, которое нашло отражение в приве- приведенном ниже программном коде, согласуется с ранее рассмотренными примерами. Многие из функций дублируют рассмотренные ранее примеры. В программе определены следую- следующие функции: /* Обработка событий мыши */ void mouse(int btn, int state, int x, int y); /* Обработка событий клавиатуры */ void key( unsigned char c, int x, int y); void display(void); /* Отображение */ /* Формирование г«ацрата со случайным цветом заливки */ void drawSquare(int х, int у); /* Обработка изменения размеров окна */ void myReshape(GLsizei, GLsizei); void myinit(void); /* Инициализация */ /* Построение квадрата заданного размера в системе координат окна */ void screen_box(int х, int у, int s); /* Обработка команд меню */ void right menu(int id); void middle menu(int id); 3.8. Простая программа рисования 131
void colorjnenu(int id); void pixeljnenu(int id); void fill_menu(int id); /* Выбор режима рисования */ int pick(int x, int y); Функция main() в этой программе также аналогична функциям main() в рассмотренных ранее примерах: int main(int argc, char **argv) { int cjnenu, pjnenu, fjnenu; glutlnit(&argc,argv); glutlnitDisplayMode (GLUT_SINGLE | GLUT_RGB); glutCreateWindow("paint"); glutDisplayFunc(display); c_menu = glutCreateMenu(colorjnenu); glutAddMenuEntry("Red",1); glutAddMenuEntry("Green", 2); glutAddMenuEntry("Blue",3); glutAddMenuEntry("Cyan",4); glutAddMenuEntry("Magenta",5); glutAddMenuEntry("Yellow",6); glutAddMenuEntry("White",7); glutAddMenuEntry("Black",8); pjnenu = glutCreateMenu(pixel jnenu); glutAddMenuEntry("increase pixel size", 1); glutAddMenuEntry("decrease pixel size", 2); fjnenu = glutCreateMenu(filljnenu); glutAddMenuEntry("fill on", 1); glutAddMenuEntry("fill off", 2); glutCreateMenu(right menu); glutAddMenuEntry("quit",1); glutAddMenuEntry("clear",2); glutAttachMenu(GLUT_RIGHT_BUTTON); glutCreateMenu(middle jnenu); glutAddSubMenu("Colors", cjnenu); glutAddSubMenu("Pixel Size", pjnenu); glutAddSubMenu("Fill", f menu); glutAttachMenu(GLUT_MIDDLE_BUTTON); myinit (); glutReshapeFunc (myReshape); glutMouseFunc (mouse); glutKeyboardFunc(key); glutMainLoop(); Функция myinit () очищает экран, устанавливает значения глобальных переменных и формирует дисплейных список для отображения 128 символов. 132 Глава 3. Ввод и взаимодействие с пользователем
void myinit(void) { /* Установка шрифта в дисплейном списке */ int i; base = glGenListsA28); for(i=0;i<128;i++) { glNewList(base+i, GL_COMPILE); glutBitmapCharacter(GLUT_BITMAP_9_BY_15, i); /* Для установки штрихового шрифта раскомментируйте следующий оператор */ /*glutStrokeCharacter(GLUT_STROKE_ROMAN, i); */ glEndList(); } glListBase(base); glViewport@,0, ww, wh); /* Настройка параметров отсекающего прямоугольника в соответствии с размерами окна операционной системы X window. Такая настройка позволяет избежать масштабирования координат объектов при изменении размеров окна приложения. */ glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho@.0, (GLdouble)ww , 0.0, (GLdouble)wh , -1.0, 1.0); /* Настройка цвета фона окна и очистка окна приложения */ glClearColor @.0, 0.0, 0.0, 1.0); glClear(GL_COLOR_BUFFER_BIT); glFlush(); } Функция display() очищает окно, устанавливает цвет фона и выводит изображения кно- кнопок. Кнопки представляют собой прямоугольники со сторонами, соответствующими пример- примерно 10% высоты и ширины окна. Такой способ вычерчивания обеспечивает изменение разме- размеров кнопок при изменении размеров окна. На поле каждого прямоугольника затем вычерчи- вычерчиваются условные обозначения соответствующих функций в виде простейших фигур. Функция myReShape() в этом примере дублирует рассмотренную в разделе 3.5.2 одно- одноименную функцию с обратным вызовом. Функция pick() выясняет, какой объект на экране выбран с помощью мыши, и работает в сочетании с функцией обработки события мыши mouse (). В OpenGL поддерживается метод выбора обобщенных объектов, который довольно сложен в реализации. Но если в некоторой задаче нас интересует только определенная прямоугольная область экрана, например занятая кнопкой, то проще разработать собственный метод выбора. Та процедура, которая представ- представлена в данной программе, работает следующим образом. Как только возбуждается событие мыши, функция mouse () вызывает функцию pick() и с помощью последней выясняет, в ка- какой части экрана расположен маркер мыши. Поскольку кнопки занимают на экране прямо- прямоугольные области, то не представляет особого труда выяснить, где находится маркер мы- 3.8. Простая программа рисован ия 133
ши — на одной из кнопок или на рабочем поле окна. Эта информация возвращается функции mouse(), которая и берет на себя остальную работу. Поскольку область экрана, на которой выполнен щелчок мышью, определяется функцией pick(), то mouse(), анализируя текущий режим, либо выводит на экран соответствующий примитив, либо запоминает текущие коор- координаты маркера мыши. Например, если пользователь выбрал режим построения треугольни- треугольников, щелкнув перед этим на кнопке с изображением треугольника, программа должна запом- запомнить положение первых двух вершин, а после указания третьей вершины построить тре- треугольник. Соответствующий фрагмент текста функции mouse () представлен ниже. case (TRIANGLE): /* Функция pick() определила ранее, что был выполнен щелчок на кнопке с изображением треугольника */ switch(count) /* Счетчик количества вершин */ { case(O): /* Сохранить координаты первой вершины */ count++; хр[0] = х; ур[0] = у; break; case(l): /* Сохранить координаты второй вершины */ count++; хр[1] = х; УР[1] = У? break; caseB): /* Третья вершина. Вычертить треугольник */ if(fill) glBegin(GL_POLYGON); else glBegin(GL LINE LOOP); glVertex2i(xp[0],wh-yp[0]); glVertex2i(xp[1],wh-yp[1]); 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 J_BY_15, к); 134 Глава 3. Ввод и взаимодействие с пользователем
Типичный результат работы программы представлен на рис. 3.20. Хотя тот вариант программы рисования, который мы рассмот- рассмотрели выше, достаточно прост и его возможно- возможности далеки от совершенства, функции про- программы при желании можно расширить, при- причем это выполняется без особого труда. Например, можно заменить растровый шрифт на штриховой, добавить возможность выбора шрифтов, настройку атрибутов (ширины и ти- типа), предоставить пользователю возможность выбирать режим вычерчивания фигур (с за- заливкой или контурный), включить другие ти- типы фигур— произвольные многоугольники, аппроксимированные круги, кривые и т.д. Од- Однако обеспечить построение гладких кривых в программе рисования, подобно тому, как это делается в коммерческих программных про- продуктах, довольно сложно. Почему это так, вы узнаете в следующем разделе. Полный текст рассмотренной программы представлен в Приложении А. Рис. 3.20. Результат работы программы рисо- рисования 3.9. Интерактивные программы анимации Программы, которые мы рассматривали до сих пор, формировали статическое изображе- изображение, т.е. выведенные на экран объекты нельзя было изменять. Но для пользователя гораздо больший интерес представляют динамические картинки, в которых отдельные объекты могут двигаться или изменяться в ответ на команды пользователя (или по ходу какого-нибудь внешнего по отношению к программе процесса). Например, можно "оживить" символы над- надписи и заставить их двигаться по экрану или показать, как меняется изображение некоторой сцены при перемещении наблюдателя. Ниже мы рассмотрим, как организовать в программе анимацию на примере простого вращающегося объекта. 3.9.1. Вращающийся квадрат Рассмотрим двухмерную точку, описываемую парой координат: х = cos9, .у = sinG. Эта точка при изменении значения угла 9 будет перемещаться вдоль окружности единичного ра- радиуса. Три точки (—sinG, cos9), (-cosG, -sinG) и (sinG, -cosG) также лежат на окружности единично- единичного радиуса и вместе с первой образуют вершины квадрата, вписанного в эту окружность (рис. 3.21). Стороны этого квадрата имеют длину V2 . Полагая, что значение 9 является глобальной переменной, можно вычертить этот квадрат с по- помощью следующей функции: (-sin в, cos в) (-cos в, sin 6») (cosfl, sin (sin в, —cos в) Рис. 3.21. Квадрат, вписанный в окруж- окружность единичного радиуса 3.9. Интерактивные программы анимации 135
void display() { glClear(); glBegin(GL_POLYGON); /* Преобразование градусов в радианы */ thetar = theta/(B*3.14159)/360.0); glVertex2f(cos(thetar), sin(thetar)); glVertex2f(-sin(thetar), cos(thetar)); glVertex2f(-cos(thetar), -sin(thetar)); glVertex2f(sin(thetar), -cos(thetar)); glEnd(); } Приведенная функция display () будет формировать изображение квадрата при любом заданном значении 9, но при этом предполагается, что значение 9 изменяется по ходу выпол- выполнения программы и в результате на экране появляется изображение вращающегося квадрата. Для этого нужно время от времени вызывать с помощью функции glutPostRedisplay(), ко- которая входит в библиотеку GLUT, функцию display (). Теперь организуем приращение угла 9 на фиксированное значение в то время, когда программа не получает сообщения ни о каких других событиях (находится в состоянии "простоя"). Это выполняется с помощью функции обработки простоя, которая регистриру- регистрируется функцией glutIdleFunc() из библиотеки 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)? } Таким образом, щелчок левой кнопкой мыши запускает вращение квадрата, а щелчок средней кнопкой — прекращает его. Если запустить программу на выполнение, то, скорее всего, выводимое изображение не создаст у вас иллюзию вращающегося квадрата, а вы уви- 136 Глава 3. Ввод и взаимодействие с пользователем
дите фрагменты квадрата, изменяющиеся с течением времени. Что же в этой программе сде- сделано неправильно? Этим вопросом мы и займемся в дальнейшем. 3.9.2. Двойная буферизация При выводе изображения нужно, чтобы частота обновления была достаточно высокой и зритель не смог заметить, как экран очищается и как на нем формируется новое изображение. Эта частота должна превышать 50 Гц, тогда зритель не заметит мелькания изображения на экране. В графических компьютерных системах это требование касается прежде всего часто- частоты регенерации изображения, т.е. быстродействия буфера кадра. Если содержимое буфера не изменяется и обеспечена частота регенерации свыше 50 Гц (а лучше где-то около 85 Гц), для зрителя будут созданы необходимые комфортные условия восприятия изображения10. Но если содержимое буфера изменяется, то зритель может увидеть совершенно нежелательные эф- эффекты, например заметить "дерганье" последовательных фаз изменяющейся картинки. Это явление наблюдается особенно отчетливо, если изображение достаточно сложное и оче- очередную его фазу не удается сформировать за один период регенерации. В этом случае зритель увидит половину кадра с новой фазой движения, а половину — с прежней. Изображение пере- перемещающегося объекта будет при этом искажено на экране. То же самое происходит и с после- последовательными процессами очистки экрана и обрисовки нового изображения, как это имеет ме- место в нашем примере с вращающимся квадратом. Хотя квадрат — это довольно простой объект, который формируется за время, меньшее периода регенерации, в нашей программе отсутствует связь между процессом формирования объекта в буфере и выводом содержимого буфера на эк- экран аппаратными средствами. Таким образом, вполне вероятно, что на экран будет отправлено содержимое буфера, в котором новый квадрат сформирован только частично. Эту проблему можно решить с помощью двойной буферизации (double buffering). Пред- Предположим, что в нашем распоряжении есть два буфера кадра, которые принято называть рабочим (front buffer) и фоновым (back buffer). Рабочий буфер — это тот, из которого выполняется реге- регенерация изображения на экране, а в фоновом изображение формируется программой. По коман- командам от прикладной программы можно переключать функции буферов: сделать рабочим тот, ко- который ранее был фоновым, а фоновым — тот, который ранее был рабочим. При переключении вызывается функция display (), в результате чего на экран начинает автоматически выводиться изображение, подготовленное ранее в фоновом буфере, а новое изображение можно формиро- формировать в том буфере, который теперь стал фоновым. Механизм двойной буферизации устанавли- устанавливается в режиме инициализации аргументом функции glutInitDisplayMode(). Вместо констан- константы GLUTSINGLE нужно задать в дизъюнкции флагов константу GLUT_DOUBLE. Переключение функций буферов выполняется функцией glutSwapBuf fers(), которая входит в состав библио- библиотеки GLUT. Если формируется довольно сложное изображение, требующее много времени, программа может спокойно работать с фоновым буфером и это никак не скажется на регене- регенерации ранее созданного изображения из рабочего буфера. После завершения формирования очередной фазы динамической картинки выполняется переключение буферов. Двойная буферизация — это стандартная технология организации компьютерной ани- анимации. Все операторы формирования изображения включаются в функцию display(), но при использовании двойной буферизации в этой функции нужно сначала очистить рабочий буфер, вызвав glClear(), а последним оператором вызвать функцию переключения буфе- буферов glutSwapBuffers(). 10Если используется чересстрочная развертка, четные и нечетные строки кадра выводятся поперемен- попеременно и, таким образом, действительная частота регенерации равна половинной частоте кадровой развертки. При этом зритель может заметить легкое дрожание изображения по вертикали, особенно когда сидит до- довольно близко к экрану. 3.9. Интерактивные программы анимации 137
Теперь вернемся к нашему вращающемуся квадрату. В рассмотренную ранее программу нужно внести два изменения. Во-первых, в функции main() нужно заказать режим двойной буферизации: glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE); Во-вторых, нужно скорректировать функцию display(), добавив в самый ее конец оператор glutSwapBuffers(); Учтите, что двойная буферизация не решает проблемы скорости формирования изобра- изображения. Фактически эта технология позволяет только избежать наложения отображения и формирования новой фазы картинки — новое изображение выводится на экран только после завершения его формирования. Но возможно получить вполне приемлемое качество динами- динамического изображения при частоте формирования новых фаз порядка 10-20 Гц. 3.9.3. Проблемы с буферизацией При создании интерактивных графических программ формирования динамического изо- изображения программисту приходится сталкиваться с целым рядом проблем, которые не могут быть решены только за счет двойной буферизации. Возвращаясь к рассмотренному выше примеру — программе рисования по командам пользователя, — предположим, что нам пона- понадобилось добавить в картинку изображение часов, которые показывали бы текущее время выполнения программы (рис. 3.22). В большинстве операционных систем суще- существуют функции, возвращающие текущее ас- астрономическое время в виде строки символов в коде ASCII, либо функции, возвращающие це- целое число, равное времени, прошедшему после некоторого события (в секундах). Периодически вызывая эти функции (например, в программе обработки простоя), можно затем преобразо- преобразовать полученное значение в приемлемый фор- формат, например часы „минуты: секунды. Послед- Последняя стадия процесса — вывести эти меняющие- меняющиеся данные на экран в виде текста. Посмотрим, что случится, если добавить со- соответствующий фрагмент в первый вариант программы построения вращающегося квадра- квадрата, в котором не использовалась двойная буфе- буферизация. Вы, скорее всего, сразу же заметите, что показания часов мигают при выполнении очистки экрана функцией display!). Попробу- Попробуем справиться с этой проблемой, воспользо- воспользовавшись технологией двойной буферизации. При переходе в режим двойной буфериза- буферизации мигание исчезнет, но вместо этой проблемы появится другая, еще более серьезная, — станут мерцать основные объекты на рабочем поле окна. Причина этого неприятного эффекта заключается в том, что используется режим немедленного вывода изображения. Объекты, ко- которые формируются программой по командам пользователя, передаются в буфер кадра един- единственный раз. Поэтому каждый объект оказывается только в одном из двух буферов и не мо- может присутствовать одновременно в двух. Теперь, справившись с миганием показаний часов, мы видим, что начали мерцать объекты в графической зоне экрана. Рис. 3.22. Окно программы рисования, в ко- которой есть часы текущего времени вы- выполнения 138 Глава 3. Ввод и взаимодействие с пользователем
Решение этой проблемы тоже можно отыскать. В нашей программе рисования для вывода объектов на экран можно использовать дисплейный список, в который следует вносить объ- объекты по мере обработки команд пользователя. После этого можно при каждом переключении очищать рабочий буфер и формировать в нем изображения всех объектов, уже включенных к этому времени в дисплейный список. Правда, для этого программу придется значительно ус- усложнить, особенно если для вывода символов также используются свои дисплейные списки. Но можно поступить и проще, сделав так, чтобы изображение каждого объекта оказалось сразу в обоих буферах— и в экранном, и в рабочем. OpenGL и некоторые другие графиче- графические системы позволяют таким образом настроить режим работы системы, чтобы можно бы- было одновременно формировать изображение во множестве буферов. Мы рассмотрим подроб- подробно эту технологию позже, в главе 9, когда будем обсуждать и другие проблемы использова- использования буферов. 3.10. Разработка интерактивных графических программ Формально определить перечень характеристик хорошей интерактивной графической программы довольно сложно, но отличить хорошую программу этого типа от плохой, пора- поработав с обеими, можно достаточно быстро. Хорошая программа, как правило, располагает следующими функциональными возможностями. 1. Плавное изображение динамических сцен без видимого '"дерганья" объектов на экране. 2. Наличие нескольких устройств ввода, с помощью которых можно управлять характе- характеристиками объектов на экране, в том числе и их динамикой. 3. Разнообразие методов ввода и отображения информации. 4. Доступный и интуитивно понятный интерфейс пользователя. 5. Обратная связь с пользователем. 6. "Лояльное" отношение к возможным ошибкам пользователя. 7. В программе должны учитываться как зрительные, так и моторные свойства человека- оператора, т.е. его способность воспринимать зрительную информацию и реагировать на нее, выполняя какие-либо действия с устройствами ввода. Важность перечисленных функциональных возможностей графической программы и сложность их реализации на практике нельзя недооценивать. Тема взаимодействия человека с машиной (HCI— human-computer interaction)— одна из тех, которые привлекают внима- внимание множества исследователей во всем мире, и ее нельзя изложить на нескольких страницах. В данной книге нас в этой теме больше всего будет интересовать "компьютерная" сторона, которая обеспечивает формирование графического изображения в такой комплексной систе- системе. Но есть несколько вопросов, общих для компьютерной графики и теории человеко- машинных систем, которые нужно учитывать при разработке интерактивных программ. 3.10.1. Инструментальные средства, экранные элементы управления и буфер кадра В рассмотренной выше программе рисования мы использовали графические средства ин- интерфейса, в частности всплывающие меню, которые поддерживаются библиотекой GLUT, и графические кнопки, которые разработали самостоятельно. Спектр экранных элементов ин- интерфейса, доступных в современных программах, значительно шире. Он включает и различ- различные линейные регуляторы, шкалы, "горячие" области экрана, звук, пиктограммы и т.п. Обыч- Обычно прикладной программист рассматривает эти элементы как стандартные и рассчитывает в 3.10. Разработка интерактивных графических программ 139
этом деле на библиотеки инструментальных программ, хотя некоторые из специалистов счи- считают, что свой продукт всегда лучше, чем заимствованный. В общем случае, почти все такие инструментальные пакеты используют для связи с прикладной программой механизм функ- функций с обратным вызовом. Значительно облегчит разработку собственных средств графиче- графического интерфейса описание методики работы с внутренними буферами графической системы, которое представлено в главе 9. Ниже на двух примерах будет показано, что использование графических построений на геометрическом уровне недостаточно эффективно при работе с элементами экранного ин- интерфейса. Гораздо эффективнее в этом случае работать напрямую с буфером кадра. Сначала рассмотрим, как выполняются операции построения всплывающего меню. При обращении к функции работы с меню на экране должно появиться его изображение, причем примерно в том месте, где находится указатель мыши в момент, когда пользователь щелкнул соответст- соответствующей кнопкой. После того как будет выбран один из пунктов меню, его изображение должно исчезнуть с экрана, а на его месте восстановлены ранее вычерченные объекты. Если использовать только операции геометрических построений, описанные ранее, выполнить та- такую операцию быстро не удастся. Наиболее целесообразно запомнить изображение, "перекрытое" меню, а затем, после завершения работы с меню, вернуть его на экран. Таким образом, нам придется работать на уровне пикселей поля экрана, а не геометрических объек- объектов и, следовательно, иметь дело с буфером кадра. При этом интенсивно используются опе- операции пересылки битового блока памяти (операции группы bitbli). Второй пример— построение "резиновой" линии (rubberbanding)— метод формирова- формирования графических изображений в интерактивном режиме. В рассмотренной выше программе рисования для задания вершин формируемого графического объекта пользователь должен был дважды (или трижды) ука- указать позиции в графической зоне окна, щелкнув кнопкой мы- мыши. При этом он только мысленно мог себе представить, как именно пройдет формируемый отрезок и как он соотносится с ранее вычерченными объектами. Новый метод позволяет пользователю постоянной видеть "эскиз" формируемого от- отрезка, который будет следить за текущим положением мыши, связывая его с ранее введенной точкой. Поведение этого "эскиза" нового отрезка очень напоминает поведение резино- резиновой нити, закрепленной одним концом на каком-либо гвозди- гвоздике, вбитом в чертежную доску, а другим концом на игле цир- циркуля или "чертилки", — куда бы ни двигалась "чертилка", между нею и вбитым гвоздиком всегда видна прямая линия, образованная натянутой нитью. Точно такую же роль играет в самом примитивном виде и искусственная "резиновая" нить в графической системе. Обычно изображение нити возникает, когда пользователь нажимает кнопку мыши, и отслеживает текущее положение указателя мыши до тех пор, пока пользо- пользователь не отпустит кнопку (рис. 3.23). Но в некоторых графи- графических системах применяется и другая последовательность операций— нить возникает после первого щелчка, а после второго и всех последующих "перепрыгивает" в новую позицию, пока не будет сформирована команда прекращения по- последовательности отрезков. При формировании изображения "резиновой" нити на каждом цикле прежнее изображение отрезка (т.е. отрезка, связывавшего предыдущее положение ука- указателя мыши с начальной точкой) стирается, а вычерчивается новое изображение, связанное с текущим положением указателя мыши. Рис. 3.23. Построение "рези- "резиновой "линии 140 Глава 3. Ввод и взаимодействие с пользователем
При построении изображения "резиновой" нити не обойтись без операций на уровне бу- буфера кадра или без специальных аппаратных средств. Эта проблема во многом сходна с про- проблемой построения изображения всплывающего меню. В каждом цикле отслеживания теку- текущего положения мыши нужно сначала вернуть изображению "первозданный" вид, а потом вычертить новый отрезок. С помощью аналогичной технологии вычерчиваются не только "резиновые" отрезки, но и "резиновые" прямоугольники и окружности. Такие инструмен- инструментальные пакеты, как GLUT, предоставляют программисту широкие возможности для выпол- выполнения операций пересылки блоков буфера кадра. В состав пакетов практически всегда вклю- включаются средства для формирования всплывающих меню и построения изображения "рези- "резиновой" нити, что избавляет прикладного программиста от необходимости самостоятельно разрабатывать их, если в приложении нужно реализовать подобные функции. 3.11. Резюме В этой главе мы лишь слегка "прикоснулись" ко многим темам, связанным с взаимодейст- взаимодействием пользователя и графической программы. Организация в программе реакции на действия пользователя с выводом динамического изображения на экран придает приложению совер- совершенно новые возможности. Мы показали, что хотя состав функций API OpenGL и не зависит от конкретной операционной системы, в любой графической программе не обойтись без ми- минимального набора средств взаимодействия с операционной системой. Роль переходного элемента между независящим от платформы API и операционной системой играет набор ин- инструментальных программ, который, с одной стороны (со стороны графической системы), имеет единообразный интерфейс, а с другой стороны, имеет множество вариантов реализа- реализации, специфических для каждой из имеющихся операционных систем. При работе с OpenGL роль такого инструментального пакета отводится библиотеке GLUT. Было рассмотрено, как отражается парадигма "клиент/сервер" на структуре графической системы и характеристиках ее отдельных компонентов. Эта парадигма не только способству- способствует рациональной организации графических приложений в сетевой среде, но позволяет созда- создавать графические программы, переносимые с одной аппаратной или программной платформы на другую. Эта концепция нашла свое воплощение в объектно-ориентированном подходе к программированию задач компьютерной графики, а также во внедрении графических прило- приложений в сетевую среду World Wide Web (эти темы будут подробно рассмотрены в главе 8). С точки зрения прикладного программиста идеология создания интерактивных графических приложений практически единая во всех системах. Графическая часть сервера включает растро- растровый дисплей, клавиатуру и устройство указания. Почти на всех рабочих станциях пользователь работает в сетевой операционной среде, поддерживающей многооконный интерфейс. Как пра- правило, в такой среде параллельно решается множество задач, но операционная система создает у пользователя иллюзию, что он является безраздельным хозяином компьютера. Избыточность в программе, настроенной на такой режим работы, минимальна, а исполь- использование логических внешних устройств избавляет прикладного программиста от заботы об организации обслуживания конкретной модели физического устройства. Стандартным методом взаимодействия с внешними устройствами в такой многопользова- многопользовательской операционной среде является механизм реакции на события, хотя при желании можно использовать и другие методы. Например, ввод-вывод по запросу и обработка событий предос- предоставляют наиболее широкие возможности в организации гибкой реакции программы на все раз- разнообразие ситуаций, возникающих во внешнем по отношению к программе мире. Интерактивная компьютерная графика представляет собой удивительно мощный инстру- инструмент с практически неограниченными возможностями. Пользуясь различными методиками организации взаимодействия с пользователем, программист может создавать программные продукты, рассчитанные на любую категорию пользователей. Возможно, вы убедитесь в этом на собственном опыте, выполнив упражнения, представленные в конце главы. 3.11. Резюме 141
3.12. Рекомендуемая литература Проект Sketchpad Сазерленда (Sutherland) описан в работе [Sut63]. Большинство концепций, положенных в основу оконного графического интерфейса, раз- разработано в научно-исследовательском центре фирмы Xerox в Пало-Альто (Xerox Palo Alto Research Center— PARC) в 1970-х годах (см. [Sch87J). Здесь же появилась на свет и мышь [Eng68]. Разработки PARC лежат и в основе популярных сегодня операционных систем — Macintosh Operating System, X Window и Microsoft Windows. Книга Фоли (Foley) и его соавторов [Fol94] содержит подробное описание процесса раз- разработки интерфейса пользователя с акцентом на аспекты использования графических средств. Книги Шнайдермана (Schneiderman) [Sch97] и Нильсона (Nielson) [Nie94] могут по- послужить хорошим вводным курсом в проблематику человеко-машинных систем. Операционная система X Window [Sch88J разработана в Массачусетсском технологиче- технологическом институте (Massachusetts Institute of Technology) и в настоящее время стала стандартом de facto для всех приверженцев UNIX. Завоевывающая все большую популярность среди пользователей ПК версия UNIX под названием Linux также поддерживает работу X Window. Режимы ввода информации и взаимодействия с пользователем, которые мы обсуждали на страницах этой книги, основываются на стандартах, реализованных в GKS [ANSI85] и PHIGS [ANSI88J. Эти стандарты разрабатывались как универсальные, т.е. пригодные и для вектор- векторных, и для растровых дисплеев, а потому в них не учитывались уникальные возможности рас- растровых графических систем (см. [Pik84, Gol83J). Хотя при изложении материала в этой главе мы опирались на инструментальную библио- библиотеку GLUT [Kil94,b], программисты на практике используют и непосредственный доступ к операционной системе X Window, и различные версии других инструментальных библиотек, позволяющих работать как с X Window [Kil94,a, OSF89], так и с Microsoft Windows. Интерес представляет и использование при разработке пользовательского интерфейса в OpenGL- программах языков написания сценариев, таких как tel/tk [Ous94]. Упражнения 3.1. Объясните, в чем суть проблем, с которыми сталкивается программист при опреде- определении штриховых шрифтов. Разработайте простую структуру данных, которая по- позволила бы определить набор символов штрихового шрифта, оградившись только отрезками прямых. 3.2. Разработайте новую версию программы отображения узора Серпинского из гла- главы 2, в которой щелчок левой кнопкой мыши запускал бы процесс формирования точек узора на экране, щелчок правой кнопкой мыши останавливал бы процесс формирования новых точек, а щелчок средней кнопкой прекращал выполнение приложения и закрывал его окно. Включите в программу обработку события изме- изменения размеров окна приложения. 3.3. Разработайте линейный регулятор, который позволял бы пользователю задавать цвет вычерчивания фигур в программе рисования. С помощью этого интерфейса пользователь сможет сначала визуально оценить новый цвет, а уже затем назначить его для формирования очередных фигур. 3.4. Виртуальный трекбол можно разработать на основе обычной мыши, "отобразив" ее на поверхность шара. Основное назначение такого устройства — позволить пользо- пользователю задавать с помощью мыши скорость, вращая шар вокруг вертикальной оси (имитируя "спин"). Разработайте такое виртуальное устройство и графическую про- программу, которая будет демонстрировать его возможности. 142 Глава 3. Ввод и взаимодействие с пользователем
3.5. Разработайте новую версию программы вычерчивания квадратов (раздел 3.5), в кото- которой использовалось бы всплывающее меню, аналогичное описанному в разделе 3.6. 3.6. Добавьте в программу рисования (раздел 3.8) индикатор времени работы, используя собственную программу считывания показаний системных часов. 3.7. Хорошую практику при освоении методов программирования взаимодействия ком- компьютера и пользователя дает разработка игровых программ. Разработайте програм- программу игры в шашки. Каждую клетку на доске следует рассматривать как объект, кото- который может выбрать пользователь. Чтобы не связывать себя тайнами шашечной стратегии, разработайте программу, в которой оба участника — люди. 3.8. Разработайте простую программу раскладки пасьянса. Сначала разработайте про- простую колоду карт, используя только те примитивы, которые мы уже рассмотрели в этой книге. Программу следует свести к выбору прямоугольных объектов на экране. 3.9. Моделирование разных видов игры в бильярд — это тоже довольно интересная за- задача для тех, кто увлекается игровыми программами. В такой программе, как и в упр. 2.18, придется вычислять траектории и определять столкновение шаров. Инте- Интерактивный аспект новой программы — задание направления движения шаров с по- помощью этакого компьютерного кия и обеспечение достаточно плавной имитации движения шара на экране. Разработайте программу для двух игроков. 3.10. Вместо того чтобы использовать для ввода команд меню или кнопки, можно просто разбить все поле графического окна на зоны и анализировать, в какой из них поль- пользователь щелкнул кнопкой мыши. Используйте этот принцип в программе рисова- рисования (раздел 3.8). 3.11. Пересчет координат точек из мировой системы координат в систему координат экрана обеспечивает однозначное соответствие, но оно не является обратимым, поскольку происходит переход от трехмерной системы координат к двухмерной. Предположим, однако, что мы имеем дело с двухмерным приложением. Является ли в нем такой пе- пересчет обратимым? Какие проблемы возникают при определении значения координат в мировой системе по положению точки в системе координат экрана? 3.12. Как применить результат упр. 3.11 к организации процедуры указания в программе? 3.13. При разработке прикладной программы программисту приходится решать, следу- следует ли использовать в ней дисплейные списки. Рассмотрите хотя бы два разных практических приложения и составьте для каждого из них список доводов за и против применения дисплейных списков. Используя шрифты, имеющиеся в со- составе библиотеки GLUT, проверьте на практике, правильно ли были сформулиро- сформулированные доводы. 3.14. Разработайте интерактивную программу, которая позволит провести "виртуальную" крысу через лабиринт, который был сформирован при выполнении упр. 2.7. Можно для управления движением крысы использовать кнопки мыши: правую и левую — для поворота соответственно вправо и влево, а среднюю — для движения вперед. 3.15. В разделе 3.9 было показано, что даже двойная буферизация в программе рисования не гарантирует прекращение мигания изображения. Можно ли справиться с ним, используя только те программы формирования изображения, которые вам известны на данной стадии изучения? Обоснуйте свой ответ. 3.16. Недорогие устройства ввода типа джойстик, как те, что часто используются в иг- игровых приставках, не имеют преобразователей, вместо которых используются трехпозиционные переключатели. Как, по-вашему, такие устройства "общаются" с программой? Упражнения 143
Тангаж 3.17. Ориентация летательного аппарата описывается ориентацией осей системы координат, показанной на рис. 3.24. Движение джойстика вперед-назад контролирует поворот вверх-вниз продольной оси аппарата {тангаж аппарата— pitch). Движение джойстика влево-вправо контролирует вращение вокруг этой оси {крен — roll). Разработайте про- программу, которая будет использовать в качестве уст- устройства ввода мышь и по ее смещению управлять аппаратом по тангажу и крену, изменяя параметры визуализации для пилота, который сидит в кабине такого аппарата. Это упражнение можно выполнить для двухмерного случая, рассматривая набор объ- объектов, видимых из кабины аппарата и пренебрегая эффектами перспективы. 3.18. Рассмотрим стол, на котором смонтирован плоский рычажный механизм (манипулятор), на конце которо- которого имеется сенсор (рис. 3.25). Предположим, что дли- длины обоих звеньев манипулятора фиксированы, а зве- звенья связаны с основанием и друг с другом простыми поворотными сочленениями (сочленениями с одной степенью свободы). Найдите соотношения, связы- связывающие углы поворота в сочленениях 9 и ф и положе- положение сенсора, закрепленного на последнем звене. 3.19. Пусть имеется ЭЛТ-монитор с размерами экрана 40x40 сантиметров. В мониторе используется про- прогрессивная развертка частотой 60 Гц, причем 10% периода кадровой развертки уходит на обратный ход луча из правого нижнего угла экрана в левый верхний. Будем считать, что разрешение монитора 1024x1024 пикселей. Найдите соотношение между временем появления сигнала на выходе светового пера и положением светового пера на поле экрана. Представьте результат в метрических единицах из- измерения и в относительных (пикселях). 3.20. Программа вычерчивания электронных схем является одним из вариантов программы рисования, рассмот- рассмотренной в этой главе. Будем считать, что схема состо- состоит из логических элементов типа И (AND), ИЛИ (OR) и НЕ (NOT), символические обозначения которых представлены на рис. 3.26. Разработайте программу, которая позволяет пользователю вычерчивать схему, пользуясь меню для выбора типа элемента и задавая его положе- положение на экране с помощью мыши. Подумайте над тем, как автоматически совмещать выход одного элемента с входом другого. 3.21. Дополните программу, разработанную в предыдущем упражнении, возможностью графически формировать последовательность входных сигналов для разработанной схемы. Программа должна в ответ выводить графики сигналов в указанных пользо- пользователем узлах схемы. Рис. 3.24. Система координат летательного аппарата Рис. 3.25. Плоский манипуля- манипулятор 144 Глава 3. Ввод и взаимодействие с пользователем
+ b OR AND NOT Рис. 3.26. Символы логических элементов элек- электронной схемы 3.22. Дополните программу, разработанную в упр. 3.20, таким образом, чтобы у пользо- пользователя была возможность вводить логические выражения. В ответ программа должна формировать электронную схему, реализующую введенное выражение. 3.23. Распространите методы, положенные в основу разработки программы упр. 3.20, на формирование схем алгоритмов программ или отображение графов, которые изу- изучаются в курсах структур данных. 3.24. Программные пакеты вычерчивания графических документов, как правило, предос- предоставляют в распоряжение пользователей множество методов формирования изобра- изображения. Разработайте интерактивную программу вычерчивания двухмерных кривых. Это приложение должно предоставлять пользователю возможность выбрать режим отображения графиков (в виде ломаной линии, столбиковой или сегментной диа- диаграммы), цвета и типа линии. 3.25. Требования к частоте регенерации дисплеев на ЭЛТ E0-85 Гц) сформулированы применительно к люминофорам с малым послесвечением. Но существуют и люми- люминофоры с длительным послесвечением. Почему, по вашему мнению, ЭЛТ с такими люминофорами не используются в большинстве дисплеев компьютерных рабочих станций? Где имеет смысл использовать подобные дисплеи? Упражнения 145
ГЛАВА 4 Объекты и геометрические преобразования Итак, мы готовы обстоятельно рассмотреть трехмерную графику. Большая часть этой главы посвящена описанию различных способов представления базовых геометриче- геометрических типов, преобразований одного представления в другое и свойств геометрических объектов, независимых от конкретного представления. Начнем с анализа математических соотношений, положенных в основу компьютерной графики. Такой подход поможет избежать в дальнейшем путаницы, которая порождается из- за отсутствия четкого представления об отличиях между геометрическими сущностями, спо- способами их описания в конкретной системе координат и математическими абстракциями. Мы будем использовать понятия аффинного и Евклидова векторных пространств, кото- которые являются математическим фундаментом для дальнейшей работы. Одна из основных целей, которую я преследую в этой главе, познакомить читателя с методами решения гео- геометрических проблем, не зависящими от выбранных систем координат. Такой подход яв- является достаточно надежной основой для всего последующего анализа, распространяется на любые частные виды представления геометрических объектов и логически приводит к идее использования обобщенных однородных координат, которая не только помогает по- понять суть проблемы, но и является основополагающей для эффективной реализации мето- методов ее решения на практике. Мы используем терминологию абстрактных типов данных для того, чтобы подчеркнуть отличие между объектом как таковым и его частным представлением. Дальнейшее обсужде- обсуждение покажет, что математические соотношения естественным образом вытекают из нашего желания манипулировать немногими базовыми геометрическими типами данных. Математи- Математический аппарат, который будет представлен в этой главе, относится к таким областям мате- математики, как векторные пространства, геометрия и линейная алгебра. Формальное изложение математики векторных пространств и матричной алгебры читатель найдет в приложениях Б и В соответственно. Следуя подходу, декларированному в начале этой книги и уже реализованному в преды- предыдущих главах, для иллюстрации основных теоретических положений будет разработана не- несложная программа, в которой мы постараемся продемонстрировать методику реализации
средствами графического API основных концепций геометрических преобразований. Пример, предложенный вашему вниманию в этой главе, имеет дело с представлением и преобразова- преобразованиями простого геометрического объекта — куба. Мы также продемонстрируем, как задавать параметры преобразований в интерактивном режиме. 4.1. Скаляры, точки и векторы Компьютерная графика имеет дело со множеством геометрических объектов, таких как точки, многоугольники и многогранники. Как уже было показано в предыдущих главах на примере двухмерных приложений, все разнообразие геометрических объектов можно свести к ограниченному множеству простейших сущностей. Нам понадобятся для этого три базовых типа — скаляры, точки и векторы. Существуют, по крайней мере, три варианта определения этих сущностей, в зависимости от того, с какой точки зрения их рассматривать — чисто ма- математической (формальной), геометрической или с точки зрения программной реализации. В конечном счете нам придется иметь дело со всеми тремя вариантами, хотя на первый взгляд и кажется, что они мало связаны друг с другом. Несмотря на то что существует огромное множество примеров каждого из трех базовых типов, в этой главе мы рассмотрим только по одному представителю каждого типа. Как бы там ни было, но для нас наибольшую важность представляет вопрос об отличиях между ма- математическим определением каждого типа и его частной реализацией, что требует основа- основательного знакомства с математикой, лежащей в основе дальнейших рассуждений. 4.1.1. Геометрическое определение базовых типов Роль фундаментального геометрического объекта в компьютерной графике играет точка. В трехмерной геометрической системе точка— это положение в трехмерном пространстве. Единственным атрибутом точки является ее положение. Математическая точка даже не имеет размера. Точки существуют в пространстве независимо от какой-либо системы коор- координат. Конечно, это может показаться неудобным, поскольку на определенную точку прихо- приходится ссылаться как "точка вот там" или "синяя точка рядом с красной". Проблема ссылок решается с помощью систем координат или фреймов (раздел 4.3), но сейчас нас интересует, как далеко можно продвинуться, не обращаясь к конкретной системе отсчета. Скаляры — это всегда действительные числа. Хотя скаляры и не имеют геометрических свойств, они понадобятся нам в качестве единиц измерения. Например, длина отрезка есть скаляр. Скаляром является и угол вращения объекта. В компьютерной графике точки часто связываются направленными отрезками, как по- показано на рис. 4.1. Такие отрезки прямых и есть векторы. В более общем смысле физики используют термин вектор для обозначения любой величины, характеризуемой направле- направлением и значением. Физические величины, такие как скорость и сила, являются векторами. Однако вектор в этом смысле не имеет фиксированной точки приложения (позиции). Сле- Следовательно, направленные отрезки, показанные на рис. 4.2, являются одинаковыми векто- векторами, поскольку имеют одно и то же направление и одинаковую длину, хотя и разные точ- точки приложения. Мы часто будем использовать в этой книге термины вектор и направлен- направленный отрезок как синонимы. Длина и направление вектора характеризуются вещественными числами. Вектор А на рис. 4.3,а имеет то же направление, что и вектор В, но В имеет длину в два раза большую, чем А, в потому можно записать В = 2А. Вектор С имеет ту же длину, что и А, но противопо- противоположное направление, а потому соотношение между ними выразится формулой С = -А. Объединять векторы можно с помощью правила сочленения начала с концом (head-to-tail rule), схематически представленного на рис. 4.3,6. На этом рисунке конец вектора А сочленен 148 Глава 4. Объекты и геометрические преобразования
с началом вектора В и сформирован новый вектор С, величина и направление которого опре- определяются отрезком, соединяющим начало вектора А и конец вектора В. Этот новый вектор будем называть суммой векторов А и В и записывать соотношение между ними в виде С=А+В. Обратите внимание на то, что поскольку векторы не имеют фиксированной точки приложе- приложения, то для геометрического сложения векторов их можно переносить параллельно самим се- себе куда угодно. Рис. 4.1. Соединение двух точек направленным отрезком Рис. 4.2. Одинаковые векторы Рис. 4.3. Соотношение между векторами: а — выраженное с помо- помощью вещественных чисел; б — правило сочленения начала с концом Точки и векторы — это разные геометрические типы. у Геометрическое представление точки в виде направленного * •р = (х, у, отрезка, соединяющего некоторую опорную точку с задан- заданной (рис. 4.4), следует рассматривать как сомнительное. Од- Однако существует операция, в которой связываются точки и направленный отрезок (см. рис. 4.1.) Можно использовать направленный отрезок для перемещения от одной точки к другой. Точно так же две точки определяют отрезок, прове- проведенный между ними, а точка и вектор определяют вторую точку. Таким образом, правильная интерпретация рис. 4.4 состоит в том, что данный вектор можно определить как ис- исходящий из фиксированной опорной точки (начала системы координат) и приходящий в определенную точку простран- Рис- 4-4- Сомнительное ства. Обратите внимание на то, что вектор, как и точка, су- представление вектора шествует независимо от опорной системы, но, как будет по- показано дальше, мы должны будем работать с их представлениями в определенной систе- системе координат. 4.1. Скаляры, точки и векторы 149
4.1.2. Математическое определение: векторное и аффинное пространства Скаляры, точки и векторы можно рассматривать как элементы математических множеств. Поэтому мы будем использовать разного рода абстрактные пространства для представления и манипулирования этими множествами объектов. В математике принято использовать такие абстрактные пространства для решения разного рода прикладных проблем — от решения дифференциальных уравнений до аппроксимации математических функций. Формальное оп- определение тех пространств, которые представляют для нас интерес — векторного, аффинно- аффинного и Евклидова, — приведено в приложении Б. Нас интересуют только такие пространства, элементами которых являются геометрические типы. Возможно, наиболее важным математическим пространством является векторное (линейное) пространство. Векторное пространство содержит две различные сущности — векто- векторы и скаляры. В этом пространстве существуют правила объединения скаляров с помощью двух операций (сложения и перемножения), и, таким образом, определено скалярное поле. Приме- Примерами скаляров могут быть вещественные и комплексные числа, а также рациональные функции. В векторном пространстве можно комбинировать скаляры и векторы и создавать новые векторы с помощью операции умножения скаляра на вектор. Можно также комбинировать и векторы с помощью операции сложения векторов. Примерами математических векторных пространств являются «-группы действительных чисел, решение однородных линейных дифференциальных уравнений и геометрические операции над направленными линейными отрезками. Аффинное пространство — это расширение векторного пространства, в которое включен дополнительный тип объектов — точка. В аффинном пространстве определена операция сложения точки и вектора, результатом которой является новая точка. Обратной ей является операция вычитания двух точек, результатом которой будет вектор. В линейном векторном пространстве отсутствует способ измерения скалярных величин. Евклидово пространство — это расширение векторного пространства, в которое введена мера для измерения расстояния (размера). В этих абстрактных пространствах объекты могут быть определены независимо от кон- конкретного представления — они просто являются элементами некоторых множеств. Одна из основных концепций векторного пространства состоит в представлении любого вектора в терминах одного или нескольких множеств базисных векторов. Представление обеспечивает связь между абстрактными объектами и их реализацией, а взаимные преобразования одних представлений в другие приводят нас к геометрическим преобразованиям. 4.1.3. Информационное определение Хотя математики предпочитают иметь дело со скалярами, точками и векторами как чле- членами множеств, которые можно комбинировать в соответствии с определенными аксиомами, для специалистов по информатике привычнее и ближе рассматривать их как абстрактные типы данных — АТД {ADT — abstract data types). Абстрактный тип данных — есть множест- множество операций над некоторыми данными, причем операции определяются независимо от внут- внутреннего представления данных или от способа их реализации в конкретной системе. Понятие абстрагирование от данных {data abstraction) является фундаментальным в современной науке о компьютерной обработке информации. Например, операция включения нового эле- элемента в список или перемножения двух многочленов может быть определена независимо от того, как именно хранится список или как представлены действительные числа в данном компьютере. Те, для кого эта концепция стала привычной, безо всякого труда воспринимают разницу между понятием объекта (или операции над объектами) и представлением объектов (или их реализацией) в конкретной системе. С точки зрения информатики нам требуется 150 Глава 4. Объекты и геометрические преобразования
средство, позволяющее объявлять геометрические объекты в тексте программы независимо от их представления или реализации в данном компьютере. Выглядеть такое объявление должно примерно таким образом: vector u,v; point p,q; scalar a,b; Для определения абстрактных типов в объектно-ориентированных языках программиро- программирования, подобных C++, можно применить такие средства, как классы и перегрузку операторов. Это позволяет записать в программе операцию над данными некоторого геометрического ти- типа в следующем виде1: q = p+a*v; Но сначала нужно определить функции, которые будут выполнять операции над данными новых типов, а для этого понадобится рассмотреть те математические соотношения, которые требуется реализовать в этих функциях. 4.1.4. Геометрические абстрактные типы данных Рассмотрев понятия скаляра, точки и вектора с трех точек зрения, мы получили матема- математическую и информационную "оболочку", в которой будем в дальнейшем работать с геомет- геометрическими объектами. Скаляры— это действительные числа, по отношению к которым можно применять обычные операции сложения и умножения. Геометрические точки — это положения в пространстве, а векторы — направленные отрезки в пространстве. На эти объек- объекты распространяются правила, определенные в абстрактном аффинном пространстве. В про- программе можно определить абстрактные типы данных для представления базовых типов объ- объектов и специфицировать операции, подчиняющиеся этим правилам. Теперь рассмотрим, как можно использовать базовые типы для выполнения геометриче- геометрических операций и формирования новых объектов. Будем обозначать в дальнейшем скаляры бу- буквами греческого алфавита а, Р, у,..., точки— прописными буквами латинского алфавита P,Q,R,..., а векторы— строчными буквами латинского алфавита u,v,w, .... Позже, в разделе 4.3, мы введем в рассмотрение и другие абстрактные математические объекты, кото- которые будем обозначать полужирными буквами латинского алфавита. До сих пор мы никак не затрагивали вопрос о системе отсчета, например системе координат, а потому векторы и точ- точки являются для нас пока что абстрактными объектами, а не объектами, представленными в определенной системе отсчета. Модуль вектора v (или его длина, абсолютная величина — magnitude) — это действительное число, которое обозначим ]v|. Операция умножения скаляра на вектор обладает следующим свойством (см. приложение Б): |а v| = |ct||v|, причем при положительном а направление вектора av то же, что и направление вектора v. Обращаю ваше внимание на то, что в дальнейшем нам понадобится специальный вектор ну- нулевой длины (нуль-вектор — zero vector), который будем обозначать полужирной цифрой 0. Существуют две эквивалентные операции, связывающие две точки и вектор. Первая — это вычитание точек, которая формирует из двух точек Р и Q вектор v (рис. 4.5). Эту опера- операцию запишем следующим образом: v = P-Q. 'Поскольку в этой книге в основном используется язык С, а не С+ -*- и поскольку OpenGL не является объ- объектно-ориентированным пакетом, нам приходится пользоваться в примерах несколько устаревшими конст- конструкциями вроде typedefiau struct. 4.1. Скаляры, точки и векторы 151
Следствием такого определения операции вычитания точек является то, что для заданной точки Q и вектора v существует единственная точка Р, которая удовлетворяет приведенному выше соотношению. Это утверждение можно формально выразить следующим образом: пусть заданы точка Q и вектор v, тогда существует точка Р, такая, что Р = v+Q. Таким образом, Р формируется с помощью операции сложения вектора с точкой. Пра- Правило сочленения начала вектора с концом другого вектора позволяет очень наглядно пред- представить операцию сложения векторов (рис. 4.6,а). Из правила сложения векторов следует и отношение, справедливое для любых трех точек P,Q и R (рис. 4.6,6): (P-Q) + (Q-R) = P-R. P-R Рис. 4.5. Вычитание точек Q Рис. 4.6. Правило сочленения начала одного вектора с концом другого: а — сложение векторов; б — соотношения между точками P-Q 4.1.5. Прямые Логическим следствием операции сложения точки и вектора (или, если вам это нравится больше, операции вычитания точек) является понятие прямой в аффинном пространстве. Рас- Рассмотрим все точки, удовлетворяющие соотношению Р(а) = P0 + ad, где Ро— произвольная точка, d— произвольный вектор, а а— некоторый скаляр. В соот- соответствии с правилами объединения точек, векторов и скаляров, определенными в аффинном пространстве, Р(а) является точкой при любом заданном значении а. Все эти точки будут лежать на прямой, как показано на рис. 4.7. Такую форму представления прямой часто назы- называют параметрической, поскольку совокупность точек, представ- представляющих прямую, формируется в результате изменения значения параметра а. При а = 0 точка на прямой совпадает с Ро, и по мере увеличения а точки располагаются все дальше от исходной в на- направлении векторам/. Если ограничить значения параметра а только неотрицательными числами, то получим луч, исходящий из точки Ро в направлении вектора d. Рис. 4.7. Определение прямой в аффинном пространстве 152 Глава 4. Объекты и геометрические преобразования
4.1.6. Аффинное сложение В аффинном пространстве определены операции сложения двух векторов, умножения вектора на скаляр и сложения вектора и точки, а операции сложения произвольных точек и умножения точки на скаляр отсутствуют. Но в этом пространстве существует также операция аффинного сложения {affine addition), в которой имеются некоторые элементы двух послед- последних операций. Для любой точки Q, вектора v и положительного скаляра а результат операции Р = Q+av описывает все точки на прямой, проходящей через точку Q в направлении вектора v (рис. 4.8). Всегда можно найти такую точку R, что v = R-Q. Следовательно, Р = Q + a(R-Q) = aR Эта операция выглядит так же, как и сложение двух точек, и, следовательно, имеет эквива- эквивалентную форму: /> = a,fi + a2Q, где а, + а2 = 1. 4.1.7. Выпуклость Выпуклым (convex) является такой объект, для которого соблюдается следующее правило: все точки, лежащие на отрезке, соединяющем две любые точки, принадлежащие объекту, также принадлежат этому объекту. Мы уже видели, на примере многоугольников (см. гла- главу 2), насколько важна выпуклость объекта. Для более формального анализа выпуклости мы теперь можем воспользоваться операцией аффинного сложения. При 0<а<1 операция аф- аффинного сложения определяет отрезок, связывающий точки R и Q, как показано на рис. 4.9. Следовательно, отрезок прямой является выпуклым объектом. Расширим операцию аффин- аффинного сложения и на объекты, заданные п точками Ри Р2, ..., Р„. Рассмотрим выражение Рис. 4.8. Аффинное сложе- Рис 4.9. Отрезок прямой, со- ние единяющий две точки Можно доказать по индукции, что эта сумма определена тогда и только тогда, когда а,+ сх2 + ... +а„=1. 4.1. Скаляры, точки и векторы 153
Множество точек, образованных аффинным сложением п точек, на которое распространяется дополнительное ограничение а,>0, /= 1,2,..., я, называется выпуклой оболочкой {convex hull) исходного набора точек (рис. 4.10). Несложно доказать по индукции, что выпуклая оболочка включает все отрезки прямых, связывающих любую пару точек из набора {Ри Р2,..., Р,,}. В геометрической трактовке выпуклая оболочка— это множество точек, при- принадлежащих поверхности минимальной площади, "натяну- "натянутой" на заданное множество исходных точек. Понятие выпук- выпуклости играет особенно важную роль при конструировании кривых и поверхностей, и мы еще не раз вернемся к нему, в частности в главе 10. Рис. 4.10. Выпуклая оболочка 4.1.8. Скалярное и векторное произведение Многие геометрические процедуры, связанные с определением углов между векторами, реализуются с помощью элементарных операций скалярного (dot, inner) и векторного (cross) произведений двух векторов. Скалярное произведение векторов и и v обозначается и • v. Если и • v = 0, то и и v являются ортогональными. В Евклидовом пространстве квадрат модуля век- вектора есть скалярное произведение вектора на самого себя: \и\2= и ¦ и. Угол между двумя векторами определяется по формуле Кроме того, выражение описывает ортогональную проекцию вектора v на и, как показано на рис. 4.11. Если в трехмерном пространстве заданы три линейно-независимых вектора, то можно с помо- помощью операции скалярного произведения построить три вектора, каждый из которых будет ортого- ортогонален двум другим. Этот процесс рассмотрен в приложении Б. Можно также использовать два не- непараллельных вектора, и и v, для того чтобы определить третий вектор п, ортогональный по отно- отношению к первым двум (рис. 4.12). Этот вектор задается с помощью операции векторного произведения (crossproduct), формальное определение которой приведено в Приложении В: п = и х v. |и| cos в Рис 4.11. Скалярное произведение и ортогональная проекция и Xv Рис. 4.12. Векторное произве- произведение 154 Глава 4. Объекты и геометрические преобразования
Модуль векторного произведения позволяет определить синус угла 6 между векторами- сомножителями и и v. Обратите внимание на то, что векторы и, v и п образуют правостороннюю систему коорди- координат {right-handed coordinate system), т.е. если вектор и направлен в ту же сторону, что и большой палец правой руки, а вектор v будет направлен вдоль указательного пальца, то век- вектор п будет параллелен среднему пальцу. 4.1.9. Плоскости Плоскость {plane) в аффинном пространстве определяется прямым расширением пара- параметрического представления прямой. Из курса элементарной геометрии вам должно быть из- известно, что три точки, не лежащие на одной прямой, однозначно определяют плоскость. Предположим, что P,QwR — это именно такие три точки в аффинном пространстве. Отре- Отрезок, который соединяет PwQ, есть множество точек, описываемое соотношением Предположим, что на этом отрезке выбрана произвольная точка и из нее проведен отрезок до точки R, как показано на рис. 4.13. Точки, расположенные на этом отрезке, можно описать, воспользовавшись вторым параметром, Р: р У'Ца R Л \ S(a) Q Эти точки определяются уже парой параметров — а и Р, а их совокупность образует плоскость, заданную точками Р, Q и R. Объединив два предыдущих уравнения, получим уравне- уравнение плоскости в параметрическом виде: Да, Р) = Р[а/> + A-аH] + A-р)Л. Полученное уравнение можно переписать в другом виде: Да, 3) = Р + Kl-aXfr-fl + О-т-Р). Учитывая, что Q-P и R-P представляют собой векторы, мож- можно показать, что плоскость также определяется точкой Ро и парой непараллельных векторов и и v следующим образом: Да, р) = Ро + аи + Pv. Несложно прийти к заключению, что при 0 < а, Р < 1 все точки Да, Р) лежат внутри тре- треугольника, образованного вершинами P,QwR. Если точка Р принадлежит плоскости, то Р-Ро = аи + pv. Можно определить вектор п, который будет ортогонален и и, и v. Воспользовавшись вектор- векторным произведением п = и х v, получим уравнение плоскости в таком виде: п-(Р-Р0) = 0. Вектор п перпендикулярен (ортогонален) плоскости; его принято называть нормалью к плос- плоскости. Уравнения прямых, в левой части которых стоит член Р(а), и плоскостей, в левой час- части которых стоит член Да, Р), принято называть уравнениями в параметрической форме, поскольку они определяют точки на прямой или плоскости как функции параметров а и р. 4.1. Скаляры, точки и векторы 155
4.2. Трехмерные примитивы В трехмерном пространстве значительно возрастает разнообразие геометрических объектов, доступных компьютерной графике. При работе на двухмерной плоскости в примерах, рассмот- рассмотренных в главе 2, мы имели дело только с отрезками, плоскими кривыми и объектами, которые имели внутреннюю область, в частности многоугольниками. В трехмерном пространстве все эти типы объектов сохраняются, но, во-первых, они могут лежать в разных плоскостях, а во-вторых, к ним добавляются пространственные кривые (рис. 4.14). Помимо плоских объектов с внутрен- внутренней областью, существуют и аналогичные пространственные объекты — участки криволиней- криволинейных поверхностей (рис. 4.15). В трехмерном пространстве появляется совершенно новый класс объектов — объемные тела, такие как параллелепипеды и эллипсоиды (рис. 4.16). Рис. 4.14. Трехмерные ' Рис 4.15. Трехмерные Рис. 4.16. Объемные кривые поверхности тела При создании графической компьютерной системы, способной работать со всем разнооб- разнообразием трехмерных объектов, программист неизбежно сталкивается со множеством проблем, из которых наиболее существенны две. Во-первых, математическое описание пространствен- пространственных объектов оказывается значительно более сложным, чем их двухмерных аналогов (если таковые вообще существуют). Во-вторых, из всего разнообразия трехмерных объектов при- приходится отбирать такие, которые могут быть эффективно реализованы методами компьютер- компьютерной графики (имеется в виду как процесс отображения, так и манипулирование объектами). Те типы объектов, которые не попали в это множество, нужно аппроксимировать — прибли- приближенно представить объектами отобранных типов. Трехмерные объекты, работа с которыми может быть реализована существующими аппа- аппаратными и программными средствами компьютерной графики, должны обладать следующи- следующими свойствами. 1. Объекты описываются поверхностями и могут рассматриваться как полые. 2. Количественно объекты полностью характеризуются множеством трехмерных вершин. 3. Объекты либо состоят из плоских выпуклых многоугольников, либо могут быть ап- аппроксимированы плоскими выпуклыми многоугольниками. Понять, почему мы ограничили множество реализуемых в графических системах объектов таким образом, можно, если принять во внимание, что эффективнее всего в современных графических системах выполняется отображение треугольников, т.е. простейших плоских многогранников. Графические рабочие станции высокого класса способны выводить на экран до 5 миллионов маленьких тонированных треугольников в секунду. Специализированные платы управления монитором для персональных компьютеров обеспечивают вывод до 1 миллиона тонированных треугольников в секунду2. Из первого условия следует, что для 2Показателем производительности графической системы обычно является количество треугольников, объединенных в полосы. Кроме того, указывается, поддерживается ли на аппаратном уровне затенение или засветка этих треугольников без снижения скорости вывода их на экран. 156 Глава 4. Объекты и геометрические преобразования
моделирования трехмерных объектов вполне достаточно двухмерных примитивов (плоский многоугольник, даже расположенный в плоскости, произвольно ориентированной в трехмер- трехмерном пространстве, является все же двухмерным примитивом). Второе условие является, по сути, расширением выводов, сделанных в главах 1 и 2. Если объект количественно полностью характеризуется множеством вершин, он наилучшим образом подходит для реализации в сис- системе с конвейерной архитектурой. Последнее из сформулированных условий следует из об- обсуждения свойств двухмерных многоугольников. Большинство графических систем оптими- оптимизировано с учетом потребностей обработки и отображения точек и многоугольников. В трех- трехмерном пространстве многоугольник определяется упорядоченным множеством вершин, но если таких вершин больше трех, то многоугольник не обязательно является плоским, т.е. все его вершины не всегда лежат на одной плоскости, а следовательно, значительно сложнее оп- определить внутреннюю область этого объекта. Поэтому в подавляющем большинстве графи- графических систем на прикладную программу возлагается ответственность за то, что отображае- отображаемые многоугольники являются плоскими. В противном случае не гарантируется корректный результат растрового преобразования. Поскольку треугольник по самой своей природе явля- является плоским многоугольником, то все поверхности в графической системе желательно пред- представлять множеством таких треугольников, причем это может выполняться как на стадии мо- моделирования объектов отображаемой сцены, так и на стадии подготовки объектов к отобра- отображению. В последнем случае именно графическая система производит разбиение {tessellation) произвольных многоугольников на треугольные компоненты. Эти же аргументы сохраняются и в отношении криволинейных поверхностей, например сферических. Такие криволинейные поверхности нужно аппроксимировать плоскими многоугольниками, причем проще всего выполняется аппроксимация треугольниками. Следовательно, даже если система моделиро- моделирования и использует при построении модели криволинейные поверхности, подразумевается, что на одном из следующих этапов обработки выполняется их полигональная аппроксимация. Исключением из этого подхода является новое направление в компьютерной графике, ко- которое получило название конструктивная геометрия тел — KIT (CSG — constructive solid geometry). В таких системах объект строится из небольшого множества монолитных (объемных) примитивов с помощью операций теории множеств, таких как объединение и пересечение. Мы вернемся к моделированию на базе конструктивной геометрии тел в главе 8. Хотя для решения задач моделирования использование концепции КГТ очень эффективно, отображать созданные модели значительно труднее, чем модели, построенные на базе поли- полигонального разбиения поверхностей. Вполне возможно, что в будущем ситуация изменится, но сейчас мы будем уделять основное внимание методам отображения полигональных моде- моделей объектов. Все примитивы, с которыми приходится работать в таких моделях, описываются множе- множествами вершин. Если перейти от абстрактных объектов к реальным, нужно сначала опреде- определиться с методами представления в графической системе вершин — точек в пространстве. 4.3. Системы координат и фреймы В предыдущих разделах мы рассматривали точки и векторы как абстрактные объекты, не привязываясь ни к какой системе отсчета. В трехмерном векторном пространстве можно од- однозначно представить любой вектор ir в виде линейной комбинации любых трех линейно- независимых векторов v,, v2 и v3 (см. приложение Б): \v = a,v, + a:v2 + (X3V3. Скаляры 0(|, а2 и а3— это компоненты (или координаты) вектора и- в базисе vb v2, v3. Это отношение показано на рис. 4.17. Можно записать представление вектора ir в некотором базисе в виде матрицы, состоящей из одного столбца: 4.3. Системы координат и фреймы 157
а = а, а, а, Здесь и далее буквы, набранные полужирным шрифтом, обозначают представление в опреде- определенном базисе, в отличие от абстрактного вектора w, который обозначается обычным шриф- шрифтом. Это же соотношение можно записать и в виде Векторы V|, v2, v3, образующие базис, определяют конкретную систему координат. Но при рассмотрении проблем, в которых принимают участие точки, векторы и скаляры, нам потребуется более общий метод определения системы координат. Суть проблемы поясняет рис. 4.18. Систему координат, представлен- представленную на рис. 4.18,а, образуют три вектора, причем все они исходят из одной и той же точки. Именно так мы и при- привыкли рисовать систему координат на бумаге. Эти три вектора можно использовать в качестве базиса для пред- представления любого вектора в трехмерном пространстве. Однако выше уже отмечалось, что векторы как геометри- геометрические объекты не имеют точки приложения, а характе- характеризуются только направлением и модулем. Следователь- Следовательно, векторы на рис. 4.18,а,б эквивалентны. Большинство студентов весьма смущает базис, представленный на пра- правой картинке, хотя с точки зрения чистой математики обе картинки имеют один и тот же смысл. Но у нас по- прежнему остается нерешенной проблема представления точки — геометрического объекта, который имеет фикси- фиксированное положение в пространстве. Рис. 4.17. Разложение вектора по векторам базиса / 1 о) б) Рис. 4.18. Системы координат: а — векторы, исходящие из од- одной и той же позиции; 6 — свободные векторы Поскольку аффинное пространство включает и точки, то, как только мы зафиксируем оп- определенную точку отсчета — начало координат, в нем можно будет однозначно предста- 158 Глава 4. Объекты и геометрические преобразования
вить любую точку. Общепринято изображать оси системы координат как исходящие из точ- точки начала координат (см. рис. 4.18,а). Это имеет смысл в аффинном пространстве, в котором можно представлять как векторы, так и точки. Однако такое представление требует исполь- использования точки отсчета и векторов базиса, которые в совокупности называются фреймом {frame). Если не требовать особой строгости в формулировках, то можно считать, что базис- базисные векторы исходят из некоторой точки Ро. В конкретном фрейме любой вектор можно од- однозначно записать в виде w = a,v, +CUV,+a3v3, точно так же, как и в векторном пространстве. Кроме того, для любой точки можно написать соотношение TI2V2 + TI3V3. Таким образом, любой вектор описывается в фрейме тремя скалярами, а описание точки включает три скаляра и данные о точке отсчета. Как будет показано в разделе 4.3.3, отказ от знакомого со школьной скамьи понятия системы координат в пользу менее привычного поня- понятия фрейма позволит избежать трудностей при работе с векторами, которые имеют направле- направление и величину, но не имеют фиксированной точки приложения. Кроме того, концепция фрейма позволяет представлять точки и векторы таким образом, что для манипуляций с ними можно использовать единый математический аппарат матричной алгебры, сохраняя, тем не менее, различие между этими двумя типами геометрических объектов. 4.3.1. Замена систем координат Очень часто требуется выяснить, как изменится представление вектора при изменении ба- базиса. Предположим, что {vb v2, v3} и {ии и2, м3} — два базиса. Каждый вектор второго базиса можно представить в первом базисе (и наоборот). Следовательно, существует девять скаляр- скалярных компонентов {у/у}, таких, что Эти скаляры можно "уложить" в матрицу 3x3: ii 112 113 М = Y 21 Y22 Y23 _Y 31 Y32 Y33. а соотношение между компонентами представить в матричной форме: Матрица М содержит информацию, необходимую для преобразования представления вектора в одном базисе в представление в другом. Обращение матрицы М дает матрицу, которая по- позволяет преобразовать представление в базисе {иь и2, м3} в представление в базисе {v,, v2, v3}. Пусть вектор w имеет в базисе {vb v2, v3} представление {a,, a2, a3}, т.е. W = + (X2V2 + CC3V3. 4.3. Системы координат и фреймы 159
Это же соотношение можно записать и в матричной форме: где а = Предположим, что b является представлением w в базисе {иии2,щ}: или и. ul где "А" Л. Таким образом, используя представление во втором базисе в терминах первого, получим: V, «2 = ьгм Следовательно, Матрица (М7) ' позволяет перейти от а к Ь: b = Аа = (Мг)"'а. Важность этого результата заключается в том, что он позволяет перейти от рассмотрения абстрактных векторов к операциям с представлениями векторов— матрицами-столбцами, состоящими из скаляров. Но при работе с такими матрицами всегда нужно помнить о том, что за ними "скрывается" определенный базис, иначе мы рискуем оказаться совсем в другой системе координат. Изменение базиса не затрагивает положение точки начала координат. Такое изменение мож- можно использовать для представления поворота или масштаба набора векторов базиса (рис. 4.19). Однако плоскопараллельное смещение фрейма или перенос начала координат (рис. 4.20) таким способом представить нельзя. После того как мы рассмотрим простой пример, будет введено понятие однородных координат, пользуясь которыми можно с помощью операций над матри- матрицами выполнять комплексное преобразование фреймов. 160 Глава 4. Объекты и геометрические преобразования
, V V2 1 Рис. 4.19. Поворот и масштаби- масштабирование базиса Рис 4.20. Плоскопараллельное смещение базиса 4.3.2. Пример изменения представления Предположим, что имеется вектор w, представление которого в некотором базисе имеет вид а = Обозначим три вектора базиса соответственно vu v2, v3. Тогда It' = V! + 2v2 + 3v3. Теперь предположим, что возникла необходимость сформировать новый базис на основе прежнего: Ml = V,, 1/2 = V, + V2, Щ ~ vl + V2 + V3- Матрица М в этом случае имеет следующий вид: М = Матрица преобразования представления из базиса {v,, v2, v3} в базис {м,, м2, м3} имеет вид 1 1 1 0 1 1 0 0 1 3 Если мы работаем в метрическом трехмерном пространстве (/?'| а не в абстрактном, то молено ас- ассоциировать базис V/. v^, \з с единичным базисом в Я4: е. = 1 0 0 . е2 = 0 1 0 0 0 1 4.3. Системы координат и фреймы 161
1 1 1 Г1 -1 1 А=(МГ)"'= Oil =0 1 -1 [0 0 lj [О 0 1 В новой системе координат представление w будет иметь вид "-Г = Аа = -1 Отсюда следует, что w =-М| - и2 + Зм3. 4.3.3. Однородные координаты Когда речь заходит о представлении точки Р, имеющей положение (х, у, z), с помощью трехмерного фрейма с началом в точке Ро и базисом {vh v2, v3}, то первой приходит в голову мысль использовать матрицу-столбец Р = где x,y,z — компоненты базисных векторов в этой точке, т.е. коэффициенты в соотношении Р = Ро + XV\ + yv2 + ZVj. Но в этом случае точка будет представлена так же, как и вектор w = 5|V| + 52v2 + 53v3, поскольку w представляется столбцом X S, А. В литературе очень часто точка (x,y,z) ассоциируется с вектором, проведенным в эту точку из начала координат. Эта ассоциация может привести к недоразумениям. Например, вектор из точки A, 1, 1) в точку B, 3,4) равен вектору, проведенному из точки @, 0, 0) в точ- точку A, 2, 3), поскольку оба вектора имеют одинаковый модуль и направление. Но первый век- вектор нельзя ассоциировать с точкой A, 2, 3) до тех пор, пока его точка приложения не будет совмещена с началом координат — точкой @, 0,0). Пренебрежение различием в представлении точек и векторов может серьезно усложнить реализацию системы, поскольку после этого не удастся представить смещение фрейма как все другие виды трансформаций — с помощью операции перемножения матриц. Поэтому пред- предпочтительнее выбрать такой способ представления, который позволит легко отличать точки от векторов. Для этого мы воспользуемся так называемыми однородными координатами (homogeneous coordinates). В системе однородных координат для представления точек и векторов трехмерного пространства используются четырехмерные матрицы-столбцы. Во фрейме, заданном набором параметров (уг, v2, v3, Л,), любую точку Р можно однозначно предста- представить соотношением Р = PQ+ <x,v, + cc2v2 + <x3v3. 162 Глава 4. Объекты и геометрические преобразования
Если на множестве точек операцию умножения на скаляры 0 и 1 определить следующим образом: 0/> = 0, 1 Р = Р, то приведенное выше соотношение можно записать в матричной форме с помощью пере- перемножения матриц: а, а, Строго говоря, это выражение не является скалярным произведением, поскольку элемен- элементы матрицы разнородны, но в компьютере такое выражение реализуется той же подпрограм- подпрограммой, что и скалярное произведение. Четырехмерная матрица-строка в правой части уравне- уравнения — это представление в однородных координатах точки Р во фрейме, определенном па- параметрами (vb v2, v3, Ро). Точку Р можно представить и матрицей-столбцом Р = В том же самом фрейме любой вектор w можно представить в виде V, = 51vl+61v1+51v1=[81 52 5, О]7 Следовательно, вектор w можно представить матрицей-столбцом О Существует множество способов геометрической интерпретации этих формул. Для нас же самое важное состоит в том, что, пользуясь формальным аппаратом однородных координат, можно выполнять операции над точками и векторами с помощью обычных операций матрич- матричной алгебры. Рассмотрим, например, изменение параметров фрейма — проблему, которую не удается просто решить при использовании обычного трехмерного представления. Если ( V|, v2, vifPQ) и ( u\, и2, «з, Q) — два фрейма, то векторы базиса второго фрейма и его начало координат можно выразить в терминах первого фрейма следующим образом: Щ = 4.3. Системы координат и фреймы 163
Эти уравнения можно записать и в матричной форме: и,' м, 2 Щ а = м V, 2 V3 где на сей раз М обозначает матрицу размером 4x4: *Yn Y.2 Y.3 О" М = Y2| Y3I Y4I Y22 Y32 Y42 Y23 Y33 Y43 0 0 1 Матрица М называется матрицей представления изменения фреймов. Матрицу М можно использовать и для непосредственного вычисления изменения пред- представления. Пусть а и b — представления в однородных координатах двух точек (или двух векторов) в разных фреймах. Тогда и. ": "з .а_ = Ь7М V, V2 v3 А. = а7 V, V2 v3 /о. Следовательно, а = М7Ь. Использование аппарата однородных координат имеет и множество других достоинств, кото- которые мы будем повсеместно использовать по ходу изложения всего материала этой книги. Возможно, наиболее важным из них является то, что все аффинные преобразования (т.е. со- сохраняющие линейные свойства геометрических объектов) при использовании представления в однородных координатах выполняются единообразно — с помощью перемножения матриц. Хотя для решения задач трехмерного пространства при этом используется четыре измерения, количество арифметических операций в результате даже снижается. Единообразное пред- представление всех аффинных преобразований позволяет, во-первых, довольно просто организо- организовать последовательное выполнение сложных преобразований (конкатенацию преобразова- преобразований), а во-вторых, реализовать большинство этих операций аппаратно, что значительно по- повышает скорость обработки. 4.3.4. Пример перехода из одного фрейма в другой Вернемся к примеру из раздела 4.3.2. Опять начинаем с базиса {vb v2, v3} и преобразуем его в базис {ии и2, щ), три уравнения для векторов которого имеют вид Щ = V|, и2 ~ vl + V2» Щ = V, + V2 + V3. Точка отсчета фрейма не меняет своего положения, а потому к этим трем уравнениям доба- добавим четвертое: 164 Глава 4. Объекты и геометрические преобразования
Нас интересуют матрица преобразования М, обратная ей матрица М ' и транспонированная матрица Мт. Матрица М имеет вид М = О О Предположим, что, помимо изменения базиса, требуется изменить и точку отсчета в новом фрейме. Ее положение в прежнем фрейме имеет представление A, 2, 3, 1). Вектор смещения v=V|+2v2+3v3 сместит Ро в Qo. Ненулевой четвертый компонент означает, что объект является точкой. Таким образом, к трем уравнениям предыдущего примера добавляется четвертое: Qo = Л>+ v,+ 2v2+ 3v3, а матрица М7 примет вид 1111 1 о о о о о Обратная матрица имеет вид 1-10 1 -1 2 1 -3 0 1 Эти матрицы позволяют преобразовывать представления в разных фреймах в обе стороны. Обратите внимание на то, что матрица А преобразует точку, имеющую в исходном фрейме представление A, 2, 3) или в однородных координатах представление Г р = в начало координат нового фрейма: " 0 0 1 Но вектор A,2,3), который имел в однородных координатах исходного фрейма представление я = 4.3. Системы координат и фреймы 165
преобразуется в новом фрейме в '-Г ь="з' О Этот результат полностью согласуется с тем, который мы получили в примере с изменением систем координат. Он также демонстрирует, как важно отличать точки от векторов при вы- выполнении операций. 4.3.5. Фреймы и абстрактные типы данных До сих пор мы рассматривали сугубо математические вопросы представления геометри- геометрических объектов. Теперь наступила пора "спуститься на фешную землю" и задуматься над тем, как вся эта математика может быть реализована в компьютерной программе. Начнем с понятия об абстрактных типах данных. В предыдущих разделах мы рассмотрели несколько типов геометрических объектов, в част- частности точки, прямые и плоскости, исполняющих в фафических системах роль примитивов — "кирпичиков", из которых строятся другие объекты. Поэтому желательно, чтобы фафическая система позволяла включить в профамму определение переменных в таком, например, виде: 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 p*{1.0,2.0,3.0}; 166 Глава 4. Объекты и геометрические преобразования
Но это повлечет за собой два нежелательных следствия. Во-первых, это приведет к смешива- смешиванию конкретной реализации и абстрактного типа данных. В результате программный код не будет переносим с одной графической системы на другую, т.е. будет зависеть от конкретной реализации. Во-вторых, при такой инициализации "за кадром" остается вопрос, представле- представление в какой системе координат или в каком фрейме формируется этим кодом. Первую проблему можно решить, разработав спецификацию набора функций, реализую- реализующих стандартные абстрактные операции, что избавит прикладного программиста от необхо- необходимости разбираться в деталях конкретной реализации. Например, инициализация точки мо- может выполняться вызовом функции р = new_point3A.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); или использовать текущий фрейм, который становится в этом случае таким же параметром текущего состояния системы, как и прочие атрибуты (тип линии, цвет и т.п.). 4.3.6. Фреймы в OpenGL При работе с OpenGL мы пользуемся двумя фреймами— фреймом камеры и мировым фреймом. Фрейм камеры можно рассматривать как фиксированный. Матрица вида задает по- положение мирового фрейма относительно фрейма камеры. Таким образом, матрица вида {model- view matrix) преобразует представление точек и векторов в однородных координатах в мировом фрейме в представление в фрейме камеры4. Поскольку матрица вида является одним из компо- компонентов текущего состояния графической системы, то, следовательно, в любой момент в системе специфицированы фреймы камеры и мировой. OpenGL поддерживает стек матриц, в котором можно сохранять текущую матрицу вида, или, что то же самое, два фрейма. Как мы уже говорили в главе 2, камера всегда размещается в точке начала координат своего фрейма. Векторы базиса этого фрейма направлены следующим образом: один, у, вверх по от- отношению к камере, второй, z, в направлении, обратном направлению визирования камеры, а третий, х, таким образом, чтобы вместе с двумя первыми образовать правостороннюю ортого- ортогональную систему координат. Другие фреймы, которые понадобятся нам в процессе размещения объектов сцены, формируются с помощью однородных преобразований относительно фрейма камеры. В разделе 4.5 будет описано, каким образом можно задавать такие преобразования. В главе 5 мы рассмотрим использование этих преобразований при размещении камеры относи- 4Можно считать и наоборот — мировой фрейм фиксирован, а фрейм камеры подвижен. Какой точки зрения вы будете придерживаться — дело вкуса. 4.3. Системы координат и фреймы 167
тельно объектов сцены. Поскольку изменение фрейма представляется матрицей вида и система позволяет сохранять текущее значение матрицы, у прикладного программиста имеется возмож- возможность переключаться между фреймами, изменяя текущее значение матрицы вида. Прежде чем перейти к подробному анализу методов выполнения преобразований и реализа- реализации этих методов в 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. Фреймы камеры и мировой: а — исходное положение по умолчанию; б—размещение по- после задания матрицы вида 168 Глава 4. Объекты и геометрические преобразования
В OpenGL матрицу вида можно задать поэлементно, передав 16 чисел в качестве аргумен- аргументов функции glLoadMatrix(). Однако не ясно, как определить нужные значения элементов. При решении геометрических задач мы всегда мыслим категориями последовательных пре- преобразований (элементарных переходов), таких как плоскопараллельные смещения, повороты и изменения масштаба. Этому подходу мы и последуем в дальнейшем. 4.4. Модель разноцветного куба Выше вы познакомились с инструментарием — математическим аппаратом и средствами его программной реализации, — необходимым для создания трехмерных графических при- приложений. Как и в рассмотренных примерах двухмерных приложений, мы будем следовать подходу, ориентированному на конвейерную архитектуру системы. Объекты снова опреде- определяются набором вершин, которые подвергаются последовательным преобразованиям до того, как соответствующие примитивы будут преобразованы в растр в буфере кадра. В основе всех преобразований лежит математический аппарат однородных координат, который позволяет эффективно реализовать весь процесс обработки графической модели. В этом разделе будет рассмотрена задача формирования на экране изображения вращаю- вращающегося куба. (Один кадр такого "мультфильма" представлен на рис. 4.22.) Всю задачу можно разбить на ряд подзадач: ¦ моделирование; ¦ преобразование во фрейм камеры; ¦ отсечение; ¦ проецирование; ¦ удаление невидимых поверхностей; ¦ преобразование в растр. Ниже мы по очереди рассмотрим каждую из подзадач. Начнем с того, каким образом можно представить трехмерный объект, опираясь на множество его вершин, аналогично то- тому, как это делалось с двухмерными объектами. Для этого нам потребуется разработать структуру данных, учитывающую связи между вершинами, реб- ребрами и гранями геометрического трехмерного объекта. Такая структура дан- данных в OpenGL поддерживается массивами вершин (vertex arrays), о которых речь пойдет в конце данного раздела5. Рис. 4.22. Один кадр анимации вращающегося куба Разобравшись с методами моделирования куба, мы перейдем к его анимации, используя для этого математический аппарат аффинных преобразований в однородных координатах (подробно они будут рассмотрены в разделе 4.5). С помощью этих преобразований будет из- изменяться матрица вида OpenGL. В главе 5 мы опять будем использовать эти преобразования в качестве одного из этапов конвейерного процесса визуализации. Описания вершин будут подвергаться последовательным преобразованиям по мере "перемещения по конвейеру", причем на каждой стадии будет применяться все то же представление вершин в однородных координатах. На выходе конвейера описания объектов, опирающиеся на множество транс- трансформированных вершин, подвергаются растровому преобразованию. На этой стадии можно смело полагать, что все будет выполнено корректно, если предыдущие преобразования были сделаны правильно. Средства поддержки операций с массивами вершин включены в версию OpenGL 1.1. 4.4. Модель разноцветного куба 169
4.4.1. Моделирование куба Пожалуй, трудно подобрать для первых экспериментов более простой трехмерный объ- объект, чем куб. Но даже такой простой объект можно моделировать в компьютере по- разному. В системе 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}; glVertexIfv(vertices[0]); glVertex3fv(vertices[3]); glVertex3fv(vertices!2]); glVertex3fv(vertices[1]); glEnd(); Другие пять граней определяются аналогично. Обратите внимание на то, что трехмерный многоугольник в программе определяется точно так же, как и двухмерные в рассмотренных выше примерах. 4.4.2. Внешние и внутренние грани При определении трехмерных многогранников порядок перечисления вершин имеет очень существенное значение. Во фрагменте программы, приведенном выше, мы перечисли- перечислили вершины первой грани в таком порядке: 0, 3, 2, 1. Перечисление в порядке 1, 0, 3, 2 приве- приведет к тому же результату, поскольку последняя вершина в списке автоматически соединяется с первой. Однако порядок 0, 1, 2, 3 даст совсем иной результат. Хотя таким образом форми- формируется тот же самый контур многоугольника, ребра будут "обходиться" в обратном направле- направлении, как показано на рис. 4.23. Следует учитывать, что многоугольник (точнее, его внутрен- внутренняя область) имеет две стороны — внешнюю и внутреннюю. Можно отображать любую из них или обе, но в любом случае нужно располагать каким-то методом идентификации сторон. 170 Глава 4. Объекты и геометрические преобразования
Будем называть грань смотрящей наружу, или внешней {outward facing), если при взгляде с внешней стороны объекта на эту грань ее вершины, расположенные в том порядке, который задан при определе- определении грани, "обходятся" против часовой стрелки. Этот метод определе- определения порядка вершин некоторого контура известен как правило правой руки {right-hand rule), поскольку, если расположить четыре согнутых пальца правой руки вдоль направления обхода контура, большой палец будет указывать наружную сторону грани. В нашем примере очень важно перечислить при определении первой грани вершины именно в порядке 0, 3, 2, 1, а не 0, 1,2,3, поскольку в Рис 4.23. Обход этом случае внешняя сторона многоугольника будет и внешней по от- вершин много- к б угольника ношению к кубу . J 4.4.3. Структура донных для представления объектов Теперь рассмотрим, каким образом описать с помощью множества вершин объемный объект — куб. Можно, например, вызывать функцию формирования многогранника glBegin(GL_POLYGON) шесть раз, передавая ей каждый раз свой набор из четырех вершин (посредством glVertex()) и завершая список вызовом glEnd(). Можно использовать другой способ — вызвать glBegin(GL QUADS), а затем передать список из 24 вершин, который завершается glEnd(). Оба эти способа будут восприняты графической системой, но при этом, хотя гео- геометрия куба и будет воспроизведена, информация о его топологии как составного объекта будет утеряна. Если рассматривать куб как многогранник, то он представляет собой сово- совокупность шести граней, которые являются многоугольниками с совпадающими вершина- вершинами, — каждая вершина принадлежит трем граням. Каждая пара соседних вершин опреде- определяет ребро, которое принадлежит двум граням. Приведенные утверждения и описывают топологию шестигранника. Они правомерны независимо от положения конкретных вер- вершин, т.е. от геометрии объекта7. В дальнейшем мы увидим, насколько существенно формировать структуру данных описания объекта таким образом, чтобы геометрия объекта в ней была отделена от его топологии. В данном примере для описания геометрии объекта используется список, или массив вер- вершин— vertices!8]. Объектом верхнего уровня иерархии является куб. Мы рассматриваем его как совокупность шести граней, а каждая грань, в свою очередь, описана четырьмя упо- упорядоченными вершинами. Доступ к определенной вершине из списка вершин осуществляется по ее индексу в этом списке. Такая структура данных схематически представлена на рис. 4.24. Одно из ее достоинств состоит в том, что каждая геометрическая позиция определяется толь- только в одном месте и не повторяется в спецификации каждой из трех граней, пересекающихся в соответствующей точке. Таким образом, если по ходу выполнения программы потребуется изменить значения координат точки, это придется сделать только в одном месте, не отслежи- отслеживая все грани, пересекающиеся в ней. 6Термины вперед и назад в этой книге рассматриваются по отношению к положительному направлению базисного вектора г. 7Мы не будем принимать во внимание особые случаи (их принято называть вырожденными случаями — singularities/ которые проявляются, если три или более вершин лежат на одной прямой или если вершины расположены таким образом, что грани не пересекаются. 4.4. Модель разноцветного куба 171
Многогранник Грани Списки вершин Вершины , А В с D Е F: ^- 0 2 1 3 7:. 6 2 J —> „^ х4,У4; Рис 4.24. Структура данных описания куба 4.4.4. Цвет куба Список вершин можно использовать и для того, чтобы хранить информацию, необходи- необходимую для раскрашивания куба. С вершинами в данном примере будут ассоциированы чистые цвета вершин цветового куба, о котором шла речь в главе 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 с , 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( 172 Глава 4. Объекты и геометрические преобразования
quad(O,3,2,1); quadB,3,7,6); quad@,4,7,3); quad(l,2,6,5); quadD,5,6,7); quad@,l,5,4); 4.4.5. Билинейная интерполяция Недостаточно только указать, каким цветом выводить на экран вершины объекта. Графи- Графическая система должна каким-то образом по этим данным раскрасить области, занимаемые на поле изображения гранями объекта, — внутренние области многоугольников. Существует довольно много способов, как "распределить" некоторый атрибут (в данном случае цвет), значение которого фиксировано в вершинах, по внутренней области, ограниченной контуром, построенным на этих вершинах, — интерполировать {interpolate) атрибут. Возможно, наи- наиболее распространенным является метод билинейной интерполяции {bilinear interpolation). Рассмотрим, как этот метод прилагается к задаче интерполяции цвета. Пусть задан четы- четырехугольник, с каждой из вершин которого связан определенный цвет, — Со, Сь С2, С3 (рис. 4.25). Для определения распределения цвета вдоль любого ребра (линии, связывающей соседние вершины) можно воспользоваться линейной интерполяцией : Согласно этим уравнениям, при изменении а от 0 до 1 формируются значения цвета С0)(о:) и С2з(сх) точек, расположенных на ребрах, соединяющих вершины 0 и 1 и 2 и 3 соответственно. При данном значении а получаем два значения цвета, С4 и С5, для соответствующих точек на двух этих ребрах. По этим значениям можно выполнить еще одну процедуру интерполя- интерполяции — на сей раз вдоль линии, соединяющей соответствующие точки на двух противолежа- противолежащих ребрах: Если четырехугольник плоский, то каждое значение цвета, сформированное по этим уравне- уравнениям, будет соответствовать определенной точке внутренней области многоугольника. Но ес- если четырехугольник не плоский, т.е. четыре его вершины не принадлежат одной плоскости, то, хотя значения цвета и сфор- сформируются, положение соответствующих точек на криволиней- криволинейной поверхности определить так просто не удастся. Существует другой алгоритм, интерполяции вдоль строк растра {scan-line interpolation), который позволяет обойти сложности, связанные с формой поверхности, "натянутой" на контур четырехугольника. Этот алгоритм можно включить в процесс растрового преобразования. Многоугольник заполня- Рис 425 Билинейная ин- ется цветом только при выводе на экран. Но еще до выполне- терполяция ния растрового преобразования многоугольник проецируется на двухмерную картинную плоскость (рис. 4.26). Если затем многоугольник закрашивается по строкам растра, строка за строкой, как показано на рис. 4.27, то для распределения цвета нПредполагается, что для работы с цветом используется RGB-система, а интерполяция выполняется раздельно по каждому из первичных цветов. 4.4. Модель разноцветного куба 173
вдоль строки растра используются только два противолежащих ребра изображения. OpenGL поддерживает этот метод не только для цветов, но и для других атрибутов, которые можно ассоциировать с вершинами. Подробнее об этом — в главе 6. Центр проецирования Рис. 4.26. Проецирование много- многоугольника Рис. 4.27. Интерполяция вдоль строк растра Итак, на этой стадии у нас есть объект, который можно отобразить на экране тем же спо- способом, который был использован для вывода трехмерного узора Серпинского в главе 2. Как вы, надеюсь, помните, тогда использовалась ортографическая проекция, которая формирует- формируется функцией glOrtho(). В разделе4.5 будут рассмотрены геометрические преобразования, после знакомства с которыми можно будет "оживить" куб на экране, а также создавать более сложные объекты. Но сначала я познакомлю вас с новым средством поддержки операций с вершинами, которое появилось в версии OpenGL 1.1. Оно позволяет не только сократить объем программного кода, но и предоставляет в распоряжение прикладного программиста методы высокого уровня для работы с многогранниками. 4.4.6. Массивы вершин При вычерчивании куба, в модели которого используются списки вершин, приходится до- довольно много раз вызывать различные функции OpenGL. Если с каждой вершиной ассоции- ассоциируется определенный цвет, то нужно выполнить 60 вызовов функций OpenGL: 6 граней, фор- формирование каждой из которых требует вызовов glBegin() и glEnd(), 4 вызовов glColor() и 4 вызовов glVertex (). Каждый вызов функции влечет за собой накладные расходы и пере- пересылку данных. Как будет показано в главе 6, при вычерчивании объекта также необходимо задавать векторы нормалей для каждой вершины, и в результате количество вызовов при формировании изображения куба даже превышает указанную цифру. Следовательно, хотя описанная структура данных и позволяет инкапсулировать информацию о геометрии и топо- топологии куба как трехмерного объекта, соответствующий программный код будет выполняться не так быстро, как нам хотелось бы. Массивы вершин {vertex arrays) в OpenGL обеспечивают значительное повышение скорости вычерчивания трехмерных объектов, состоящих из многоугольников, сохраняя при этом воз- возможность инкапсуляции их внутренней топологической структуры. Использование такой струк- структуры данных предусматривает три этапа. Первый — разрешение работы с механизмом обслу- обслуживания массивов векторов. Второй — передача исполнительной системе OpenGL информации о том, где и в каком формате содержатся в программе массивы вершин. Третий — формирова- формирование объектов с использованием этих массивов. Две первые операции выполняются в процессе инициализации программы, а третья, как правило, реализуется с помощью функций с обратным вызовом. Ниже будет показано, как это делается на примере формирования куба. 174 Глава 4. Объекты и геометрические преобразования
Пакет 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 информацию о том, где находятся массивы. Это выполняется следующим образом: glVertexPointerC, GL_FLOAT, 0, vertices); glColorPointerC, GL_FLOAT, 0, colors); Первые три аргумента несут информацию о том, что элементы массивов являются соответ- соответственно трехмерными цветами и вершинами, которые сохраняются в формате float, и что элементы образуют непрерывный массив. Четвертый аргумент — указатель на определен- определенный массив. Теперь нужно включить в структуру данных информацию об отношениях между верши- вершинами и гранями в кубе. Для этого организуется массив из 24 упорядоченных индексов вершин для шести граней: GLubyte cubeIndices[24]={0,3,2,l, 2,3,7,6, 0,4,7,3, 1,2,6,5, 4,5,6,7, 0,1,5,4}; Первая грань имеет вершины с индексами @, 3, 2,1), вторая — B, 3, 7,6) и т.д. Обратите внимание на то, что при упорядочении индексов вершин нужно учитывать порядок их обхода в многоугольнике при взгляде с внешней стороны куба. Массив индексов вершин также дол- должен иметь статус глобального. Теперь можно приступить к формированию трехмерного объекта — в данном случае ку- куба — на основе информации из этих массивов. При формировании изображения обрабатыва- обрабатываются все элементы объявленных массивов (в данном случае — цветов и вершин). Имеется не- несколько опций настройки, касающихся режима обработки массивов при отображении. Для настройки используется функция glDrawElements(type, n, format, pointer) Аргументы функции имеют следующий смысл: 4.4. Модель разноцветного куба 175
¦ type — тип элемента, например линия или многоугольник, который определяется массивом; ¦ п — количество элементов, которые планируется отобразить; ¦ format — описывает формат данных в индексном массиве; ¦ pointer — указатель на первый используемый индекс. При формировании изображения куба в функции отображения display () нужно шесть раз вызвать функцию glDrawelements(), по одному разу на каждую грань: for(i=0;i<6;i++) glDrawElements(GL_POLYGON; 4, GL_UNSIGNED_BYTE, &cubelndex[4*i]); Таким образом, после формирования и инициализации массивов в процессе отображения придется выполнить только шесть вызовов функций OpenGL вместо пятидесяти с лишним. Поворачивать изображение куба можно тем же способом, что и раньше, поскольку функция glDrawElementsO использует при формировании изображения параметры текущего состоя- состояния, в том числе и матрицу вида. Можно еще больше упростить процесс отображения, если учесть, что каждая грань куба является именно четырехугольником, а не многоугольником общего вида. Тогда можно ис- использовать в функции glDrawelements ()тип GL_QUADS вместо GL_POLYGON и вызывать эту функцию в display () только один раз вместо шести вызовов при вычерчивании многоуголь- многоугольников общего вида: glDrawElements(GL_QUADS, 24, GL_UNSIGNED_BYTE, cubelndices); Если в glDrawelements () используется тип GL_QUADS, то вычерчивание следующего четырех- четырехугольника начинается автоматически после завершения обработки каждой очередной четвер- четверки вершин. 4.5. Аффинные преобразования Под преобразованием {трансформацией — transformation) в дальнейшем будем понимать функцию, которая принимает (точку или вектор) и отображает ее на другую точку (или век- вектор). Графически смысл преобразования представлен на рис. 4.28. Для точек преобразование формально записывается в виде Q-ПП а для векторов — в виде v = /?(м). При использовании однородных координат и векторы, и точки представляются в виде четы- четырехмерных матриц-столбцов. В таком случае преобразование имеет единообразный вид и для точек, и для векторов одного и того же фрейма: v=/u). Эта формулировка носит слишком общий характер, чтобы ее можно было использовать на практике, поскольку она распространяется на любые однозначные отображения точек и векторов. На практике, даже располагая удобным описанием функции J0, нужно применять преобразование ко всем точкам кривой. Если, например, трансформируется отрезок прямой, обобщенное преобразование придется применить к каждой точке между двумя заданными конечными. 176 Глава 4. Объекты и геометрические преобразования
Мы будем рассматривать ограниченный класс преобразо- преобразований в четырехмерном пространстве однородных координат. В этом пространстве и точки, и векторы представляются как вершины, т.е. как наборы из четырех чисел9. Этот класс можно определить формально, наложив определенные ограничения на вид функции ft). Наиболее важное ограничение — линей- линейность преобразования. Функция/) является линейной тогда и только тогда, когда для любых скаляров а и C и для любых вершин р и q выполняется соотношение Рис. 4.28. Преобразование Особое значение такого свойства преобразований состоит в том, что если известно преоб- преобразование вершин, то всегда можно получить и преобразование линейной комбинации вер- вершин, используя для этого линейную комбинацию преобразований. В результате отпадает не- необходимость в повторном вычислении преобразований вершин при определении преобразо- преобразования их линейной комбинации. В четырехмерном пространстве мы работаем с представлениями точек и векторов. Ли- Линейное преобразование, которое трансформирует представление точки (или вектора) в другое представление, всегда может быть записано в терминах двух представлений и и v с помощью перемножения матриц: v = Аи, где А — квадратная матрица. Сравнивая это выражение с полученным в разделе 4.3 для операции изменения фрейма, приходим к выводу, что, если А невырождена, любое линей- линейное преобразование соответствует изменению фрейма. Следовательно, линейное преобра- преобразование можно рассматривать с двух точек зрения: как изменение представления или фрейма, которое приводит к новому представлению вершин, или как преобразование вер- вершин в рамках того же фрейма. При выполнении операций в однородных координатах А представляет собой матрицу 4x4, которая оставляет неизменным четвертый компонент представления (м). Матрица А имеет вид А = а п а, а 13 а «2 12 21 "-22 3 «3. «32 «33 0 0 0 1 Двенадцать элементов матрицы могут изменяться произвольным образом, и мы говорим о та- таком преобразовании, что оно имеет 12 степеней свободы {degrees of freedom). Но точки и векторы в аффинном пространстве имеют немного другое представление. Любой вектор можно представить в виде и = 9Рассматриваются только такие функции преобразования, которые отображают одни вершины на другие вершины и которые подчиняются правилам манипулирования с точками и векторами, описанными ранее в этой главе и в приложении Б. 4.5. Аффинные преобразования 177
а любую точку — в виде "Р, 1 Если рассмотреть применение произвольного преобразования А по отношению к вектору v = Аи, то придем к выводу, что на результат влияют только 9 элементов матрицы А, т.е. преобразо- преобразование векторов обладает только девятью степенями свободы. Аффинные преобразования то- точек обладают всеми двенадцатью степенями свободы. Несложно показать, что аффинное преобразование преобразует прямую линию в прямую линию. Предположим, что прямая линия записана в виде Да) = P0 + ad, где Ро — точка, ас/— вектор. В некотором фрейме прямая выражается с помощью представ- представления ро и d соответственно, точки Ро и вектора d в этом фрейме: р(а) = ро + ad. Для любой матрицы аффинного преобразования А справедливо соотношение Ар(а) = Ар0 + aAd. Таким образом, можно построить преобразование прямой, выполнив сначала преобразование Ро и d, а затем воспользоваться любым алгоритмом формирования прямой. Если используется форма представления отрезка двумя точками р(а) = аро + A-<х)рь получим аналогичный результат— нужно сначала выполнить преобразование р0 и р,, а затем по ним построить преобразованный отрезок. Поскольку в матрице А имеется 12 элементов, которые можно выбирать произвольно, то аффинное преобразование прямой или прямоли- прямолинейного отрезка имеет 12 степеней свободы. До сих пор мы рассуждали в терминах абстрактного математического пространства. Од- Однако эти рассуждения имеют совершенно конкретное приложение к практическим проблемам компьютерной графики. Из них следует, что для построения в системе компьютерной графи- графики изображения преобразованного отрезка необходимо выполнить только преобразование представлений в однородных координатах конечных точек этого отрезка, а, следовательно, графическую систему можно реализовать в виде конвейера аффинных преобразований пред- представлений отдельных точек в однородных координатах. К счастью, подавляющее большинство преобразований, которые используются в компью- компьютерной графике, можно свести в конечному множеству аффинных преобразований. Это мно- множество включает вращение, плоскопараллельное смещение (сдвиг) и масштабирование. Вне- Внеся небольшие изменения, таким же образом можно выразить и проективные преобразова- преобразования — параллельное и перспективное. 4.6. Поворот, сдвиг и масштабирование Рассматривая преобразования, нам придется все время переключаться от геометрических объектов как абстрактных сущностей к их конкретным представлениям в определенных фреймах. В прикладной программе мы имеем дело с представлениями объектов. В этом раз- 178 Глава 4. Объекты и геометрические преобразования
деле сначала будет показано, что наиболее важные аффинные преобразования можно сфор- сформулировать независимо от конкретного представления. Затем будут выведены матрицы этих преобразований, которые позволяют работать с представлениями точек и векторов. О том, как эти преобразования реализованы в OpenGL, рассказывается в разделе 4.8. Мы рассматриваем преобразования как способ перемещения группы точек, описывающих один или несколько геометрических объектов, в новую позицию. Хотя существует множество способов выполнить перемещение отдельной точки, оказывается, что имеется только один способ однообразно выполнить такое преобразование по отношению к множеству точек, со- сохранив при этом отношения между вершинами в объекте. 4.6.1. Плоскопараллельное смещение Плоскопараллельное смещение, или сдвиг {translation),— это операция, которая смещает точки на фиксированное расстояние вдоль заданного направления (рис. 4.29). Сдвиг задается только вектором смещения d, поскольку для всех точек Р на объекте справедливо соотношение P'=P + d. Обратите внимание на то, что такое определение операции никак не связано с системой от- отсчета или фреймом представления. Сдвиг имеет 3 степени свободы, поскольку можно произ- произвольно задать три компонента вектора смещения. о) Рис 4.29. Сдвиг: а — объект в исходной позиции; б — сдвинутый объект 4.6.2. Поворот Задать поворот {rotation) несколько сложнее, чем сдвиг, так как требуется специфициро- специфицировать больше параметров. Начнем с простого примера поворота точкой вокруг начала коорди- координат в двухмерной плоскости, как показано на рис. 4.30. Поскольку за- задана определенная точка — начало координат, — мы имеем дело с оп- определенным фреймом. Двухмерная точка (х, у) в этом фрейме повора- поворачивается вокруг начала координат на угол 6 и занимает после поворота позицию (х' у'). Формулы описания поворота несложно получить, вос- воспользовавшись полярным представлением точек (х, у) и (х1, уу. х = р cos0, у = р sin<)>, x'=pcos(9 + ф), у' = р sin(9 + ф). Воспользовавшись формулами приведения синуса и косинуса суммы углов, получим х' = р cos0 cos0 - р siru|> sin8 = x cos6 -у sin9, у' = р созф sinG + р siiu|> cos9 = х sin9 + у cos9. Рис. 4.30. Поворот в двухмерной системе коор- координат 4.6. Поворот, сдвиг и масштабирование 179
Эти же уравнения можно записать и в матричной форме: (Vlfcose -sin el [у'\ [sine cosG О том, как можно расширить эти формулы на трехмерный случай, вы узнаете в разделе 4.7. Обратите внимание на три свойства этого преобразования, которые сохраняются и в дру- других вариантах операции поворота. 1. Существует некоторая точка, она называется фиксированной точкой (fixed point) пре- преобразования (в приведенном выше примере— начало координат), которая остается неподвижной при применении к ней этого преобразования. На рис. 4.31 показано, ка- какой результат получится при повороте вокруг фиксированной точки, отличной от на- начала координат. 2. Учитывая, что двухмерное пространство является частным случаем трехмерного, можно распространить сделанные выводы и на три измерения. В правосторонней сис- системе координат, когда оси х и у выглядят привычным для всех нас образом, положи- положительное направление оси z трехмерной системы координат будет направлено на на- наблюдателя (т.е. ось z "исходит" из листа бумаг, на котором начерчены оси * и у). По- Положительное направление поворота — это направление против часовой стрелки, если смотреть со стороны положительной полуоси г, исходящей из фиксированной точки. Это же определение положительного направления поворота мы будем использовать и для трехмерного случая при повороте вокруг произвольной оси. 3. Двухмерное вращение на плоскости эквивалентно трехмерному вращению вокруг оси z. Все точки на плоскости, параллельной ху (т.е. имеющие одинаковое значение компоненты г), поворачиваются одинаково, сохраняя значение компонентам. Рис 4.31. Поворот вокруг фиксированной точки Эти свойства мы используем при выводе обобщенного преобразования поворота в трех- трехмерном пространстве, не зависящего от конкретного фрейма. Для такого преобразования (рис. 4.32) нужно специфицировать три параметра — фиксированную точку (Pf), угол пово- поворота (9) и прямую или вектор, вокруг которого выполняется поворот (ось вращения). Если фиксированная точка задана, то в нашем распоряжении имеются 3 степени свободы: два угла необходимы для задания ориентации оси вращения и один угол задает сам поворот. Поворот и сдвиг относятся к группе так называемых изометрических преобразований (или преобразований твердого тела — rigid-body transformations). Комбинация этих преоб- преобразований не может изменить формы объекта, а изменяет только его положение в простран- пространстве — позицию и ориентацию. Следовательно, сами по себе повороты и смещения не дают 180 Глава 4. Объекты и геометрические преобразования
полного набора аффинных преобразований. Преобразования, представленные на рис. 4.33, также являются аффинными, но не относятся к группе изометрических преобразований. Рис. 4.32. Трехмерный поворот Рис. 4.33. Анизометрические преобразования 4.6.3. Масштабирование Масштабирование (scaling) представляет собой анизометрическое аффинное преобразо- преобразование. Практически любое аффинное преобразование можно свести к суперпозиции преобра- преобразований масштабирования, поворота и сдвига. Преобразование масштабирования увеличивает или уменьшает размеры объекта. На рис. 4.34 показаны два варианта масштабирования — пропор- пропорциональное (равномерное во всех направлениях или преобразо- преобразование подобия) и растяжение только в одном направлении. По- Последний вариант также понадобится включить в набор элемен- элементарных преобразований, которыми мы будем пользоваться в дальнейшем при моделировании и визуализации объектов. Как видно на рис. 4.35, преобразование масштабирования также имеет фиксированную точку. Таким образом, набор параметров этого преобразования включает фиксированную точку, направле- направление, вдоль которого изменяется масштаб, и масштабный множи- множитель (а). При а > 1 объект растягивается в заданном направлении, а при 0<сх <1 объект сжимается. При отрицательном значении ос происходит отражение (reflection) объекта относительно заданной фиксированной точки в указанном направлении (рис. 4.36). Рис. 4.34. Пропорциональ- Пропорциональное и неравномерное масштабирование Рис 4.35. Параметры масшта- масштабирования Рис. 4.36. Отражение 4.6. Поворот, сдвиг и масштабирование 181
4.7. Преобразования в однородных координатах В большинстве графических API приходится работать в определенной системе отсчета (системе координат). Хотя программист и может изменить эту систему координат (как прави- правило, фрейм), он не может работать с представлениями на высоком уровне, например с выра- выражениями вида Q = Р + av. Вместо этого программист имеет дело с представлениями в однородных координатах и вы- выражениями вида q = р + O.V. В некотором фрейме любое аффинное преобразование может быть представлено матрицей 4x4 вида А = ос,, ее, а а, 13 "-14 «23 «24 «31 «32 «33 0 0 0 4.7.1. Сдвиг X У z 1 . р' = ~х'~ у' z' 1 d- «2 «3 0 Преобразование сдвига смещает точки в новые позиции в соответствии с заданным векто- вектором смещения. Если точка р сдвигается в р' на расстояние d, то такое преобразование можно записать в виде р1 = р + d. В однородных координатах р, р' и d выражаются следующим образом: Р = Тогда эти уравнения можно записать в виде отдельных выражений для каждого компонента: х' = х+ о^, У'=У + Оу, z' = z + ou. Метод представления преобразования сдвига посредством суммирования матриц-столбцов плохо сочетается с другими аффинными преобразованиями. Существует и другой метод представления сдвига — с помощью перемножения матриц: Р' = Тр, где 10 0а. Т = 0 1 0 av 0 0 1 аг 0 0 0 1 182 Глава 4. Объекты и геометрические преобразования
Матрица Т называется матрицей сдвига (translation matrix). Иногда ее записывают в виде T(Oj., a,, ex.), чтобы подчеркнуть три независимых параметра преобразования. На первый взгляд может показаться, что четвертый элемент в матрицах-столбцах из- излишний, но оказывается, что при использовании трехмерной версии выражения р1 = Тр тот же результат не получается. Поэтому иногда применение однородных координат для выполнения преобразования сдвига рассматривается как своего рода трюк, позволяю- позволяющий заменить суммирование трехмерных матриц-столбцов перемножением четырехмер- четырехмерных матриц. Матрицу обратного преобразования можно сформировать либо с помощью алгоритма об- обращения матриц, либо приняв во внимание, что обратное преобразование — это сдвиг на рас- расстояние ~d. В любом случае получим 1 0 0 0 0 1 0 0 0 0 1 0 -а, -ау -а. 1 4.7.2. Масштабирование Как уже говорилось выше, преобразования поворота и масштабирования характеризу- характеризуются фиксированной точкой, которая остается неподвижной при выполнении этих преоб- преобразований. Сейчас будем считать, что фиксированной точкой является начало координат, а в дальнейшем покажем, как с помощью суперпозиции базовых преобразований можно сформировать преобразование масштабирования относительно произвольной фиксирован- фиксированной точки. Матрица преобразования масштабирования, имеющего фиксированную точку в начале координат, позволяет задавать масштабные коэффициенты по каждой из координатных осей независимо друг от друга. Имеем три уравнения: которые можно выразить в однородных координатах матричным уравнением P=Sp, где (Рд'Ру'Рс]- "Р, 0 0 0 0 р, 0 0 0 0 р. 0 0" 0 0 1 В этой матрице, а также в матрице сдвига и других матрицах преобразований однородных координат четвертая строка не зависит от характера преобразования, а служит для того, что- чтобы после выполнения перемножения сохранялось значение 1 в четвертом компоненте точки. Для обращения матрицы масштабирования необходимо использовать обратные значения масштабных коэффициентов по осям: s-(p,.p,.p,).8[jL.J-.JL 4.7. Преобразования в однородных координатах 183
4.7.3. Поворот Сначала рассмотрим поворот вокруг начала координат, т.е. будем считать, что фиксиро- фиксированная точка преобразования поворота находится в начале координат. Поскольку поворот можно выполнять независимо вокруг каждой из осей координат, это преобразование имеет три степени свободы. Но нужно учитывать, что перемножение матриц не является коммута- коммутативной операцией (см. приложение В). Если за поворотом вокруг оси х на угол 0 следует по- поворот вокруг оси у на угол ф, то результат будет отличаться от полученного при выполнении этих же преобразований в другой последовательности. Чтобы найти матрицу поворота вокруг отдельной оси системы координат, можно исполь- использовать результат, полученный при анализе поворота в двухмерной системе (см. раздел 4.6). Мы уже видели, что поворот в двухмерной системе фактически является поворотом вокруг оси z трехмерной системы координат и что после выполнения такого преобразования точ- точки остаются на той же плоскости, т.е. компонент - точки не изменяется. Следовательно, уравнения поворота вокруг оси г на угол 0 в трехмерном пространстве можно записать следующим образом: х' = xcosQ - ys\nQ, y' = xs\r\Q +>>cos0, г' = г, или в матричном виде: где cose -sine О О' sine cosO О О О 0 10 0 0 0 1 Рассуждая аналогично, можно сформировать и матрицы поворота вокруг осей jc и у. При по- повороте вокруг оси х значения компонентов х точек остаются неизменными, и получаем двух- двухмерный поворот в плоскости х = const. При повороте вокруг оси у сохраняются значения компоненту точек. Матрицы этих поворотов имеют вид 10 0 0' 0 0 0 cose sinO 0 cose -: 0 sine 0 0 1 0 0 -sine cose 0 sine 0 COS 6 0 0 0 1 0 0 0 1 Знак элементов матриц, содержащих sin, соответствует принятому определению положитель- положительного направления поворота в правосторонней системе координат. Обозначим через R любую из трех приведенных выше матриц поворотов вокруг осей сис- системы координат. После поворота на угол 0 преобразованные точки всегда можно вернуть в исходное состояние, выполнив поворот вокруг той же оси на угол -0. Следовательно, 184 Глава 4. Объекты и геометрические преобразования
R '@) Обратите внимание и на то, что элементы, содержащие cos, всегда размещаются на глав- главной диагонали матрицы, а пара элементов, содержащих sin, — по разные стороны от глав- главной диагонали. Воспользовавшись тригонометрическими тождествами cos(-e) = cos0 и sin(-6) = -sine, получим, что R '@) = R7@). В разделе 4.8 будет показано, как сформировать матрицу поворота вокруг произвольной оси с фиксированной точкой в начале координат, используя перемножение матриц поворота вокруг отдельных осей системы координат: R = R,R, R.. Учитывая, что преобразование произведения есть произведение преобразований в обрат- обратном порядке, можно показать, что для любой матрицы поворота выполняется соотношение Матрицы, для которых операция транспонирования дает тот же результат, что и обраще- обращение, называются ортогональными {orthogonal matrix); любая ортогональная матрица описы- описывает некоторое преобразование поворота с фиксированной точкой в начале координат. 4.7.4. Скос Хотя любое аффинное преобразование можно свести к последовательности поворо- поворотов, сдвигов и изменений масштаба, существует один вид преобразования, который на- настолько важен в компьютерной графике, что мы будем рассматривать его также как ба- базовый тип. Это преобразование скоса {shear). Рассмотрим куб, центр которого находит- находится в начале системы координат, а ребра параллельны осям координат (рис. 4.37). Если сместить верхнюю грань куба вправо, а нижнюю — влево, объект будет скошен в на- направлении оси д:. Учтите, что при этом компоненты у и z всех точек на объекте остаются неизменными. Мы будем называть показанное на рисунке преобразование дг-скосом, чтобы отличать его от скоса в другом направлении. Воспользовавшись простыми триго- тригонометрическими соотношениями, которые поясняются на рис. 4.38, можно охарактери- охарактеризовать преобразование такого типа единственным параметром — углом скоса 0. Уравне- Уравнения преобразования имеют вид х' = х + yctgQ, откуда следует вид матрицы скоса:  ctgG О О' 0 10 0 0 0 10 0 0 0 1 н,(е) = Матрицу обратного преобразования несложно получить, если учесть, что следует выполнить скос в обратном направлении; следовательно, Н,'@) = Н/Н>). 4.7. Преобразования в однородных координатах 185
z z Рис. 4.37. Преобразование скоса (х, у) (х, /) Рис 4.38. К определению матрицы скоса 4.8. Суперпозиция преобразований В этом разделе продемонстрировано, как формировать матрицу сложного аффинного преобразования, перемножая матрицы отдельных базовых (канонических) преобразований. Выражаясь языком математики, можно сказать, что сложное преобразование есть суперпози- суперпозиция базовых преобразований (concatenating), которые были рассмотрены в предыдущем раз- разделе. Использованию такой стратегии формирования матрицы сложного преобразования сле- следует отдать предпочтение перед непосредственным вычислением элементов матрицы. Этот подход хорошо "вписывается" в конвейерную архитектуру графической системы. Предположим, что выполняются три последовательных преобразования точки р и при этом формируется новая точка q. Поскольку произведение матриц ассоциативно, можно за- записать такую последовательность преобразований в виде q = CBAp, без использования скобок. Однако порядок выполнения операций существенно влияет на производительность процесса вычисления. Например, можно сначала умножить матрицу А, затем на полученный результат умножить матрицу В, а потом умножить матрицу С (этот по- порядок соответствует группировке, представленной на рис. 4.39): q = (C(B(Ap))). |—> Щ, в у';;;||—> с |—> < Рис. 4.39. Последовательное выполнение преобразований Если выполняется преобразование единственной точки, то этот порядок наиболее эффек- эффективен, поскольку на каждом шаге квадратная матрица умножается на матрицу-столбец. Но если необходимо преобразовать множество точек, то операцию следует реализовать в два этапа. Сначала вычислять произведение квадратных матриц отдельных преобразований: М = СВА, а затем использовать полученную матрицу для обработки всего множества точек: q = Mp. Этот порядок реализуется в графическом конвейере, как показано на рис. 4.40. Сначала вычисляется матрица произведения М, затем она загружается в ту секцию конвейера, ко- 186 Глава 4. Объекты и геометрические преобразования
торая выполняет преобразование. Если посчитать количест- количество операций, то окажется, что вычисление матрицы М тре- требует несколько большего числа операций, чем (С(В(Ар))), но затем при обработке каждой из сотен или тысяч точек требуется выполнить только одну операцию умножения квадратной матрицы на матрицу-столбец. Ниже на примерах будет показано, как вычислять матрицу М. СВА м Рис. 4.40. Выполнение преобра- преобразований в графическом конвейере 4.8.1. Поворот вокруг произвольной фиксированной точки В первом примере будет показано, как формировать матрицу преобразований поворота вокруг произвольной фиксированной точки, используя канонические матрицы поворота во- вокруг начала координат. Направление оси поворота в примере совпадает с направлением ко- координатной оси 2, но этот же метод можно использовать и при другом направлении оси. Рассмотрим куб, центр которого находится в точке р/, а ребра параллельны осям коор- координат. Требуется повернуть куб вокруг оси z, но сохранить неизменным положение центра (рис. 4.41). Если бы точка р/совпадала с началом координат, мы могли бы воспользоваться уже сформированной матрицей R.F). Отсюда следует, что сначала нужно сдвинуть куб та- таким образом, чтобы его центр оказался в начале координат, а затем воспользоваться мат- матрицей R:(Q). Последняя операция — сдвинуть куб так, чтобы его центр занял прежнее по- положение р/. Эта последовательность преобразований представлена на рис. 4.42. Используя введенные раньше обозначения, нужно сначала выполнить преобразование Т(-ру), далее — R.F), а последним выполнить Т(ру). В результате суперпозиции этих преобразований по- получим матрицу а) 6) Рис. 4.41. Поворот куба вокруг собственного центра После перемножения матриц получим: cosG -sinG О X; -A^cosG + ^sinG cosG 0 yf -.xysinG-^cosG M.= sinG О О 4.8. Суперпозиция преобразований 187
Рис. 4.42. Последовательность канонических преобразований 4.8.2. Поворот вокруг произвольной оси Теперь покажем, как можно представить поворот вокруг произвольной оси в виде су- суперпозиции трех поворотов вокруг осей координат. Порядок выполнения канонических преобразований в этом случае не однозначен (см. упр. 4.10), хотя результат будет одно- однозначным. В примере начало координат выбрано в качестве фиксированной точки преобра- преобразования. Первой операцией будет поворот вокруг оси z, затем поворот вокруг оси у и по- последним — поворот вокруг оси х. Рассмотрим куб, центр которого находится в начале координат, а ребра параллельны осям системы координат, как показано на рис. 4.43,а. Сначала повернем его вокруг оси z на угол а таким образом, чтобы ориентация граней куба стала такой, как на рис. 4.43,6. Затем повернем куб на угол 3 вокруг оси у, как показано на рис. 4.44. Последний поворот — на угол у вокруг оси х, как показано на рис. 4.45. б) Рис. 4.43. Поворот куба вокруг оси г: а — до поворота; б — после поворота Таким образом, матрица поворота вокруг произвольной оси получается в результате пе- перемножения трех матриц: Несложный эксперимент должен убедить вас в том, что правильно подобрав значения уг- углов а, Р и у, можно таким способом обеспечить поворот вокруг оси с любой ориентацией, хо- хотя выбор углов — задача непростая, о чем будет рассказано в разделе 4.8.4. 188 Глава 4. Объекты и геометрические преобразования
а) 6) Рис. 4.44. Поворот куба вокруг оси у У а) Рис. 4.45. Поворот куба вокруг оси х 6) 4.8.3. Преобразование экземпляра Рассмотренный пример с кубом, который можно ориентировать как угодно, подводит нас к обобщенному подходу, который можно использовать при моделировании объектов. Рассмотрим сцену, состоящую из множества простых объектов (рис. 4.46). Один из возможных вариантов формирования описания такой сцены— определить каждый объект сцены множеством вер- вершин, учитывая его положение, размеры и ориентацию. Другой подход — сначала определить набор прототипов объектов сце- сцены определенного фиксированного размера, положения и ори- ориентации, причем прототипами могут быть как стандартные объ- объекты (параллелепипед или сфера), так и более сложные — стол, стул и т.п. Каждый конкретный объект сцены — это экземпляр прототипа (instance) со своими размерами, положением и ори- ориентацией в пространстве сцены. Размеры, положение и ориента- ориентация экземпляра задаются каноническими преобразованиями, ко- которые в данном случае будем называть преобразованиями эк- земпляра {instance transformation). В результате описание сцены ™с' примет вид базы данных, опирающейся на список идентифика- торов прототипов (например, 1 — куб, 2 — сфера и т.д.). Преобразование экземпляра применяется в порядке, представленном на рис. 4.47. Объект обычно определяется в собственном фрейме, начало координат которого находится в центре масс объекта, а оси координат параллельны характерным ребрам объекта или его оси сим- симметрии. Сначала устанавливаются размеры экземпляра, затем его сдвигают и поворачивают. Следовательно, преобразование экземпляра имеет вид М = TRS. °' ^ена из простых ооъектов 4.8. Суперпозиция преобразований 189
Моделирование на основе преобразований экземпляров эффективно реализуется не толь- только в системах с конвейерной архитектурой, но и в системах, работающих с дисплейными списками, о которых шла речь в главе 3. Описания сложных прототипов, которые исполь- используются в модели многократно, передаются на сервер только один раз в виде дисплейного списка. Отображение каждого экземпляра потребует только пересылки на сервер соответст- соответствующей матрицы преобразования экземпляра. 4.8.4. Поворот вокруг произвольной оси Пример выполнения поворота, который мы рассмотрим в данном разделе, продемонстри- продемонстрирует, как использовать углы поворотов вокруг осей координат для задания поворота вокруг произвольного вектора. Рассмотрим поворот куба, показанного на рис. 4.48. Для определения произвольного поворота в пространстве требуются три параметра: фиксированная точка пре- преобразования (в качестве таковой выберем центр куба — точку р0), вектор, вокруг которого выполняется поворот, и угол поворота. Обратите внимание на то, что ни один из них не зави- зависит от конкретного фрейма, а следовательно, мы специфицируем преобразование, не связы- связываясь ни с какой системой координат. Но при формировании матриц аффинных преобразова- преобразований нам придется привязаться к определенному фрейму. I Рис. 4.47. Преобразование экземпляра Рис 4.48. Поворот куба вокруг произвольной оси Вектор, вокруг которого поворачивается куб, можно специфицировать разными способа- способами, например, можно задать его двумя точками Pi и р2: и = р2-р,. Обратите внимание на то, что порядок использования точек задает положительное направле- направление вращения, и хотя на рисунке вектор и проходит через точку р0, имеет значение только его направление. Выполнение дальнейших операций облегчит нормализация вектора оси поворо- поворота — замена и вектором единичной длины, имеющим то же направление: аг а, ос. 190 Глава 4. Объекты и геометрические преобразования
P2-Pl Выше уже было показано, что преобразование упрощается, если перенести фиксированную точку в начало координат. Таким образом, первый сдвиг — это Т(-р0), а последний — Т(ро). После начального сдвига задача поворота приобретает вид, как на рис. 4.49. В предыдущем примере (см. раздел 4.8.2) было показано, что поворот вокруг произвольной оси можно свести к по- последовательности поворотов вокруг отдельных осей коор- координат. Основная загвоздка при этом — определить, на какой угол нужно повернуть вокруг каждой оси. Поэтому приме- применим другую стратегию — выполним два первых поворота та- таким образом, чтобы ось поворота v совместилась с коорди- координатной осью г. Затем повернем вокруг z на заданный угол 0, после чего выполним первые два поворота в обратном поряд- порядке и обратном направлении. Таким образом, матрица ком- комплексного преобразования имеет вид произведения: r = R,(-ex) R,(-e,) R.(er) R/e,) R^e,). Эта последовательность поворотов показана на рис. 4.50. Наиболее сложная часть проце- процедуры — определение Qx и 6Г Рис. 4.49. Перенос фиксиро- фиксированной точки в начало координат z z z 'У z Рис. 4.50. Последовательность вращений вокруг координатных осей Рассмотрим компоненты вектора v. Поскольку v является вектором единичной длины, то Проведем отрезок от начала координат в точку (а*, Оу, а.). Этот отрезок имеет единичную длину и ориентирован в направлении вектора v. Опустим перпендикуляры из точки (а,, а,, а.) на каждую координатную ось, как показано на рис. 4.51. Три направляющих угла {direction angles) — ф„ фя ф. — это углы между отрезком (или вектором v) и координатными осями. Между направляющими косинусами {direction cosines) и компонентами v существует очевид- очевидное соотношение: CLy, соэф. = a... Независимы только два направляющих угла, поскольку 4.8. Суперпозиция преобразований 191
Зная значения направляющих косинусов, можно вычислить значения углов Qx и ву. Рассмот- Рассмотрим рис. 4.52. На нем видно, что поворот точки (о^, о^,, а.) приводит к такому повороту от- отрезка, что он оказывается в плоскости у = 0. Длина проекции отрезка (до выполнения поворо- поворота) на плоскость х = 0 равна d. Можно и по-другому трактовать этот рисунок. Считайте, что плоскость х = 0 — это стена, и рассмотрите удаленный источник света, расположенный где- то на положительной полуоси х. Отрезок, который вы увидите на стене, — это тень, отбрасы- отбрасываемая отрезком, проведенным из начала координат в точку (с^, а,,, а.). Учтите, что длина те- тени меньше длины отрезка. Можно сказать, что отрезок сжат до величины d = Ja\ + a) . Ис- Искомый угол поворота— это угол между этой тенью и осью z. Однако в матрице поворота присутствуют не углы, а их синусы и косинусы, следовательно, нам не потребуется вычислять само значение 9„ а только 1 0 0 0 0 ajd ay/d 0 0 -ay/d a. Id 0 0 0 0 1 Рис. 4.57. Направляющие углы оси поворота Рис. 4.52. Вычисление угла по- поворота вокруг оси х Элементы матрицы R,, вычисляются исходя из аналогичных рассуждений (рис. 4.53). Обрати- Обратите внимание на то, что поворот вокруг оси у выполняется по часовой стрелке, поэтому нужно внимательно следить за знаками тех элементов матрицы, кото- которые содержат функцию синуса. Матрица R>. будет иметь вид у «,(«,)¦ d 0 tx 0 0 1 0 0 -ax 0 d 0 0 0 0 1 Последний шаг— перемножить матрицы отдельных преобра- преобразований: м = т(ро) R,(-e,) |ц-е,) R.Fr) вд) кх(ех) т(-Ро). Теперь рассмотрим численный пример. Пусть требуется по- повернуть объект на 45° вокруг прямой, которая проходит через начало координат и точку A,2,3). Оставим фиксированную точку преобразования в начале координат. Первый шаг — оп- Рис 4.53. Вычисление угла поворота вокруг оси у 192 Глава 4. Объекты и геометрические преобразования
ределение точки на прямой, которая отстоит от начала координат на единичное расстояние. Эту точку получим после нормализации тройки A,2, 3). Получим Шу/ы, 2/>/l4, 3/VT4] или в однородных координатах — (l/VU, 2/-УГ4, 3/-v/l4, l). После выполнения первой фазы поворота эта точка перейдет в положение @, 0, 1, 1). Сначала будет выполнен поворот вокруг оси х на угол, равный arccos-T= . В результате точка vl3 (\/\fl4, 2/VU, 3/>/l4, \\ преобразуется в точку (\/у/\4, 0. Vl3/14, l), которая принадлежит плоскости у = 0. Вокруг оси у объект поворачивается на угол, равный arccosVl3/14 . В результате прямая окажется вытянутой вдоль оси г, и можно будет выполнить поворот вокруг этой оси на 45°. Последний этап — выполнить первые два поворота в обратном порядке и в обратном направлении. Перемножив матрицы этих преобразований, получим матрицу R: R -arccosj— R D5) r[ arccosj— R | arccos ¦'[ V14 J л >[ Vl4j \ 13V2 2-V2-3V7 6-зУ2+4>/7 28 14 28 2-V2+3V7 4 + 5^2 6-3V2-V7 14 14 14 6-3V2-W7 6-3>/2+>/7 18 + 5V2 28 14 28 0 0 0 Эта матрица не изменяет положение точек, которые находятся на прямой, проходящей через начало координат и точку A,2, 3). Если потребуется выполнить такой же поворот, но фикси- фиксированная точка располагается не в начале координат, а в некоторой позиции р/, то нужно скорректировать матрицу преобразования: M = T(py)RT(-P/). Этот пример показывает, как использовать множество простейших преобразований для выполнения сложного преобразования. Задача поворота вокруг произвольной оси и произ- произвольной фиксированной точки возникает в множестве приложений. Варианты ее решения за- зависят в основном от того, каким образом задана ось поворота. Однако почти всегда можно воспользоваться описанной выше методикой для определения направляющих углов или на- направляющих косинусов. 4.9. Матрицы преобразований в OpenGL В этом разделе речь пойдет о реализации механизма преобразований в однородных коор- координатах в виде программного пакета и об интерфейсе между этим пакетом и прикладной про- программой. В OpenGL существуют три матрицы, которые входят в состав параметров, характе- характеризующих текущее состояние графической системы. В этой главе мы рассмотрим только матрицу вида (model-view matrix). Всеми тремя матрицами можно манипулировать с помо- помощью одного и того же набора функций, а для выбора, с какой именно матрицей выполняются операции, используется функция glMatrixMode(). 4.9. Матрицы преобразований в OpenGL 193
4.9.1. Текущая матрица преобразования В большинстве графических систем используется текущая матрица преобразования — ТМП (СТМ— current transformation matrix). Эта матрица применяется для преобразования всех вершин. Если изменяется ТМП, изменяется текущее состояние системы. Умножение на ТМП является одной из стадий конвейерного процесса обработки информации в графи- графической системе (рис. 4.54). Обозначим матрицу ТМП через С. Тогда, если р — это верши- вершина, то при "перемещении" ее по конвейеру формируется произведение Ср. Матрица ТМП имеет размер 4x4 и может быть изменена функциями, которые входят в состав графиче- графического пакета10. Вершины тмп ¦ Вершины Рис. 4.54. Текущая матрица преобразования В исходном состоянии ТМП является единичной матрицей размера 4x4; при необходимо- необходимости в любой момент прикладная программа может ее реинициализировать. Будем использо- использовать символ <— для обозначения процедуры замены содержимого матрицы. Операция ини- инициализации в нашей системе обозначений будет выражаться следующим образом: Функции изменения С разделены на две группы: функции присвоения новых значений эле- элементам матрицы и функции преобразования матрицы умножением ее на другую матрицу справа или слева". В подавляющем большинстве графических систем поддерживаются три вида преобразований: сдвиг, масштабирование с фиксированной точкой в начале координат и поворот с фиксированной точкой в начале координат. В принятой системе символических обозначений операции умножения матрицы ТМП справа на матрицы преобразований выра- выражаются следующим образом: с<-ст, c<-cs, C«-CR. Операции установки элементов ТМП обозначаются так: С«-Т, c<-s, Cf-R. В большинстве графических систем можно присвоить элементам ТМП значения из любой другой матрицы или умножить ее справа на произвольную матрицу М: С<-М, с<-см. 10 В OpenGL матрица вида представляет собой матрицу аффинного преобразования, которая имеет только 12 степеней свободы, как об этом уже упоминалось в разделе 4.5. Матрица проецирования, ко- которая будет рассмотрена в главе 5, также имеет размер 4x4, но не является матрицей аффинного пре- преобразования. " В OpenGL используется только умножение справа. PHIGS допускает умножение как справа, так и слева. 194 Глава 4. Объекты и геометрические преобразования
4.9.2. Поворот, сдвиг и масштабирование В OpenGL матрица ТМП, которая преобразует координатные описания всех примитивов, является произведением двух матриц— матрицы вида GL_MODELVIEW и матрицы проецирова- проецирования GLJPROJECTION (рис. 4.55). Программист имеет возможность манипулировать каждой из них по отдельности, выбирая нужную с помощью функции glMatrixMode(). Выбранная мат- матрица загружается вызовом функции glLoadMatrixf(<указатель_на_матрицу>); или приравнивается к единичной матрице (инициализируется) вызовом функции glLoadIdentity(); Вершины ; '' '• ¦ :: ¦ -i ¦• - •"'¦' '" I Вершины > Вид I > Проецирование! ^ ТМП Рис. 4.55. Матрицы вида и проецирования Произвольную матрицу размера 4x4 можно задать указателем на массив из 16 чисел, в кото- котором элементы матрицы перечислены по столбцам. Изменить выбранную матрицу можно с помощью функции умножения справа glMultMatrixf (<указатель_на_матрицу>). Для выпол- выполнения поворота, сдвига и масштабирования служат три специальные функции: glRotatef(angle, vx, vy, vz); glTranslatef(dx, dy, d2); glScalef(sx, sy, sz); Каждая из этих функций изменяет выбранную с помощью glMatrixMode() матрицу текущего состояния, домножая ее справа. В функции поворота glRotatef (angle, vx, vy, vz) первый аргумент angle задает угол поворота в градусах, а три последующих — vx, vy и vz — компо- компоненты вектора оси поворота. Аргументы функции сдвига glTranslatef ()— компоненты вектора смещения. Аргументы функции масштабирования glScalef () — масштабные коэф- коэффициенты по координатным осям. 4.9.3. Поворот вокруг фиксированной точки средствами OpenGL В разделе 4.8 было показано, что для поворота вокруг произвольной фиксированной точ- точки (т.е. точки, отличной от начала координат) сначала нужно выполнить преобразование сдвига, совмещающее заданную фиксированную точку с началом координат, потом преобра- преобразование поворота вокруг начала координат, а затем обратное преобразование сдвига, "возвращающее" фиксированную точку в прежнюю позицию. В приведенной ниже последо- последовательности вызовов функций OpenGL сначала устанавливается режим работы с матрицей вида, затем выполняются описанные выше преобразования для следующих исходных данных: угол поворота 45°, ось поворота параллельна прямой, проведенной через начало координат и точку A, 2 , 3), а фиксированная точка имеет координаты D, 5, 6): glMatrixMode(GL_MODELVIEW) glLoadIdentityG; glTranslatefD.О, 5.0, 6.0); 4.9. Матрицы преобразований в OpenGL 195
glRotatefD5.0, 1.0, 2.0, 3.0); glTranslatef(-4.0, -5.0, -6.0); Обратите внимание на то, что работая с пакетом OpenGL, не нужно формировать матрицу поворота вокруг произвольной оси так, как мы это делали в примере из раздела 4.8.4, хотя в качестве упражнения попробуйте запрограммировать этот метод с помощью имеющихся в составе OpenGL функций умножения матриц. 4.9.4. Последовательность выполнения преобразований В приведенной выше последовательности обращения к функциям OpenGL внимательный читатель наверняка отметил, что преобразования задаются "в обратном порядке", т.е. первое преобразование сдвига задается пятым оператором, а последнее — третьим. Дело в том, что в OpenGL действует следующее правило: Преобразование, которое должно быть в цепочке последним, передается первым вы- вызовом соответствующей функции. Несложный анализ убедит вас в справедливости этого правила, которое учитывает особенно- особенности выполнения операций над текущей матрицей преобразования в исполнительной системе OpenGL. Дело в том, что ТМП всегда домножается справа на новую матрицу преобразования. Поэтому приведенную выше последовательность вызовов функций можно выразить с помо- помощью введенной раньше системы обозначений следующим образом: С<-1, С <- СТD.0, 5.0, 6.0), C<-CRD5.0, 1.0,2.0,3.0), С<-СТ(-4.0, -5.0, -6.0). На каждом шаге домножаем ТМП справа на новую матрицу преобразования, в результате чего вся процедура будет иметь результат С = ТD.0, 5.0, 6.0) RD5.0, 1.0, 2.0, 3.0) Т(-4.0, -5.0, -6.0), который полностью соответствует формуле, приведенной в разделе 4.8.1. Каждая вершина р, которая задается после того, как будет сформирована такая матрица вида, умножается справа на матрицу С, в результате чего формируется новая вершина q = Cp. Можно и по-другому рассматривать такой порядок операций в OpenOL, например вос- воспользовавшись концепцией стека. Изменение матрицы ТМП имеет аналогию с занесением матриц в стек. После завершения цепочки матрицы извлекаются из стека в обратном порядке и образуют представленную выше последовательность сомножителей. Аналогия эта только концептуальная, поскольку в исполнительной системе OpenGL стек используется совсем ина- иначе, а матрица, сформированная функцией преобразования, немедленно умножается справа на матрицу ТМП. Что касается стека матриц, то он нам часто понадобится при выполнении мо- моделирования, о чем будет подробно рассказано в главе 8. В составе OpenGL имеются функ- функции записи матрицы в стек glPushMatrix() и извлечения матрицы из стека glPopMatrix(), которые будут использоваться при построении иерархии объектов сцены. 4.9.5. Вращение куба В описанной ниже программе мы воспользуемся кубом, определенным в разделе 4.4, и будем вращать его по командам, формируемым при нажатии кнопок мыши. Нам потребуются три функции с обратным вызовом, которые регистрируются следующим образом: 196 Глава 4. Объекты и геометрические преобразования
glutDisplayFunc(display); glutldleFunc(spincube); glutMouseFunc(mouse); В функции отображения display() сначала устанавливается матрица вида, для чего исполь- используются три угла, заданных в функции обработки событий мыши mouse(). Затем с помощью функции colorcube(), которую мы рассматривали в разделе 4.4, вычерчивается куб. В этом примере будет использоваться двойная буферизация. При каждом вызове display() очища- очищаются буфер кадра и буфер глубины. Последний используется для удаления невидимых по- поверхностей. Выполнение display () завершается процедурой переключения буферов: void display(void) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); glRotatef(theta[0], glRotatef(theta[l], glRotatef(theta[2]f colorcube(); glutSwapBuffers(); 1 0 0 • o, • o, .0, 0 1 0 • o, • o, .0, 0 0 1 .0); .0); .0); В функции обработки событий мыши 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.9. Матрицы преобразований в OpenGL 197
4.9.6. Загрузка матриц и использование стека матриц Хотя в большинстве случаев для формирования матрицы преобразования вполне доста- достаточно функций поворота, сдвига и масштабирования, в некоторых ситуациях приходится формировать матрицу поэлементно. В частности, это приходится делать при формировании матрицы скоса. Точно так же, как матрицу преобразования можно приравнять к единичной матрице, в нее можно загрузить и произвольную 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(....); glRotate?( ); glScalef ( ); /* Здесь вычерчиваются объекты */ glPopMatrix(); 4.10. Взаимодействие пользователя с трехмерными графическими приложениями В рассмотренном выше примере для управления направлением вращения куба на экране использовалась трехкнопочная мышь. Но возможности такого интерфейса пользователя с приложением слишком ограничены. Вместо того чтобы три кнопки мыши использовать для задания ориентации, их можно было бы применить для управления программой, например ее завершением. Как уже отмечалось в разделе 4.8, существует множество способов выполнения поворота вокруг произвольной оси. Мы использовали последовательное вращение вокруг осей коорди- 198 Глава 4. Объекты и геометрические преобразования
нат х,у hz, но можно организовать этот процесс и по-другому: сначала повернуть вокруг оси *, затем вокруг оси у, а закончить опять поворотом вокруг оси х. В таком случае можно до- добиться любой ориентации объекта, используя только две кнопки мыши. Однако по-прежнему остается проблема с направлением вращения — в нашей программе поворот всегда выполня- выполняется только в одном направлении. Для пользователя же желательно иметь возможность пово- поворачивать в обе стороны — и вперед, и назад — и останавливать процесс, когда объект будет сориентирован, как задумано. Библиотека GLUT позволяет использовать для управления процессом кнопки мыши в со- сочетании с управляющими клавишами клавиатуры. Например, левой кнопкой мыши можно задать вращение вокруг оси х по часовой стрелке, а нажав одновременно еще и клавишу <Ctrl> — против часовой стрелки. Но есть и другие способы, которые позволяют организовать более гибкий и удобный ин- интерфейс пользователя с программой. Ниже мы рассмотрим два таких способа. 4.10.1. Использование областей экрана Предположим, что решено использовать одну кнопку мыши для управления ориентацией объекта, другую — для сдвига его ближе к наблюдателю или дальше от него, а третью — для смещения объекта вправо-влево или вверх-вниз. Для организации таких процедур можно воспользоваться функцией с обратным вызовом motion (), которая возвращает информацию о том, какая кнопка мыши нажата и где находится указатель мыши на экране. Информацию о положении указателя мыши можно использовать для управления скоростью вращения или смещения и их направлением. Как уже отмечалось, можно добиться произвольной ориентации, выполняя поворот только во- вокруг двух осей координат. Следовательно, для управления ориентацией нам достаточно одной кнопки мыши и информации о текущем положении указателя мыши на экране. Если при нажатии левой кнопки мыши указатель находится в центре экрана, поворот не выполняется. Если в этот момент указатель находится вверху экрана, то объект поворачивается вокруг оси х по часовой стрелке, а если внизу экрана — против часовой стрелки. Аналогично организуется и вращение во- вокруг оси у, но указатель должен находиться справа или слева от центра. Когда указатель находится в углу экрана, можно одновременно поворачивать объект и вокруг оси х, и вокруг оси у. Сдвиг объекта вправо-влево или вверх-вниз можно выполнять по этой же схеме, но ис- использовать правую кнопку мыши. Для управления движением объекта к наблюдателю или от него (вдоль оси z) будем использовать среднюю кнопку. При этом можно принимать во вни- внимание только положение указателя по вертикали относительно центра экрана. Мы предоставляем читателям возможность самостоятельно реализовать описанный алго- алгоритм в виде программы (см. упр. 4.19). 4.10.2. Виртуальный трекбол Идея использовать текущее положение указателя мыши для управления поворотом вокруг двух осей координат очень близка к тем методам, которые используются при работе с трек- трекболом (см. главе 3). Ниже мы рассмотрим, как развить эти идеи и создать виртуальный трек- трекбол, используя только мышь. Одно из достоинств этого "устройства" состоит в том, что задав с его помощью параметры, можно организовать непрерывное вращение, которое прекратится только по команде пользователя. Кроме того, оно позволяет оперативно, "на ходу", изменять скорость вращения и его направление. Этот же принцип можно использовать и для управле- управления смещением объекта. Начнем с того, что определим соответствие между положением точки на поверхности ша- шарика трекбола и позицией указателя мыши. Будем рассматривать трекбол, представленный на рис. 4.56, и предположим, что радиус его шарика равен одной единице. Тогда можно поста- 4.10. Взаимодействие пользователя с трехмерными графическими приложениями 199
вить в соответствие точкам поверхности шарика точки на плоскости у = О (рис. 4.57). Точка (.х, у, z) на поверхности шарика проецируется в точку (х, О, z) на плоскости. Такая проекция обратима, поскольку известно, что трехмерная точка, спроецированная в определенную точку на плоскости, должна удовлетворять соотношению, описывающему сферу: x2+y2+z2 = 1. Следовательно, данной точке (х, О, z) на плоскости должна соответствовать точка (дг, у, z) на верхней полусфере, где v = - v2 --2 Рис. 4.56. Фрейм трекбола Рис. 4.57. Проекция на плоскость точки на по- поверхности шарика трекбола Таким образом, по положению указателя мыши на экране можно восстанавливать трех- трехмерную точку на поверхности шарика трекбола. Рассмотрим теперь две точки, р, и р2, на верхней полусфере шарика. Векторы, проведенные из центра шарика к этим точкам, опреде- определяют ориентацию плоскости, нормаль к которой можно вычислить как векторное произведе- произведение этих векторов (рис. 4.58): п = р, х р2. Поворачивая трекбол вокруг оси п, можно совместить вектор pi с р2. Угол по- поворота— это угол между векторами pi и р;, который можно вычислить по значению модуля векторного произведения. По- Поскольку р, и р2 являются единичными век- векторами, угол определяется соотношением |sin9| = |п|. Если с большой скоростью отслежи- отслеживать траекторию указателя мыши, так что каждое элементарное смещение ее пози- позиции на экране будет достаточно мало, то можно воспользоваться аппроксимацией угла 0, не прибегая к обратным тригонометрическим функциям: sine « 0. Рис. 4.58. Определение плоскости поворота 200 Глава 4. Объекты и геометрические преобразования
Реализовать обработку положения трекбола можно с помощью функций обработки собы- событий простоя, движения мыши и нажатия кнопок мыши из библиотеки GLUT. Этот процесс можно рассматривать в терминах трех логических переменных или флагов, которые контро- контролируют движение мыши и перерисовку изображения. При инициализации программы этим логическим переменным присваиваются такие значения12: bool trackingMouse = false; bool trackballMove = false; bool redrawContinue = false; Если значение флага redrawContinue равно true, то функция обработки простоя вы- вызывает функцию перерисовки изображения. Если значение флага trackingMouse равно true, то обновляется позиция трекбола в функции обработки движения. Если значение флага trackballMove равно true, то обновляется матрица вращения, которая использу- используется в функции display (). Значения флагов изменяются в функции обработки событий мыши mouse (). Когда поль- пользователь нажимает кнопку мыши (определенную или любую — это определяет разработчик программы), запускается процесс обновления позиции трекбола в функции обработки собы- события движения мыши, причем, если пользователь передвигает мышь, вызывается функция пе- перерисовки объектов на экране. Когда пользователь отпускает кнопку мыши, отслеживание прекращается. Два последних положения мыши используются для определения вектора ско- скорости, что позволяет организовать непрерывное обновление матрицы поворота и таким обра- образом — непрерывное вращение объектов на экране. В этом случае объект будет вращаться и после того, как пользователь отпустит кнопку мыши. Текст программы, реализующей описанный алгоритм, находится в файле cube2.c. Учти- Учтите, что в этой программе используется некоторое упрощение при определении скорости вра- вращения, но в ней также обрабатываются ситуации, не рассмотренные в данном описании. На- Например, что делать, если указатель мыши находится в углу экрана и соответствующая точка на плоскости не может быть спроектирована на верхнюю полусферу поверхности шарика трекбола. 4.10.3. Плавное вращение Описанный пример демонстрирует, с какими сложностями встречается программист при использовании рассмотренных выше методов определения параметров поворота. Этот под- подход основан на .углах Эйлера, которые описывают повороты вокруг осей системы координат. Он позволяет сформировать матрицу произвольного поворота, используя суперпозицию про- простых поворотов вокруг осей системы координат х,у и z. Хотя OpenGL и позволяет самостоя- самостоятельно сформировать любую матрицу поворота, но удобнее использовать принцип суперпо- суперпозиции для определения как оси вращения, так и соответствующих углов. Рассмотрим, что получится в программе анимации, если попытаться организовать движе- движение объекта при переходе от одной ориентации к другой. Подходящу