Загрузка...
 
Печать

DirectX Graphics. Начало работы

  • Является одним из компонентов DirectX (версии 8 и более поздних), отвечающим за вывод на экран 2D и 3D-графики.1

Прежде чем начать работать с DirectX Graphics, его необходимо грамотно инициализировать. До появления DirectX 8 за отрисовку 2D-графики отвечал специальный компонент DirectDraw. В версии 8 компоненты Direct3D и DirectDraw объединили в один - DirectX Graphics.1

Закрыть
noteОбрати внимание

В более ранних версиях DirectX компонент DirectX Graphics состоял из двух отдельных компонентов: Direct3D и DirectDraw.
Несмотря на это, в литературе и в исходных кодах примеров можно встретить множество отсылок к Direct3D. Множество "ушей" в DirectX Graphics растёт именно из этого, казалось бы, преданного забвению, компонента. Поэтому разом забыть о Direct3D вряд ли получится.

С выходом DirectX 8 даже ветеранам игрокодинга потребовалось время для ознакомления со всеми прибамбасами нового API.
Перед началом работы (в нашем случае) с DirectX 8 всегда проделывай следующие шаги:

  • Включай в топовый заголовочный файл директиву #include D3D8.H.
  • Добавляй в Решение/Проект файл библиотеки D3D8.LIB.
  • Указывай корректные пути к каталогам включения (include) и библиотечных файлов (LIB) DirectX SDK при создании каждого нового Решения/Проекта.

DirectX SDK 8 можно взять здесь: http://old-dos.ru/index.php?page=files&mode=files&do=show&id=2629(external link)
Весь процесс неплохо расписан в статьях:

Эти два файла являются основными (и часто единственными), которые требуются для создания простых приложений под DirectX Graphics. При компиляции они автоматически "подтянут" в Решение/Проект другие (вспомогательные) файлы из каталога DirectX SDK. Это нормально.

Компоненты Direct3D (DirectX 8)

Конструктивно Direct3D состоит из нескольких COM-объектов, у каждого из которых своё назначение. Так объект IDirect3D8 применяется для общего контроля графической системы, а IDirect3DDevice8 применяется для контроля процесса вывода графики на экран. Вот далеко неполный их список:

Компонент Описание
IDirect3D8 Применяется для сбора информации о графическом адаптере и настройке его программных интерфейсов.
IDirect3DDevice8 Имеет дело напрямую с 3D-ускорителем графического адаптера. Рендерит графику, управляет графическими ресурсами, создаёт и назначает рендер-стейты (render states) и фильтры затенения (shade filters) + делает много чего ещё.
IDirect3DVertexBuffer8 Содержит массив информации о вершинах, используемых для построения полигонов.
IDirect3DTexture8 Применяется для хранения всех изображений, используемых для окрашивания граней (faces) 2D и 3D-объектов.

Инициализация Direct3D (DirectX 8)

Вот основные 4 шага:

  1. Назначаем интерфейс Direct3D.
  2. Выбираем режим дисплея (display mode).
  3. Назначаем метод презентации (presentation method).
  4. Создаём интерфейс устройства (device interface) и инициализируем экран.

Назначаем интерфейс Direct3D (Obtaining the Direct3D Interface)

Здесь мы инициализируем объект IDirect3D8. Для этого есть специальная функция Direct3DCreate8:

IDirect3D8 *Direct3DCreate8(UINT SDKVersion);  // Версия DirectX SDK

Да, в единственном параметре указываем версию DirectX SDK. При успешном выполнении функция возвращает значение - указатель на только что созданный объект IDirect3D8. В случае неудачи возвращается NULL.
Вот пример её применения:

IDirect3D g_D3D;  // Глобальный IDirect3D8 объект
if((g_D3D = Direct3DCreate8(D3D_SDK_VERSION)) == NULL)
{
 // Завершить работу, выдав ошибку.
}

Выбираем режима дисплея (Selecting a Display Mode)

После того, как объект IDirect3D создан, можно приступать к сбору информации о графической системе компьютера. В частности создаётся список видеорежимов (display modes), поддерживаемых Direct3D. Можно также запросить инфу о текущем видеорежиме экрана.
Каждый видеорежим состоит из трёх компонентов:

  • Разрешение экрана (ширина (width) и высота (height) в пикселях)
  • Глубина цвета (color depth; число цветов, одновременно отображаемое на экране)
  • Частота обновления (refresh rate) экрана.

Например, ты выбрал видеорежим с разрешением 640х480, глубиной цвета 16 бит и частотой обновления видеоадаптера по умолчанию.
Вся инфа о видеорежиме содержится в специальной структуре D3DDISPLAYMODE:

