Программирование графических процессоров с использованием Direct3D и HLSL

Графическая библиотека Direct3D


В данном курсе автором будут рассматриваться примеры работы библиотеки Direct3D с использованием языков программирования C++ (среда Microsoft Visual Studio) и Pascal (среда Delphi). Предполагается, что читатель уже установил и настроил все необходимые программные средства. Для успешной работы с графической библиотекой Direct3D в среде Microsoft Visual Studio необходимо установить набор Microsoft DirectX Software Development Kit, который можно скачать с сайта http://www.microsoft.com. Для пользователей, которые планируют разрабатывать подобные проекты на языке Pascal в среде, например, Delphi, требуется наличия в системе заголовочных файлов, найти которые можно по адресу http://www.clootie.ru. Кроме этого в системе должен присутствовать пакет библиотек DirectX End-User Runtimes. На момент написания этих строк в сети Интернет была доступна версия DirectX 9.0c с обновлениями за июнь 2006 года.

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

В среде Microsoft Visual Studio подключение заголовочного файла Direct3D SDK проделывается следующим образом:

… #include <d3d9.h> …

В проектах на Delphi строчка подключения должна выглядеть так:

… uses …, Direct3D9; …

Кроме этого для проектов Visual Studio требуется еще подключить статическую библиотеку d3d9.lib. Это можно проделать либо указанием в настройках проекта, либо явно прописать в коде посредством директивы препроцессора:

#pragma comment (lib, "d3d9.lib").

Следующий шаг заключается в объявлении указателя на главный интерфейс IDirect3D9 и указателя на интерфейс устройства.

В языке C++ эта строчка кода будет выглядеть следующим образом:

… LPDIRECT3D9 direct3d = NULL; LPDIRECT3DDEVICE9 device = NULL; …

Для языка Pascal эти объявления можно записать так:

… var direct3d: IDirect3D9; device: IDirect3DDevice9; …

Вся работа начинается с создания главного объекта. Именно создание главного объекта Direct3D позволит осуществить доступ ко всем возможностям, предоставляемым его интерфейсами.
Создание главного объекта – это вызов предусмотренной функции (Direct3DCreate9) с единственным параметром (D3D_SDK_VERSION). D3D_SDK_VERSION – это предопределенная константа, описанная в заголовочном модуле (d3d9.h или Direct3D9.pas) и указывающая номер версии библиотеки DirectX. В этом можно убедиться, заглянув в справку помощи по DirectX.

D3D_SDK_VERSION
Версия DirectXЧисловое значение константы
8.0120
8.1220
9.031
9.0a31
9.0b31
9.0c32


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

C++direct3d = Direct3DCreate9( D3D_SDK_VERSION );
Pascaldirect3d := Direct3DCreate9( D3D_SDK_VERSION );
После создания объекта Direct3D функция Direct3DCreate9() возвращает указатель на интерфейс либо пустое значение, если вызов произошел с ошибкой. Это может свидетельствовать о том, что на Вашем компьютере отсутствует библиотека DirectX.

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

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


Весь процесс "отрисовки" или рендеринга происходит в так называемый BackBuffer (задний буфер). Все что Direct3D выводит на экран, рисуется (помещается) в BackBuffer, а затем копируется в первичный буфер (FrontBuffer). В терминологии DirectX буфер – это определенный кусок памяти, в котором лежит некоторая графическая или не совсем информация. В данном случае BackBuffer – это место в памяти видеоадаптера отведенное под данные, которые будут показаны на экран по требованию. При этом следует заметить, что формат переднего и заднего буферов должны совпадать, т.е. у них должны быть одинаковые размеры, количество цветов и т.д.

Следующим шагом является получение текущих установок рабочего стола, а именно, какой формат пикселя (сколько битов отведено под каждую составляющую цвета) присутствует в данный момент. Для этого можно воспользоваться, вызвав метод GetAdapterDisplayMode главного объекта Direct3D. Примеры вызовов этого метода для языков C++ и Pascal приведены в следующей таблице:

C++

D3DDISPLAYMODE display; … direct3d ->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &display );
Pascal

var display: TD3DDisplayMode; … direct3d.GetAdapterDisplayMode( D3DADAPTER_DEFAULT, display );
У метода GetAdapterDisplayMode имеются два аргумента:

  1. константа, определяющая номер (индекс) видеоадаптера, для которого запрашиваются параметры;
  2. указатель на переменную, в которую помещается результат выполнения команды.


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

C++

typedef struct _D3DDISPLAYMODE { UINT Width; UINT Height; UINT RefreshRate; D3DFORMAT Format; } D3DDISPLAYMODE;
Pascal

TD3DDisplayMode = packed record Width: LongWord; Height: LongWord; RefreshRate: LongWord; Format: TD3DFormat; end {_D3DDISPLAYMODE};
Первые два поля данной структуры определяют соответственно ширину и высоту рабочего стола, третье поле содержит частоту обновления монитора, и последнее поле – формат пикселя.


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

Следующий шаг – заполнение структуры D3DPRESENT_PARAMETERS, которая будет задавать параметры поверхности вывода (рендеринга). Для этого необходимо объявить вспомогательную переменную и заполнить ее поля, например, следующим образом:

C++

