Введение в программирование трехмерных игр с DX9

         

Буфер глубины



1.3.6. Буфер глубины

Буфер глубины (depth buffer)— это поверхность, которая содержит не изображение, а информацию о глубине отдельных пикселей. Каждому пикселю в окончательном изображении соответствует элемент буфера глубины. Так, если размер окончательного изображения 640 × 480 пикселей, в буфере глубины должно быть 640 × 480 элементов.

На Рисунок  1.6 показана простая сцена, где одни объекты частично скрывают другие, находящиеся позади них. Чтобы определить, пиксели какого объекта находятся поверх всех других, Direct3D использует технологию, называемую буфером глубины (depth buffer) или z-буферизацией (z-buffering).



Цепочка обмена и переключение страниц



1.3.5. Цепочка обмена и переключение страниц

Direct3D управляет набором поверхностей, обычно двумя или тремя, который называется цепочка обмена (swap chain) и представляется интерфейсом IDirect3DSwapChain9. Мы не будем подробно говорить об этом интерфейсе, поскольку всю работу берет на себя Direct3D и вмешиваться в его действия приходится очень редко. Вместо этого мы поговорим о его назначении и общих принципах работы.

Цепочка обмена и, говоря более точно, техника переключения страниц, используется для достижения плавности анимации. На Рисунок 1.4 показана цепочка обмена, состоящая из двух поверхностей.





DDEVTYPE



1.1.2. D3DDEVTYPE

В коде устройство HAL указывается константой D3DDEVTYPE_HAL, являющейся членом перечисления D3DDEVTYPE. точно так же устройство REF указывается с помощью константы D3DDEVTYPE_REF, являющейся членом того же перечисления. Очень важно помнить эти типы устройств, поскольку при создании устройства вам придется указывать, какой именно тип использовать.



Файлы dUtilityh/cpp



1.5.1. Файлы d3dUtility.h/cpp

Перед тем, как перейти к примеру из этой главы, давайте потратим немного времени, чтобы познакомиться с функциями, предоставляемыми файлами d3dUtility.h/cpp. Вот как выглядит код из файла d3dUtility.h:

// Включение основного заголовочного файла Direct3DX. В нем осуществляется // включение других, необходимых нам заголовочных файлов Direct3D. #include <d3dx9.h>

