DirectX шаг за шагом: Модель из файла
Вам наверное надоело смотреть на кубики на экране, поэтому в этом уроке мы загрузим обычную трехмерную модель на экран. В уроке будет мало теории так как он будет базироваться на всех предыдущих. А вот кода будет много:)
Мы напишем загрузчик модели из формата ms3d. Этот формат используется в программе MilkShape 3D. Я выбрал этот формат потому что он на мой взгляд наиболее простой и при этом может использоваться в реальных проектах. obj и 3ds — это слишком скучно, x — мертв, а колада слишком перегружена для нашего урока.
Будет одно очень важное ограничение к модели — у нее должен быть всего один материал и одна текстура, это ведь урок, вы сами сможете сделать загрузку модели использующей несколько материалов. Также мы пока не будем делать анимацию, только статическую модель.
Подготовка [ ]
Как обычно, создайте проект, подключите фреймворк третьей версии, создайте main.cpp:
Пока у нас нет еще класса StaticMesh, этот класс и будет отвечать за модель. Мы объявили его дружественным, чтобы иметь возможность обращаться к приватным членам рендера из него. Это решение немного кривовато, но нам пока сойдет:) Мы же не правильную архитектуру учим, а DirectX.
Еще мы добавили две матрицы пространства — видовую и проекционную. А вот мировая здесь не нужна, так как будет в StaticMesh.
В инициализации сначала задаем параметры видовой матрицы. Вы не забыли что видовая матрица — это наша камера из которой мы смотрим на наш мир?
Затем задаем проекционную матрицу и инициализируем наш меш. mesh.ms3d — это имя файла модели.
Спецификация ms3d [ ]
Создайте ms3dspec.h и пишите:
Это не полная спецификация, а только та часть которая нам нужна для урока. Более полную вы можете посмотреть здесь либо скачать с оффициального сайта.
Данные структуры позволят нам быстро загрузить информацию из файла.
StaticMesh [ ]
Почти все здесь должно быть вам уже знакомо и понятно, а если нет, то вернитесь к предыдущим урокам.
Из интересного здесь только m_objMatrix. Эта матрица указывает расположение нашего объекта в мире. То есть это мировая матрица. Трансформируя ее вы сможете двигать модель, вращать и масштабировать ее.
Вначале опишем вершину и структуру константного буфера. Затем вспомогательная функция ToString() которая перевод char* в wchar_t* что очень нам пригодится ниже.
В начале мы будем грузить файл меша (метод m_loadMS3DFile() ) А затем инициализировать шейдеры (метод m_InitShader() ).
Начнем с загрузки модели из файла:
Здесь происходит всего лишь чтение файла и заполнение структур описанных в спецификации данными из этого файла. Теперь ниже:
Мы получили данные из файла и теперь должны из них сделать данные которые передадим нашей видеокарте. Сначала определяем количество индексов. Дело в том что ms3d хранит треугольники. И каждый треугольник содержит три индекса. Поэтому мы умножаем количество треугольников на 3.
Затем создаем нужное количество вершин.
В циклах заполняем сначала наши идексы, а затем вершины.
После циклов мы вызовем метод m_LoadTextures() который загрузит текстуру из первого материала. Вы помните об условии что в этом уроке только один материал (а значит всего одна текстура на модель)?
И удаляем теперь ненужные данные.
Здесь мы создаем три буфера (вершинный, индексный и константный). Как вы видите, мы обращаемся к устройству DirectX через указатель на рендер ( m_render->m_pd3dDevice). Это работает, потому что StaticMesh — друг рендера:)
Метод загрузки текстуры простой:
Метод инициализации шейдеров:
Грузим и инициализируем шейдеры, описываем формат ввода (у нас есть две характеристики вершины — позиция и координаты текстуры). Далее создаем семплер описывающий вывод текстур.
Метод рендера меш у нас такой:
В начале вращаем матрицу меша, затем перемножаем матрицу меша с видовой и проекционной.
Шейдеры [ ]
У нас будет два шейдера. Мы их можем использовать для всех мешей.
Собирайте. Где взять сам меш? Вы можете сами создать его в MilkShape 3D (не забудьте — всего один материал), либо откуда-то скачать. Вот меш который использовал я при написании данного урока. Данный меш и текстура были взяты из игры World of Warcraft и все права принадлежат Blizzard. Используя программу WoW Model Viewer вы сможете сами взять любую модель из этой игры, просто выберите ее и экспортируйте в ms3d (но только ищите те у которых всего одна текстура).
Урок DirectX 12. Рисуем первый треугольник
DirectX 12 — это последняя версия собственного API компьютерной графики от Microsoft, используемого для платформ Windows и Xbox. Он, как и Vulkan, нацелен на создание менее сложного драйвера и API, более близкого к архитектуре современных графических процессоров.
DirectX фокусируется на рендеринге в реальном времени, поэтому он предназначен для разработчиков игр и систем автоматизированного проектирования (CAD). Поскольку это отраслевой стандарт API компьютерной графики, можно ожидать, что почти все совместимые аппаратные средства будут иметь его надежную поддержку, и он станет стандартом для коммерческих проектов.
Этот графический API является самым популярным и вездесущим из всех: от программ для создания 3D/изображений наподобие Marmoset Toolbag 3, Adobe PhotoShop, Autodesk Maya, до коммерческих игр вроде OverWatch от Blizzard и Fortnite от Epic, подавляющего большинства игр на Steam от Valve и многого другого (несмотря на эксклюзивность платформы).
DirectX 12 сейчас поддерживается в:
- Windows 10
- Xbox One
- Xbox One X
В Windows 7 имеется частичная поддержка через D3D12 On 7.
Его поддерживает множество языков:
- C
- C++
- C#
- Rust (через dx-sys)
Я подготовил хранилище Github со всем, что понадобится. На современном C++ мы собираемся пройтись по приложению Первый Треугольник. Это программа, которая создает треугольник и визуализирует его на экране.
Подготовка
Теперь напечатайте в терминале следующее.
git clone https://github.com/alaingalvan/directx12-seed —recurse-submodules
# Зайдите в папку
# Если вы забыли про рекурсию подмодулей, всегда можно запустить:
git submodule update —init
# Создать папку для компиляции проекта
# Чтобы сделать ваше Visual Studio решение на Windows x64
# Чтобы сделать XCode проект на Mac OS
# Чтобы сделать .make файл на Linux
# Для любой платформы:
Обзор
Документация DirectX 12 рекомендует использовать ComPtr <T> в качестве альтернативы к std :: shared_ptr <T>, потому что так лучше отлаживать и легче инициализировать структуры данных DirectX 12.
Независимо от того, выберете ли вы ComPtr <T> или нет, этапы рендеринга растровой графики с помощью DirectX 12 очень похожи на этапы других современных графических API:
- Инициализация API — создаются ваши Фабрика, Адаптер, Устройство, Очередь, Распределитель команд, Список команд, Цепочка обмена.
- Инициализация ресурсов — ваш Буфер вершин, Индексный буфер, Однородный буфер, Представления целей визуализации, Графический конвейер, Примитивы синхронизации.
- Визуализация — обновление ваших однородных данных, добавление команд в очередь и ожидание следующего фрейма.
Далее будут объяснены фрагменты, которые можно найти в хранилище Github, с определенными опущенными частями и переменными-членами (mMemberVariable), объявленными встроенными без префикса m, чтобы их тип был легче видеть. Примеры здесь могут работать сами по себе.
Создание окна
Мы используем CrossWindow для создания кроссплатформенного окна, поэтому создать окно и обновить его очень просто:
Инициализация API
Фабрика

