КулЛиб - Классная библиотека! Скачать книги бесплатно 

Компьютерная графика. Рейтрейсинг и растеризация. [Гэбриел Гамбетта] (pdf) читать онлайн

Книга в формате pdf! Изображения и текст могут не отображаться!


 [Настройки текста]  [Cбросить фильтры]
Tlgm: @it_boooks

Tlgm: @it_boooks

Tlgm: @it_boooks

КОМПЬЮТЕРНАЯ
ГРАФИКА
РЕЙТРЕЙСИНГ И РАСТЕРИЗАЦИЯ

ГЭБРИЕЛ ГАМБЕТТА

2022

Tlgm: @it_boooks

ББК 32.973.2-044.4
УДК 004.92
Г18

Гамбетта Гэбриел
Г18 Компьютерная графика. Рейтрейсинг и растеризация. — СПб.: Питер, 2022. —
224 с.: ил. — (Серия «Библиотека программиста»).
ISBN 978-5-4461-1911-0
За красивыми образами анимационного фильма и реалистичной средой популярных видеоигр
скрываются загадочные алгоритмы.
В этой книге вы познакомитесь с двумя основными направлениями современной графики: рейтрейсингом и растеризацией. Такая литература пугает новичков из-за большого количества математики.
Но только не в этом случае. Познакомьтесь с 3D-рендерингом без длинных формул!
Вы создадите полноценные рабочие рендеры — рейтрейсинг, симулирующий лучи света и их отражение от объектов, растеризатор 3D-моделей, научитесь создавать реалистичные отражения и тени,
а также отрисовывать сцены с любой точки обзора.
Наглядные примеры с псевдокодом позволят без проблем создавать рендеры на любом языке,
а живые JavaScript-демки каждого алгоритма вдохновят на самостоятельные подвиги.

16+ (В соответствии с Федеральным законом от 29 декабря 2010 г. № 436-ФЗ.)
ББК 32.973.2-044.4
УДК 004.92

Права на издание получены по соглашению с No Starch Press. Все права защищены. Никакая часть данной книги
не может быть воспроизведена в какой бы то ни было форме без письменного разрешения владельцев авторских
прав.
Информация, содержащаяся в данной книге, получена из источников, рассматриваемых издательством как надежные. Тем не менее, имея в виду возможные человеческие или технические ошибки, издательство не может
гарантировать абсолютную точность и полноту приводимых сведений и не несет ответственности за возможные
ошибки, связанные с использованием книги. Издательство не несет ответственности за доступность материалов,
ссылки на которые вы можете найти в этой книге. На момент подготовки книги к изданию все ссылки на интернетресурсы были действующими.

ISBN 978-1718500761 англ.

ISBN 978-5-4461-1911-0

© 2021 by Gabriel Gambetta. Computer Graphics from Scratch:
A Programmer’s Guide to 3D Rendering, ISBN 9781718500761
published by No Starch Press Inc. 245 8th Street, San Francisco,
California United States 94103.
Russian edition published under license by No Starch Press Inc.
© Перевод на русский язык ООО «Прогресс Книга», 2022
© Издание на русском языке, оформление ООО «Прогресс Книга»,
2022
© Серия «Библиотека программиста», 2022

Tlgm: @it_boooks

Краткое содержание
Об авторе........................................................................................................................... 14
О техническом редакторе................................................................................................ 15
Благодарности.................................................................................................................... 16
Введение.............................................................................................................................. 17
Глава 1. Вводные понятия........................................................................................................ 22
ЧАСТЬ I. ТРАССИРОВКА ЛУЧЕЙ
Глава 2. Базовая трассировка лучей.................................................................................. 32
Глава 3. Свет............................................................................................................................... 47
Глава 4. Тени и отражения...................................................................................................... 67
Глава 5. Расширение возможностей трассировщика лучей....................................... 80
ЧАСТЬ II. РАСТЕРИЗАЦИЯ
Глава 6. Прямые......................................................................................................................... 92
Глава 7. Закрашенные треугольники. ...............................................................................103
Глава 8. Затененные треугольники....................................................................................109
Глава 9. Перспективная проекция.....................................................................................115
Глава 10. Описание и рендеринг сцены..........................................................................122
Глава 11. Отсечение...............................................................................................................143

Tlgm: @it_boooks

6   Краткое содержание
Глава 12. Удаление скрытых поверхностей....................................................................159
Глава 13. Затенение...............................................................................................................173
Глава 14. Текстуры...................................................................................................................186
Глава 15. Расширение растеризатора.............................................................................199
Послесловие..................................................................................................................... 211
Приложение. Линейная алгебра.....................................................................................213

Tlgm: @it_boooks

Оглавление
Об авторе........................................................................................................................... 14
О техническом редакторе................................................................................................ 15
Благодарности.................................................................................................................... 16
Введение.............................................................................................................................. 17
Для кого эта книга.................................................................................................................. 17
Охват книги.............................................................................................................................. 18
Зачем читать эту книгу......................................................................................................... 19
Структура издания. ............................................................................................................... 19
И еще об авторе. ................................................................................................................... 21
От издательства...................................................................................................................... 21
Глава 1. Вводные понятия........................................................................................................ 22
Холст........................................................................................................................................... 22
Система координат........................................................................................................ 22
Цветовые модели............................................................................................................ 24
Субтрактивная цветовая модель. .............................................................................. 24
Аддитивная цветовая модель...................................................................................... 26
Забудьте лишние детали.............................................................................................. 27
Глубина цвета и представление. ....................................................................................... 28
Управление цветом................................................................................................................ 29
Сцена. ........................................................................................................................................ 29
Итоги главы. ............................................................................................................................. 30

Tlgm: @it_boooks

8   Оглавление
ЧАСТЬ I. ТРАССИРОВКА ЛУЧЕЙ
Глава 2. Базовая трассировка лучей.................................................................................. 32
Рендеринг ландшафта Швеции.......................................................................................... 32
Основные допущения........................................................................................................... 34
С холста на окно просмотра. ............................................................................................ 36
Трассировка лучей. ............................................................................................................... 36
Уравнение траектории луча. ...................................................................................... 37
Уравнение сферы........................................................................................................... 38
Луч встречается со сферой......................................................................................... 39
Рендеринг первых сфер. ...................................................................................................... 41
Итоги главы. ............................................................................................................................. 46
Глава 3. Свет............................................................................................................................... 47
Упрощающие допущения. ................................................................................................... 47
Источники света..................................................................................................................... 48
Точечный свет................................................................................................................... 48
Направленный свет........................................................................................................ 48
Рассеянный свет. ............................................................................................................. 49
Освещение одной точки...................................................................................................... 50
Диффузное отражение. ....................................................................................................... 51
Моделирование диффузного отражения............................................................... 52
Уравнение диффузного отражения.......................................................................... 54
Нормали сферы. ............................................................................................................. 54
Рендеринг с диффузным отражением...................................................................... 55
Зеркальное отражение........................................................................................................ 57
Моделирование зеркального отражения. ............................................................. 59
Выражение зеркального отражения. ....................................................................... 62
Уравнение полного освещения.................................................................................. 62
Рендеринг с зеркальными отражениями................................................................. 63
Итоги главы. ............................................................................................................................. 66

Tlgm: @it_boooks

Оглавление   9

Глава 4. Тени и отражения...................................................................................................... 67
Тени............................................................................................................................................. 67
Принцип формирования теней.................................................................................. 67
Рендеринг с тенями......................................................................................................... 70
Отражения. .............................................................................................................................. 73
Зеркала и отражения.................................................................................................... 73
Рендеринг с отражениями............................................................................................ 76
Итоги главы. ............................................................................................................................. 79
Глава 5. Расширение возможностей трассировщика лучей....................................... 80
Свободное расположение камеры.................................................................................. 80
Улучшение производительности. ...................................................................................... 82
Распараллеливание....................................................................................................... 82
Кэширование неизменяемых значений. ................................................................. 82
Оптимизация теней........................................................................................................ 83
Иерархические структуры. ......................................................................................... 84
Прореживание. ............................................................................................................... 84
Поддержка других примитивов......................................................................................... 85
Конструктивная блочная геометрия................................................................................. 86
Прозрачность.......................................................................................................................... 87
Преломление. .................................................................................................................. 88
Избыточная выборка............................................................................................................ 89
Итоги главы. ............................................................................................................................. 89
ЧАСТЬ II. РАСТЕРИЗАЦИЯ
Глава 6. Прямые......................................................................................................................... 92
Описание прямых.................................................................................................................. 93
Рисование прямых. ................................................................................................................ 94
Рисование прямых с любым уклоном.............................................................................. 98
Линейная интерполяция....................................................................................................... 99
Итоги главы. ...........................................................................................................................102

Tlgm: @it_boooks

10   Оглавление
Глава 7. Закрашенные треугольники. ...............................................................................103
Отрисовка каркасных треугольников...........................................................................103
Отрисовка закрашенных треугольников.....................................................................104
Итоги главы. ...........................................................................................................................108
Глава 8. Затененные треугольники....................................................................................109
Определение задачи..........................................................................................................109
Вычисление затенения краев...........................................................................................110
Вычисление внутреннего затенения. .............................................................................112
Итоги главы. ...........................................................................................................................114
Глава 9. Перспективная проекция.....................................................................................115
Базовые допущения. ...........................................................................................................115
Поиск P'. ..................................................................................................................................116
Уравнение проекции...........................................................................................................117
Свойства уравнения проекции........................................................................................118
Проецирование первого 3D-объекта...........................................................................119
Итоги главы. ...........................................................................................................................121
Глава 10. Описание и рендеринг сцены..........................................................................122
Представление куба. ..........................................................................................................122
Модели и экземпляры.........................................................................................................125
Преобразование модели..................................................................................................129
Преобразование камеры. ................................................................................................131
Матрица преобразований...............................................................................................134
Однородные координаты..................................................................................................135
Матрица вращений в однородных координатах...............................................136
Матрица масштабирования в однородных координатах. .............................136
Матрица для переноса в однородных координатах........................................137
Матрица проекций в однородных координатах................................................137
Матрица отображения из окна просмотра на холст в однородных
координатах...................................................................................................................138
Возвращение к матрице преобразований..................................................................139
Итоги главы. ...........................................................................................................................141

Tlgm: @it_boooks

Оглавление   11

Глава 11. Отсечение...............................................................................................................143
Обзор процесса отсечения..............................................................................................143
Отсекаемый объем..............................................................................................................144
Отсечение сцены по плоскости.......................................................................................145
Определение плоскостей отсечения.............................................................................148
Отсечение объектов............................................................................................................149
Отсечение треугольников. ................................................................................................152
Пересечение отрезка с плоскостью.......................................................................154
Псевдокод отсечения...................................................................................................155
Место отсечения в конвейере рендеринга. ................................................................157
Итоги главы. ...........................................................................................................................158
Глава 12. Удаление скрытых поверхностей....................................................................159
Рендеринг сплошных объектов. .......................................................................................159
Алгоритм художника...........................................................................................................160
Буферизация глубины.........................................................................................................161
Использование 1/Z вместо Z..................................................................................164
Отбрасывание задней грани...........................................................................................168
Классификация треугольников. ...............................................................................170
Итоги главы. ...........................................................................................................................171
Глава 13. Затенение...............................................................................................................173
Затенение и освещение.....................................................................................................173
Плоское затенение..............................................................................................................174
Затенение по Гуро...............................................................................................................176
Затенение по Фонгу. ..........................................................................................................180
Итоги главы. ...........................................................................................................................184
Глава 14. Текстуры...................................................................................................................186
Закрашивание ящика.........................................................................................................186
Билинейная фильтрация.....................................................................................................191
MIP-текстурирование.........................................................................................................194
Трилинейная фильтрация...................................................................................................197
Итоги главы. ...........................................................................................................................198

Tlgm: @it_boooks

