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

Сетки в библиотеке D3DX

Чаще всего вы будете иметь дело с двумя типами сеток Direct3D: стандартными сетками (standard mesh) и скелетными сетками (skinned mesh). Стандартные сетки, они и есть стандартные. У них нет никаких прибамбасов, за исключением возможности использовать текстуры для улучшения внешнего вида.

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

Перед тем, как более подробно поговорить о стандартных и скелетных сетках, давайте взглянем на особый объект, используемый обоими типами сеток для хранения данных — ID3DXBuffer.

Объект ID3DXBuffer

Объект ID3DXBuffer используется для хранения и восстановления буферов данных. Библиотека D3DX применяет объект ID3DXBuffer для хранения информации о сетках, такой как материалы и списки текстур. О том как действует объект буфера данных вы подробнее узнаете в разделе «Стандартные сетки» этой главы.

У интерфейса ID3DXBuffer есть всего две функции. Первая — это ID3DXBuffer::GetBufferPointer, которая применяется для получения указателя на данные, хранящиеся в буфере объекта. Вызов функции GetBufferPointer возвращает указатель типа void, который вы можете привести к любому типу данных.

void *ID3DXBuffer::GetBufferPointer();

Вторая функция — это ID3DXBuffer::GetBufferSize, которая возвращает количество байтов, выделенных для хранения данных.

DWORD ID3DXBuffer:GetBufferSize();

Вы можете создать объект ID3DXBuffer для своих собственных нужд с помощью функции D3DXCreateBuffer:

HRESULT D3DXCreateBuffer(
    DWORD       NumBytes,     // Размер создаваемого буфера
    ID3DXBuffer **ppvBuffer); // Созданный объект буфера

Что хорошего в прототипе функции без примера ее использования — так вот он (создание объекта буфера размером 1024 байта и заполнение его нулями):

ID3DXBuffer *pBuffer;

// Создаем буфер
if(SUCCEEDED(D3DXCreateBuffer(1024, &pBuffer))) {

    // Получаем указатель на буфер
    char *pPtr = pBuffer->GetBufferPoint();

    // Заполняем буфер нулями
    memset(pPtr, 0, pBuffer->GetBufferSize());

    // Освобождаем буфер
    pBuffer->Release();
}

Стандартные сетки

Стандартные сетки очень просты; они содержат только описание сетки. С этими сетками проще всего работать, так что с них лучше всего начинать обучение. Еще более упрощает работу со стандартными сетками использование библиотеки D3DX, поскольку для загрузки и отображения стандартной сетки в D3DX требуется всего несколько строк кода. Стандартные сетки, с которыми я буду работать в книге, представляются объектом ID3DXMesh, который отвечает за хранение и рисование единственной сетки.

После объявления экземпляра объекта ID3DXMesh, воспользуйтесь следующей функцией для загрузки объекта с сеткой из X-файла:

HRESULT D3DXLoadMeshFromX(
    LPSTR            pFilename,     // Имя X-файла
    DWORD            Options,       // D3DXMESH_SYSTEMMEM
    IDirect3DDevice9 *pDevice,      // инициализированный объект устройства
    ID3DXBuffer      **ppAdjacency, // NULL
    ID3DXBuffer      **pMaterials,  // Буфер для данных материалов
    DWORD            pNumMaterials, // Количество материалов в сетке
    ID3DXMesh        **ppMesh);     // Создаваемый объект сетки

Большинство аргументов функции D3DXLoadMeshFromX заполняются библиотекой D3DX во время работы функции. Вы указываете имя загружаемого X-файла, неинициализированные объекты ID3DXBuffer и ID3DXMesh и переменную типа DWORD для хранения количества используемых в сетке материалов.

Если вы попытаетесь загрузить X-файл, содержащий несколько сеток, функция D3DXLoadMeshFromX объединит их все в единую сетку. На данном этапе это нас вполне устраивает. Взгляните на фрагмент рабочего кода, который загружает единственную сетку:

// g_pD3DDevice = ранее инициализированный объект устройства
ID3DXBuffer *pD3DXMaterials;
DWORD       g_dwNumMaterials;
ID3DXMesh   *g_pD3DXMesh;