Фабрики — это точка входа в DirectX 12 API, и следовательно, первое, что вам нужно создать.
Вы также можете создавать отладочные структуры данных, такие как debugController, которые могут включать проверку использования API.
Адаптер

Адаптер предоставляет информацию о физических характеристиках данного устройства DirectX.
Устройство

Устройство — это ваша основная точка входа в DirectX 12 API, предоставляющая доступ к внутренним частям API. Это ключ к важным структурам данных, таким как конвейеры, шейдеры, состояние рендеринга и т.д.
Устройство отладки позволяет вам полагаться на режим отладки DirectX 12. Структуры данных, созданные с помощью DirectX, может быть трудно отслеживать. С Device вы сможете предотвратить утечку данных или проверить, правильно ли вы создаете или используете API.
Очередь команд

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

Синхронизация примитивов позволяет вашей программе знать, когда определенные задачи были выполнены GPU, например, загрузка в эксклюзивную память GPU или когда вы закончили представлять на экране.
Swap chain

Swap chain занимается обменом между фрейм-буферами, чтобы показать ваш рендер в заданном окне.
Инициализация ресурсов
Корневая подпись
Корневая подпись представляет собой объекты, которые определяют тип ресурсов, привязанных к графическому конвейеру:
Буфер вершин

Буфер вершин хранит информацию о каждой вершине, доступную в виде атрибутов в вашем вершинном шейдере. Все буферы являются объектами ID3D12Resource в DirectX 12, хоть вершинные, хоть индексные, однородные и т.д.
Индексный буфер

