Введение в программирование трехмерных игр с DX9

       

Куб, созданный и визуализированный как объект IDXMesh



Рисунок 10.5. Куб, созданный и визуализированный как объект ID3DXMesh


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

Создание пустой сетки.

Заполнение сетки данными о геометрии куба.

Указание подгрупп, к которым относится каждая из граней сетки.

Генерация информации о смежности граней сетки.

Оптимизация сетки.

Рисование сетки.

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

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

void dumpVertices(std::ofstream& outFile, ID3DXMesh* mesh); void dumpIndices(std::ofstream& outFile, ID3DXMesh* mesh); void dumpAttributeBuffer(std::ofstream& outFile, ID3DXMesh* mesh); void dumpAdjacencyBuffer(std::ofstream& outFile, ID3DXMesh* mesh); void dumpAttributeTable(std::ofstream& outFile, ID3DXMesh* mesh);

Имена этих функций описывают выполняемые ими действия. Поскольку реализация функций достаточно прямолинейна, мы не будем обсуждать их (посмотрите исходный код в сопроводительных файлах). В качестве примера мы рассмотрим в данном разделе функцию dumpAttributeTable.

Обзор примера мы начнем с объявлений глобальных переменных:

ID3DXMesh* Mesh = 0; const DWORD NumSubsets = 3; IDirect3DTexture9* Textures[3] = {0, 0, 0}; // текстуры для подгрупп std::ofstream OutFile; // используется для вывода данных сетки в файл

Здесь мы объявляем указатель на сетку, которую мы создадим позже. Также мы указываем, что в создаваемой сетке будут три подгруппы. В рассматриваемом примере при визуализации каждой из подгрупп используется отдельная текстура; массив Textures хранит текстуры для каждой подгруппы, причем i-ый элемент массива текстур соответствует i-ой подгруппе сетки.
И, наконец, переменная OutFile используется для вывода данных сетки в текстовый файл. Мы передаем этот объект в функции dump*.

Основная часть работы данного приложения выполняется в функции Setup. Сперва мы создаем пустую сетку:

bool Setup() { HRESULT hr = 0; hr = D3DXCreateMeshFVF( 12, 24, D3DXMESH_MANAGED, Vertex::FVF, Device, &Mesh);

Здесь мы выделяем память для сетки, содержащей 12 граней и 24 вершины, необходимых для описания куба.

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

// Заполнение вершин куба Vertex* v = 0; Mesh->LockVertexBuffer(0, (void**)&v);

// вершины передней грани куба v[0] = Vertex(-1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f); v[1] = Vertex(-1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f); . . . v[22] = Vertex( 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f); v[23] = Vertex( 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f);

Mesh->UnlockVertexBuffer();

// Описание треугольных граней куба WORD* i = 0; Mesh->LockIndexBuffer(0, (void**)&i);

// индексы граней, образующих переднюю сторону куба i[0] = 0; i[1] = 1; i[2] = 2; i[3] = 0; i[4] = 2; i[5] = 3; . . . // индексы граней, образующих правую сторону куба i[30] = 20; i[31] = 21; i[32] = 22; i[33] = 20; i[34] = 22; i[35] = 23;

Mesh->UnlockIndexBuffer();

Геометрия сетки определена, и мы должны не забыть указать к какой подгруппе относится каждый из образующих сетку треугольников. Вспомните, что эти сведения хранятся в буфере атрибутов. В рассматриваемом примере мы указываем, что первые четыре из описанных в буфере индексов треугольника относятся к подгруппе 0, следующие четыре треугольника — к подгруппе 1, и последние четыре треугольника (всего получается 12) — к подгруппе 2. Это делает следующий фрагмент кода:

DWORD* attributeBuffer = 0; Mesh->LockAttributeBuffer(0, &attributeBuffer);



for(int a = 0; a < 4; a++) // треугольники 1-4 attributeBuffer[a] = 0; // подгруппа 0

for(int b = 4; b < 8; b++) // треугольники 5-8 attributeBuffer[b] = 1; // подгруппа 1

for(int c = 8; c < 12; c++) // треугольники 9-12 attributeBuffer[c] = 2; // подгруппа 2

Mesh->UnlockAttributeBuffer();

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

std::vector<DWORD> adjacencyBuffer(Mesh->GetNumFaces() * 3); Mesh->GenerateAdjacency(0.0f, &adjacencyBuffer[0]);

Затем можно оптимизировать сетку, как показано ниже:

hr = Mesh->OptimizeInplace( D3DXMESHOPT_ATTRSORT | D3DXMESHOPT_COMPACT | D3DXMESHOPT_VERTEXCACHE, &adjacencyBuffer[0], 0, 0, 0);

К данному моменту инициализация сетки закончена и мы готовы визуализировать ее. Но остался еще один, последний, фрагмент кода функции Setup, который надо рассмотреть. В нем используются упомянутые ранее функции dump* для вывода информации о сетке в текстовый файл. Предоставляемая ими возможность исследовать данные сетки помогает при отладке и при изучении внутренней структуры объекта сетки.

OutFile.open("MeshDump.txt");

dumpVertices(OutFile, Mesh); dumpIndices(OutFile, Mesh); dumpAttributeTable(OutFile, Mesh); dumpAttributeBuffer(OutFile, Mesh); dumpAdjacencyBuffer(OutFile, Mesh);

OutFile.close();

...Пропущены загрузка текстур, установка режимов визуализаии и т.д.

return true; } // конец функции Setup()