12   Оглавление
Глава 15. Расширение растеризатора.............................................................................199
Карты нормалей...................................................................................................................199
Наложение карты среды....................................................................................................202
Тени...........................................................................................................................................203
Трафаретные тени........................................................................................................203
Создание теневых объемов. .....................................................................................205
Подсчет пересечений луча и теневого объема..................................................206
Настройка трафаретного буфера..........................................................................208
Теневая карта.................................................................................................................209
Итоги главы. ...........................................................................................................................210
Послесловие..................................................................................................................... 211
Приложение. Линейная алгебра.....................................................................................213
Точки.........................................................................................................................................213
Векторы....................................................................................................................................214
Представление векторов. ..........................................................................................214
Модуль вектора. ...........................................................................................................215
Операции с точками и векторами..................................................................................215
Вычитание точек............................................................................................................215
Сложение точки и вектора. .......................................................................................216
Сложение векторов......................................................................................................217
Умножение вектора на число...................................................................................217
Перемножение векторов. ..........................................................................................218
Скалярное произведение..........................................................................................218
Векторное произведение...........................................................................................219
Матрицы..................................................................................................................................220
Операции с матрицами..............................................................................................220
Сложение матриц.........................................................................................................220
Умножение матрицы на число..................................................................................221
Перемножение матриц...............................................................................................221
Умножение матрицы на вектор................................................................................222

Tlgm: @it_boooks

Посвящается моему отцу (1947–2007), архитектору и программисту-самоучке,
направившему меня на этот путь.

Мой отец, двухлетний я и ZX81

Моя первая задокументированная программа, рисовавшая линии на экране
ZX-Spectrum+, которую я написал в шесть с половиной лет

Tlgm: @it_boooks

Об авторе
Габриэл Гамбетта (Gabriel Gambetta) в пять лет начал писать игры на ZX Spectrum.
Изучив информатику и поработав в одной крупной организации на родине
в Уругвае, он организовал свою компанию по разработке игр, которой руководил десять лет, параллельно преподавая компьютерную графику в университете.
С 2011 года Габриэл работает в Google Zürich (за исключением периода, когда
он был инженером лондонской компании Improbable, специализирующейся на
многопользовательских играх, и одного года в Мадриде, где он осваивал актерское
мастерство и киносъемку).

Tlgm: @it_boooks

О техническом редакторе
Алехандро Сеговия Азапиан (Alejandro Segovia Azapian) — инженер ПО, уже
14 лет занимающийся компьютерной графикой. За это время он успел поработать
в нескольких ведущих компаниях по созданию 3D-графики, включая Autodesk,
Electronic Arts, PDI/DreamWorks и WB Games. Там Алехандро принимал участие
в разных проектах по реализации графики в реальном времени, будь то приложения,
игры, игровые движки или фреймворки. Сейчас он занимается разработкой программного обеспечения для GPU в ведущей компании по производству бытовой
электроники в Купертино, штат Калифорния.

Tlgm: @it_boooks

Благодарности
Книга, которую вы собираетесь прочесть, создавалась почти 20 лет. Она появилась
благодаря стараниям многих людей.
Омар Паганини (Omar Paganini) и Эрнесто Окампо Эдье (Ernesto Ocampo
Edye). Будучи деканами инженерного факультета и факультета компьютерных наук в Католическом университете Уругвая, они доверили мне, студенту
четвертого курса, руководство направлением компьютерной графики, позволив полностью переработать учебную программу по своему усмотрению.
Весь первый год моего преподавания профессор Роберто Люблинерман
(Roberto Lublinerman) был моим наставником.
Мои студенты с 2003 по 2008 год. Они оказались невольными подопытными
для оттачивания моего преподавательского мастерства, но это не мешало
им принимать и уважать профессора, который был старше их всего на год
(а в некоторых случаях даже младше). Я понимал, что все мои труды не напрасны, когда видел их сияющие от радости лица после создания первого
изображения по принципу трассировки лучей.
Алехандро Сеговия Азапиан. Он прошел путь от моего студента до ассистента. В итоге Алехандро стал моим близким другом, который время от времени
помогал мне в проработке материалов книги. Я горжусь тем, что причастен
к его становлению в качестве высококлассного специалиста по оптимизации
производительности и рендерингу в реальном времени. Эта книга стала такой
благодаря его технической редактуре.
Дж. К. Ван Винкель (J. C. Van Winkel) проделал дополнительную редакторскую работу, внеся много ценных предложений по улучшению содержания.
Читатели Hacker News. Мои заметки лекций, чертежи и демки размещались
на первой полосе портала Hacker News. Этим интересовались многие, в том
числе издательство No Starch Press. Благодаря этому счастливому стечению
обстоятельств моя книга увидела свет.
Билл Поллок (Bill Pollock), Алекс Фрид (Alex Freed), Кэсси Андреадис
(Kassie Andreadis) и вся команда No Starch Press. Эти люди помогли довести до ума и переоформить мои, как мне казалось, готовые для публикации
конспекты с чертежами в реальную книгу. Никогда бы не подумал, что это
может потребовать стольких усилий. На обложке лишь мое имя, но нужно
понимать, что книга стала продуктом коллективных стараний.

Tlgm: @it_boooks

Введение
Компьютерная графика — удивительная тема. Представьте, как можно перейти
от набора геометрических данных и нескольких алгоритмов к спецэффектам для
«Звездных войн» и «Мстителей», «Историй игрушек» и «Холодного сердца» либо
популярных игр вроде Fortnite или Call of Duty?
При этом область компьютерной графики еще и пугающе огромна: от рендеринга
3D-сцен до создания фильтров изображений, от цифровой типографии до симуляции систем частиц. Одной книги будет мало для раскрытия всех дисциплин,
связанных с этой темой. Скорее, потребуется целая библиотека. Здесь же мы сосредоточимся только на рендеринге 3D-сцен.
Книга основана на моем многолетнем опыте преподавания. Это моя скромная
попытка доступно представить этот срез компьютерной графики. Книга будет
понятна и старшекласснику, но при этом в ней много ценного материала и для
профессиональных разработчиков. В издании рассматриваются те же темы, что
и в полноценном университетском курсе.

Для кого эта книга
Она будет полезна как школьникам, так и матерым профессионалам. Работая
над текстом, я сознательно склонялся в пользу простоты и ясности изложения.
Вы увидите это по рассматриваемым идеям и алгоритмам. Везде, где достичь
результата можно было несколькими путями, я предпочитал самый понятный,
стараясь не усложнять рассуждения и не вносить путаницу. Ориентиром мне
служила фраза Альберта Эйнштейна: «Делай просто, насколько это возможно,
но не проще».
Здесь вам не пригодятся особые знания или программные и аппаратные зависимости. Единственный используемый в книге примитив — метод, позволяющий
устанавливать цвет пикселя. Все максимально приближено к освоению материала
с нуля.
Алгоритмы здесь просты, а используемая в них математика вполне понятна —
максимум немного тригонометрии из курса средней школы. Нам понадобится
и линейная алгебра, но в книге есть краткое приложение, где практично изложена
вся важная информация.

Tlgm: @it_boooks

18   Введение

Охват книги
Начав с азов, мы придем к созданию двух полноценных модулей рендеринга:
трассировщика лучей и растеризатора. Несмотря на абсолютно разные подходы,
результат будет схожим. Посмотрите на рис. В.1.

Рис. В.1. Простая сцена, отрисованная трассировщиком лучей (слева)
и растеризатором (справа), созданными по материалам этой книги
Возможности трассировщика лучей и растеризатора пересекаются, но не одинаковы. В дальнейшем мы рассмотрим их отличительные черты. Некоторые вы можете
увидеть на рис. В.2.

Рис. В.2. И у трассировщика лучей, и у растеризатора есть характерные особенности.
Слева: тени с трассировкой лучей и рекурсивные отражения. Справа: растеризованные
текстуры

Tlgm: @it_boooks

Введение   19

На протяжении книги приводятся неформальный псевдокод и ссылки на полноценные рабочие JavaScript-реализации, которые можно выполнять в любом браузере.

Зачем читать эту книгу
Вы получите знания, необходимые для написания программных модулей рендеринга. Здесь мы не будем использовать или изучать существующие API рендеринга:
OpenGL, Vulkan, Metal или DirectX.
Современные графические ускорители мощны и общедоступны. Поэтому редко
приходится писать чистый программный рендер, но такой навык будет полезен
по ряду причин.
Шейдеры и ПО. Первые GPU из начала 1990-х реализовывали свои алгоритмы рендеринга аппаратно. Их можно было использовать, но не изменять
(именно поэтому большинство игр из середины 1990-х выглядят похоже).
Сегодня разработчики пишут свои алгоритмы рендеринга (шейдеры), которые выполняются на специальных микросхемах GPU.
Знание — сила. Понимая теории разных техник рендеринга, вы будете не просто копировать недопонятые фрагменты кода и слепо следовать популярным
подходам. Вы сможете писать более эффективные шейдеры и конвейеры
рендеринга.
Графика — это весело. Работа с графикой — одна из немногих областей компьютерных наук, где мы получаем быструю отдачу. Вы можете бесконечно
радоваться, верно выполнив SQL-запрос. Но это ничто по сравнению с впечатлением от первого успеха в построении отражений по трассировке лучей.
Несколько лет я преподавал в университете компьютерную графику и часто
задавался вопросом, почему мне никак не надоест учить одному и тому же
семестр за семестром. Но, когда я видел радостные лица студентов, которые
ставили свои первые готовые сцены на заставки Рабочего стола, я понимал,
что все это не напрасно.

Структура издания
Книга состоит из двух частей: «Трассировка лучей» и «Растеризация», соответствующих двум модулям рендеринга, которые мы будем создавать. В первой главе даются базовые знания. Лучше читать все главы поочередно, но обе части достаточно
самостоятельны и для независимого изучения. Для большего удобства прочтите
описание каждой главы.
Глава 1. Вводные понятия. Здесь дается определение холсту и единственному инструменту для рисования — PutPixel. В этой же главе вы научитесь
представлять цвета и управлять ими.

Tlgm: @it_boooks

20   Введение
Часть I. Трассировка лучей
Глава 2. Базовая трассировка лучей. Здесь мы создадим простой алгоритм
трассировки лучей, способный отрисовывать несколько сфер, которые будут
выглядеть как цветные круги.
Глава 3. Свет. В этой главе мы определим модель взаимодействия света с объектами и расширим трассировщик возможностью симуляции света. Теперь
сферы будут выглядеть как сферы.
Глава 4. Тени и отражения. Дальше улучшаем внешний вид сфер: теперь
они отбрасывают друг на друга тени и могут иметь зеркальные поверхности.
Глава 5. Расширение возможностей трассировщика лучей. Здесь дается
обзор дополнительных возможностей трассировщика, которые не вписываются в рамки книги, но по желанию их можно добавить.
Часть II. Растеризация
Глава 6. Прямые. Начинаем с пустого холста и создаем алгоритм для рисования отрезков.
Глава 7. Закрашенные треугольники. Используя некоторые идеи из предыдущей главы, мы разработаем алгоритм рисования треугольников, заполненных одним цветом.
Глава 8. Затененные треугольники. Расширяем алгоритм из главы 7 для
закрашивания треугольников плавным цветовым градиентом.
Глава 9. Перспективная проекция. Здесь мы отвлечемся от 2D-рисования,
чтобы рассмотреть геометрические и математические принципы для преобразования 3D-точки в 2D-точку, которую можно будет нарисовать на
холсте.
Глава 10. Описание и рендеринг сцены. Разрабатываем представление для
объектов сцены и изучаем использование перспективной проекции для их
отрисовки на холсте.
Глава 11. Отсечение. Создаем алгоритм удаления невидимых для камеры
частей сцены, позволяющий безопасно отрисовывать ее из любой позиции
камеры.
Глава 12. Удаление скрытых поверхностей. Совмещаем перспективную
проекцию и затененные треугольники для рендеринга визуально твердых
объектов. Выполнить задачу успешно можно, сделав так, чтобы удаленные
объекты не перекрывали более близкие.
Глава 13. Затенение. Учимся применять уравнение освещенности из главы 3
к целым треугольникам.
Глава 14. Текстуры. Разрабатываем алгоритм для имитации деталей поверхности на наших треугольниках.

