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

         

Данные геометрии и содержимое



Рисунок 10.3. Данные геометрии и содержимое буфера атрибутов отсортированы по значению идентификатора атрибута, благодаря чему данные отдельной подгруппы хранятся в непрерывном фрагменте памяти. Теперь можно легко определить где начинаются и где заканчиваются данные подгруппы. Обратите внимание, что каждый блок с пометкой «Tri» в буфере индексов представлен тремя индексами


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

typedef struct _D3DXATTRIBUTERANGE { DWORD AttribId; DWORD FaceStart; DWORD FaceCount; DWORD VertexStart; DWORD VertexCount; } D3DXATTRIBUTERANGE;

AttribId — Идентификатор подгруппы.

FaceStart — Смещение в буфере индексов (FaceStart * 3), указывающее где в буфере расположен первый треугольник, относящийся к данной подгруппе.

FaceCount — Количество граней (треугольников) в подгруппе.

VertexStart — Смещение в буфере вершин, указывающее где в буфере расположена первая вершина, относящаяся к данной подгруппе.

VertexCount — Количество вершин в подгруппе.

Мы можем просто просмотреть члены даных структуры D3DXATTRIBUTERANGE, что показано на Рисунок  10.3. Таблица атрибутов для сетки на Рисунок  10.3 состоит из трех элементов — по одному на каждую подгруппу сетки.

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


Для доступа к таблице атрибутов сетки используется следующий метод:

HRESULT ID3DXMesh::GetAttributeTable( D3DXATTRIBUTERANGE* pAttribTable, DWORD* pAttribTableSize );

Данный метод делает две веши: возвращает количество атрибутов в таблице атрибутов и заполняет массив структур D3DXATTRIBUTERANGE данными атрибутов.

Чтобы получить количество элементов в таблице атрибутов, мы передаем в первом аргументе 0:

DWORD numSubsets = 0; Mesh->GetAttributeTable(0, &numSubsets);

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

D3DXATTRIBUTERANGE table = new D3DXATTRIBUTERANGE [numSubsets]; Mesh->GetAttributeTable(table, &numSubsets);

Мы можем явно инициализировать таблицу атрибутов с помощью метода ID3DXMesh::SetAttributeTable. В приведенном ниже фрагменте выполняется инициализация таблицы атрибутов для 12 подгрупп:

D3DXATTRIBUTERANGE attributeTable[12];

// ...заполняем массив attributeTable данными

Mesh->SetAttributeTable(attributeTable, 12);


Данные о смежности


Для некоторых операций с сетками, например, для оптимизации, необходимо знать какие треугольники соседствуют с данным. Эта информация хранится в массиве данных о смежности граней сетки (adjacency array).



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

A = i × 3

B = i × 3 + 1

C = i × 3 + 2

Обратите внимание, что если в элементе массива хранится значение ULONG_MAX= 4294967295, это означает, что с данной стороной треугольника никакие грани не соседствуют. Для обозначения данного случая можно также использовать значение –1, поскольку присваивание переменной типа DWORD значения –1 аналогично присваиванию значения ULONG_MAX. Чтобы лучше понять это, вспомните, что DWORD — это беззнаковое 32-разрядное целое число.

Поскольку у треугольника три стороны с ним может соседствовать не более трех других треугольников (Рисунок  10.4).



Дом, разбитый на несколько подгрупп



Рисунок 10.1. Дом, разбитый на несколько подгрупп


Мы отмечаем подгруппы присваивая каждой из них уникальный положительный номер. Можно использовать любые значения, которые могут храниться в переменной типа DWORD. Например, на Рисунок  10.1 мы присваиваем подгруппам номера 0, 1, 2 и 3.

Каждому треугольнику сетки присваивается идентификатор атрибута (attribute ID), определяющий подгруппу, к которой относится данный треугольник. Например, на Рисунок  10.1 у треугольников, образующих пол дома идентификатор атрибута будет равен 0, поскольку они относятся к подгруппе 0. Аналогичным образом, у треугольников, образующих стены идентификатор атрибута будет равен 1, показывая, что они находятся в подгруппе 1.

Идентификаторы атрибутов треугольников хранятся в буфере атрибутов (attribute buffer) сетки, представляющем собой массив значений типа DWORD. Поскольку каждой грани соответствует элемент буфера атрибутов, число элементов буфера атрибутов равно количеству граней сетки. Между элементами буфера атрибутов и треугольниками, описанными в буфере индексов установлено строгое соответствие: i-ый элемент буфера атрибутов относится к i-ому треугольнику из буфера индексов, образованному вершинами, на которые указывают следующие три индекса из буфера индексов:

A = i × 3

B = i × 3 + 1

C = i × 3 + 2

Это соответствие показано на Рисунок  10.2:



Геометрия сетки

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

