netlib.narod.ru< Назад | Оглавление | Далее >

Графическое ядро

Вы добрались до серьезного материала! Графическое ядро составляет значительную часть ядра игры и является самым большим и сложным объектом ядра, который мы рассмотрим. Возможности классов графического ядра представлены в таблице 6.1.


Таблица 6.1. Компоненты графического ядра



Класс Описание

cGraphics Обеспечивает инициализацию Direct3D, включение режимов визуализации, установку текстур, материалов и освещения.
cTexture Хранит одну текстуру и предоставляет функции для рисования двухмерных фрагментов текстуры на экране.
cMaterial Хранит описание одного материала.
cLight Хранит описание одного источника света.
cFont Служит оберткой для объекта ID3DXFont о котором говорилось в главе 2, «Рисование с DirectX Graphics».
cVertexBuffer Упрощает работу с буферами вершин.
cWorldPosition Управляет матрицей мирового преобразования, позволяя вам быстро позиционировать, масштабировать и вращать объекты.
cCamera Содержит матрицу преобразования вида, которую можно изменять через интерфейс объекта.
cMesh Содержит список сеток, загруженных из X-файла и их материалов. Используйте этот класс вместе с классом cObject.
cObject Представляет один объект в трехмерном мире. Управляет ориентацией объекта, сеткой и состоянием анимации.
cAnimation Содержит список анимаций, загруженных из X-файла. Используйте этот класс вместе с классом cObject.


Большинство классов графического ядра просты для использования, так что вы можете только скользнуть взглядом по их возможностям. Объявления классов каждого объекта ядра игры самодокументируемы, поэтому внимательно читайте их. Вы можете начать с cGraphics — прародителя всех объектов графического ядра.

Графическая система и cGraphics

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

class cGraphics
{
  protected:
    HWND m_hWnd;        // Дескриптор родительского окна

    IDirect3D9 *m_pD3D;             // Объект Direct3D
    IDirect3DDevice9 *m_pD3DDevice; // Объект устройства

    ID3DXSprite *m_pSprite; // Объект двухмерного спрайта

    D3DDISPLAYMODE m_d3ddm; // Свойства видеорежима

    BOOL m_Windowed; // Флаг включения оконного режима
    BOOL m_ZBuffer;  // Флаг использования Z-буфера
    BOOL m_HAL;      // Флаг аппаратного ускорения

    long m_Width;  // Ширина для видеорежима
    long m_Height; // Высота для видеорежима
    char m_BPP;    // Количество бит в пикселе

    char m_AmbientRed;   // Красная составляющая фонового света
    char m_AmbientGreen; // Зеленая составляющая фонового света
    char m_AmbientBlue;  // Синяя составляющая фонового света

  public:
    cGraphics(); // Конструктор
    ~cGraphics(); // Деструктор

    // Функции для получения COM-интерфейсов
    IDirect3D9 *GetDirect3DCOM();
    IDirect3DDevice9 *GetDeviceCOM();
    ID3DXSprite *GetSpriteCOM();

    BOOL Init();     // Инициализация графического объекта
    BOOL Shutdown(); // Завершение работы графического объекта

    // Инициализация видеорежима по указанным параметрам
    BOOL SetMode(HWND hWnd, BOOL Windowed = TRUE,
                 BOOL UseZBuffer = FALSE,
                 long Width = 0, long Height = 0,
                 char BPP = 0);

    // Функции возвращают количество видеорежимов
    // и информацию о них
    long GetNumDisplayModes(D3DFORMAT Format);
    BOOL GetDisplayModeInfo(long Num, D3DDISPLAYMODE *Mode),
                            D3DFORMAT Format);

    // Возвращает количество бит в пикселе
    // для заданного видеорежима
    char GetFormatBPP(D3DFORMAT Format);

    // Смотрит, существует ли указанный видеорежим.
    // Задайте Format и Windowed, затем установите HAL
    // в TRUE для проверки аппаратного ускорения или
    // в FALSE для проверки эмуляции
    BOOL CheckFormat(D3DFORMAT Format,BOOL Windowed,BOOL HAL);

    BOOL Display();     // Отображает вторичный буфер
                        // (выполняя переключение страниц)

    BOOL BeginScene();  // Вызывается перед началом визуализации чего-либо
    BOOL EndScene();    // Вызывается, когда все визуализировано

    BOOL BeginSprite(); // Вызывается чтобы разрешить рисование спрайтов
    BOOL EndSprite();   // Вызывается для завершения рисования спрайтов

    // Функции для очистки экрана и/или Z-буфера
    BOOL Clear(long Color = 0, float ZBuffer = 1.0f);
    BOOL ClearDisplay(long Color = 0);
    BOOL ClearZBuffer(float ZBuffer = 1.0f);

    // Функции для получения размеров экрана и количества бит в пикселе
    long GetWidth();
    long GetHeight();
    char GetBPP();

    // Функции для проверки наличия аппаратного ускорения и Z-буфера
    // Используются только после установки видеорежима
    BOOL GetHAL();
    BOOL GetZBuffer();

    // Установка нового преобразования перспективы
    BOOL SetPerspective(float FOV=D3DX_PI/4,
                        float Aspect=1.3333f,
                        float Near=1.0f, float Far=10000.0f);

    // Функции для установки мирового преобразования
    // и преобразования вида
    BOOL SetWorldPosition(cWorldPosition *WorldPos);
    BOOL SetCamera(cCamera *Camera);

    // Функции для установки текущих света, материала и текстуры
    BOOL SetLight(long Num, cLight *Light);
    BOOL SetMaterial(cMaterial *Material);
    BOOL SetTexture(short Num, cTexture *Texture);

    // Установка и получение компонент фонового освещения
    BOOL SetAmbientLight(char Red, char Green, char Blue);
    BOOL GetAmbientLight(char *Red, char *Green, char *Blue);

    // Включение или выключение заданного источника света (0-n)
    BOOL EnableLight(long Num, BOOL Enable = TRUE);

    // Включение и выключение освещения, z-буферизации,
    // альфа-смешивания и альфа-проверки. Для смешивания указываются
    // необязательные функции смешивания
    BOOL EnableLighting(BOOL Enable = TRUE);
    BOOL EnableZBuffer(BOOL Enable = TRUE);
    BOOL EnableAlphaBlending(BOOL Enable = TRUE,
                             DWORD Src = D3DBLEND_SRCALPHA,
                             DWORD Dest = D3DBLEND_INVSRCALPHA);
    BOOL EnableAlphaTesting(BOOL Enable = TRUE);
};

