Назад в библиотеку

Анатомия игровых движков

Авторы: Д. Чеканов

Источник: http://www.3dnews.ru/...

Введение

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

Если вы когда-либо обращали внимание на структуру игры Quake, то в ней движком является quake.exe, а игровые части - это QAGame.dll и CGame.dll. Впрочем, сейчас вам это может ни о чем не говорить. Что ж, все еще впереди.

Визуализатор (renderer)

Итак, начнем наш разговор об игровых движках с визуализатора, причем говорить мы будем с точки зрения разработчика. Фактически, во всех дальнейших частях «Анатомии...» мы будем рассматривать движок именно с такой точки зрения.

Итак, что такое визуализатор и почему он столь важен? Дело в том, что без него вы ничего на экране не увидите. Он визуализирует сцену для игрока, чтобы он смог принимать определенные решения на основе отображаемой на экране информации. Визуализатор, в общем-то, первая вещь, которую вам нужно создать при построении игрового движка. Ведь если вы ничего не видите, то как вы узнаете, что ваш код работает? На визуализатор обычно уходит более 50% ресурсов процессора, и именно за визуализатор игровых разработчиков чаще всего и критикуют. Если вы напишите его с ошибками, то через десять дней вся индустрия будет смеяться над вашими программистскими талантами, над вашей игрой и над вашей компанией. На визуализатор также очень часто смотрят продавцы, и именно его часто принимают во внимание при оценке той или иной игры. После этого вас, наверное, не сильно вдохновляет идея создания визуализатора, но без хорошего визуализатора ваша игра никогда не попадет в Top 10.

Визуализатор в наши дни должен использовать 3D ускоритель, API, трехмерные вычисления. Так что вам нужно обязательно разобраться, как же работает 3D «железо». Для приставок сейчас требуются аналогичные знания, но с приставками, по крайней мере, вам не требуется достичь постоянно меняющейся цели. Железо приставки не меняется, в отличие от ПК, на протяжении всего времени ее жизни.

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

Создание 3D мира

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

3D объекты хранятся в виде точек трехмерного мира (их еще называют вершинами), причем они связаны друг с другом определенным образом, так что компьютер знает, между какими точками проводить линии или какие точки образуют поверхность. Представьте себе коробку. Она имеет восемь точек в углах. При этом коробка состоит из шести поверхностей, каждая из них образует сторону коробки. Только что мы описали объект в трехмерном мире. Как видим, ничего трудного здесь нет. Если же мы будем рассматривать более сложные вещи, к примеру, уровень в Quake, то там используются тысячи вершин (даже ближе к сотням тысяч), и тысячи полигональных поверхностей. Выше вы видите фигуру монстра, выполненную в виде проволочного каркаса. Она очень близка к описанию коробки, которое мы дали выше. Отличие здесь так же заключается в числе вершин и количестве маленьких полигонов, из которых состоит монстр.

Формат хранения миров и моделей является функцией визуализатора, а не частью приложения/игры. Логике игры не нужно знать, как объекты представляются в памяти, или как визуализатор будет их отображать. Игра просто должна быть в курсе, что именно отображает визуализатор, в правильном ли ракурсе он отображает, и те ли он показывает модели, что нужно.

В хорошем движке всегда возможна полная замена визуализатора на новый, при этом не потребуется залезать в игровой код. Многие кросс-платформенные движки типа Unreal, и многие доморощенные приставочные игровые движки предоставляют такую возможность. К примеру, в версии игры под GameCube можно легко заменить визуализатор.

Но давайте вернемся к представлению объектов. Дело в том, что в компьютере существуют и другие способы представления точек в пространстве помимо использования координатной системы. Вы можете задать их математически, используя функцию для описания прямых или кривых линий. Затем будут вычислены полигоны, которые практически все 3D карты используют в качестве окончательных единиц (примитивов) рендеринга. Примитив - это минимальная единица рендеринга, которую вы можете использовать на графической карте, и практически все железо сейчас понимает под примитивом полигон с тремя вершинами, а проще говоря - треугольник. Новые карты ATi и nVidia позволяют вам производить математический рендеринг (с помощью поверхностей высокого порядка), но такая функция не распространена среди других карт, поэтому ее еще рано использовать в качестве базовой стратегии рендеринга.

Конечно, математический рендеринг иногда слишком дорогостоящ с точки зрения вычислений, но он интересен в качестве основы для новых и экспериментальных технологий, типа создания рельефа или смягчения краев объектов. Мы более подробно опишем поверхности высокого порядка в разделе про патчи.

Отбрасывание невидимых вершин и полигонов (culling)

Давайте представим себе мир, описанный сотнями тысяч вершин/полигонов. Скажем, вы используете вид от первого лица, которое смотрит сбоку на наш трехмерный мир. В поле зрения человека находится множество полигонов нашего мира, хотя огромное их количество человек просто не видит, поскольку другие объекты, типа стены, их закрывают. Даже лучшие игровые программисты не могут обработать 300 000 треугольников на современных 3D картах при сохранении 60 fps. Карты просто не обладают такой мощью, поэтому нам нужно приложить некоторые усилия для отбрасывания полигонов, которые человек не видит, перед передачей сцены карте. Этот процесс называется отбрасыванием невидимых вершин и полигонов (culling).

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

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