Структура D3DDISPLAYMODE
typdef struct _D3DDISPLAYMODE
{
 UINT Width;  // Ширина экрана в пикселях.
 UINT Height;  // Высота экрана в пикселях.
 UINT RefreshRate;  // Частота обновления (0 - частота обновления по умолчанию).
 D3DFORMAT Format;  // Формат цветности.
} D3DDISPLAYMODE;

Если с разрешением и частотой обновления всё ясно, то формат цветности рассмотрим подробнее.

Режимы цветности (Color modes)
В графических редакторах обычно можно выбирать число бит на пиксель (16, 24, 32) при сохранении изображения. Чем больше бит на пиксель ты используешь, тем больше цветов можно одновременно отобразить и тем больше для этого потребуется памяти. При этом чаще всего обращаются к т.н. режимам цветности (color modes), называемым по кол-ву бит на каждый из цветовых компонентов (red, green, blue и иногда alpha-канал). Например, допустим, я выбрал режим цветности 16 бит (16-bit color mode): 5 бит на красный (red), 5 на зелёный (green), 5 на синий (blue) и 1 бит - альфа-канал (обычно это канал прозрачности). Имя в своём распоряжении 5 бит, каждый цветовой компонент может сохранить до 32 своих оттенков. Альфа-канал здесь располагает всего одним битом (т.е. вкл. и выкл.).
При рендеринге в реальном рендеринге мы не обращаемся ко всей 16-битной палитре, а указываем число бит на каждый компонент (1 alpha, 5 red, 5 green, 5 blue). Стандартным принят цветовой режим 555 (5 бит на красный, 5 на зелёный, 5 на синий), 565 (5 бит на красный, 6 на зелёный, 5 на синий) и 888 (по 8 бит на каждый цветовой компонент). Обрати внимание, что альфа-канал здесь вообще не фигурирует, т.к. используется далеко не всегда.
Direct3D определяет эти режимы цветности в виде специальных макросов, которые на деле представляют собой обычные перечисления (массивы типа enum). Вот наиболее часто применяемые:
Таблица 1. Макросы цветовых режимов Direct3D (Direct3D Color Mode Macros)
IDirect3DDevice8 применяется для контроля процесса вывода графики на экран. Вот далеко неполный их список:

Значение Описание формата
D3DFMT_R8G8B8 (24-bit) 8 бит на красный, 8 на зелёный и 8 на синий компонент.
D3DFMT_A8R8G8B8 (32-bit) 8 бит на альфа-канал, 8 на красный, 8 на зелёный и 8 на синий компонент.
D3DFMT_X8R8G8B8 (32-bit) 8 бит на неиспользуемый (unused) канал, 8 на красный, 8 на зелёный и 8 на синий компонент.
D3DFMT_R5G6B5 (16-bit) 5 бит на красный, 6 на зелёный и 5 на синий компонент.
D3DFMT_X1R5G5B5 (16-bit) 1 бит на неиспользуемый (unused) канал, 5 бит на красный, 5 на зелёный и 5 на синий компонент.
D3DFMT_A1R5G5B5 (16-bit) 1 бит на альфа-канал, 5 бит на красный, 5 на зелёный и 5 на синий компонент.


Вот пример инициализации структуры D3DDISPLAYMODE, устанавливающей видеорежим с разрешением 640х480 и использующий формат цветности D3DFMT_R5G6B5:

D3DDISPLAYMODE d3ddm;
d3ddm.Width   = 640;
d3ddm.Height  = 480;
d3ddm.RefreshRate = 0;  // Используем то, которое стоит по умолчанию
d3ddm.format  = D3DFMT_R5G6B5;

Для проверки того, поддерживается ли выбранный цветовой формат текущим видеоадаптером, надо просто заполнить структуру D3DDISPLAYMODE необходимой инфой и вызвать функцию CheckDeviceType:

// g_pD3D - предварительно проинициализированный объект Direct3D
// d3dmm - предварительно проинициализированная структура D3DDISPLAYMODE

// Проверяем, существует ли выбранный видеорежим.
if(FAILED(m_pD3D->CheckDeviceType(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &d3dmm, &d3dmm, FALSE)))
{
 // Ошибка. Данный режим цветности не поддерживается видеоадаптером.
}

Закрыть
noteОбрати внимание

Ключевые слова FAILED и SUCCEDED являются макросами и соответствуют возвращаемым Direct3D значениям типа HRESULT. Почти все функции Direct3D в случае успеха возвращают значение D3D_OK. В случае неудачи - любое другое значение.

Закрыть
noteОбрати внимание

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

Закрыть
noteОбрати внимание

