OpenGoW

OpenGoW: Первые успехи

Автор:

Читатель, прости что информация подается разрозненно, я не любитель писать.
Пишу я сюда больше в ожидании помощи. Если кто что знает полезного по теме, прошу, напишите в комментариях. Буду рад если укажите на ошибки, особенно в запятых и терминологии (некоторые названия я натягивал и придумывал).

Интро


Не знаю как вы,  но я тащусь от God of War. И как-то решил сделать порт на pc (7 месяцев назад). Начал с первой части, да и играл я вообще только в первые две на ps2 (остальные консоли даже в руках не держал).
Кстати у всех игр серии различия минимальные, так что информация здесь относится не только к первой части, что не может не радовать.

План


- Проверить подойдет ли golang для рендеринга и работой с сырыми данными. В общем этот этап прошел, golang оправдал себя (жалко что не в gui) https://github.com/mogaika/god_of_war_tools
- Разобрать форматы на костыле (golang + webgl = https://github.com/mogaika/god_of_war_browser). Собственно сейчас я этим и занимаюсь.
- Написать двиг, в идеале чтоб под планшетики работало, а еще лучше если его не обязательно форкать надо было, если я захочу со второй частью тоже самое сделать. Надеюсь что доживу до этого момента.

Текущий статус


Внимание: ко всем пунктам добавлять: "работает почти всегда, половина флагов не понятно что делают".

Файлы


Игра занимает 2 слоя dvd диска, что примерно равно ~9 гигам, хотя на самом деле это пространство специально "растянуто". Лишний вес набирается как от копирование игровых архивов по несколько раз, что скорее всего (но не подтвреждено) идет на ускорение работы с диском, так и от копирование одинаковой информации между wad архивами игры. Например текстуры неба/дерева/блеска/дыма будут далеко не раз повторяться из архива в архив, но это уже определенно способствует ускорению  загрузки частей игры. Напомню что в игре карта абсолютно бесшоновая, и в большинстве случаев вы можете вернуться на очень приличное расстояние назад.

Архивы и ноды


Архивы собственно являются не сколько архивами, а инструкцией как жестко распаковать ноды движка в память. Движок всю информацию хранит в нодах разного типа: текстуры, камеры, коллизия, настройки игры, эффекты, модели, системные переменные, в общем почти все. Очень прикольно что до нод можно достучаться по строчному пути, типа /GlobGame/Settings/MusicVolume, или даже так /OLYMP2A.WAD/CXT_mainRoom/gotree25/joints/jRoot. Каждое имя поддирекории он превратит в хеши строк, пройдется от корня, вызывая метод интерфеса мастер-сервера ноды, который вернет определенную субноду. На деле все это используется не так часто, критичные к скорости места обращаются напрямую к указателям и тому-подобному, но все-равно на ps2 я не ожидал такой гибкости (а стоило).
Ресурсы в архиве при объявлении могут ссылаться или содержать в себе другие ресурсы, нужные текущей ноде. Например, текстура требует объекты палитры и индексы для неё, затем материал требует кубмап и дифус текстуры, модель требует меши и материалы, объект ожидает модели, партиклы и анимации, при этом инстанс объекта ссылается на объект, а чанк карты ссылается на все эти инстансы. Очень печально что игра хило переиспользует ноды для оптимизации, иногда бывает что есть по 5 одинаковых текстур в одном архиве, если не вспоминать что некоторые текстуры не используют и половины своей палитры (можно чтобы несколько текстур использовали одну палитру), хорошо что, например, разные модели ссылаются на одинаковые текстуры (хотя модели сами могут неоправданно дублироваться из-за запекания освещения). У каждого архива - свой неймспейс, за который его ноды не могут выглядывать. Правда из-за этого непонятна фича с использованием ресурсов из других архивов, например, ноды анимации могут расширять анимации игрока, который грузится из другого архива один раз на всю игру. Хотя, возможно, это уже внутренняя реализация сервера анимаций, тогда не нарушается правило неймспейса. Но, если учесть что половину флагов я так и не понял, возможно у нод есть флаг глобальной области видимости, или типа того.

Текстуры


Текстуры устроены достаточно просто. Есть массив палитры, есть массив индексов. Только я так и не понял почему иногда текстуры ипортируютя криво, например эта:
texture_importing_problems | OpenGoW: Первые успехи
видно что в центре фиолетововые "брызги". Может кто знает с чем это связанно (меня напрягает что у некоторых текстур в конце названия виднеется bmp)?

Материалы


С ними я занимался для галочки. Очевидно материалы держат цвет и текстуры, но как определить какая текстура за что отвечает не понятно (бывают кубмап/диффуз/рефлекшн[не помню как точно называется, отвечает за поглащение цвета]), как и почему в некоторых текстура прозрачность - это черный цвет, а в других - полная прозрачность - тоже.

Меши


Непонятные слова ниже искать на этой картинке
Изображение
С мешами интереснее всего. Они хранятся в виде набора комманд (батчей) для шины vif, которая грузит данные меша в память "геометрического" процессора vu1.
Т.е. мы знаем в какую ячейку памяти vu1 мы загрузим данные. А отличать тип данных можно как-раз по этим адресам памяти. Т.е. в файле у нас что-то типа (если перевести в человекочитаемый язык):
// установить что мы будем распаковывать данные в память vu1 с шагом (аналог аргумента stride в функциях opengl, только работает наоборот).
// например, у нас XYZW|XYZW|RGB|RGB|UV|UV, а в памяти vu1 мы хотим построить XYZW|RGB|UV|XYZW|RGB|UV.
Stcycl wl=01 cl=03
// то что это текстурные координаты я определил по target адресу, и да, не бойтесь что адреса такие маленькие, ведь vu1 работает с векторами 4х4float.
vif unpack [ uv2]: 65 elements: 51 components: 2 type: 16 target: 001 sign: true addr: true size: 000144
Stmod  mode=[pos] (1) // туфта для управления шиной
Strow  proc command
// xyzw, где w - это флаги для программы, исполняющейся на vu1, например, использовать ли вертекс для построение треугольника с предыдущими 2мя, или только запомнить его, и не строить треугольника
vif unpack [xyzw]: 6d elements: 51 components: 4 type: 16 target: 003 sign: true addr: true size: 000288
Stmod  mode= pos  (0)
// rgba per vertex
vif unpack [rgba]: 6e elements: 51 components: 4 type: 08 target: 002 sign: false addr: true size: 000144
// выключаем strip, теперь данные нам нужны подряд, друг за другом
Stcycl wl=01 cl=01
// загружаем разные флаги, например тут могут быть перечислены веса для jointов
vif unpack [vmta]: 6c elements: 01 components: 4 type: 32 target: 000 sign: true addr: true size: 000010
vif unpack [meta]: 6c elements: 01 components: 4 type: 32 target: 0f4 sign: true addr: true size: 000010
nop
// говорим vu1 процессору, что он может начать обрабатывать наши данные
Mscall proc command

Для парсинга таких структур приходится эмулировать обработку пакетов в памяти. По хорошему надо отреверсить код, который исполняется на vu1, но до этого руки не дошли (точнее дошли, но сломались об непонятную магию и 5 одновременно исполняющихся инструкций с иногда работающей синхронизацией), тогда прояснятся магические флаги, которые передаются в пакетах (хотя сейчас ясно что там передается наличие тех или иных данных о вертексах (позиция/цвет/текстура/нормаль) и их джойнты. Таких батчей может быть несколько, и они не могут быть больше, чем есть места у vu1. А в нем, кстати, одновременно должно поместиться 3 таких батча, что позволяет оптимизировать работу в 3 потока (1 - "ee core" считает анимацию и всякую лабуду, 2 - gif шина передает данные в памать vu1, 3 - vu1 обрабатывает данные в памяти и отправляет их в gs через path1).
В нашем батче можно заметить что у xyzw пакета стоит размер 16 и флаг sign - что в сумме дает нам int16, эта практика распространяется на всю игру. Я не могу поверить что хватает 2х байтов на координату для такой более-менее графики.
Насчет per vertex rgba можно сказать что он используется везде. На всех статичных объектах запечены тени, из этого кстати следует что одинаковые объекты на сцене, типа камней/дверей/стен будут сохранены в памяти и переданы по шине очень много раз, при этом отличаться они будут только цветом вертексов.

+ пример модели без текстуры

Батчи пакетов группируются по материалу в массив, потом еще по корневому joint в массив - на выходе получаем меш.

Модель


Модель содержит в себе:
- меши (может быть несколько)
- текстуры для мешей
- параметры эффектов (туман/скайбокс/веревка/ ...)

Объект


Дела с объектами не так плохо. Внутри себя содержат дерево костей с матрицами и другими няшками, при этом скелет предназначен не только для модели, но также и для дочерних объектов, типа еммитера, или камеры.
Определить где bind-pose матрицы для джойнтов не удается. Вроде как inverse bind-pose есть, но изредка они задаются не для всех джойнтов, и немного кривые. При этом для каждого джойнта обязательно есть local idle-pos матрица, дублированная позицией, вращением и размером. В общем in-progress.

Анимации


Анимации могут быть дочерними почти к любому типу, при этом, видимо, могут менять их почти любое свойство. Формат еще не ковырял.

Итог


Работы Развлечения еще много. Нужно реверсить/разбираться/фиксить. Камеры/коллайдеры/скрипты совсем еще не тронуты. Рал что проделал уже неплохую работу, все-таки это веселье на выходные, да и то с большими перерывами.
Не стесняемся задавать вопросы, особенно рода "как помочь и где форкать".
Хотя на самом деле к форкам репозиторий не готов, в отличии от звездочек (так вы выразите заинтерисованность и заставите меня продолжить работу)
https://github.com/mogaika/god_of_war_browser
Вот вам призрак спарты из главного меню, у которого натянули hipoly модель лица прямо поверх его lopoly-ingame прообраза (шов на голове)
+ Показать

Ах да, названия не было, и я позаимствовал идею у openLara.

#god, #god of war, #golang, #opensoure, #PS2, #reverse, #WebGL, #движки, #теория

24 октября 2016

Комментарии [11]