Трехмерная графика для геймера

Posted by Anuriel on сентября 30, 2004 - 00:00

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

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

Цифры перед буквой "D"
Так уж исторически сложилось, что наша с вами среда обитания представляет собой трехмерное пространство. Это означает, что каждая его точка может быть однозначно задана координатами по ширине (x), высоте (y) и глубине (z) — т.е. она существует в трех измерениях. В действительности, конечно, можно назвать как минимум еще одно измерение — время, но в нашем случае, для описания технологии построения одного кадра, оно несущественно.
В зависимости от количества измерений, которые в итоге проецируются на плоский (т.е. двумерный) экран монитора, различают двух- и трехмерную графику. Для краткости это обозначают как "2D" или "3D", где "D" — первая буква английского слова dimension (измерение). Часто можно услышать и про "2.5D графику": в зависимости от контекста, так могут называть либо не совсем честную реализацию 3D-графики (в играх на движке Build, например Duke Nukem 3D, Blood и др.), либо графику в изометрической проекции ("вид сверху-сбоку", как в Diablo, Fallout и т.д.).
В отличие от двухмерной графики, где все действие происходит в двух измерениях (высота и ширина, оси x и y), трехмерная графика просчитывается в трех измерениях, а картинку приходится все-таки отображать на старых добрых двумерных (т.е. плоских) мониторах. И для того чтобы сделать ваш монитор окном в трехмерный (и на вид уже почти настоящий) мир, программам и "железу" приходится выполнить воистину огромное количество различных действий. От частного будем двигаться к общему, и поэтому сперва немного поговорим о физических принципах формирования изображения на экране.

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

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

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

Мы наш, мы новый мир построим. Трехмерный.
Если вы когда-нибудь что-нибудь рисовали (или хотя бы смотрели на чужие рисунки), то наверняка замечали, что перспектива, правильные тени и некоторые другие трюки (четкие близкие объекты и размытые дальние) делают изображение куда реалистичнее. И в самом деле: если предмет отбрасывает тень, а объекты в кадре уменьшаются с увеличением "расстояния" до них (в итоге скрываясь в некоторой точке исчезновения), то они подсознательно кажутся нам "настоящими". Что же нам нужно сделать, чтобы показать трехмерный мир в плоском окне монитора? Для начала необходимо его создать; затем определить видимую игроком его часть и эту часть красиво изобразить на плоскости с учетом вышеупомянутых хитростей (правильное освещение, тени, перспектива и т.д.) Однако это просто лишь на словах — на деле каждый из этапов весьма и весьма непрост.

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

Что нужно сделать визуализатору? Первым делом, разобраться, что же рисовать на экране: перевести координаты объектов в поле зрения игрока из "мировых" (связанных с глобальной системой отсчета) в локальные (т.е. для системы отсчета, связанной с экраном). Затем он должен определить, какие полигоны перекрывают друг друга (не видны) и не тратить время на их отрисовку. Когда "нужные" полигоны определены, визуализатор их воспроизводит, освещает и натягивает соответствующие текстуры. Освещение сцены проходит в соответствии с заданными при создании мира источниками (или с использованием динамических — вроде фонарика в руке игрового персонажа). Затем информация о полигонах и их закраске в кадре отправляется через графический API на видеокарту, которая "материализует" картинку на мониторе, в полной мере задействуя графический процессор (GPU в новых картах).

Графический конвейер
Давайте поближе познакомимся с устройством типичного графического движка.
Что же должен сделать движок для отображения одного кадра на экране? Первым делом, оценить местоположение камеры в игровом мире. Затем он должен составить список объектов, находящихся в поле зрения камеры, по возможности отбрасывая перекрытые другими (т.е. невидимые) полигоны и учитывая фазу движения (анимации) каждого из подвижных объектов. Первая стадия работы движка заканчивается определением уровня детализации и выбором соответствующего размера текстур — чем выше уровень детализации, тем больше разрешение текстур. Работы первой стадии выполняются самим движком и графическим API.

Вторая, весьма трудоемкая в плане вычислений стадия заключается в работе с геометрией. В первую очередь под этим понимается двойное преобразование координат: сначала из мировой системы координат в систему, связанную с камерой, а впоследствии из этой системы — в систему координат экрана. Различное вращение, масштабирование и трансляция данных из одной системы координат в другую — вот основные занятия геометрической части движка. За всем этим стоят матричные преобразования из линейной алгебры: для трансформации вектор вершины 1х4 (x,y,z,w, где w — коэффициент масштабирования при перспективной коррекции) банально умножается на специальную матрицу трансформаций. После выполнения трансформаций происходит отбрасывание "задних" (опять-таки, невидимых) частей полигонов. Затем геометрический движок переходит к работе над освещением: выполняется расчет расстояния между источниками света и объектами, а также направления света. И, наконец, — определение и отсечение не попадающих в область видимости частей полигонов.