HRESULT ID3DXMesh::GetVertexBuffer(LPDIRECT3DVERTEXBUFFER9* ppVB); HRESULT ID3DXMesh::GetIndexBuffer(LPDIRECT3DINDEXBUFFER9* ppIB);

А вот пример того, как эти методы используются в программе:

IDirect3DVertexBuffer9* vb = 0; Mesh->GetVertexBuffer(&vb);

IDirect3DIndexBuffer9* ib = 0; Mesh->GetIndexBuffer(&ib);

ПРИМЕЧАНИЕ

Учтите, что в качестве примитивов в интерфейсе ID3DXMesh поддерживаются только индексированные списки треугольников.

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

HRESULT ID3DXMesh::LockVertexBuffer(DWORD Flags, BYTE** ppData); HRESULT ID3DXMesh::LockIndexBuffer(DWORD Flags, BYTE** ppData);

Параметр Flags указывает, как именно должна осуществляться блокировка. Флаги блокировки буферов вершин и индексов были описаны в главе 3, где мы впервые познакомились с этими буферами. Аргумент ppData — это адрес указателя, который после завершения работы функции будет указывать на занятую буфером область памяти.

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

HRESULT ID3DXMesh::UnlockVertexBuffer(); HRESULT ID3DXMesh::UnlockIndexBuffer();

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

DWORD GetFVF() — Возвращает значение типа DWORD, описывающее формат вершин сетки.

DWORD GetNumVertices() — Возвращает количество вершин в буфере вершин.

DWORD GetNumBytesPerVertex() — Возвращает количество байт, занимаемых описанием одной вершины.

DWORD GetNumFaces() — Возвращает количество граней (треугольных ячеек) в сетке.



Сетки: часть I


Мы уже работали с интерфейсом ID3DXMesh, когда использовали функции D3DXCreate*; в данной главе мы исследуем этот интерфейс более подробно. Почти вся глава посвящена обзору членов данных и методов, относящихся к интерфейсу ID3DXMesh.

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

Цели

Изучить внутреннюю организацию данных в объекте ID3DXMesh.

Узнать, как создать ID3DXMesh.

Узнать, как оптимизировать ID3DXMesh.

Узнать, как визуализировать ID3DXMesh.



Сетка содержит буфер вершин, буфер


Сетка содержит буфер вершин, буфер индексов и буфер атрибутов. Буфер вершин и буфер индексов хранят данные о геометрии сетки (данные вершин и описание образуемых ими треугольников). Буфер атрибутов содержит по одному значению для каждого треугольника, которое определяет к какой подгруппе относится данный треугольник.
Сетка может быть оптимизирована с помощью методов OptimizeInplace или Optimize. При оптимизации выполняется реогранизация данных о геометрии сетки, чтобы повысить эффективность ее визуализации. Если оптимизация выполняется с указанием флага D3DXMESHOPT_ATTRSORT, будет создана таблица атрибутов. Таблица атрибутов позволяет визуализировать подгруппы сетки путем простого просмотра данных в элементах таблицы.
Данные о смежности граней сетки представляют собой массив значений типа DWORD, содержащий по три значения для каждого треугольника сетки. Эти три значения соответствуют трем сторонам треугольника и указывают, какой треугольник соседствует с данной стороной.
Можно создать пустую сетку с помощью функции D3DXCreateMeshFVF. Затем записываются данные о сетке с помощью соответствующих методов блокировки (LockVertexBuffer, LockIndexBuffer и LockAttributeBuffer).

Каждому треугольнику соответствуют



Рисунок 10.4. Каждому треугольнику соответствуют три элемента в массиве данных о смежности граней, идентифицирующие треугольники смежные с данным. Например, с треугольником Tri 1 соседствуют два треугольника (Tri 0 и Tri 2). Следовательно, в массиве данных о смежности граней треугольнику Tri 1 будут соответствовать три элемента со значениями 0, 2 и –1, указывающие что соседями данного треугольника являются Tri 0 и Tri 2. Значение –1 указывает, что у одной стороны треугольника Tri 1 нет смежных граней


Следовательно, в массиве данных о смежности граней должно быть ID3DXBaseMesh::GetNumFaces() * 3 элементов — по три возможных соседа для каждой грани сетки.

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

HRESULT ID3DXMesh::GenerateAdjacency( FLOAT fEpsilon, DWORD* pAdjacency );

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

pAdjacency — Указатель на массив значений типа DWORD, который будет заполнен данными о смежности граней.

Пример использования метода:

DWORD adjacencyInfo[Mesh->GetNumFaces() * 3]; Mesh->GenerateAdjacency(0.001f, adjacencyInfo);



Клонирование


Иногда может потребоваться скопировать данные одной сетки в другую. Это можно сделать с помощью метода ID3DXBaseMesh::CloneMeshFVF.

