netlib.narod.ru | < Назад | Оглавление | Далее > |
До сих пор мы работали с простыми геометрическими объектами, такими как сферы, цилиндры, кубы, и использовали функции D3DXCreate*. Если вы попытаетесь сконструировать собственный трехмерный объект, указывая координаты его вершин, то несомненно обнаружите, что это очень нудное занятие. Для того, чтобы облегчить эту скучную работу по созданию данных трехмерных объектов были созданы специальные приложения, называемые редакторами трехмерных моделей (3D modelers). Эти редакторы позволяют пользователю создавать сложные и реалистично выглядящие сетки в визуальной интерактивной среде с помощью богатого набора инструментов, что делает процесс моделирования гораздо проще. Наиболее популярными редакторами моделей в отрасли программирования игр являются 3DS Max (www.discreet.com), LightWave 3D (www.newtek.com) и Maya (www.aliaswavefront.com).
Естественно, эти редакторы могут экспортировать данные созданной сетки (геометрию, материалы, анимацию и другие полезные данные) в файл. Следовательно, нам остается написать процедуру чтения файла, которая будет извлекать все данные сетки, после чего мы сможем использовать их в нашем приложении. Это очень хорошее решение, но есть и более удобный вариант. Существует формат файла с данными сетки, называемый X-файл (с расширением .X). Большинство популярных редакторов моделей могут выполнять экспорт данных в этот формат и, кроме того, существует множество программ-конвертеров, преобразующих распространенные форматы файлов сеток в файлы формата .X. Главным удобством X-файлов является то, что они являются «родным» форматом DirectX и, следовательно, библиотека D3DX без дополнительных усилий с вашей стороны поддерживает работу с X-файлами. Это значит, что библиотека D3DX предоставляет функции для чтения и записи X-файлов, а значит, если мы используем этот формат, нам не надо писать собственные процедуры чтения и записи.
Для загрузки содержащихся в X-файле данных сетки мы будем использовать показанную ниже функцию. Обратите внимание, что этот метод создает объект ID3DXMesh и загружает в него данные геометрии из X-файла.
HRESULT D3DXLoadMeshFromX( LPCSTR pFilename, DWORD Options, LPDIRECT3DDEVICE9 pDevice, LPD3DXBUFFER *ppAdjacency, LPD3DXBUFFER *ppMaterials, LPD3DXBUFFER* ppEffectInstances, PDWORD pNumMaterials, LPD3DXMESH *ppMesh );
pFilename — Имя загружаемого X-файла.
Options — Один или несколько флагов, определяющих параметры создаваемой сетки. Полный список флагов приведен в описании перечисления D3DXMESH в документации SDK. Наиболее часто используются следующие флаги:
D3DXMESH_32BIT — Сетка будет использовать 32-разрядные индексы.
D3DXMESH_MANAGED — Сетка будет размещена в управляемом пуле памяти.
D3DXMESH_WRITEONLY — Данные сетки будут только записываться и не будут читаться.
D3DXMESH_DYNAMIC — Буферы сетки будут динамическими.
pDevice — Связанное с сеткой устройство.
ppAdjacency — Возвращает буфер ID3DXBuffer, содержащий массив значений типа DWORD, хранящий информацию о смежности граней сетки.
ppMaterials — Возвращает буфер ID3DXBuffer, содержащий массив структур D3DXMATERIAL, хранящий данные о материалах сетки. Мы подробнее поговорим о материалах сетки в следующем разделе.
ppEffectInstances — Возвращает буфер ID3DXBuffer, содержащий массив структур D3DXEFFECTINSTANCE. Мы игнорируем этот параметр и всегда будем передавать в нем 0.
pNumMaterials — Возвращает количество используемых в сетке материалов (то есть количество элементов D3DXMATERIAL в массиве, возвращаемом через указатель ppMaterials).
ppMesh — Возвращает созданный объект ID3DXMesh, заполненный данными о геометрии из X-файла.
Седьмой аргумент функции D3DXLoadMeshFromX возвращает количество используемых в сетке материалов, а пятый аргумент возвращает массив структур D3DXMATERIAL, содержащих данные этих материалов. Определение структуры D3DXMATERIAL выглядит так:
typedef struct D3DXMATERIAL { D3DMATERIAL9 MatD3D; LPSTR pTextureFilename; } D3DXMATERIAL;
Это очень простая структура; она содержит базовую структуру D3DMATERIAL9 и указатель на завершающуюся нулем строку, которая является именем файла связанной с материалом текстуры. X-файлы не содержат в себе данных текстур; вместо этого они содержат имена файлов, которые используются для обращения к графическим файлам, содержащим реальные данные текстур. Следовательно, после загрузки X-файла с помощью функции D3DXLoadMeshFromX мы должны загрузить текстуры, используя указанные имена файлов. Мы покажем как это сделать в следующем разделе.
Особенно ценно, что функция D3DXLoadMeshFromX загружает данные из X-файла таким образом, что i-ый элемент в возвращаемом ею массиве D3DXMATERIAL соответствует i-ой подгруппе сетки. Соответственно подгруппы нумеруются в порядке 0, 1, 2, ..., n – 1, где n — это количество подгрупп и материалов. Это позволяет визуализировать сетку с помощью простого цикла, перебирающего все подгруппы и визуализирующего их.
Теперь мы взглянем на относящийся к рассматриваемой теме код из первого примера к данной главе, который называется XFile. Этот пример загружает X-файл с именем bigship1.x, который находится в папке с DirectX SDK. Полный исходный код примера расположен в сопроводительных файлах. Окно рассматриваемой программы показано на рис. 11.1.
Рис. 11.1. Окно программы XFile
В данном примере используются следующие глобальные переменные:
ID3DXMesh* Mesh = 0; std::vector<D3DMATERIAL9> Mtrls(0); std::vector<IDirect3DTexture9*> Textures(0);
Здесь мы объявляем объект ID3DXMesh, который будет использоваться для хранения данных сетки, загружаемых из X-файла. Мы также объявляем векторы материалов и текстур, которые будут хранить используемые в сетке материалы и текстуры.
Начнем с реализации нашей стандартной функции Setup. Сначала мы загружаем X-файл:
bool Setup() { HRESULT hr = 0; // // Загрузка данных из X-файла // ID3DXBuffer* adjBuffer = 0; ID3DXBuffer* mtrlBuffer = 0; DWORD numMtrls = 0; hr = D3DXLoadMeshFromX( "bigship1.x", D3DXMESH_MANAGED, Device, &adjBuffer, &mtrlBuffer, 0, &numMtrls, &Mesh); if(FAILED(hr)) { ::MessageBox(0, "D3DXLoadMeshFromX() - FAILED", 0, 0); return false; }
После того, как данные из X-файла загружены мы должны перебрать все элементы массива D3DXMATERIAL и загрузить текстуры, на которые ссылается сетка:
// // Извлечение материалов и загрузка текстур // if(mtrlBuffer != 0 && numMtrls != 0) { D3DXMATERIAL* mtrls=(D3DXMATERIAL*)mtrlBuffer-> GetBufferPointer(); for(int i = 0; i < numMtrls; i++) { // При загрузке в свойстве MatD3D не устанавливается // значение для фонового света, поэтому установим его сейчас mtrls[i].MatD3D.Ambient = mtrls[i].MatD3D.Diffuse; // Сохраняем i-ый материал Mtrls.push_back(mtrls[i].MatD3D); // Проверяем, связана ли с i-ым материалом текстура if( mtrls[i].pTextureFilename != 0 ) { // Да, загружаем текстуру для i-ой подгруппы IDirect3DTexture9* tex = 0; D3DXCreateTextureFromFile( Device, mtrls[i].pTextureFilename, &tex); // Сохраняем загруженную текстуру Textures.push_back(tex); } else { // Нет текстуры для i-ой подгруппы Textures.push_back(0); } } } d3d::Release<ID3DXBuffer*>(mtrlBuffer); // закончили работу с буфером . . // Пропущен код, не относящийся к теме данной главы . // (т.е. установка освещения, матриц вида и проекции и т.д.) . return true; } // конец функции Setup()
В функции Display мы в каждом кадре слегка разворачиваем сетку, чтобы она вращалась. Сетка визуализируется с помощью простого цикла, поскольку ее подгруппам присвоены номера, идущие в порядке 0, 1, 2, ..., n – 1, где n — это количество подгрупп:
bool Display(float timeDelta) { if(Device) { // // Обновление: поворот сетки // static float y = 0.0f; D3DXMATRIX yRot; D3DXMatrixRotationY(&yRot, y); y += timeDelta; if( y >= 6.28f ) y = 0.0f; D3DXMATRIX World = yRot; Device->SetTransform(D3DTS_WORLD, &World); // // Визуализация // Device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xffffffff, 1.0f, 0); Device->BeginScene(); for(int i = 0; i < Mtrls.size(); i++) { Device->SetMaterial(&Mtrls[i]); Device->SetTexture(0, Textures[i]); Mesh->DrawSubset(i); } Device->EndScene(); Device->Present(0, 0, 0, 0); } return true; }
Может получиться так, что в X-файле отсутствуют данные о нормалях вершин. В этом случае нам необходимо вручную вычислить нормали вершин, поскольку они необходимы для расчета освещения. Мы уже немного говорили о том, что делать в таком случае в главе 5. Однако теперь, когда мы знаем об интерфейсе ID3DXMesh и его родителе ID3DXBaseMesh, для генерации нормалей вершин произвольной сетки можно воспользоваться следующей функцией:
HRESULT D3DXComputeNormals( LPD3DXBASEMESH pMesh, // Сетка, для которой вычисляются нормали const DWORD *pAdjacency // Информация о смежности граней );
Эта функция генерирует нормали вершин используя усреднение нормалей. Если предоставлена информация о смежности граней, то дублирующиеся вершины будут игнорироваться. Если же информация о смежности не предоставлена, то дублирующиеся вершины будут получать нормали вычисленные путем усреднения нормалей тех граней, к которым они относятся. При реализации необходимо учесть, что настраиваемый формат вершин той сетки, которую мы передаем в параметре pMesh, должен содержать флаг D3DFVF_NORMAL.
Обратите внимание, что если X-файл не содержит данных нормалей вершин, в формате вершин объекта ID3DXMesh, создаваемого функцией D3DXLoadMeshFromX, флага D3DFVF_NORMAL не будет. Следовательно, перед тем как вызвать функцию D3DXComputeNormals, мы должны клонировать сетку, указав для клона формат вершин с установленным флагом D3DFVF_NORMAL. Эту особенность демонстрирует приведенный ниже фрагмент кода:
// Флаг D3DFVF_NORMAL указан в формате вершин сетки? if (!(pMesh->GetFVF() & D3DFVF_NORMAL)) { // Нет, клонируем сетку и добавляем флаг D3DFVF_NORMAL // к ее формату вершин: ID3DXMesh* pTempMesh = 0; pMesh->CloneMeshFVF( D3DXMESH_MANAGED, pMesh->GetFVF() | D3DFVF_NORMAL, // добавляем флаг Device, &pTempMesh); // Вычисляем нормали: D3DXComputeNormals(pTempMesh, 0); pMesh->Release(); // удаляем старую сетку pMesh = pTempMesh; // сохраняем новую сетку с нормалями }
netlib.narod.ru | < Назад | Оглавление | Далее > |