Tlgm: @it_boooks

Введение   21

Глава 15. Расширение растеризатора. Обзор возможностей для растеризатора, выходящих за рамки книги.
Приложение. Линейная алгебра. Знакомимся с используемыми в книге базовыми понятиями из линейной алгебры: точками, векторами и матрицами.
Рассматриваем операции, которые можно выполнять с этими элементами,
и некоторые примеры их применения.

И еще об авторе
Сейчас я работаю ведущим инженером в Google. До этого же трудился в компании
Improbable (http://improbable.com), у которой есть неплохие шансы создать настоящую матрицу (или в корне изменить разработку мультиплеерных игр). Около
десяти лет я руководил собственной компанией по разработке компьютерных игр
Mystery Studio (http://mysterystudio.com). За это время мы выпустили почти 20 игр,
хотя вы о них наверняка даже не слышали.
Пять лет я преподавал компьютерную графику один семестр на третьем курсе
и признателен всем моим студентам, благодаря которым у меня была возможность
отточить все знания и навыки, лежащие в основе этой книги.
У меня есть и другие увлечения, помимо компьютерной графики, в том числе не инженерного характера. Приглашаю посетить мой сайт http://gabrielgambetta.com, где
есть много дополнительной информации и контактные данные для связи.

От издательства
Ваши замечания, предложения, вопросы отправляйте по адресу comp@piter.com
(издательство «Питер», компьютерная редакция).
Мы будем рады узнать ваше мнение!
На веб-сайте издательства www.piter.com вы найдете подробную информацию о наших книгах.

О научном редакторе русскоязычного издания
Дмитрий Соколов окончил мат-мех СПбГУ, хабилитированный доктор, доцент
университета Лотарингии, руководитель небольшой научной группы PIXEL
(https://pixel.inria.fr/). На платформе GitHub у него 2,6 тысячи подписчиков (https://
github.com/ssloy), а его репозитории по компьютерной графике в сумме имеют двадцать тысяч звезд.

Tlgm: @it_boooks

1

Вводные понятия

Трассировщик лучей и растеризатор реализуют рендеринг
3D-сцены на 2D-поверхности по-разному. Но у обоих подходов
есть несколько базовых общих принципов.
В этой главе мы познакомимся с холстом для отрисовки изображений и системой координат, с помощью которой будем ссылаться
на пиксели холста. Еще мы научимся представлять цвета и управлять ими, а также
описывать 3D-сцену для ее обработки модулями рендеринга.

Холст
Мы будем рисовать на холсте — прямоугольном массиве пикселей, которые могут
закрашиваться независимо. В нашем случае неважно, на экране холст или на бумаге. Нам нужно будет представлять 3D-сцену на 2D-холсте, поэтому рендеринг мы
будем делать на этом абстрактном прямоугольном массиве пикселей.
Все реализации в этой книге будут строиться на основе одной простой функции,
присваивающей цвет пикселю холста:
canvas.PutPixel(x, y, color)

В этом методе находится три аргумента: координата x, координата y и цвет. Начнем
с координат.

Система координат
У холста есть измеряемые в пикселях ширина и высота, которые мы назовем Cw
и Ch. Для обращения к этим пикселям нужна система координат. На большинстве
компьютерных экранов исходная точка отсчета будет в верхнем левом углу. При
этом x возрастает вправо, а y — вниз, как показано на рис. 1.1.

Tlgm: @it_boooks

Глава 1. Вводные понятия   23

Рис. 1.1. Система координат, используемая на большинстве компьютерных экранов
Эта система координат естественна для компьютера, так как опирается на принцип организации видеопамяти, но не очень удобна для людей. Вместо этого программисты 3D-графики используют систему координат для рисования графиков
на бумаге: исходная точка находится в центре, x возрастает вправо и уменьшается
влево, а y возрастает вверх и уменьшается вниз, как показано на рис. 1.2.

Рис. 1.2. Система координат, которую мы будем использовать для холста
При использовании этой системы координат диапазон x представлен как
а y — как

,

. Предположим, что использование функции PutPixel с выходя-

щими за указанный диапазон координатами ничего не дает.

Tlgm: @it_boooks

24   Глава 1. Вводные понятия
В наших примерах холст будет отрисовываться на экране, значит, нам придется
выполнять преобразование из одной системы координат в другую. Для этого нужно
изменить центр системы и обратить направление оси Y. Вот итоговые уравнения
преобразования:
;
.
Предположим, что PutPixel выполняет преобразование автоматически. Теперь
можно представлять холст как имеющий исходную точку координат в середине,
с x, возрастающим вправо, а y — вверх экрана.
Взглянем на последний аргумент PutPixel: цвет.

Цветовые модели
Теория цвета удивительна, но выходит за рамки этой книги, поэтому ниже я приведу упрощенную версию только интересующих нас аспектов.
Когда свет попадает в человеческий глаз, он стимулирует светочувствительные
клетки на его задней стенке. Они генерируют сигналы для мозга согласно длине
волны воспринимаемого света, которые мы интерпретируем как цвета.
В обычных условиях человек не может видеть длину волны, выходящую за видимый диапазон. Длина волны и частота находятся в обратной зависимости (чем
выше частота волны, тем меньше расстояние между ее пиками). Именно поэтому
инфракрасный свет (длина волны больше 740 нм, что соответствует частотам ниже
405 ТГц) безвреден, а ультрафиолетовый (длина волны менее 380 нм, что соответствует частотам выше 790 ТГц) может обжечь кожу.
Любой вообразимый цвет можно описать как разные комбинации этих цветов.
«Белый» — это сумма всех цветов, а «черный» — их полное отсутствие. Описывать
цвета через их длины волн очень неудобно, но почти все их можно создать линейной
комбинацией всего трех основных цветов.

Субтрактивная цветовая модель
Субтрактивная цветовая модель — это просто вычурное название для процесса
смешивания цветов, знакомого вам с детства. Представьте, что берете белый лист
бумаги и красный, синий и желтый карандаши. Сначала вы рисуете желтый круг
и пересекающийся с ним синий, получая зеленый цвет. При наложении желтого
и красного получается оранжевый, при совмещении красного и синего — пурпурный. Если смешать все три цвета, то выйдет почти черный. Детство — увлекательная

Tlgm: @it_boooks

Глава 1. Вводные понятия   25

пора, не так ли? На рис. 1.3 показаны основные цвета субтрактивной модели и создаваемые их смешением вариации.

Рис. 1.3. Основные цвета субтрактивной схемы и их комбинации
У объектов разный цвет из-за того, что они по-разному поглощают и отражают свет.
Начнем с белого света, наподобие солнечного (солнечный свет нельзя назвать белым,
но для наших целей достаточно их сходства). Белый свет содержит весь диапазон
световых волн. После столкновения с объектом поверхность последнего поглощает
некоторые волны, а остальные отражает, в зависимости от материала. После часть
отраженного света достигает наших глаз, и мозг преобразует эту информацию в цвет.
Какой именно? Тот, что представляет сумму длин волн, отраженных поверхностью.
А что происходит с карандашами? Мы начинаем с белого света, отражающегося от
бумаги. Она белая, поэтому отражает большую часть падающего на нее света. При
рисовании желтым карандашом вы добавляете слой материала, поглощающего
волны определенной длины, но пропускающего другие. Они отражаются бумагой,
снова проходят через желтый слой, достигают наших глаз, и мозг интерпретирует
именно эту комбинацию длин волн как «желтый». В этом случае желтый слой вычитает (субтрактирует) часть волн из исходного белого света.
Каждый цветной круг можно рассматривать как фильтр. При рисовании синего
круга, накладывающегося на желтый, вы отфильтровываете из исходного света еще
больше длин волн и глаз достигают те, что не отсеялись синим или желтым кругом,
а мозг их воспринимает как «зеленый».

Tlgm: @it_boooks

26   Глава 1. Вводные понятия
Короче говоря, мы начинаем со всеми длинами волн и для создания нужного цвета
вычитаем их часть из основных цветов. В этой модели цвета получаются путем
вычитания определенных длин волн из основных цветов, чем и обусловлено ее
название.
Но эта модель не совсем верна. На самом деле основные цвета субтрактивной схемы — не синий, красный и желтый, как учат детей и студентов, а циан, маджента
и желтый. Более того, смешение этих трех основных цветов создает очень темный тон,
который не является полностью черным. Поэтому в качестве четвертого основного
добавляется чистый черный цвет. В результате мы получаем цветовую схему CMYK
(Cyan, Magenta, Yellow, Key или Black) (рис. 1.4).

Рис. 1.4. Четыре основных субтрактивных цвета, используемые в картриджах
Эта схема используется в картриджах цветных принтеров, а иногда ее можно увидеть и в контурах картинок дешевых флаеров, где разные цвета немного съезжают
друг с друга.

Аддитивная цветовая модель
Субтрактивная модель — это только половина истории. Если вы когда-нибудь
разглядывали экран вблизи или через увеличительное стекло (либо, чего уж греха таить, случайно на него чихали), то наверняка видели мелкие цветные точки:
красные, зеленые и синие.

Tlgm: @it_boooks

Глава 1. Вводные понятия   27

Принцип работы экранов компьютеров противоположен бумаге, которая не излучает свет, а только отражает часть получаемого. Здесь мы начинаем с белого цвета
и вычитаем ненужные длины волн. Экраны же, наоборот, черные, но при этом сами
излучают свет. И в таком случае мы начинаем без цвета и добавляем волны нужной
длины. Для этого необходимы другие основные цвета. Большинство из них можно
создать, добавляя к черной поверхности красный, зеленый и синий в разных пропорциях. Это называется цветовой схемой RGB или аддитивной цветовой моделью,
показанной на рис. 1.5.
Комбинация аддитивных основных цветов светлее ее компонентов, а комбинация
субтрактивных основных цветов — темнее. Все основные аддитивные цвета складываются в белый, а субтрактивные — в черный.

Рис. 1.5. Аддитивные основные цвета и некоторые из их комбинаций

Забудьте лишние детали
Теперь, когда вам все это известно, вы можете выборочно забыть детали и сосредоточиться на самом важном для нашей работы.
Большинство цветов можно выразить в RGB или в CMYK (либо в другой цветовой схеме), преобразовывая при этом одно цветовое пространство в другое. Наш
приоритет — это рендеринг изображений на экране, поэтому в книге мы будем
использовать схему RGB.

Tlgm: @it_boooks

28   Глава 1. Вводные понятия
Как уже говорилось, объекты поглощают часть достигающего их света, а остальной
отражают. Воспринимаемый нами «цвет» поверхности определяется длинами поглощаемых и отражаемых волн. С этого момента мы будем рассматривать цвет как
свойство поверхности и забудем о длинах волн.

Глубина цвета и представление
Цвет в мониторе зависит от смешения в разных пропорциях красного, зеленого
и синего. Для этого мелкие цветные точки экрана подсвечиваются с разной интенсивностью, которая определяется разницей в подаваемом напряжении.
Сколько же значений интенсивности можно получить? Несмотря на непрерывность напряжения, мы будем управлять цветами с помощью компьютера, использующего дискретные величины (то есть их ограниченное число). Чем больше
оттенков красного, зеленого и синего мы можем представить, тем больше цветов
можно создать.
В большинстве встречающихся сегодня изображений используется по 8 бит на один
основной цвет. Это называется цветовым каналом. Используя 8 бит на канал, мы
получаем 24 бита на пиксель, итого 224 цветов (примерно 16,7 миллиона). Именно
такой формат, известный как R8G8B8 или просто 888, мы и будем использовать
в книге. Об этом формате можно сказать, что его глубина цвета — 24 бита.
Но это не единственный возможный формат. Не так давно для экономии памяти
в ходу были 15- и 16-битные аналоги. В случае с 15 битами присваивалось по
5 бит на канал, а с 16 битами — 5 бит для красного, 6 для зеленого и 5 для синего
(этот формат назывался R5G6B5 или 565). Зеленому выделяется дополнительный бит, потому что наши глаза более чувствительны к изменению именно
в этом цвете.
При использовании 16 бит мы получаем 216 цветов (примерно 65 000), то есть по
одному для каждых 256 цветов в 24-битном режиме. Несмотря на то что 65 000 цветов — это очень много, на изображениях, где цвета изменяются очень плавно, можно
заметить едва уловимые «переходы». Но при использовании 16,7 миллиона цветов
их не видно, так как здесь уже достаточно битов для всех промежуточных оттенков.
В некоторых специализированных приложениях, таких как цветоустановка для
кинофильмов, хорошим решением будет задействовать дополнительные биты на
каждый канал для расширения спектра цветовых деталей.
Мы будем использовать для выражения цвета 3 байта со значением 8-битного
цветового канала в диапазоне от 0 до 255 в каждом. Цвета мы будем выражать как
(R, G, B) — например, (255, 0, 0) представляет чистый красный, (255, 255, 255) —
белый, а (255, 0, 128) — красновато-пурпурный.

Tlgm: @it_boooks

Глава 1. Вводные понятия   29

Управление цветом
Для управления цветом мы будем использовать небольшой набор операций. Если
вы знакомы с линейной алгеброй, то можете представить цвета как векторы в трехмерном цветовом пространстве. Если же нет, то ничего страшного, сейчас мы разберем все базовые операции, которые будем использовать.
Интенсивность цвета можно изменять,умножая каждый из его цветовых каналов
на константу:
k(R, G, B) = (kR, kG, kB).
Например, (32, 0, 128) вдвое ярче, чем (16, 0, 64).
Мы можем совместить два цвета, по отдельности сложив их цветовые каналы:
(R1, G1, B1) + (R2, G2, B2) = (R1 + R2, G1 + G2, B1 + B2).
Если нужно совместить красный (255, 0, 0) и зеленый (0, 255, 0), то мы складываем
их поканально и получаем (255, 255, 0) — желтый.
Учтите, что эти операции могут давать недействительные значения. Удваивание
интенсивности (192, 64, 32) дает значение R, выходящее за цветовой диапазон.
Любое значение больше 255 мы будем принимать за 255, а любое значение меньше 0 — за 0. Такой прием называется обрезкой значения до диапазона 0–255.
Это чем-то похоже на то, что происходит, когда мы делаем недо- или переэкспонированный фотоснимок: получаем полностью черные либо полностью белые
области.
На этом вводная часть по теме цветов и PutPixel заканчивается. Теперь уделим
немного времени изучению представления 3D-объектов при рендеринге.

Сцена
Вы уже познакомились с холстом (абстрактной поверхностью, на которой закрашиваются пиксели). Теперь перейдем к отображению объектов, рассмотрев еще
одну абстракцию — сцену.
Сцена — набор объектов, которые вы отрисовываете. Это может быть что угодно: от
сферы, дрейфующей в бесконечном пустом пространстве (начнем мы именно с этого), и до невероятно детализированной модели внутренностей угрюмого носа огра.
Для рассмотрения объектов в рамках сцены нам нужна система координат. Использовать ту же систему, что и для холста, мы не можем по двум причинам: во-первых,
холст двухмерный, а сцена трехмерная, во-вторых, холст и сцена используют разные

Tlgm: @it_boooks

30   Глава 1. Вводные понятия
единицы измерения: на холсте — пиксели, а для сцены — реальные единицы (имперская или метрическая системы).
Выбор осей здесь произволен, поэтому мы возьмем самые подходящие для нас.
Предположим, что Y вертикальная, а X и Z горизонтальные и все три оси перпендикулярны друг другу. Рассматривайте плоскость XZ как «пол», а XY и YZ —как
вертикальные «стены» квадратной комнаты. Это согласуется с системой координат,
выбранной нами для холста, где Y — вертикаль, а X — горизонталь. На рис. 1.6 показано, как все это выглядит.

Рис. 1.6. Система координат для сцен
Выбор единиц измерения сцены тоже произволен и зависит от того, что она будет
представлять. К примеру, 1 может означать 1 дюйм при моделировании чашки или 1
астрономическую единицу при моделировании Солнечной системы. Неважно, что
выражают выбранные единицы измерения, пока мы согласованно их используем.
С этого момента можно смело их игнорировать.

Итоги главы
В этой главе вы познакомились с холстом, абстракцией и с базовым методом
для построения всего остального: PutPixel. Еще мы выбрали систему координат
для ориентации в пространстве пикселей холста и рассмотрели способ представления цвета этих пикселей. В завершение было введено понятие сцены и для нее
тоже выбрана система координат.
Теперь можно приступать к созданию трассировщика лучей и растеризатора на
базе заложенных нами основ.

Tlgm: @it_boooks

ЧАСТЬ I
ТРАССИРОВКА ЛУЧЕЙ

Tlgm: @it_boooks

2

Базовая трассировка лучей

Эта глава посвящена алгоритму трассировки лучей. Начнем
с рассмотрения его сути и написания простого псевдокода, потом вы научитесь представлять лучи света и объекты в сцене.
В конце мы выработаем способ вычисления лучей, создающих
видимое изображение объектов сцены, и вы узнаете, как представлять их на холсте.

Рендеринг ландшафта Швеции
Представим, что мы направились в одно из экзотических мест планеты, где увидели настолько потрясающий пейзаж, что очень хочется запечатлеть его прелесть.
На рис. 2.1 вы видите пример такого пейзажа.
У нас есть холст и кисточка для рисования, но художественный талант напрочь
отсутствует. Неужели надежды нет? Вовсе не обязательно. Таланта у нас может
и не быть, но зато есть методический подход. Поэтому мы берем москитную сетку,
вырезаем из нее прямоугольный кусок, вставляем в рамку и крепим к палке. Затем
мы выбираем лучшую точку обзора красот ландшафта и ставим другую палку как
отметку точной позиции, где должен находиться глаз.
Рисовать мы еще не начали, но теперь у нас есть фиксированная точка обзора и рамка,
через которую можно наблюдать ландшафт. Эта статичная рамка делится сеткой на
мелкие клетки. А вот теперь очередь методологии. Мы рисуем на холсте сетку с таким же количеством клеток, что и в москитной рамке. Теперь смотрим на ее верхнюю
левую клетку: какой преобладающий цвет мы через нее видим? Небесно-голубой.
Значит, левую верхнюю клетку закрашиваем небесно-голубым. Так мы делаем для
каждой, и вскоре холст уже начинает выражать неплохую картину пейзажа, похожую
на вид через рамку. Получившееся изображение можно увидеть на рис. 2.2.

Tlgm: @it_boooks

Глава 2. Базовая трассировка лучей   33

Рис. 2.1. Потрясающий ландшафт в Швеции

Рис. 2.2. Грубое представление ландшафта

Tlgm: @it_boooks

34   Часть I. Трассировка лучей
Если подумать, то компьютер — очень методичная машина без всякого художественного таланта. А процесс создания картины можно описать так:
Каждую маленькую клетку холста
Закрасить правильным цветом

Очень просто! И все же эта формулировка слишком расплывчата для выполнения
на компьютере. Поэтому углубимся в детали:
Нужным образом разместить точку обзора и рамку
Для каждой клетки холста:
Определить, какая клетка сетки соответствует этой клетке на холсте
Определить цвет, наблюдаемый через клетку сетки
Закрасить клетку этим цветом

Даже такое описание все еще слишком размыто, хотя уже похоже на алгоритм.
Вы удивитесь, но это и есть высокоуровневое представление всего алгоритма трассировки лучей. Все верно, ничего сложного.

Основные допущения
Частично шарм компьютерной графики — в рисовании на экране. Для ускорения
этого процесса мы сделаем ряд упрощающих допущений.
Эти допущения немного ограничат наши возможности, но в следующих главах мы
от них избавимся.
Во-первых, предположим фиксированную точку обзора. Такая точка обзора, где
в примере со шведским ландшафтом мы размещаем глаз, обычно называется позицией камеры. Назовем ее O. Допустим, что камера занимает одну точку в пространстве, находится в исходной позиции системы координат и никогда не смещается.
То есть сейчас O = (0, 0, 0).
Во-вторых, предположим фиксированную ориентацию камеры. Ориентация
камеры определяет ее направление. Пусть в нашем случае она смотрит в направлении положительной части оси Z (обозначим ее как ) при положительной
части оси Y ( ), идущей вверх, и положительной части оси X ( ), идущей вправо
(рис. 2.3).
Теперь позиция и ориентация камеры фиксированы. Но для полного соответствия
аналогии не хватает «рамки», через которую мы смотрим на сцену. Допустим, что
у нее размеры Vw и Vh и она находится перед камерой (перпендикулярно оси ).
Предположим также, что от камеры рамка находится на расстоянии d, ее стороны
параллельны осям X и Y и она центрирована относительно . Звучит запутанно,
но на деле все просто. Взгляните на рис. 2.4.

Tlgm: @it_boooks

Глава 2. Базовая трассировка лучей   35

Рис. 2.3. Позиция и ориентация камеры

Рис. 2.4. Позиция и ориентация окна просмотра
Прямоугольник, который будет окном в мир, называется окном просмотра. Обратите внимание, что его размер и расстояние до камеры определяют угол видимости
камеры — поле зрения, или FOV (field of view). Человеческое поле зрения охватывает
почти 180° по горизонтали (но большая его часть — размытое периферийное зрение
без ощущения глубины). Для простоты мы определим Vw = Vh = d = 1. В результате
FOV будет составлять примерно 53°. Это ведет к получению не сильно искаженных
адекватных изображений.
Вернемся к алгоритму и, пользуясь подходящими терминами, пронумеруем шаги
в листинге 2.1.
Листинг 2.1. Описание алгоритма трассировки лучей



Поместить камеру и окно просмотра нужным образом
Для каждого пикселя на холсте:
 Определить, какая клетка окна просмотра соответствует этому пикселю
 Определить цвет, видимый через эту клетку
 Закрасить пиксель этим цветом

Tlgm: @it_boooks

36   Часть I. Трассировка лучей
Мы только что закончили шаг  (точнее, пока проигнорировали его). Шаг  тривиален: здесь просто используется canvas.PutPixel(x, y, color). Давайте разберем
шаг , а потом сосредоточимся на более изощренных способах выполнения шага .
Этому будет посвящено несколько следующих глав.

С холста на окно просмотра
Шаг  нашего алгоритма из листинга 2.1 просит определить, какая клетка окна просмотра соответствует этому пикселю. Координаты пикселя на холсте — назовем их
Cx и Cy — нам известны. Заметьте, что мы очень удачно разместили окно просмотра
и ориентация его осей соответствует ориентации осей холста, а центр совпадает с его
центром. Окно просмотра измеряется в глобальных единицах, а холст — в пикселях, поэтому для перехода от координат холста к пространственным координатам
нужно лишь изменить масштаб.
;
.
Есть еще кое-что. Окно просмотра двухмерное, но встроено в трехмерное пространство. Мы определили его расположение на расстоянии d от камеры. Значит,
для каждой точки на этой плоскости (называемой плоскостью проекции) по определению z = d, следовательно:
Vz = d.
С этим шагом мы покончили. Для каждого пикселя (Cx, Cy) холста мы можем определить соответствующую ему точку в окне просмотра (Vx, Vy, Vz).

Трассировка лучей
Следующий шаг — выяснить, какой цвет имеет свет, проходящий через (Vx, Vy, Vz)
с позиции обзора камеры (Ox, Oy, Oz).
В реальном мире свет исходит от источника (солнце, лампа накаливания и т. д.), отражается от нескольких объектов и достигает наших глаз. Можно попробовать воссоздать путь каждого фотона, покидающего имитированные нами источники света,
но для этого нужно чрезвычайно много времени. Придется симулировать немыслимое
число фотонов (одна лампа на 100 Вт испускает 1020 фотонов в секунду), и только
их малая часть после прохождения через окно просмотра достигнет (Ox, Oy, Oz).

Tlgm: @it_boooks

Глава 2. Базовая трассировка лучей   37

Такая техника называется трассировкой или отображением фотонов, но она выходит за рамки этой книги.
Вместо этого мы рассмотрим лучи света «наоборот». Начнем с исходящего от камеры луча, проходящего через точку в окне просмотра и прокладывающего свой путь
вплоть до достижения объекта сцены. Данный объект — это то, что камера «видит»
через конкретную точку окна просмотра. Итак, в качестве первой аппроксимации
мы просто возьмем цвет этого объекта как «цвет света, проходящего через эту точку», как показано на рис. 2.5.

Рис. 2.5. Маленькая клетка в окне просмотра, представляющая один пиксель холста,
закрашенный цветом объекта, который камера через него видит
Теперь нам нужны кое-какие уравнения.

Уравнение траектории луча
Для нас удобнее всего будет представить луч с помощью параметрического уравнения. Мы знаем, что он проходит через O, и знаем его направление (от O к V), значит,
можно выразить любую точку P в луче как:
P = O + t(V – O).
Здесь t — любое вещественное число. Путем подстановки в это уравнение каждого
значения t от 0 до +∞ мы получаем каждую точку P вдоль луча.
Обозначим (V – O), направление луча, как . Тогда уравнение станет таким:
.
Чтобы лучше понять это уравнение, представьте, что мы начинаем луч в исходной
точке (O) и «продвигаемся» вдоль его направления ( ) на некоторую величину (t).

Tlgm: @it_boooks

38   Часть I. Трассировка лучей
Такое продвижение включает все точки вдоль луча. Подробнее об этих векторных
операциях можно узнать в приложении «Линейная алгебра». На рис. 2.6 показано
наше уравнение в действии.



Рис. 2.6. Некоторые точки луча O + tD для разных значений t
На рис. 2.6 показаны точки вдоль луча, которые соответствуют t = 0,5 и t = 1,0.
Каждое значение t дает другую точку вдоль луча.

Уравнение сферы
Теперь нам нужен объект, чтобы лучам было с чем столкнуться. Для строительных
элементов сцен мы можем взять любой геометрический примитив. Для трассировки лучей мы воспользуемся сферами, потому что ими проще управлять через
уравнения.
Сфера — это множество точек, пролегающих на равном расстоянии от некоторой
заданной точки. Это расстояние называется радиусом сферы. На рис. 2.7 показана
сфера, определенная по ее центру C и радиусу r.

Рис. 2.7. Сфера, определенная по ее центру и радиусу
Согласно вышеприведенному определению, если C будет центром, а r — радиусом
сферы, точки P на поверхности этой сферы должны удовлетворять такому уравнению:
Расстояние(P, C) = r.

Tlgm: @it_boooks

Глава 2. Базовая трассировка лучей   39

Немного поиграем с этим уравнением. Если какие-то моменты будут вам непонятны, обратитесь к приложению «Линейная алгебра».
Расстояние между P и С — это длина вектора от P к C:
|P – C| = r.
Длина вектора | | — это квадратный корень из его скалярного произведения с самим
собой
:
.
Чтобы избавиться от квадратного корня, возведем обе стороны в квадрат:
〈P – C, P – C〉 = r2.
Все эти формулировки уравнения равнозначны, но с последней работать будет
удобнее.

Луч встречается со сферой
Теперь у нас есть два уравнения: описывающее точки на сфере и описывающее
точки на луче:
〈P – C, P – C〉 = r2;
P=O+t .
Пересекаются ли луч и сфера? Если да, то где?
Предположим, что луч пересекается со сферой в точке P. Она есть и вдоль луча,
и на поверхности сферы, значит, она должна удовлетворять обоим уравнениям.
Обратите внимание, что единственная переменная в этих уравнениях — это параметр t, ведь O, , C и r даны, а P — искомая точка.
Так как P в обоих уравнениях — это одна и та же точка, можно подставить вместо
нее в первом уравнении выражение для P из второго, что даст нам:
〈O + t – C, O + t – C〉 = r2.
Если мы можем найти значения t, удовлетворяющие этому уравнению, то сможем
подставить их в уравнение луча для нахождения точек, в которых луч пересекает
сферу.

Tlgm: @it_boooks

40   Часть I. Трассировка лучей
Сейчас это уравнение выглядит очень громоздко, поэтому мы попробуем его упростить с помощью алгебраических действий.
Во-первых, пусть

= O – C. Тогда можно записать уравнение как:


+ t 〉 = r2.

+t ,

Используя дистрибутивность скалярного произведения, разобьем его на составляющие (пояснения тоже можете найти в приложении «Линейная алгебра»):



,

+t ,

〉+〈

+ t , t 〉 = r2;

〉 + 〈t ,

〉+〈

, t 〉 + 〈t , t 〉 = r2.

Слегка изменив порядок выражений, получаем:
〈t , t 〉 + 2〈

,t 〉+〈

〉 = r2.

,

Вынесение t за скалярные произведения и перемещение r2 на другую сторону
уравнения дает нам:
t2〈 , 〉 + t(2〈

, 〉) + 〈

,

〉 – r2 = 0.

Напомню, что скалярное произведение двух векторов — это вещественное число,
значит, каждое выражение между угловыми скобками тоже будет вещественным.
Если дать им имена, то получится уже более знакомая форма:
a = 〈 , 〉;
b = 2〈
c=〈

,

, 〉;
〉 – r2;

at2 + bt + c = 0.
Перед нами старое доброе квадратичное уравнение. Его решения — это значения
параметра t, где луч пересекает сферу:
.
К счастью, это имеет геометрический смысл. Если помните, квадратичное уравнение может не иметь решений, иметь одно двойное решение или же два разных.
Это определяется значением дискриминанта b2 – 4ac. Эти варианты в точности
соответствуют случаям, когда луч не пересекает сферу, проходит по касательной
к сфере или пересекает ее насквозь (рис. 2.8).

Tlgm: @it_boooks

Глава 2. Базовая трассировка лучей   41

Рис. 2.8. Геометрическая интерпретация квадратичного уравнения: нет решений,
одно решение или два решения
Как только мы нашли значение t, можно подставить его обратно в уравнение луча
и получить точку пересечения P, соответствующую этому значению.

Рендеринг первых сфер
Подытожим. Для каждого пикселя холста мы можем вычислить соответствующую
точку окна просмотра. С учетом позиции камеры можно выразить уравнение исходящего от камеры луча и проходящего через эту точку окна просмотра. С помощью
сферы можно вычислить точки, в которых ее пересекает луч.
Все, что нужно сделать, — вычислить пересечения луча с каждой сферой, оставить
из них самое близкое к камере и закрасить пиксель на холсте соответствующим
цветом. Теперь мы почти готовы к рендерингу наших первых сфер, но параметр t
заслуживает больше внимания. Вернемся к уравнению луча:
P = O + t(V – O).
Исходная точка и направление луча фиксированы, поэтому изменение t в диапазоне
вещественных чисел будет давать каждую точку P на этом луче. Обратите внимание,

Tlgm: @it_boooks

42   Часть I. Трассировка лучей
что для t = 0 мы получаем P = O, а для t = 1 получаем P = V. Отрицательные значения t дают точки в противоположном направлении — позади камеры. Поэтому мы
можем разделить пространство на три части, как показано в табл. 2.1. На рис. 2.9
оно представлено схематически.
Табл. 2.1. Подразделение параметрического пространства
t 0 {
 i += light.intensity * n_dot_l/(length(N) * length(L))
}
}
}
return i
}

