Графическая библиотека 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.0 | 120 |
8.1 | 220 |
9.0 | 31 |
9.0a | 31 |
9.0b | 31 |
9.0c | 32 |
В результате чего, указав необходимую версию D3D_SDK_VERSION, можно рассчитывать и на определенные возможности, поддерживаемые созданным объектом. Программная конструкция по созданию главного объекта Direct3D будет выглядеть следующим образом:
C++ | direct3d = Direct3DCreate9( D3D_SDK_VERSION ); |
Pascal | direct3d := Direct3DCreate9( D3D_SDK_VERSION ); |
Через главный объект мы не можем производить вывод графики. Используя его, мы можем только узнать возможности и специфическую информацию о видеокарте и создать устройство, представляющее видеоадаптер. А вот уже с помощью устройства мы можем рисовать, накладывать текстуры, освещать сцену и т.д.
Прежде чем разбираться с методом создания устройства вывода, необходимо понять, как устроен процесс рендеринга в библиотеке 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 ); |
- константа, определяющая номер (индекс) видеоадаптера, для которого запрашиваются параметры;
- указатель на переменную, в которую помещается результат выполнения команды.
Если у вас в системе присутствует всего один видеоадаптер, то в качестве первого параметра можно передавать ноль. Второй аргумент представляет собой переменную структурного типа следующего содержания:
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( ¶ms, 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; … |
Следующий шаг инициализационных действий состоит в создании устройства вывода. Это действие реализуется с помощью вызова метода CreateDevice главного объекта:
C++ | direct3d->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd, D3DCREATE_ SOFTWARE_VERTEXPROCESSING, ¶ms, &device ) |
Pascal | direct3d.CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, Handle, D3DCREATE_SOFTWARE_VERTEXPROCESSING, @params, device ); |
Третий параметр позволяет задать окно (Handle), куда будет производиться вывод сцены. Четвертый аргумент (D3DCREATE_SOFTWARE_VERTEXPROCESSING) указывает, что обработка вершин сцены будет производиться по фиксированным заданным правилам (этот параметр следует указывать, если видеоадаптер не поддерживает архитектуру шейдеров). Предпоследний – пятый параметр хранит параметры создаваемого устройства вывода. И последний аргумент – это имя переменной, в которую при успешном вызове будет помещен результат работы метода.
Таким образом, схему работы библиотеки Direct3D можно представить так.
После того, как все наши инициализации и настройки устройства вывода проведены, наступает заключительный шаг, которой состоит в непосредственном построении и отображении сцены на экране дисплея. Наверно самым простым примером построения сцены является вывод пустого окна, закрашенного определенным цветом. Это можно реализовать с помощью метода Clear, который содержится в интерфейсе IDirect3DDevice9. Этот метод закрашивает задний буфер (BackBuffer) указанным цветом. Программная реализация такого вывода будет следующая:
C++ | device->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.0f, 0); |
Pascal | device.Clear(0,nil,D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.0, 0); |
Функция 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 ); |
Pascal | device.Present(nil, nil, 0, nil); |
Подытожив все рассмотренные шаги по инициализации и процедуре рендеринга, можно все их представить в виде следующей блок-схемы.
Таким образом, последовательность шагов инициализации библиотеки Direct3D может выглядеть следующим образом:
C++ | direct3d = Direct3DCreate9( D3D_SDK_VERSION ); direct3d ->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &display ); ZeroMemory( ¶ms, sizeof(params) ); params.Windowed = TRUE; params.SwapEffect = D3DSWAPEFFECT_DISCARD; params.BackBufferFormat = display.Format; direct3d->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, ¶ms, &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 ); |