Сбор информации о поддерживаемых видеорежимах делают только для полноэкранного режима (fullscreen mode).


Часто в игру дополнительно встраивают поддержку оконного режима (windowed mode). Такие игры напоминают обычные оконные приложения: всё действо происходит в стандартном окне Windows. В этом случае Direct3D заполняет информацию о текущем видеорежиме автоматически. В коде это выглядит так:

// g_pD3D - предварительно проинициализированный объект Direct3D
D3DDISPLAYMODE d3ddm;
if(FAILED(g_pD3D->GetDisplayMode(D3DADAPTER_DEFAULT, &d3dmm)))
{
 // Ошибка.
}

В случае успешного выполнения вышеуказанный вызов метода IDirect3D8::GetDisplayMode возвращает корректную структуру D3DDISPLAYMODE.

Назначаем метод презентации (Setting the Presentation Method)

Следующий шаг - указать Direct3D, каким образом представить отрендеренную графику игроку. Будет ли это оконный (windowed) режим или полноэкранный (fullscreen) или будем сначала выводит в бэкбуфер (backbuffer) а затем свопить? Какую частоту обновления (refresh rate) использовать? Вся эта информация (и многое другое) содержится в структуре D3DPRESENT_PARAMETERS:

Структура D3DPRESENT_PARAMETERS
typedef struct _D3DPRESENT_PARAMETERS
{
 UINT BackBufferWidth;  // Ширина бэкбуфера.
 UINT BackBufferHeight;  // Высота бэкбуфера.
 D3DFORMAT BackBufferFormat;  // То же самое, что и формат видеорежима (display mode format)
 UINT BackBufferCount;  // Обычно 1
 D3DMULTISAMPLE_TYPE MultiSampleType // Тип мультисэмплирования. Обычно 0.
 D3DSWAPEFFECT SwapEffect;  // Как будут сменяться бэкбуферы (swap - это тот же flip) и выводиться на экран.
 HWND hDeviceWindow;  // NULL
 BOOL Windowed;  // TRUE - оконный режим. FALSE - полноэкранный режим.
 BOOL EnableAutoDepthStencil;  // FALSE
 D3DFORMAT AutoDepthStencilFormat;  // 0
 DWORD Flags;  // 0
 UINT FullScreen_RefreshRateHz;  // 0
 UINT FullScreen_PresentationInterval;  // 0
} D3DPRESENT_PARAMETERS;

Здесь многие параметры можно тупо выставить в 0 (или NULL) или пропустить. Тем не менее, элементы, связанные с бэкбуферами, надо знать и уметь в них разбираться.

Рис.1 Рендеринг в бэкбуфер невидим для пользователя до тех пор, пока не произойдёт флип
Рис.1 Рендеринг в бэкбуфер невидим для пользователя до тех пор, пока не произойдёт флип

Бэкбуфер (Backbuffer)

С методом презентации тесно связано понятие бэкбуфера.
Для вывода изображения на экран Direct3D использует так называемые бэкбуферы (от англ. "back buffer" - задний буфер, закадровый буфер) - специальные области в памяти, где готовится очередой кадр изображения. Обычно есть два буфера:

  • передний (front buffer)
  • задний (back buffer; иногда их бывает несколько).

Передний буфер - это кадр который ты видишь на экране в определенный момент времени. Всё выглядит так, как будто изображение напрямую выводится на экран. Однако графический конвейер DirectX рендерит изображение не сразу на экран, а в задний буфер (back buffer) - внеэкранную текстуру, поверхность для рисования, размещаемую в памяти компьютера (её размеры всегда равны размеру экрана монитора или разрешению экрана), на которой происходят все операции по прорисовке кадра. (Та же технология применяется и в OpenGL.) Именно поэтому бэкбуфер часто называют "целью рендеринга по умолчанию" графического конвейера (default render target; в последних версиях DirectX render target может указывать не на бэкбуфер). Когда все команды рендеринга данного кадра в бэкбуфере закончились, вызывается специальная команда Flip (от англ. "перевернуть", быстро поменять местами; в последних версиях DirectX для этого используют функцию Present), которая просто копирует содержимое заднего буфера в передний. И всё то, что рисовали в задний буфер, моментально оказывается в переднем буфере, то есть на экране (см. Рис.1). Содержимое экрана - наоборот, помещается в бэкбуфер. (Впрочем, далеко не всегда.) Таким образом DirectX формирует изображение не напрямую на экран, а "рисует" в специальную текстуру (расположенную в бэкбуфере, в памяти), а затем эта текстура в полностью готовом виде выводится на экран. И так со скоростью 30-60 раз в секунду! Это сделано для того, чтобы движущаяся картинка на экране всегда оставалась плавной, чтобы зритель (игрок) не видел как на экране строится сцена. Ввиду такой колоссальной скорости в большинстве видеокарт работа с бэкбуферами изображения реализована на аппаратном уровне, что, безусловно, увеличивает быстродействие и качество выводимой картинки.