HRESULT ID3DXMesh::CloneMeshFVF( DWORD Options, DWORD FVF, LPDIRECT3DDEVICE9 pDevice, LPD3DXMESH* ppCloneMesh );

Options — Один или несколько флагов, определяющих параметры создаваемого клона сетки. Полный список флагов приведен в описании перечисления D3DXMESH в документации SDK. Наиболее часто используются следующие флаги:

D3DXMESH_32BIT — Сетка будет использовать 32-разрядные индексы.

D3DXMESH_MANAGED — Сетка будет размещена в управляемом пуле памяти.

D3DXMESH_WRITEONLY — Данные сетки будут только записываться и не будут читаться.

D3DXMESH_DYNAMIC — Буферы сетки будут динамическими.

FVF — Настраиваемый формат вершин, используемый для создаваемого клона сетки.

pDevice — Устройство, связанное с клоном сетки.

ppCloneMesh — Возвращает созданный клон сетки.

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

// предполагается, что _mesh и device корректные указатели ID3DXMesh* clone = 0; Mesh->CloneMeshFVF( Mesh->GetOptions(), // используем те же параметры, // что и для исходной сетки D3DFVF_XYZ | D3DFVF_NORMAL,// задаем формат вершин клона Device, &clone);



Куб, созданный и визуализированный как объект 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; }


Оптимизация

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

HRESULT ID3DXMesh::OptimizeInplace( DWORD Flags, CONST DWORD* pAdjacencyIn, DWORD* pAdjacencyOut, DWORD* pFaceRemap, LPD3DXBUFFER* ppVertexRemap );

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

D3DXMESHOPT_COMPACT — Удаляет из сетки неиспользуемые вершины и индексы.

D3DXMESHOPT_ATTRSORT — Сортирует треугольники по значению идентификатора атрибута и создает таблицу атрибутов. Это повышает эффективность работы метода DrawSubset (см. раздел 10.5).

D3DXMESHOPT_VERTEXCACHE — Увеличивает частоту попаданий кэша вершин.

D3DXMESHOPT_STRIPREORDER — Реорганизует индексы таким образом, чтобы полосы треугольников были максимальной длины.

D3DXMESHOPT_IGNOREVERTS — Оптимизировать только индексы, вершины игнорируются.

ПРИМЕЧАНИЕ

Флаги D3DXMESHOPT_VERTEXCACHE и D3DXMESHOPT_STRIPREORDER нельзя использовать вместе.

pAdjacencyIn — Указатель на массив данных смежности граней неоптимизированной сетки.

pAdjacencyOut — Указатель на массив значений типа DWORD, который будет заполнен информацией о смежности граней оптимизированной сетки. В массиве должно быть ID3DXMesh::GetNumFaces() * 3 элементов. Если эта информация вам не нужна, укажите в данном параметре 0.

pFaceRemap — Указатель на массив значений типа DWORD, который будет заполнен информацией о перемещении граней. В массиве должно быть ID3DXMesh::GetNumFaces() элементов. При оптимизации сетки ее грани в буфере индексов могут перемещаться. Информация о перемещении сообщает куда в результате оптимизации была перемещена данная грань оригинала; следовательно i-ый элемент массива pFaceRemap содержит индекс грани, указывающий куда была перемещена i-ая грань исходной неоптимизированной сетки.
Если вам не нужна эта информация, укажите в данном параметре 0.

ppVertexRemap — Адрес указателя на буфер ID3DXBuffer (см. раздел 11.1), который будет заполнен информацией о перемещении вершин. Буфер должен содержать ID3DXMesh::GetNumVertices() вершин. При оптимизации сетки вершины, находящиеся в буфере вершин, могут перемещаться. Информация о перемещении сообщает куда в результате оптимизации была перемещена данная вершина оригинала; следовательно i-ый элемент массива ppVertexRemap содержит индекс вершины, указывающий куда была перемещена i-ая вершина исходной неоптимизированной сетки. Если вам не нужна эта информация, укажите в данном параметре 0.

Пример вызова метода:

// Получаем информацию о смежности граней // неоптимизированной сетки DWORD adjacencyInfo[Mesh->GetNumFaces() * 3]; Mesh->GenerateAdjacency(0.0f, adjacencyInfo);

// Массив для хранения информации о смежности граней // оптимизированной сетки DWORD optimizedAdjacencyInfo[Mesh->GetNumFaces() * 3];

Mesh->OptimizeInplace( D3DXMESHOPT_ATTRSORT | D3DXMESHOPT_COMPACT | D3DXMESHOPT_VERTEXCACHE, adjacencyInfo, optimizedAdjacencyInfo, 0, 0);

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

