Вместо конвертации ассетов - переписываю игровой runtime с нуля
Перенос игровых ресурсов между разными проектами обычно строится по знакомой схеме: файл извлекают из исходной игры, преобразуют в промежуточный формат, адаптируют под требования нового движка, а затем заново собирают сцену. Такой подход кажется самым практичным, поскольку для многих популярных игр уже существуют готовые инструменты. Например, OpenAssetTools и ZoneTool автоматизируют работу с данными Call of Duty, а UE Viewer умеет экспортировать ресурсы Unreal Engine в GLTF, PSK, DDS и другие форматы.
Однако у конвертации есть фундаментальный недостаток: она часто теряет часть исходной логики. Недостаточно перенести вершины, текстуры и скелет. Важны материалы, параметры освещения, поведение эффектов, структура шейдеров и набор констант, которые исходный движок передаёт видеокарте. Поэтому вместо бесконечной адаптации ассетов можно выбрать другой путь - реализовать runtime, способный понимать ресурсы нескольких игр напрямую.
Именно такую идею я проверяю в экспериментальном проекте IW4L. Это самостоятельная разработка игрового движка, в основе которой лежит IW4 - технология Call of Duty: Modern Warfare 2 2009 года. Концепция проекта описана подробнее в материале о том, как выполняется создание игрового движка с нуля и почему иногда проще переписать runtime, чем строить конвейер конвертации.
Традиционная цепочка выглядит так:
> ассет игры A → конвертер → формат игры B → runtime игры B.
В IW4L промежуточный этап стараются убрать:
> ассеты игр A, B и C → IW4L.
В таком варианте runtime сам определяет происхождение ресурса, разбирает его внутреннюю структуру и выбирает подходящий способ исполнения. Это особенно интересно для Modern Warfare 2, Black Ops и Modern Warfare 3. Все три проекта используют близкие поколения одного семейства технологий, но их fastfile-архивы различаются разрядностью и внутренним устройством.
Чтение самих файлов оказалось сравнительно простой задачей. Настоящая сложность начинается после загрузки данных. Представим сцену, где карта взята из MW2, оружие - из Black Ops, а дополнительная модель закреплена на персонаже из MW3. Геометрию можно привести к единому представлению: вершины, индексы, UV-координаты и текстуры объединяются без катастрофических проблем. Но затем возникает вопрос: как корректно отрисовать всё это на экране?
За отображение геометрии, материалов, освещения и эффектов отвечают небольшие программы, выполняемые на GPU, - шейдеры. В оригинальных играх они представлены байт-кодом Shader Model 3 для Direct3D 9. IW4L написан на Rust с использованием Bevy, а графический слой построен поверх wgpu. Следовательно, требуется преобразование SM3 в WGSL.
Похожая задача уже решалась в DXVK. Этот проект позволяет запускать Direct3D поверх Vulkan и включает полноценный путь трансляции D3D9 в SPIR-V. Благодаря этому уже существуют решения для декодирования инструкций Shader Model 3 и обработки множества сложных частных случаев.
Но одной общей таблицы соответствий вроде `shader_name → translated_shader` здесь недостаточно. Несмотря на совместимость с SM3, шейдеры MW2, Black Ops и MW3 могут заметно отличаться по содержимому. Более того, программы с одинаковыми именами иногда используют совершенно разные инструкции и ожидают различные данные.
Поэтому происхождение ресурса приходится учитывать на всём пути от загрузки до финальной отрисовки:
> `(game, shader) → translated_shader`.
Если движок получает модель из Black Ops, ему необходимо помнить, что материал относится к поколению T5, выбрать соответствующую технику рендеринга, загрузить нужные вершинный и пиксельный шейдеры, подготовить правильные константы и только после этого передать объект в общий графический конвейер.
Шейдеры ожидают матрицы трансформации, положение камеры, параметры света, тумана, материала и множество других значений. Исходный движок формировал их по собственным правилам, причём между MW2, Black Ops и MW3 различаются не только наборы параметров, но и смысл отдельных полей.
Именно поэтому визуальные ошибки могут возникать даже при технически успешной загрузке. Например, на одном из тестов карта Afghan из MW2 отображалась вместе с оружием FAMAS из Black Ops. Геометрия, текстуры и шейдер оружия были подключены корректно, однако модель выглядела чрезмерно яркой. Причина заключалась не в повреждённых ресурсах: карта рассчитывала освещение по правилам IW4, тогда как материал оружия ожидал параметры, характерные для T5.
В результате внутри одного кадра могут одновременно присутствовать карта IW5, оружие IW4 или T5, освещение одного поколения и графический pipeline другого. Поддержка чужих файлов в таком случае превращается не в простой импорт, а в задачу совместимости нескольких версий движка.
Это принципиально отличается от обычной конвертации 3D моделей для игр. Конвертер старается привести ресурс к единому формату заранее, а универсальный runtime выполняет согласование непосредственно во время работы. Такой подход сложнее в разработке, зато позволяет сохранять больше особенностей оригинальных материалов и поведения объектов.
Отдельная проблема - производительность. Если каждый ресурс приходится распознавать, преобразовывать и связывать с нужной техникой рендеринга во время запуска, возрастает нагрузка на CPU и увеличивается время подготовки кадра. Поэтому перспективным направлением может стать кэширование результатов трансляции шейдеров, предварительная компиляция часто используемых материалов и разделение данных на неизменяемые и динамические.
Не менее важна диагностика. При смешивании ассетов разных игр полезно фиксировать не только название ресурса, но и его поколение движка, версию формата, набор зависимостей и ожидаемые параметры. Подобная метаинформация помогает понять, почему объект выглядит неправильно, даже если все файлы успешно прочитаны.
Пока невозможно сказать, удастся ли закрыть все пограничные случаи взаимодействия карт, оружия, эффектов и освещения. Тем не менее проект уже показывает, что альтернативный подход к интеграции ресурсов реален. Вместо того чтобы каждый раз выполнять перенос игровых ассетов между движками, можно научить один runtime работать сразу с несколькими экосистемами.
На раннем этапе IW4L уже демонстрировал сцены, в которых ресурсы разных игр объединялись в одном пространстве. Следующий месяц разработки должен показать, насколько устойчивой окажется такая архитектура и получится ли приблизить экспериментальный runtime к полноценной платформе для совместимого запуска ассетов.
Проект создаётся с активным использованием языковых моделей: на текущий момент кодовая база насчитывает около 370 тысяч строк и создавалась при участии десятков различных LLM. При этом сам материал о ходе разработки подготовлен человеком.