С cGraphics вы можете сделать многое, но все начинается с вызова cGraphics::Init. Потом вы можете перечислить различные видеорежимы, или перескочить и вызвать cGraphics::SetMode, чтобы все закрутилось. Как минимум, функции SetMode требуется дескриптор родительского окна. По умолчанию будет установлен оконный режим (в противоположность полноэкранному) с выводом без Z-буфера.

ПРИМЕЧАНИЕ
Функция cGraphics::SetMode очень талантлива. Она определяет есть ли поддержка аппаратного ускорения и Z-буфера. Если эти возможности отсутствуют, функция SetMode будет использовать предоставляемую Direct3D программную эмуляцию функций трехмерной графики и отключит Z-буфер, чтобы гарантировать установку режима.

Если вы хотите использовать полноэкранный режим, то должны присвоить параметру Windowed значение FALSE и указать допустимые значения Width, Height и количества бит в пикселе (BPP). Если какому-то из этих параметров вы присвоите 0, SetMode будет использовать текущие параметры рабочего стола. Если вы используете оконный режим, и задали значения Width или Height, то размеры родительского окна будут изменены в соответствии с новыми значениями.

Теперь вы можете недоумевать, что делать дальше. Перед тем, как что-либо визуализировать, вы должны вызвать функцию cGraphics::BeginScene. После того, как визуализация закончена, вызовите cGraphics::EndScene. Затем вызовите cGraphics::Display для отображения вашей графики.

Хотите очищать экран перед визуализацией? Делайте это с помощью трех функций очистки. Теперь вы можете включать источники света, материалы и текстуры (как показано в этом разделе), — cGraphics работает так, как я описал в главе 2, так что здесь вас ничто не должно пугать.

ВНИМАНИЕ!
Если вы не используете Z-буфер, не вызывайте функцию Clear, поскольку она получает значение для Z-буфера. Вместо нее используйте функцию ClearDisplay.

Для установки и включения освещения вы вызываете функцию EnableLighting. Альфа-смешивание позволяет добиться потрясающих результатов, и вы можете задавать необходимые коэффициенты смешивания (для приемника и для источника). Альфа-проверка помогает вам рисовать эти надоедливые прозрачные текстуры (как показано в главе 2).

Изображения и cTexture

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

class cTexture
{
  protected:
    cGraphics *m_Graphics;           // Родительский cGraphics
    IDirect3DTexture9 *m_Texture;    // COM-интерфейс текстуры
    unsigned long m_Width, m_Height; // Размеры текстуры

  public:
    cTexture(); // Конструктор
    ~cTexture(); // Деструктор

    IDirect3DTexture9 *GetTextureCOM(); // Возвращает COM-интерфейс
                                        // текстуры

    // Загружает текстуру из файла
    BOOL Load(cGraphics *Graphics, char *Filename,
              DWORD Transparent = 0,
              D3DFORMAT Format = D3DFMT_UNKNOWN);

    // Создает текстуру, используя указанные размеры и формат
    BOOL Create(cGraphics *Graphics,
                DWORD Width, DWORD Height, D3DFORMAT Format);

    // Конфигурирует класс cTexture на основе существующего
    // экземпляра объекта IDirect3DTexture9
    BOOL Create(cGraphics *Graphics, \
                IDirect3DTexture9 *Texture);

    BOOL Free();     // Освобождает объект текстуры

    BOOL IsLoaded(); // Возврашает TRUE если текстура загружена

    long GetWidth();       // Возвращает ширину (шаг) текстуры
    long GetHeight();      // Возвращает высоту текстуры
    D3DFORMAT GetFormat(); // Возвращает формат хранения текстуры

    // Рисует двухмерный фрагмент текстуры на устройстве
    BOOL Blit(long DestX, long DestY, 
              long SrcX = 0, long SrcY = 0,
              long Width = 0, long Height = 0,
              float XScale = 1.0f, float YScale = 1.0f,
              D3DCOLOR Color = 0xFFFFFFFF);
};

Текстура в класс cTexture может быть загружена из двух источников: из файла с изображением на диске или из существующего объекта IDirect3DTexture9. Если вы загружаете изображение с диска, вызовите cTexture::Load. Этой функции для работы требуется пара параметров: во-первых, ранее инициализированный объект cGraphics, и, во-вторых, имя загружаемого файла с изображением.

Есть еще два необязательных аргумента — цветовой ключ для прозрачности (если вы используете текстуры с прозрачными пикселями) и формат хранения. По умолчанию для Transparent используется значение 0, сообщающее функции Load, что прозрачные пиксели не используются. Предоставление значения типа D3DCOLOR_RGBA меняет ситуацию (убедитесь, что вы указали значение 255 для альфа-составляющей).

Когда используете Format, укажите формат хранения текстуры Direct3D, такой как D3DFMT_A1R5G5B5. Помните, что если у текстуры есть прозрачные пиксели, у нее должен быть альфа-канал, так что убедитесь, что используете такой формат как D3DFMT_A1R5G5B5 или D3DFMT_A8R8G8B8.