if(FAILED(D3DXLoadMeshFromX("mesh.x", D3DXMESH_SYSTEMMEM,
                            g_pD3DDevice, NULL, &pD3DXMaterials,
                            &g_dwNumMaterials, &g_pD3DXMesh))) {
    // Произошла ошибка
}

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

D3DXMATERIAL      *pMaterials      = NULL;
D3DMATERIAL9      *g_pMaterialList = NULL;
IDirect3DTexture9 **g_pTextureList;

// Получаем указатель на список материалов
pMaterials = (D3DXMATERIAL*)pD3DXMaterials->GetBufferPointer();

if(pMaterials != NULL) {

    // Создаем массив структур данных материалов
    // для копирования в него данных
    g_pMaterialList = new D3DMATERIAL9[dwNumMaterials];

    // Создаем массив указателей на объекты текстуры
    g_pTextureList = new IDirect3DTexture9[dwNumMaterials];

    // Копируем материалы
    for(DWORD i = 0; i < dwNumMaterials; i++) {
        g_pMaterialList[i] = pMaterials[i].MatD3D;

        // Делаем фоновую составляющую цвета такой же,
        // как и рассеиваемая
        g_pMaterialList[i].Ambient = g_pMaterialList[i].Diffuse;

        // Создаем и загружаем текстуры (если они есть)
        if(FAILED(D3DXCreateTextureFromFileA(g_pD3DDevice,
            g_pMaterials[i]->pTextureFilename, &g_pTextureList[i])))
            g_pTextureList[i] = NULL;
    }

    // Освобождаем буфер материалов, использовавшийся для загрузки
    pD3DXMaterials->Release();
} else {
    // Если материалы не были загружены, создаем
    // материал по умолчанию
    g_dwNumMaterials = 1;

    // Создаем белый материал
    g_pMaterialList = new D3DMATERIAL9[1];
    g_pMaterialList[i].Diffuse.r = 1.0f;
    g_pMaterialList[i].Diffuse.g = 1.0f;
    g_pMaterialList[i].Diffuse.b = 1.0f;
    g_pMaterialList[i].Diffuse.a = 1.0f;
    g_pMaterialList[i].Ambient = g_pMaterialList[i].Diffuse;

    // Создаем пустую ссылку на текстуру
    g_pTextureList = new IDirect3DTexture9[1];
    g_pTextureList[0] = NULL;
}

После завершения показанного выше фрагмента кода, вы получаете замечательный новый список материалов и текстур, все элементы которого инициализированы и готовы к использованию в ваших сценах. Единственная оставшаяся задача — визуализация вашей сетки.

Визуализация сетки

Сердцем объекта ID3DXMesh является единственная функция визуализации с именем DrawSubset, которая выполняет работу по визуализации подгруппы сетки. Подгруппа (subset) — это часть сетки отделенная по причине смены параметров визуализации по сравнению с предыдущей подгруппой, например из-за смены материала или текстуры. Вы можете разделить сетку на несколько подгрупп (например, как показано на рис. 2.26). Ваша задача — понять, что представляет каждая подгруппа и визуализировать ее.


Рис. 2.26. Подгруппы используются для разделения отдельных частей сетки

Рис. 2.26. Подгруппы используются для разделения отдельных частей сетки


После загрузки X-файла у вас будет объект сетки и материалы. Подгруппы сетки связаны с этими материалами, так что если у вас в сетке есть пять материалов, это значит, что сетка содержит пять подгрупп для рисования.

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

// g_pD3DDevice = ранее инициализированный объект устройства
// pD3DXMesh    = ранее загруженный объект ID3DXMesh 
// matWorld     = матрица мирового преобразования сетки 