Закрыть
noteОбрати внимание

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

Рис.2 Использование бэкбуферов для вывода сцены на экран
Рис.2 Использование бэкбуферов для вывода сцены на экран

Так вот, чаще всего полноэкранные приложения используют 2 бэкбуфера (т.н. двойная буферизация) - 1 передний и 1 задний буфер. Оконное приложение (даже игра под Direct3D!) имеет в своём распоряжении всего 1 бэкбуфер (т.е. изображение рендерится напрямую в область видимости). При двойной буферизации в то время как Direc3D рендерит текущий кадр в один из бэкбуферов (с точки зрения Direct3D они вообще не подразделяются на передний и задний), другой бэкбуфер (который содержит последний кадр, "отрисованный" на нём) показывается на экране. Как только Direct3D закончит рендеринг во внеэкранный бэкбуфер, он быстро меняет бэкбуферы местами, выводя на экран только что отрендеренный кадр (см. Рис.2). Этот процесс продолжается на протяжении всей жизни приложения (по крайней мере, пока в рендеринге участвует Direct3D). Если приложение работает на скорости 40 fps, то это означает, что Direct3D рендерит, показывает и меняет местами бэкбуферы 40 раз в секунду, что даёт на экране чёткое и плавное изображение в движении.
Существует несколько различных способов использования бэкбуферов для вывода сцены на экран, но мы не будем сейчас подробно рассматривать этот процесс. В данный момент нас интересует лишь способ изменения количества используемых бэк-буферов. Когда ты создаёшь оконное приложение, Direct3D игнорирует любые указанные значения данного параметра, так как в оконном режиме он может использовать всего один бэкбуфер. И напротив, при выборе полноэкранного режима ты можешь использовать столько бэкбуферов, сколько пожелаешь. Наиболее часто указывают число бэк-буферов равное:

  • 2 (т.н. "двойная буферизация", "double buffering");
  • 3 (т.н. "тройная буферизация", "triple buffering").

Чем больше бэкбуферов ты используешь, тем (теоретически) более сглаженными выглядят переходы между кадрами, что даёт более плавную анимацию. В то же время, в большинстве случаев, когда число бэкбуферов больше двух, разница в качестве анимации часто просто неуловима для человеческого глаза.
Указание слишком большого числа бэкбуферов влечёт за собой повышенный расход памяти.
Рассмотрим пример. При запуске приложения в полноэкранном режиме в разрешении 1280х1024 и глубиной цветности 32 бита, то в этом случае 1 бэк-буфер займёт 1280 х 1024 х 32 = 41 943 040 бит памяти. Так как в одном байте 8 бит, получившийся результат соответствует 5 242 880 байт, или 5,2 мегабайта. При использовании 3-х таких бэкбуферов потребуется почти 16 Мб памяти видеокарты (бэкбуферы хранятся именно там). А ведь ещё нам потребуется загрузить в видеопамять текстуры, полигональные сетки (меши), данные вершин и многое другое. Таким образом, как видишь, если система располагает достаточным объёмом видеопамяти, ты можешь без труда использовать 3 и более бэкбуферов. Как вариант, можно снизить разрешение и/или глубину цветности, что уменьшит количество требуемой памяти.

Закрыть
noteОбрати внимание

Если установить слишком большое значение числа бэкбуферов, процесс создания устройства Direct3D может завершиться неудачей. Число бэкбуферов, которое поддерживается приложением, зависит от видеоадаптера и объёма доступной видеопамяти. Для лучшей совместимости желательно всегда использовать не более трёх бэкбуферов. Как вариант, эту опцию можно указать в меню графических настроек, чтобы дать пользователю возможность самостоятельно указывать этот параметр. Это также улучшит совместимость игры со старыми и более слабыми видеокартами, которые часто не поддерживают более двух бэкбуферов.

Рис.3 Swap chain (цепочка перелистывыния) бэкбуферов в деле
Рис.3 Swap chain (цепочка перелистывыния) бэкбуферов в деле

Бэкбуферы выводятся на экран в порядке зацикленной очереди (весь процесс называется swap chain - от англ. "быстро сменяемая цепочка, очередь"). Когда ты командуешь Direct3D вывести очередной кадр на экран, вся цепочка бэкбуферов смещается на одну позицию (просиходит т.н. flip или flipping - от англ. "щелчок, схлопывание"). Таким образом первый бэкбуфер становится последним, а второй бэкбуфер становится первым (см. Рис.3).