На третьей стадии графического конвейера, т.е. процесса создания кадра, визуализатором (renderer) производится окончательный расчет изображения. На основе информации, полученной с двух предыдущих стадий (освещенность, эффекты, текстуры), рассчитывается цвет каждого пикселя на экране.
Происходит затенение, текстурирование и проверка прозрачности (в соответствии с ней меняется цвет пикселей), добавляется туман (если нужно), отрабатывают пиксельные шейдеры (в случае их наличия), картинка фильтруется и сглаживается (если включены соответствующие опции). Только после этого полученное изображение выводится на экран — наконец-то! А ведь таких кадров движку нужно "нарисовать" хотя бы 30-60 в секунду! Учитывая, что средняя сложность сцены сегодня составляет десятки и сотни тысяч полигонов со множеством источников света, объем вычислений, думаю, вы можете представить сами.

История трехмерного ускорения
В старые добрые славные времена видеоадаптер занимался лишь тем, что принимал созданный процессором кадр, сохранял его в своей небольшой по объему видеопамяти (256Кб — 1MB) и отправлял на монитор. С увеличением количества цветов на мониторе, появлением графического интерфейса Windows и прочих новинок прогресса, видеоадаптеры превратились в графические акселераторы. Ускоряли они, в основном, прорисовку окон, курсора и других двухмерных мелочей, все же немного облегчая работу тогдашних процессоров. Да и объем памяти подрос — до 4MB. Так и жили бы мы с вами, наверное, в старом добром 2D, не тужили, слыхом не слыхивая о всяких FX5900 и X800, кабы не компания 3Dfx, представившая в далеком 1996-м ускоритель трехмерной графики Voodoo Graphics. Сия карточка соединялась специальным кабелем с обычным видеоакселератором и "включалась" лишь при работе в 3D-режиме, занимаясь исключительно визуализацией (рендерингом). Само собой, скорость вывода тогда была смешной — 50 мегатекселей в секунду против нынешних 8320 у ATi Radeon X800 XT. За восемь лет, как видите, "грубая сила" видеоакселераторов возросла в 166 раз! Вместе с первой 3D-платой появился и специализированный API — Glide3D. Достаточно быстрый и мощный, из-за своей закрытости (с ним могли работать лишь карты 3Dfx) он вскоре проиграл OpenGL и Direct3D, и теперь вместе с самой 3Dfx покоится на свалке истории.

Качество изображения на первом трехмерном ускорителе оставляло желать лучшего: в случае трехмерной графики видеосигнал проходил лишнее соединительное звено (2D-ускоритель) и лишь затем попадал в монитор. Очевидно, что для улучшения качества изображения нужно было совместить в одной карте возможности 3D и 2D ускорителей, что и было проделано 3Dfx — новая плата компании называлась Voodoo Rush. Она выглядела монстроузно, работала не слишком быстро и широкого распространения не получила. В то же время первый залп дала и nVidia, представившая чип NV1 (платы Diamond Edge 3D): первый блин вышел ужасным комом, и если бы не NV3 (кодовое название — Riva128), на рынке ускорителей все могло бы сложится совсем иначе... Но, тем не менее, NV3 вышел и стал достаточно популярен (благодаря хорошему качеству в 2D, высокой скорости в Direct3D и полноценному видеовходу/выходу (VIVO)). Однако с выходом 3Dfx Voodoo2, nVidia осталась не у дел — чип 3Dfx демонстрировал отличную производительность и в Direct3D, и в OpenGL, и в Glide3D. Последнее было особенно важным — большинство тогдашних игр "затачивались" именно под API от 3Dfx. Новый акселератор, вследствие прогрессивной архитектуры, умел накладывать по две текстуры за проход (мультитекстурирование), совершать трилинейную фильтрацию и краевое сглаживание. В общем, чудо-карточка. Кроме того, два Vodoo2 можно было объединить в режиме SLI (Scan Line Interleave — черезстрочная развертка): каждый акселератор в таком случае обрабатывал свою половину изображения (четные или нечетные строки), благодаря чему производительность увеличивалась почти двукратно!