Наиболее вероятно, что вы будете использовать класс cTexture совместно с функцией cGraphics::SetTexture для рисования текстурированных полигонов. С другой стороны, если вы используете растровое изображение объекта текстуры для рисования непосредственно на экране, можно воспользоваться функцией cTexture::Blit, использующей специальный объект, называемый ID3DXSprite. Пока вы ничего не знаете про ID3DXSprite — мы познакомимся с ним в главе 7, «Использование двухмерной графики».

Сейчас я покажу, как использовать функцию Blit. Вам необходимо задать координаты места назначения, где текстура будет нарисована на экране, а также координаты верхнего левого угла источника, ширину, высоту, коэффициенты масштабирования и значение модуляции цвета, которое вы хотите использовать. В главе 7 мы более подробно рассмотрим использование функции Blit, а сейчас вот быстрый пример загрузки текстуры (называемой texture.bmp) и ее отображения на экране:

// g_Graphics = ранее инициализированный объект cGraphics
cTexture Texture;
Texture.Load(&g_Graphics, "texture.bmp");

// Рисуем текстуру в точке экрана 0,0 (используя двухмерный метод)
Texture.Blit(0,0);

Texture.Free(); // Выгружаем текстуру из памяти

Цвета и cMaterial

В главе 2 обсуждалась важность использования материалов, меняющих визуальное представление визуализируемых объектов путем смены цвета рисуемых граней. Чтобы вам было проще менять цвета материалов, воспользуйтесь классом cMaterial:

class cMaterial
{
  protected:
    D3DMATERIAL9 m_Material; // Структура данных материала

  public:
    cMaterial(); // Конструктор

    D3DMATERIAL9 *GetMaterial(); // Возвращает объект D3DMATERIAL8

    // Установка и получение рассеиваемой составляющей цвета
    BOOL SetDiffuseColor(char Red, char Green, char Blue);
    BOOL GetDiffuseColor(char *Red, char *Green, char *Blue);

    // Установка и получение фоновой составляющей цвета
    BOOL SetAmbientColor(char Red, char Green, char Blue);
    BOOL GetAmbientColor(char *Red, char *Green, char *Blue);

    // Установка и получение отражаемой составляющей цвета
    BOOL SetSpecularColor(char Red, char Green, char Blue);
    BOOL GetSpecularColor(char *Red, char *Green, char *Blue);

    // Установка и получение испускаемой составляющей цвета
    BOOL SetEmissiveColor(char Red, char Green, char Blue);
    BOOL GetEmissiveColor(char *Red, char *Green, char *Blue);

    // Установка и получение мощности
    BOOL SetPower(float Power);
    float GetPower(float Power);
};

Как видите, класс cMaterial содержит одну структуру D3DMATERIAL9 и предоставляет функции для установки и получения различных цветовых составляющих. Чтобы установить цветовую составляющую, укажите величины ее компонентов в диапазоне от 0 до 255. Для получения цветовой составляющей передайте соответствующей функции указатели на переменные типа char.

Вот пример использования cMaterial для создания желтого материала:

// g_Graphics = ранее инициализированный объект cGraphics
cMaterial YellowMaterial;

YellowMaterial.SetDiffuseColor(255,255,0);
YellowMaterial.SetAmbientColor(255,255,0);

g_Graphics.SetMaterial(&YellowMaterial); // Установка материала

 

ПРИМЕЧАНИЕ
При создании экземпляра класса cMaterial происходит очистка члена m_Material и создается полностью белый материал.

Вы используете класс cMaterial совместно с функцией cGraphics::SetMaterial для установки текущего материала визуализации.

Освещение с cLight

Источники света — простые игрушки, почти как материалы. С источниками света можно выполнять множество операций различными способами, поэтому я оборачиваю все это (по крайней мере, все о чем вы прочитали в главе 2) в класс с именем cLight:

class cLight
{
  protected:
    D3DLIGHT9 m_Light; // Структура данных источника света

  public:
    cLight(); // Конструктор

    D3DLIGHT9 *GetLight(); // Получение структуры данных источника света

    BOOL SetType(D3DLIGHTTYPE Type); // Установка типа источника света:
                                     // D3DLIGHT_POINT
                                     // D3DLIGHT_SPOT
                                     // D3DLIGHT_DIRECTIONAL

    // Абсолютное или относительное перемещение
    //источника света из текущей позиции
    BOOL Move(float XPos, float YPos, float ZPos);
    BOOL MoveRel(float XPos, float YPos, float ZPos);

    // Получение текущей позиции источника света
    // в указанные переменные
    BOOL GetPos(float *XPos, float *YPos, float *ZPos);

    // Установка абсолютного или относительного
    // направления лучей света
    BOOL Point(float XPos, float YPos, float ZPos);
    BOOL PointRel(float XPos, float YPos, float ZPos);

    // Получение текущего направления источника света
    // в указанные переменные
    BOOL GetDirection(float *XPos, float *YPos, float *ZPos);

    // Установка и получение различных цветовых компонент
    BOOL SetDiffuseColor(char Red, char Green, char Blue);
    BOOL GetDiffuseColor(char *Red, char *Green, char *Blue);
    BOOL SetSpecularColor(char Red, char Green, char Blue);
    BOOL GetSpecularColor(char *Red, char *Green, char *Blue);
    BOOL SetAmbientColor(char Red, char Green, char Blue);
    BOOL GetAmbientColor(char *Red, char *Green, char *Blue);

    // Установка и получение дальности освещения
    BOOL  SetRange(float Range);
    float GetRange();

    // Установка и получение значения затухания
    BOOL  SetFalloff(float Falloff);
    float GetFalloff();

    // Установка и получение различных коэффициентов затухания
    BOOL  SetAttenuation0(float Attenuation);
    float GetAttenuation0();
    BOOL  SetAttenuation1(float Attenuation);
    float GetAttenuation1();
    BOOL  SetAttenuation2(float Attenuation);
    float GetAttenuation2();

    // Установка и получение значения Theta
    BOOL SetTheta(float Theta);
    float GetTheta();

