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

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




Intro

DirectX 8 Graphics является одним из компонентов DirectX (версии 8 и более поздних), отвечающим за вывод на экран 2D и 3D-графики.1 В более ранних версиях DirectX компонент DirectX Graphics состоял из двух отдельных компонентов: Direct3D и DirectDraw. В версии 8 эти два компонента объединили в один - DirectX Graphics. Несмотря на это, в литературе и в исходных кодах примеров можно встретить множество отсылок к Direct3D. Множество "ушей" в DirectX Graphics растёт именно из этого, казалось бы, преданного забвению, компонента. Часто в литературе понятия Direct3D и DirectX Graphics синонимичны.
С выходом DirectX 8 даже ветеранам игрокодинга потребовалось время для ознакомления со всеми фичами нового API.
Для компиляции примеров нам понадобится:
  • MS Visual C++ 2010 Express,
  • Microsoft DirectX SDK 8.
Всё легко гуглится + есть в разделе "Софт" нашего сайта.
Перед началом работы (в нашем случае) с DirectX 8 всегда проделывай следующие шаги:
  • Включай в топовый заголовочный файл директиву #include D3D8.H.
  • Добавляй в Решение/Проект файл библиотеки D3D8.LIB.
  • Указывай корректные пути к каталогам включения (include) и библиотечных файлов (LIB) DirectX SDK 8 при создании каждого нового Решения/Проекта.
Эти два файла являются основными (и часто единственными), которые требуются для создания простых приложений под DirectX Graphics. При компиляции они автоматически "подтянут" в Решение/Проект другие (вспомогательные) файлы из каталога DirectX SDK 8. Это нормально. Весь процесс неплохо расписан в статьях:

Компоненты 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) и инициализируем экран.
Прежде чем начать использовать Direc3D, его надо грамотно проинициализировать.

Создаём объект интерфейса 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 на данном компе. Можно также запросить инфу о текущем видеорежиме экрана (display mode). Каждый видеорежим состоит из трёх компонентов:
  • Разрешение экрана (ширина (width) и высота (height) в пикселях);
  • Глубина цвета (color depth; число цветов, одновременно отображаемое на экране);
  • Частота обновления (refresh rate) экрана.
Допустим, ты выбрал видеорежим с разрешением 640х480, глубиной цвета 16 бит и частотой обновления видеоадаптера по умолчанию.
Вся инфа о видеорежиме содержится в специальной структуре 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 ColorMode Macros)
ФОРМАТ ЦВЕТНОСТИ ОПИСАНИЕ
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

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

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

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

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

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

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

D3DDISPLAYMODE d3ddm;

if(FAILED(g_pD3D->GetDisplayMode(D3DADAPTER_DEFAULT, &d3dmm)))
{
	// Ошибка.
}

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

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

Следующий шаг - указать Direct3D, каким образом представить отрендеренную графику игроку. Будет ли это оконный (windowed) режим или полноэкранный (fullscreen) или будем сначала выводить в бэкбуфер (backbuffer) а затем флипить (англ. flip - буквально "быстро перелистнуть")? Какую частоту обновления (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) или пропустить. Тем не менее, элементы, связанные с бэкбуферами, надо знать и уметь в них разбираться.

Бэкбуфер (Backbuffer)


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

Image
Рис.2 Применение бэкбуферов для вывода сцены на экран

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


С методом презентации тесно связано понятие бэкбуфера. Для вывода изображения на экран 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 бэкбуфера (т.н. двойная буферизация) - 1 передний и 1 задний буфер. Оконное приложение (даже игра под Direct3D!) имеет в своём распоряжении всего один бэкбуфер (т.е. изображение рендерится напрямую в область видимости). При двойной буферизации в то время как Direct3D рендерит текущий кадр в один из бэкбуферов (с точки зрения 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 может завершиться неудачей. Число бэкбуферов, которое поддерживается приложением, зависит от видеоадаптера и объёма доступной видеопамяти. Для лучшей совместимости желательно всегда использовать не более трёх бэкбуферов. Как вариант, эту опцию можно указать в меню графических настроек, чтобы дать пользователю возможность самостоятельно указывать этот параметр. Это также улучшит совместимость игры со старыми и более слабыми видеокартами, которые часто не поддерживают более двух бэкбуферов.

Бэкбуферы выводятся на экран в порядке зацикленной очереди (весь процесс называется 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; // Использовать формат цветности выбранного видеорежима.


Создаём объект устройства Direct3D и инициализируем экран (Initializing the Display)

Здесь сперва создаём объект интерфейса IDirect3DDevice8, который является программным воплощением реального аппаратного видеоадаптера. Он применяется для контроля процесса вывода графики на экран. Предварительно заполнив структуры 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. Вот её прототип:
HRESULT IDirect3DDevice8::Reset(D3DPRESENT_PARAMETERS *pPresentationParameters);

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

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

Источники


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


Последние изменения страницы Вторник 17 / Май, 2022 14:56:43 MSK

Search Wiki Page

Точное совпадение

Категории

|--> C#
|--> C++