DirectX 8 Graphics. Meshes (полигональные сетки)
Вообще, ядро DirectX не работает с мешами, а только с полигонами. 1 Поддержку работы с мешами вновь "оставили на откуп" вспомогательной библиотеке D3DX (DirectX 8), которая предоставляет ряд объектов, предназначенных для хранения и рендеринга полигональных сеток (далее - мешей).
На самом низком уровне меши состоят из сотен (часто тысяч) вершин (vertices) и полигонов (polygons), каждые из которых требуют весьма сложных манипуляций. К счастью, в DirectX для хранения полигональных сеток предусмотрен свой собственный формат файлов с расширением .X, описывающий 3D-модель а также все её вершины, грани, нормали, текстуры.
Для компиляции примеров на понадобится:
- MS Visual С++ 2010 Express,
- Microsoft DirectX SDK 8.
Содержание
- DirectX 8 Graphics. Meshes (полигональные сетки)
- Формат файлов .X
- Работа с мешами с применением вспомогательной библиотеки d3dx.lib (DirectX 8)
- Скинированные меши (Skinned Meshes)
- Пример приложения, показывающего меш (Win32, MSVC++2010, DirectX 8)
- Создаём Проект приложения
- Добавляем в Проект WinMain.cpp
- Готовим Проект XFile01 к компиляции
- Указываем пути к DirectX SDK 8.1
- Указываем пути к Windows SDK
- Выбираем многобайтовую кодировку
- Отключаем инкрементную компоновку (incremental linking)
- Прописываем библиотеки d3dx8.lib, d3d8.lib, dxguid.lib, d3dxof.lib и winmm.lib в окне "Дополнительные зависимости" (Additional dependencies) компоновщика (Linker)
- Отключаем использование компоновщиком библиотеки libci.dll
- Компилируем Проект XFile01
- Готовим .X файл
- Готовим текстуру
- Ссылка на готовый Проект XFile01
- Задание на дом
- Исследуем код примера
- 3D-анимации меша, хранимые в .Х-файлах
- Итоги
- Источники
Формат файлов .X
- Это проприетарный формат от Майкрософт, предназначенный для хранения 3D-моделей.
- Основан на шаблонах (templates), которые очень схожи с обычными структурами (structures) в языке С.
- Расширяемый.
- Имеет много возможностей для хардкорных программеров.
Шаблоны (templates) .Х-файлов
В .Х-файлах шаблоны применяются для хранения массива данных. Обычно в шаблонах .Х-файла хранится информация:- о вершинах меша,
- его полигонах,
- текстурных картах (texture maps),
- нормалях (normals).
Игрокодер может создавать инстансы (instances) своих собственных шаблонов точно также, как это делается в C/C++. Техника "ссылки на шаблон" (template referencing) позволяет определить (define) в .Х-файле базовый шаблон и затем многократно использовать данные из него без необходимости переопределять другие шаблоны. Представь, что ты определил один меш, который хочешь использовать в рамках одного .Х-файла несколько раз (например: человек стоит, человек сидит, бежит и т.д.). Вместо того, чтобы вновь и вновь определять новые шаблоны, достаточно просто определить меш 1 раз и затем просто использовать ссылки на исходный шаблон.
Работа с большинством шаблонов .Х-файлов для игрокодера "прозрачна". При загрузке .Х-файла ему не придётся иметь с ними дело напрямую. Единственные исключения здесь:
- шаблоны меша (mesh templates),
- шаблоны фрейма (frame templates).
Иерархия фреймов (Frame Hierarchy)
- Лежит в основе применения продвинутых (advanced) мешей и их анимации.
- Без неё никуда при использовании скинированных (skinned) мешей (об этом ниже).
- Применяется для изоляции отдельных объектов сцены друг от друга.
Шаблон фрейма (frame tamplate), также известный как ключевой фрейм (frame of reference), используют для группировки одного или нескольких шаблонов (обычно это шаблоны меша) для более удобной работы с ними. Часто создают один (базовый) меш, после чего используют ещё несколько фреймов, содержащих ссылки на фрейм с базовым мешем. Это позволяет применять один и тот же меш несколько раз в пределах одного .Х-файла.
Возьмём, к примеру, меш бильярдного шара. Так как в наборе 15 шаров, мы создаём 15 фреймов, каждый из которых содержит ссылку на исходный меш шара. Далее каждый фрейм шара можно ориентировать в пространстве (путём применения шаблона матрицы трансформации фрейма (frame transformation matrix template)) на площади бильярдного стола. Это заставит каждый инстанс меша перемещаться по столу вместе с собственным фреймом. Таким образом из одного меша мы получим 15 бильярдных шаров.
Помимо возможности множить инстансы меша, фреймы также позволяют создавать иерархию фреймов (frame hierarchy). Именно она определяет (defines) структуру сцены или группы мешей.
К примеру, возьмём человеческий скелет. Представь, что он представляет собой набок фреймов (set of frames). На вершине иерархии находится грудная клетка (chest), к которой присоединены другие кости (см. Рис.1), к тем, в свою очередь, другие и т.д. Здесь фрейм грудной клетки является исходным (корневым фреймом, root frame). У корневого фрейма нет родительского фрейма. То есть он расположен в самом верху иерархического списка и не принадлежит никакому другому фрейму. Фреймы, присоединённые к другому фрейму (фреймам) называют потомками (child frames; nodes). Например предплечье (upper arm) является родителем (parent) для руки, а рука - родителем для ладони (hand). Предплечье, рука и ладонь являются потомками фрейма "грудная клетка".
При движении фрейма вместе с ним также движутся все его потомки. Это логично, ведь при движении предплечья присоединённые к нему рука и кисть (= ладонь) также двигаются вместе с ним. При движении одной только кисти руки, двигаться будет только она, т.к. у неё нет фреймов потомков (если, конечно, каждому пальцу не назначен свой фрейм).
У каждого фрейма есть своя отдельная ориентация в пространстве, которая в терминах .Х-формата называется трансформация фрейма (frame transformation). Трансформация любого дочернего фрейма (= фрейма-потомка) применяется (apply) к т.н. топ-трансформации (top transformation) т.е. трансформации фрейма-родителя (parent frame). Каждая трансформация транслируется вниз, от самого верхнего уровня иерархии до самого последнего фрейма-потомка (child frame). Например, когда ты вращаешь предплечьем (upper arm) трансформация вращения также действует вниз по иерархии, ко всем фреймам-потомкам (рука, кисть). При этом трансформация родителя комбинируется с трансформациями всех его потомков.
Создание .Х-мешей (Creating .X Meshes)
Наиболее популярный способ создания .Х-файлов - экспорт готовой модели из 3D-редактора. Для этого 3D-редактор должен поддерживать экспорт .Х-моделей, что встречается довольно редко. На помощь приходят плагины от сторонних разработчиков. В таблице ниже представлены наиболее популярные 3D-редакторы + кратко описан способ экспорта .X-файла:НАЗВАНИЕ | ОПИСАНИЕ | ЗАГРУЗКА И УСТАНОВКА |
---|---|---|
3D Studio Max 7.0 | Shareware Trial-версия самого популярного программного пакета для создания и редактирования 3D-моделей. Полностью работоспособная версия с 30-дневным ознакомительным периодом. По истечении 30 дней перестанет работать. На английском. Работает на любой ОС семейства Windows не старше XP. | Берём здесь: http://www.ag.ru/files/software/11/11003. Самораспаковывающийся архив весит прибл. 85 Мб. Распаковываем в любую папку на компьютере и запускаем Setup.exe . После установки запускаем приложение с помощью ярлыка на Рабочем столе. При запуске появляется наг-скрин с запросом на активацию программы. Выбираем "Remind me later" и жмём "Next". Для нормальной работы программы в MS Windows 7/8/10 x64 в свойствах ярлыка жми "Дополнительно" и отметь пункт "Запуск от имени администратора". 3DSMax 7 (как впрочем и многие другие версии) очень не любит видеоадаптеры, интегрированные в процессор. Сильно желательно комп с дискретной видеокартой. Пусть даже это будет ноутбук с GeForce 310. |
Gmax | Официально бесплатная (для некоммерческого использования) программа для создания и редактирования 3D-моделей. На английском. Работает на любой ОС семейства Windows не старше XP. Из минусов: в штатной поставке сохраняет и открывает только файлы внутреннего формата gmax. Отсутствует .x-экспортер. Тот, что ставится отдельно (MS Flight Simulator GamePack) экспортирует .x-файлы с лишними фреймами. Из-за этого они не открываются игровым приложением. | Берём здесь: https://www.turbosquid.com/gmax. После установки запускаем приложение с помощью ярлыка на Рабочем столе. При первом запуске запрашивает идентификатор пользователя. Ссылка для его получения устарела. Поэтому архив с необходимыми reg-файлами ищем в Гугле по запросу "Gmax v1.2 Registration Utility" либо забираем архив gmax_registration_workaround.zip отсюда: https://neverwintervault.org/project/nwn1/other/tool/gmax-12-registration-workaround. После скачивания открываем архив и последовательно запускаем два файла с расширением .reg, согласившись на добавление информации из них в реестр вашей ОС. |
Milkshape 3D | Небольшой (6 Мб) 3D-редактор, умеющий экспортировать .X-файлы уже в базовой комплектации. Впрочем, не только их. На английском языке. Работает в любой ОС семейства MS Windows. Из минусов - нетривиальный интерфейс, к которому сложно привыкнуть. | Берём здесь: http://www.milkshape3d.com. Триальная версия работает не более 30 дней и потом просит зарегистрироваться (= заплатить). Прога хранит свои настройки в каталоге C:\Users\<Имя пользователя >\AppData\Roaming \MilkShape3D1.x.x\ (Win7/8/10). |
Anim8or | (Читается "анимэйтор".) Небольшой (4,7 Мб) бесплатный 3D-редактор от Steven Glanville (NVIDIA). На английском языке. Создаёт 3D-фигуры из стандартных примитивов. Работает в любой ОС семейства MS Windows. Позволяет экспортировать модели в форматы 3DS (устаревший формат файлов моделей 3DS Max версий 1-3), LWO (Lightwave 3D) и OBJ. Из минусов: нестандартный интерфейс, среди стандартных примитивов отсутствует плоскость (plane). | Берём здесь: https://anim8or.com/index.html. На офсайте программы переходим в раздел DOWNLOAD и скачиваем animv100.zip. Скачанный .zip-архив распаковать в любую папку на жёстком диске и запустить Anim8or.exe . |
Wings 3D | Бесплатный (для некоммерческого использования) 3D-редактор. На английском языке. Для работы требует видеокарту с поддержкой OpenGL 2.1 и выше. | Берём здесь: http://www.wings3d.com . На офсайте программы переходим в раздел Downloads и скачиваем инсталлятор. |
Blender 2.79 | Бесплатный (для некоммерческого использования) мультиплатформенный 3D-редактор. На английском языке. Написан на Python. Одна из последних версий, работающая в Win7. Для работы требует видеокарту с поддержкой OpenGL 2.1 и выше. Поддерживает экспорт моделей в файлы самых разных форматов, включая DirectX (.x). | Берём здесь: https://download.blender.org/release/Blender2.79 (110 Мб). Скачивай инсталлятор или архив под разрядность своей ОС. |
Blender 3 | Бесплатный (для некоммерческого использования) мультиплатформенный 3D-редактор. На английском языке. Написан на Python. Для работы требует ОС Win8.1 и выше + видеокарту с поддержкой OpenGL 2.1 и выше. Поддерживает экспорт моделей в файлы самых разных форматов, включая DirectX (.x). | Актуальную версию берём здесь: https://www.blender.org (210 Мб). На офсайте программы переходим в раздел Download и скачиваем инсталлятор под твою ОС. |
Autodesk Softimage Mod Tool 7.5 | Бесплатная (для некоммерческого использования) 32-битная версия легендарного 3D-редактора Softimage XSI ("Ксюша"; в ней, в частности, делали сцены с жидким металлом в х/ф "Терминатор 2"). На английском языке. Дружит с Win Vista/7/8/10. Для работы требует видеокарту с поддержкой OpenGL 2.1 и выше. Поддерживает экспорт моделей в файлы самых разных форматов, включая DirectX (.x). Дополнения с паками известных игр (Half-Life 2, Crysis) позволяют ещё больше расширить функционал. Многие (особенно в Японии) называли его лучшим 3D-редактором. С 2008 года проект закрыт и Mod Tool не обновляется. | Берём здесь: https://www.moddb.com/downloads/autodesk-softimage-mod-tool-75 (445 Мб). |
MakeHuman 1.2.0 | Бесплатный (опенсорсный) 3D-редактор человекоподобных персонажей. Дружит с Win 7/8/10. Для работы требует видеокарту с поддержкой OpenGL 2.1 и выше. Собственный формат файлов моделей MakeHuman умеет импортировать Blender. Тем не менее, Blender-импортер (+ ещё пару бонусов) на всякий случай заботливо положили в архив с программой. На сайте программы http://www.makehumancommunity.org в разделе "Assets" есть допстаф (модели людей, одежда, аксессуары и т.д.) | Берём здесь: http://download.tuxfamily.org/makehuman/releases/makehuman-community-1.2.0-windows.zip (335 Мб). |
Парсинг .X-файлов
- Не так сложен, как может показаться на первый взгляд.
- Проще выполнять, подключив вспомогательную библиотеку D3DX.
Примечание
Для использования компонентов интерфейса IDirectXFile (DirectX 8) необходимо подключить к Проекту заголовочные файлы dxfile.h, rmxfguid.h, rmxftmpl.h и библиотеки dxguid.lib, d3dxof.lib .
... BOOL ParseXFile(char *Filename) { IDirectXFile *pDXFile = NULL; IDirectXFileEnumObject *pDXEnum = NULL; IDirectXFileData *pDXData = NULL; // Создаём объект .X-файла if(FAILED(DirectXFileCreate(&pDXFile))) return FALSE; // Регистрируем нужные шаблоны. // Применяем стандартные шаблоны Direct3D (Retained Mode). if(FAILED(pDXFile->RegisterTemplates((LPVOID)D3DRM_XTEMPLATES, D3DRM_XTEMPLATE_BYTES))) { pDXFile->Release(); return FALSE; } // Создаём объект энумерации (enumeration object). if(FAILED(pDXFile->CreateEnumObject((LPVOID)Filename, DXFILELOAD_FROMFILE, &pDXEnum))) { pDXFile->Release(); return FALSE; } // Ищем ключевые шаблоны (=шаблоны верхнего уровня; top-level templates). while(SUCCEEDED(pDXEnum->GetNextDataObject(&pDXData))) { ParseXFileData(pDXData); ReleaseCOM(pDXData); } // Освобождаем память, ранее выделенную под объекты. ReleaseCOM(pDXEnum); ReleaseCOM(pDXFile); // В случае успеха возвращаем TRUE, return TRUE; } void ParseXFileData(IDirectXFileData *pData) { IDirectXFileObject *pSubObj = NULL; IDirectXFileData *pSubData = NULL; IDirectXFileDataReference *pDataRef = NULL; const GUID *pType = NULL; char *pName = NULL; DWORD dwSize; char *pBuffer; // Получаем тип шаблона. if(FAILED(pData->GetType(SpType))) return; // Получаем имя шаблона (если есть). if(FAILED(pData->GetName(NULL, SdwSize))) return; if(dwSize) { if((pName = new char[dwSize]) != NULL) { pData->GetName(pName, &dwSize); } } // Если шаблон не найден, присваиваем топ-шаблону имя по умолчанию. if(pName == NULL) { if((pName = new char[9]) == NULL) return; strcpy(pName, "Template"); } // Смотрим, что за шаблон. // Сюда переходим из своего кода. // Сканируем в поисках вложенных (embedded) шаблонов. while(SUCCEEDED(pData->GetNextObject(&pSubObj))) { // Обрабатываем вложенные ссылки. if(SUCCEEDED(pSubObj->QueryInterface(IID_IDirectXFileDataReference, (void**)&pDataRef))) { if(SUCCEEDED(pDataRef->Resolve(&pSubData))) { ParseXFileData(pSubData); ReleaseCOM(pSubData); } ReleaseCOM(pDataRef); } // Обрабатываем вложенные шаблоны без ссылок (non-referenced). if(SUCCEEDED(pSubObj->QueryInterface(IID_IDirectXFileData, (void**)&pSubData))) { ParseXFileData(pSubData); ReleaseCOM(pSubData); } ReleaseCOM(pSubObj); } // Очищаем буфер с именем. delete pName; } ...
В коде видим реализацию всего двух больших функций: ParseXFile и ParseXFileData. Они работают вместе, парся каждый шаблон, найденный в .Х-файле.
- ParseXFile открывает .Х-файл и энумерирует (=сканирует) его в поиске самых верхних шаблонов (topmost templates) в иерархии.
- ParseXFileData обрабатывает данные переданного шаблона. Её выполнение начинается с запроса типа шаблона и имени инстанса (если есть).
При вызове ParseXFile ей в качестве параметра передаётся имя загружаемого .X-файла. Далее две другие функции позаботятся обо всём остальном. При изучении темы "Скинированные меши" (Skinned meshs) они не раз нам помогут.
Работа с мешами с применением вспомогательной библиотеки d3dx.lib (DirectX 8)
Напомним, данная библиотека расположена в каталоге с установленным DirectX SDK 8. В нашем случае путь к ней такой: c:\DXSDK8\lib .Вообще, в Direct3D все меши делятся на 2 вида:
Стандартный (standart; статичный, static) | Не меняет свою геометрию при рендеринге. Для улучшения своего внешнего вида может использовать только текстурирование (texture-mapping). |
Скинированный(skinned; анимированный, animated) | Не меняет свою геометрию при рендеринге. Для улучшения своего внешнего вида может использовать только текстурирование (texture-mapping).Данный вид мешей уникален, т.к. способен менять свою форму в процессе рендеринга (=deformable). Для подготовки меша к деформации, как правило, (в 3D-редакторе) его вершины присоединяют (attach) к т.н. костям (bones; здесь служебные объекты, помогающие в анимировании меша). Всякий раз при движении костей присоединённые вершины меша двигаются вместе с ними. |
Объект ID3DXBuffer (DirectX 8)
- Специальный объект, который используют оба вида мешей для хранения своих данных.
- Имеет всего две функции: GetBufferPointer и GetBufferSize.
void *ID3DXBuffer::GetBufferPointer();
Функция GetBufferSize возвращает число байт, задействованных под буфер объекта:
DWORD ID3DXBuffer::GetBufferSize ();
Библиотека D3DX использует объект ID3DXBuffer для хранения информации о конкретном меше. Например, это могут быть списки (lists) материалов и текстурных карт.
ID3DXBuffer создаётся путём вызова функции D3DXCreateBuffer:
HRESULT D3DXCreateBuffer ( DWORD NumBytes, // Размер создаваемого буфера. ID3DXBuffer **ppvBuffer // Указатель на объект буфера. );
Вот пример, в котором создаётся объект буфера размером 1024 байт и заполняется нулями:
ID3DXBuffer *pBuffer; // Создаём буфер if(SUCCEEDED(D3DXCreateBuffer (1024, SpBuffer))) { // Получаем указатель на буфер. char *pPtr = pBuffer->GetBufferPointer (); // Заполняем буфер нулями. memset(pPtr, 0, pBuffer->GetBufferSize ()); // Освобождаем буфер. pBuffer->Release(); }
Стандартные меши (Standart Meshes; DirectX 8)
- Содержит определение шаблона меша, заданного создателями DirectX.
- Самый простой вид меша, с которого лучше всего начать работу.
Для примера создадим объект меша ID3DXMesh (подробнее об этом см. ниже).
После создания инстанса ID3DXMesh вызываем функцию D3DXLoadMeshFromX, загружающую меш из .Х-файла. Вот её прототип:
HRESULT D3DXLoadMeshFromX( LPSTR pFilename, // Имя загружаемого файла .Х-файла DWORD Options, // Обычно здесь указывают D3DXMESH_SYSTEMMEM. IDirect3DDevice8 *pDevice, // Предварительно проинициализированный объект устройства Direct3D. ID3DXBuffer **Adjacency, // NULL ID3DXBuffer **pMaterials, // Указатель на готовый объект буфера ID3DXBuffer, //содержащий инфу по материалам меша. DWORD pNumMaterials, // Кол-во материалов у меша. ID3DXMesh **ppMesh // Указатель на создаваемый D3DX-o6ъект меша. );
Многие аргументы здесь библиотека D3DX заполняет автоматически. Программер предоставляет только следующие параметры:
- имя .Х-файла;
- указатель на готовый объект буфера ID3DXBuffer, содержащий инфу по материалам меша;
- кол-во материалов меша;
- указатель на создаваемый D3DX-объект меша.
Вот пример кода, загружающего меш:
... // g_pD3DDevice - предварительно созданный и проинициализированный объект устройства Direct3D. ID3DXBuffer *pD3DXMaterials; DWORD g_dwNumMaterials; ID3DXMesh *g_pD3DXMesh; if(FAILED(D3DXLoadMeshFromX("mesh.x", D3DXMESH_SYSTEMMEM, g_pD3DDevice, NULL, &pD3DXMaterials, &g_dwNumMaterials, &g_pD3DXMesh))) { // Ошибка. } ...
Сразу после (успешной) загрузки меша из .Х-файла запрашиваем инфу о его материалах (materials) и текстурных картах (texture-maps):
... D3DXMATERIAL *pMaterials = NULL; D3DMATERIAL8 *g_pMaterialList = NULL; IDirect3DTexture8 **g_pTextureList; // Получаем указатель на список материалов (material list). pMaterials = (D3DXMATERIAL*)pD3DXMaterials->GetBufferPointer(); if(pMaterials != NULL) { // Выделяем структуру для копирования туда данных. g_pMaterialList = new D3DMATERIAL8(dwNumMaterials); // Создаём массив указателей на объекты текстур (texture object pointers array). g_pTextureList = new IDirect3DTexture8(dwNumMaterials); // Копируем инфу по материалам меша. for (DWORD i=0; KdwNumMaterials; i++) { g_pMaterialList[i] = pMaterials[i].MatD3D; // Делаем цвет тени (ambient color) такой же, как рассеяный (diffuse color). g pMaterialList[i].Ambient = g pMaterialList[i].Diffuse; // Загружаем текстуры (если есть). if(FAILED(D3DXCreateTextureFromFile(g_pD3DDevice, g_pMaterials[i]->pTextureFilename, &g_pTextureList[i]))) { g_pTextureList[i] = NULL; } } // Освобождаем буфер материалов по завершении их загрузки. pD3DXMaterials->Release (); } else { // Если материалы не загружены, создаём один материал по умолчанию. g_dwNumMaterials = 1; // Создаём белый (white) материал. g_pMaterialList = new D3DMATERIAL8[i]; g pMaterialList[i].Diffuse.г = 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 IDirect3DTexture8[i]; g_pTextureList[0] = NULL; } ...
После выполнения данного фрагмента у нас в распоряжении окажется список материалов и текстур, полностью готовый к применению в сцене. Следующий шаг - рендеринг меша.
Рендерим меши (Rendering Meshes; DirectX 8)
В "сердце" объекта ID3DXMesh лежит функция рендеринга DrawSubset. Субсетом (subset) называют часть (portion) меша, которая отделена, на случай изменения условий рендеринга. Самый типичный такой случай - смена материала или текстуры. Меш можно разбивать на множество субсетов. Здесь главное - помнить, что представляет собой каждый из них. Сразу после загрузки .Х-файла, ты остаёшься один на один с объектом меша и его материалами. Число субсетов совпадает с числом материалов. То есть, если объект меша содержит 5 материалов, меш содержит 5 субсетов, готовых к рендерингу. Такая зависимость упрощает процесс рендеринга меша. Мы просто сканируем каждый материал, назначаем его субсету и затем просто этот самый субсет рендерим. Весь процесс повторяется, пока весь меш не будет отрисован.Для позиционирования меша в мировом пространстве (world space), до начала рендеринга назначаем мешу мировую матрицу трансформации (world transformation matrix).
Вот пример рендеринга меша, загруженного и подготовленного в предыдущих абзацах:
... // g_pD3DDevice - предварительно созданный и проинициализированный объект устройства Direct3D. // pD3DXmesh - готовый инстанс объекта ID3DXMesh. // matWorld - мировая матрица трансформации меша. // Начало сцены if(SUCCEEDED(g_pD3DDevice->BeginScene())) { // Назначаем мировую матрицу трансформации меша. g_pD3DDevice->SetTransform(D3DTS_WORLD, SmatWorld); // Проходим цикл по каждому материалу меша. 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(); } ...
Прежде чем рендерить меш, сперва необходимо установить (set) его мировую матрицу трансформации (world transformation matrix). Она необходима для корректного позиционирования меша в мировом пространстве. При загрузке нескольких мешей их можно объединить, создав анимированный объект путём простого изменения положения его частей в пространстве. Именно эта концепция лежит в основе real-time 3D-анимации.
Скинированные меши (Skinned Meshes)
- Могут динамически изменяться во время рендеринга.
- объединения отдельных вершин в специальные структуры, "присоединяемые" к т.н. костям (bones);
- перемещения отдельных фреймов иерархии.
Внутри .Х-файла кости представлены в виде иерархии фреймов (frame hierarchy). При моделировании меша фреймы объединяют по принципу родитель (parent) - потомок (child). При повороте родительского фрейма, все, присоединённые к нему, дочерние фреймы также наследуют трансформацию фрейма-родителя, которая, к тому же, комбинируется с их собственными трансформациями. Это заметно упрощает процесс анимирования для программера: двигаем родительский фрейм, а все дочерние двигаются вместе с ним.
Работу с анимированным мешем начинают с загрузки .Х-файла и парсинга его шаблонов (аналогично процессу описанному для стандартных мешей). Далее формируем список фреймов (list of frames) и иерархию фреймов.
Загрузка скинированных мешей (Load Skinned Meshes; DirectX 8)
Выше мы рассматривали процесс загрузки стандартного меша из .Х-файла. Там мы вызывали несколько функций из библиотеки D3DX для обработки шаблонов (templates). При загрузке скинированных мешей принцип тот же. Разве что применяется другая функция - D3DXLoadSkinMeshFromXof. В результате её выполнения считываются шаблоны из .Х-файла и на основе интерфейса ID3DXSkinMesh создаётся объект скинированного меша. Весь процесс проще представить, имея перед глазами исходный код примера. Поэтому создадим пример.Пример приложения, показывающего меш (Win32, MSVC++2010, DirectX 8)
Создадим пример, который:- загружает .X файл,
- грамотно его парсит,
- "достаёт" шаблоны фреймов (frame templates; о них читай чуть ниже),
- готовит текстуру и наносит её на 3D-модель.
- MS Visual С++ 2010;
- DirectX SDK 8.1
- Windows SDK (для программирования под Win7/8/10).
- + инструкции по их установке ты найдёшь в разделе "Софт" нашего сайта;
- Бесплатны;
- Без труда гуглятся.
Создаём Проект приложения
- Стартуй MS Visual C++ 2010, если не сделал этого раньше.
- Создай пустой Проект с именем XFile01.
Весь процесс подробно расписан в статье MS Visual Cpp 2010 Express. Установка и указание путей к DirectX SDK.
Добавляем в Проект WinMain.cpp
Для чистоты эксперимента мы создали пустой Проект, т.е. без каких-либо файлов в нём. Создадим единственный файл с исходным кодом WinMain.cpp.- В "Обозревателе решений" главного окна MSVC++2010 щёлкни правой кнопкой мыши по папке (в терминологии Майкрософт это не папки, а фильтры!) "Файлы исходного кода" Проекта XFile01.
- Во всплывающем меню Добавить->Создать элемент...
- В появившемся окне выбери "Файл С++ (.срр)" и в поле "Имя" введи WinMain.cpp.
- Жмём "Добавить".
Добавленный файл сразу откроется в правой части MSVC++2010.
- В только что созданном и открытом файле WinMain.cpp набираем следующий код:
/************************************************** WinMain.cpp Chapter 6 XFile/Skinned Mesh Demo Programming Role-Playing Games with DirectX by Jim Adams (01 Jan 2002) Required libraries: WINMM.LIB, D3D8.LIB, D3DX8.LIB, and DXGUID.LIB **************************************************/ // Macro to release COM objects #define ReleaseCOM(x) if(x) { x->Release(); x = NULL; } // Include files #include <windows.h> #include <stdio.h> #include "d3d8.h" #include "d3dx8.h" #include "dxfile.h" #include "rmxfguid.h" #include "rmxftmpl.h" // Window handles, class and caption text // Дескриптор окна, класс и текст тулбара заданы в виде констант. // Для удобства. HWND g_hWnd; HINSTANCE g_hInst; static char g_szClass[] = "XFileClass"; static char g_szCaption[] = "XFile Demo by Jim Adams"; // The Direct3D and Device object IDirect3D8 *g_pD3D = NULL; IDirect3DDevice8 *g_pD3DDevice = NULL; // A mesh definition structure typedef struct sMesh { char *m_Name; // Name of mesh ID3DXMesh *m_Mesh; // Mesh object ID3DXSkinMesh *m_SkinMesh; // Skin mesh object DWORD m_NumMaterials; // # materials in mesh D3DMATERIAL8 *m_Materials; // Array of materials IDirect3DTexture8 **m_Textures; // Array of textures DWORD m_NumBones; // # of bones ID3DXBuffer *m_BoneNames; // Names of bones ID3DXBuffer *m_BoneTransforms; // Internal transformations D3DXMATRIX *m_BoneMatrices; // X file bone matrices sMesh *m_Next; // Next mesh in list sMesh() { m_Name = NULL; // Clear all structure data m_Mesh = NULL; m_SkinMesh = NULL; m_NumMaterials = 0; m_Materials = NULL; m_Textures = NULL; m_NumBones = 0; m_BoneNames = NULL; m_BoneTransforms = NULL; m_Next = NULL; } ~sMesh() { // Free all used resources delete [] m_Name; ReleaseCOM(m_Mesh); ReleaseCOM(m_SkinMesh); delete [] m_Materials; if(m_Textures != NULL) { for(DWORD i=0;i<m_NumMaterials;i++) { ReleaseCOM(m_Textures[i]); } delete [] m_Textures; } ReleaseCOM(m_BoneNames); ReleaseCOM(m_BoneTransforms); delete m_Next; // Delete next mesh in list } } sMesh; // Structure to contain frame information typedef struct sFrame { char *m_Name; // Frame's name sMesh *m_Mesh; // Linked list of meshes sFrame *m_Sibling; // Sibling frame sFrame *m_Child; // Child frame sFrame() { // Clear all data m_Name = NULL; m_Mesh = NULL; m_Sibling = m_Child = NULL; } ~sFrame() { // Delete all used resources, including linked list of frames delete m_Name; delete m_Mesh; delete m_Child; delete m_Sibling; } } sFrame; // Parent frame for .X file sFrame *g_pParentFrame = NULL; // Function prototypes int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev, \ LPSTR szCmdLine, int nCmdShow); long FAR PASCAL WindowProc(HWND hWnd, UINT uMsg, \ WPARAM wParam, LPARAM lParam); BOOL DoInit(); BOOL DoShutdown(); BOOL DoFrame(); BOOL SetupMesh(); sFrame *LoadFile(char *Filename); sFrame *ParseXFile(char *Filename); void ParseXFileData(IDirectXFileData *pDataObj, sFrame *ParentFrame); void DrawFrame(sFrame *Frame); int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev, \ LPSTR szCmdLine, int nCmdShow) { WNDCLASSEX wcex; MSG Msg; g_hInst = hInst; // Create the window class here and register it wcex.cbSize = sizeof(wcex); wcex.style = CS_CLASSDC; wcex.lpfnWndProc = WindowProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInst; wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = NULL; wcex.lpszMenuName = NULL; wcex.lpszClassName = g_szClass; wcex.hIconSm = LoadIcon(NULL, IDI_APPLICATION); if(!RegisterClassEx(&wcex)) return FALSE; // Create the Main Window g_hWnd = CreateWindow(g_szClass, g_szCaption, WS_CAPTION | WS_SYSMENU, 0, 0, 400, 400, NULL, NULL, hInst, NULL ); if(!g_hWnd) return FALSE; ShowWindow(g_hWnd, SW_NORMAL); UpdateWindow(g_hWnd); // Run init function and return on error if(DoInit() == FALSE) return FALSE; // Start message pump, waiting for signal to quit ZeroMemory(&Msg, sizeof(MSG)); while(Msg.message != WM_QUIT) { if(PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&Msg); DispatchMessage(&Msg); } if(DoFrame() == FALSE) break; } // Run shutdown function DoShutdown(); UnregisterClass(g_szClass, hInst); return Msg.wParam; } long FAR PASCAL WindowProc(HWND hWnd, UINT uMsg, \ WPARAM wParam, LPARAM lParam) { switch(uMsg) { case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hWnd, uMsg, wParam, lParam); } BOOL DoInit() { D3DPRESENT_PARAMETERS d3dpp; D3DDISPLAYMODE d3ddm; D3DXMATRIX matProj, matView; // Do a windowed mode initialization of Direct3D if((g_pD3D = Direct3DCreate8(D3D_SDK_VERSION)) == NULL) return FALSE; if(FAILED(g_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm))) return FALSE; ZeroMemory(&d3dpp, sizeof(d3dpp)); d3dpp.Windowed = TRUE; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.BackBufferFormat = d3ddm.Format; d3dpp.EnableAutoDepthStencil = TRUE; d3dpp.AutoDepthStencilFormat = D3DFMT_D16; if(FAILED(g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, g_hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &g_pD3DDevice))) return FALSE; // Set the rendering states g_pD3DDevice->SetRenderState(D3DRS_LIGHTING, FALSE); g_pD3DDevice->SetRenderState(D3DRS_ZENABLE, TRUE); // Create and set the projection matrix D3DXMatrixPerspectiveFovLH(&matProj, D3DX_PI/4.0f, 1.33333f, 1.0f, 1000.0f); g_pD3DDevice->SetTransform(D3DTS_PROJECTION, &matProj); // Create and set the view matrix D3DXMatrixLookAtLH(&matView, \ &D3DXVECTOR3(0.0f, 50.0f, -150.0f), \ &D3DXVECTOR3(0.0f, 50.0f, 0.0f), \ &D3DXVECTOR3(0.0f, 1.0f, 0.0f)); g_pD3DDevice->SetTransform(D3DTS_VIEW, &matView); // Load a skinned mesh from an .X file g_pParentFrame = LoadFile("warrior.x"); return TRUE; } BOOL DoShutdown() { // Release frames and meshes if(g_pParentFrame != NULL) delete g_pParentFrame; // Release device and 3D objects if(g_pD3DDevice != NULL) g_pD3DDevice->Release(); if(g_pD3D != NULL) g_pD3D->Release(); return TRUE; } BOOL DoFrame() { D3DXMATRIX matWorld; // Clear device backbuffer g_pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_RGBA(0,64,128,255), 1.0f, 0); // Begin scene if(SUCCEEDED(g_pD3DDevice->BeginScene())) { // Set world transformation to rotate on Y-axis D3DXMatrixRotationY(&matWorld, (float)timeGetTime() / 1000.0f); g_pD3DDevice->SetTransform(D3DTS_WORLD, &matWorld); // Draw frames DrawFrame(g_pParentFrame); // Release texture g_pD3DDevice->SetTexture(0, NULL); // End the scene g_pD3DDevice->EndScene(); } // Display the scene g_pD3DDevice->Present(NULL, NULL, NULL, NULL); return TRUE; } sFrame *LoadFile(char *Filename) { return ParseXFile(Filename); } sFrame *ParseXFile(char *Filename) { IDirectXFile *pDXFile = NULL; IDirectXFileEnumObject *pDXEnum = NULL; IDirectXFileData *pDXData = NULL; sFrame *Frame; // Create the file object if(FAILED(DirectXFileCreate(&pDXFile))) return FALSE; // Register the templates if(FAILED(pDXFile->RegisterTemplates((LPVOID)D3DRM_XTEMPLATES, D3DRM_XTEMPLATE_BYTES))) { pDXFile->Release(); return FALSE; } // Create an enumeration object if(FAILED(pDXFile->CreateEnumObject((LPVOID)Filename, DXFILELOAD_FROMFILE, &pDXEnum))) { pDXFile->Release(); return FALSE; } // Allocate a frame that becomes root Frame = new sFrame(); // Loop through all objects looking for the frames and meshes while(SUCCEEDED(pDXEnum->GetNextDataObject(&pDXData))) { ParseXFileData(pDXData, Frame); ReleaseCOM(pDXData); } // Release used COM objects ReleaseCOM(pDXEnum); ReleaseCOM(pDXFile); // Return root frame return Frame; } void ParseXFileData(IDirectXFileData *pDataObj, sFrame *ParentFrame) { IDirectXFileObject *pSubObj = NULL; IDirectXFileData *pSubData = NULL; IDirectXFileDataReference *pDataRef = NULL; const GUID *Type = NULL; char *Name = NULL; DWORD Size; sFrame *Frame = NULL; sFrame *SubFrame = NULL; sMesh *Mesh = NULL; ID3DXBuffer *MaterialBuffer = NULL; D3DXMATERIAL *Materials = NULL; ID3DXBuffer *Adjacency = NULL; DWORD *AdjacencyIn = NULL; DWORD *AdjacencyOut = NULL; DWORD i; // Get the template type if(FAILED(pDataObj->GetType(&Type))) return; // Get the template name (if any) if(FAILED(pDataObj->GetName(NULL, &Size))) return; if(Size) { if((Name = new char[Size]) != NULL) pDataObj->GetName(Name, &Size); } // Give template a default name if none found if(Name == NULL) { if((Name = new char[9]) == NULL) return; strcpy(Name, "Template"); } // Set sub frame SubFrame = ParentFrame; // Process the templates // Frame if(*Type == TID_D3DRMFrame) { // Create a new frame structure Frame = new sFrame(); // Store the name Frame->m_Name = Name; Name = NULL; // Add to parent frame Frame->m_Sibling = ParentFrame->m_Child; ParentFrame->m_Child = Frame; // Set sub frame parent SubFrame = Frame; } // Load a mesh if(*Type == TID_D3DRMMesh) { // Create a new mesh structure Mesh = new sMesh(); // Store the name Mesh->m_Name = Name; Name = NULL; // Load mesh data (as a skinned mesh) if(FAILED(D3DXLoadSkinMeshFromXof(pDataObj, 0, g_pD3DDevice, &Adjacency, &MaterialBuffer, &Mesh->m_NumMaterials, &Mesh->m_BoneNames, &Mesh->m_BoneTransforms, &Mesh->m_SkinMesh))) { delete Mesh; return; } // Convert to regular mesh if no bones if(!(Mesh->m_NumBones = Mesh->m_SkinMesh->GetNumBones())) { // Convert to a regular mesh Mesh->m_SkinMesh->GetOriginalMesh(&Mesh->m_Mesh); ReleaseCOM(Mesh->m_SkinMesh); } else { // Get a pointer to bone matrices Mesh->m_BoneMatrices = (D3DXMATRIX*)Mesh->m_BoneTransforms->GetBufferPointer(); // Allocate buffers for adjacency info AdjacencyIn = (DWORD*)Adjacency->GetBufferPointer(); AdjacencyOut = new DWORD[Mesh->m_SkinMesh->GetNumFaces() * 3]; // Generate the skin mesh object if(FAILED(Mesh->m_SkinMesh->GenerateSkinnedMesh( D3DXMESH_WRITEONLY, 0.0f, AdjacencyIn, AdjacencyOut, NULL, NULL, &Mesh->m_Mesh))) { // Convert to a regular mesh if error Mesh->m_SkinMesh->GetOriginalMesh(&Mesh->m_Mesh); ReleaseCOM(Mesh->m_SkinMesh); Mesh->m_NumBones = 0; } delete [] AdjacencyOut; ReleaseCOM(Adjacency); } // Load materials or create a default one if none if(!Mesh->m_NumMaterials) { // Create a default one Mesh->m_Materials = new D3DMATERIAL8[1]; Mesh->m_Textures = new LPDIRECT3DTEXTURE8[1]; ZeroMemory(Mesh->m_Materials, sizeof(D3DMATERIAL8)); Mesh->m_Materials[0].Diffuse.r = 1.0f; Mesh->m_Materials[0].Diffuse.g = 1.0f; Mesh->m_Materials[0].Diffuse.b = 1.0f; Mesh->m_Materials[0].Diffuse.a = 1.0f; Mesh->m_Materials[0].Ambient = Mesh->m_Materials[0].Diffuse; Mesh->m_Materials[0].Specular = Mesh->m_Materials[0].Diffuse; Mesh->m_Textures[0] = NULL; Mesh->m_NumMaterials = 1; } else { // Load the materials Materials = (D3DXMATERIAL*)MaterialBuffer->GetBufferPointer(); Mesh->m_Materials = new D3DMATERIAL8[Mesh->m_NumMaterials]; Mesh->m_Textures = new LPDIRECT3DTEXTURE8[Mesh->m_NumMaterials]; for(i=0;i<Mesh->m_NumMaterials;i++) { Mesh->m_Materials[i] = Materials[i].MatD3D; Mesh->m_Materials[i].Ambient = Mesh->m_Materials[i].Diffuse; // Build a texture path and load it if(FAILED(D3DXCreateTextureFromFile(g_pD3DDevice, Materials[i].pTextureFilename, &Mesh->m_Textures[i]))) { Mesh->m_Textures[i] = NULL; } } } ReleaseCOM(MaterialBuffer); // Link in mesh Mesh->m_Next = ParentFrame->m_Mesh; ParentFrame->m_Mesh = Mesh; } // Skip animation sets and animations if(*Type == TID_D3DRMAnimationSet || *Type == TID_D3DRMAnimation || *Type == TID_D3DRMAnimationKey) { delete [] Name; return; } // Release name buffer delete [] Name; // Scan for embedded templates while(SUCCEEDED(pDataObj->GetNextObject(&pSubObj))) { // Process embedded references if(SUCCEEDED(pSubObj->QueryInterface(IID_IDirectXFileDataReference, (void**)&pDataRef))) { if(SUCCEEDED(pDataRef->Resolve(&pSubData))) { ParseXFileData(pSubData, SubFrame); ReleaseCOM(pSubData); } ReleaseCOM(pDataRef); } // Process non-referenced embedded templates if(SUCCEEDED(pSubObj->QueryInterface(IID_IDirectXFileData, (void**)&pSubData))) { ParseXFileData(pSubData, SubFrame); ReleaseCOM(pSubData); } ReleaseCOM(pSubObj); } return; } void DrawFrame(sFrame *Frame) { sMesh *Mesh; D3DXMATRIX *Matrices; DWORD i; // Return if no frame if(Frame == NULL) return; // Draw meshes if any in frame if((Mesh = Frame->m_Mesh) != NULL) { // Generate mesh from skinned mesh to draw with if(Mesh->m_SkinMesh != NULL) { // Allocate an array of matrices to orient bones Matrices = new D3DXMATRIX[Mesh->m_NumBones]; // Set all bones orientation to identity for(i=0;i<Mesh->m_NumBones;i++) D3DXMatrixIdentity(&Matrices[i]); // Update skinned mesh Mesh->m_SkinMesh->UpdateSkinnedMesh(Matrices, NULL, Mesh->m_Mesh); // Render the mesh for(i=0;i<Mesh->m_NumMaterials;i++) { g_pD3DDevice->SetMaterial(&Mesh->m_Materials[i]); g_pD3DDevice->SetTexture(0, Mesh->m_Textures[i]); Mesh->m_Mesh->DrawSubset(i); } // Free array of matrices delete [] Matrices; } // Go to next mesh Mesh = Mesh->m_Next; } // Draw child frames DrawFrame(Frame->m_Child); // Draw sibling frames DrawFrame(Frame->m_Sibling); }
- Сохрани Решение (Файл -> Сохранить все).
- Бесплатная IDE MS Visual С++ 2010 Express edition;
- Бесплатный DirectX SDK 8.1 .
Но вернёмся к нашему Проекту XFile01. С момента выхода MS Visual С++ 6.0 прошло немало времени. С MSVC++2010 Express их разделяют аж 12 лет. Из-за этого вышеприведённый код (написанный в начале 2002 г.) на данном этапе в MSVC++2010 Express компилироваться не будет, выдавая многочисленные ошибки. Даже с настроенными путями к DirectX SDK. Но мы это исправим.
Готовим Проект XFile01 к компиляции
Для успешной компиляции изменим настройки (=свойства) текущего Проекта XFile01, созданного в MSVC++2010. При этом сам код из книги 2002 года останется нетронутым. Или почти нетронутым. В ходе тестирования выяснилось, что косячный автор в своём коде вызывает функцию GenerateSkinnedMesh с пятью параметрами, вместо положенных семи. Вот её прототип из документации DirectX SDK 8:HRESULT GenerateSkinnedMesh( DWORD Options, FLOAT minWeight, CONST LPDWORD pAdjacencyIn, LPDWORD pAdjacencyOut, DWORD* pFaceRemap, LPD3DXBUFFER* ppVertexRemap, LPD3DXMESH* ppMesh );
Также функцию UpdateSkinnedMesh он вызывает с двумя параметрами, вместо положенных трёх. Вот её прототип из документации DirectX SDK 8:
HRESULT UpdateSkinnedMesh( CONST D3DXMATRIX* pBoneTransforms, CONST D3DXMATRIX* pBonelnvTransforms, LPD3DXMESH pMesh );
За числом параметров в своих функциях DirectX следит строго, в противном случае выдавая многочисленные ошибки в ходе компиляции. В приведённом выше исходном коде мы доставили на месте недостающих параметров значения NULL и всё пошло как по маслу.
Обрати внимание
Напомним, что такую настройку необходимо повторно проделывать при создании каждого нового Проекта. Ниже представлен алгоритм действий по настройке Проекта, созданного в MSVC++2010 Express с применением DirectX SDK. При создании приложений под платформы, отличные от Win32, либо применении более новых версий DirectX SDK, процесс конфигурирования Проекта может отличаться от приведённого ниже.
Указываем пути к DirectX SDK 8.1
Если попытаться скомпилировать Проект XFile01 в таком виде, то ничего не выйдет.В единственном файле исходного кода WinMain.cpp можно увидеть инклуды различных заголовочных файлов DirectX (весии 8):
... // Include files #include <windows.h> #include <stdio.h> #include "d3d8.h" #include "d3dx8.h" #include "dxfile.h" #include "rmxfguid.h" #include "rmxftmpl.h" ...
На данном этапе MSVC++2010 ничего не знает об их местоположении. В статье MS Visual Cpp 2010 Express. Установка и указание путей к DirectX SDK мы указывали пути к DirectX SDK (версии 9 и выше). Для DirectX SDK 8 это делается аналогично. Начнём.
- Убедись, что MSVC++2010 запущена и в ней открыт наш текущий Проект XFile01.
- Убедись, что DirectX SDK 8.1 установлен на компьютере и ты уверенно можешь назвать полный путь к его каталогу.
- В Обозревателе решений видим: "Решение "XFile01"", а строкой ниже жирным шрифтом название Проекта (тоже XFile01).
- Жмём правой кнопкой мыши по названию Проекта XFile01. Во всплывающем меню выбираем пункт "Свойства".
Или в Главном меню выбираем Проект->Свойства. Или нажимаем Alt+F7.
- В появившемся меню свойств проекта выбираем Свойства конфигурации -> Каталоги VC++.
Указываем каталог включений (include) DirectX SDK 8.1
- В меню свойств Проекта щёлкаем левой кнопкой мыши по пункту Каталоги включения. В правой части этой строки видим кнопку с чёрным треугольником, указывающим на наличие выпадающего меню. Нажимаем на неё -> выбираем "Изменить..."
- В появившемся меню "Каталоги включения" жмём кнопку "Создать строку" (с жёлтой папкой) и указываем полный путь к заголовочным файлам DirectX SDK 8.1 (include).
- Жмём "OK".
Указываем каталог библиотек (lib) DirectX SDK 8.1
- В меню свойств Проекта щёлкаем левой кнопкой мыши по пункту Каталоги библиотек. В правой части этой строки видим кнопку с чёрным треугольником, указывающим на наличие выпадающего меню. Нажимаем на неё -> выбираем "Изменить..."
- В появившемся меню "Каталоги библиотек" жмём кнопку "Создать строку" (с жёлтой папкой) и указываем полный путь к 32-разрядным версиям файлов библиотек DirectX SDK 8.1 (lib). В нашем случае это c:\DXSDK8\lib\. Можно просто выбрать каталог из дерева каталогов, нажав кнопку с троеточием, расположенную справа от строки ввода.
- Жмём "ОК".
- На Странице свойств тоже жмём "ОК".
- Сохрани Решение (File -> Save All).
Указываем пути к Windows SDK
Помимо указания путей к заголовкам (include) и библиотекам (lib) DirectX SDK, для любого DirectX-Проекта также необходимо указать пути к заголовкам (include) и библиотекам (lib) Windows SDK. Самое смешное, что в MS Visual C++ 2010 эти пути указываются по умолчанию для каждого создаваемого Проекта. В этом нетрудно убедиться, если ещё раз открыть Проект -> Свойства -> Свойства конфигурации - >Каталоги VC++ -> Каталоги включения -> Изменить. В окне "Каталоги включения" в нижней (недоступной для редактирования) части видим список "Унаследованные значения", где в третьей строке стоит значение:$(WindowsSdkDir)include
В результате видим, что добавленные пути к DirectX SDK (в обоих окнах: include и lib) расположены вверху списка, а пути к Windows SDK - в недоступной области, на несколько строк ниже.
Но! Заголовочным файлам DirectX SDK "жизненно важно", чтобы они включались после включений заголовков Windows SDK.
Важно!
Каталоги, пути к которым указаны в окнах "Каталоги включения" и "Каталоги библиотек" при компиляции считываются один за другим по списку сверху вниз. Поэтому для корректного указания путей надо разместить пути к Windows SDK выше, а к DirectX SDK - ниже по списку (чтобы они считывались последними).
В данной ситуации мы не можем поднять пути к Windows SDK, прописанные по умолчанию при создании Проекта, т.к. они расположены в специальной нередактируемой области (ограничение бесплатной версии MSVC++2010 Express). Но можем схитрить и добавить ещё раз пути к тем же самым каталогам Windows SDK, подняв эти строки выше строк DirectX SDK.
Выше мы указывали пути к DirectX SDK. Для путей к Windows SDK это делается аналогично. Начнём.
- Убедись, что MSVC++2010 запущена и в ней открыт Проект, с которым работаешь в данный момент.
- Убедись, что Windows SDK (в нашем случае версия 7) установлен на компьютере и ты уверенно можешь назвать полный путь к его каталогу.
- Жмём правой кнопкой мыши по названию Проекта. Во всплывающем меню выбираем пункт "Свойства".
Или в Главном меню выбираем Проект->Свойства. Или нажимаем Alt+F7.
В появившемся меню свойств проекта выбираем Свойства конфигурации -> Каталоги VC++ . В правой части этой страницы расположены пути ко всевозможным каталогам. Здесь нас, как и в прошлый раз, интересуют только 2 строки: Каталоги включения и Каталоги библиотек.
Указываем каталог включений (include) Windows SDK
- В меню свойств Проекта щёлкаем левой кнопкой мыши по пункту Каталоги включения. В правой части этой строки видим кнопку с чёрным треугольником, указывающим на наличие выпадающего меню. Нажимаем на неё -> выбираем "Изменить..."
В появившемся меню "Каталоги включения" жмём кнопку "Создать строку" (с жёлтой папкой) и указываем полный путь к заголовочным (include) файлам Windows SDK. В нашем случае (Win7 x64) это C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Include. Можно просто выбрать каталог из дерева каталогов, нажав кнопку с троеточием, расположенную справа от строки ввода.
- Меняй порядок считывания каталогов включений с помощью кнопок с чёрными стрелками в верхней части окна "Каталоги вложений".
- Жмём "ОК".
Указываем каталог библиотек (lib) Windows SDK
- В меню свойств Проекта щёлкаем левой кнопкой мыши по пункту Каталоги библиотек. В правой части этой строки видим кнопку с чёрным треугольником, указывающим на наличие выпадающего меню. Нажимаем на неё -> выбираем "Изменить..."
- В появившемся меню "Каталоги библиотек" жмём кнопку "Создать строку" (с жёлтой папкой) и указываем полный путь к папке с файлами библиотек (lib) Windows SDK. В нашем случае (Win7 x64) это C:\Program Files (x86)\MicrosoftSDKs\Windows\v7.0A\Lib. Можно просто выбрать каталог из дерева каталогов, нажав кнопку с троеточием, расположенную справа от строки ввода.
- Меняй порядок считывания каталогов вложений с помощью кнопок с чёрными стрелками в верхней части окна.
- Жмём "ОК".
- На Странице свойств тоже жмём "ОК".
- Сохрани Решение (File -> Save All). Готово.
Выбираем многобайтовую кодировку
Примечание
В MS Visual C++ 2010 в настройках по умолчанию стоит набор (кодировка) символов UNICODE. В MS Visual C++ 6.0 - напротив, по умолчанию стоит кодировка ANSI (многобайтовая). Данная настройка сильно влияет на типы используемых переменных, что приводит к заметным различиям в исходном коде.
Несмотря на то, что во всех случаях рекомендуется использовать кодировку UNICODE, поддерживаемую во всех современных ОС семейства MS Windows (начиная с Win 2000/XP), большинство книг по программированию игр на классическом C++ придерживаются именно многобайтовой кодировки. Чтобы сильно не переделывать исходные коды под UNICODE, данный Проект мы настроим под многобайтовую кодировку.
- Убедись, что MSVC++2010 запущена и в ней открыт наш текущий Проект XFile01.
- В Обозревателе решений щёлкаем правой кнопкой мыши по названию только что созданного Проекта XFile01.
- Во всплывающем контекстном меню выбираем "Свойства".
- В появившемся окне установки свойств Проекта жмём Свойства конфигурации->Общие, в правой части в строке "Набор символов" выставляем значение "Использовать многобайтовую кодировку".
- Жмём ОК.
- Сохрани Решение (Файл->Сохранить все).
Отключаем инкрементную компоновку (incremental linking)
Инкрементная компоновка призвана сократить время компилирования. Но на деле её присутствие часто вызывает ошибки вроде этой:Error LNK1123: сбой при преобразовании в COFF: файл недопустим или поврежден
Ошибка LINK : fatal error LNK1123: failure during conversion to COFF: file invalid or corrupt
В MS Visual C++ 2010 даже компиляция консольных приложений нередко завершается неудачей, а вместо исполняемого файла программист видит сообщение:
LINK : fatal error LNK1123: failure during conversion to COFF: file invalid or corrupt.
ПРИЧИНА: MS Visual С++ 2010 "не нравится" версия .NET Framework, установленная в операционной системе. В общих чертах, MS Visual С++ 2010 спрограммирована для работы под управлением .NET Framework 4.0 и сильно к нему привязана. Если точнее, к нему сильно привязана система инкрементной компоновки приложений (incremental linking), которая по умолчанию включена для всех создаваемых проектов.
Во время установки MS Visual C++ 2010 пытается установить свой "родной" .NET Framework 4.0, проверяя версию этой программной платформы, установленную в ОС на данный момент. Если версия .NET Framework ниже 4.0, то она обновляется до 4.0 и всё прекрасно компилируется. Если версия .NET Framework выше 4.0, то всё оставляется как есть: IDE успешно завершает установку, но при компиляции ВСЕХ приложений выскакивает данная ошибка. Более того, ошибка была замечена даже при наличии в системе .NET Framework версии 4.0, но отличающейся от "родной" припиской вроде "Beta" или "Release Candidate".
ВАРИАНТЫ РЕШЕНИЙ:
1. Отключить инкрементную линковку в опциях Проекта.
В главном меню MSVC++2010 выбираем: Проект->Свойства->Свойства конфигурации->Компоновщик(Linker)->Включить инкрементное построение (Incremental Linking). Данный пункт по умолчанию включен для всех новых проектов. Выставляем его в Нет (No). И жмём OK. Инкрементное построение заметно сокращает время компилирования больших проектов. Но в нашем случае его отсутствие некритично.
2. Удалить (переместить в другое место) утилиту cvtres.exe из каталога bin установленной MS Visual C++ 2010.
В нашем случае (Win7 x64) полный путь до данного файла такой: C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin\cvtres.exe . Официальное название данного приложения - Microsoft® Resource File To COFF Object Conversion Utility (утилита конвертации файлов двоичных ресурсов в Component Object File Format). Опытным путём установлено, что при линковке IDE "спотыкается" именно об него.
3. Удалить из системы все версии .NET Framework (включая языковые пакеты и всякие профайлеры, если есть) и саму MS Visual C++ 2010.
Всё вышеперечисленное можно без труда найти в меню "Программы и компоненты" (MS Windows Vista/7/8). Затем заново установить MS Visual C++ 2010. При этом автоматом установится .NET Framework 4.0, идущий с ней в наборе.
Третий пункт - самый долгий. На деле почти всегда хватает выполнения первых двух. Данным вопросом озадачивались ребята здесь: https://www.cyberforum.ru/cpp-beginners/thread637174.html?ysclid=l3akpmanrq. После этого линковка (=компоновка) проходит идеально. Подобные "костыли" в IDE от Майков - не редкость. Можно предположить, что команда с головой ударилась в тестирование .NET-возможностей MSVC++2010, совсем забыв о Win32-направлении (либо признав его бесперспективным).
Отключим инкрементную компоновку в свойствах открытого Проекта. Для MS Visual C++ 2010 порядок следующий:
- Убедись, что MSVC++2010 запущена и в ней открыт Проект, с которым работаешь.
- В Обозревателе решений щёлкаем правой кнопкой мыши по названию Проекта.
- Во всплывающем контекстном меню выбираем "Свойства".
- В появившемся окне установки свойств Проекта жмём Свойства конфигурации -> Компоновщик -> Общие (Configuration Properties -> Linker -> General), в правой части в строке "Включить инкрементную компоновку" ставим значение Нет (/INCREMENTAL:NO).
- Жмём ОК.
Прописываем библиотеки d3dx8.lib, d3d8.lib, dxguid.lib, d3dxof.lib и winmm.lib в окне "Дополнительные зависимости" (Additional dependencies) компоновщика (Linker)
Данный этап проходил даже автор кода Jim Adams в 2002 году, прогая на MSVC++6.0. В начальных комментариях листинга WinMain.cpp он намекнул, что библиотеки d3dx8.lib, d3d8.lib, winmm.lib и dxguid.lib необходимо явно указывать в списке дополнительных зависимостей линкера (=компоновщика):/************************************************** WinMain.cpp Chapter 6 XFile/Skinned Mesh Demo Programming Role-Playing Games with DirectX by Jim Adams (01 Jan 2002) Required libraries: WINMM.LIB, D3D8.LIB, D3DX8.LIB, and DXGUID.LIB **************************************************/ ...
Библиотека WINMM.LIB расположена в папке с установленным Windows SDK (в нашем случае по пути C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Lib), пути к которой мы уже прописали выше.
Библиотеки D3D8.LIB, D3DX8.LIB и DXGUID.LIB расположены в папке с установленным DirectX SDK 8 (в нашем случае по пути C:\DXSDK8\lib), пути к которой мы также прописали выше.
На деле выяснилось, что одна из функций примера также юзает библиотеку d3dxof.lib. Поэтому её тоже добавим. Она также расположена в папке с установленным DirectX SDK 8.
Т.к. мы создаём исполняемое приложение (исполняемый .ехе-файл), а не библиотеку, то после компиляции полученный объектный модуль сразу линкуется путём вызова компоновщика (linker). Так вот, этот самый компоновщик по ранее прописанным каталогам данные библиотеки не ищет. Поэтому их необходимо указывать отдельно в окне настроек Проекта, в разделе "Компоновщик".
ОК, начинаем.
- Убедись, что MSVC++2010 запущена и в ней открыт наш текущий Проект XFile01.
- В Главном меню MS Visual С++ 2010 выбираем Проект -> Свойства (Project -> Properties).
- В появившемся окне установки свойств Проекта последовательно щёлкаем по раскрывающимся ветвям иерархического дерева: Свойства конфигурации -> Компоновщик -> Ввод (Configuration Properties -> Linker -> Input).
- В правой части, напротив строки "Дополнительные зависимости" жмём кнопку с чёрным треугольником.
- Во всплывающем списке жмём "Изменить".
- В появившемся окне "Дополнительные зависимости" в верхнем поле ввода прописываем в столбик (один под другим) имена файлов пяти библиотек:
d3d8.lib
dxguid.lib
d3dxof.lib
Winmm.lib
- Жмём ОК, ОК.
- Сохрани Решение (Файл->Сохранить все).
Отключаем использование компоновщиком библиотеки libci.dll
Да, даже на данном этапе компиляция Проекта выдаст ошибку. Библиотека libci.dll использовалась в VisualStudio когда-то очень давно, и в современных версях IDE её нет. Тем не менее компоновщик почти всегда вызывает её при компиляции, ругаясь на её отсутствие. Самый простой способ это исправить - запретить использовать libci.dll по умолчанию.ОК, начнём.
- Убедись, что MSVC++2010 запущена и в ней открыт Проект, с которым работаешь в данный момент.
- В Главном меню MS Visual C++ 2010 выбираем Проект -> Свойства (Project -> Properties).
- В появившемся окне установки свойств Проекта последовательно щёлкаем по раскрывающимся ветвям иерархического дерева: Свойства конфигурации -> Компоновщик -> Командная строка (Configuration Properties -> Linker -> Command Promt).
- Пишем в него строку: /NODEFAULTLIB:libci
- Жмём ОК.
- Сохрани Решение (Файл->Сохранить все).
Компилируем Проект XFile01
Наконец, наш тестовый Проект готов к компиляции.- Жми кнопку с зелёным треугольником на панели инструментов главного окна MSVC++2010 или F5 нак лавиатуре.
После компилирования приложение XFile01 автоматически запустится и покажет окно с голубым фоном. Это нормально, т.к. .Х-файл и его текстуру пока никто не готовил.
Обрати внимание
Если попытаться запустить скомпилированный .exe-файл на компьютере без установленной MSVC++2010, то экзешник (скорее всего) "ругнётся" на отсутствующую библиотеку MSVCR100D.dll. Буква D в её имени означает Debug, т.е. данное приложение скомпилировано с отладочной конфигурацией (профилем), которая выставляется по умолчанию у каждого вновь создаваемого Проекта/Решения. При релизе игры приложение напротив, компилируют с конфигурацией Release (релиз). При этом при создании инсталлятора в дистрибутив с игрой добавляют т.н. набор библиотек времени выполнения (в нашем слчае MS Visual C++ 2010 Runtime Redistributable). Он устанавливается вместе с игрой, т.к. без него игра тупо не стартанёт. Если для Release-профиля такой набор нетрудно нагуглить (например здесь: https://www.mydigitallife.net/visual-c-2010-runtime-redistributable-package-x86-x64-ia64-free-download, 4,8 Мб для 64-разрядной версии ОС), то для запуска Debug-версии на отдельном компе на него потребуется установить целую MSVC++2010. Всё так сложно в том числе с целью не допустить утечек предрелизных разработок с компов игрокодерских компаний. Релиз есть релиз. А с дебаг-версиями, как правило, работают только сами игрокодеры, на своих компах отлавливая ошибки.
Конфигурации Debug и Release легко переключаются на странице свойств открытого Проекта (в MSVC++2010 в главном меню выбираем Проект->Свойства, в верхней части диалога видим всплывающий список "Конфигурация").
Готовим .X файл
В конце реализации авторской функции Dolnit видим функцию загрузки .Х-файла warror.x:... // Load a skinned mesh from an .X file g_pParentFrame = LoadFile("warrior.x"); ...
Очевидно, что здесь можно указать любое другое имя файла.
- Создай в любом 3D-редакторе или скачай с Интернета любой файл модели с расширением .х .
В целях экономии времени возьми файл warrior.x из архива с примером от Jim Adams: https://disk.yandex.ru/d/pyoDHEdm1j514g. К сожалению, автор книги не рассказал, с какими сеттингами он создавал данный .Х-файл.
- Помести его в папку с исполняемым файлом Проекта XFile01.
- Повторно запусти XFile01.exe.
Готовим текстуру
По умолчанию DirectX-приложения ищут сопутствующую текстуру в том же каталоге, где расположен .Х-файл. Причём их имена должны совпадать.- Создай в любом графическом редакторе (проще всего в обычном Paint) файл растрового изображения.
- Сконвертируй его в формат .bmp и переименуй в warrior.bmp.
- Помести его в папку с исполняемым файлом Проекта XFile01.
- Повторно запусти XFile01.exe.
Ссылка на готовый Проект XFile01
https://disk.yandex.ru/d/pyoDHEdm1j514g (ZIP-архив; 67 Кб). В архив также включены .Х-файл с мешем и .BMP-файл текстуры.Задание на дом
В папке с примерами (samples) DirectX SDK 8 есть пример "Tut06_Meshes", также демонстрирующий загрузку меша из .X-файла. В нашем случае путь до него такой: c:\DXSDK8\samples\Multimedia\Direct3D\Tutorials\Tut06_Meshes\ .- Найди в папке с данным примером файл исходного кода Meshes.срр.
- Создай новый Проект Meshes01 в MSVC++2010 и скомпилируй исходный код, содержащийся в Meshes.срр.
- Зрительно сравни исходные коды примеров Джима Адамса и MS DirectX SDK 8, выяви сходства и различия.
- Подумай, почему модель тигра из данного примера (tiger.x), "подсунутая" приложению XFile01.exe под именем warrior.x не хочет открываться.
- Проведи обратный эксперимент, "подсунув" скачанные модели приложению, полученному в результате компиляции Проекта Meshes01.
- Запусти программу mview.exe.
- Открой в ней файл warrior.х из примера к данному уроку. (В главном меню жми File->Open Mesh File...)
- Открой окно иерархии фреймов. (В главном меню жми View->Hierarchy.)
- Щёлкни левой кнопкой мыши по символу + .
- Последовательно нажимая на символы +, раскрой все ветви иерархии фреймов. Изучи их "говорящие" имена и расположение в иерархии.
- Проделай те же операции с файлом tiger.х, расположенном в папке с примерами установленного DiretcX SDK 8.
Вероятно, решающее значение для корректного открытия .X файла имеет метод его сохранения из 3D-редактора. Один из примеров корректных сеттингов в окне импорта 3DS Мах представлен здесь: http://www.cgdev.net/axe/download.php .
Исследуем код примера
В начале листинга видим пару структур:- sMesh - описывает загружаемый меш.
- sFrame - содержит иерархию фреймов + матрицы трансформаций каждого фрейма.
Загрузка скинированного меша
Функция-обёртка (wrapper function) LoadMesh вызывает функцию ParseXFile:... sFrame *LoadFile(char *Filename) { return ParseXFile(Filename); } sFrame *ParseXFile(char *Filename) { IDirectXFile *pDXFile = NULL; IDirectXFileEnumObject *pDXEnum = NULL; IDirectXFileData *pDXData = NULL; sFrame *Frame; // Create the file object if(FAILED(DirectXFileCreate(&pDXFile))) return FALSE; // Register the templates if(FAILED(pDXFile->RegisterTemplates((LPVOID)D3DRM_XTEMPLATES, D3DRM_XTEMPLATE_BYTES))) { pDXFile->Release(); return FALSE; } // Create an enumeration object if(FAILED(pDXFile->CreateEnumObject((LPVOID)Filename, DXFILELOAD_FROMFILE, &pDXEnum))) { pDXFile->Release(); return FALSE; } // Allocate a frame that becomes root Frame = new sFrame(); // Loop through all objects looking for the frames and meshes while(SUCCEEDED(pDXEnum->GetNextDataObject(&pDXData))) { ParseXFileData(pDXData, Frame); ReleaseCOM(pDXData); } // Release used COM objects ReleaseCOM(pDXEnum); ReleaseCOM(pDXFile); // Return root frame return Frame; } ...
Обе функции авторские. Шаблоны фреймов (Frame Tamplates), по мере считывания (=энумерации) функцией LoadFile, добавляются в общую иерархию фреймов. Все остальные шаблоны (не являющиеся шаблонами фреймов) также энумерируются в виде серии дочерних (точнее, не имеющих своих потомков) шаблонов.
При обнаружении шаблона меша (Mesh Template) во время энумерирования .Х-файла функция LoadFile загружает меш путём вызова DirectX-функции D3DXLoadSkinMeshFromXof. Сразу после этого объект меша добавляется в связный список (linked list) загруженных мешей. У каждого фрейма есть указатель на тот или иной загруженный меш. Это позволяет на основе одного меша создавать несколько фреймов.
Как только меш окажется загруженным, функция загрузки автоматически сопоставит кости меша с их соответствующими фреймами и затем загрузит материалы. Если меш не содержит костей, он автоматически конвертируется в стандартный меш (standart mesh).
Таким образом, для загрузки скинированного меша (или серии таких мешей) вызывают функцию LoadFile, указав в единственном параметре имя загружаемого .Х-файла:
... // Load a skinned mesh from an .X file g_pParentFrame = LoadFile("warrior.x"); ...
В свою очередь функция LoadFile вызывает функцию ParseXFile, которая возвращает в первую функцию указатель на фрейм, являющийся ключевым (корневым; root frame).
Сразу после загрузки меша обновляем (Update) его и рендерим (Render). Обновление меша запрашивает указатель на объект стандартного меша (standart mesh object), который затем также используется для рендеринга (меша).
Обновление и рендеринг скинированного меша (Updating and Rendering a Skinned Mesh)
Прежде чем рендерить скинированный меш, его надо сперва обновить (update). Ведь он, на самом деле, не знает, как именно отрисовывать самого себя. Для этого применяют все (родительскую + собственную) матрицы трансформации к каждой из костей и вызывают функцию ID3DXSkinMesh::UpdateSkinnedMesh, передавая ей в качестве аргумента указатель на инстанс объекта ID3DXMesh, представляющий собой итоговую форму меша с применением всех нужных деформаций.Для изменения трансформации фрейма мы модифицируем её матрицу вращения (frame's rotation transformation matrix). Данной операции часто оказывается достаточно, т.к. дочерние фреймы не могут быть оттранслированы. Может только ключевой фрейм.
Сперва определяем, какой из фремов будем модифицировать. Для этого сканируем список фреймов и затем путём вызова функции D3DXMatrix* изменяем вращение каждого из них.
Как только вращение фреймов окончено и у каждого установлена нужная ориентация в пространстве, всё готово к обновлению всех трансформаций в иерархии фреймов. Начинают с ключевого фрейма, который сориентирован в мире именно там, где будет рендериться меш. Затем матрица трансформации ключевого фрейма передаётся следующему дочернему фрейму и комбинируется с его собственной. Этот процесс продолжается до тех пор, пока матрицы трансформаций всех фреймов не будут обновлены.
3D-анимации меша, хранимые в .Х-файлах
3D-анимация:- Может храниться в самом .Х-файле.
- Сильно отличается от своей 2D-сестрёнки.
В основе 3D-анимации лежит изменение матриц трансформации фреймов, используемых для переориентирования мешей с течением времени. Таким образом меши, расположенные во фреймах могут вращаться и изменять своё положение прямо у игрока на глазах. Такое перемещение мешей и является анимацией. Меши в реальном времени могут быть:
- перемещены (при изменении матрицы трансляции; translation matrix);
- повёрнуты (при изменении матрицы вращения; rotation matrix);
- увеличены или уменьшены в размерах (при изменении матрицы масштабирования; scale matrix).
Каждая анимация в .Х-файле представлена в виде набора специальных шаблонов анимации (animation templates). Загрузка данных из них происходит примерно тем же образом, что и загрузка скинированного меша. Загружать анимацию из .Х-файла весьма непросто. У каждой анимации надо обрабатывать шаблон анимации, шаблон набора анимации (animation set template), временные шаблоны (time templates), шаблоны ключевых кадров (key-frame templates) и т.д.
Шаблоны анимации содержат т.н. ключи (keys), которые применяются в анимации по ключевым кадрам (key frame ainamtion). Каждый ключ представляет собой одну трансформацию:
- translation (изменение положения, перемещение);
- вращение (rotation);
- изменение масштаба (scaling).
Обрати внимание
Когда мы говорим про время, в нашем случае оно не привязано к какой-либо системе измерения. Игрокодер сам решает, в чём его измерять. Например отрезки времени можно измерять в секундах, прошедших с определённого момента. Временем также может быть прошедшее число кадров. Для упрощения задачи работы со временем его интервалы обычно привязываются к системному времени компьютера, позволяющему отсчитывать равные промежутки времени.
Группы анимаций (Animation sets)
В пределах одного .Х-файла несколько анимаций объединяются в группы или наборы. Каждому набору "приписан" определённый фрейм. Возможна ситуация, когда одному набору назначено более одного ключа анимации. Например фрейм может быть изменён набором ключей вращения (set of rotation-keys) и набором ключей трансляции (set of translation-keys) одновременно. К счастью, 3D-редакторы (чаще всего) позволяют не иметь дело с данными анимаций .Х-файла напрямую.Анимация по ключевым кадрам (Key Frame animation)
- Берутся два различных положения объекта (два разных кадра анимации) и интерполируются (плавно переходят) из одного в другой с течением времени.
Любопытный факт
Метод интерполяции широко применяется в математике. Он представляет собой способ расчёта промежуточных значений между двумя числами, в течение определённого промежутка времени. Например, если от дома до твоей работы 2 километра и ты ежедневно добираешься туда за 30 минут, то по этим данным можно определить пройденную дистанцию на каждом конкретном временном участке по следующей формуле: Distance = (DistanceToWork / TravelTime) * CurrentTime .
Путём несложных вычислений определяем, что, спустя 26 минут после начала пути, ты пройдёшь (2 / 30) * 26 = 1,73 км.
Как ты знаешь, иерархия фреймов формируется по принципу присоединения одних фреймов к другим. Добавим сюда вычисления интерполяции промежуточных кадров и получим плавную анимацию. В данной статье мы применяем технику матричных ключевых кадров (matrix key framing). Благодаря применению матричных объектов из библиотеки D3DX техника матричных ключевых кадров реализуется достаточно просто. Допустим, у нас есть две матрицы: Mat1 и Mat2, представляющие собой начальную (starting) и конечную (ending) матрицы соответственно. Временной отрезок представлен как Length, а текущее время - Time (т.е. промежуток от отметки 0 до Length; См. Рис.2). Интерполированная матрица (interpolated matrix) вычисляется так:
// D3DXMATRIX Mat1, Mat2; // DWORD Length, Time; D3DXMATRIX Mat1nt; // Результирующая матрица интерполяции. // Вычисляем матрицу интерполяции. Mat1nt = (Mat2 - Mat1) / Length; // Умножаем на время. Mat1nt *= Time; // Прибавляем начальную матрицу и готово! Matlnt += Mat1;
В результате получаем матрицу, содержащую ориентацию/положение 3D-объекта на любом промежуточном отрезке времени.
Итоги
На первый взгляд урезанная MSVC++2010 Express Edition при грамотном применении оказывается вполне удобной и пригодной для игрокодинга IDE.Источники
1. Adams J. Programming Role Playing Games with DirectX 8.0. - Premier Press, 2002
Последние изменения страницы Среда 18 / Май, 2022 00:02:31 MSK
Последние комментарии wiki