В листинге 3.1 мы рассматриваем три типа освещения по-разному. Рассеянный свет
самый простой и обрабатывается напрямую . Точечные и направленные источники

Tlgm: @it_boooks

56   Часть I. Трассировка лучей
делят большую часть кода, в частности вычисление интенсивности . Но векторы
направлений вычисляются иначе ( и ), в зависимости от типа. Условие  гарантирует, что мы не добавляем отрицательные значения.
Осталось только использовать ComputeLighting в TraceRay. Мы замещаем строку,
возвращающую цвет сферы:
return closest_sphere.color

этим фрагментом:
P = O + closest_t * D // Вычисляем пересечение
N = P - closest_sphere.center // Вычисляем нормаль сферы
// в пересечении N = N / length(N)
return closest_sphere.color * ComputeLighting(P, N)

Ну и ради забавы добавим большую желтую сферу:
sphere {
color = (255, 255, 0) # Желтый
center = (0, -5001, 0)
radius = 5000
}

Запускаем рендерер, и теперь сферы стали похожи на сферы (рис. 3.7)!

Рис. 3.7. Диффузное отражение добавляет в сцену ощущение глубины и объема

Tlgm: @it_boooks

Глава 3. Свет   57

Живую реализацию этого алгоритма можно найти по адресу https://gabrielgambet­
ta.com/cgfs/diffuse-demo.
Так, стоп. Как получилось, что большая желтая сфера стала плоским желтым
полом? Она не стала. Просто она гораздо больше других сфер, а камера слишком
близко. Наша планета тоже кажется плоской, когда мы смотрим себе под ноги.