namespace d3d { bool InitD3D( HINSTANCE hInstance, // [in] Экземпляр приложения. int width, int height, // [in] Размеры вторичного буфера. bool windowed, // [in] Оконный (true) или // полноэкранный (false) режим. D3DDEVTYPE deviceType, // [in] HAL или REF IDirect3DDevice9** device); // [out] Созданное устройство.

int EnterMsgLoop( bool (*ptr_display)(float timeDelta));

LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

template<class T> void Release(T t) { if(t) { t->Release(); t = 0; } }

template<class T> void Delete(T t) { if(t) { delete t; t = 0; } } }

InitD3D— Эта функция инициализирует главное окно приложения и содержит код инициализации Direct3D, который обсуждался в разделе 1.4. Если функция завершается нормально, она возвращает указатель на созданный интерфейс IDirect3DDevice9. Обратите внимание, что параметры функции позволяют задать размеры окна и то, в каком режиме — оконном или полноэкранном — будет работать приложение. Чтобы познакомиться с деталями реализации, посмотрите код примера.

EnterMsgLoop — Эта функция является оберткой для цикла обработки сообщений приложения. Она получает указатель на функцию визуализации. Функция визуализации — это функция в которой находится код для вывода создаваемого в примере изображения. Циклу сообщений необходимо знать, какая функция используется для визуализации, чтобы он мог вызывать ее и отображать сцену во время ожидания сообщений.

int d3d::EnterMsgLoop(bool(*ptr_display)(float timeDelta)) { MSG msg; ::ZeroMemory(&msg, sizeof(MSG));


static float lastTime = (float)timeGetTime(); while(msg.message != WM_QUIT) { if(::PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); } else { float currTime = (float)timeGetTime(); float timeDelta = (currTime - lastTime) * 0.001f; ptr_display(timeDelta); // вызов функции визуализации lastTime = currTime; } } return msg.wParam; }

Часть кода занимается вычислением времени, прошедшего между обращениями к функции ptr_display, то есть времени между кадрами.

Release — Этот шаблон разработан в качестве вспомогательной функции для освобождения COM-интерфейсов и присваивания указателям на них нулевых значений.

Delete — Этот шаблон разработан в качестве вспомогательной функции для удаления объектов, освобождения занимаемой ими памяти и присваивания указателям на них нулевых значений.

WndProc — Объявление оконной процедуры для главного окна приложения.


Формат пикселей


1.3.3. Формат пикселей

При создании поверхностей или текстур нам часто надо будет определить формат пикселей ресурсов Direct3D. Формат определяется как член перечисления D3DFORMAT. Вот некоторые форматы:

D3DFMT_R8G8B8— 24-разрядный формат пикселей, где, начиная с самого левого разряда, 8 бит отведены для красного цвета, 8 бит — для зеленого и 8 бит — для синего.

D3DFMT_X8R8G8B8 — 32-разрядный формат пикселей, где, начиная с самого левого разряда, 8 бит не используются, 8 бит отведены для красного цвета, 8 бит — для зеленого и 8 бит — для синего.

D3DFMT_A8R8G8B8 — 32-разрядный формат пикселей, где, начиная с самого левого разряда, 8 бит используются для альфа-канала, 8 бит отведены для красного цвета, 8 бит — для зеленого и 8 бит — для синего.

D3DFMT_A16B16G16R16F — 64-разрядный формат пикселей с плавающей запятой. Начиная с самого левого разряда, 16 бит используются для альфа-канала, 16 бит отведены для синего цвета, 16 бит — для зеленого и 16 бит — для красного.

D3DFMT_A32B32G32R32F — 128-разрядный формат пикселей с плавающей запятой. Начиная с самого левого разряда, 32 разряда используются для альфа-канала, 32 разряда отведены для синего цвета, 32 разряда — для зеленого и 32 разряда — для красного.

Полный список поддерживаемых форматов пикселей приведен в описании перечисления D3DFORMAT в документации к SDK.

ПРИМЕЧАНИЕ



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


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



COM

Модель компонентных объектов (Component Object Model, COM) — это технология, позволяющая DirectX быть независимым от языка программирования и совместимым со всеми предыдущими версиями. Обычно мы будем ссылаться на COM-объект, как на интерфейс, о котором, в нашем случае, можно думать как о классе C++. Большинство особенностей COM при программировании для DirectX на C++ остаются прозрачными и никак не влияют на работу. Есть только один важный момент, о котором следует помнить: для получения указателя на COM-интерфейс необходимо вызвать специальную функцию или метод другого COM-интерфейса; нельзя пользоваться ключевым словом C++ new. Кроме того, завершив работу с COM-интерфейсом, следует вызвать его метод Release (все COM-интерфейсы наследуют функциональность от интерфеса IUnknown, в котором есть метод Release), а не удалять его оператором delete. COM-объекты самостоятельно осуществляют управление памятью.

Конечно, можно еще много говорить о COM, но эта информация не требуется для эффективного использования DirectX.

ПРИМЕЧАНИЕ

В коде для обозначения COM-интерфейсов используется заглавная буква I. Например, COM-интерфейс, представляющий поверхность называется IDirect3DSurface9. Первые три формата (D3DFMT_R8G8B8, D3DFMT_X8R8G8B8 и D3DFMT_A8R8G8B8) широко распространены и поддерживаются большинством современных видеокарт. Поддержка форматов с плавающей точкой и некоторых других форматов (упомянутых в документации SDK) встречается реже. При использовании малораспространенного формата не забудьте проверить перед его использованием, что установленная видеокарта поддерживает его.

Цели

Изучить как Direct3D взаимодействует с графическим оборудованием.

Понять, какую роль в Direct3D играет COM.

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

Узнать, как инициализировать Direct3D.

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



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


В следующих подразделах будет показано как инициализировать Direct3D. Процесс инициализации Direct3D может быть разбит на следующие этапы:

Запрос указателя на интерфейс IDirect3D9. Этот интерфейс применяется для получения информации об установленных в компьютере устройствах и создания интерфейса IDirect3DDevice9, являющимся нашим объектом C++, представляющим аппаратные устройства, используемые для вывода трехмерной графики.

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

Инициализация экземпляра структуры D3DPRESENT_PARAMETERS. Эта структура содержит ряд параметров, позволяющих нам задать характеристики интерфейса IDirect3DDevice9, который мы намереваемся создать.

Создание объекта IDirect3DDevice9, основываясь на инициализированной структуре D3DPRESENT_PARAMETERS. Как говорилось ранее, объект IDirect3DDevice9— это наш объект C++, представляющий аппаратные устройства, используемые для отображения трехмерной графики.

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



Direct3D можно представлять как посредника


Direct3D можно представлять как посредника между программистом и графическим оборудованием. программист вызывает функции Direct3D, которые в свою очередь, приказывают графическому оборудованию выполнить необходимые операции, взаимодействуя с ним через уровень абстрагирования от оборудования (HAL) устройства.
Устройство REF позволяет разработчикам тестировать те возможности, которые предлагаются Direct3D, но не реализованы в используемом оборудовании.
Модель компонентных объектов (COM) — это технология, позволяющая DirectX быть независимым от языка программирования и совместимым со всеми предыдущими версиями. Рабртающим с Direct3D программистам не требуется детальное знание особенностей и принципов работы COM; достаточно знать как запросить и освободить COM-интерфейс.
Поверхность — это специальный интерфейс Direct3D, используемый для хранения двухмерных изображений. Формат пикселей поверхности задается с помощью членов перечисления D3DFORMAT. Поверхности и другие ресурсы Direct3D могут хранится в нескольких различных пулах памяти, определяемых с помощью членов перечисления D3DPOOL. Кроме того, поверхности могут использовать множественную выборку, в результате чего края объектов будут выглядеть более гладкими.
Интерфейс IDirect3D9 применяется для получения информации об установленных в системе графических устройствах. Например, через этот интерфейс мы можем получить описание возможностей устройства. Кроме того, он используется для создания интерфейса IDirect3DDevice9.
Интерфейс IDirect3DDevice9 можно представлять себе как программное средство управления графическим устройством. Например, вызов метода IDirect3DDevice9::Clear прикажет устройству очистить указанную поверхность.
Каркас примера используется для обеспечения единой согласованной структуры для всех рассматриваемых в книге приложений. Вспомогательный код из файлов d3dUtility.h/cpp является оберткой для кода инициализации, который должен быть реализован в каждом приложении. Создав эти обертки мы скрываем код, что позволяет в примерах сосредоточиться непосредственно на рассматриваемой теме.

Каркас примера



1.5.2. Каркас примера

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

bool Setup() — Это функция в которой инициализируется все, что должно быть инициализировано для данного приложения. В ней осуществляется выделение ресурсов, проверка возможностей устройств и установка состояний приложения.

void Cleanup() — В этой функции мы освобождаем все ресурсы, выделенные для приложения в функции Setup, в основном это освобождение памяти.

bool Display(float timeDelta) — В эту функцию мы помещаем весь код, отвечающий за рисование и код, который должен выполняться при переходе от кадра к кадру, например выполняющий изменение местоположения объектов. Параметр timeDelta — это время, прощедшее с момента вывода предыдущего кадра и используется он для синхронизации анимации с частотой смены кадров.



Множественная выборка



1.3.2. Множественная выборка

Множественная выборка (multisampling)— это техника, использующаяся для сглаживания ступенчатых линий на изображении, представленном в виде матрицы пикселей. Наиболее частое применение множественной выборки — полноэкранное сглаживание (full-screen antialiasing) (Рисунок  1.3).



Обзор Direct


Direct3D— это низкоуровневый графический API (программный интерфейс для приложений), позволяющий отображать трехмерные миры используя аппаратные ускорители трехмерной графики. Direct3D можно представлять как посредника между приложением и графическим устройством (аппаратурой трехмерной графики). Например, чтобы приказать графическому устройству очистить экран, приложение должно вызвать метод Direct3D IDirect3DDevice9::Clear. Взаимоотношения между приложением, Direct3D и аппаратурой компьютера показаны на Рисунок  1.1.



Окно программы, рассматриваемой в этой главе



Рисунок 1.7. Окно программы, рассматриваемой в этой главе


Этот пример, как и все другие примеры в этой книге, использует код из файлов d3dUtility.h и d3dUtility.cpp, которые можно скачать с веб-сайта, посвященного этой книге. Эти файлы содержат функции, реализующие общие задачи, которые должно выполнять каждое Direct3D-приложение, такие как создание окна, инициализация Direct3D и вход в цикл обработки сообщений. Благодаря созданию функций-оберток для этих задач, в коде примеров мы можем сосредоточиться на рассматриваемой в соответствующей главе теме. Кроме того, на протяжении книги мы будем добавлять к этим файлам полезный вспомогательный код.



Поверхности



1.3.1. Поверхности

Поверхность (surface)— это прямоугольный массив пикселей, используемый Direct3D в основном для хранения двухмерных изображений. Основные параметры поверхности представлены на Рисунок  1.2. Обратите внимание, что хотя мы представляем данные поверхности в виде матрицы, в действительности данные пикселей хранятся в линейном массиве.



Обработка вершин


1.3.7. Обработка вершин

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

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

ПРИМЕЧАНИЕ

Иногда, вместо того, чтобы сказать, что видеокарта поддерживает аппаратную обработку вершин, говорят что видеокарта поддерживает аппаратную обработку преобразований и расчета освещения.

Мы начинаем с включения заголовочного файла d3dUtility.h и объявления глобальной переменной для устройства:

#include "d3dUtility.h" IDirect3DDevice9* Device = 0;

Затем мы реализуем функции, входящие в каркас приложения:

bool Setup() { return true; } void Cleanup() {

}

В данном примере нам не требуются никакие ресурсы, так что методы Setup и Cleanup остаются пустыми.

bool Display(float timeDelta) { if(Device) { Device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x00000000, 1.0f, 0); Device->Present(0, 0, 0, 0); // показ вторичного буфера } return true; }

Метод Display вызывает метод IDirect3DDevice9::Clear, который очищает вторичный буфер и буфер глубины/трафарета, заполняя их черным цветом и константой 1.0 соответственно. Обратите внимание, что если приложение не остановлено, мы выполняем только код рисования. Объявление функции IDirect3DDevice9::Clear выглядит так:

HRESULT IDirect3DDevice9::Clear( DWORD Count, const D3DRECT* pRects, DWORD Flags, D3DCOLOR Color, float Z, DWORD Stencil );

Count — Количество прямоугольников в массиве pRects.

pRects — Массив очищаемых прямоугольных областей экрана. Он позволяет очищать отдельные фрагменты поверхности.

Flags — Указывает, какую поверхность очищать. Можно указывать одну или несколько из следующих поверхностей:

D3DCLEAR_TARGET — Целевая поверхность визуализации, обычно это вторичный буфер.

D3DCLEAR_ZBUFFER — Буфер глубины.

D3DCLEAR_STENCIL — Буфер трафарета.



Color — Цвет, которым будет заполнена поверхность визуализации.

Z — Значение, которым будет заполнен буфер глубины (z-буфер).

Stencil — Значение, которым будет заполнен буфер трафарета.

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

Оконная процедура обрабатывает пару событий, а именно позволяет выходить из приложения, нажав клавишу Esc.

LRESULT CALLBACK d3d::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch( msg ) { case WM_DESTROY: ::PostQuitMessage(0); break;

case WM_KEYDOWN: if( wParam == VK_ESCAPE ) ::DestroyWindow(hwnd); break; } return ::DefWindowProc(hwnd, msg, wParam, lParam); }

Функция WinMain выполняет следующие действия:

Инициализирует главное окно и Direct3D.

Вызывает процедуру Setup для инициализации приложения.

Запускает цикл обработки сообщений, указав в качестве функции визуализации функцию Display.

Освобождает выделенные приложению ресурсы и объект IDirect3DDevice9.

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE prevInstance, LPSTR cmdLine, int showCmd) { if(!d3d::InitD3D(hinstance, 800, 600, true, D3DDEVTYPE_HAL, &Device)) { ::MessageBox(0, "InitD3D() - FAILED", 0, 0); return 0; }

if(!Setup()) { ::MessageBox(0, "Setup() - FAILED", 0, 0); return 0; }

d3d::EnterMsgLoop( Display );

Cleanup();

Device->Release();

return 0; }

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

Для большинства примеров из этой книги наша задача будет заключаться в написании реализаций функций Setup, Cleanup и Display.

ПРИМЕЧАНИЕ

СОВЕТ

ПРИМЕЧАНИЕ



Предварительная подготовка


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



Приложение D Init


1.5.3. Приложение D3D Init

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

ПРИМЕЧАНИЕ

Рассматриваемый пример иллюстрирует те же идеи, что и урок Tutorial 1 из документации DirectX SDK. Завершив чтение этой главы, вы можете прочесть урок Tutorial 1, чтобы познакомиться с другой точкой зрения. Помните, что для построения данного приложения компоновщику необходимы библиотеки d3d9.lib, d3dx9.lib и winmm.lib.

Пример приложения: инициализация Direct


В рассматриваемом в этой главе примере мы выполним инициализацию Direct3D-приложения и очистим экран, заполнив его черным цветом (Рисунок 1.7).



Проверка поддержки аппаратной обработки вершин



1.4.2. Проверка поддержки аппаратной обработки вершин

Создавая объект IDirect3DDevice9, представляющий первичный видеоадаптер, мы должны указать используемый им способ обработки вершин. Мы хотим по-возможности использовать аппаратную обработку вершин, но не все видеокарты поддерживают ее и мы сперва должны проверить наличие поддержки аппаратной обработки вершин.

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

HRESULT IDirect3D9::GetDeviceCaps( UINT Adapter, D3DDEVTYPE DeviceType, D3DCAPS9 *pCaps );

Adapter— указывает физический видеоадаптер, возможности которого мы хотим определить.

DeviceType — задает тип устройства (т.е. аппаратное устройство (D3DDEVTYPE_HAL) или программное устройство (D3DDEVTYPE_REF)).

pCaps — возвращает инициализированную структуру с описанием возможностей устройства.

Теперь мы можем проверить возможности устройства, как описывалось в разделе 1.3.8. Это иллюстрирует следующий фрагмент кода:

// Заполняем структуру D3DCAPS9 информацией о // возможностях первичного видеоадаптера.

D3DCAPS9 caps; d3d9->GetDeviceCaps( D3DADAPTER_DEFAULT, // Означает первичный видеоадаптер. deviceType, // Задает тип устройства, обычно D3DDEVTYPE_HAL. &caps); // Возвращает заполненную структуру D3DCAPS9, которая содержит // информацию о возможностях первичного видеоадаптера.

// Поддерживается аппаратная обработка вершин? int vp = 0; if(caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) { // да, сохраняем в vp флаг поддержки аппаратной // обработки вершин. vp = D3DCREATE_HARDWARE_VERTEXPROCESSING; } else { // Нет, сохраняем в vp флаг использования программной // обработки вершин. vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING; }

Обратите внимание, что мы сохраняем способ обработки вершин, который будем использовать, в переменной vp. Это вызвано тем, что нам надо будет указать используемый тип обработки вершин, когда позднее мы будем создавать объект IDirect3DDevice9.

ПРИМЕЧАНИЕ

Идентификаторы D3DCREATE_HARDWARE_VERTEXPROCESSING и D3DCREATE_SOFTWARE_VERTEXPROCESSING — это предопределенные константы, означающие соответственно аппаратную обработку вершин и программную обработку вершин. Разрабатывая приложения, использующие новые, специальные или дополнительные возможности (другими словами, возможности не поддерживаемые большинством устройтв) следует всегда проверять возможности устройства (D3DCAPS9), чтобы перед использованием требуемой возможности убедиться, что устройство поддерживает ее. Никогда не предполагайте, что необходимая вам возможность будет доступна. Также обраите внимание, что приложения из этой книги в большинстве случаев не следуют данной рекомендации — в большинстве случаев мы не проверяем возможности устройств. Если какое-либо из рассматриваемых в книге приложений не работает на вашей системе, это скорее всего вызвано тем, что ваша аппаратура не поддерживает возможности, необходимые приложению. В таких случаях попробуйте использовать устройство REF.

Пул памяти



1.3.4. Пул памяти

Поверхности и другие ресурсы Direct3D могут быть размещены в различных областях памяти. Используемая область памяти задается с помощью одной из констант перечисления D3DPOOL. Доступны следующие варианты:

D3DPOOL_DEFAULT — Выбор используемого по умолчанию пула памяти позволяет Direct3D размещать ресурс в той области памяти, которая наиболее подходит для ресурсов данного типа с учетом его использования. Это может быть видеопамять, память AGP или системная память. Обратите внимание, что ресурсы, размещаемые в пуле по умолчанию, должны быть уничтожены (освобождены) до вызова метода IDirect3DDevice9::Reset, и могут быть повторно инициализированы после сброса.

D3DPOOL_MANAGED — ресурсы размещаются в пуле памяти, управляемом Direct3D (это значит, что при необходимости они могут автоматически перемещаться устройством в видеопамять или память AGP). Кроме того, в системной памяти хранится резервная копия ресурса. Если приложение получает доступ или изменяет ресурс, работа ведется с копией в системной памяти. Затем, если надо, Direct3D автоматически обновляет данные в видеопамяти.

D3DPOOL_SYSTEMMEM — Ресурс будет размещен в системной памяти.

D3DPOOL_SCRATCH — Указывает, что ресурс будет размещен в системной памяти. Отличие этого пула от D3DPOOL_SYSTEMMEM в том, что ресурс не должен соответствовать налагаемым графическим устройствам ограничениям. Следовательно, у графического устройства нет доступа к такому ресурсу. Тем не менее, эти ресурсы могут применяться в операциях копирования как в качестве источника, так и в качестве приемника.



Рисунок Цепочка обмена из двух поверхностей (первичного и вторичного буфера)



Рисунок  1.4. Цепочка обмена из двух поверхностей (первичного и вторичного буфера)

<
На Рисунок  1.4 в первичном буфере ( front buffer) находится та поверхность, которая в данный момент отображается на экране монитора. Монитор не показывает изображение находящееся в первичном буфере мгновенно; например, при установленной для монитора частоте кадров 60 Гц, вывод изображения занимает одну шестидесятую секунды. Частота кадров в приложении часто отличается от частоты кадров монитора (например, приложение может визуализировать кадр быстрее, чем монитор отобразит его). Мы не хотим обновлять содержимое первичного буфера, занося в него следующий кадр, в то время, когда монитор еще не закончил отображать текущий, но мы также не хотим останавливать визуализацию кадров, чтобы подождать, пока монитор полностью выведет текущий кадр. Поэтому мы визуализируем кадр во внеэкранной поверхности (вторичном буфере, back buffer); затем, когда монитор закончит отображение поверхности из первичного буфера, мы перемещаем этот буфер в конец цепочки обмена, и передвигаем в цепочке вторичный буфер, чтобы он стал первичным.

Этот процесс называется показом (presenting). На Рисунок  1.5 показана цепочка обмена до и после показа.




Рисунок Группа объектов, частично закрывающих друг друга



Рисунок  1.6. Группа объектов, частично закрывающих друг друга

< Буфер глубины работает путем вычисления значения глубины каждого пикселя и последующего выполнения проверки глубины. Проверка глубины заключается в сравнении значений глубины пикселов, расположенных в заданной позиции. Сравнение выигрывает тот пиксель, который находится ближе всего к камере, и именно он будет сохранен. Это имеет смысл, поскольку самый близкий к камере пиксель скрывает все остальные пиксели, находящиеся позади него.

Формат буфера глубины определяет точность сравнения глубины пикселей. То есть 24-разрядный буфер глубины обеспечвает более высокую точность, чем 16-разрядный. Большинство приложений замечательно работают с 24-разрядным буфером глубины, хотя Direct3D поддерживает и 32-разрядный буфер.

D3DFMT_D32 — 32-разрядный буфер глубины.

D3DFMT_D24S8 — 24-разрядный буфер глубины с 8 разрядами, зарезервированными для буфера трафарета.

D3DFMT_D24X8 — 24-разрядный буфер глубины.

D3DFMT_D24X4S4 — 24-разрядный буфер глубины с 4 разрядами, зарезервированными под буфер трафарета.

D3DFMT_D16 — 16-разрядный буфер глубины.

ПРИМЕЧАНИЕ

Буфер трафарета представляет собой более сложную тему, которая будет подробно разобрана в главе 8.

Рисунок Показ Используя содержащую



Рисунок  1.5. Показ. Используя содержащую две поверхности цепочку обмена, мы видим, что основу показа составляет переключение поверхностей

Таким образом, структура кода визуализации будет следующей:

Визуализировать вторичный буфер.

Показать вторичный буфер.

Перейти к пункту 1.



Рисунок Поверхность



Рисунок  1.2. Поверхность

Ширина и высота поверхности измеряются в пикселах. Шаг (pitch) поверхности измеряется в байтах. Более того, шаг поверхности может отличаться от ее ширины в зависимости от используемого оборудования. Вы не должны считать, что шаг = ширина * sizeof(пиксель).

В коде поверхность представляется интерфейсом IDirect3DSurface9. Он предоставляет методы для прямого чтения и записи данных поверхности, а также методы для получения информации о поверхности. Вот наиболее важные методы интерфейса IDirect3DSurface9:

LockRect — Этот метод позволяет получить указатель на память поверхности. После этого, используя арифметику указателей, мы можем читать и записывать отдельные пиксели поверхности.

UnlockRect — После того, как мы вызвали метод LockRect и завершили работу с памятью поверхности, нам следует разблокировать поверхность, вызвав этот метод.

GetDesc — Метод возвращает параметры поверхности, заполняя структуру D3DSURFACE_DESC.

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

// Предполагается, что _surface - указатель на интерфейс IDirect3DSurface9 // Предполагается, что у поверхности 32-разрядный формат пикселей

// Получаем описание поверхности D3DSURFACE_DESC surfaceDesc; _surface->GetDesc(&surfaceDesc);

// Получаем указатель на данные пикселей поверхности D3DLOCKED_RECT lockedRect; _surface->LockRect( &lockedRect, // получаемый указатель на данные 0, // блокируем всю поверхность 0); // дополнительных параметров блокировки нет

// Перебираем все пиксели поверхности и делаем их красными DWORD* imageData = (DWORD*)lockedRect.pBits; for(int i = 0; i < surfaceDesc.Height; i++) { for(int j = 0; j < surfaceDesc.Width; j++) { // индекс в текстуре, обратите внимание, что мы используем шаг и // делим его на 4, поскольку шаг задается в байтах, // а каждый пиксель занимает 4 байта. int index = i * lockedRect.Pitch / 4 + j;

imageData[index] = 0xffff0000; // красный цвет } } _surface->UnlockRect();

Структура D3DLOCKED_RECT определена следующим образом:

typedef struct _D3DLOCKED_RECT { INT Pitch; // шаг поверхности void *pBits; // указатель на начало памяти поверхности } D3DLOCKED_RECT;

Приведем несколько комментариев к коду блокировки поверхности. Очень важно предположение о 32-разрядном формате пикселей, поскольку мы выполняем приведение к типу DWORD, который является 32-разрядным. Благодаря этому мы можем считать, что каждое значение DWORD представляет один пиксель. И не заморачивайтесь о том, как 0xffff0000 представляет красный цвет — мы поговорим об этом в главе 4.



Рисунок Слева изображена линия



Рисунок  1.3. Слева изображена линия с зазубренным краем. Справа — та же линия, но с использованием множественной выборки, выглядящая более гладкой

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

D3DMULTISAMPLE_NONE — Множественная выборка не используется.

D3DMULTISAMPLE_1_SAMPLE...D3DMULTISAMPLE_16_SAMPLE — Устанавливает уровень множественной выборки от 1 до 16.

От типа множественной выборки зависит уровень качества. Тип констант — DWORD.

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





1.4.4. Создание интерфейса IDirect3DDevice9

Заполнив структуру D3DPRESENT_PARAMETERS мы можем создать объект IDirect3DDevice9 с помощью следующего метода:

HRESULT IDirect3D9::CreateDevice( UINT Adapter, D3DDEVTYPE DeviceType, HWND hFocusWindow, DWORD BehaviorFlags, D3DPRESENT_PARAMETERS *pPresentationParameters, IDirect3DDevice9** ppReturnedDeviceInterface );

Adapter — указывает физический видеоадаптер, который будет представлять создаваемый объект IDirect3DDevice9.

DeviceType — задает тип используемого устройства (т.е. аппаратное устройство (D3DDEVTYPE_HAL) или программное устройство (D3DDEVTYPE_REF)).

hFocusWindow — дескриптор окна с которым будет связано устройство. Обычно это то окно, в которое будет выводиться изображение и для наших целей мы здесь задаем тот же дескриптор, который указан в члене d3dpp.hDeviceWindow структуры D3DPRESENT_PARAMETERS.

BehaviorFlags — в этом параметре указывается значение D3DCREATE_HARDWARE_VERTEXPROCESSING либо D3DCREATE_SOFTWARE_VERTEXPROCESSING.

pPresentationParameters — указывается инициализированный экземпляр структуры D3DPRESENT_PARAMETERS, задающий параметры устройства.

ppReturnedDeviceInterface — возвращает указатель на созданное устройство.

Вот пример использования функции:

IDirect3DDevice9* device = 0; hr = d3d9->CreateDevice( D3DADAPTER_DEFAULT, // первичный видеоадаптер D3DDEVTYPE_HAL, // тип устройства hwnd, // окно, связанное с устройством D3DCREATE_HARDWARE_VERTEXPROCESSING, // тип обработки вершин &d3dpp, // параметры показа &device); // возвращает созданное устройство

if(FAILED(hr)) { ::MessageBox(0, "CreateDevice() - FAILED", 0, 0); return 0; }


Устройство REF



1.1.1. Устройство REF

Вам может потребоваться написать прогамму Direct3D, использующую те аппаратные возможности, которые не поддерживает ваша система. Для этих целей Direct3D предоставляет вспомогательный растеризатор (reference rasterizer, известный также как устройство REF), который программно эмулирует все предлагаемые API Direct3D возможности. Это позволяет вам писать и проверять код, использующий возможности Direct3D, не поддерживаемые вашей аппаратурой. Например, в четвертой части этой книги мы будем использовать вершинные и пиксельные шейдеры, которые могут не поддерживаться вашей видеокартой. Если ваша видеокарта не поддерживает шейдеры, вы можете проверить работу кода с помощью устройства REF. Очень важно понимать, что устройство REF предназначено только для разработчиков. Оно присутствует только в DirectXSDK и не может быть предоставлено конечным пользователям. Кроме того, следует помнить, что устройство REF работает очень медленно и не годится ни для каких целей, кроме тестирования.



Возможности устройств


1.3.8. Возможности устройств

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

Это иллюстрирует следующий пример. Предположим, мы хотим проверить способно ли устройство осуществлять аппаратную обработку вершин (или, другими словами, поддерживает ли устройство аппаратную обработку преобразований и расчета освещения). Посмотрев описание структуры D3DCAPS9 в документации SDK, мы обнаружим, что бит D3DDEVCAPS_HWTRANSFORMANDLIGHT в члене данных D3DCAPS9::DevCaps указывает, поддерживает ли устройство аппаратную обработку преобразований и расчета освещенности. Следовательно наша проверка, подразумевая, что caps — это экземпляр структуры D3DCAPS9, будет выглядеть следующим образом:

bool supportsHardwareVertexProcessing;

// Если бит установлен, значит данная возможность // поддерживается устройством if(caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) { // Бит установлен - возможность поддерживается supportsHardwareVertexProcessing = true; } else { // Бит сброшен - возможность не поддерживается hardwareSupportsVertexProcessing = false; }

ПРИМЕЧАНИЕ

DevCaps означает «возможности устройства» (device capabilities).

ПРИМЕЧАНИЕ

Как инициализировать экземпляр структуры D3DCAPS9 на основании возможностей конкретного устройства мы покажем в следующем разделе.

ПРИМЕЧАНИЕ

Мы рекомендуем вам посмотреть описание структуры D3DCAPS9 в документации к SDK и изучить полный список возможностей устройств, поддерживаемых в Direct3D.

Взаимосвязь между приложением, Direct и аппаратурой



Рисунок 1.1. Взаимосвязь между приложением, Direct3D и аппаратурой


На Рисунок  1.1 блок с названием Direct3D представляет набор документированных интерфейсов и функций, которые Direct3D предоставляет приложениям и программистам. Эти интерфейсы и функции охватывают полный набор функциональных возможностей, предлагаемых данной версией Direct3D. Обратите внимание, что предложение возможости Direct3D не означает, что она будет поддерживаться аппаратурой.

На Рисунок  1.1 изображена промежуточная стадия между Direct3D и графическим устройством — уровень абстрагирования от аппаратуры (Hardware Abstraction Layer, HAL). Direct3D не может напрямую взаимодействовать с аппаратурой, поскольку продаются сотни различных видеокарт и каждая видеокарта отличается набором поддерживаемых функций и способом реализации тех функций, которые поддерживаются. Например, две разные видеокарты могут совершенно по-разному выполнять очистку экрана. Поэтому Direct3D требует, чтобы производители оборудования реализовали уровень абстрагирования от оборудования (HAL), который представляет собой зависящий от аппаратуры код, указывающий устройству ка выполнять те или иные операции. Благодаря этому Direct3D не требуется знать специфику конкретных устройств, и его спецификации не зависят от используемого оборудования.

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



Заполнение структуры DPRESENT_PARAMETERS


1.4.3. Заполнение структуры D3DPRESENT_PARAMETERS

Следующий этап процесса инициализации — заполнение экземпляра структуры D3DPRESENT_PARAMETERS. Эта структура используется для задания ряда характеристик объекта IDirect3DDevice9, который мы будем создавать, и объявлена следующим образом:

typedef struct _D3DPRESENT_PARAMETERS_ { UINT BackBufferWidth; UINT BackBufferHeight; D3DFORMAT BackBufferFormat; UINT BackBufferCount; D3DMULTISAMPLE_TYPE MultiSampleType; DWORD MultiSampleQuality; D3DSWAPEFFECT SwapEffect; HWND hDeviceWindow; BOOL Windowed; BOOL EnableAutoDepthStencil; D3DFORMAT AutoDepthStencilFormat; DWORD Flags; UINT FullScreen_RefreshRateInHz; UINT PresentationInterval; } D3DPRESENT_PARAMETERS;

ПРИМЕЧАНИЕ

В приведенном ниже описании членов структуры D3DPRESENT_PARAMETERS перечислены только те из них, которые, по нашему мнению, наиболее важны для новичков. За полным описанием структуры мы отсылаем вас к документации SDK.

BackBufferWidth — Ширина поверхности вторичного буфера в пикселах.

BackBufferHeight — Высота поверхности вторичного буфера в пикселах.

BackBufferFormat — Формат пикселей во вторичном буфере (т.е. для 32-разрядного формата укажите D3DFMT_A8R8G8B8).

BackBufferCount — Количество используемых вторичных буферов. Обычно мы задаем значение 1, чтобы указать, что нам нужен только один вторичный буфер.

MultiSampleType — Используемый для вторичного буфера тип множественной выборки. Подробная информация об этом параметре содержится в документации SDK.

MultiSampleQuality — Уровень качества множественной выборки. Подробная информация об этом параметре содержится в документации SDK.

SwapEffect — Член перечисления D3DSWAPEFFECT, указывающий, как будет осуществляться переключение буферов в цепочке переключений. Для большинства случаев можно указать значение D3DSWAPEFFECT_DISCARD.

hDeviceWindow — Дескриптор связанного с устройством окна. Укажите дескриптор того окна приложения, в котором вы хотите выводить изображение.


Windowed — Укажите true для запуска приложения в оконном режиме или false — для работы в полноэкранном режиме.

EnableAutoDepthStencil — укажите значение true, чтобы Direct3D автоматически создал и поддерживал буферы глубины и трафарета.

AutoDepthStencilFormat — формат буферов глубины и трафарета (например, для 24-разрядного буфера глубины с 8 разрядами, зарезервированными для буфера трафарета, укажите D3DFMT_D24S8).

Flags&nbnsp;— Дополнительные параметры. Укажите ноль (если флаги отсутствуют) или член набора D3DPRESENTFLAG. Полный список допустимых флагов приведен в документации. Здесь мы рассмотрим два наиболее часто используемых:

D3DPRESENTFLAG_LOCKABLE_BACKBUFFER — Указывает, что вторичный буфер может быть заблокирован. Обратите внимание, что использование блокируемого вторичного буфера снижает производительность.

D3DPRESENTFLAG_DISCARD_DEPTHSTENCIL — указывает, что буфер глубины и трафарета после показа вторичного буфера становится некорректным. Под словом «некорректным» мы подразумеваем, что данные, хранящиеся в буфере глубины и трафарета могут стать неверными. Это может увеличить производительность.



FullScreen_RefreshRateInHz — Частота кадров; используйте частоту кадров по умолчанию, указав значение D3DPRESENT_RATE_DEFAULT.

PresentationInterval — член набора D3DPRESENT. Полный список допустимых значений приведен в документации. Наиболее часто используются следующие два:

D3DPRESENT_INTERVAL_IMMEDIATE — Показ выполняется немедленно.

D3DPRESENT_INTERVAL_DEFAULT — Direct3D сам выбирает частоту показа. Обычно она равна частоте кадров.



Вот пример заполнения структуры:

D3DPRESENT_PARAMETERS d3dpp;

d3dpp.BackBufferWidth = 800; d3dpp.BackBufferHeight = 600; d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8; //формат пикселей d3dpp.BackBufferCount = 1; d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE; d3dpp.MultiSampleQuality = 0; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.hDeviceWindow = hwnd; d3dpp.Windowed = false; // полноэкранный режим d3dpp.EnableAutoDepthStencil = true; d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8; // формат буфера глубины d3dpp.Flags = 0; d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT; d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;


с запроса указателя на интерфейс



1.4.1. Запрос интерфейса IDirect3D9

Инициализация Direct3D начинается с запроса указателя на интерфейс IDirect3D9. Это выполняется с помощью простого вызова специальной функции Direct3D, как показано в приведенном ниже фрагменте кода:

IDirect3D9* _d3d9; _d3d9 = Direct3DCreate9(D3D_SDK_VERSION);

Единственным параметром функции Direct3DCreate9 всегда должна быть константа D3D_SDK_VERSION, гарантирующая, что при построении приложения будут использованы правильные заголовочные файлы. Если при работе функции возникли ошибки, она возвращает нулевой указатель.

Объект IDirect3D9 используется для двух вещей: перечисления устройств и создания объекта IDirect3DDevice9. Перечисление устройств подразумевает определение возможностей, видеорежимов, форматов и другой информации о каждой установлденной в системе видеокарте. Например, чтобы создать представляющий физическое устройство объект IDirect3DDevice9, необходимо указать поддерживаемую устройством конфигурацию видеорежима и формата. Чтобы найти такую работающую конфигурацию, используются методы перечисления IDirect3D9.

Однако, поскольку перечисление устройств может быть достаточно сложной задачей, а мы хотим как можно быстрее перейти к работе с Direct3D, мы решили не выполнять перечисление устройств, за исключением единственной проверки, о которой будет рассказано в следующем разделе. Чтобы отсутствие перечисления не привело к проблемам, мы выбрали «безопасную» конфигурацию, которую должны поддерживать почти все аппаратные устройства.