    // Установка и получение значения Phi
    BOOL SetPhi(float Phi);
    float GetPhi();
};

Чтобы использовать источники света в вашем собственном проекте вам необходимо объявить экземпляр класса cLight, указать тип используемого источника света (выбрав из стандартных типов источников света Direct3D, как показано в комментариях), задать цвет и местоположение (и, если необходимо, направление) где вы хотите разместить источник света. Чтобы установить источник света в сцене, воспользуйтесь функцией cGraphics::SetLight, показанной ранее в разделе «Графическая система и cGraphics».

ПРИМЕЧАНИЕ
Конструктор класса cLight создает белый точечный источник света (см. главу 2), размещенный в точке с координатами 0, 0, 0. Также учтите, что при вызове Point или PointRel вектор направления нормализуется (его компоненты будут меньше или равны 1.0, как говорилось в главе 2).

Вот пример создания белого источника направленного света:

// g_Graphics = ранее инициализированный объект cGraphics
cLight DirLight;

DirLight.SetType(D3DLIGHT_DIRECTIONAL);
DirLight.Move(0.0, 10.0f, 0.0f);   // Помещаем в 10 единицах над
                                   // началом координат
DirLight.Point(0.0, -1.0, 0.0f);   // Направляем вниз

g_Graphics.SetLight(0, &DirLight); // Устанавливаем источник света 0
g_Graphics.EnableLight(0, TRUE);   // Включаем освещение

Текст и шрифты с использованием cFont

Хотя иметь дело с объектом ID3DXFont достаточно просто, установка шрифта может вызвать затруднения. Для быстрой и простой инициализации шрифтов в вашем проекте вы можете использовать класс cFont:

class cFont
{
  private:
    ID3DXFont *m_Font; // COM-объект шрифта

  public:
    cFont();  // Конструктор
    ~cFont(); // Деструктор

    ID3DXFont *GetFontCOM(); // Возвращает COM-объект шрифта

    // Создание и освобождение шрифта
    BOOL Create(cGraphics *Graphics, char *Name,
                long Size = 16, BOOL Bold = FALSE,
                BOOL Italic = FALSE, BOOL Underline = FALSE,
                BOOL Strikeout = FALSE);
    BOOL Free();

    // Начало и завершение операций рисования текста
    BOOL Begin();
    BOOL End();

    // Печать указанного текста
    BOOL Print(char *Text, long XPos, long YPos,
               long Width = 0, long Height = 0,
               D3DCOLOR Color = 0xFFFFFFFF, DWORD Format = 0);
};

Чтобы начать использовать шрифт, вы должны создать его функцией cFont::Create. Вы передаете ей ранее инициализированный объект cGraphics вместе с именем шрифта (таким, как Arial или Times New Roman) и указываете высоту шрифта в пикселах. В качестве необязательных параметров вы можете указать полужирное, курсивное, подчеркнутое и перечеркнутое начертание шрифта.

Обратите внимание на пару функций cFont::Begin и cFont::End, сообщающих Direct3D, что вы собираетесь начать рисование текста, и что вы завершили печать текста, соответственно. Вам не требуется явно вызывать эти функции, поскольку метод Print делает это за вас (если вы не сделали это сами). Однако, отсутствие вызовов Begin и End замедляет вывод текста, поскольку функция Print будет постоянно вызывать Begin и End каждый раз, когда вы печатаете очередную строку текста.

Для печати строки текста вы вызываете функцию cFont::Рrint, передавая ей указатель на текст, который хотите напечатать, координаты, с которых начнется печать, размеры ограничивающего прямоугольника, по которому будет отсекаться текст (по умолчанию значения Width и Height равны 0, что означает печать на всем экране), цвет текста (по умолчанию белый; используйте для задания цвета макрос D3DCOLOR_RGBA) и форматирование текста (комбинация флагов, перечисленных в таблице 6.2).


Таблица 6.2. Флаги форматирования cFont::Print



Флаг Описание

DT_BOTTOM Выравнивает текст по нижнему краю ограничивающего прямоугольника.
DT_CENTER Центрирует текст по горизонтали в ограничивающем прямоугольнике.
DT_LEFT Выравнивает текст по левому краю ограничивающего прямоугольника.
DT_NOCLIP Рисуем текст без обрезания по ограничивающему прямоугольнику. Обеспечивает более быструю печать.
DT_RIGHT Выравнивает текст по правому краю ограничивающего прямоугольника.
DT_TOP Выравнивает текст по верхнему краю ограничивающего прямоугольника.
DT_WORDBREAK Переносит слова при достижении правого края ограничивающего прямоугольника.


Вот пример создания и использования экземпляра класса cFont:

// g_Graphics = ранее инициализированный объект cGraphics 
cFont ArialFont;

ArialFont.Create(&g_Graphics, "Arial");      // Шрифт Arial, размер 16
ArialFont.Print("I can print fonts!", 0, 0); // Печать в точке 0,0

Вершины и cVertexBuffer

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

class cVertexBuffer
{
  private:
    cGraphics *m_Graphics;         // Родительский cGraphics
    IDirect3DVertexBuffer9 *m_pVB; // COM-объект буфера вершин 

    DWORD m_NumVertices;           // Количество вершин
    DWORD m_VertexSize;            // Размер вершины
    DWORD m_FVF;                   // Дескриптор FVF

    BOOL m_Locked;                 // Флаг блокировки буфера
    char *m_Ptr;                   // Указатель на буфер

  public:
    cVertexBuffer();  // Конструктор
    ~cVertexBuffer(); // Деструктор

    // Функции для получения COM-интерфейса,
    // размера, FVF, и количества вершин
    IDirect3DVertexBuffer9 *GetVertexBufferCOM();
    unsigned long GetVertexSize();
    unsigned long GetVertexFVF();
    unsigned long GetNumVertices();