// Начало сцены
if(SUCCEEDED(g_pD3DDevice->BeginScene())) {
    // Устанавливаем матрицу мирового преобразования сетки
    g_pD3DDevice->SetTransform(D3DTS_WORLD, &matWorld);

    // Перебираем в цикле каждый материал в сетке
    for(DWORD i = 0; i < g_dwNumMaterials; i++) {
        // Устанавливаем материал и текстуру
        g_pD3DDevice->SetMaterial(&g_pMaterialList[i]);
        g_pD3DDevice->SetTexture(0, g_pTextureList[i]);

        // Рисуем подгруппу сетки
        pD3DXMesh->DrawSubset(i);
    }
    // Завершение сцены
    g_pD3DDevice->EndScene();
}

Помните, что прежде чем визуализировать сетку, вам надо установить ее матрицу мирового преобразования, чтобы поместить ее где-нибудь под каким-нибудь углом в вашем трехмерном мире. Если вы загрузили несколько сеток, то можете соединить их в форме объекта, анимируемого путем изменения ориентации отдельных сеток. Это основы трехмерной анимации (подробнее об этой теме мы поговорим в разделе «X-стиль трехмерной анимации», далее в этой главе).

Скелетные сетки

Одна из самых захватывающих возможностей Direct3D — скелетные сетки. Как я уже упоминал, скелетные сетки могут динамически деформироваться. Это достигается путем подсоединения отдельных вершин сетки к структуре лежащих в основе «костей» или иерархии фреймов. Скелетные сетки используют кости для определения своей формы; при перемещении костей сетка соответствующим образом деформируется.

Кости представляются как иерархия фреймов внутри X-файла. При моделировании сетки вы выстраиваете фреймы в структуру «предок–потомок». Когда изменяется ориентация родительского фрейма, присоединенные к нему дочерние фреймы наследуют родительское преобразование и комбинируют его со своими собственными преобразованиями. Это упрощает анимацию — вы перемещаете единственный фрейм и все, присоединенные к нему фреймы перемещаются следом.

Для загрузки и использования скелетных сеток вы имеете дело непосредственно с объектами данных X-файла, как это делалось ранее в разделе «Разбор X-файлов». (Я же говорил вам, что этот код еще пригодится.) Для разбора объектов вам потребуется управлять списком фреймов (и их иерархией).

Загрузка скелетных сеток

В процессе перечисления объектов данных, содержащихся в X-файле, вам потребуется вызывать различные функции загрузки сеток библиотеки D3DX для обработки объектов данных. Одна из представляющих для нас интерес функций, которую мы будем применять для загрузки скелетных сеток, — это D3DXLoadSkinMeshFromXof. Она выполняет работу по чтению объекта данных сетки из X-файла, созданию содержащего сетку объекта ID3DXMesh и созданию объекта ID3DXSkinInfo, описывающего соединение костей и вершин, что необходимо для деформации сетки.

Поскольку код для разбора иерархии фреймов и загрузки сеток достаточно сложен, он приводится на сопроводительном CD-ROM к этой книге (в папке \BookCode\Chap02\XFile). Я слышу ваши вздохи, но не волнуйтесь — код хорошо прокомментирован и я прямо сейчас уделю время его исследованию. Сперва вы находите несколько структур, содержащих иерархию фреймов (заполненную матрицами преобразования фреймов) и сетки.

Функция LoadMesh использует слегка модифицированную версию показанной ранее (в разделе «Разбор X-файлов») функции разбора. Объекты данных фреймов, после того, как они перечислены в функции LoadFile, добавляются к иерархии фреймов. Затем перечисляются другие объекты, находящиеся внутри объектов фреймов, обеспечивая иерархию наборами дочерних объектов.

Если в процессе перечисления объектов из X-файла будет обнаружен объект данных сетки, функция LoadFile загрузит сетку с помощью функции D3DXLoadSkinMeshFromXof. Затем загруженный объект сетки добавляется к связанному списку сеток. Фреймы содержат указатели на сетки, так что вы можете использовать одну и ту же сетку несколько раз (используя обращение по ссылке).

После того, как сетка загружена, функция загрузки сопоставляет кости сетки с соответствующими им фреймами и загружает материалы сетки.

Чтобы загрузить скелетную сетку или набор сеток, вызовите функцию LoadMesh, передав ей имя X-файла. В свою очередь, LoadFile вызывает функцию ParseXFile. Затем, после возврата из функции LoadFile, вы получаете указатель на фрейм, который является корнем для всех других фреймов.

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

