Лабораторная-работа-№6-Мн.-ОС-5

advertisement
Многопользовательские операционные системы – 5 курс
ЛАБОРАТОРНАЯ РАБОТА № 6
Основы работы с Direct3D 9
Цель работы: изучить принципы работы с Direct3D 9.
Краткие теоретические сведения
В среде 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.
Программная конструкция по созданию главного объекта Direct3D
будет выглядеть следующим образом:
C++
1
Многопользовательские операционные системы – 5 курс
direct3d = Direct3DCreate9( D3D_SDK_VERSION );
Pascal
direct3d := 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;
…
2
Многопользовательские операционные системы – 5 курс
direct3d.GetAdapterDisplayMode( D3DADAPTER_DEFAULT, display
);
У метода GetAdapterDisplayMode имеются два аргумента:
константа, определяющая номер (индекс) видеоадаптера, для которого
запрашиваются параметры;
указатель на переменную, в которую помещается результат
выполнения команды.
Следующий
шаг
–
заполнение
структуры
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 )
Pascal
direct3d.CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
Handle, D3DCREATE_SOFTWARE_VERTEXPROCESSING,
3
Многопользовательские операционные системы – 5 курс
@params, device );
В результате вызова метода CreateDevice получаем ссылку на
интерфейс IDirect3DDevice9, с помощью которого мы будем производить
рендеринг сцены. Поясним значения параметров метода CreateDevice.
Первый параметр (D3DADAPTER_DEFAULT) указывает номер адаптера,
установленного в системе. Второй аргумент метода (D3DDEVTYPE_HAL)
определяет тип устройства; значение D3DDEVTYPE_HAL - позволяет
использовать аппаратное ускорение на 100%, D3DDEVTYPE_REF –
указывает использовать только программные действия (ресурсы
центрального процессора). Третий параметр позволяет задать окно (Handle),
куда будет производиться вывод сцены. Четвертый аргумент
(D3DCREATE_SOFTWARE_VERTEXPROCESSING)
указывает,
что
обработка вершин сцены будет производиться по фиксированным заданным
правилам (этот параметр следует указывать, если видеоадаптер не
поддерживает архитектуру шейдеров). Предпоследний – пятый параметр
хранит параметры создаваемого устройства вывода. И последний аргумент –
это имя переменной, в которую при успешном вызове будет помещен
результат работы метода.
После того, как все наши инициализации и настройки устройства
вывода проведены, наступает заключительный шаг, которой состоит в
непосредственном построении и отображении сцены на экране дисплея.
Наверно самым простым примером построения сцены является вывод
пустого окна, закрашенного определенным цветом. Это можно реализовать с
помощью метода 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);
Первый
параметр
метода
Clear
обозначает
количество
прямоугольников для очистки области вывода (0 обозначает, что очистке
подвергается вся область вывода). Второй параметр представляет собой
указатель на массив, содержащий набор величин типа TRect. Каждая
отдельная структура указывает, какое место в поверхности вывода следует
очищать (значение NULL требует очистки всей поверхности отображения).
Третий параметр обозначает что именно (какую поверхность) следует
очищать. В данном случае очистке подвергается только поверхность вывода.
Кроме этого, метод Clear может использоваться для очистки Z-буфера
(константа D3DCLEAR_ZBUFFER) и буфера трафарета (константа
D3DCLEAR_STENCIL). Четвертый параметр указывает, каким цветом
следует
заполнять
поверхность
рендеринга.
Функция
D3DCOLOR_XRGB(Red,Green,Blue) возвращает цвет из трех составляющих
– красного, зеленого и синего цветов. Значения параметров должны лежать в
4
Многопользовательские операционные системы – 5 курс
диапазоне [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);
Интерес здесь представляет, как правило, только третий параметр. Если
он равен нулю, то идентификатор окна, в который происходит вывод, берется
из ранее установленного, при создании устройства. Все остальные параметры
выставляют, как правило, в 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
);
5
Многопользовательские операционные системы – 5 курс
Построение любой сцены в Direct3D, будь это обычная плоская кривая,
например, график функции одной переменной, или сложная трехмерная
композиция, происходит с помощью простейших геометрических
примитивов, таких как точка, отрезок прямой и треугольник. Элементарным
строительным материалом для перечисленных примитивов является
вершина. Именно набором вершин задается тот или иной примитив. Чтобы
использовать вершины при построении сцены необходимо определить их
тип. Для хранения вершин отведем область памяти, представляющую собой
обычный массив определенного типа. Вначале необходимо описать, что из
себя будет представлять вершина – задать определенный формат.
Программно этот шаг реализуется через структуры следующим образом:
C++
struct MYVERTEX1
{
FLOAT x, y, z, rhw;
DWORD color;
};
MYVERTEX1 data1[100];
struct MYVERTEX2
{
FLOAT x, y, z;
FLOAT n1, n2, n3;
};
MYVERTEX2 data2[100];
Pascal
MyVertex1 = packed record
x, y, z, rhw: Single;
color: DWORD;
end;
MyVertex2 = packed record
x, y, z: Single;
n1, n2, n3: Single;
end;
var
data1: array [0..99] of MyVertex1;
data2: array [0..99] of MyVertex2;
Тем самым данные о вершинах будут храниться в массиве, и иметь
строго определенный формат (тип). В процессе визуализации сцены
необходимо указать графической библиотеке, что собой представляет одна
вершина примитива. Это реализуется с помощью механизма флагов,
называемого FVF (Flexible Vertex Format – гибкий формат вершин).
Существует порядка двух десятков флагов FVF для определения формата
вершины. Самые распространенные и наиболее используемые флаги FVF для
6
Многопользовательские операционные системы – 5 курс
определения формата вершины представлены в следующей таблице:
D3DFVF_DIFFUSE
используется цветовая компонента вершины
D3DFVF_NORMAL вершина содержит нормали
D3DFVF_TEX1 задает текстурные координаты вершины
D3DFVF_PSIZE определяет размер частицы
D3DFVF_XYZ вершина содержит три координаты
D3DFVF_XYZRHW преобразованный формат вершин
Комбинация конкретных флагов дает возможность сообщить системе, с
каким форматом (типом) вершин она имеет дело в данный момент времени.
Так, например,
(D3DFVF_XYZ OR D3DFVF_NORMAL) – вершина содержит нормали
и координаты, (D3DFVF_XYZRHW OR D3DFVF_DIFFUSE) - цветная
преобразованная вершина, где OR – операция логического "или". Установка
формата вершин осуществляется с помощью вызова метода SetFVF
интерфейса IDirect3DDevice9. Программно это реализуется так:
C++
#define MY_FVF (D3DFVF_XYZRHW|D3DFVF_DIFFUSE)
…
device->SetFVF(MY_FVF);
Pascal
const MY_FVF = D3DFVF_XYZRHW or D3DFVF_DIFFUSE;
…
device.SetFVF(MY_FVF);
Следует особо остановиться на флаге D3DFVF_XYZRHW. Этот флаг
позволяет описывать вершины, определенные на плоскости. Он служит для
графической системы индикатором того, что построение сцены происходит
на плоскости в координатах окна (формы) вывода. При этом формат
вершины должен задаваться четверкой чисел x,y,z,w, две последних из
которых, как правило, не используются, однако должны присутствовать при
описании вершины.
В качестве примера попытаемся вывести на экран 100 точек, случайно
"разбросанных" на форме. Для этого необходимо проделать следующие
шаги:
Описать структуру, в которой будут храниться координаты точек;
Объявить массив нужной размерности (в нашем случае на 100
элементов) и заполнить его данными (в нашем случае случайными
координатами).
Вызвать метод для вывода этого массива на экран.
Первые два шага нам реализовать уже не составляет особого труда.
Третий же шаг может быть реализован с помощью вызова метода
DrawPrimitiveUP интерфейса IDirect3DDevice9. Прототип данного метода
выглядит так:
DrawPrimitiveUP(
Тип выводимого примитива,
7
Многопользовательские операционные системы – 5 курс
Количество примитивов,
Массив данных,
Размер "шага в байтах" от одной вершины до другой
)
Первый параметр указывает на тип выводимого примитива и задается
одной из следующих констант: D3DPT_POINTLIST, D3DPT_LINELIST,
D3DPT_LINESTRIP, D3DPT_TRIANGLELIST, D3DPT_TRIANGLESTRIP,
D3DPT_TRIANGLEFAN. Второй аргумент задает количество выводимых
примитивов. Третий параметр является указателем на область в памяти, где
располагаются данные о вершинах (в нашем случае это заполненный
массив). И последний параметр задает, сколько байтов отводится для
хранения одной вершины.
Возвращаясь к нашему примеру, вывод ста случайных точек на
плоскости можно осуществить, например, с помощью следующего
программного кода:
C++
struct MYVERTEX
{
FLOAT x, y, z, rhw;
}data[100];
#define MY_FVF (D3DFVF_XYZRHW);
// заполнение массива data "случайными" точками …
// процедура вывода сцены
device->SetFVF(MY_FVF);
device->DrawPrimitiveUP( D3DPT_POINTLIST, 100, data,
sizeof(MYVERTEX) );
Pascal
type
MyVertex = packed record
x, y, z, rhw: Single;
end;
const
MY_FVF = D3DFVF_XYZRHW;
var
data: array [0..99] of MyVertex;
// заполнение массива data "случайными" точками …
// процедура вывода сцены
device.SetFVF(MY_FVF);
device.DrawPrimitiveUP( D3DPT_POINTLIST, 100, data,
SizeOf(MyVertex) );
8
Многопользовательские операционные системы – 5 курс
Остановимся теперь на процедуре непосредственного вывода
результатов на экран. Процесс непосредственного воспроизведения
примитивов
рекомендуют
обрамлять
двумя
действиями.
Перед
отображением необходимо вызвать метод BeginScene, а после
воспроизведения – метод EndScene интерфейса IDirect3DDevice9. Первый
метод информирует устройство вывода (видеокарту), что следует
подготовиться к воспроизведению результатов. Второй метод сообщает
устройству о том, что процесс воспроизведения для текущего кадра закончен
и теперь можно осуществлять переключение буферов рендеринга. Таким
образом,
процедура
воспроизведения
сцены
должна
выглядеть
приблизительно следующим образом:
C++
VOID Render()
{
device->BeginScene();
device->Clear(0,
NULL,
D3DCOLOR_XRGB(0,0,0),
1.0f, 0);
device->SetFVF(…);
device->DrawPrimitiveUP(…);
device->EndScene();
device->Present( NULL, NULL, NULL, NULL );
}
D3DCLEAR_TARGET,
Pascal
procedure Render;
begin
device.BeginScene;
device.Clear(0,nil,D3DCLEAR_TARGET,
1.0,0);
device.SetFVF(…);
device.DrawPrimitiveUP(…);
device.EndScene;
device.Present(nil, nil, 0, nil);
end;
D3DCOLOR_XRGB(0,0,0),
Рассмотрим оставшиеся типы примитивов, которые имеются в наличии
у библиотеки Direct3D.
Вывод отрезков
Для построения независимых отрезков первым аргументом метода
DrawPrimitiveUP необходимо указать константу D3DPT_LINELIST. При этом
следует заметить, что количество выводимых примитивов (второй параметр
метода) будет в два раза меньше количества точек в массиве.
Для построения связных отрезков первым аргументом метода
DrawPrimitiveUP указывается константа D3DPT_LINESTRIP. При этом
количество выводимых примитивов будет на единицу меньше количества
точек в исходном массиве данных.
Вывод треугольников
Если первым аргументом метода DrawPrimitiveUP указывается
9
Многопользовательские операционные системы – 5 курс
константа D3DPT_TRIANGLELIST, то каждая триада (тройка) вершин задает
три вершины одного (независимого) треугольника. Количества выводимых
примитивов (треугольников) будет в три раза меньше чем размер массива
данных.
Если первым аргументом метода DrawPrimitiveUP указывается
константа D3DPT_TRIANGLESTRIP, то речь идет о группе связных
треугольников. Первые три вершины задают первый треугольник, вторая,
третья и четвертая определяют второй треугольник, третья, четвертая и пятая
– третий и т.д. В результате получается лента соприкасающихся
треугольников. Следует заметить, что использование связных треугольников
самый экономный и эффективный способ построений.
Если первым аргументом метода DrawPrimitiveUP указывается
константа D3DPT_TRIANGLEFAN, то строится группа связных
треугольников, но данные здесь трактуются немного иначе. Треугольники
связаны подобно раскрою зонтика или веера. По такой схеме удобно строить
конусы и пирамиды в пространстве, выпуклые многоугольники и эллипсы в
плоскости.
В предыдущих примерах фигурировали бинарные примитивы (белые
фигуры на черном фоне). Чтобы окрасить примитивы нужно задать цвет
каждой вершины примитива. Это проделывается в два этапа. Сначала мы
должны изменить тип вершины, а затем добавить в комбинацию флагов FVF
значение D3DFVF_DIFFUSE.
#define MY_FVF (D3DFVF_XYZRHW|D3DFVF_DIFFUSE)
Далее каждой вершине необходимо присвоить некоторый цвет. Это
можно проделать с помощью макроса-функции D3DCOLOR_XRGB(r, g, b),
где в качестве параметров передается тройка основных цветов (веса) –
красного, зеленого и синего. При этом, код, отвечающий за визуализацию
сцены, не претерпит никаких изменений.
Задание
Используя среду программирования Delphi, Builder или Visual Studio
создать программу работающую с Direct3D 9 и обладающую следующими
свойствами:
1. Инициализирующую Direct3D 9.
2. Закрашивающую экран чёрным цветом.
3. Выводящую на экран красный треугольник и по таймеру двигающую его
по экрану.
Требования к оформлению отчёта
Отчёт по работе не требуется.
10
Download