    // Создание и освобождение буфера вершин
    BOOL Create(cGraphics *Graphics,
                unsigned long NumVertices,
                DWORD Descriptor,
                long VertexSize);
    BOOL Free();
    BOOL IsLoaded();      // Возвращает TRUE если выделена
                          //память под буфер

    // Копирует последовательность вершин в буфер
    BOOL Set(unsigned long FirstVertex,
             unsigned long NumVertices, void *VertexList);

    // Визуализирует буфер вершин на устройстве
    BOOL Render(unsigned long FirstVertex,
                unsigned long NumPrimitives, DWORD Type);

    // Блокирует и разблокирует буфер вершин для доступа
    BOOL Lock(unsigned long FirstVertex = 0,
              unsigned long NumVertices = 0);
    BOOL Unlock();
    void *GetPtr();      // Возвращает указатель на
                         // заблокированный буфер вершин
};

Вы должны создать буфер вершин с помощью функции cVertexBuffer::Create, которой передается родительский объект cGraphics, количество вершин, для которых выделяется место, дескриптор настраиваемого формата вершин (FVF) и размер (в байтах) одной вершины. Да, конечно, вы все еще должны создать структуру данных вершины, чтобы работать с этим классом. Не беспокойтесь — как вы вскоре увидите, это не так трудно.

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

Теперь вы готовы визуализировать полигоны, используя cVertexBuffer::Render. Обратите внимание, что вы должны указать первую вершину, с которой начинается рисование и общее количество рисуемых примитивов (треугольных граней). Параметр Type используется точно также, как это делалось в главе 2 (и как описано в таблице 6.3).


Таблица 6.3. Флаги типов cVertexBuffer::Render



Флаг Описание

D3DPT_POINTLIST Набор вершин, рисуемых как пиксели.
D3DPT_LINELIST Набор изолированных (несоединенных) линий.
D3DPT_LINESTRIP Последовательность соединенных линий. Каждая линия рисуется от предыдущей вершины к текущей, подобно игре «соедини точки».
D3DPT_TRIANGLELIST Набор треугольников по три вершины на треугольник.
D3DPT_TRIANGLESTRIP Полоса треугольников. Для создания грани используется одна новая вершина и две из предыдущего треугольника.
D3DPT_TRIANGLEFAN Веер треугольников. Каждая вершина для создания грани соединяется с центральной вершиной и с предыдущей вершиной.


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

// g_Graphics = ранее инициализированный объект cGraphics
// Определяем структуру данных вершины и дескриптор FVF
typedef struct sVertex {
    float x, y, z, rhw;
    D3DCOLOR Diffuse;
} sVertex;
#define VERTEXFVF (D3DFVF_XYZRHW | D3DFVF_DIFFUSE)

cVertexBuffer g_VB;
g_VB.Create(&g_Graphics, 4, VERTEXFVF, sizeof(sVertex));

// Вершины полосы треугольников в порядке по часовой стрелке
sVertex Verts[4] = {
    {   0.0f,   0.0f, 0.0f, 1.0f, D3DCOLOR_RGBA(255,   0,   0, 255) },
    { 200.0f,   0.0f, 0.0f, 1.0f, D3DCOLOR_RGBA(  0, 255,   0, 255) },
    {   0.0f, 200.0f, 0.0f, 1.0f, D3DCOLOR_RGBA(  0,   0, 255, 255) },
    { 200.0f, 200.0f, 0.0f, 1.0f, D3DCOLOR_RGBA(255, 255, 255, 255) },
};
g_VB.Set(0, 4, (void*)&Verts);

// Визуализируем полосу треугольников
g_VB.Render(0, 2, D3DPT_TRIANGLESTRIP);

// Освобождаем буфер вершин
g_VB.Free();

Мировое преобразование с cWorldPosition

Хотя работать с матрицей мирового преобразования не сложно, разве не замечательно было бы иметь класс, заботящийся обо всех деталях — таких, как мировые координаты, значения вращения и коэффициенты масштабирования? Как насчет того, чтобы добавить вращение щитов, только для аромата?

И вот все это в cWorldPosition:

class cWorldPosition
{
  protected:
    BOOL m_Billboard; // Флаг использования щита

    // Текущие местоположение, поворот и масштаб
    float m_XPos, m_YPos, m_ZPos;
    float m_XRotation, m_YRotation, m_ZRotation;
    float m_XScale, m_YScale, m_ZScale;

    D3DXMATRIX m_matWorld;       // Матрица мирового преобразования
    D3DXMATRIX m_matScale;       // Матрица масштабирования
    D3DXMATRIX m_matRotation;    // Матрица вращения
    D3DXMATRIX m_matTranslation; // Матрица перемещения

    D3DXMATRIX *m_matCombine1;   // Комбинационная матрица 1
    D3DXMATRIX *m_matCombine2;   // Комбинационная матрица 2

  public:
    cWorldPosition(); // Конструктор

    // Возвращает матрицу мирового преобразования
    D3DXMATRIX *GetMatrix(cGraphics *Graphics = NULL);

    // Установка внешних матриц для комбинирования с мировой матрицей
    BOOL SetCombineMatrix1(D3DXMATRIX *Matrix = NULL);
    BOOL SetCombineMatrix2(D3DXMATRIX *Matrix = NULL);

    BOOL Copy(cWorldPosition *DestPos); // Копирование в другой класс

    // Перемещение в мировых координатах (и относительно текущих)
    BOOL Move(float XPos, float YPos, float ZPos);
    BOOL MoveRel(float XAdd, float YAdd, float ZAdd);

    // Установка значений поворота (и относительно текущих)
    BOOL Rotate(float XRot, float YRot, float ZRot);
    BOOL RotateRel(float XAdd, float YAdd, float ZAdd);

    // Установка коэффициентов масштабирования (и относительно текущих)
    BOOL Scale(float XScale, float YScale, float ZScale);
    BOOL ScaleRel(float XAdd, float YAdd, float ZAdd);