Это число обычно высчитывается при условии, что все полигоны находятся на экране, они используют одну и ту же текстуру, они одинакового размера, при этом приложение ничем не занимается, кроме как отсылкой полигонов на карту. Однако в реальных ситуациях, как вы можете догадаться, приложение параллельно занимается множеством других вещей - осуществляет 3D трансформацию полигонов, освещение полигонов, передает дополнительные текстуры в память видеокарты и т.д. При этом на карту требуется отправить не только текстуры, но и данные для каждого полигона. Некоторые новые карты позволяют вам хранить данные о геометрии модели и мира внутри памяти самой карты, но такой подход слишком дорог с точки зрения занимаемого пространства, которое обычно отводится под текстуры. К тому же в данном случае нужно удостовериться, что посланные вершины модели будут использоваться в каждом кадре, иначе вы просто впустую потратите память на карте. Но мы немного отвлеклись. Главное, что вам здесь следует понять - то, что вы читаете на коробке с вашей новой видеокартой в корне отличается от того, что вы фактически с ней получите, особенно если у вас медленный процессор или мало памяти.

Простейшие методы отбрасывания ненужной геометрии

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

Я уверен, что вы уже встречались с термином «BSP деревья» при разговорах о Doom или Quake. BSP означает Binary Space Partitioning - двоичное разделение пространства. Данный метод разделяет мир на маленькие секции и группирует полигоны мира таким образом, чтобы легко можно было определить, что видно и что не видно. Такой метод полезен для программных визуализаторов, которым не нужна прорисовка лишних геометрических объектов. Побочным следствием BSP деревьев является то, что вы легко можете определить место, где вы находитесь в мире.

Движок, использующий порталы (первым таким движком в игровом мире стал Prey от 3D Realms) строится на базе создания собственной модели каждой местности (или комнаты), при этом через двери (или порталы) в каждой секции можно видеть другую секцию. Визуализатор отрисовывает каждую секцию индивидуально, как разные сцены. Впрочем, хватит теории. Достаточно сказать, что подобная работа является просто необходимой для каждого визуализатора. Некоторые методы подпадают под «отбрасывание ненужной геометрии», некоторые нет, но все они призваны решить проблему избежания ненужной работы на достаточно ранней стадии. Что касается стрелялок от первого лица, где в поле зрения обычно находится множество треугольников, и игрок сам управляет ракурсом своего зрения, очень важно, чтобы лишние треугольники, которые он не может видеть, были отброшены. То же самое относится и к космическим симуляторам, где вы можете видеть очень далеко - там следует отбрасывать все лишнее дальше области видимости. Для игр, где ракурс фиксирован, скажем для стратегий реального времени, отбрасывание реализуется несколько проще. Часто эта часть визуализатора реализуется программно и не использует карту, но через некоторое время карта сама будет все это делать.

Простейший графический конвейер

Давайте вкратце опишем основные ступени графического конвейера, от игры до отображения полигонов.

• Игра определяет, какие объекты сейчас задействованы, какие модели они имеют, какие текстуры они используют, что с ними будет происходить дальше и где они находятся в мире. Игра также определяет местоположение камеры и ее направление.
• Игра передает информацию визуализатору. В случае моделей, визуализатор должен сначала оценить размер моделей и местоположение камеры, и затем определить, будет ли модель вообще присутствовать на экране, находится ли она слева от зрителя (от камеры), сзади, или она настолько удалена, что ее вообще не видно. Визуализатор может даже использовать некоторый способ описания мира для нахождения видимости модели (смотрите следующий пункт).
• Система визуализации мира определяет, где в мире находится камера, и какие секции/полигоны мира видны в поле зрения камеры. Все это можно осуществить многими способами, начиная от самого простого и прямого метода разделения мира на секции и описания «я могу видеть секции A, B и C из секции D» для каждой секции до более элегантных BSP деревьев. Все полигоны, проходящие через тест отсечения лишней геометрии, передаются визуализатору полигонов.
• Для каждого полигона, передавшегося на визуализатор, визуализатор осуществляет трансформацию полигона в соответствии с локальной математикой (то есть анимацией модели) и математикой мира (местоположения модели по отношению к камере). Затем полигоны исследуются на предмет наличия нелицевых полигонов (находящихся на невидимой стороне объекта). Нелицевые полигоны опять же отбрасываются. Оставшиеся полигоны освещаются в соответствии с действующими световыми источниками. Визуализатор затем смотрит на то, какие текстуры полигон использует и удостоверяется, что API/видеокарта будет использовать те же текстуры для отображения. Затем полигоны направляются на API рендеринга и затем на видеокарту.

Возможно, подобное описание слишком примитивно, но зато оно дает вам общее представление. Ниже приведено более подробное описание конвейера.

  1. Приложение/сцена
    • Прохождение по базе данных сцены/геометрии
    • Передвижение объектов, нацеливание и смещение поля зрения камеры
    • Анимационные движения моделей
    • Описание содержания 3D мира
    • Проверка видимости объекта, включая по возможности отбрасывание перекрытых полигонов
    • Выбор уровня детализации (LOD)
  2. Геометрия
    • Трансформация (поворот, трансляция, масштабирование)
    • Трансформация из пространства модели в пространство мира (Direct3D)
    • Трансформация из пространства мира в видимое пространство
    • Проекция поля зрения
    • Простейшее отбрасывание невидимых полигонов
    • Отбрасывание нелицевых полигонов (может быть сделано и позже в пространстве экрана)
    • Освещение
    • Разделение перспективы - трансформация в усеченное пространство
    • Отсечение невидимых на экране частей (в соответствии с областью видимости)
    • Трансформация в пространство экрана
  3. Настройка треугольников
    • Отбрасывание нелицевых полигонов (может быть сделано и раньше перед освещением)
    • Вычисление наклона/дельты
    • Преобразование строк развертки
  4. Рендеринг/растеризация
    • Затенение
    • Текстурирование
    • Туман
    • Проверка альфа-прозрачности
    • Буфер глубины
    • Сглаживание (опционально)
    • Отображение

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