Абсолютно верно, для использования скелетной сетки вам необходимо создать два контейнера сеток. Первая сетка — это та, которая загружена из X-файла. Это всего лишь стандартная сетка, использующая объект ID3DXMesh для хранения своих данных. Ваша задача сейчас — клонировать (дублировать) эту сетку, чтобы использовать дубликат при визуализации деформированной скелетной сетки.

Для создания клонированной сетки, вызовите функцию объекта сетки CloneMeshFVF:

HRESULT ID3DXMesh::CloneMeshFVF(
    DWORD             Options,       // Параметры сетки (установите 0)
    DWORD             FVF,           // Новый FVF
    LPDIRECT3DDEVICE9 pDevice,       // Устройство для создания сетки
    LPD3DXMESH        *ppCloneMesh); // Новый объект сетки

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

// pMesh = объект ID3DXMesh, загруженный из X-файла
ID3DXMesh *pSkinMesh = NULL;
pMesh->CloneMeshFVF(0, pMesh->GetFvF(), pD3DDevice, &pSkinMesh);

Теперь, когда у вас есть второй контейнер сетки (объект скелетной сетки), вы можете начать изменять структуру костей скелетной сетки, обновлять секту и визуализировать результат на экране!

Обновление и визуализация скелетной сетки

Чтобы модифицировать преобразования костей, вы создаете массив объектов D3DXMATRIX (по одной матрице для кости). Так, если в вашей скелетной сетке используется 10 костей, то вам необходимо 10 объектов D3DXMATRIX для хранения преобразований каждой кости.

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

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

void *Src, *Dest;
pMesh->LockVertexBuffer(0, (void**)&Src);
pSkinnedMesh->LockVertexBuffer(0, (void**)&Dest);

После блокировки вызовите функцию ID3DXSkinInfo::UpdateSkinnedMesh, чтобы преобразовать все вершины скелетной сетки согласно ориентации костей, заданной в массиве объектов D3DXMATRIX. В приведенном ниже фрагменте подразумевается, что массив объектов D3DXMATRIX называется matTransforms:

// matTransforms = массив объектов D3DXMATRIX
// pSkinInfo     = объект D3DXSkinInfo, полученный при
//                 вызове D3DXLoadSkinMeshFromXof.
pSkinInfo->UpdateSkinnedMesh(matTransforms, NULL, Src, Dest);

Теперь просто разблокируйте буферы вершин и визуализируйте контейнер скелетной сетки (клонированную сетку):

pSkinnedMesh->UnlockVertexBuffer();
pMesh->UnlockVertexBuffer();

// Визуализируем объект pSkinnedMesh, используя ту же технику
// что и ранее в этой главе. (Помните, что pSkinnedMesh
// это просто объект ID3DXMesh - используйте материалы и
// текстуры pMesh перед вызовом pSkinnedMesh->DrawSubset.

Я знаю, что сказал очень мало, и материал, возможно, трудно понять, но дело в том, что тема скелетных сеток слишком обширна, чтобы ее можно было рассмотреть на нескольких страницах. Для лучшего понимания того, как работать со скелетными сетками в собственных проектах, я настоятельно рекомендую вам изучить демонстрационные примеры, находящиеся на сопроводительном CD-ROM.

В конце этой главы вы найдете краткую информацию о демонстрационном примере XFile, который захотите изучить, поскольку он показывает как загрузить и визуализировать скелетную сетку.

X-стиль трехмерной анимации

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

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

Когда вы имеете дело со скелетными сетками, использование преобразований фреймов — это единственный способ анимации сетки. Поскольку скелетная сетка — это единая сетка (она не составлена из нескольких сеток), для того, чтобы деформировать вершины вам надо менять фреймы. Простейший способ модифицировать матрицы преобразования фреймов — это воспользоваться техникой, называемой ключевые кадры (key framing).

Техника ключевых кадров