Зеркальное отражение
Теперь переключим внимание на глянцевые объекты. Их вид меняется при смещении точки обзора.
Представьте себе бильярдный шар или машину, только что выехавшую с мойки.
Такие вещи обычно отражают на себе яркие пятна, которые будто бы перемещаются
при вашем движении вокруг них. В отличие от матовых, видимость нами этих объектов определяется точкой обзора.
Если обойти красный бильярдный шар вокруг, то он останется красным, но смещается яркое белое пятно, придающее ему блеск. Значит, новый эффект, который мы
хотим смоделировать, не замещает диффузное отражение, а дополняет его.
Теперь нужно понять причину. Для этого мы внимательно рассмотрим принцип
отражения поверхностями света. Из предыдущего раздела мы знаем, что при достижении лучом поверхности матового объекта он рассеивается обратно в сцену
равномерно во всех направлениях. Это происходит, потому что поверхность объекта
не идеально гладкая и при детальном рассмотрении выглядит как много мельчайших поверхностей, направленных в разные стороны (рис. 3.8).

Рис. 3.8. Так может выглядеть шероховатая поверхность матового объекта
под микроскопом. Падающие лучи света отражаются произвольно

Tlgm: @it_boooks

58   Часть I. Трассировка лучей
Но что, если поверхность не будет такой шероховатой? Рассмотрим другую крайность: идеально отполированное зеркало. При столкновении с ним луч отражается
в одном направлении. Если назвать направление отраженного света и сохранить
условие, что направлен в сторону источника света, то получится ситуация как
на рис. 3.9.

Рис. 3.9. Лучи света, отраженные зеркалом
Гладкая отполированная поверхность в той или иной степени проявляет свойства зеркала. Именно поэтому отражение называется зеркальным (specular), от
лат. speculum — «зеркало».
От идеально отполированного зеркала падающий луч света отражается в одном
направлении . Поэтому мы и видим отраженные объекты очень ясно: для каждого
падающего луча света есть всего один отраженный луч . Но не все объекты отполированы идеально. Большинство света отражается в направлении , но часть
его отражается в близком к направлении. Чем ближе к , тем больше света отражается в этом направлении, как можно видеть на рис. 3.10. Именно степень блеска
объекта и определяет, насколько быстро отраженный свет уменьшается по мере
отклонения от .
Нам нужно определить, сколько света от отражается обратно в направлении нашей точки обзора. Если — это вектор обзора, указывающий из точки P в сторону
камеры, а α — угол между и , мы получаем рис. 3.11.
При α = 0° весь свет отражается в направлении . При α = 90° свет вообще не отражается. Как и в случае с диффузным отражением, нам нужно математическое
выражение, позволяющее определить, что происходит при промежуточных значениях α.