Как только приложение передало сцену API, API начинает использовать аппаратную трансформацию и освещение (T&L), которая сейчас уже повсеместно используется в 3D картах. Мы не будем здесь подробно описывать матричные вычисления, упомянем лишь, что трансформация позволяет 3D карте отрисовывать полигоны (или что вы там хотите отрисовать) под правильным углом и в правильном месте мира относительно ракурса камеры в данный момент.

Для каждой вершины здесь производятся очень сложные операции, включая отсечение (clipping) для определения, является ли полигон действительно видимым, находится ли он за экраном или показывается лишь частично. Освещение определяет, насколько яркой должна быть текстура, в зависимости от того, как свет падает на данные вершины и под каким углом. Раньше все эти операции выполнял процессор, но современные видеоускорители переложили их большей частью на себя, так что процессор в это время может заняться другими важными делами. Конечно, все это прекрасно, но поскольку не все карты оснащены аппаратным T&L, вам все же придется писать эти процедуры самому (не забывайте, что мы опять же отражаем точку зрения разработчика).

Патчи (поверхности высокого порядка)

Помимо треугольников сейчас все чаще начинают использоваться патчи. Патчи (так называют поверхности высокого порядка) - это хорошее изобретение, поскольку они могут описать геометрию (как правило, это геометрия, состоящая из кривых) с помощью математической функции. Сравните одну функцию с кучей полигонов, где вам необходимо будет описывать еще и их позиционирование. Благодаря патчам по функции вы можете создать (и деформировать) сетку полигонов на лету, а затем решить, сколько полигонов вам на самом деле нужно получить от патча. К примеру, вам нужно описать трубу. В вашем мире огромное количество этих труб. В некоторых комнатах, где вам уже нужно отобразить 10 000 полигонов, вы можете сказать: «Хорошо, пусть у этой трубы будет 100 полигонов, поскольку в этом месте и так этих полигонов выше крыши, так что не будем жертвовать частотой кадров». Но в другой комнате, где всего 5 000 полигонов, вы скажете «Ну а теперь путь у этой трубы будет 500 полигонов, потому что мы не подошли к пределу количества полигонов на этом кадре». Все прекрасно, но не забывайте, что вам сначала нужно все это просчитать и построить сетку. А это далеко не тривиальная задача. Но с другой стороны, мы получаем хорошую экономию при отсылке функции патча по AGP против загрузки шины массой вершин для описания того же объекта. SOF2 использует патчи при построении ландшафта.

К тому же у ATi сейчас используется интересная технология TruForm, которая может преобразовать модель, построенную из треугольников, в модель на основе поверхностей высокого порядка, а затем вновь преобразовать ее в модель из треугольников, многократно увеличив их число. Затем эта модель будет отослана на конвейер для последующей обработки. ATi как раз добавила ступень TruForm перед T&L движком. Минусом здесь является то, что вы не можете контролировать, какие поверхности следует обработать, а какие - нет. Ведь некоторые грани вам все же нужно оставить четкими. К примеру, не будет ничего хорошего, если ваш нос будет плавно переходить в лицо. Однако технология достаточно перспективна, и мы наверняка увидим ее совершенствование в будущем.

Освещаем мир

Когда освещение работает, вы его почти не замечаете. Но стоит только его убрать - вы сразу же это увидите. Существует несколько подходов к освещению, начиная от простого определения ориентации полигона к источнику света и добавления процента освещения на основе расстояния от полигона до источника, и заканчивая генерацией точных карт освещения для наложения на основные текстуры. А некоторые API имеют сразу несколько встроенных методов освещения. К примеру, OpenGL предлагает освещение по полигонам, по вершинами или по пикселям.

При вершинном освещении определяется, сколько полигонов касаются одной вершины, и затем вычисляется результирующая нормаль к данной вершине (по нормалям всех примыкающих полигонов). Каждая вершина для данного полигона будет иметь несколько отличающуюся нормаль, так что вы можете проградуировать или интерполировать освещение по полигону, что приведет к более точному освещению. Преимущество такого подхода заключается в том, что «железо» может помочь выполнять освещение с помощью модуля аппаратной трансформации и освещения (T&L). Минусом является то, что при вершинном освещении не получается создавать тени. К примеру, обе руки на модели будут освещены одинаково, даже если свет падает справа, и левая рука должна находиться в тени от тела.

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

Что касается вершинного затенения (его также называют затенением по Гуро) движок рендеринга будет закрашивать уже каждую вершину нужным цветом. При отрисовке пикселя полигона его цвет будет вычисляться с помощью интерполяции цветов вершин, на основе удаленности пикселя от вершин. (Именно такой способ и используется на моделях в Quake III).

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

В различном свете

Следующий подход к освещению базируется на вторых текстурных картах (картах освещения, light map), которые смешиваются с существующей текстурой для создания эффекта освещения. Все это прекрасно, но перед визуализацией вам нужно создать карты. Если же в игре у вас используетcя динамическое освещение (то есть источник света либо передвигается, либо включается/выключается), то вам будет необходимо создавать карты освещения на каждый кадр, изменяя их при движении динамического источника. Карты освещения можно создавать довольно быстро, но они требуют памяти для хранения соответствующих текстур. Конечно, можно задействовать технологию сжатия текстур для уменьшения занимаемого пространства, или сокращать размер текстур, даже превратив их в двухцветные (но тогда вы не получите цветного освещения) и т.д. Однако если на сцене используется несколько источников динамического освещения, то создание карт освещения может ощутимо ударить по производительности.