Voodoo Banshee объединил на одной карте часть мощи Voodoo2 (мультитекстурирования не было) и двухмерный акселератор, став достаточно привлекательной покупкой: неплохие 3D- и 2D-возможности по приятной цене. Однако благополучие почивавшей на лаврах 3Dfx буквально подорвала nVidia своим революционным чипом Riva TNT. ТNT здесь означает не тринитротолуол, а всего лишь TwiN-Texel (возможность наложения двух текстур за проход), но звучит все равно угрожающе. Карты на базе TNT поддерживали 32-битный цвет, а благодаря Twin-Texel могли работать вдвое быстрее в обычном режиме (при наложении одной текстуры на полигон). Все больше появлялось качественных Direct3D-игр, все сильнее виделись различия между 16-битным и 32-битным цветом. Робкий дебют ATi на рынке игровых видеоускорителей не смог стать громким — представленный в конце 1998 чип Rage128 из-за кривых драйверов и отсутствия новаторских технологий так и не смог конкурировать с nVidia Riva TNT и 3Dfx Voodoo 2/Banshee. После слияния с компанией STB Systems 3Dfx начала производить видеокарты под собственным лейблом, и это стало началом конца. Voodoo 3 была ограничена максимальным объемом памяти (16MB) и не умела работать в 32-битном цвете, а карты последующих семейств (Voodoo4 и Voodoo5) на базе чипа VSA-100 в достаточных количествах так толком и не появились на прилавках. В результате 3Dfx была куплена nVidia в конце 2000 года. Однако радоваться последней было рано — на горизонте замаячил новый, опасный и серьезный соперник. Конечно, были и S3 со своим Savage4, и Intel с i740, но по мощности TNT2 легко оставлял позади обоих конкурентов. Другая компания, название которой сейчас знает любой более-менее опытный игрок, весьма успешно начала ставить nVidia палки в колеса...

Через год после анонса Rage128 канадская фирма ATi представила вполне конкурентоспособный Rage 128 Pro, а вслед за анонсом первого в мире GPU nVidia GeForce 256 — мощный и недорогой GPU Radeon. И гонка началась: на GeForce 2 ATi ответила Radeon 7200, а в пику GeForce 3, явившим миру чудо пиксельных шейдеров, вышел весьма удачный Radeon 8500 (9100). Потом "девятитысячники" от ATi сражались с четырех- и пятитысячниками nVidia, правда, с переменным успехом. Традиционно карты ATi были чуть быстрее и чуть дешевле (хоть и не во всех секторах), зато nVidia выгодно отличало качество драйверов. В предпоследнем поколении акселераторов FX5950 Ultra в целом проигрывал Radeon 9800XT, однако новейшие карты теперь демонстрируют паритет по части производительности: X800 XT примерно равен по скорости GeForce 6800 Ultra. Причем, первого выгодно отличает невысокое (по сравнению с конкурентом) энергопотребление. Очевидно, что при таком раскладе все будут решать цены и маркетинг. Поживем-увидим, кто кого!

Про RGB
Цвета люминофорных элементов на экране (красный (Red), зеленый (Green) и синий (Blue)) взяты не с потолка — они образуют цветовую схему RGB. При сложении этих трех цветов различной интенсивности можно получать любые оттенки. В частности, при использовании 32-битного кодирования цвета (32 бита на канал), интенсивность каждого цветового канала можно описать 256-ю значениями, а общее число оттенков в таком случае составит 256х256х256 = 16777216, т.е. 16,7 миллионов. В схеме RGB легко кодируется любой цвет. К примеру, (0,0,0) — код черного цвета (пушки не работают), (255,0,255) означает пурпурный (на полную работают "синяя" и "красная"), ну и (255,255,255), при котором все три пушки работают с максимальной интенсивностью, является кодом белого цвета. Монитор сам излучает свет, поэтому цвета с него мы воспринимаем немного иначе, чем, например, с газеты: там, как и для других объектов, которые мы рассматриваем в отраженном свете, применяется CMYK-модель (Cyan, Magenta, Yellow, blacK — голубой, пурпурный, желтый, черный). Она, в отличие от аддитивной модели RGB, является субстративной (вычитаемой): значения интенсивностей каждого из четырех цветов там вычитаются друг из друга (черный цвет — интенсивности всех каналов по максимуму, белый — по минимуму).

Трехмерная графика для геймера

Трехмерная графика для геймера

