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

Двумерная графика с использованием Direct3D

Разбить на страницы
Показывать лекцию целиком

Библиотека DirectX

С ростом производительности персональных компьютеров и появлением видеоакселераторов нового поколения трехмерная графика быстро распространилась в большинстве сфер жизни пользователей: дома, в малом и среднем офисе, игровых залах. Одновременно с развитием новых технологий ускорения видеоизображения началось на первый взгляд скрытое, но ожесточенное сражение производителей чипсетов, видеокарт и программного обеспечения по завоеванию любви и признания конечного пользователя. Кроме этой конкурентной борьбы присутствует еще и более глобальная – признание одного из промышленных стандартов большинством разработчиков компьютерных игр и производителями графических видеокарт. Объектом борьбы в данном случае выступает один из прикладных программных интерфейсов ( API – Application Programming Interfaces ). На данный момент среди общего числа подобных API остались только две графические библиотеки, которые можно рассматривать как основные – DirectX корпорации Microsoft и OpenGL – компании Silicon Graphics.

DirectX – совокупность технологий, разработанных корпорацией Microsoft с целью превратить Windows в оптимальную платформу для мультимедийных приложений и компьютерных игр с полноцветной графикой, видео, трехмерной анимацией и объемным звуком. История появления технологии DirectX уходит к 1995 году, когда под пристальное внимание корпорации попала британская компания RenderMorphics с небольшим проектом, представленным на обычной выставке. Этот проект умел отображать неплохие трехмерные объекты в реальном времени на обычном персональном компьютере, доступном каждому. После приобретения данной компании, Microsoft приступает к разработке графической библиотеки под Windows 95. Данная разработка вылилась в создание нового API, который дал разработчикам игр более прямой доступ до аппаратного обеспечения, увеличив тем самым производительность игр под Windows. В данном тексте речь будет идти о девятой версии DirectX.

Вообще, DirectX – набор API функций, предоставляющий низкоуровневый интерфейс к аппаратным средствам (ускорители 3D графики, звуковые и сетевые платы) персонального компьютера. Этот набор функций позволяет не привязываться жестко к тем или иным аппаратным средствам и не требует написания аппаратно-зависимого кода (если аппаратные средства не поддерживают каких-либо возможностей, то они эмулируются на программном уровне).

