Энциклопедия третьего измерения, часть 2
В прошлой статье мы обозрели технологию создания виртуальных миров, с птичьего полета рассмотрели общие основы трехмерного бытия. Давайте займемся вещами более конкретными. Как
Рис. 1. Тесселятор по специальной формуле определяет нормаль каждого полигона.
совместить тонкие программные материи и материальное компьютерное “железо”? Создали мы в оперативной памяти математическую модель красочного мира. Эта модель через миллисекунды оказалась у нас перед глазами — на поверхности монитора. Интересно, как? С помощью процесса, который называется рендеринг (от английского render ). Неужели для этого процесса не придумали русского слова, спросите вы? Придумали. И слово это визуализация. В кругу программистов оно не прижилось, и сейчас его употребляют в более узком смысле. Так моделлеры называют просчет сцены в каком-нибудь трехмерном редакторе. Поэтому, хоть все мы и патриоты, давайте для точности употреблять слово “рендер”.Рендеринг — это все те экзекуции, которые производят над моделью программные библиотеки и “железо”, начиная с первичной обработки и заканчивая сохранением получившегося кадра во фрейм-буфер. Давайте совершим увлекательную экскурсию в пыточную камеру и понаблюдаем, что делают экзекуторы с математической моделью прекрасного некогда мира. Все операции рендеринга выполняются по многоступенчатому механизму, который назвали конвейером рендеринга. Этот самый конвейер состоит из стадий тесселяции , геометрической обработки и растеризации. Почему обработчик трехмерного мира назвали столь хитро? Дело в том, что сам принцип конвейерной обработки 3D является технологическим стандартом, а не прихотью какой-то отдельной конторы. По конвейерному принципу работают все трехмерные программные интерфейсы и все графические акселераторы. Это — закон.
Рис. 2. Любая поверхность разбивается на полигоны, а полигоны — на треугольники.
Тесселяция, или грустный сказ о муках полигона Каждый этап конвейера обозначается какой-то буквой. Официально первый этап конвейера называется тесселяция и обозначается буквой “ T ”. Однако садисты и экзекуторы предпочитают называть его триангуляцией , памятуя об объекте пыток. В процессе пыток цельный трехмерный мир разрывается на составляющие, разбирается на мелкие винтики. Винтики растаскиваются на атомы, а атомы стресс-пытками в газовых камерах доводятся до состояния водорода с кучей неприкаянной энергии. Говоря сухим и научным языком, в процессе тесселяции поверхности объектов разбиваются на полигоны, а полигоны — на треугольники (хотя в некоторых случаях эта стадия опускается). Даже в таком относительно простом процессе есть несколько условностей и сложностей. Не все йогурты одинаково полезны, и не все акселераторы одинаково воспринимают результат тесселяции. Некоторые довольно непритязательны, жуют все, что дают, а некоторые до неприличия капризны. Им подавай только треугольники с горизонтальной верхней или нижней гранью, а то и с
Рис. 3. Нормали играют очень важную роль в жизнедеятельности полигонов. Например, они используются в алгоритмах отсечения невидимых поверхностей и динамического освещения и затенения.
описаниями уравнений ребер. Вопреки всеобщему мнению, что всея акселератор силен и могуч, тесселяцию он ускорить при всем желании не сможет. Отныне и вовеки веков операция эта производится программно. А вы говорите, GeForce 15… Геометрическая обработка, или зачем нам нужен GPU Стадия “ G ” называется геометрической обработкой. Раньше этот этап конвейера выполнялся полностью программно, но с появлением геометрических процессоров ( GPU ) часть (только часть) тягот геометрических преобразований сняли с плеч центрального процессора. Геометрическая обработка — общее название для ряда операций, благодаря которым мы видим на экране проецированное реалистичное изображение. Первая из них — трансформация (transformation). Во время трансформации преобразуются координаты объектов. По-научному, к ним применяются матрицы преобразований. А если по-русски, каждый объект посылают, куда ему надо, поворачивают как надо и масштабируют. До и после преобразования координат выполняется отсечение (clipping). После отсечения выполняется освещение. В процессе освещения решается уравнение освещенности , результатом которого становится определение цвета каждой вершины
Рис. 4. Так действует карта освещенности. Вначале берем исходную текстуру…
полигона с учетом всех источников света и свойств материала. К сожалению, еще далек тот день, когда освещение будет полностью рассчитываться силами акселератора. Современные акселераторы уже умеют это делать, но… очень медленно. Разработчикам проще использовать старые добрые карты освещенности (lightmap). Для всех источников света заранее создаются текстуры освещенности, которые накладываются на основные текстуры. Карты освещенности разработчики либо рендерят на рабочих станциях с помощью технологии трассировки лучей (ray tracing), либо рисуют от руки. При таком подходе нечего и говорить о динамичных источниках света. В лучшем случае, позволят разбить одинокую лампочку. Все остальное статично. После освещения координаты сцены преобразуются в координаты экрана. Для убыстрения рендера координаты вершин переводятся из плавающей точки в фиксированную точку (не создавать же отдельный графический сопроцессор).
Рис. 5 …потом создаем карту освещенности для нужного источника света…
Растеризация, или будни палача Последний этап конвейера называется растеризацией и обозначается буквой “ R ”. Это единственный этап конвейера, который даже в старых акселераторах выполнялся на аппаратном уровне. Растеризация включает в себя субпиксельную подготовку и собственно рендер. Наиболее сложный этап растеризации — удаление скрытых поверхностей (HSR — Hidden Surface Removal). О нем давайте поговорим подробнее. Казалось бы, чего проще отправить рендериться всю сцену целиком. В итоге получим громадный кадр, который потом можно обрезать до размера экрана. Вот только играли бы мы с вами сейчас не в UT 2004, а в тетрис, если бы разработчики решали проблему таким путем. Ведь этот громадный кадр способен посадить на мель любую современную видеокарту. Поэтому поступают по-другому. Определяются полигоны, которые гарантированно не попадут в кадр. Эти полигоны отсекаются. И рендерится только та часть сцены, которую в данный момент “видит” виртуальная камера. Такой подход экономит системные ресурсы. Все было бы хорошо, если бы не одно
Рис. 6. …и получаем световое пятно от фонаря на стене.
“но”. Отсечь невидимые поверхности — задача нетривиальная. Как учитывать полигоны, у которых “в кадре” оказывается только кусочек? А как учесть полигон, который находится где-то там за стенкой и все равно не виден, а нам придется его зачем-то просчитывать? В разные времена проблему отсечения невидимых поверхностей решали по-разному. Но начало всегда было одинаковым. Шестью плоскостями по трем координатам ограничивается область сцены, которая гарантированно будет видна на экране. Эти плоскости образуют объем отсечения (clipping volume), который берется с некоторым запасом. Затем в дело вступает backface culling — отбрасывание задних граней. У каждого полигона помимо координат вершин есть важнейшая характеристика — нормаль. Это вектор, который лежит на перпендикуляре, восставленном из геометрического центра треугольника. С помощью специальной функции можно по координатам вершин треугольника определить его нормаль. Я не стал бы углубляться в математику, если бы нормаль не была важнейшим компонентом для расчета таких вещей, как игровая физика, реалистичные тени, грани отсечения и многие другие. У каждого полигона есть две стороны — лицевая и обратная. Нормаль определяет, куда “смотрит” полигон. Представьте себе сферу. Нормали сферы направлены во внешнюю сторону, и ее поверхность образована лицевыми сторонами граней. Примерно половина полигонов сферы “смотрит” от экрана, вторая половина — на экран. А это значит, что все полигоны, смотрящие от экрана, гарантированно не видны. Их можно отсечь. И рендерить акселератору придется только половину полигонов сферы. Конечно, не все игровые объекты такие симметричные, как сфера, но у большинства примерно половина полигонов не видна. На этом принципе основывается backface culling. О том, как с помощью нормали рассчитываются реалистичные тени и столкновения объектов, мы поговорим в одной из следующих статей цикла.
Рис. 7. Динамическое освещение — альтернативный картам освещенности способ создания реалистичного освещения. Выглядит он куда эффектней, но не каждый акселератор потянет такую красоту.
В зоне отсечения все еще много полигонов. И б о льшая их часть не видна. Это — игровые объекты и персонажи за стенами, под полами, над потолками. Обычными способами отсечь их невозможно. Простой расчет, что находится перед стенкой, а что — за ней, занял бы массу процессорного времени. А ведь есть еще
Рис. 8. Типовое BSP-дерево малополигональной модели.
и окна, а то и полупрозрачные панели. Считать до скончания веков. Разработчики графических движков и “железа” пытались придумать самый рациональный путь решения этой проблемы. И придумали! Речь идет о BSP-деревьях. Наверняка вы много раз о них слышали, но никто толком не мог объяснить, что это такое. Недаром вы сейчас читаете Энциклопедию Третьего Измерения , и именно здесь вы найдете ответы на свои вопросы. BSP (Binary Space Partitioning) — это двоичное дерево предварительной сортировки треугольников сцены. С его помощью в процессе рендера можно очень быстро отсортировать треугольники по расстоянию от наблюдателя. Ясно, что в отсортированном BSP-дереве верхние треугольники будут видимыми, а нижние — нет. Но построить BSP-дерево — сама по себе ресурсоемкая задача. Этот метод не идеален. Можно отсечь невидимые треугольники и другим методом — методом порталов. Все объекты в помещении, где находится игрок, считаются условно видимыми (хотя потом их видимость дополнительно проверяется), а предметы вне комнаты — невидимыми. Разработчики уровня расставляют логические объекты — порталы — в дверях, окнах, полупрозрачных
Рис. 9. Другой способ отсечь невидимые объекты — разделить сцену плоскостями на логические зоны.
перегородках — словом, в местах, через которые игрок сможет увидеть другие комнаты. При рендере объекты вне текущего помещения просчитываются только сквозь порталы, что значительно упрощает задачу. К сожалению, это метод почти не подходит для открытых пространств. Для ускорения процедуры отсечения невидимых граней разработчики применяют и некоторые допущения и упрощения сцены. Всю сцену разбивают на большие кластеры , например на отдельные здания. Каждый кластер разбивают на кластеры поменьше: здания — на комнаты, и т.д. В каждой комнате выделяют отдельные предметы: столы, стулья, ящики, шкафы. Сложные предметы (людей, например) представляют в виде параллелепипедов. Из всего этого собирается большое иерархическое дерево уровня с кластерами вместо листьев. Теперь, если весь большой кластер игроку не виден, то и все подкластеры внутри кластера тоже не видны, а значит, их не надо обсчитывать. Такой подход экономит мощности акселератора, но вот применяют его нечасто. Никакой даже самый интеллектуальный акселератор на сегодняшний день не может сам разбить уровень на кластеры и подкластеры. Все тяготы по построению иерархии кластеров ложатся на хрупкие плечи левел-дизайнеров. А кому нужна дополнительная головная боль? Хорошая идея тонет в человеческой лени. Никакой из существующих способов отсечения невидимых граней нельзя назвать ни эффективным, ни оптимальным. Различными способами сцена освобождается от лишних полигонов.
Рис. 10. Для такой плотной застройки высокополигональных зданий из Half-Life 2 тяжело задать логические зоны, но еще тяжелее построить BSP- дерево. Поэтому иногда программисты пользуются гибридными методами.
Думаете, проблемы на этом заканчиваются? Как бы не так. С невидимыми полигонами мы разобрались, но остались еще перекрывающиеся полигоны. Представьте себе модель стола. Мысленно разбейте ее на полигоны. Какие-то полигоны будут перекрывать другие. Но это в нашей реальности. В виртуальной реальности не все так однозначно. Если полигоны отрендерятся в произвольном порядке, вполне может получиться такая картина: одна из задних ножек стола перекрывает стол спереди, торец где-то на заднем фоне, а передняя ножка вообще вывернута наизнанку. И получается у нас не стол, а сплошное недоразумение. А все потому, что полигоны нельзя рендерить абы как. Их надо рендерить в строгом порядке. Те, что дальше — рендерятся раньше. Те, что ближе — позже. Это еще не все. Бывает, что два полигона пересекаются в некоторой точке. Кого из них рендерить первым? Нам понятно, что надо отрендерить часть первого и часть второго. Но вот процессору это совсем не понятно. Как разобраться с этими трудностями? Есть два общепринятых метода. Первый — хорошо знакомый вам Z-буфер. А второй — так называемая Z-сортировка. На Z-сортировке мы подробно останавливаться не будем, так как это довольно грубый и малоэффективный метод. А вот о Z-буфере поговорим, так как геймеры с
Рис. 11. Так задается объем отсечения (clipping volume).
этим термином встречаются очень часто. Почти в каждой игре есть настройки, связанные с Z-буфером. Давайте разберемся, что это за зверь. Z-буфер — это специальная область видеопамяти. Чаще всего Z-буфер располагается во фрейм-буфере. В Z-буфере хранится значение глубины для каждого пиксела. Когда рендерится новый пиксел треугольника, его глубина сравнивается со значением, которое уже хранится в Z-буфере для точки на экране с такими же координатами X и Y, то есть с соответствующей точкой предыдущего треугольника. Когда новый пиксел “глубже”, чем значение в Z-буфере, пиксел не виден. Если значение его глубины меньше значения в Z-буфере, пиксел виден, и значение его глубины записывается в Z-буфер. В современных акселераторах часто используется W-буфер , в котором хранятся значения, обратные Z-глубине. W-координаты тем удобны, что без лишних расчетов корректно соотносятся с перспективой, тогда как для Z-координат приходится корректировать результат интерполяции координат вершин треугольников. А теперь от теории перейдем к практике.
Рис. 12. Две накладывающиеся фигуры до работы Z-буфера…
Поговорим о настройках в компьютерных играх, связанных с Z-буфером. Прежде всего, во многих играх Z-буфер можно отключить. Это не значит, что изображение превратится в беспорядочную мешанину перекрывающих друг друга полигонов. Просто вместо Z-буферизации в дело вступят упрощенные алгоритмы. С одной стороны, освободится значительный кусок фрейм-буфера, и вы сможете играть в более высоких разрешениях. Но за это придется расплачиваться появлением очень неприятного артефакта — Z-алиасинга (он же Z-алайзинг). Часто вы будете видеть, как полигоны “проваливаются” друг сквозь друга. Какое уж тут погружение в игровой мир! Некоторые игры позволяют задать глубину или разрядность Z-буфера. Чем выше разрешающая способность, тем точнее рендерятся полигоны. Вас могут попросить выбрать между 16-, 24- и 32-разрядным Z-буфером. Если выбрать буфер с малой разрядностью, может появиться Z-алиасинг, потому что порой пикселам двух треугольников будет соответствовать одна и та же Z-координата, а значит — и глубина. После того как определено, какие пикселы полигонов входят в конечный кадр, акселератор рендерит сцену. Но и тут есть хитрость. Если кто-то из вас пробовал программировать графику под Паскаль, он наверняка сталкивался с одной проблемой — мерцанием изображения. Мерцание появляется из-за того, что экран обновляется не мгновенно, а “на глазах” у игрока. Вся графика прорисовывается в реальном времени. Эта проблема осталась в наследство и ваятелям 3D. И решили они ее просто и изящно. Каждый новый кадр сначала рендерится не на экран, а в специальный буфер в видеопамяти, который называется фрейм-буфером. У фрейм-буфера два слоя. В заднем — новый
Рис. 13. … и после.
кадр, а в переднем — кадр текущий. Когда приходит время очередного рендера, содержимое этих слоев меняется местами ( swap ). В итоге на экране мы видим новый кадр, а старый кадр пересылается в задний буфер, где немедленно затирается очередным свежеотрендеренным. Этот метод получил название двойной буферизации. Уже известный вам Z-буфер часто делит жизненное видеопространство с двойным кадром во фрейм-буфере. Отсюда и некоторые сложности и взаимоуступки в их совместной работе. *** * *** Мы с вами детально, этап за этапом, рассмотрели работу акселератора и его драйверов (так как некоторые функции реализованы только программно). Некоторые моменты я намеренно опустил, потому что мы поговорим о них в следующих частях цикла. К ним относятся мультитекстурирование , альфа-смешение , туманы разного рода, мип-мэппинг , антиалиасинг , билинейная и трилинейная фильтрации и многое другое. Не поговорили мы и о некоторых “железных” особенностях акселератора, например о RAMDAC. О нем, а также об особенностях устройства и наладки акселераторов, мы поговорим в заключительной статье цикла. Надеюсь, в материале вы открыли для себя много нового и полезного. Теперь, читая в мудреном пресс-релизе о новых методах отсечения невидимых поверхностей, вы будете понимать, о чем идет речь. И если новенькая игра предложит вам выбрать разрешение Z-буфера, вы не растеряетесь.