Tlgm: @it_boooks

Глава 3. Свет   59

Рис. 3.10. На не идеально
полированных поверхностях чем ближе направление

к вектору R, тем больше лучей отражается в этом направлении

Рис. 3.11. Векторы и углы, задействованные в вычислении зеркального отражения

Моделирование зеркального отражения
В начале этой главы я отметил, что у некоторых моделей нет физических прототипов. Это одна из них. Следующая модель произвольна, но мы ее используем,
потому что она проста в вычислении и смотрится симпатично.
Рассмотрим свойства cos(α): cos(0) = 1 и cos(±90) = 0, как раз то, что нам нужно.
При этом значения постепенно уменьшаются от 0 до 90 в виде очень изящной
кривой, показанной на рис. 3.12.

Tlgm: @it_boooks

60   Часть I. Трассировка лучей

Рис. 3.12. График для cos(α)
Это значит, что cos(α) отвечает всем нашим требованиям к зеркальной функции
отражения, так почему бы его не использовать?
Есть еще одна деталь. Если с ходу задействовать эту формулу, то каждый объект
окажется одинаково глянцевым. Как же адаптировать уравнение для выражения
разных степеней глянца?
Напомню, что глянец (блеск) — это мера того, как быстро уменьшается функция
отражения при возрастании α. Простой способ получения разных кривых блеска — возведение cos(α) в положительную степень s. 0 ≤ cos(α) ≤ 1, поэтому мы
гарантированно получаем 0 ≤ cos(α)s ≤ 1. Следовательно, cos(α)s подобен cos(α),
только «уже» его. На рис. 3.13 показан график для cos(α)s при разных значениях s.

Рис. 3.13. График для cos(α)s

Tlgm: @it_boooks

Глава 3. Свет   61

Чем выше значение s, тем «уже» становится функция около 0 и ярче выглядит объект; s называется зеркальной экспонентой и относится к свойствам поверхности. Эта
модель не основана на физической действительности, поэтому значения s можно
определить только путем проб и ошибок — по сути, подбирая их, пока они не будут
выглядеть «правильно». В случае же с моделью на основе физики можно заглянуть
в двухлучевые функции отражательной способности (BDRF).
Теперь объединим все это. Луч света, исходящий из направления , сталкивается
с поверхностью с зеркальной экспонентой s в точке P, где нормаль поверхности —
это . Сколько света отразится в направлении наблюдателя ?
Согласно нашей модели, это значение cos(α)s, где α — угол между и ; — это ,
отраженный по отношению к . Поэтому сперва нам нужно вычислить из и .
Можно разделить на два вектора ( и ) так, чтобы
лелен , а
перпендикулярен (рис. 3.14).

, где

парал-



Рис. 3.14. Разделение L на его компоненты LP и LN
— проекция на . Исходя из свойств скалярного произведения и того, что
, длина этой проекции равна
. Мы определили
параллельным к ,
значит,
.
Поскольку

, можно сразу получить

.

Теперь рассмотрим . Он симметричен к относительно , поэтому его параллельный компонент совпадает с компонентом , а его перпендикулярный компонент
противоположен компоненту . То есть
. Все это отражено на рис. 3.15.
Подставляя найденные выше выражения, получаем:
.
А после небольшого упрощения:
.

Tlgm: @it_boooks

62   Часть I. Трассировка лучей



Рис. 3.15. Вычисление L

Выражение зеркального отражения
Теперь можно составить уравнение для зеркального отражения:
;
.
Как и в случае с диффузным светом, cos(α) может оказаться отрицательным, и нам,
как и раньше, нужно это игнорировать. Кроме того, не каждый объект должен быть
глянцевым. Для матовых выражение зеркальности вообще не должно вычисляться.
Мы отметим это в сцене установкой зеркальной экспоненты на –1 и так обработаем
объекты.

Уравнение полного освещения
Мы можем добавить выражение зеркального отражения в создаваемое нами
уравнение освещенности и получить единую формулу, описывающую освещение
в точке:
,

где IP — полное освещение в точке P, IA — интенсивность рассеянного света, N — нормаль поверхности в P, V — вектор от P к камере, s — зеркальная экспонента поверхности, Ii — интенсивность потока света i, Li — вектор из P к свету i, а Ri — вектор
отражения в P для потока света i.

Tlgm: @it_boooks

Глава 3. Свет   63

Рендеринг с зеркальными отражениями
Теперь добавим зеркальные отражения в нашу сцену. Для начала кое-что в ней
изменим:
sphere {
center = (0, -1, 3)
radius = 1
color = (255, 0, 0) # Красный
specular = 500 # Глянцевый
}
sphere {
center = (2, 0, 4)
radius = 1
color = (0, 0, 255) # Синий
specular = 500 # Глянцевый
}
sphere {
center = (-2, 0, 4)
radius = 1
color = (0, 255, 0) # Зеленый
specular = 10 # Немного глянцевый
}
sphere {
center = (0, -5001, 0)
radius = 5000
color = (255, 255, 0) # Желтый
specular = 1000 # Сильно глянцевый
}

Это та же сцена, что и ранее, но с добавлением в определения сфер зеркальных
экспонент.
На уровне кода нам нужно изменить ComputeLighting для вычисления выражения
зеркальности в случае необходимости и добавления его к полному свету. Обратите
внимание, что этой функции теперь требуются и s, как видно из листинга 3.2.
Листинг 3.2. ComputeLighting, поддерживающая диффузное и зеркальное отражения
ComputeLighting(P, N, V, s) {
i = 0.0
for light in scene.Lights {
if light.type == ambient {
i += light.intensity
} else {
if light.type == point {
L = light.position - P
} else {
L = light.direction
}

Tlgm: @it_boooks

64   Часть I. Трассировка лучей
// Диффузное
n_dot_l = dot(N, L) if n_dot_l > 0 {
i += light.intensity * n_dot_l/(length(N) * length(L))
}
// Зеркальное
if s != -1 {
R = 2 * N * dot(N, L) - L
r_dot_v = dot(R, V)
 if r_dot_v > 0 {
i += light.intensity * pow(r_dot_v/(length(R) * length(V)), s)
}
}



}

}
}
return i

Большую часть кода мы не трогаем. Но добавляем фрагмент для обработки зеркальных отражений, чтобы он применялся только к глянцевым объектам , и убеждаемся в том, что не добавляем отрицательную интенсивность света , как делали
для диффузного отражения.
В конце нужно изменить TraceRay для передачи новых параметров в ComputeLighting.
С s все просто: она берется прямо из определения сцены. Но откуда берется ? Это
вектор, направленный от объекта к камере. К счастью, в TraceRay у нас уже есть
вектор, направленный от камеры к объекту, — это , направление трассируемого
луча. Значит, — это просто
.
В листинге 3.3 дается новый вариант TraceRay, уже с зеркальным отражением.
Листинг 3.3. TraceRay с зеркальным отражением
TraceRay(O, D, t_min, t_max) {
closest_t = inf
closest_sphere = NULL
for sphere in scene.Spheres {
t1, t2 = IntersectRaySphere(O, D, sphere)
if t1 in [t_min, t_max] and t1 < closest_t {
closest_t = t1
closest_sphere = sphere
}
if t2 in [t_min, t_max] and t2 < closest_t {
closest_t = t2
closest_sphere = sphere
}
}
if closest_sphere == NULL {
return BACKGROUND_COLOR
}

Tlgm: @it_boooks

Глава 3. Свет   65

}

P = O + closest_t * D // Вычисляем пересечение
N = P - closest_sphere.center
// Вычисляем нормаль сферы в пересечении N = N / length(N)
 return closest_sphere.color * ComputeLighting(P, N, -D, closest_sphere.specular)

Вычисление цвета 1 сложнее, чем кажется. Напомню, что цвета нужно умножать
поканально и результаты вписывать в диапазон канала (в нашем случае 0–255).
В этом примере сцены значения интенсивности света суммируются в 1,0. Но после
добавления зеркальных отражений эти значения могут выйти из данного диапазона.
Вознаграждение за все это жонглирование векторами вы видите на рис. 3.16.

Рис. 3.16. Сцена с рассеянным, диффузным и зеркальным отражениями. В дополнение
к глубине и объему здесь у каждой поверхности появились свои отличия
Живую реализацию этого алгоритма можно найти по адресу https://gabrielgambet­
ta.com/cgfs/specular-demo.
Обратите внимание, что на рис. 3.16 красная сфера с зеркальной экспонентой,
равной 500, отражает на себе более яркое пятно блеска, чем зеленая со значением
зеркальной экспоненты 10. Так происходит из-за определенного кадрирования изображения и размещения источников света в сцене. У левой половины зеркальной
сферы все еще нет зеркального отражения.

Tlgm: @it_boooks

66   Часть I. Трассировка лучей

Итоги главы
В этой главе мы взяли очень простой трассировщик лучей из предыдущей и наделили его способностью моделировать источники света, а также определили способ
их взаимодействия с объектами сцены.
Есть три типа источников света: точечные, направленные и рассеянные. Вы узнали,
как с их помощью представлять разный тип освещения из реальной жизни, и научились описывать их в определении сцены.
После мы перешли к рассмотрению поверхности объектов сцены, разделив их на
матовые и глянцевые. Используя принцип взаимодействия лучей света с ними, мы
разработали две модели — диффузного и зеркального отражения — для вычисления
количества света, отражаемого ими в камеру.
Конечный результат рендеринга сцены оказался намного реалистичнее: теперь мы
не просто видим контуры объектов, но и получаем ощущение их глубины, объема
и материала, из которого они сделаны. И все же здесь не хватает основного аспекта
освещения — теней. О них мы и поговорим в следующей главе.

Tlgm: @it_boooks

4

Тени и отражения

Наш квест по реалистичному рендерингу сцены продолжается.
В прошлой главе мы смоделировали способ взаимодействия
лучей света с поверхностями. В этой главе мы смоделируем
два аспекта процесса взаимодействия света со сценой: объекты,
отбрасывающие тени и отражающиеся в других объектах.

Тени
Там, где присутствуют свет и объекты, есть и тени. Все это у нас есть, так где же
тени?

Принцип формирования теней
Начнем с основного вопроса: откуда вообще берутся тени? Они возникают, когда
лучи света не могут достичь объекта из-за встреченного на пути препятствия.
Ранее мы рассмотрели только локальные взаимодействия между источником света
и поверхностью, проигнорировав все остальные процессы сцены. Чтобы возникли
тени, нужно посмотреть глобальнее и проанализировать взаимодействие между
источником света, желаемой поверхностью и другими объектами сцены.
Воплотить это несложно. Только следует помнить, что «если между точкой и источником света есть объект, то освещение, исходящее от этого источника, добавлять
не нужно».

Tlgm: @it_boooks

68   Часть I. Трассировка лучей
На рис. 4.1 есть два случая, которые нам необходимо будет различать.