Варианты структуры D3DPRESENT_PARAMETERS для оконного и полноэкранного приложений

Часто в окне настроек игры игрок самостоятельно может выбирать оконный или полноэкранный режим, ставя галку напротив соответствующего пункта. Вот примерный код заполнения структуры D3DPRESENT_PARAMETERS для обоих случаев:

// d3dmm - предварительно проинициализированная структура D3DDISPLAYMODE
D3DPRESENT_PARAMETERS d3dpp;

// Очищаем содержимое структуры
ZeroMemory(&d3dpp, sizeof(D3DPRESENT_PARAMETERS));

// Для оконного (windowed) режима.
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.BackBufferFormat = d3dmm.Format;  // Использовать формат текущего видеорежима.

// Для полноэкранного (fullscreen) режима.
d3dpp.Windowed = FALSE;
d3dpp.SwapEffect = D3DSWAPEFFECT_FLIP;
d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
d3dpp.FullScreen_PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT;
d3dpp.BackBufferFormat = d3dmm.Format;  // Использовать формат цветности выбранного видеорежима.

Создание интерфейса устройства (Device Interface) и инициализация экрана (Initializing the Display)

И вот теперь, в завершающем третьем шаге мы можем создать интерфейс устройства Direct3D (Direct3D device inteface), являющийся "раброчей лошадкой" всей 3D-системы. Предварительно заполнив структуры D3DDISPLAYMODE и D3DPRESENT_PARAMETERS, вызываем функцию IDirect3D8::CreateDevice, создающую и инициализирующую интерфейс устройства Direct3D. Вот её прототип:

HRESULT IDirect3D8::CreateDevice(
 UINT Adapter,  // D3DADAPTER_DEFAULT
 D3DDEVTYPE DeviceType,  // D3DDEVTYPE_HAL
 HWND hFocusWindow,  // Дескриптор окна, в которое всё будет рендериться.
 DWORD BehaviorFlags, // D3DCREATE_SOFTWARE_VERTEXPROCESSING
 D3DPRESENT_PARAMETERS *pPresentationParameters,  // d3dpp
 IDirect3DDevice8 *ppReturnedDeviceInterface);  // объект устройства

Как видим, в многочисленных параметрах данной функции мы передаём указатель на предварительно заполненную структуру D3DPRESENT_PARAMETERS. Также указываем дескриптор окна, куда всё будет рендериться. Остальные параметры вполне очевидны и изменяются редко. В последнем параметре создаём указатель на только что созданный объект устройства Direct3D (Direct3D device object).
Вот пример вызова функции IDirect3D8::CreateDevice:

// g_pD3D - предварительно проинициализированный объект Direct3D.
// hWnd - дескриптор окна, куда всё бкдет рендериться.
// d3dpp - предварительно заполненная структура презентации (D3DPRESENT_PARAMETERS).
IDirect3DDevice8 *g_pD3DDevice;

if(FAILED(g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING,
 &d3dpp, &g_pD3DDevice)))
{
 // Ошибка.
}

Утрата контроля над объектом устройства (Losing the Device). Функция Reset.

Как правило, программный объект устройства Direct3D работает как надо: графика рендерится, идёт обычная работа с ресурсами памяти. Но иногда происходит т.н. утрата объекта устройства (losing the device).
Утраченный объект устройства по той или иной причине лишился контроля над графическими ресурсами. Такое может произойти из-за того, что другое приложение "захватило" (gained) контроль над графическим адаптером и сдампило (damped) всю память, содержащую графические данные твоего приложения. Иногда это происходит из-за уход Windows в спящий режим (sleep mode). Во всех случаях необходимо вернуть контроль над графическим адаптером.
Но как узнать, что контроль утрачен? Путём проверки (examining) возвращаемых значений вызываемых функций. Чуть ниже в главе "Презентация сцены" (Presenting the scene) мы рассмотрим вывод отрендеренного изображения на экран. При вызове описанной там функции, в случае, когда объект устройства возвращает значение D3DERR_DEVICELOST, устройство утрачено.
Восстановление контроля осуществляется путём вызова функции IDirect3D8::Reset. Вот её прототип:

Прототип функции Reset
HRESULT IDirect3DDevice8::Reset(D3DPRESENT_PARAMETERS *pPresentationParameters);

Здесь в единственном параметре указывается структура презентации (D3DPRESENT_PARAMETERS), созданная ранее. В нашем случае пример использования функции Reset выглядит так:

// g_pD3D - предварительно проинициализированный объект Direct3D
// d3dpp - предварительно заполненная структура презентации (D3DPRESENT_PARAMETERS).
g_pD3DDevice->Reset(&d3dpp);

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