    // Обновление матриц и предоставление объекта cGraphics для щита
    BOOL Update(cGraphics *Graphics = NULL);

    // Разрешение и запрещение использования щитов
    BOOL EnableBillboard(BOOL Enable = TRUE);

    // Получение текущей позиции, поворота и масштаба
    float GetXPos();
    float GetYPos();
    float GetZPos();
    float GetXRotation();
    float GetYRotation();
    float GetZRotation();
    float GetXScale();
    float GetYScale();
    float GetZScale();
};

Большинство функций самодокументируемые; вопросы вызывают только Update, GetMatrix, SetCombineMatrix1 и SetCombineMatrix2. Функция Update заново строит матрицу мирового преобразования, используя хранящуюся ориентацию и учитывая матрицу щита и две внешние матрицы (которые я называю комбинационными матрицами). Для установки источников двух комбинационных матриц используются функции SetCombineMatrix1 и SetCombineMatrix2.

ПРИМЕЧАНИЕ
Комбинационные матрицы (или матрицы привязки) представляют преобразования, необходимые для присоединения одного объекта к другому, например, для присоединения оружия к сетке руки. Две матрицы представляют локальную ориентацию фрейма присоединяемой сетки и мировую ориентацию присоединяемой сетки соответственно.

Функция GetMatrix возвращает текущую матрицу мирового преобразования. Убедитесь, что передали функции GetMatrix текущий объект cGraphics, который используется для вычисления матрицы щита (она вычисляется из транспонированной матрицы вида).

Вот пример ориентации двух объектов в трехмерном мире (один присоединяется к другому):

cWorldPosition ObjectPos, ObjectPos2;

ObjectPos.Move(10.0f, 100.0f, -56.0f);
ObjectPos.Rotate(1.57f, 0.0f, 0.785f);
ObjectPos.Update(); // Вычисляем обновленную матрицу

// Комбинируем второй объект с первым (наследование ориентации)
ObjectPos2.SetCombineMatrix1(ObjectPos.GetMatrix());
ObjectPos2.Rotate(0.0f, 0.0f, 3.14f);
ObjectPos2.Update(); // Вычисляем обновленную матрицу,
                     // используя комбинирование

Преобразование вида и cCamera

Класс cCamera, во многом похожий на cWorldPosition, имеет дело с матрицей преобразования вида:

class cCamera
{
  protected:
    float m_XPos, m_YPos, m_ZPos; // Координаты местоположения
    float m_XRot, m_YRot, m_ZRot; // Значения поворота

    // Отслеживание ориентации камеры
    float m_StartXPos, m_StartYPos, m_StartZPos;
    float m_StartXRot, m_StartYRot, m_StartZRot;
    float m_EndXPos, m_EndYPos, m_EndZPos;
    float m_EndXRot, m_EndYRot, m_EndZRot;

    D3DXMATRIX m_matWorld;       // Матрица мирового преобразования
    D3DXMATRIX m_matTranslation; // Матрица перемещения
    D3DXMATRIX m_matRotation;    // Матрица вращения

  public:
    cCamera(); // Конструктор

    D3DXMATRIX *GetMatrix(); // Получение матрицы преобразования вида
    BOOL Update();           // Обновление матрицы преобразования

    // Перемещение и вращение камеры (вида)
    BOOL Move(float XPos, float YPos, float ZPos);
    BOOL MoveRel(float XAdd, float YAdd, float ZAdd);
    BOOL Rotate(float XRot, float YRot, float ZRot);
    BOOL RotateRel(float XAdd, float YAdd, float ZAdd);

    // Направляем камеру из точки Eye на точку At
    BOOL Point(float XEye, float YEye, float ZEye,
               float XAt, float YAt, float ZAt);

    // Установка начала и конца отслеживания ориентации
    BOOL SetStartTrack();
    BOOL SetEndTrack();

    // Интерполяция ориентации камеры вдоль траектории
    // с использованием времени (0.0 - 1.0) и общей длины.
    BOOL Track(float Time, float Length);

    // Получение значений перемещения и вращения
    float GetXPos();
    float GetYPos();
    float GetZPos();
    float GetXRotation();
    float GetYRotation();
    float GetZRotation();
};

Класс cCamera работает во многом также как класс cWorldPosition, так что я воздержусь от вводных слов. Единственное отличие заключается в добавлении функций Point, SetStartTrack, SetEndTrack и Track. Функцию Point вы используете для ориентирования порта просмотра и его мгновенного направления на заданную точку.

Трио относящихся к траектории камеры функций отслеживает перемещение камеры с течением времени. Чтобы использовать возможности отслеживания ориентации камеры поместите камеру в начальное положение и вызовите функцию cCamera::SetStartTrack. Затем переместите камеру в конечную позицию и вызовите cCamera::SetEndTrack.

Теперь понятна причина вызова cCamera::Track (перед вызовом cCamera::Update) — ориентирование камеры согласно созданной вами траектории. Параметр Time функции Track меняется от 0.0 (начальная ориентация) до 1.0 (конечная ориентация) и любое значение между этими двумя перемещает камеру вдоль траектории. Параметр Length может быть любым значением, с которым вы работаете (например, миллисекундами).

Отслеживание камеры может создать сногсшибательные эффекты, так что давайте перейдем к примеру:

cCamera Cam;

// Размещаем камеру в точке 0.0f, 100.0f, -100.0f
// и направляем на начало координат
Cam.Point(0.0f, 100.0f, -100.0f, 0.0f, 0.0f, 0.0f);
Cam.SetStartTrack();

// Перемещаем в конечную позицию
Cam.Point(-100.0f, 0.0f, 0.0f, 0.0f, 100.0f, 0.0f);
Cam.SetEndTrack();

// Помещаем камеру на полпути через 10000 миллисекунд
Cam.Track(0.5f, 10000);
Cam.Update();

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

g_Graphics.SetCamera(&Cam); // Не вызывайте преждевременно Update