Краткий словарь 3D-терминов
Битовое изображение (bitmap, битовая карта) — растровое изображение, т.е. состоящее из множества точек-пикселей, имеющих атрибут цвета.

Видеокарта (синонимы: графический ускоритель, 3D-акселератор, трехмерный ускоритель, видеоускоритель) — карта расширения, имеющая в своем составе чип для ускорения специфических операций при работе с 2D- и 3D-графикой. Все современные 3D-ускорители являются одновременно и 2D-ускорителями. Видеокарта (трехмерный ускоритель) характеризуется следующими параметрами: фирмой-производителем, используемым графическим чипом, объемом видеопамяти, разрядностью ("шириной") шины памяти и наличием дополнительных функций (вывод на второй монитор, DVI-выход для ЖКИ-мониторов, ТВ-тюнер, видеовход/выход VIVO и т.д.)

Графический движок — часть кода программы (игры), отвечающая за создание и вывод изображения на экран.

Драйвер — программа, непосредственно взаимодействующая с аппаратной частью видеокарты с одной стороны и API/другими программами — с другой.

Трехмерная графика для геймераЗатенение (shading) — вычисление цвета каждой точки полигона в зависимости от действующих источников освещения. Существует плоское затенение (flat shading), затенение методом Гуро (Gouraud shading) и наиболее реалистичное попиксельное затенение (метод Фонга, Phong shading). В случае плоского затенения каждый полигон окрашивается каким-либо одним цветом. Цвет определяется в зависимости от угла между направлением(-ями) на источник(-и) света и нормалью (перпендикуляром) к поверхности полигона. Для реализации затенения по Гуро строятся нормали ко всем вершинам полигона. Их цвет определяется в зависимости от угла между направлением(-ями) на источник(-и) света и нормалью. Цвета отдельных пикселей полигонов интерполируются (рассчитываются) как средние от цветов вершин. Для затенения по Фонгу нормали строятся для всех пикселей полигона, и в зависимости от угла между нормалью и направлением(-ями) на источник(-и) света определяется их цвет. Такое затенение выглядит наиболее эффектно и правдоподобно, а посему теперь применяется повсеместно.

Трехмерная графика для геймера

Пиксель (pixel, сокр. от "picture element") — мельчайший различимый элемент экрана (точка), или мельчайший элемент визуализированного 3D-изображения.

Полигон (polygon, т.е. многоугольник)
— плоская геометрическая фигура. Используются в трехмерной графике для создания каркасных моделей. Чаще всего в качестве полигонов используются трех-, четырех- и пятиугольники. Для получения реалистичной картинки полигоны впоследствии обтягиваются текстурами. Чем больше полигонов используется для создания модели, тем более "настоящей" она выглядит.

Трехмерная графика для геймера

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

Тексель (texel, сокр. от "texture element")
— мельчайший элемент текстуры.

Текстура (texture)
— двухмерное, битовое изображение. Обычно хранится в памяти видеоакселератора или компьютера в сжатом виде. "Натягивается" на полигоны, придавая им более реальный вид.

API (Application Programming Interface)
— программный интерфейс приложения. Своеобразный "мост" между программой и видеокартой (а точнее, ее драйвером), набор стандартизированных инструментов для реализации возможностей видеокарты. За счет использования API в программе и его поддержки видеокартой достигается аппаратная независимость: любая видеокарта, поддерживающая некоторый API, "поймет" написанный под него код. Наиболее популярные API для работы с трехмерной графикой — это OpenGL (Silicon Graphics) и Direct3D (Microsoft). Некогда популярный Glide3D умер вместе с родителем (3Dfx).

GPU (Graphics Processing Unit)
— графический процессор. Термин, введенный nVidia для карт семейства GeForce 256 (и всех последующих). Эти карты умели производить аппаратную трансформацию и освещение (hardware transform & lighting — читайте о нем в следующем номере), что позволяло серьезнейшим образом разгрузить основной процессор (CPU) и сделать реальными ранее недоступные эффекты. GPU максимально оптимизирован для выполнения узкого (но все время расширяющегося) круга графических задач, поэтому его производительность в них значительно выше, нежели у обычного процессора. По сложности архитектуры современные GPU (вроде Ge-Force6 и X800) превосходят обыкновенные CPU.

Продолжение следует

За помощь в подготовке 3D-моделей для материала огромное спасибо Павлу "PavAks" Аксенову

Николай "Nickky" Щетько

№ 57