Рис. 4.1. Если между точкой и источником света есть объект,
тень всегда будет отбрасываться
У нас уже есть все нужные инструменты. Начнем с направленного света. Нам
известна P — интересующая нас точка. Нам известен — часть определения
света. Зная P и , можно определить луч (
), проходящий из этой точки
поверхности к бесконечно удаленному источнику света. Этот луч с чем-то
пересекается? Если ответ отрицательный, то между точкой и источником света
ничего нет. Поэтому мы вычисляем освещение от этого источника, как и раньше. Если же пересекается, то точка будет в тени и освещение от этого источника
нужно игнорировать.
Мы уже умеем вычислять ближайшее пересечение между лучом и сферой. Для этого
у нас есть TraceRay, с помощью которой мы трассируем исходящие из камеры лучи.
Можно повторно использовать большую ее часть для вычисления ближайшего
пересечения луча света с остальной частью сцены.
Хотя при этом параметры функции будут немного другие.
Теперь луч начинается не от камеры, а от точки P.
Направление луча не (V – O), а .

Tlgm: @it_boooks

Глава 4. Тени и отражения   69

Мы не хотим, чтобы объекты позади P отбрасывали тени на эту точку, значит,
нам нужно tmin = 0.
Мы имеем дело с бесконечно удаленными направленными источниками
света, поэтому даже очень далекий объект должен все равно отбрасывать
тень на P. Значит, tmax = +∞.
На рис. 4.2 есть две точки: P0 и P1. При трассировке луча, исходящего из P0 к источнику света, пересечений с объектами нет. Значит, свет может достичь P0 и тени
на ней не будет. Если же с P1 между лучом и сферой мы находим два пересечения
с t > 0 (пересечение находится между поверхностью и источником света), точка
находится в тени.

Рис. 4.2. Сфера отбрасывает тень на P1, но не на P0
Точечные источники можно рассматривать так же, но есть два исключения.
Во-первых, непостоянен, но мы уже умеем вычислять его из P и позиции света.
Во-вторых, мы не хотим, чтобы объекты дальше источника света смогли отбрасывать тени на P. Значит, в этом случае нам нужно tmax = 1, чтобы при достижении
источника света луч останавливался.
На рис. 4.3 есть все эти ситуации. Когда мы испускаем луч из P0 к L0, то находим
пересечения с небольшой сферой. Но для них t > 1, то есть они не находятся между
источником света и P0. Значит, мы их игнорируем, поэтому P0 находится не в тени.
С другой стороны, луч из P1 с направлением L1 пересекает большую сферу с 0 < t < 1,
и в результате она отбрасывает тень на P1.
При этом надо учесть буквальный граничный случай. Рассмотрим луч
. Если
мы ищем пересечения начиная с tmin = 0, то найдем одно в самой точке P! Мы знаем,
что P находится на сфере, значит, для t = 0
. То есть каждая точка будет
отбрасывать тень на саму себя.

Tlgm: @it_boooks

70   Часть I. Трассировка лучей

Рис. 4.3. Определяем, отбрасывается ли тень на точку, используя значение t
Простейшее решение — установить tmin на очень малое значение вместо 0. С точки
зрения геометрии мы говорим, что луч должен начинаться немного над поверхностью, а не из самой P. Так диапазон будет [ε, +∞] для направленных источников
и [ε, 1] для точечных.
Вы можете захотеть это исправить и не вычислять пересечения между лучом
и сферой, которой принадлежит P. Для сфер вариант сработает, но не подойдет
для объектов с формами посложнее. Например, когда вы прикрываетесь рукой от
солнца, то она отбрасывает тень на ваше лицо, обе поверхности в этом случае —
часть одного объекта — вашего тела.

Рендеринг с тенями
Теперь переведем все это в псевдокод.
Ранее TraceRay сначала вычислял ближайшее пересечение луча со сферой, а потом
освещение в нем. Нам нужно извлечь код ближайшего пересечения — он нам понадобится для вычисления теней (листинг 4.1).
Листинг 4.1. Вычисление ближайшего пересечения
ClosestIntersection(O, D, t_min, t_max) {
closest_t = inf
closest_sphere = NULL
for sphere in scene.Spheres {
t1, t2 = IntersectRaySphere(O, D, sphere)
if t1 in [t_min, t_max] and t1 < closest_t {
closest_t = t1
closest_sphere = sphere
}

Tlgm: @it_boooks

Глава 4. Тени и отражения   71

if t2 in [t_min, t_max] and t2 < closest_t {
closest_t = t2
closest_sphere = sphere
}

}

}
return closest_sphere, closest_t

Перепишем TraceRay для повторного использования этой функции и получим ее
упрощенный вариант (листинг 4.2).
Листинг 4.2. Упрощенная версия TraceRay после вычленения ClosestIntersection
TraceRay(O, D, t_min, t_max) {
closest_sphere, closest_t = ClosestIntersection(O, D, t_min, t_max)
if closest_sphere == NULL {
return BACKGROUND_COLOR
}
P = O + closest_t * D
N = P - closest_sphere.center
N = N / length(N)
return closest_sphere.color * ComputeLighting(P, N, -D, closest_sphere.specular)
}

Теперь добавим в ComputeLighting проверку теней  (листинг 4.3).
Листинг 4.3. ComputeLighting с поддержкой теней
ComputeLighting(P, N, V, s) {
i = 0.0
for light in scene.Lights {
if light.type == ambient {
i += light.intensity
} else {
if light.type == point {
L = light.position - P
t_max = 1
} else {
L = light.direction
t_max = inf
}
// Проверка теней
shadow_sphere, shadow_t = ClosestIntersection(P, L, 0.001, t_max)
if shadow_sphere != NULL {
continue
}



// Диффузное
n_dot_l = dot(N, L) if n_dot_l > 0 {
i += light.intensity * n_dot_l / (length(N) * length(L))
}

Tlgm: @it_boooks

72   Часть I. Трассировка лучей
// Зеркальное
if s != -1 {
R = 2 * N * dot(N, L) - L r_dot_v = dot(R, V)
if r_dot_v > 0 {
i += light.intensity * pow(r_dot_v / (length(R) * length(V)), s)
}
}

}

}
}
return i

На рис. 4.4 показан итоговый вариант нашей сцены.

Рис. 4.4. Сцена на основе трассировки лучей, с тенями
Живую реализацию этого алгоритма можно найти по ссылке https://gabrielgambetta.
com/cgfs/shadows-demo. В этом демо доступен выбор трассировки из t = 0 и из t = ε,
чтобы вы смогли понять разницу между этими вариантами.
Вот теперь мы уже кое-чего добились. Объекты в сцене взаимодействуют реалистичнее, отбрасывая друг на друга тени. Дальше мы рассмотрим другие варианты
взаимодействия между ними — отражение объектами других объектов.

Tlgm: @it_boooks

Глава 4. Тени и отражения   73

Отражения
Мы уже говорили о зеркалоподобных поверхностях, но тогда речь шла только
о придании им глянца. А можем ли мы использовать объекты, которые будут отражать на своей поверхности другие объекты? Можем, и в трассировщике сделать
это будет просто, хотя сначала голова может пойти кругом.

Зеркала и отражения
Разберем принцип работы зеркала. Когда мы смотрим в него, то видим отражаемые
им лучи света. Как показано на рис. 4.5, они отражаются симметрично относительно
нормали поверхности.

Рис. 4.5. Луч света отражается от зеркала в направлении, симметричном
относительно нормали его поверхности
Допустим, мы трассируем луч, и ближайшее пересечение происходит с зеркалом.
Какого цвета будет этот луч? Это не цвет самого зеркала, потому что мы наблюдаем отраженный свет. Значит, нужно выяснить, откуда он исходит и каким цветом
обладает. Для этого вычислим направление отраженного луча и определим цвет
света, идущего из этого направления.
Если бы только у нас была функция, помогающая нам это сделать… Подождите-ка!
У нас она есть и имя ей TraceRay!
В основном цикле для каждого пикселя мы создаем луч из камеры в сцену и вызываем TraceRay для определения цвета, который камера видит в этом направлении.
Если TraceRay определяет, что камера видит зеркало, то этой функции нужно
лишь вычислить направление отраженного луча. Так мы сможем определить
цвет света, идущего из этого направления. Причем вызывать TraceRay должна…
саму себя.
Перечитывайте два последних абзаца, пока полностью не поймете их. Если это
ваша первая встреча с рекурсивной трассировкой лучей, то прочесть придется

Tlgm: @it_boooks

74   Часть I. Трассировка лучей
не единожды. Не спешите, я подожду — и как только эйфория от прекрасного
момента «Ага! Понял!» начнет рассеиваться, мы сформулируем все более четко.
При создании рекурсивного алгоритма (вызывающего самого себя) нам нужно
убедиться, что мы не порождаем бесконечный цикл (известный как «Программа
не отвечает. Закрыть?»). У этого алгоритма есть два естественных условия выхода:
когда луч сталкивается с неотражающим объектом и когда он ни с чем не сталкивается. Но есть простой случай, когда мы можем попасть в западню бесконечного
цикла: эффект бесконечного коридора. Его можно увидеть, если поставить два зеркала напротив друг друга.
Есть много способов предотвратить это. Но мы просто введем в алгоритм понятие
ограничения рекурсии, которое поможет нам контролировать, насколько «глубоко»
она может уйти. Назовем это ограничение r. Когда r = 0, мы видим объекты без отражений. Когда r = 1, мы видим объекты и отражения некоторых объектов в них
(рис. 4.6).

Рис. 4.6. Отражения некоторых объектов в других объектах (r = 1). Мы видим сферы,
отраженные от других сфер, но сами они отражений не дают

Tlgm: @it_boooks

Глава 4. Тени и отражения   75

При r = 2 мы видим объекты, отражения некоторых объектов и отражения их отражений. На рис. 4.7 виден результат для r = 3. Нет смысла уходить глубже трех
уровней, ведь разницы уже почти не будет.

Рис. 4.7. Отражения, ограниченные тремя рекурсивными вызовами (r = 3). Теперь мы
видим в них отражения отражений сфер
Внесем еще одно отличение: отражаемость не должна определяться как «есть» или
«нет» и объекты могут быть только частично отражающими. Мы присвоим каждой
поверхности число между 0 и 1 для указания степени ее отражаемости. Потом мы
вычислим средневзвешенное локально освещенного и отраженного цвета, используя это число как вес.
Ну и в завершение уточним параметры для рекурсивного вызова TraceRay.
Луч начинается от поверхности объекта в точке P.
Направление отраженного луча — это направление падающего луча, отраженного от P. В TraceRay есть , направление падающего луча к P, значит,
направление отраженного луча будет
, отраженным относительно .

Tlgm: @it_boooks

76   Часть I. Трассировка лучей
Нам не нужно, чтобы объекты отражали сами себя, поэтому tmin = ε.
Мы хотим видеть отраженный объект независимо от его удаленности, поэтому tmax = +∞.
Ограничение рекурсии будет на 1 меньше его текущей границы (во избежание бесконечной рекурсии).
Теперь можно переводить все это в псевдокод.

Рендеринг с отражениями
Давайте добавим в наш трассировщик отражения. Для начала изменим определение
сцены, добавив каждой поверхности свойство reflective. Оно будет описывать
степень ее отражаемости в диапазоне от 0 (совсем не отражает) до 1 (идеальное
зеркало).
sphere {
center = (0, -1, 3)
radius = 1
color = (255, 0, 0) # Красная
specular = 500 # Глянцевая
reflective = 0.2 # Слегка отражающая
}
sphere {
center = (-2, 1, 3)
radius = 1
color = (0, 0, 255) # Синяя
specular = 500 # Глянцевая
reflective = 0.3 # Чуть более отражающая
}
sphere {
center = (2, 1, 3)
radius = 1
color = (0, 255, 0) # Зеленая
specular = 10 # Немного глянцевая
reflective = 0.4 # Еще более отражающая
}
sphere {
color = (255, 255, 0) # Желтая
center = (0, -5001, 0)
radius = 5000
specular = 1000 # Очень глянцевая
reflective = 0.5 # Полуотражающая
}

Tlgm: @it_boooks

Глава 4. Тени и отражения   77

