Наконец-то мы закончили рассмотрение C++ и создали заготовку программы использующую WinAPI. С сегодняшнего дня мы приступаем ко второму этапу - к изучению DirectX API (API - интерфейс программироваия приложений), который предоставляет DirectX SDK.

В ближайшие месяцы мы познакомимся с базовыми возможностями DirectX API. После этого уже можно будет попытаться сделать простенькую игру.

Хочу сказать сразу, то что мы будем изучать - не сложно, а очень очень сложно! От вас потребуются недюжинные усилия. Придётся хорошо потрудиться чтобы всё усвоить. Но поверьте, результат этого стоит!

Поехали!
COM

DirectX основан на модели COM (Component Object Model - компонентная модель объектов). В COM все классы наследуют от одного базового класса IUnknown. У этого класса есть три метода. Соответственно все производные классы (в том числе и те, которые входят в состав DirectX API) наследуют возможности IUnknown. Мы пока будем пользоваться только одним методом этого класса Release. Этот метод освобождает память, которую занимал ваш объект. Например, у нас есть указатель на объект IDirect3D (это базовый класс Direct3D) - d3d. Чтобы освободить память, нужно вызвать метод Release:

d3d->Release();
Запомните: использовать delete с объектами DirectX нельзя!.

Мы не будем пока подробно останавливаться на COM. Создавать COM объекты довольно сложно. Но вот пользоваться COM легко. Все объекты DirectX являются объектами COM.

Классы COM называются интерфейсами. Перед каждым именем интерфейса стоит буква I: IDirect3DDevice9, IDirect3DVertexBuffer9. Вначале вам может показаться что интерфейсов очень много. Но это не так. В Direct3D всего около двадцати интерфейсов (классов).

Заметьте, что операция new не используется при создании объектов (экземпляров интерфейсов). Дело в том, что в базовом классе IUnknown есть специальный метод, который позволяет получить любой интерфейс COM. Как это происходит, останется за рамками сегодняшнего урока. Вообще говоря, как таковыми возможностями COM мы пользоваться не будем (кроме метода Release) - разработчики DirectX API спрятали всё что связано с COM, взамен предоставив удобные методы интерфейсов.

Ещё кое-что насчёт имён интерфейсов: в разных учебных материалах вам может встретиться такой код:

IDirect3DVolumeTexture9* vm;
PDIRECT3DVOLUMETEXTURE9 vm;
LPDIRECT3DVOLUMETEXTURE9 vm;
И так для каждого интерфейса. Все три строки делают одно и то же - создают указатель на IDirect3DVolumeTexture. Просто в двух последних строках используется переопределение IDirect3DVolumeTexture9* с помощью typedef. Мы всегда будем пользоваться первым вариант
ом.

И ещё одно важное замечание: все интерфейсы (объекты) создаются как указатели! Почему так, мы ещё обсудим.

DirectX 9 vs. DirectX 10 и DirectX 11

Мы будем изучать DirectX 9. В DirectX 10 и DirectX 11 очень много отличий. Там всё устроено немножко по другому. Десятую версию мы скорее всего вообще не будем изучать. Возможно сразу перейдём на 11. Почему же всё-таки мы будем изучать DirectX 9? Первое: DirectX 9 уже прошёл проверку временем - 6 лет это не шутки. Второе: большинство пользователей Windows до сих пор использует Windows XP, а с этой операционной системы не получится запустить приложения созданные с помощью DirectX 10 и DirectX 11. И наоборот, в Windows Vista и Windows 7 можно запустить программы использующие DirectX 9.

Из чего же, из чего же сделан Direct3D

DirectX API состоит из нескольких частей. Сейчас нас интересует часть, которая отвечает за графику. В DirectX за графику отвечает Direct3D.

Модуль Direct3D отвечает в DirectX за трёхмерную графику. В DirectX нет модуля для вывода двухмерной графики. Двухмерная графика выводится средствами Direct3D. Раньше существовал отдельны модуль DirectDraw. А в одиннадцатую версию собираются включать его реинкарнацию Direct2D.

Итак, читайте и запоминайте! Direct3D состоит из следующих частей:

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

Главным интерфейсом Direct3D является IDirect3DDevice9. На основе этого интерфейса строится вся программа. В IDirect3DDevice9 больше ста методов. В других интерфейсах методов намного меньше. Бывает по три метода.

Ещё раз: интерфейсы - это обычные классы с набором методов.

Следующая составная часть Direct3D - функции. То есть эти функции не привязаны ни к какому интерфейсу. Функций немного - ровно четыре штуки.

Самая главная функция - Direct3DCreate9. Данная функция позволяет создать объект IDirect3D9 (базовый интерфейс, который начинает запускать программу). Данная функция позволяет не влезать во внутренности COM. Если бы её не было, то пришлось бы воспользоваться средствами COM для получения интерфейса IDirect3D9.