К примеру, функция dumpAttributeTable записывает в файл данные из таблицы атрибутов. Вот ее реализация:

void dumpAttributeTable(std::ofstream& outFile, ID3DXMesh* mesh) { outFile << "Attribute Table:" << std::endl; outFile << "----------------" << std::endl << std::endl;



// количество элементов в таблице атрибутов DWORD numEntries = 0;

mesh->GetAttributeTable(0, &numEntries);

std::vector<D3DXATTRIBUTERANGE> table(numEntries);

mesh->GetAttributeTable(&table[0], &numEntries);

for(int i = 0; i < numEntries; i++) { outFile << "Entry " << i << std::endl; outFile << "------" << std::endl;

outFile << "Subset ID: " << table[i].AttribId << std::endl; outFile << "Face Start: " << table[i].FaceStart << std::endl; outFile << "Face Count: " << table[i].FaceCount << std::endl; outFile << "Vertex Start: " << table[i].VertexStart << std::endl; outFile << "Vertex Count: " << table[i].VertexCount << std::endl; outFile << "std::endl; }

outFile << std::endl << std::endl; }

Ниже приведен фрагмент файла MeshDump.txt, создаваемого при работе рассматриваемого приложения, который содержит данные, записываемые функцией dumpAttributeTable.

Attribute Table: ---------------- Entry 0 ------------ Subset ID: 0 Face Start: 0 Face Count: 4 Vertex Start: 0 Vertex Count: 8 Entry 1 ------------ Subset ID: 1 Face Start: 4 Face Count: 4 Vertex Start: 8 Vertex Count: 8 Entry 2 ------------ Subset ID: 2 Face Start: 8 Face Count: 4 Vertex Start: 16 Vertex Count: 8

Как видите, все соответствует тем данным, которые мы указали в коде при инициализации сетки — три подгруппы в каждую из которых входят четыре треугольника. Мы рекомендуем вам исследовать весь текст формируемого данной программой файла MeshDump.txt. Вы найдете его в папке приложения в сопроводительных файлах.

И, наконец, мы можем визуализировать сетку с помощью приведенного ниже кода; собственно говоря, мы просто перебираем в цикле все подгруппы сетки, устанавливаем для каждой из них соответствующую текстуру и затем рисуем подгруппу. Все получается так просто потому что мы задали подгруппам последовательные номера, идущие в порядке 0, 1, 2, ..., n – 1, где n — это количество подгрупп.

bool Display(float timeDelta) { if( Device ) { //...код обновления кадра пропущен

Device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x00000000, 1.0f, 0); Device->BeginScene();

for(int i = 0; i < NumSubsets; i++) { Device->SetTexture(0, Textures[i]); Mesh->DrawSubset(i); }

Device->EndScene(); Device->Present(0, 0, 0, 0); } return true; }


Содержание раздела