Индексный буфер содержит индивидуальные индексы каждых треугольника/линии/точки, которые вы собираетесь нарисовать.
Константный uniform буфер

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

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

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

Этап конвейера описывает все необходимое для выполнения основанного на растеризации данного вызова отрисовки.
Рендеринг
Рендеринг в DirectX 12 — это простое дело обновления любых однородных объектов, которые вы намереваетесь обновить, отправка списков команд для выполнения, представление цепочки обмена, чтобы обновлялось окно вашей ОС, и оповещение вашего приложения о завершении рендеринга.
Устранение дескрипторов
Если вы используете структуры данных ComPtr <T>, то как и в случае с общими указателями, вам не нужно беспокоиться об устранении любых созданных вами дескрипторов. Если же вы этого не сделаете, вы можете вызвать функцию Release (), встроенную в каждую структуру данных DirectX.
Заключение
DirectX 12 — это многофункциональный и надежный API компьютерной графики, идеально подходящий для коммерческих проектов. Он похож на другие современные графические API по своей архитектуре, а также является основным API, поддерживаемым как разработчиками драйверов оборудования, так и создателями коммерческих продуктов. В этом посте рассматривается отрисовка, основанная на растровых данных, однако есть и другие достойные рассмотрения аспекты DirectX, которые здесь не обсуждались, такие как:
-
— Аппаратно ускоренное выполнение модели машинного обучения. — Аппаратно ускоренные трассировка лучей и обход сцены.
- Вычислительные шейдеры — Основанное на GPGPU выполнение произвольных задач, таких как обработка изображений, физика и т.д.
Весь исходный код, описанный в этом посте, вы найдете в хранилище Github.
Как создать x directx файл

Microsoft Visual Studio 3D Starter Kit

Рисунок 1. Начальное состояние Microsoft Visual Studio 3D Starter Kit
- Анимировано пять объектов: четыре фигуры вращаются вокруг чайника, а чайник, в свою очередь, «танцует».
- Каждый предмет сделан из отдельного материала; некоторые имеют сплошной цвет, а поверхность куба представляет собой растровый рисунок.
- Источник света находится в верхнем левом углу сцены.
- В правом нижнем углу экрана расположен счетчик кадровой скорости (количество кадров в секунду).
- Сверху находится индикатор очков.
- Если щелкнуть какой-либо предмет, он выделяется и увеличивается количество очков.
- Если щелкнуть экран игры правой кнопкой мыши или провести по экрану от нижнего края к середине, появятся две кнопки для последовательного переключения цвета чайника.

Рисунок 2. Текстура футбольного мяча, приспособленная к сфере