Следующая часть - макросы. Макросы это такие функции, которые определены с помощью define. Например:

#define D3DCOLOR_ARGB(a,r,g,b) ((D3DCOLOR)((((a)&0xff)<<24)|(((r)&0xff)<<16)|(((g)&0xff)<<8)|((b)&0xff)))

Мы ещё их подробно разберём. Макросов всего штук 15.

Дальше идут структуры - штук пятьдесят. Например: D3DCOLOR, D3DCAPS9. Кстати, не все структуры Direct3D являются структурами в привычном нам понимани этого слова. Та же D3DCOLOR определена вот так:

typedef DWORD D3DCOLOR; // DWORD - это unsigned long
Но при этом хранит четыре значения. Как? Мы ещё разберёмся. (Узнать о механизме получения отдельных битов в стандартных типах можно в уроке из раздела алгоритмы Битовые шкалы)

Следующая часть - перечисления. Тоже около пятидесяти. Например: D3DFORMAT.

И последняя часть Direct3D - конcтанты. Сколько их, я затрудняюсь ответить. Скорей всего, больше двухсот. Примеры: D3DFVF_XYZ. D3DUSAGE_DEPTHSTENCIL.

Вот и всё! Если вы внимательно следили, то могли заметить, что в именах интерфейсов и функций, заглавные буквы используются в начале слов, а сами слова записываются без пробелов. Это относится и к методам интерфейсов.

А имена всего остального в DirectX API: структур, констант, макросов и перечислений - состоят только из заглавных букв, а слова разделяются символом подчёркивания _.

При разборе методов мы будем пользоваться стандартной формой записи:

IDirect3DPixelShader9::GetDevice
Напомню что эта запись обозначает: здесь идёт речь о методе GetDevice интерфейса (класса) IDirect3DPixelShader9.

На этом можно было бы закрывать сайт - самое главное-то я уже рассказал. Но как обычно, дьявол прячется в деталях.

Добавление библиотечных файлов к проекту

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

Создайте проект и напишите каркас программы под Windows.

Добавление библиотек DirectX к проекту

Теперь необходимо сообщить вашему проекту, что вы будете пользоваться библиотеками DirectX. Пока что мы будем пользоваться одной - d3d9.lib.

Выбираем пункт меню Проект -> имя_проекта Свойства или нажимаем (Alt+F7).

В открывшемся окне, в левой части раскрываем Configuration Properties (Свойства конфигурации). Затем раскрвываем узел Linker (Компоновщик) и выбираем пункт Input (Ввод).

В правой части окна в пункте Additional Dependecies (Дополнительные зависимости) впишите d3d9.lib. Всё! Жмём OK. Вот картинка, для наглядности:
Фото

Не забывайте добавлять необходимые библиотеки в каждый проект. Как я уже писал, пока что нам нужна только библиотека d3d9.lib, которая отвечает за Direct3D в DirectX 9.

Инициализация Direct3D

Теперь рассмотрим, что конкретно нужно сделать, чтобы запустить приложение использующее Direct3D.

Сначала создаётся интерфейс IDirect3D9. Этот объект представляет Direct3D. Он содержит свойства видеокарт, которые установлены на компьютере. Кроме того, его главная задача - получить указатель на интерфейс IDirect3DDevice9 (создать устройство). Делается это с помощью метода IDirect3D9::CreateDevice.

Перед тем как создавать устройство, необходимо заполнить структуру D3DPRESENT_PARAMETERS. Эта структура содержит свойства окна, в которое будет осуществляться вывод.

Устройство может быть как физическим (непосредственно видеокарта), так и программным (тогда вся функциональность Direct3D обеспечивается центральным процессором). Программные устройства используются в основном при отладке/создании программы. Эти устройства медленны. Но они могут выполнить любую возможность Direct3D.

Аппаратные устройства, наоборот, очень быстрые. Но старые видеокарты могут не поддерживать какие-нибудь возможности Direct3D (например, шейдеры третьей версии и выше. У меня, кстати, такая видеокарта была до 2008, помню с Titan Quest'ом совсем замучался). Если же мы хотим протестировать эти возможности, то придётся создавать программное устройство.

После создания устройства можно начинать рисовать.

Перед выводом каждого кадра в окно, окно необходимо очищать от содержимого предыдущего кадра. После очистки окна можно приступать к выводу графики текущего кадра.

Поверхности (Surfaces) в Direct3D

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

Параметры основной и фоновой поверхностей задаются в D3DPRESENT_PARAMETERS. Т.е. Direct3D автоматически создаёт две поверхности.

Подробнее о поверхностях в следующем уроке.
Выход из программы

Перед тем как завершить выполнение программы, необходимо "отпустить" интерфейсы (с помощью методов Release). В самом простом случае необходимо освободить два интерфейса: IDirect3D9 и IDirect3DDevice9.

На сегодня всё.