DirectX представляет собой набор следующих основных компонент:

  • DirectX Graphics (компонент, который управляет всем графическим выводом; этот API предоставляет функции для работы с 2D и 3D рисованием)
  • DirectInput (компонент, включающий поддержку (API) таких устройств ввода как клавиатура, мышь, джойстик)
  • DirectPlay (компонент, поддерживающий работу с коммуникационной средой (сетью); не зависит от сетевого протокола и метода соединения)
  • DirectSound (компонент, позволяющий микшировать звук в реальном времени и предоставляющий прямой доступ к звуковой карте)
  • DirectShow (компонент для работы с новой периферией – цифровыми фото и видеокамерами)
  • Технология COM

    DirectX API базируется на технологии COM ( Component Object Model ) – компонентная модель объектов. Основная идея технологии COM заключается в работе с указателями на виртуальную таблицу функций. COM объект это обычный DLL файл, который зарегистрирован в системе. Доступ к COM объектам осуществляется через так называемые интерфейсы. Один COM объект может содержать в себе несколько интерфейсов. Один интерфейс представляет собой набор функций, объединенных общим назначением. Любой COM объект содержит в себе (наследуется от) интерфейс IUnknown.

    Интерфейс IUnknown обеспечивает два базовых свойства COM-объектов:

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

    Основной принцип работы с технологией COM выражается следующими постулатами:

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

  • Создать объект;
  • Получить указатель на соответствующий интерфейс;
  • Используя данный указатель вызвать нужный метод.
  • На языке C++ данный механизм вызова метода может быть реализован так:

    // создали объект и получили указатель на интерфейс IDirect3D9
    pd3d = Direct3DCreate9 (…);
    
    // вызвали метод CreateDevice полученного интерфейса (IDirect3D9)
    pd3d -> CreateDevice (…);

    Механизм интерфейсов имеет обратную совместимость, т.е. приложения, которые используют объекты, например, DirectX7, будут работать и с DirectX9. Следует особо отметить, что разработка приложений для DirectX может вестись с использованием языков высокого уровня: Visual Basic, C++, C#, Pascal (Delphi).

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

    Архитектура DirectX в своей работе предусматривает так называемый уровень абстрагирования аппаратных средств – HAL ( Hardware Abstraction Layer ), который функционирует как промежуточное звено между программным обеспечением и аппаратурой, позволяя разработчикам обращаться к тем или иным компонентам, не зная их марки, модели и других деталей. В результате чего такой уровень абстракции оборудования позволяет настроить работу при любом аппаратном обеспечении. Особо следует отметить, что драйверы устройств (видео-, звуковых и сетевых карт) обычно создаются (пишутся) самими производителями оборудования.

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

    В данном курсе автором будут рассматриваться примеры работы библиотеки . Для пользователей, которые планируют разрабатывать подобные проекты на языке 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 функция 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 имеются два аргумента:

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

    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 )
    Pascal
    direct3d.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);
    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) возвращает цвет из трех составляющих – красного, зеленого и синего цветов. Значения параметров должны лежать в диапазоне [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 );

    Вывод простейших примитивов

    Построение любой сцены в 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 для определения формата вершины представлены в следующей таблице:

    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(
    	  Тип выводимого примитива,
    	  Количество примитивов,
    	  Массив данных,
    	  Размер "шага в байтах" от одной вершины до другой
    )

    Первый параметр указывает на тип выводимого примитива и задается одной из следующих констант: 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) );

    Остановимся теперь на процедуре непосредственного вывода результатов на экран. Процесс непосредственного воспроизведения примитивов рекомендуют обрамлять двумя действиями. Перед отображением необходимо вызвать метод BeginScene, а после воспроизведения – метод EndScene интерфейса IDirect3DDevice9. Первый метод информирует устройство вывода (видеокарту), что следует подготовиться к воспроизведению результатов. Второй метод сообщает устройству о том, что процесс воспроизведения для текущего кадра закончен и теперь можно осуществлять переключение буферов рендеринга. Таким образом, процедура воспроизведения сцены должна выглядеть приблизительно следующим образом:

    C++
    VOID Render()
    {
    device->BeginScene();
    device->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0),
     1.0f, 0);
    device->SetFVF(…);
    device->DrawPrimitiveUP(…);
    device->EndScene();
    device->Present( NULL, NULL, NULL, NULL );
    }
    Pascal
    procedure Render;
    begin
      device.BeginScene;
      device.Clear(0,nil,D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.0,0);
      device.SetFVF(…);
      device.DrawPrimitiveUP(…);
      device.EndScene;
      device.Present(nil, nil, 0, nil);
    end;

    Рассмотрим оставшиеся типы примитивов, которые имеются в наличии у библиотеки Direct3D.

  • Вывод отрезков
  • Для построения независимых отрезков первым аргументом метода DrawPrimitiveUP необходимо указать константу D3DPT_LINELIST. При этом следует заметить, что количество выводимых примитивов (второй параметр метода) будет в два раза меньше количества точек в массиве.
  • Для построения связных отрезков первым аргументом метода DrawPrimitiveUP указывается константа D3DPT_LINESTRIP. При этом количество выводимых примитивов будет на единицу меньше количества точек в исходном массиве данных.
  • Вывод треугольников
  • Если первым аргументом метода DrawPrimitiveUP указывается константа D3DPT_TRIANGLELIST, то каждая триада (тройка) вершин задает три вершины одного (независимого) треугольника. Количества выводимых примитивов (треугольников) будет в три раза меньше чем размер массива данных.
  • Если первым аргументом метода DrawPrimitiveUP указывается константа D3DPT_TRIANGLESTRIP, то речь идет о группе связных треугольников. Первые три вершины задают первый треугольник, вторая, третья и четвертая определяют второй треугольник, третья, четвертая и пятая – третий и т.д. В результате получается лента соприкасающихся треугольников. Следует заметить, что использование связных треугольников самый экономный и эффективный способ построений.
  • Если первым аргументом метода DrawPrimitiveUP указывается константа D3DPT_TRIANGLEFAN, то строится группа связных треугольников, но данные здесь трактуются немного иначе. Треугольники связаны подобно раскрою зонтика или веера. По такой схеме удобно строить конусы и пирамиды в пространстве, выпуклые многоугольники и эллипсы в плоскости.
  • Все рассмотренные шесть видов примитивов по способу их построения можно представить в виде следующего рисунка:

    В предыдущих примерах фигурировали бинарные примитивы (белые фигуры на черном фоне). Чтобы окрасить примитивы нужно задать цвет каждой вершины примитива. Это проделывается в два этапа. Сначала мы должны изменить тип вершины, а затем добавить в комбинацию флагов FVF значение D3DFVF_DIFFUSE.

    C++
    struct MYVERTEX
    {
        FLOAT x, y, z, rhw;
        DWORD color;
    }
    
    #define MY_FVF (D3DFVF_XYZRHW|D3DFVF_DIFFUSE)
    Pascal
    type
        MyVertex = packed record
            x, y, z, rhw: Single;
            color: DWORD;
        end;
    
    const
          MY_FVF = D3DFVF_XYZRHW or D3DFVF_DIFFUSE;

    Далее каждой вершине необходимо присвоить некоторый цвет. Это можно проделать с помощью макроса-функции D3DCOLOR_XRGB(r, g, b), где в качестве параметров передается тройка основных цветов (веса) – красного, зеленого и синего. При этом, код, отвечающий за визуализацию сцены, не претерпит никаких изменений.

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

    С одной стороны данная триангуляция имеет 10 треугольников, каждый из которых содержит 3 вершины. Таким образом, для хранения 30 вершин при расходах 20 байт на каждую вершину, необходимо 600 байт машинной памяти. С другой стороны, можно хранить отдельно сами вершины и список номеров (индексов) вершин для каждого треугольника. В этом случае расходы на машинную память будут следующими: 9 вершин по 20 байт на каждую потребует 180 байт, 10 триплетов индексов вершин, например, по 2 байта на индекс потребуют 60 байт. Итого для такой организации хранения данных модели – в нашем случае триангуляции, необходимо 240 байт, что в два с половиной раза меньше чем при организации хранения вершин всех десяти треугольников. Использование дополнительного массива, который будет хранить только лишь номера вершин модели, позволит избежать повторения данных при задании вершин. Так, например, представленная триангуляция может быть описана следующим образом.

    10 треугольников = {

    1: (0,3,2),

    2: (0,1,3),

    3: (2,3,4),

    4: (3,6,4),

    5: (1,5,3),

    6: (3,5,6),

    7: (1,7,5),

    8: (5,7,6),

    9: (6,7,8),

    10: (4,6,8)

    } = 2 байта/индекс*

    3 индекса*

    10 треугольников =

    60 байт

    Программно этот подход может быть реализован с помощью вызова метода DrawIndexedPrimitiveUP интерфейса IDirect3DDevice9, который имеет 8 параметров. Первый параметр задает тип выводимых примитивов и представляет собой одну константу из уже известного набора. Второй параметр определяет минимальный вершинный индекс и, как правило, он устанавливается в значение ноль. Третий параметр определяет количество вершин в модели. Четвертый параметр задает количество выводимых примитивов. Пятый аргумент метода представляет собой указатель на массив, содержащий набор индексов. Шестой аргумент определяет формат индексных данных и может принимать два значения: D3DFMT_INDEX16 и D3DFMT_INDEX32. Т.е. под один индекс может быть отведено либо 16, либо 32 бита машинной памяти. Предпоследний, седьмой аргумент метода представляет собой указатель на массив, содержащий набор вершин модели. И последний параметр определяет количество байтов, необходимых для хранения одной вершины. Таким образом, обобщив эти строки можно записать формат вызова данного метода:

    DrawIndexedPrimitiveUP(
    1. тип выводимых примитивов,
    2. минимальный индекс вершин,
    3. количество вершин,
    4. количество выводимых примитивов,
    5. указатель на массив индексов,
    6. формат индексов,
    7. указатель на массив вершин,
    8. количество байтов для хранения одной вершины
    )

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

    C++
    struct MYVERTEX
    {
        FLOAT x, y, z, rhw;
        DWORD color;
    }
    
    #define MY_FVF (D3DFVF_XYZRHW|D3DFVF_DIFFUSE)
    
    WORD indices[] = {0,3,2, 0,1,3, 2,3,4, 3,6,4, 1,5,3, 3,5,6, 
    1,7,5, 5,7,6, 6,7,8, 4,6,8};
    
    MYVERTEX points[] = 
    {
        { 119, 354, 0, 0, D3DCOLOR_XRGB(255,0,0) },
        {  47, 248, 0, 0, D3DCOLOR_XRGB(0,255,0) },
        …
    }
    …
    device-> DrawIndexedPrimitiveUP(D3DPT_TRIANGLELIST, 0, 9, 10, 
    indices, D3DFMT_INDEX16, points, sizeof(MYVERTEX) );
    Pascal
    type
        MyVertex = packed record
            x, y, z, rhw: Single;
            color: DWORD;
        end;
    
    const
          MY_FVF = D3DFVF_XYZRHW or D3DFVF_DIFFUSE;
          indices: array [0..29] of Word =
          (0,3,2, 0,1,3, 2,3,4, 3,6,4, 1,5,3, 3,5,6, 
    1,7,5, 5,7,6, 6,7,8, 4,6,8);
    
    var points : array [0..8] of MyVertex;
    …
      points[0].x := 119;
      points[0].y := 354;
      points[0].color := D3DCOLOR_XRGB(255,0,0);
    …
    
    device.DrawIndexedPrimitiveUP(D3DPT_TRIANGLELIST, 0, 9, 10, 
    indices, D3DFMT_INDEX16, points, SizeOf(MyVertex));

    Результат построения триангуляции "с цветом" показан на следующем рисунке.

    Вообще говоря, функции вывода примитивов DrawPrimitiveUP и DrawIndexedPrimitiveUP являются довольно медленными с эффективной точки зрения, т.к. производят довольно много лишних операций. Для хранения вершин мы использовали обычные переменные (в нашем случае массивы), хотя для таких ситуаций предназначен специальный буфер вершин. Чтобы начать работы с этим буфером вершин необходимо проделать следующие шаги:

  • Объявить переменную, в которой будет храниться адрес буфера вершин. Программно это будет выглядеть так.
    C++ LPDIRECT3DVERTEXBUFFER9 VBuffer = NULL;
    Pascal var VBuffer: IDirect3DVertexBuffer9;
  • Воспользовавшись методом CreateVertexBuffer интерфейса IDirect3DDevice9 создать буфер вершин.
  • Заполнить буфер данными о вершинах. Этот шаг реализуется в три приема. Вначале необходимо запереть буфер, т.к. заполнение его может производиться только в закрытом состоянии. Достигается это вызовом метода Lock интерфейса IDirect3DVertexBuffer9. Второй шаг состоит в непосредственном копировании данных с помощью стандартной функции Win32API – memcpy(). И третий шаг заключается в отпирании буфера вершин с помощью метода Unlock интерфейса IDirect3DVertexBuffer9. Таким образом, этот шаг можно реализовать следующим образом:
    C++
    LPDIRECT3DVERTEXBUFFER9     VBuffer     = NULL;
    VOID* pBuff;
    …
    device->CreateVertexBuffer(sizeof(points), 0, MY_FVF,
                     D3DPOOL_DEFAULT, VBuffer, NULL );
    VBuffer->Lock( 0, sizeof(points), (void**)pBuff, 0 );
    memcpy( pBuff, points, sizeof(points) );
    VBuffer-> Unlock();
    Pascal
    var VBuffer: IDirect3DVertexBuffer9;
          pBuff: Pointer;
    …
    device.CreateVertexBuffer(SizeOf(points), 0, MY_FVF,  
                      D3DPOOL_DEFAULT, VBuffer, nil);
    VBuffer.Lock(0, SizeOf(points), pBuff, 0);
    Move( points, pBuff^, SizeOf(points) );
    VBuffer.Unlock;

    Разберем значение параметров метода CreateVertexBuffer(). Первый параметр задает размер буфера вершин в байтах. Второй аргумент определяет параметры работы с буфером и, как правило, всегда это значение выставляется в ноль. Третий параметр задает формат вершин буфера через набор FVF флагов. Четвертый параметр определяет месторасположение буфера вершин. Значение D3DPOOL_DEFAULT говорит о том, что библиотека сама позаботится о размещении буфера в памяти. Пятый аргумент задает адрес переменной, в которую будет помещен результат вызова метода, т.е. эта переменная будет хранить адрес буфера вершин. И Шестой параметр не используется, является зарезервированным в настоящее время и всегда должен быть пустым.

    Рассмотрим теперь значения параметров метода Lock. Первый параметр определяет смещение от начала буфера, с которого будет производиться запирание области (значение 0 указывает на то, что запирается весь буфер с самого начала). Второй аргумент задает размер запираемой области в байтах. Третий параметр возвращает адрес запираемой области. И последний аргумент задает набор флагов способа запирания и, как правило, всегда равен нулю.

  • И последний шаг – вывод примитивов на экран. Библиотека Direct3D позволяет выводить данные в несколько потоков. Созданный буфер вершин является примером одного такого потока. Вначале вывода сцены на экран необходимо связать наш буфер вершин с одним из потоков данных. Это реализуется с помощью вызова метода SetStreamSource интерфейса IDirect3DDevice9. Этот метод имеет четыре параметра. Первый из них определяет номер потока вывода. Если в программе используется только один буфер вершин, то этот параметр должен быть 0. Второй параметр содержит указатель на переменную, ассоциированную с буфером вершин. Третий – определяет смещение от начала буфера, с которого нужно производить считывание данных. Четвертый параметр задает размер одной вершины в байтах. Далее необходимо указать формат выводимых вершин. Это проделывается с помощью вызова метода SetFVF интерфейса IDirect3DDevice9. И затем производится непосредственный вывод примитивов с помощью функции DrawPrimitive интерфейса IDirect3DDevice9. Метод DrawPrimitive имеет три параметра: первый – тип выводимых примитивов, второй – индекс начальной выводимой вершины и третий определяет количество выводимых примитивов. Программно шаг вывода одного треугольника выглядит так:
    C++
    device->SetStreamSource( 0, VBuffer, 0, sizeof(MYVERTEX) );
    device->SetFVF( MY_FVF );
    device->DrawPrimitive( D3DPT_TRIANGLELIST , 0, 1 );
    Pascal
    device.SetStreamSource(0, VBuffer, 0, SizeOf(MyVertex));
    device.SetFVF(MY_FVF);
    device.DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);
  • Библиотека Direct3D имеет богатый набор по способу отображения выводимых примитивов. Программист может указать нужный ему способ отображения полигонов с помощью задания режима воспроизведения. Существует три режима воспроизведения полигонов:

  • режим вершинной модели определяет вывод только вершин полигонов без ребер и без закраски внутренних точек примитива и реализуется с помощью вызова метода SetRenderState(D3DRS_FILLMODE, D3DFILL_POINT) интерфейса IDirect3DDevice9 ;
  • режим каркасной модели задает вывод только ребер полигонов без закраски внутренних точек примитива и реализуется с помощью вызова метода SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME) интерфейса IDirect3DDevice9 ;
  • режим сплошной модели определяет вывод полигонов с закрашенными внутренними точками и реализуется с помощью вызова метода SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID) интерфейса IDirect3DDevice9.
  • Кроме того, сплошная модель вывода подразделяется на два вида закрашивания: плоское заполнение либо закрашивание с интерполяцией. Первое реализуется через вызов метода SetRenderState(D3DRS_SHADEMODE, D3DSHADE_FLAT), второе через SetRenderState(D3DRS_SHADEMODE, D3DSHADE_ GOURAUD).

    Данные режимы воспроизведения можно представить с помощью следующей схемы.

    Страницы:

    Библиотека DirectX

    С ростом производительности персональных компьютеров и появлением видеоакселераторов нового поколения трехмерная графика быстро распространилась в большинстве сфер жизни пользователей: дома, в малом и среднем офисе, игровых залах. Одновременно с развитием новых технологий ускорения видеоизображения началось на первый взгляд скрытое, но ожесточенное сражение производителей чипсетов, видеокарт и программного обеспечения по завоеванию любви и признания конечного пользователя. Кроме этой конкурентной борьбы присутствует еще и более глобальная – признание одного из промышленных стандартов большинством разработчиков компьютерных игр и производителями графических видеокарт. Объектом борьбы в данном случае выступает один из прикладных программных интерфейсов ( API – Application Programming Interfaces ). На данный момент среди общего числа подобных API остались только две графические библиотеки, которые можно рассматривать как основные – DirectX корпорации Microsoft и OpenGL – компании Silicon Graphics.

    DirectX – совокупность технологий, разработанных корпорацией Microsoft с целью превратить Windows в оптимальную платформу для мультимедийных приложений и компьютерных игр с полноцветной графикой, видео, трехмерной анимацией и объемным звуком. История появления технологии DirectX уходит к 1995 году, когда под пристальное внимание корпорации попала британская компания RenderMorphics с небольшим проектом, представленным на обычной выставке. Этот проект умел отображать неплохие трехмерные объекты в реальном времени на обычном персональном компьютере, доступном каждому. После приобретения данной компании, Microsoft приступает к разработке графической библиотеки под Windows 95. Данная разработка вылилась в создание нового API, который дал разработчикам игр более прямой доступ до аппаратного обеспечения, увеличив тем самым производительность игр под Windows. В данном тексте речь будет идти о девятой версии DirectX.

    Вообще, DirectX – набор API функций, предоставляющий низкоуровневый интерфейс к аппаратным средствам (ускорители 3D графики, звуковые и сетевые платы) персонального компьютера. Этот набор функций позволяет не привязываться жестко к тем или иным аппаратным средствам и не требует написания аппаратно-зависимого кода (если аппаратные средства не поддерживают каких-либо возможностей, то они эмулируются на программном уровне).

    DirectX представляет собой набор следующих основных компонент:

  • DirectX Graphics (компонент, который управляет всем графическим выводом; этот API предоставляет функции для работы с 2D и 3D рисованием)
  • DirectInput (компонент, включающий поддержку (API) таких устройств ввода как клавиатура, мышь, джойстик)
  • DirectPlay (компонент, поддерживающий работу с коммуникационной средой (сетью); не зависит от сетевого протокола и метода соединения)
  • DirectSound (компонент, позволяющий микшировать звук в реальном времени и предоставляющий прямой доступ к звуковой карте)
  • DirectShow (компонент для работы с новой периферией – цифровыми фото и видеокамерами)
  • Технология COM

    DirectX API базируется на технологии COM ( Component Object Model ) – компонентная модель объектов. Основная идея технологии COM заключается в работе с указателями на виртуальную таблицу функций. COM объект это обычный DLL файл, который зарегистрирован в системе. Доступ к COM объектам осуществляется через так называемые интерфейсы. Один COM объект может содержать в себе несколько интерфейсов. Один интерфейс представляет собой набор функций, объединенных общим назначением. Любой COM объект содержит в себе (наследуется от) интерфейс IUnknown.

    Интерфейс IUnknown обеспечивает два базовых свойства COM-объектов:

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

    Основной принцип работы с технологией COM выражается следующими постулатами:

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

  • Создать объект;
  • Получить указатель на соответствующий интерфейс;
  • Используя данный указатель вызвать нужный метод.
  • На языке C++ данный механизм вызова метода может быть реализован так:

    // создали объект и получили указатель на интерфейс IDirect3D9
    pd3d = Direct3DCreate9 (…);
    
    // вызвали метод CreateDevice полученного интерфейса (IDirect3D9)
    pd3d -> CreateDevice (…);

    Механизм интерфейсов имеет обратную совместимость, т.е. приложения, которые используют объекты, например, DirectX7, будут работать и с DirectX9. Следует особо отметить, что разработка приложений для DirectX может вестись с использованием языков высокого уровня: Visual Basic, C++, C#, Pascal (Delphi).

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

    Архитектура DirectX в своей работе предусматривает так называемый уровень абстрагирования аппаратных средств – HAL ( Hardware Abstraction Layer ), который функционирует как промежуточное звено между программным обеспечением и аппаратурой, позволяя разработчикам обращаться к тем или иным компонентам, не зная их марки, модели и других деталей. В результате чего такой уровень абстракции оборудования позволяет настроить работу при любом аппаратном обеспечении. Особо следует отметить, что драйверы устройств (видео-, звуковых и сетевых карт) обычно создаются (пишутся) самими производителями оборудования.

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

    В данном курсе автором будут рассматриваться примеры работы библиотеки . Для пользователей, которые планируют разрабатывать подобные проекты на языке 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 функция 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 имеются два аргумента:

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

    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 )
    Pascal
    direct3d.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);
    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) возвращает цвет из трех составляющих – красного, зеленого и синего цветов. Значения параметров должны лежать в диапазоне [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 );

    Вывод простейших примитивов

    Построение любой сцены в 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 для определения формата вершины представлены в следующей таблице:

    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(
    	  Тип выводимого примитива,
    	  Количество примитивов,
    	  Массив данных,
    	  Размер "шага в байтах" от одной вершины до другой
    )

    Первый параметр указывает на тип выводимого примитива и задается одной из следующих констант: 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) );

    Остановимся теперь на процедуре непосредственного вывода результатов на экран. Процесс непосредственного воспроизведения примитивов рекомендуют обрамлять двумя действиями. Перед отображением необходимо вызвать метод BeginScene, а после воспроизведения – метод EndScene интерфейса IDirect3DDevice9. Первый метод информирует устройство вывода (видеокарту), что следует подготовиться к воспроизведению результатов. Второй метод сообщает устройству о том, что процесс воспроизведения для текущего кадра закончен и теперь можно осуществлять переключение буферов рендеринга. Таким образом, процедура воспроизведения сцены должна выглядеть приблизительно следующим образом:

    C++
    VOID Render()
    {
    device->BeginScene();
    device->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0),
     1.0f, 0);
    device->SetFVF(…);
    device->DrawPrimitiveUP(…);
    device->EndScene();
    device->Present( NULL, NULL, NULL, NULL );
    }
    Pascal
    procedure Render;
    begin
      device.BeginScene;
      device.Clear(0,nil,D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.0,0);
      device.SetFVF(…);
      device.DrawPrimitiveUP(…);
      device.EndScene;
      device.Present(nil, nil, 0, nil);
    end;

    Рассмотрим оставшиеся типы примитивов, которые имеются в наличии у библиотеки Direct3D.

  • Вывод отрезков
  • Для построения независимых отрезков первым аргументом метода DrawPrimitiveUP необходимо указать константу D3DPT_LINELIST. При этом следует заметить, что количество выводимых примитивов (второй параметр метода) будет в два раза меньше количества точек в массиве.
  • Для построения связных отрезков первым аргументом метода DrawPrimitiveUP указывается константа D3DPT_LINESTRIP. При этом количество выводимых примитивов будет на единицу меньше количества точек в исходном массиве данных.
  • Вывод треугольников
  • Если первым аргументом метода DrawPrimitiveUP указывается константа D3DPT_TRIANGLELIST, то каждая триада (тройка) вершин задает три вершины одного (независимого) треугольника. Количества выводимых примитивов (треугольников) будет в три раза меньше чем размер массива данных.
  • Если первым аргументом метода DrawPrimitiveUP указывается константа D3DPT_TRIANGLESTRIP, то речь идет о группе связных треугольников. Первые три вершины задают первый треугольник, вторая, третья и четвертая определяют второй треугольник, третья, четвертая и пятая – третий и т.д. В результате получается лента соприкасающихся треугольников. Следует заметить, что использование связных треугольников самый экономный и эффективный способ построений.
  • Если первым аргументом метода DrawPrimitiveUP указывается константа D3DPT_TRIANGLEFAN, то строится группа связных треугольников, но данные здесь трактуются немного иначе. Треугольники связаны подобно раскрою зонтика или веера. По такой схеме удобно строить конусы и пирамиды в пространстве, выпуклые многоугольники и эллипсы в плоскости.
  • Все рассмотренные шесть видов примитивов по способу их построения можно представить в виде следующего рисунка:

    В предыдущих примерах фигурировали бинарные примитивы (белые фигуры на черном фоне). Чтобы окрасить примитивы нужно задать цвет каждой вершины примитива. Это проделывается в два этапа. Сначала мы должны изменить тип вершины, а затем добавить в комбинацию флагов FVF значение D3DFVF_DIFFUSE.

    C++
    struct MYVERTEX
    {
        FLOAT x, y, z, rhw;
        DWORD color;
    }
    
    #define MY_FVF (D3DFVF_XYZRHW|D3DFVF_DIFFUSE)
    Pascal
    type
        MyVertex = packed record
            x, y, z, rhw: Single;
            color: DWORD;
        end;
    
    const
          MY_FVF = D3DFVF_XYZRHW or D3DFVF_DIFFUSE;

    Далее каждой вершине необходимо присвоить некоторый цвет. Это можно проделать с помощью макроса-функции D3DCOLOR_XRGB(r, g, b), где в качестве параметров передается тройка основных цветов (веса) – красного, зеленого и синего. При этом, код, отвечающий за визуализацию сцены, не претерпит никаких изменений.

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

    С одной стороны данная триангуляция имеет 10 треугольников, каждый из которых содержит 3 вершины. Таким образом, для хранения 30 вершин при расходах 20 байт на каждую вершину, необходимо 600 байт машинной памяти. С другой стороны, можно хранить отдельно сами вершины и список номеров (индексов) вершин для каждого треугольника. В этом случае расходы на машинную память будут следующими: 9 вершин по 20 байт на каждую потребует 180 байт, 10 триплетов индексов вершин, например, по 2 байта на индекс потребуют 60 байт. Итого для такой организации хранения данных модели – в нашем случае триангуляции, необходимо 240 байт, что в два с половиной раза меньше чем при организации хранения вершин всех десяти треугольников. Использование дополнительного массива, который будет хранить только лишь номера вершин модели, позволит избежать повторения данных при задании вершин. Так, например, представленная триангуляция может быть описана следующим образом.

    10 треугольников = {

    1: (0,3,2),

    2: (0,1,3),

    3: (2,3,4),

    4: (3,6,4),

    5: (1,5,3),

    6: (3,5,6),

    7: (1,7,5),

    8: (5,7,6),

    9: (6,7,8),

    10: (4,6,8)

    } = 2 байта/индекс*

    3 индекса*

    10 треугольников =

    60 байт

    Программно этот подход может быть реализован с помощью вызова метода DrawIndexedPrimitiveUP интерфейса IDirect3DDevice9, который имеет 8 параметров. Первый параметр задает тип выводимых примитивов и представляет собой одну константу из уже известного набора. Второй параметр определяет минимальный вершинный индекс и, как правило, он устанавливается в значение ноль. Третий параметр определяет количество вершин в модели. Четвертый параметр задает количество выводимых примитивов. Пятый аргумент метода представляет собой указатель на массив, содержащий набор индексов. Шестой аргумент определяет формат индексных данных и может принимать два значения: D3DFMT_INDEX16 и D3DFMT_INDEX32. Т.е. под один индекс может быть отведено либо 16, либо 32 бита машинной памяти. Предпоследний, седьмой аргумент метода представляет собой указатель на массив, содержащий набор вершин модели. И последний параметр определяет количество байтов, необходимых для хранения одной вершины. Таким образом, обобщив эти строки можно записать формат вызова данного метода:

    DrawIndexedPrimitiveUP(
    1. тип выводимых примитивов,
    2. минимальный индекс вершин,
    3. количество вершин,
    4. количество выводимых примитивов,
    5. указатель на массив индексов,
    6. формат индексов,
    7. указатель на массив вершин,
    8. количество байтов для хранения одной вершины
    )

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

    C++
    struct MYVERTEX
    {
        FLOAT x, y, z, rhw;
        DWORD color;
    }
    
    #define MY_FVF (D3DFVF_XYZRHW|D3DFVF_DIFFUSE)
    
    WORD indices[] = {0,3,2, 0,1,3, 2,3,4, 3,6,4, 1,5,3, 3,5,6, 
    1,7,5, 5,7,6, 6,7,8, 4,6,8};
    
    MYVERTEX points[] = 
    {
        { 119, 354, 0, 0, D3DCOLOR_XRGB(255,0,0) },
        {  47, 248, 0, 0, D3DCOLOR_XRGB(0,255,0) },
        …
    }
    …
    device-> DrawIndexedPrimitiveUP(D3DPT_TRIANGLELIST, 0, 9, 10, 
    indices, D3DFMT_INDEX16, points, sizeof(MYVERTEX) );
    Pascal
    type
        MyVertex = packed record
            x, y, z, rhw: Single;
            color: DWORD;
        end;
    
    const
          MY_FVF = D3DFVF_XYZRHW or D3DFVF_DIFFUSE;
          indices: array [0..29] of Word =
          (0,3,2, 0,1,3, 2,3,4, 3,6,4, 1,5,3, 3,5,6, 
    1,7,5, 5,7,6, 6,7,8, 4,6,8);
    
    var points : array [0..8] of MyVertex;
    …
      points[0].x := 119;
      points[0].y := 354;
      points[0].color := D3DCOLOR_XRGB(255,0,0);
    …
    
    device.DrawIndexedPrimitiveUP(D3DPT_TRIANGLELIST, 0, 9, 10, 
    indices, D3DFMT_INDEX16, points, SizeOf(MyVertex));

    Результат построения триангуляции "с цветом" показан на следующем рисунке.

    Вообще говоря, функции вывода примитивов DrawPrimitiveUP и DrawIndexedPrimitiveUP являются довольно медленными с эффективной точки зрения, т.к. производят довольно много лишних операций. Для хранения вершин мы использовали обычные переменные (в нашем случае массивы), хотя для таких ситуаций предназначен специальный буфер вершин. Чтобы начать работы с этим буфером вершин необходимо проделать следующие шаги:

  • Объявить переменную, в которой будет храниться адрес буфера вершин. Программно это будет выглядеть так.
    C++ LPDIRECT3DVERTEXBUFFER9 VBuffer = NULL;
    Pascal var VBuffer: IDirect3DVertexBuffer9;
  • Воспользовавшись методом CreateVertexBuffer интерфейса IDirect3DDevice9 создать буфер вершин.
  • Заполнить буфер данными о вершинах. Этот шаг реализуется в три приема. Вначале необходимо запереть буфер, т.к. заполнение его может производиться только в закрытом состоянии. Достигается это вызовом метода Lock интерфейса IDirect3DVertexBuffer9. Второй шаг состоит в непосредственном копировании данных с помощью стандартной функции Win32API – memcpy(). И третий шаг заключается в отпирании буфера вершин с помощью метода Unlock интерфейса IDirect3DVertexBuffer9. Таким образом, этот шаг можно реализовать следующим образом:
    C++
    LPDIRECT3DVERTEXBUFFER9     VBuffer     = NULL;
    VOID* pBuff;
    …
    device->CreateVertexBuffer(sizeof(points), 0, MY_FVF,
                     D3DPOOL_DEFAULT, VBuffer, NULL );
    VBuffer->Lock( 0, sizeof(points), (void**)pBuff, 0 );
    memcpy( pBuff, points, sizeof(points) );
    VBuffer-> Unlock();
    Pascal
    var VBuffer: IDirect3DVertexBuffer9;
          pBuff: Pointer;
    …
    device.CreateVertexBuffer(SizeOf(points), 0, MY_FVF,  
                      D3DPOOL_DEFAULT, VBuffer, nil);
    VBuffer.Lock(0, SizeOf(points), pBuff, 0);
    Move( points, pBuff^, SizeOf(points) );
    VBuffer.Unlock;

    Разберем значение параметров метода CreateVertexBuffer(). Первый параметр задает размер буфера вершин в байтах. Второй аргумент определяет параметры работы с буфером и, как правило, всегда это значение выставляется в ноль. Третий параметр задает формат вершин буфера через набор FVF флагов. Четвертый параметр определяет месторасположение буфера вершин. Значение D3DPOOL_DEFAULT говорит о том, что библиотека сама позаботится о размещении буфера в памяти. Пятый аргумент задает адрес переменной, в которую будет помещен результат вызова метода, т.е. эта переменная будет хранить адрес буфера вершин. И Шестой параметр не используется, является зарезервированным в настоящее время и всегда должен быть пустым.

    Рассмотрим теперь значения параметров метода Lock. Первый параметр определяет смещение от начала буфера, с которого будет производиться запирание области (значение 0 указывает на то, что запирается весь буфер с самого начала). Второй аргумент задает размер запираемой области в байтах. Третий параметр возвращает адрес запираемой области. И последний аргумент задает набор флагов способа запирания и, как правило, всегда равен нулю.

  • И последний шаг – вывод примитивов на экран. Библиотека Direct3D позволяет выводить данные в несколько потоков. Созданный буфер вершин является примером одного такого потока. Вначале вывода сцены на экран необходимо связать наш буфер вершин с одним из потоков данных. Это реализуется с помощью вызова метода SetStreamSource интерфейса IDirect3DDevice9. Этот метод имеет четыре параметра. Первый из них определяет номер потока вывода. Если в программе используется только один буфер вершин, то этот параметр должен быть 0. Второй параметр содержит указатель на переменную, ассоциированную с буфером вершин. Третий – определяет смещение от начала буфера, с которого нужно производить считывание данных. Четвертый параметр задает размер одной вершины в байтах. Далее необходимо указать формат выводимых вершин. Это проделывается с помощью вызова метода SetFVF интерфейса IDirect3DDevice9. И затем производится непосредственный вывод примитивов с помощью функции DrawPrimitive интерфейса IDirect3DDevice9. Метод DrawPrimitive имеет три параметра: первый – тип выводимых примитивов, второй – индекс начальной выводимой вершины и третий определяет количество выводимых примитивов. Программно шаг вывода одного треугольника выглядит так:
    C++
    device->SetStreamSource( 0, VBuffer, 0, sizeof(MYVERTEX) );
    device->SetFVF( MY_FVF );
    device->DrawPrimitive( D3DPT_TRIANGLELIST , 0, 1 );
    Pascal
    device.SetStreamSource(0, VBuffer, 0, SizeOf(MyVertex));
    device.SetFVF(MY_FVF);
    device.DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);
  • Библиотека Direct3D имеет богатый набор по способу отображения выводимых примитивов. Программист может указать нужный ему способ отображения полигонов с помощью задания режима воспроизведения. Существует три режима воспроизведения полигонов:

  • режим вершинной модели определяет вывод только вершин полигонов без ребер и без закраски внутренних точек примитива и реализуется с помощью вызова метода SetRenderState(D3DRS_FILLMODE, D3DFILL_POINT) интерфейса IDirect3DDevice9 ;
  • режим каркасной модели задает вывод только ребер полигонов без закраски внутренних точек примитива и реализуется с помощью вызова метода SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME) интерфейса IDirect3DDevice9 ;
  • режим сплошной модели определяет вывод полигонов с закрашенными внутренними точками и реализуется с помощью вызова метода SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID) интерфейса IDirect3DDevice9.
  • Кроме того, сплошная модель вывода подразделяется на два вида закрашивания: плоское заполнение либо закрашивание с интерполяцией. Первое реализуется через вызов метода SetRenderState(D3DRS_SHADEMODE, D3DSHADE_FLAT), второе через SetRenderState(D3DRS_SHADEMODE, D3DSHADE_ GOURAUD).

    Данные режимы воспроизведения можно представить с помощью следующей схемы.

    Вернуться к учебному плану