D3DPRESENT_PARAMETERS params; … ZeroMemory( &params, sizeof(params) ); params.Windowed = TRUE; params.SwapEffect = D3DSWAPEFFECT_DISCARD; params.BackBufferFormat = display.Format; …
Pascal

var params: TD3DPresentParameters; … ZeroMemory( @params, SizeOf(params) ); params.Windowed := True; params.SwapEffect := D3DSWAPEFFECT_DISCARD; params.BackBufferFormat := display.Format; …
Здесь приведены строки для минимального набора обязательных действий. Строка ZeroMemory(…) заполняет указанную в качестве первого параметра структуру нулями. Строка "params.Windowed := …" указывает, что вывод будет производиться в некоторое окно. Строка "params.SwapEffect := …" задает режим работы механизма двойной буферизации. И последняя строка "params.BackBufferFormat := …" указывает какой формат буфера будет использоваться.

Следующий шаг инициализационных действий состоит в создании устройства вывода. Это действие реализуется с помощью вызова метода CreateDevice главного объекта:

C++direct3d->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd, D3DCREATE_ SOFTWARE_VERTEXPROCESSING, &params, &device )

Pascaldirect3d.CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, Handle, D3DCREATE_SOFTWARE_VERTEXPROCESSING, @params, device );
В результате вызова метода CreateDevice получаем ссылку на интерфейс IDirect3DDevice9, с помощью которого мы будем производить рендеринг сцены. Поясним значения параметров метода CreateDevice. Первый параметр (D3DADAPTER_DEFAULT) указывает номер адаптера, установленного в системе. Второй аргумент метода (D3DDEVTYPE_HAL) определяет тип устройства; значение D3DDEVTYPE_HAL - позволяет использовать аппаратное ускорение на 100%, D3DDEVTYPE_REF – указывает использовать только программные действия (ресурсы центрального процессора).


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

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



После того, как все наши инициализации и настройки устройства вывода проведены, наступает заключительный шаг, которой состоит в непосредственном построении и отображении сцены на экране дисплея. Наверно самым простым примером построения сцены является вывод пустого окна, закрашенного определенным цветом. Это можно реализовать с помощью метода Clear, который содержится в интерфейсе IDirect3DDevice9. Этот метод закрашивает задний буфер (BackBuffer) указанным цветом. Программная реализация такого вывода будет следующая:

C++device->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.0f, 0);
Pascaldevice.Clear(0,nil,D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.0, 0);
Первый параметр метода Clear обозначает количество прямоугольников для очистки области вывода (0 обозначает, что очистке подвергается вся область вывода). Второй параметр представляет собой указатель на массив, содержащий набор величин типа TRect. Каждая отдельная структура указывает, какое место в поверхности вывода следует очищать (значение NULL требует очистки всей поверхности отображения). Третий параметр обозначает что именно (какую поверхность) следует очищать. В данном случае очистке подвергается только поверхность вывода. Кроме этого, метод Clear может использоваться для очистки Z-буфера (константа D3DCLEAR_ZBUFFER) и буфера трафарета (константа D3DCLEAR_STENCIL). Четвертый параметр указывает, каким цветом следует заполнять поверхность рендеринга.


Функция D3DCOLOR_XRGB(Red,Green,Blue) возвращает цвет из трех составляющих – красного, зеленого и синего цветов. Значения параметров должны лежать в диапазоне [0,…,255]. Предпоследний параметр указывает значение, которым будет заполнен буфер глубины (Z-буфер). Значения этого параметра должны лежать в диапазоне [0.0,…,1.0], где 0 – соответствует ближайшей границе, 1 – дальней. Последний параметр метода задает значение для заполнения буфера шаблона (Stencil буфера).

Следующий шаг процесса рендеринга – это непосредственный вывод содержимого заднего буфера (BackBuffer) в окно визуализации. Этот шаг еще называют переключением буферов, и осуществляется он с помощью метода Present интерфейса IDirect3DDevice9. Программный код с использованием этого метода выглядит следующим образом:

C++device->Present( NULL, NULL, NULL, NULL );
Pascaldevice.Present(nil, nil, 0, nil);
Интерес здесь представляет, как правило, только третий параметр. Если он равен нулю, то идентификатор окна, в который происходит вывод, берется из ранее установленного, при создании устройства. Все остальные параметры выставляют, как правило, в NULL. Таким образом, минимальный набор инструкций для процедуры рендеринга, состоит в вызове двух методов Clear и Present интерфейса IDirect3DDevice9.

Подытожив все рассмотренные шаги по инициализации и процедуре рендеринга, можно все их представить в виде следующей блок-схемы.



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

C++

direct3d = Direct3DCreate9( D3D_SDK_VERSION ); direct3d ->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &display );

ZeroMemory( &params, sizeof(params) ); params.Windowed = TRUE; params.SwapEffect = D3DSWAPEFFECT_DISCARD; params.BackBufferFormat = display.Format;

direct3d->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &params, &device );
Pascal

direct3d := Direct3DCreate9( D3D_SDK_VERSION ); direct3d.GetAdapterDisplayMode( D3DADAPTER_DEFAULT, display );

ZeroMemory( @params, SizeOf(params) ); params.Windowed := True; params.SwapEffect := D3DSWAPEFFECT_DISCARD; params.BackBufferFormat := display.Format;

direct3d.CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, Handle, D3DCREATE_SOFTWARE_VERTEXPROCESSING, @params, device );

Содержание раздела