Библиотека D3DX (DirectX 8)

Несмотря на то, что Microsoft упростила многие интерфейсы, пользоваться ими не так-то просто. Для того, чтобы упростить жизнь программерам и ускорить работу с графикой софтверный гигант создал библиотеку D3DX. Она содержит в себе множество полезных функций для работы с графикой (мешами, текстурами, шрифтами, математическими вычислениями и т.д.).
Для начала работы с D3DX необходимо добавить в Решение/Проект библиотеку D3DX8.LIB из подкаталога lib установленного DirectX SDK 8 (путь по умолчанию C:\DXSDK\lib\) + добавить в топовый заголовочный файл директиву #include D3DX8.H.
Напомню, DirectX SDK 8 можно взять здесь: http://old-dos.ru/index.php?page=files&mode=files&do=show&id=2629(external link)
Имена всех функций данной библиотеки начинаются с префикса D3DX. Помимо функций, она также содержит COM-объекты, как например ID3DXBaseMesh.

Математические вычисления с помощью библиотеки D3DX

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

Математические матрицы (Math matrices)

Матричная математика (Matrix math) является разделом линейной алгебры (linear algebra). Она позволяет заметно упростить вычисления, а также снизить их объём. Причём не только в 3D-графике. Нам она нужна в первую очередь для расчёта 3D-трансформаций.
В свете того, что любой объект в Direct3D состоит из множества вершин (vertices), именно Direct3D занимается преобразованием (трансформацией) их координат с целью последующего вывода на экран. В каждом кадре ты можешь трансформировать тысячи вершин, из которых состоит сцена. Для таких преобразований Direct3D использует математические матрицы(external link), представляющие собой таблицы чисел, в которых каждый элемент имеет своё особое значение. В нашем случае эти элементы (=числа) представляют собой трансформации, применяемые к вершинам. Скомпоновав все необходимые вычисления в одну компактную форму (матрицу), мы здорово экономим время и ресурсы.

Закрыть
noteОбрати внимание

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

Построение матриц (Matrix construction). Класс D3DXMATRIX библиотеки D3DX.

Матрицы бывают самых разных размеров. Но в нашем случае нас интересуют только матрицы размером 4х4 элемента, состоящие из 4-х строк (rows) и 4-х столбцов (columns).
Direct3D хранит матрицы в специальной структуре D3DMATRIX:

Структура D3DMATRIX
typedef struct _D3DMATRIX {
 D3DVALUE _11, _12, _13, _14;
 D3DVALUE _21, _22, _23, _24;
 D3DVALUE _31, _32, _33, _34;
 D3DVALUE _41, _42, _43, _44;
} D3DMATRIX;

Закрыть
noteОбрати внимание

Тип данных D3DVALUE представляет собой макрос, основанный на типе float.


Для заполнения матрицы данными трансформации (transformation data) обычно применяют библиотеку D3DX. (Но это далеко не единственный способ.) В D3DX вместо структуры D3DMATRIX используется производный объект специального класса D3DXMATRIX, оснащённого массой полезных функций.
Такие преобразования (трансформации) координат как трансляция (translation) и изменение масштаба (scale) используют по одной матрице матрицу. Но для выполнения вращения (rotation) задействуются целых 3 матрицы (по одной на каждую ось вращения). Это значит, что для выполнения типичного полного цикла трансформаций (вращение, трансляция, изменение масштаба) в общей сложности потребуется 5 матриц.
Первый набор функций для создания матриц вращения выглядит так:

D3DXMATRIX *D3DXMatrixRotationX (
 D3DXMATRIX *pOut,  // Матрица на выходе (output matrix)
 FLOAT Angle);     // Угол поворота по оси X вокруг центра

D3DXMATRIX *D3DXMatrixRotationY (
 D3DXMATRIX *pOut,  // Матрица на выходе (output matrix)
 FLOAT Angle);     // Угол поворота по оси Y вокруг центра

D3DXMATRIX *D3DXMatrixRotationZ (
 D3DXMATRIX *pOut,  // Матрица на выходе (output matrix)
 FLOAT Angle);     // Угол поворота по оси Z вокруг центра


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

Закрыть
noteОбрати внимание

Все функции библиотеки D3DX для работы с матрицами возвращают указатель типа D3DXMATRIX, представляющий собой указатель на результирующую матрицу. Это позволяет использовать данные функции в качестве встроенных (inline) в другие функции. Вот пример:
D3DXMATRIX matMatrix, matResult;
matResult = D3DXMatrixRotationZ(&matMatrix, 1.57f);