Так что во многих играх используется гибридный подход к освещению. Quake III, к примеру, задействует карты освещения для мира и вершинное освещение для анимированных моделей. Предварительно просчитанное освещение не влияет на анимированные модели - они берут общую освещенность для всей модели от полигона, на котором стоит модель - затем на модель накладывается динамическое освещение для придания нужного эффекта. Гибридное освещение - это компромисс между производительностью и качеством, который многие люди не замечают, но который все же позволяет создать правдоподобные эффекты. Собственно, все игры и основываются на подобном принципе - создание максимально правдоподобного эффекта, пусть даже не совсем верного по своей сути. Конечно, в новом движке Doom все будет по-другому, но и сам движок требует 1 ГГц процессор и карточку не ниже GeForce2 для включения всех эффектов. Прогресс налицо, но и его цена - тоже.

Как только сцена была трансформирована и освещена, происходят операции отсечения. Если не вдаваться в детали, при отсечении определяется, какие треугольники находятся полностью внутри сцены (в поле зрения - view frustum), а какие лишь частично заходят на сцену. Треугольники, полностью находящиеся на сцене, принимаются и обрабатываются. Если треугольник захватывается лишь частично, то его часть вне поля зрения должна быть отсечена, а остающаяся внутри сцены часть должна пройти повторную тесселяцию, чтобы новый полигон полностью попал внутрь поля зрения.

Как только операция отсечения будет завершена, на следующей ступени конвейера происходит пересчет координат треугольников (его также называют scan-line conversion), когда сцена переносится на двумерное пространство координат экрана. Затем происходит непосредственно рендеринг.

Текстурирование и кратная фильтрация

Текстуры очень важны для придания 3D сценам реалистичного вида. Они представляют собой маленькие картинки, которые разбиваются на полигоны и накладываются на объект или на какое-либо место в сцене. Большое количество текстур съедает ощутимый объем памяти, поэтому к текстурам применяются различные технологии уменьшения их размера. Сжатие текстур - одна из подобных технологий, при этом сжатие позволяет сохранить необходимое количество информации. Сжатые текстуры занимают меньше места на CD с игрой, и, что более важно, в оперативной памяти и в памяти на видеокарте. Еще одним плюсом является то, что когда карта запрашивает отображение текстуры на экране в первый раз, то из оперативной памяти по шине AGP в память видеокарты отсылается сжатая (меньшая по размеру) версия текстуры, что опять же немного экономит пропускную способность. Сжатие текстур - это хорошо. Ниже мы чуть подробнее остановимся на сжатии текстур.

Кратная фильтрация (Mip-Mapping)

Еще одной технологией, используемой в игровых движках для сокращения потребления памяти и пропускной способности, является кратная фильтрация текстур. При кратной фильтрации создаются несколько копий одной и той же текстуры, причем каждая последующая копия в два раза меньше по размеру предыдущей. Зачем же это надо? Для ответа на поставленный вопрос вам нужно понимать, как 3D карта отображает текстуру. В простейшем случае вы берете текстуру, прилепляете ее к полигону и отображаете его на экран. Здесь мы получаем отношение 1:1, когда один тексель (элемент текстуры) соответствует одному пикселю полигона данного объекта. Если же полигон затем уменьшился на экране в два раза, то на экран будут выведены, скажем, только четные тексели. Обычно ничего плохого в этом нет, но часто такой подход приводит к появлению различных артефактов. Представьте себе кирпичную стену. Скажем, оригинальная текстура - это и есть кирпичная стена с множеством кирпичей, но слой цемента между ними составляет всего один пиксель. Если вы уменьшите полигон в два раза от нормального размера, то при этом будут отображаться только четные пиксели, следовательно, цементная прослойка может вовсе исчезнуть с экрана. Вот мы и получаем нежелательный артефакт.

При использовании кратной фильтрации изображение масштабируется программно, еще до отсылки видеокарте. Таким образом, его можно предварительно обработать, чтобы слой цемента не исчез. Когда 3D карта отображает полигон с текстурой на нем, она определяет масштаб и решает «давай вместо масштабирования самой большой текстуры, я возьму маленькую текстуру - она будет выглядеть лучше». В этом и заключается кратная фильтрация.

Множественные текстуры и наложение карт неровностей

Использование одной текстуры позволяет получить реалистичную 3D графику, однако использование нескольких текстур приводит к еще более потрясающим эффектам. Для этого, конечно, потребуется несколько проходов рендеринга, которые будут потреблять скорость заполнения (fill rate). Однако многоконвейерные 3D ускорители типа ATi Radeon или nVidia GeForce2 и выше позволяют наложить несколько текстур за один проход. При создании многотекстурного эффекта на полигон накладывается первая текстура, затем над ним отображается еще один полигон с другой текстурой и с установленным коэффициентом прозрачности. Благодаря этому можно создать текстуры с эффектом движения, пульсации или даже текстуры с тенями (как мы описывали в части статьи про освещение). Просто нарисуйте первую текстуру, затем нарисуйте сверху полностью черную текстуру с коэффициентом прозрачности - и вы сразу же получите эффект тени. Такая технология называется наложением карт освещения (light mapping, иногда sometimes-dark mapping), и до выхода нового Doom именно такая технология использовалась при освещении уровней в движках Id.

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

