Трехмерная графика, 3D-акселераторы, видеоускорители — кто из наших читателей не слышал этих слов? Но многие ли знают, как пара специализированных микросхем превращает гору цифр — исходную сцену — в реалистичную плоскую картинку, кажущуюся нам достоверной проекцией объемного мира? Не претендуя на полноту охвата и глубину изложения, попробуем обрисовать этот не такой уж и сложный процесс, а заодно разобраться с сопутствующей терминологией.
Современные трехмерные ускорители работают с так называемой полигональной графикой, то есть любой объект представляется как набор плоских многоугольников (Практически всегда эти многоугольники рано (еще в драйверах) или поздно (в видеокарте) разбиваются на простейшие треугольники. На то есть много причин — удобство работы, ограниченные возможности оборудования, но главная — большинству алгоритмов закраски изображения нужно, чтобы полигоны были плоскими, то есть чтобы все их вершины лежали в одной плоскости. А для треугольников это требование выполняется автоматически). Объект задается вершинами, определяющими ключевые точки, и полигонами, которые образованы линиями, соединяющими вершины (рис. 1). Цвет на полигоны накладывается по специальным алгоритмам — как правило, с использованием заранее нарисованных плоских изображений — текстур. Процесс этот называется закраской. А задача 3D-акселератора сводится к тому, чтобы за заданный промежуток времени нарисовать и закрасить как можно больше полигонов (в новейших играх на экране одновременно присутствуют несколько сотен тысяч треугольников, формирующих сцену).
Для отображения треугольников используется математический аппарат так называемых однородных координат. Не вдаваясь в подробности — требуется один раз рассчитать пару матриц 4x4 (матрицы преобразования и проектирования), после чего расчет положения любой точки трехмерной сцены на плоском экране сведется к умножению вектора исходных координат на эти матрицы — всего-то 32 умножений, 24 сложений и 3 деления! У современного процессора на эти операции уйдет 20–40 тактов (За один такт можно выполнить сразу несколько операций), а если задействовать векторные операции (например, SSE/2, которые позволяют работать с векторами длины 4), то и вовсе хватит 8–10 тактов (для сравнения: одно обращение к оперативной памяти занимает от 100 до 350 тактов процессора). Это, конечно, без учета «накладных расходов», но все же очевидно, что процессор с рабочей частотой под 3 ГГц (3x109) запросто может просчитать за секунду координаты десятка миллионов (107) точек — очень много даже по меркам современных игр (в оперативной памяти эти точки займут не меньше 60–120 Мбайт). К сожалению, картинка из одних лишь вершин (и даже вершин, соединенных ребрами) удовлетворит далеко не всех пользователей. Но именно с нее все и начиналось (рис. 2).
Проекция геометрической модели сферы, приближаемой по большим кругам 32-угольниками [2]. В ней — тысячи граней и примерно столько же вершин. Картинки 4 и 6 рассчитаны чисто геометрическими методами (сортировка треугольников), без привлечения ресурсов 3D-ускорителя и использования Z-буфера. 200 тысяч треугольников, образующих график [6], процессор AMD Athlon XP 2500+ обсчитывает со скоростью около 3–5 fps без каких-либо оптимизаций.
Итак, изобразить «геометрию» сцены не составляет особого труда — акселератор для этого не нужен. Трудности начинаются, когда мы хотим сцену закрасить. Если на проекции полигонов просто-напросто наложить один тон, на экране получится что-то совершенно невразумительное (рис. 3). Если же присвоить каждому полигону свой цвет (и при этом, например, учесть освещенность объекта), то мы увидим (Здесь есть еще «проблема удаления невидимых поверхностей» — когда нужно определить, какой из перекрывающихся полигонов к нам «ближе» (и потому должен рисоваться поверх собрата). Существуют чисто геометрические подходы к ее решению (например, тайловый подход, tiling), но чаще всего эту стадию совмещают с закраской (техника буфера глубины, Z-buffer)) уже нормальный объемный объект (рис. 4). И хотя таким способом можно получать очень красивые изображения (рис. 6), это все же не то, чего мы хотим добиться .
Радикально решает проблему использование текстур — картинок, «натянутых» на полигоны. Точнее, для каждой вершины указываются некоторые ее плоские текстурные координаты в двухмерном (Еще бывают одномерные, трехмерные и даже четырех-пятимерные текстуры — иногда с ними удобнее работать. Последние, правда, используются крайне редко) изображении-текстуре. Если нужно посчитать цвет конкретной точки полигона, мы смотрим на ее расположение относительно трех остальных точек треугольника и ищем аналогичную по расположению относительно «текстурных» вершин точку в текстуре. Ее цвет и будет цветом нашей точки (рис. 5).
К сожалению, в этом случае для каждой точки экрана, попавшей в треугольник сцены, нужно найти текстурные координаты (а это не слишком быстрый процесс) и провести так называемую выборку из текстуры — вычислить цвет текстуры в полученной точке. Последняя задача не так проста, как кажется: чаще всего расчетная точка получается не целочисленной и попадает «между пикселами» изображения. В итоге расчеты получаются гораздо более трудоемкими, нежели «прямолинейное» геометрическое преобразование, да и выполнять их приходится чаще — в полигон может попасть не одна сотня точек, и для каждой из них может потребоваться не одна текстура. На экране может присутствовать всего сотня треугольников, но, чтобы обеспечить жалкий десяток кадров в секунду в разрешении 1024x768, графическому процессору все равно придется рассчитывать как минимум 7–8 миллионов точек в секунду! Вдобавок текстуры зачастую являются очень большими изображениями и, в отличие от ограниченного набора геометрической информации, в кэш-память процессора не помещаются, вынуждая его непрерывно обращаться к не столь быстрой оперативной памяти. Таким образом, закраска становится «бутылочным горлышком» при отрисовке сцены, и именно для аппаратного ускорения соответствующих операций были созданы первые 3D-акселераторы.