Следующая функция создаёт матрицу трансляции (translation matrix), которая используется для перемещения (изменения текущей позиции) объектов:

Прототип функции D3DXMatrixTranslation
D3DXMATRIX *D3DXMatrixTranslation(
 D3DXMATRIX *pOut,  // Матрица на выходе (output matrix)
 FLOAT x,   // Смещение координаты по оси X
 FLOAT y,   // Смещение координаты по оси Y
 FLOAT z);  // Смещение координаты по оси Z

Здесь координаты представляют собой смещения (offsets) координат относительно локального центра координат (origin) объекта. Данные транслируемые значения используются для преобразования вершин объекта из локальных координат пространства (local space coordinates) в мировую систему координат (world space coordinates).

Функция D3DXMatrixScaling изменяет масштаб (scale) объектов относительно их локального центра координат:

Прототип функции D3DXMatrixScaling
D3DXMATRIX *D3DXMatrixScaling(
 D3DXMATRIX *pOut,  // Матрица выода (output matrix)
 FLOAT sx,  // Изменение масштаба по оси x.
 FLOAT sy,  // Изменение масштаба по оси y.
 FLOAT sz);  // Изменение масштаба по оси z.

Обычный (исходный) масштаб объекта принят за 1,0. Для увеличения объекта в 2 раза указывают значение 2,0 для координат по каждой из осей. Для уменьшения объекта в 2 раза указывают масштаб 0,5.

Помимо рассмотренных выше матриц, в Direct3D (и высшей математике) также существует специальный вид матриц - единичная матрица (identity matrix). Несколько её элементов установлены в 0, остальные - в 1. При перемножении единичной матрицы на обычную в результате получают исходную обычную (неизменённую) матрицу. Единичные матрицы полезны когда надо скомбинировать (=перемножить) две матрицы, но при этом исходная матрица должна остаться без изменений. Для создания единичной матрицы в D3DX даже есть отдельная функция D3DXMatrixIdentity:

D3DXMATRIX *D3DXMatrixIdentity(D3DXMATRIX *pOut);

В качестве парамтера передаётся результирующая матрица (output matrix).

Вот пример применения всех расмотренных выше матриц:

...
D3DXMATRIX matXRot, matYRot, matZRot;
D3DXMATRIX matTrans, matScale;

// Настраиваем поворот объекта на 45 град. (0,785 радиан) по каждой из осей.
D3DXMatrixRotationX(&matXRot, 0.785f);
D3DXMatrixRotationY(&matYRot, 0.785f);
D3DXMatrixRotationZ(&matZRot, 0.785f);

// Настраиваем транслацию (перемещение) объекта по вектору (100,200,300).
D3DXMatrixTranslation(&matTrans, 100.0f, 200.0f, 300.0f);

// Изменяем масштаб (scale) объекта, увеличивая его в 2 раза по каждой из осей.
D3DXMatrixScaling(&matScale, 2.0f, 2.0f, 2.0f);
...

Комбинирование матриц (Combining matrices)

После заполнения различных матриц значениями, используемыми при трансформациях, можно применять их к каждой отдельной вершине. Для упрощения задачи отдельные матрицы, содержащие значения для трансляции, поворота и изменения масштаба, можно комбинировать в одну путём их простого перемножения. Данную операцию также назвают конкатенацией матриц(external link) (matrix concatenation) и она является ключом к оптимизации всех матричных вычислений.
Создав одну матрицу в каждом кадре ты можешь применять её к каждой вершине сцены. В этом случае эффект будет такой же, как и при применнении каждой из составляющих матриц по отдельности.
Матрицы не так сложны в использовании. Просто в них надо немного разобраться.
В D3DX для комбинирования двух матриц есть специальная функция D3DXMatrixMultiply:

Прототип функции D3DXMatrixMultiply
D3DXMATRIX *D3DXMatrixMultiply(
 D3DXMATRIX *pOut,  // Результирующая матрица (output matrix)
 CONST D3DXMATRIX *pM1,   // Матрица-операнд 1 (source matrix 1).
 CONST D3DXMATRIX *pM2);  // Матрица-операнд 2 (source matrix 2).

Указав две комбинируемые матрицы и матрицу результат на выходе получим готовую матрицу, результат сложения двух исходных матриц.
Вооружившись данной функцией, скомбинируем матрицы вращения (3 шт.; по одной на каждую ось), трансляции (translation) и масштабирования (scale) в одну результирующую матрицу, которая включает в себя все эти трансформации:

D3DMATRIX matResult;  // Результирующая (output) матрица.

// Приводим результирующую матрицу к виду единичной матрицы (identity matrix).
D3DXMatrixIdentity(&matResult);