Загружаемые сетки и использование cMesh

Теперь вы не скажете, что работать с сетками не трудно, правда? Конечно, я говорю о скелетных и обычных сетках, которые вы использовали в главе 2. Цель cMesh — заключить этих маленьких демонов в набор простых для использования классов и затем применять их с другими объектами, которые визуализируют сетки на экране.

class cMesh
{
  private:
    cGraphics *m_Graphics; // Родительский объект cGraphics

    long  m_NumMeshes;  // Количество сеток в классе
    sMesh *m_Meshes;    // Список сеток

    long   m_NumFrames; // Количество фреймов в классе
    sFrame *m_Frames;   // Список фреймов

    D3DXVECTOR3 m_Min, m_Max; // Координаты ограничивающего
                              // параллелепипеда
    float m_Radius;           // Радиус ограничивающей сферы

    // Функция выполняет разбор отдельного шаблона X-файла
    void ParseXFileData(IDirectXFileData *pData,
                        sFrame *ParentFrame, char *TexturePath);

    // Сопоставление костей и матриц преобразования фреймов
    void MapFramesToBones(sFrame *Frame);

  public:
    cMesh();  // Конструктор
    ~cMesh(); // Деструктор

    BOOL IsLoaded();     // Возвращает TRUE если сетка загружена

    long   GetNumFrames();        // Возвращает количество фреймов
                                  // в списке
    sFrame *GetParentFrame();     // Возвращает самый верхний фрейм
                                  // из списка
    sFrame *GetFrame(char *Name); // Поиск фрейма в списке
    long   GetNumMeshes();        // Возвращает количество сеток в списке
    sMesh  *GetParentMesh();      // Возвращает самую верхнюю сетку
                                  // из списка
    sMesh  *GetMesh(char *Name);  // Поиск сетки в списке

    // Получаем координаты ограничивающего параллелепипеда
    // и радиус ограничивающей сферы
    BOOL GetBounds(float *MinX, float *MinY, float *MinZ,
                   float *MaxX, float *MaxY, float *MaxZ,
                   float *Radius);

    // Загрузка и освобождение .X--файла
    // (можно задать необязательный путь к карте текстур)
    BOOL Load(cGraphics *Graphics, char *Filename,
              char *TexturePath = ".\\");
    BOOL Free();
};

Класс выглядит небольшим, но я не показал вам структуры sMesh и sFrame, которые использует класс cMesh. Эти две структуры являются связанными списками объектов сеток и определений фреймов. Они также хранят различные ориентации для фреймов и списки материалов и текстур. Пойдемте дальше и загрузим исходный код графического ядра, чтобы взглянуть на него; он хорошо прокомментирован и вам будет нетрудно в нем разобраться.

Единственная вещь, которую вы будете делать с cMesh, это использование его для загрузки сеток из X-файлов, как показано ниже:

// g_Graphics = ранее инициализированный объект cGraphics
cMesh Mesh;

Mesh.Load(&g_Graphics, "Mesh.x");

Mesh.Free(); // Освобождаем сетку, когда закончили с ней работать

Рисуем объекты, используя cObject

Когда приходит время рисовать сетки, вы должны создать мост от определения сетки до экрана. «Почему нельзя поддерживать визуализацию, используя объект cMesh», спрашиваете вы? Ответ — расходование памяти. Что если вы хотите использовать одну и ту же сетку снова и снова? Решение — применить объект cObject:

class cObject
{
  protected:
    cGraphics *m_Graphics; // Родительский объект cGraphics

    cMesh *m_Mesh;                 // Сетка для рисования
    sAnimationSet *m_AnimationSet; // Анимационный набор
    cWorldPosition m_Pos;          // Мировая ориентация

    BOOL m_Billboard;          // Флаг объекта щита

    unsigned long m_StartTime; // Начальное время анимации

    // Функции обновляют ориентацию фреймов и рисуют сетки
    void UpdateFrame(sFrame *Frame, D3DXMATRIX *Matrix);
    void DrawFrame(sFrame *Frame);
    void DrawMesh(sMesh *Mesh);

  public:
    cObject();  // Конструктор
    ~cObject(); // Деструктор

    // Создаем и освобождаем объект
    // (с необязательным указанием сетки)
    BOOL Create(cGraphics *Graphics, cMesh *Mesh = NULL);
    BOOL Free();

    // Включаем и выключаем поддержку для щитов
    BOOL EnableBillboard(BOOL Enable = TRUE);

    // Присоединяем объект к фрейму другого объекта
    // (Это вызовет комбинирование матриц при обновлении).
    // По умолчанию к первому фрейму найденному в сетке.
    BOOL AttachToObject(cObject *Object,
                        char *FrameName = NULL);

    // Ориентация объекта
    BOOL Move(float XPos, float YPos, float ZPos);
    BOOL MoveRel(float XAdd, float YAdd, float ZAdd);
    BOOL Rotate(float XRot, float YRot, float ZRot);
    BOOL RotateRel(float XAdd, float YAdd, float ZAdd);
    BOOL Scale(float XScale, float YScale, float ZScale);
    BOOL ScaleRel(float XAdd, float YAdd, float ZAdd);

    D3DXMATRIX *GetMatrix(); // Получение матрицы объекта

    // Получение ориентации объекта
    float GetXPos();
    float GetYPos();
    float GetZPos();
    float GetXRotation();
    float GetYRotation();
    float GetZRotation();
    float GetXScale();
    float GetYScale();
    float GetZScale();

    // Получение масштабированного ограничивающего параллелепипеда
    // и радиуса ограничивающей сферы
    BOOL GetBounds(float *MinX, float *MinY, float *MinZ,
                   float *MaxX, float *MaxY, float *MaxZ,
                   float *Radius);

    // Установка сетки, которую будет рисовать класс
    BOOL SetMesh(cMesh *Mesh);