Наложение карт неровностей позволяет получить более эффектные детализированные текстуры, хотя и здесь требуется определенная ловкость рук, поскольку отображаемые неровности не изменяются относительно угла зрения пользователя. Учитывая по-пиксельные операции новейших карт ATi и nVidia этот недостаток можно устранить. Впрочем, наложение карт текстур до последнего времени не слишком часто использовалось разработчиками игр. Ситуация должна измениться - технология, как видим, полезная и интересная.

Пробуксовка кэша — это плохо

Существуют различные технологии минимизации пробуксовки кэша, собственно они и подразумеваются под эффективным управлением кэшем текстур - жизненно важным элементом ускорения движков 3D игр. Управление кэшем текстур - полезная вещь, благодаря ней карта может запрашивать текстуру из основной памяти только один раз, а не много раз. Мы как бы говорим карте: «посмотри на все эти полигоны - они используют одну и ту же текстуру, так что давай мы загрузим ее один раз, и не будем загружать ее с каждым полигоном». Благодаря управлению кэшем текстур API (или программа, стоящая за драйвером видеокарты) не будет загружать текстуру на карту более одного раза. Мощные API типа OpenGL обычно сами управляют кэшированием текстур, то есть API часто само занимается хранением текстур на видеокарте и основной памяти компьютера на основе различных критериев. Однако главные проблемы здесь заключаются в том, что: а) часто вы не знаете правила, которые использует API и б) часто в кадре нужно отобразить больше текстур, чем вмещается в память видеокарты.

Еще одной полезной технологией является сжатие текстур, которое мы уже немного затрагивали чуть выше. Текстуры сжимаются очень похожим на сжатие MP3 файлов способом, хотя о таком уровне сжатия приходится лишь мечтать. Если при сжатии WAV в MP3 мы получаем коэффициент сжатия 11:1, то при аппаратном сжатии текстур обычно получается отношение 4:1, но даже такие цифры заметно улучшают производительность. Кроме всего прочего, карта расжимает текстуры только при необходимости при отображении текстуры «на лету». Это очень хорошо, но мы лишь приоткрыли завесу над будущими технологиями.

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

Описанный процесс не приводит к эффективному использованию T&L модуля на видеокарте (если он там есть). Ведь мы получаем большое количество маленьких групп полигонов, использующих одну и ту же текстуру в разных участках экрана, причем все они базируются на различных матрицах трансформации. То есть в данном случае большее время будет потрачено на настройку аппаратного T&L движка видеокарты. Подобный способ прекрасно работает для персонажей в игре, поскольку они используют одинаковую текстуру по всей модели. Но при отрисовке мира он не является эффективным, поскольку множество полигонов будут использовать одну и ту же текстуру стены. Конечно, обычно подобная ситуация не влияет заметным образом на скорость, поскольку текстуры для мира не слишком велики, так что система кэширования в API легко с ними справится и сохранит текстуру на карте при повторном использовании.

На приставках обычно не используется никакой системы кэширования текстур (конечно, если вы ее не напишите). В случае PS2 лучшего всего использовать подход «текстура за один раз». В Xbox он не актуален, поскольку карта не обладает своей выделенной памятью (архитектура UMA), и текстуры всегда хранятся в основной памяти.

Второе наиболее часто встречающееся узкое место современных стрелялок заключается в потугах прокачать по AGP шине слишком большое количество текстур. Следующее узкое место - обработка геометрии, которая отвечает за то, чтобы объекты появились именно там, где надо. Математика, использующаяся при генерации правильных координат каждой вершины в моделях, на сегодняшний момент отнимает больше всего вычислительных ресурсов 3D стрелялок. Так что вам следует постоянно следить за бюджетом текстур, если вы не хотите, чтобы ваша AGP карта была перегружена. Эффективным шагом будет снижение ограничение уровня кратной фильтрации по максимальному размеру, благодаря чему вы сможете уполовинить размер передаваемых по карте текстур. Конечно, визуальное качество ухудшится, особенно в заставках, зато частота кадров ощутимо возрастет. Такой подход очень хорош для игр по сети. И Soldier of Fortune II, и Jedi Knight II: Outcast используют ограничение кратной фильтрации, то есть они рассчитаны на будущие ускорители, которые пока лишь появляются на рынке. Для того чтобы эти игры работали с нормальным размером текстур, вам нужно минимум 128 Мб памяти на вашем видеоускорителе.

Эффективное использование памяти

Давайте поговорим о том, как память на 3D карте используется сегодня, и какие улучшения нас ждут в будущем. Большинство 3D ускорителей сегодня работают с 32-битным цветом, где 8 бит отводятся на красный, 8 на зеленый, 8 на синий и 8 на атрибут прозрачности для любого пикселя. Такая палитра позволяет получить 256 оттенков для красного, синего и зеленого, что в совокупности дает 16,7 млн цветов - это именно то число цветов, которое человек способен вообще различить на мониторе. Почему же гуру в области дизайна игр Джон Кармак ратует за переход к 64-битному цвету? Если мы не можем увидеть разницы, то зачем нам нужно увеличивать число цветов? Дело здесь совершенно в другом. Предположим, что у нас есть модель, которая освещена несколькими источниками разного цвета. Мы берем оригинальный цвет модели, а затем просчитываем один источника света, который изменяет значение цвета. Затем мы просчитываем еще один источник, который тоже изменяет значение цвета. Здесь и выявляется проблема - мы можем оперировать только 8-ю битами, и после наложения 4-х цветов 8 бит недостаточно для приемлемого представления получившегося света. При этом мы становимся свидетелями ошибок квантизации, которые являются ошибками округления при недостаточном числе битов. То есть вам уже очень скоро будет недоставать битов, и при этом цветовая информация будет теряться. Если бы на каждый цвет отводилось 16 или 32 бита, то можно было бы повысить цветовое разрешение. И даже после наложения оттенка за оттенком мы бы получили адекватное финальное значение цвета. Однако подобное увеличение битов быстро приводит к увеличению потребляемой памяти.

Не следует забывать, что большая часть памяти карты отводится под хранение текстур. Каждый 3D ускоритель несет ограниченное количество памяти, часть которой должна быть отведена под "передний" и "задний" буферы, z-буфер, ну и конечно, текстуры. Если на первой карте Voodoo1 было 2 Мб памяти, то на Riva TNT количество было увеличено до 16 Мб. Затем GeForce2 и ATi Rage получили 32 Мб, а теперь мы стали свидетелями перехода от 64 Мб к 128 Мб. Почему большой объем памяти так важен? Что ж, давайте немножко посчитаем.

Предположим, ваша игра будет работать в 32-битном цвете при разрешении 1280x1024 с 32-битным Z-буфером, поскольку вы желаете получить максимальное визуальное качество. Хорошо, тогда у нас выходит 4 байта на пиксель для экрана, плюс 4 байта на пиксель для Z-буфера, поскольку и там и там размер пикселя составляет 32 бита. При разрешении 1280x1024 мы имеем 1 310 720 пикселей. Умножим на 8 (суммарное число байт для каждого пикселя "переднего" буфера и Z-буфера) и получим 10 485 760 байт. Добавим "задний" буфер и мы получаем 1280x1024x12 байт, или 15 728 640 байт, что равно 15 Мбайт. Если бы на нашем ускорителе было 16 Мб памяти, то под текстуры у нас остался бы жалкий 1 Мб. Если сами текстуры тоже 32-битные, что сегодня является общепринятой практикой, то в 1 Мб мы можем хранить 1 Мб / 4 байта = 262 144 пикселя. Это примерно 4 текстуры размером 256x256.

Приведенный пример ясно показывает, что старые 16 Мб карты не соответствуют требованиям сегодняшних игр. Нам просто придется перезагружать текстуры для каждого кадра в карту. Именно для этого и была предназначена AGP шина, однако AGP все-таки медленнее кадрового буфера 3D карты, поэтому вы получите ощутимое падение производительности. Очевидно, если вы понизите цветовое разрешение текстур до 16-ти бит, то вы сможете передавать их в два раза быстрее по AGP шине. Также если вы уменьшите и экранное цветовое разрешение, то на вашей карте освободится дополнительное место для кеширования текстур. Но вы никогда не сможете определить, как пользователь настроил свою систему. Если их карта может работать на высоком разрешении и глубине цвета, то, скорее всего, именно они и выставлены.

Наведем туман

Теперь давайте перейдем к туману, который является визуальным эффектом. Большинство современных движков поддерживают туман, поскольку он бывает очень полезен для сокрытия мира на удалении, так что вы не видите, как модели и география сцены внезапно появляются на горизонте при попадании в зону видимости. Существует также и объемный туман (volumetric fogging). В данном случае он не служит для сокрытия горизонта, а является физическим объектом, который вы можете видеть, проходить сквозь него или обходить стороной. При этом визуальные эффекты от тумана будут меняться. Подумайте о путешествии сквозь облако - тогда вы сразу поймете, что такое объемный туман. Хорошим примером реализации объемного тумана являются несколько уровней Quake III или версия Lucas Arts Rogue Squadron II для Game Cube. Как нам кажется, там используются одни из самых реалистичных туманов.

При разговоре о тумане неплохо бы упомянуть про альфа-тестирование и альфа-сопряжение (alpha blending) текстур. Когда визуализатор отображает какой-либо пиксель на экране, при условии прохождения пикселем теста Z-буфера (описано ниже), мы должны осуществить альфа-тестирование. При этом мы можем обнаружить, что пиксель должен быть прозрачным, дабы показать, что за ним скрывается. Тогда мы должны знать значение пикселя, который находится за текущим, и далее нам следует обработать цвета двух пикселей и вывести на экран уже результирующий пиксель. Такая операция проходит по сценарию "чтение-модификация-запись", и она потребляет намного больше времени, чем обычная запись пикселя.

Существует несколько алгоритмов сопряжения (смешения) пикселей. Прямое альфа-сопряжение добавляет некоторый процент "теневого" пикселя к оставшимся процентам от нового пикселя. Добавочное альфа-сопряжение берет процент "старого" пикселя и просто добавляет его к новому пикселю (не учитывая процентное соотношение). Последний способ дает более яркие эффекты (к примеру, эффект светового меча Кайла в Jedi Knight II).

С каждым новым поколением 3D ускорителей, мы получаем новые и более сложные виды альфа-сопряжения, которые позволяют создавать умопомрачительные эффекты. Ну и конечно, с учетом пиксельных программ (шейдеров) в GF3+4 и последних платах Radeon, пределом здесь может стать только ваша фантазия.

Трафаретное затенение и проверка глубины