// Перемножаем (комбинируем) результирующую матрицу с матрицей масштабирования.
D3DXMatrixMultiply(&matResult, &matResult, &matScale);

// Перемножаем (комбинируем) результирующую матрицу с матрицами вращения.
D3DXMatrixMultiply(&matResult, &matResult, &matXRot);
D3DXMatrixMultiply(&matResult, &matResult, &matYRot);
D3DXMatrixMultiply(&matResult, &matResult, &matZRot);

// Перемножаем (комбинируем) результирующую матрицу с матрицей трансляции.
D3DXMatrixMultiply(&matResult, &matResult, &matTrans);

Закрыть
noteОбрати внимание

Порядок перемножения матриц очень важен. В данном примере результирующая матрица сначала была скомбинирована с матрицей масштабирования, затем с матрицами вращения (по осям X, Y и Z), и в последнюю очередь с матрицей трансляции. Возьми эту последовательность за правило. Если комбинировать матрицы в другом порядке, то и результирующая матрица будет другой, что в будущем приведёт к нежелательным последствиям.

Алгоритм преобразования координат. Local -> World -> View -> Projection

Координаты каждой вершины, прежде чем та будет отрендерена на экран монитора, проходят несколько стадий преобразований:

  1. Локальные (local, по сути они являются нетрансформированными - untransformed) координаты сперва преобразуются в мировые (world). Для этого применяют мировую матрицу трансформации (world transformation matrix, world matrix), содержащую преобразования, применяемые для позиционирования объекта в 3D-пространстве. Иногда её называют local-to-world-матрицей.
  2. Мировые (world) координаты преобразуются в координаты вида (view coordinates). Для этого применяют матрицу вида (viewing matrix), которая позиционирует вершины относительно наблюдателя (=виртуальной камеры) в 3D-пространстве. Иногда её называют world-to-view-матрицей.
  3. Координаты вида (view coordinates) преобразуются в координаты проекции (projection coordinates). Для этого применяется матрица проекции (projection matrix), которая проецирует 3D-координаты вершин на 2D-поверхность экрана монитора (в терминологии Windows GDI это холст (canvas), в Direct3D - это фрейм (frame)) для последующего рендеринга. Иногда её называют view-to-projection-матрицей.

Т.е. всего применяется 3 матрицы преобразований:

  • Мировая матрица (World matrix)
  • Матрица вида (View matrix)
  • Матрица проекции (Projection matrix)

Запомни порядок преобразования координат (в Direct3D он всегда неизменен):
Локальные -> Мировые -> Вида -> Проекции

Закрыть
noteОбрати внимание

При работе с матрицей вида (view matrix) небходимо использовать отрицательные (reverse, negative) значения положения вьюера (=виртуальной камеры) для его ориентирования на объекты. А всё из-за того, что как правило начальная позиция наблюдателя остаётся неизменной (locked) и находится в точке начала координат (0, 0, 0). Когда наблюдатель ("viewer") двигается относительно мира, то все объекты мира смещаются относительно него. Например, когда наблюдатель перемещается на 10 единиц (units) вперёд, то по факту объекты мира перемещаются на 10 единиц назад. Когда наблюдатель поворачивает "голову" на 10 градусов влево, то объекты мира, в свою очередь, тоже смещаются (вообще они вращаются относительно наблюдателя) на 10 градусов вправо.

Мировая матрица (world matrix) и матрица вида (view matrix), в свою очередь, представляет собой результирующую матрицу, полученную в результате перемножения матриц масштабирования, вращения (3 шт) и трансляции, рассмотренных выше. Так вот, для получения корректных матриц мира и вида их "компоненты" необходимо перемножать в строго определённом порядке:

  • При создании мировой матрицы (для пребразования локальных координат в мировые) порядок перемножения отдельных (individual) матриц следующий:

R = S*X*Y*Z*T
, где R - результирующая матрица; S - матрица масштабирования (scale matrix); X, Y и Z - матрицы вращения (по одной на каждую ось); T - матрица трансляции (translation matrix).

  • В создании матрицы вида (для преобразования мировых координат в координаты вида) участвуют только матрицы трансляции (translation) и вращения (rotation)! Здесь порядок перемножения отдельных (individual) матриц следующий:

R = T*X*Y*Z
, где R - результирующая матрица; T - матрица трансляции (translation matrix); X, Y и Z - матрицы вращения (по одной на каждую ось).

Источники:


1. Adams J. Programming Role Playing Games with DirectX 8.0. - Premier Press. 2002

Contributors to this page: slymentat .
Последнее изменение страницы Понедельник 18 / Май, 2020 14:03:47 MSK автор slymentat.

Помочь проекту

Яндекс-деньги: 410011791055108