В компьютерной анимации ключевыми кадрами (key frame) называется техника, в которой берется две завершенных ориентации объекта (ключевые кадры) и выполняется интерполяция между ними на основании какого-нибудь фактора, например, времени. Другими словами, зная ориентацию объекта в двух различных кадрах (каждый со своей ориентацией, называемой ключом), вы можете вычислить его ориентацию в любой момент времени между этими ключами (как показано на рис. 2.27).


Рис. 2.27. Ориентация фреймов интерполируется в зависимости от прошедшего с начала времени

Рис. 2.27. Ориентация фреймов интерполируется в зависимости от прошедшего с начала времени


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

Как вы уже знаете, иерархия фреймов создается из подсоединенных один к другому фреймов. Вы также знаете, как анимировать фреймы сетки используя объекты анимации X-файла. Теперь вам только надо выполнить вычисления интерполяции между этими фреймами, чтобы получить плавную анимацию.

Тот способ использования ключевых кадров, который я показываю в этой книге, — это матрицы ключевых кадров (matrix key framing). Поскольку вы уже используете объекты матриц D3DX, данную форму ключевых кадров использовать легко. Предположим, у вас есть две матрицы: mat1 и mat2, являющиеся начальной и конечной матрицей соответственно. Промежуток времени между ними представлен как Length, а текущее время представлено как Time (в диапазоне от 0 до Length). Вы вычисляете интерполированную матрицу следующим образом:

// D3DXMATRIX Mat1, Mat2;
// DWORD Length, Time;
D3DXMATRIX MatInt; // Итоговая интерполированная матрица

// Вычисляем интерполированную матрицу
MatInt = (Mat2 - Mat1) / Length;

// Умножаем на время
MatInt *= Time;

// Добавляем Mat1 и это результат!
MatInt += Mat1;

 

ПРИМЕЧАНИЕ
Интерполяция (interpolating) — это способ вычисления промежуточных значений между двумя числами на основании времени. Например, если ваш дом находится в двух милях от работы, и путь до работы занимает у вас 30 минут, вы можете определить расстояние до работы в любой момент времени, используя следующее вычисление интерполяции:
Distance = (DistanceToWork / TravelTime) * CurrentTime;
Как видите, через 26 минут вы проедете ((2 / 30) * 26) = 1.73 мили.

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

Анимация в X-файлах

Microsoft обеспечила хранение данных анимации в X-файлах. Данные анимации находятся внутри ряда специальных объектов данных, и вы можете загружать данные из этих объектов анимации используя ту же самую технику, которую применяли для загрузки скелетных сеток.

Загрузка анимации из X-файла очень беспорядочна; есть анимация в целом, каждая с объектом анимации, объектом анимационного набора, объектом времени, объектами ключевых кадров — с какой кучей вещей приходится иметь дело!

Вместо того, чтобы здесь пробираться через создание пакета анимации, обратитесь к коду на прилагаемом к книге CD-ROM (загляните в папку \BookCode\Chap02\XFile). Вы можете также обратиться к главе 6, «Создание ядра игры», чтобы исследовать законченный пакет анимации, созданный для этой книги. Пока, тем не менее, продолжите чтение этого раздела, чтобы узнать как работатет анимация в X-файлах.

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

У каждого ключа есть связанное с ним время, в которое он должен стать активным. Другими словами, ключ вращения с time = 0 означает, что когда время равно 0 используются значения вращения из ключа. Второй ключ вращения становится активным когда time = 200. Пока время идет, вычисляются интерполированные значения вращения, находящиеся где-нибудь между первым и вторым ключами вращения. Когда время достигает 200, значение вращения равно тому, которое указано во втором ключе. Такая форма интерполяции применяется к значениям ключей всех типов.

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

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

И снова на помощь приходят программы трехмерного моделирования, такие как 3D Studio Max или MilkShape 3D; благодаря им вам никогда не придется иметь дело непосредственно с данными анимации в X-файле. Кроме того, используя приведенный в этой книге код, вы можете в ваших играх загружать и показывать полностью анимированные сетки, абсолютно не тратя времени! Более полную информацию о загрузке и использовании анимации вы найдете в главе 6.


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

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