Трафаретное затенение (stencil shadowing) открывает перед нами новые возможности. Мы не будем вдаваться в детали (данная тема заслуживает отдельной статьи), но суть трафаретного затенения заключается в просчете вида модели с перспективы источника света, а затем в использовании полученного результата для создания полигона с результирующей текстурой на нужном предмете. То есть вы, фактически, создаете объемное освещение, которое "падает" на остальные полигоны. В результате мы получаем реалистичное освещение, которое имеет свою перспективу. Но такое освещение потребляет существенные вычислительные ресурсы, поскольку здесь требуется создавать текстуру "на лету" и делать несколько проходов рендеринга для одной сцены. Вы можете создавать тени множеством способов, и, как это обычно бывает, результирующее качество зависит от задействованных вычислительных ресурсов. Здесь разделяют так называемые "четкие" и "мягкие" тени, причем последние более предпочтительны, поскольку они аккуратнее моделируют способ появления теней в реальном мире. К тому же существует несколько других "достаточно хороших" способов отображения теней, которыми пользуются разработчики игр.

Проверка глубины

Итак, мы подошли к проверке глубины, при которой закрытые пиксели будут отбрасываться, чтобы избежать избыточной отрисовки (overdraw). Понятие избыточной отрисовки достаточно просто формулируется - чем больше раз вы выводите один и тот же пиксель в кадре, тем больше избыточная отрисовка. Это число зависит от количества элементов на 3D сцене в измерении по глубине (оси Z). Если избыточная отрисовка происходит слишком часто, например, при создании сияющих эффектов заклинаний, как в Heretic II, то частота кадров будет заметно уменьшаться. Некоторые визуальные эффекты в Heretic II, когда несколько людей на экране начинали творить заклинания друг на друга, приводили к ситуациям, в которых один пиксель мог отрисовываться на экране 40 раз! Тут и к гадалке не ходи - необходимо изменить подобную ситуацию, особенно в программном визуализаторе, который просто не может справиться с такой загрузкой, не превратив игру в слайд-шоу. Проверка глубины - это технология, которая используется для определения перекрытия одних объектов другими в данном положении пикселя, чтобы избежать избыточной отрисовки.

Посмотрите на данную сцену и подумайте о том, что вы видите. Другими словами, какие из объектов друг друга перекрывают? На этот вопрос как раз и отвечает проверка глубины.

Достаточно просто объяснить, почему проверка глубины увеличивает частоту кадров. Представьте себе детальную сцену, где находится множество полигонов (или пикселей) друг за другом, и пусть до передачи сцены визуализатору не существует быстрого способа отбрасывания перекрытых полигонов. При сортировке непрозрачных полигонов по оси Z, когда ближние к экрану полигоны будут отображены первыми, вы заполняете сцену самыми близкими к вам пикселями. Когда вы начинаете отображать пиксели за существующими (как определяет проверка глубины), их можно легко отбросить, экономя время и ресурсы. Если бы вы отображали объекты на экране наоборот, от самых дальних к самым ближним, то все перекрытые объекты полностью бы отрисовывались, после чего их бы перекрывали другие объекты. Чем сложнее сцена, тем хуже была бы ситуация. Так что от проверки глубины есть реальная польза.

Сглаживание

Давайте вкратце поговорим про сглаживание. При отрисовке отдельного полигона, 3D карта проверит уже созданную сцену, и "замажет" границы нового полигона, чтобы вы не увидели на экране характерных "лесенок". Технология реализуется двумя способами. Первый подход - на уровне отдельных полигонов, который требует отображения полигонов в порядке от задних к передним, чтобы каждый полигон смог правильно "смазаться" с теми полигонами, что находятся позади его. При беспорядочном рендеринге вы получаете различные артефакты. Второй подход заключается в рендеринге целого кадра в более высоком разрешении, чем нужно, и затем уменьшении изображения для устранения "лесенок" при масштабировании. Второй подход дает хороший результат, но он сильно сказывается на потреблении памяти и на ее пропускной способности, поскольку карте нужно обработать больше пикселей, чем на итоговом кадре. Большинство новых видеоускорителей хорошо справляются с такой работой, причем вы можете выбрать различные режимы сглаживания по отношению качество/производительность.

Вершинные и пиксельные программы (шейдеры)

Перед завершением разговора про технологии рендеринга, давайте немного остановимся на вершинных и пиксельных программах, поскольку в последнее время они привлекают достаточно много к себе внимания. Вершинные программы позволяют напрямую задействовать аппаратные возможности карты, без сильного упора на API. К примеру, если карта имеет аппаратный модуль T&L, вы можете написать DirectX или OpenGL код и надеяться, что вершины пройдут через T&L модуль (удостовериться в этом нельзя, поскольку все происходит на уровне драйвера), или вы можете обратиться к железу и использовать вершинную программу-шейдер напрямую. Благодаря вершинным программам вы можете писать код, напрямую использующий функции карты - T&L модуль и другие функции, предлагаемые видеоускорителем. Фактически подобная возможность уже существует в современном поколении карт ATi и nVidia.

К сожалению, механизм доступа к вершинным программам у них не одинаков. Вы не можете просто написать код вершинной программы и запускать ее на любой карте, как это осуществлялось с кодом OpenGL и DirectX. Однако, поскольку программа работает напрямую с "железом", вы можете рассчитывать на быстрый рендеринг всевозможных эффектов. (Равно как и создание новых потрясающих эффектов - вы можете делать то, что не позволяет API). Фактически вершинные программы переносят 3D карты обратно в мир приставок, где используется прямой доступ к оборудованию, и разработчики выжимают максимум из системы, а не просто уповают на API. Некоторых программистов такой подход шокирует, но таков прогресс. Вообще, вершинные программы - это процедуры, использующиеся для просчета и реализации вершинных эффектов перед передачей их на карту для рендеринга. У вас есть выбор - вы можете выполнить такие же процедуры программно на центральном процессоре или использовать вершинные программы на карте. Трансформация скелета анимированной модели - это основная задача для вершинных программ.

Пиксельные программы (шейдеры) - это процедуры, которые применяются к каждому пикселю при рендеринге текстуры. Вы можете отказаться использовать встроенные режимы сопряжения пикселей и заставить карту применять для этого вашу программу. Благодаря этому вы можете получить новые интересные пиксельные эффекты, типа расфокусировки текстур при удалении, добавлении теплового марева или внутреннего отражения воды.

Поскольку ATi и nVidia пришли к соглашению по версиям пиксельных программ (и DX9 вводит новый язык программирования), будет неудивительно, если DirectX и OpenGL пойдут по пути Glide - с него просто начинать, но он отнюдь не является лучшим способом выжимать максимум ресурсов из карты. В любом случае, будет интересно проследить за развитием ситуации.

В завершение...

Итак, визуализатор - эта та часть игры, за которую программистов сильнее всего ругают. Визуальная привлекательность много значит в игровом бизнесе, так что ее всегда необходимо учитывать. Одним из самых плохих факторов для программистов визуализатора является скорость, с которой развивается индустрия 3D карт. Сегодня вы стараетесь получить картинку с нормально работающей прозрачностью, а завтра nVidia проводит презентацию по вершинному программированию. Все очень быстро меняется, и по большей части тот код, что был написан для 3D карт за последние 4 года, теперь можно выкидывать - он требует полной переработки. Даже Джон Кармак упомянул, что код, написанный им четыре года назад и выжимающий максимум из 3D карты, теперь просто банален - поэтому он решил полностью перерабатывать визуализатор для каждого нового проекта Id. Тим Свини из Epic согласен с Джоном:

"Мы потратили целых 9 месяцев на замену кода визуализатора. Первый Unreal опирался на полностью программный рендеринг, далее он был расширен до аппаратного рендеринга. Следующее поколение движка разрабатывается под GeForce и лучшие карты, в нем обрабатывается в 100 раз больше полигонов, чем в Unreal Tournament. Мы полностью заменили визуализатор. К счастью, наш движок модульный и мы смогли не ворошить остальную его часть - редактор, физику, AI, сеть, - хотя мы их, конечно же, тоже улучшаем.

API: плохо это или хорошо?

Итак, что же такое API? Это прикладной интерфейс программирования, который является прослойкой между программой и драйвером карты. К примеру, почти каждая 3D карта сегодня по-разному реализует трехмерность. Однако все карты предоставляют разработчику единый интерфейс, что позволяет писать одинаковый код для всех карт. По крайней мере, в теории. Почти три года назад это было верно, но сегодня положение на рынке изменилось, и в лидеры вышла nVidia.

Что касается ПК, то вы можете создать свой собственный программный растеризатор, который будет использовать центральный процессор для отрисовки спрайтов, полигонов и частиц - и люди до сих пор так и делают. Age of Empires II: Age of Kings - это пример хорошего программного растеризатора, точно так же, как и Unreal. Затем вы можете использовать один из двух возможных графических API, OpenGL или DirectX. OpenGL - это кросс-платформенный API (программы, написанные под него, будут работать под Linux, Windows и MacOS), он существует уже достаточно большой период времени, его хорошо знают и понимают, но и его возраст тоже начинает ставить свои барьеры. Еще несколько лет назад над функциональностью OpenGL драйвера работали все производители карт. Однако, как только функциональность была достигнута, перед разработчиками далее не стояло четкого пути для развития, поэтому каждый стал дополнять драйвер своей функциональностью, используя расширения OpenGL.

3dfx создала T-буфер. nVidia озадачилась аппаратной трансформацией и освещением. Matrox пошла в направлении наложения карт неровностей. И так далее. Как видим, за несколько лет в 3D графике произошли существенные изменения.

В любом случае, вторым возможным выбором является DirectX. Он полностью контролируется Microsoft, и по понятным причинам он существует только на ПК с Windows и Xbox. Никаких версий под Apple или Linux. Поскольку Microsoft контролирует DirectX, он встроен во все современные версии Windows.

Основное отличие между DirectX и OpenGL заключается в том, что последний является собственностью "сообщества", а первый - Microsoft. Если вы хотите, чтобы DirectX поддерживал новые функции вашей 3D карты, то вам нужно обращаться к Microsoft, надеяться, что компания к вам прислушается и ждать новой версии DirectX. Что касается OpenGL, то поскольку производитель карты поставляет OpenGL драйвер в комплекте, вы можете сразу же получить доступ к функциям карты через OpenGL расширения. Все, конечно, хорошо, но с точки зрения разработчика игры, вы не можете надеяться на распространенность расширений при написании кода. И хотя расширения могут увеличить скорость на 50%, вы не можете потребовать от каждого пользователя установить GeForce3 в свой компьютер. Нет, конечно, можете, но в следующем году вы будете сушить сухари.

Мы упустили различные детали, но суть ситуации вы должны представлять себе достаточно четко. С DirectX вы всегда знаете, чего вы получите от карты в данный момент времени, так как если функция аппаратно на карте не реализована, DirectX будет программно ее эмулировать (не всегда эффективно, поскольку в ряде случаев такой подход сильно замедляет игру, но мы сейчас говорим не об этом). С OpenGL вы можете выждать из карты больше, однако вы не будете уверены, присутствуют ли на пользовательской машине нужные вам функции.