    // Установка новой анимации (с именем и начальным временем)
    BOOL SetAnimation(cAnimation *Animation,
                      char *Name = NULL,
                      unsigned long StartTime = 0);
    char *GetAnimation(); // Получение указателя на имя анимации

    // Сброс воспроизведения анимации и установка
    // нового времени начала
    BOOL ResetAnimation(unsigned long StartTime = 0);

    // Обновление анимации на основе времени
    // с использованием плавной интерполяции
    BOOL UpdateAnimation(unsigned long Time, BOOL Smooth = TRUE);

    // Возвращает TRUE если анимация завершена во время Time
    BOOL AnimationComplete(unsigned long Time);

    BOOL Update(); // Обновление матрицы преобразования объекта
    BOOL Render(); // Рисуем объект, используя мировое преобразование
};

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

ПРИМЕЧАНИЕ
Ограничивающий параллелепипед (bounding box) — это набор координат, представляющих максимальные значения координат вершин сетки. Например, если у самой высокой вершины сетки значение координаты Y равно 100.0, то именно это значение будет использовано для верхней поверхности ограничивающего параллелепипеда. То же самое верно для левой, правой, нижней, передней и задней граней ограничивающего параллелепипеда сетки. Ограничивающая сфера (bounding sphere) — это почти то же самое, но вместо того, чтобы использовать параллелепипед, мы используем сферу, заключающую в себе сетку.
Ограничивающие параллелепипеды и сферы очень полезны при обнаружении столкновений, то есть когда надо узнать, столкнулся ли один объект с другим.

Чтобы работать с cObject просто объявите экземпляр класса и присоедините к нему ранее загруженный объект сетки. Затем вы можете произвольным образом ориентировать объект и визуализировать его на экране, как показано в следующем примере:

// g_Graphics = ранее инициализированный объект cGraphics
// g_Mesh = ранее загруженный объект сетки
cObject g_Object;

g_Object.Create(&g_Graphics, &g_Mesh);
g_Object.Move(100.0f, 50.0f, 100.0f);
g_Object.Render();

Делаем сетки двигающимися с cAnimation

Завершает описание графического ядра cAnimation — компонент для анимации сеток. Благодаря cAnimation вы можете загрузить комплект анимационных последовательностей из X-файла и использовать их совместно с cObject для анимации сеток.

Класс cAnimation небольшой. Подобно cMesh, cAnimation содержит несколько структур, хранящих список данных анимации. Взгляните на объявление класса:

class cAnimation
{
  protected:
    long          m_NumAnimations; // Количество анимаций в классе
    sAnimationSet *m_AnimationSet; // Список анимаций

    // Разбираем отдельный шаблон X-файла
    void ParseXFileData(IDirectXFileData *DataObj,
                        sAnimationSet *ParentAnim,
                        sAnimation *CurrentAnim);

  public:
    cAnimation();  // Конструктор
    ~cAnimation(); // Деструктор

    BOOL IsLoaded(); / / Возвращает TRUE, если анимация загружена

    // Возвращает количество анимаций в списке,
    // верхнюю анимацию и длину анимации
    long GetNumAnimations();
    sAnimationSet *GetAnimationSet(char *Name = NULL);
    unsigned long GetLength(char *Name = NULL);

    // Загрузка и освобождение анимации (можно указать сетку,
    // к которой будет применена анимация)
    BOOL Load(char *Filename, cMesh *MapMesh = NULL);
    BOOL Free();

    BOOL MapToMesh(cMesh *Mesh); // Применение анимации к сетке

    // Включение и выключение зацикленной анимации
    BOOL SetLoop(BOOL ToLoop, char *Name = NULL);
};

В классе cAnimation можно использовать только четыре общих функции: Load, Free, MapToMesh и SetLoop. Привязка анимации к сетке необходима, чтобы класс анимации мог найти матрицы сетки, которые ему следует изменять. Что касается SetLoop, обратите внимание на параметр Name, задающий имя анимации для которой будет установлен циклический повтор.

Анимациям (также как фреймам и сеткам) внутри X-файла могут быть присвоены имена. Это позволяет запаковать в один X-файл несколько анимаций и обращаться к ним по имени. Например, если ваш X-файл содержит анимационный набор с именем Walk, вы можете передать строку "Walk" в параметре Name. Если значение Name равно NULL, будет выбрана самая верхняя анимация в списке.

Другая вещь, которую вы обязаны заметить, — параметр StartTime в классе cObject. Этот параметр задает исходное значение, которое анимация использует при определении времени анимации. Таким способом, если вы синхронизируете ваши анимации по времени (используя такие функции, как timeGetTime), вы можете установить StartTime на то время, когда должно начаться воспроизведение анимации.

ПРИМЕЧАНИЕ
При воспроизведении анимации время произвольно; вы можете измерять время в секундах, миллисекундах, кадрах и т.д. Вы сами должны принять решение, поддерживать и измерять время согласно вашим потребностям.

Тогда последующие вызовы cObject::UpdateAnimation будут использовать разницу между указанным вами в их вызове временем и значением StartTime, предоставляя вам ясный механизм синхронизации (другими словами, точную синхронизацию, у которой время начала воспроизведения равно 0, а не произвольному значению).

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

// g_Graphics = ранее инициализированный объект cGraphics
// g_Mesh     = загруженный объект cMesh
// g_Object   = загруженный объект cObject
cAnimation Anim;

// Загрузка и зацикливание анимации ходьбы
Anim.Load("Mesh.x", &g_Mesh);
Anim.SetLoop(TRUE, "Walk");

// Применение анимации ходьбы к объекту
g_Object.SetAnimation(&Anim, "Walk", timeGetTime());

// Вход в цикл визуализации объекта и
// обновления анимации в каждом кадре
g_Object.UpdateAnimation(timeGetTime(), TRUE);
g_Object.Render();

// Завершив работу освобождаем анимацию
Anim.Free();

netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz