Выбрать главу

Что мы имеем: львиная доля создателей игр выбирает DirectX за удобную поддержку современных возможностей, остальные предпочитают OpenGL - за переносимость и простоту написания программ. Ситуация в общем-то нормальная, если вспомнить, для чего создавалась каждая из технологий. Но если мы обратим внимание, что Doom III, самая нашумевшая игра последнего времени, написана на OpenGL, то поневоле закрадывается подозрение, что не все так просто. Может быть, в id Software знают какой-то секрет, делающий применение OpenGL предпочтительнее? Нет, просто команда разработчиков уже привыкла работать на нем. Это еще раз красноречиво говорит о том, что GL мало чем уступает DirectX.

Я много говорил о возможностях современного железа, но ни разу не упомянул о том, что же это за возможности и что для их использования предоставляют OpenGL и DirectX. Прежде всего, нынешние видеокарты - это программируемые устройства. Теперь некоторый код можно выполнять прямо на графическом процессоре (GPU). Эти программы называют шейдерами. Они позволяют создавать сложнейшие эффекты в реальном времени. Изначально эти программы можно было писать только на «фирменных» языках производителей видеокарт - ATI и nVidia. Оба языка очень похожи на ассемблер, но, увы, несовместимы. А кому захочется писать два разных кода для реализации одного и того же эффекта? И вот в DirectX 8.0 появляется универсальный язык программирования шейдеров. Он тоже похож на ассемблер, но позволяет обойтись одним шейдером для обоих типов видеокарт. И снова закавыка: много ли людей любят программировать на языках низкого уровня? И вот Microsoft представляет в своем DX 9.0 уже C-подобный язык HLSL. ARB в ответ выкатил аналогичную технологию GLSL. Как уже говорилось, обе эти технологии далеки от совершенства, прежде всего из-за слабой оптимизации компиляторов. Например, создатели игры Chronicles of Riddick (кстати, использовавшие OpenGL) для увеличения производительности написали все шейдеры в двух вариантах, на родных ассемблероподобных языках от ATI и nVidia. Это выглядит странным, так как обычные компиляторы показывают совсем неплохие результаты.

Итак, у нас две библиотеки с практически одинаковыми возможностями и быстродействием. Отличия лишь в сложности написания кода. И даже здесь явного лидерства нет. Все зависит от уровня используемых в программе эффектов. Если нам достаточно базовой функциональности OpenGL (то есть мы не используем расширений), то разумнее использовать именно GL, а если дело дошло до продвинутых эффектов, тут предпочтительнее Direct3D. Но с появлением второго GL ситуация может в корне измениться, так как переход на новую версию избавит GL от большинства недостатков. Но Microsoft тоже не будет стоять на месте, и вполне возможно, что к моменту своего выхода OGL 2.0 уже устареет. Да и в дальнейшем для успешной конкуренции с DX ARB понадобится существенно переработать механизм внесения изменений в библиотеку, чтобы сделать OpenGL более динамично развивающимся.

Так что же выбрать начинающему 3D-программисту? Молодому поколению я, скрепя сердце, посоветую DirectX. Прежде всего потому, что знание DX поможет стать создателем игр. Все российские компании, разрабатывающие игры, используют эту технологию. А вот совет программистам с опытом не так однозначен. Чтобы ваша программа работала не только на Windows, но и на других операционных системах, следует выбрать GL. Далее следует решить, какие эффекты вам необходимы: можете ли вы обойтись стандартной функциональностью GL, или вам непременно нужно использовать более «продвинутые» эффекты. И, наконец, на сцену выходят личные предпочтения: одним придется по вкусу объектная структура DirectX, другим же она покажется слишком громоздкой. И, конечно, не стоит забывать старый анекдот: «Какой дистрибутив Linux ставить? Тот, который стоит у ближайшего гуру».

Простейшая программа на OpenGL и на Direct3D

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

Для начала очистим фон. В OpenGL для этого потребуется написать следующую строку:

glClear(GL_COLOR_BUFFER_BIT);

где glClear() - функция, предназначенная для очистки буферов, а константа GL_COLOR_BUFFER_BIT указывает, что именно нужно очистить.

Для аналогичной операции в D3D нужно написать

d3d_Device-›Clear (0, 0, D3DCLEAR_TARGET, D3DCOLOR_XRGB (0, 0, 0), 0, 0);

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

Вот рабочее пространство и готово, можно приступать к рисованию. В OGL все примитивы для обрисовки должны находиться между вызовами glBegin() и glEnd(), некоторым их аналогом в D3D являются d3d_Device-›BeginScene () и d3d_Device-›EndScene (). В случае с OGL уже можно привести фрагмент кода, выполняющий нашу задачу:

glBegin(GL_TRIANGLES);

glColor3d(1,0,0);

glVertex3d(1,2,3);

glColor3d(0,1,0);

glVertex3d(4,5,6);

glColor3d(0,0,1);

glVertex3d(7,8,9);

glVertex3d - это вершины нашего треугольника, Название функции интуитивно понятно, но 3d, как ни странно, не означает «трехмерный»; точнее, 3 - как раз означает, а вот d - это тип аргументов (double). GL_TRIANGLES указывает, что все последующие вершины будут последовательно образовывать треугольники (если бы у нас было не три, а шесть вершин - получилось бы два треугольника). glColor3d принимает на вход цветовые компоненты RGB, и все вершины, идущие после него и до следующего вызова glColor, будут иметь соответствующий цвет.

В D3D перед рисованием необходимо подготовить специальную структуру данных:

struct CUSTOMVERTEX { FLOAT x, y, z, rhw; DWORD color;};

CUSTOMVERTEX g_Vertices[] =

{1, 2, 3, 1, 0xffff0000},

{4, 5, 6, 1, 0xff00ff00},

{7, 8, 9, 1, 0xff0000ff},

LPDIRECT3DVERTEXBUFFER8 p_VertexBuffer = NULL;

Эта структура содержит и координаты вершин, и их цвета. p_VertexBuffer - указатель на нашу структуру, для хранения вершин.

Теперь напишем следующее:

d3d_Device-›CreateVertexBuffer (3*sizeof(CUSTOMVERTEX),

0, D3DFVF_XYZRHW | D3DFVF_DIFFUSE, D3DPOOL_DEFAULT, amp;p_VertexBuffer);

VOID* pVertices;

p_VertexBuffer-›Lock (0, sizeof(g_Vertices), (BYTE**) amp;pVertices, 0);

memcpy (pVertices, g_Vertices, sizeof(g_Vertices));

p_VertexBuffer-›Unlock();

CreateVertexBuffer - выделяет место на устройстве под три вершины (3*sizeof(CUSTOMVERTEX),) и присваивает его нашему указателю (p_VertexBuffer). Далее «запираем» (Lock()) буфер вершин, чтобы случайно его не повредить. Копируем весь наш буфер (memcpy()) в буфер видеокарты. И снова разрешаем доступ (Unlock();).

И наконец, мы готовы к рисованию:

d3d_Device-›BeginScene ();

d3d_Device-›SetVertexShader (D3DFVF_CUSTOMVERTEX);

d3d_Device-›SetStreamSource (0, p_VertexBuffer, sizeof(CUSTOMVERTEX));

d3d_Device-›DrawPrimitive (D3DFVF_XYZRHW | D3DFVF_DIFFUSE, 0, 1);