HRESULT ID3DXMesh::Optimize( DWORD Flags, CONST DWORD* pAdjacencyIn, DWORD* pAdjacencyOut, DWORD* pFaceRemap, LPD3DXBUFFER* ppVertexRemap, LPD3DXMESH* ppOptMesh // возвращает оптимизированную сетку );


Подгруппы и буфер атрибутов


Сетка состоит из одной или нескольких подгрупп. Подгруппой (subset) называется группа треугольных граней сетки, которая визуализируется вся с использованием одних и тех же атрибутов. Под атрибутами мы понимаем материал, текстуру и режим визуализации. На Рисунок 10.1 показано, как как можно разделить на несколько подгрупп сетку, представляющую дом.



Пример приложения: создание и визуализация сетки


Сопроводительный пример к данной главе визуализирует сетку куба (Рисунок 10.5).



Рисование


Интерфейс ID3DXMesh предоставляет метод DrawSubset(DWORD AttribId), позволяющий нарисовать все треугольники, относящиеся к подгруппе сетки, заданной аргументом AttribId. Например, чтобы нарисовать все треугольники, относящиеся к подгруппе 0, мы должны написать:

Mesh->DrawSubset(0);

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

for(int i = 0; i < numSubsets; i++) { Device->SetMaterial(mtrls[i]); Device->SetTexture(0, textures[i]); Mesh->DrawSubset(i); }



Соответствие между треугольниками



Рисунок 10.2. Соответствие между треугольниками, описываемыми элементами буфера индексов и элементами буфера атрибутов. Как видите, треугольник 0 относится к подгруппе 0, треугольник 1 — к подгруппе 4, а треугольник n — к подгруппе 2


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

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

// Чтение из буфера атрибутов или запись в него

Mesh->UnlockAttributeBuffer();



Создание сетки (DXCreateMeshFVF)

До сих пор мы создавали сетки с помощью функций D3DXCreate*. Однако мы также можем создать «пустую» сетку, воспользовавшись функцией D3DXCreateMeshFVF. Говоря о создании пустой сетки мы подразумеваем, что указываем количество вершин и граней, входящих в сетку, после чего функция D3DXCreateMeshFVF создает буферы вершин, индексов и атрибутов требуемого размера. После того, как буферы созданы, мы вручную заполняем их данными сетки (это значит, что мы должны записать данные вершин, индексы и атрибуты в буфер вершин, буфер индексов и буфер атрибутов, соответственно).

Как уже было сказано, для создания пустой сетки используется функция D3DXCreateMeshFVF:

HRESULT D3DXCreateMeshFVF( DWORD NumFaces, DWORD NumVertices, DWORD Options, DWORD FVF, LPDIRECT3DDEVICE9 pDevice, LPD3DXMESH* ppMesh );

NumFaces — Количество граней в создаваемой сетке. Должно быть больше нуля.

NumVertices — Количество вершин в создаваемой сетке. Должно быть больше нуля.

Options — Один или несколько флагов, определяющих параметры создаваемой сетки. Полный список флагов приведен в описании перечисления D3DXMESH в документации SDK. Наиболее часто используются следующие флаги:

D3DXMESH_32BIT — Сетка будет использовать 32-разрядные индексы.

D3DXMESH_MANAGED — Сетка будет размещена в управляемом пуле памяти.

D3DXMESH_WRITEONLY — Данные сетки будут только записываться и не будут читаться.

D3DXMESH_DYNAMIC — Буферы сетки будут динамическими.

FVF — Настраиваемый формат вершин для создаваемой сетки.

pDevice — Связанное с сеткой устройство.

ppMesh — Возвращает созданную сетку.

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

Вы также можете создать пустую сетку с помощью функции D3DXCreateMesh. Ее прототип выглядит так:

HRESULT D3DXCreateMesh( DWORD NumFaces, DWORD NumVertices, DWORD Options, CONST LPD3DVERTEXELEMENT9* pDeclaration, LPDIRECT3DDEVICE9 pDevice, LPD3DXMESH* ppMesh );

Все параметры, за исключением четвертого, аналогичны параметрам функции D3DXCreateMeshFVF. Вместо того, чтобы указать формат вершин (FVF), мы передаем функции описывающий формат вершин массив структур D3DVERTEXELEMENT9. Сейчас мы не будем углубляться в изучение структуры D3DVERTEXELEMENT9; однако следует упомянуть следующую связанную функцию:

HRESULT D3DXDeclaratorFromFVF( DWORD FVF, // входной формат D3DVERTEXELEMENT9 Declaration[MAX_FVF_DECL_SIZE] //выходной формат );

ПРИМЕЧАНИЕ

Структура D3DVERTEXELEMENT9 обсуждается в главе 17.

Эта функция получает настраиваемый формат вершин FVF и возвращает соответствующий ему массив структур D3DVERTEXELEMENT9. Взгляните на определение MAX_FVF_DECL_SIZE:

typedef enum { MAX_FVF_DECL_SIZE = 18 } MAX_FVF_DECL_SIZE;



Таблица атрибутов


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