Рисунок 3. Трехмерная OBJ-модель мяча
Формат файлов X (DirectX)
Существует множество способов хранения тех или иных данных. 1 Игры хранят карты уровней и меши в файлах, которые считываются в память во время их (игр) выполнения. Общепринята практика, когда 3D-дизайнер (graphic artist) проектирует 3D-персонажей и сохраняет их в файле. Готовые модели загружаются игровым приложением в память, в том числе средствами DirectX.
Во времена DOS для каждой игры (или серии игр на схожем движке) придумывались свои уникальные форматы файлов для хранения игровых данных. Но с приходом DirectX ситуация значительно изменилась. Даже несмотря на то, что уникальные форматы файлов в современных играх — нередкое явление.
Компания Майкрософт, разработавшая DirectX, впервые предложила унифицировать формат хранения игровых данных, придумав для этого универсальный формат файлов с расширением x (икс). Вообще, это даже не формат, а целая система, позволяющая хранить любые цифровые данные (изображения, спрайты, меши, анимации и т.д.). .X-файлы позволяют сохранять и загружать любые данные любого размера.
Внутренняя структура .x-файла
Синтаксис .x-файла похож на синтаксис языка C. Но есть и отличия.
Заголовок (Header)
В первой строке любого .x-файла всегда идёт заголовок. В нём указывается версия и режим (mode) хранения информации (text или binary). При создании .x-файлов вручную заголовок из листинга выше можно просто копировать без изменений.
Шаблоны (Templates)
Примечание
Определение шаблона начинается со служебного слова template, с разу за которым следует имя шаблона. В примере выше имя шаблона MY_GAME_INFO. Это имя затем будет использоваться при создании "инстансов" данного шаблона внутри .x-файла.
В первой строке внутри фигурных скобок (тела объявления шаблона) всегда указывается GUID (Globally Unique Identifier), представляющий собой набор букв и цифр. Именно по GUID DirectX отличает один шаблон от другого. Он также применяется для идентификации шаблонов внутри DirectX-приложений. GUID генерируется с помощью специального приложения GUID Generator либо с помощью бесплатных веб-сервисов вроде https://www.guidgenerator.com.
Сразу после GUID указываются пользовательские члены данных. Типы данных здесь так же очень схожи с оными в C++. В нашем случае шаблон MY_GAME_INFO содержит 3 переменных члена: две строки (string), содержащие название игры и имя создателя, и одна типа DWORD, где будет храниться значение текущей версии игры. Вот список типов данных и служебных слов, допустимых к применению в .x-файлах:
| ARRAY | STRING |
| BINARY | SWORD |
| BINARY_RESOURCE | TEMPLATE |
| CHAR | UCHAR |
| CSTRING | ULONGLONG |
| DOUBLE | UNICODE |
| DWORD | WORD |
| SDWORD |
Шаблоны-потомки
Троеточие в квадратных скобках, указанное в теле объявления шаблона, означает, что инстансы данного шаблона могут иметь объекты-потомки (child objects). При этом их количество и тип данных заранее неизвестны (= определяются программером позднее и могут быть любыми). Шаблоны, способные (в перспективе) содержать объекты-потомки, называют открытыми (open templates). Шаблоны без объектов-потомков — закрытыми (closed templates).
Объекты данных (Data Objects)
- Обычно составляют львиную долю содержимого .x-файлов.
- Представляют собой "инстансы" шаблонов, объявленных в начале .x-файла.
- В теле объекта (или инстанса шаблона) членам шаблона присваиваются конкретные значения:
Описание объекта начинается с указания имени шаблона, на базе которого он создаётся (MY_GAME_INFO). Далее через пробел указывается имя объекта (выбирается программером произвольно), по которому его будут искать парсинг-функции DirectX.
Ниже в фигурных скобках указываются свойства (properties) объекта (по одному на каждой строке; каждая строка оканчивается символом точки с запятой(;)). Они указываются строго в том порядке, в котором они были инициализированы в шаблоне объекта .
Родительские (parent) и дочерние (child) объекты
Каждая аптечка представляет собой отдельный объект, хранящиеся в виде дочерних объектов объекта шаблона MY_LEVEL. Синтаксис здесь предельно прост: дочерние объекты указываются внутри фигурных скобок объекта-родителя.
Объекты данных и ссылки (Data Objects and References)
Здесь ссылка Game01 в объекте шаблона MY_LEVEL ссылается по имени на объект Game01 шаблона MY_GAME_INFO. При этом без разницы, кем они приходятся друг другу (родитель или потомок). При выполнении итерации объектов .x-файлов средствами DirectX, каждый из них проходит проверку на то, является ли данный объект ссылкой.
Стандартные шаблоны
- Созданы разработчиками DirectX.
Пользовательские шаблоны (Custom Templates)
- В них можно хранить любые пользовательские данные.
- Создаются по аналогии с шаблонами DirectX
Читаем .x-файлы средствами DirectX
- Для этого DirectX предоставляет ряд специальных интерфейсов.
Подготовка
Регистрируем шаблоны
Примечание
Открытие .x-файла
Данный метод возвращает указатель на созданный инстанс класса ID3DXFileEnumObject, представляющий содержимое .x-файла, загруженное в память.
Таблица 1. Возможные значения второго параметра функции CreateEnumObject
| ЗНАЧЕНИЕ | ОПИСАНИЕ |
|---|---|
| DXFILELOAD_FROMFILE | Загружает файл из файла. |
| DXFILELOAD_FROMRESOURCE | Загружает файл из двоичного ресурса. |
| DXFILELOAD_FROMMEMORY | Загружает файл из памяти. |
| DXFILELOAD_FROMSTREAM | Загружает файл из потока. |
| DXFILELOAD_FROMURL | Загружает файл из Интернет-ресурса. |
В случае успешного выполнения CreateEnumObject возвращает значение DX_FILEOK. В случае неудачи возвращает одну из следующих строк:
| ЗНАЧЕНИЕ | ОПИСАНИЕ |
|---|---|
| DXFILEERR_BADALLOC | Ошибка выделения памяти. |
| DXFILEERR_BADFILEFLOATSIZE | Некорректный размер файла (значение с плавающей точкой). |
| DXFILEERR_BADFILETYPE | Файл не является валидным .x-файлом. |
| DXFILEERR_BADFILEVERSION | Некорректная версия .x-файла. |
| DXFILEERR_BADRESOURCE | Некорректный ресурс. |
| DXFILEERR_BADVALUE | Некорректный параметр. |
| DXFILEERR_FILENOTFOUND | Файл не найден. |
| DXFILEERR_RESOURCENOTFOUND | Двоичный ресурс не найден. |
| DXFILEERR_URLNOTFOUND | Интернет-адрес не найден. |
Вот пример:
Энумерация объектов на вершине иерархии (Enumerating Top Objects)
Энумерация дочерних объектов (Enumerating Child Objects)
Здесь объект Data типа ID3DXFileData представляет любой из объектов .x-файла, в независимости от того, какое место в иерархии он занимает.
Обработка дочерних объектов (Processing Child Objects)
- объекты-инстансы,
- ссылки (references).
Полный код энумерирования объектов (Enumeration Overview)
Получаем данные объекта (Getting Object Data)
Во время энумерации объектов происходит получение и обработка данных, хранящихся в них (см. комментарий //Do stuff here в коде выше). Для этого применяются нижеуказанные методы, экспонированные интерфесом ID3DXFileData.
Имя объекта
- Берётся их загруженного объекта .x-файла.
- Является необязательным свойством.
Тип объекта
- Берётся их загруженного объекта .x-файла.
В случае поиска стандартного (= не пользовательского) типа объекта указывают его GUID-имя. Вот полный список GUID-имён одного только шаблона Mesh:
| TID_D3DRMAnimation | TID_D3DRMAnimationKey |
| TID_D3DRMAnimationOptions | TID_D3DRMAnimationSet |
| TID_D3DRMAppData | TID_D3DRMBoolean |
| TID_D3DRMBoolean2d | TID_D3DRMCamera |
| TID_D3DRMColorRGB | TID_D3DRMColorRGBA |
| TID_D3DRMCoords2d | TlD_D3DRMExternalVisual |
| TID_D3DRMFIoatKeys | TID_D3DRMFrame |
| TID_D3DRMFramePosition | TID_D3DRMFrameRotation |
| TID_D3DRMFrameTransformMatrix | TID_D3DRMFrameVelocity |
| TID_D3DRMGuid | TID_D3DRMIndexedColor |
| TID_D3DRMInfo | TID_D3DRMInlineData |
| TID_D3DRMLight | TID_D3DRMLightAttenuation |
| TID_D3DRMLightPenumbra | TID_D3DRMLightRange |
| TID_D3DRMLightUmbra | TID_D3DRMMaterial |
| TID_D3DRMMaterialAmbientColor | TID_D3DRMMaterialArray |
| TID_D3DRMMaterialDiffuseColor | TID_D3DRMMaterialEmissiveColor |
| TlD_D3DRMMaterialPower | TID_D3DRMMaterialSpecularColor |
| TlD_D3DRMMaterialWrap | TID_D3DRMMatrix4x4 |
| TID_D3DRMMesh | TID_D3DRMMeshFace |
| TlD_D3DRMMeshFaceWraps | TID_D3DRMMeshMaterialList |
| TID_D3DRMMeshNormals | TID_D3DRMMeshTextureCoords |
| TID_D3DRMMeshVertexColors | TID_D3DRMProgressiveMesh |
| TID_D3DRMPropertyBag | TID_D3DRMRightHanded |
| TID_D3DRMString Property | TID_D3DRMTextureFilename |
| TID_D3DRMTextureReference | TID_D3DRMTimedFloatKeys |
| TID_D3DRMUrl | TID_D3DRMVector |
Данные объекта (Object Data)
Как только залочили буфер данных, получаем к ним доступ. Сами данные можно считывать строка за стройкой в том порядке, что они указаны в шаблоне объекта в .x-файле.
Сохраняем объект данных в .x-файл
Подготовка
Сохранение данных
Строим дерево иерархии
Подтверждение сохранения данных (Committing Data)
Заключение
Многие из функций, описанных в данной статье, никогда не применяются в игрокодинге. В то же время они могут быть полезны, например, при создании собственного редактора моделей или клона приложения Mesh viewer. Как видим, DirectX располагает массой интересных методов, многие из которых относительно "факультативны". .x-файлы для экспериментов можно найти в каталоге Samples установленного DirectX SDK.