Мы уже используем формулу отражения луча при вычислении зеркальных отражений, поэтому ее можно исключить. Она получает луч и нормаль , возвращая ,
отраженный относительно .
ReflectRay(R, N) {
return 2 * N * dot(N, R) - R;
}

Единственное, что следует поправить в ComputeLighting, — заменить уравнение
отражения на вызов ReflectRay.
Есть небольшое изменение в основном методе — нужно передавать ограничение
рекурсии в высокоуровневый вызов TraceRay:
color = TraceRay(O, D, 1, inf, recursion_depth)

Можно установить начальное значение recursion_depth на разумную величину,
например 3.
Единственные заметные изменения происходят в конце TraceRay, где мы рекурсивно
вычисляем отражения. В листинге 4.4 можно увидеть это наглядно.
Листинг 4.4. Псевдокод трассировщика лучей с отражениями
TraceRay(O, D, t_min, t_max, recursion_depth) {
closest_sphere, closest_t = ClosestIntersection(O, D, t_min, t_max)
if closest_sphere == NULL {
return BACKGROUND_COLOR
}
// Вычисляем локальный цвет
P = O + closest_t * D
N = P - closest_sphere.center N = N / length(N)
local_color = closest_sphere.color * ComputeLighting(P, N,-D,
closest_sphere.specular)
//
//
 r
if

Если достигается граница рекурсии либо объект
оказывается неотражающим, процесс завершается
= closest_sphere.reflective
recursion_depth abs(dy) {
// Прямая скорее горизонтальна
// Убеждаемся, что x0 < x1 if x0 > x1 {
swap(P0, P1)

Tlgm: @it_boooks

Глава 6. Прямые   99

}

}
a = dy/dx
y = y0
for x = x0 to x1 {
canvas.PutPixel(x, y, color)
y = y + a
}
} else {
// Прямая скорее вертикальна
// Убеждаемся, что y0 < y1
if y0 > y1 {
swap(P0, P1)
}
a = dx/dy
x = x0
for y = y0 to y1 {
canvas.PutPixel(x, y, color)
x = x + a
}
}

Вариант неэстетичный, но рабочий. Здесь мы видим много повторяющегося кода,
а также сильно переплетаются логика выбора функции, логика вычисления значений и сам процесс отрисовки пикселей. Но это исправимо!

Линейная интерполяция
У нас есть две линейные функции: y = f(x) и x = f(y). Забудем о пикселях и напишем
их в более общем виде: d = f(i), где i — независимая переменная, для которой мы
выбираем значения, а d — зависимая переменная, чье значение зависит от первой.
Ее нам нужно вычислить. В горизонтальном случае x — независимая переменная,
а y — зависимая. В вертикальном случае — наоборот.
Любую функцию можно записать как d = f(i). Но мы знаем два момента, полностью
определяющие нашу функцию: то, что она линейная, и два ее значения — d0 = f(i0)
и d1 = f(i1). Можно написать простую функцию, получающую эти значения и возвращающую список всех промежуточных значений d, предположив, что i0 < i1:
Interpolate (i0, d0, i1, d1) {
values = []
a = (d1 - d0) / (i1 - i0)
d = d0
for i = i0 to i1 {
values.append(d)
d = d + a
}
return values
}

Tlgm: @it_boooks

100   Часть II. Растеризация
По форме эта функция похожа на первые две версии DrawLine, но переменные
здесь — i и d, а не x и y. И вместо отрисовки пикселей она сохраняет значения
в списке.
Обратите внимание, что значение d, соответствующее i0, возвращается в values[0],
значение i0 + 1 — в values[1] и т. д. Как правило, значение для in возвращается
в values[i_n - i_0], предполагая, что in находится в диапазоне [i0, i1].
Рассмотрим граничный случай: нам может понадобиться вычислить d = f(i), когда
i0 = i1. Здесь мы не можем вычислить даже a, поэтому просто рассматриваем это
как особый случай:
Interpolate (i0, d0, i1, d1) {
if i0 == i1 {
return [ d0 ]
}
values = []
a = (d1 - d0) / (i1 - i0)
d = d0
for i = i0 to i1 {
values.append(d)
d = d + a
}
return values
}

Как деталь реализации и до конца книги значения i всегда будут целыми (поскольку
представляют пиксели), а значения d всегда будут с плавающей запятой (поскольку
представляют значения общей линейной функции).
Теперь можно написать DrawLine с помощью Interpolate (листинг 6.2).
Листинг 6.2. Версия DrawLine, использующая Interpolate
DrawLine(P0, P1, color) {
if abs(x1 - x0) > abs(y1 - y0) {
// Прямая скорее горизонтальна
// Убеждаемся, что x0 < x1
if x0 > x1 {
swap(P0, P1)
}
ys = Interpolate(x0, y0, x1, y1)
for x = x0 to x1 {
canvas.PutPixel(x, ys[x - x0], color)
}
} else {
// Прямая скорее вертикальна
// Убеждаемся, что y0 < y1
if y0 > y1 {

Tlgm: @it_boooks

Глава 6. Прямые   101

}

}

swap(P0, P1)
}
xs = Interpolate(y0, x0, y1, x1)
for y = y0 to y1 {
canvas.PutPixel(xs[y - y0], y, color)
}

Этот вариант DrawLine может обрабатывать правильно все случаи (рис. 6.5).

Рис. 6.5. Отрефакторенный алгоритм обрабатывает все случаи верно
Живое демо этого алгоритма можно найти по адресу https://gabrielgambetta.com/
cgfs/lines-demo.
Эта версия не сильно короче предыдущей, но она более четко отделяет вычисление
промежуточных значений y и x от определения, какая переменная является независимой и от самого кода отрисовки пикселей.
Вы удивитесь, но этот алгоритм отрисовки прямых лучший или самый быстрый из
существующих. Таким можно назвать и алгоритм Брезенхэма. Но мы рассмотрели
именно этот по двум причинам. Во-первых, его проще понять, а для нашей книги
это базовый принцип. Во-вторых, он дал нам функцию Interpolate, которую мы
будем активно использовать до конца книги.

Tlgm: @it_boooks

102   Часть II. Растеризация

Итоги главы
В этой главе вы проделали свои первые шаги по построению растеризатора. С помощью единственного инструмента в нашем арсенале, PutPixel, мы разработали
алгоритм, способный рисовать на холсте отрезки прямых.
Мы разработали и вспомогательный метод Interpolate, представляющий способ
эффективного вычисления значений линейной функции. Мы будем часто пользоваться этим методом. Поэтому прежде, чем перейти к новой главе, убедитесь, что
вы все поняли в этой.
Дальше с помощью Interpolate мы нарисуем на холсте более сложные и интересные
фигуры — треугольники.

Tlgm: @it_boooks

7

Закрашенные
треугольники

Ранее вы сделали первые шаги к отрисовке простых фигур —
отрезков прямых. Для этого мы использовали только PutPixel
и алгоритм на основе простой математики. В этой главе мы
повторно задействуем часть этой математики для отрисовки
закрашенного треугольника.

Отрисовка каркасных
треугольников
С помощью метода DrawLine можно нарисовать контуры треугольника:
DrawWireframeTriangle (P0, P1, P2, color) {
DrawLine(P0, P1, color);
DrawLine(P1, P2, color);
DrawLine(P2, P0, color);
}

Такой вид контуров называется проволочным каркасом, потому что треугольник
выглядит так, будто сделан из проволоки (рис. 7.1).
Многообещающее начало! Дальше мы разберем заполнение этого треугольника
цветом.

Tlgm: @it_boooks

104   Часть II. Растеризация

Рис. 7.1. Каркасный треугольник с вершинами (–200, –250),
(200, 50) и (20, 250)

Отрисовка закрашенных
треугольников
Нам нужно нарисовать треугольник, закрашенный любым цветом на выбор. Сделать это можно несколькими способами. Рисовать закрашенные треугольники мы
будем, рассматривая их как коллекции горизонтальных отрезков, которые в совокупности будут выглядеть как треугольник. На рис. 7.2 схематически показано, как
выглядит такой треугольник в виде отдельных отрезков.

Рис. 7.2. Отрисовка закрашенного треугольника
с помощью горизонтальных отрезков

Tlgm: @it_boooks

Глава 7. Закрашенные треугольники    105

Ниже очень приблизительно обозначено то, что мы собираемся делать:
for each horizontal line y between the triangle's top and bottom
compute x_left and x_right for this y
DrawLine(x_left, y, x_right, y)

Начнем с «между вершиной и основанием треугольника». Треугольник определяется тремя вершинами: P0, P1 и P2. Если эти точки упорядочить по возрастанию
значения y так, чтобы y0 ≤ y1 ≤ y2, тогда диапазон значений y, входящих в треугольник, будет [y0, y2]:
if y1 < y0 { swap(P1, P0) }
if y2 < y0 { swap(P2, P0) }
if y2 < y1 { swap(P2, P1) }

Подобное упорядочение вершин все упрощает: после этого можно всегда предполагать, что P0 — нижняя точка треугольника, a P2 — верхняя. Благодаря этому у нас
не будет путаницы с возможным их порядком.
Дальше вычислим массивы x_left и x_right. Это будет непросто, потому что у тре­
угольника три стороны, а не две. И все же при рассмотрении только значений y
у нас всегда будет длинная сторона от P0 к P1 и две коротких: от P0 к P1 и от P1 к P2.
Есть особый случай, когда y0 = y1 или y1 = y2. То есть когда одна из сторон треугольника горизонтальна. В такой ситуации две другие стороны одной высоты, значит,
любую из них можно рассматривать как высокую. Нужно ли тогда выбрать левую
или правую? К счастью, это неважно. Алгоритм будет поддерживать обе горизонтальные. Поэтому мы можем продолжать придерживаться нашего определения,
что длинная сторона — это та, что идет от P0 к P2.
Значения для x_right будут браться либо из длинной стороны, либо из объединения
коротких сторон. Значения для x_left будут браться из другого множества. Начнем
с вычисления значений x для всех трех сторон. Мы будем рисовать горизонтальные
отрезки, поэтому нам нужно ровно по одному значению x для каждого значения y.
Это значит, что можно вычислить эти значения с помощью Interpolate, использовав y как независимую переменную, а x — как зависимую:
x01 = Interpolate(y0, x0, y1, x1)
x12 = Interpolate(y1, x1, y2, x2)
x02 = Interpolate(y0, x0, y2, x2)

Значения x для одной из сторон находятся в x02. Значения для другой стороны
берутся из объединения x01 и x12. Обратите внимание, что в x01 и x12 есть повторяющееся значение: x для y1 — одновременно последнее значение x01 и первое x12.
Нам нужно избавиться от одного из них (мы произвольно выбираем последнее
значение x01), после чего объединить эти массивы:
remove_last(x01)
x012 = x01 + x12

Tlgm: @it_boooks

106   Часть II. Растеризация
Мы получили x02 и x012, и теперь нужно определить, какой из них x_left, а какой
x_right. Для этого можно выбрать любую горизонтальную прямую (например,
среднюю) и сравнить ее значения x в x02 и x012. Если x в x02 окажется меньше, чем
x в x012, тогда x02 должен быть x_left, а если наоборот — x_right.
m = floor(x02.length / 2)
if x02[m] < x012[m] {
x_left = x02
x_right = x012
} else {
x_left = x012
x_right = x02
}

Теперь у нас есть все данные для отрисовки горизонтальных отрезков. Можно
использовать DrawLine, но она очень общая, а сейчас мы рисуем горизонтальные
прямые слева направо, значит, эффективнее будет использовать цикл for. Это даст
нам больше контроля над каждым рисуемым пикселем, что особенно пригодится
в дальнейшем.
В листинге 7.1 отражена завершенная функция DrawFilledTriangle.
Листинг 7.1. Функция для отрисовки закрашенных треугольников
DrawFilledTriangle (P0, P1, P2, color) {
 // Упорядочиваем точки, чтобы y0