Недостатки формата glTF
В Dagon 1.0 я очень много времени потратил на поддержку этого монструозного формата моделей, и спустя пять лет работы с ним у меня не осталось ничего, кроме разочарования. Главный вывод: glTF — не для игровых ассетов (т.е. карт и отдельных переиспользуемых моделей). Это формат, предназначенный исключительно для просмотра, причем в основном в браузере, на WebGL, при помощи вьюверов типа Sketchfab. В этом он свою задачу выполняет, хотя и тоже неидеально. Но то, что его все начали использовать как формат обмена данными между программами моделирования и игровыми движками — это фундаментальная ошибка, которую я и сам по наивности допустил. Ниже мой личный список недостатков glTF, актуальных, прежде всего, в контексте игрового рендеринга.
- Трансформация нод в glTF хранится либо в виде комбинации позиции, поворота и масштаба (TRS), либо в виде матрицы 4×4. Такая вариативность сильно усложняет игровую логику, так как без декомпозиции матрицы обратно в TRS вы не можете ничего анимировать. Декомпозиция — задача нетривиальная, у нее нет стопроцентно надежных решений, и это делает glTF-сцены в общем случае неподходящими для интерактивных приложений. Намного оптимальнее всегда хранить трансформацию в виде TRS и конвертировать в матрицу в движке, что является простейшей задачей.
- Нефиксированный лейаут вершин — то есть, физическое расположение атрибутов в общем массиве данных может вариьироваться от одного меша к другому, равно как и формат чисел в буферах. Это совместимо с OpenGL/WebGL, но очень плохо для низкоуровневых API, где формат вершин фиксирован на уровне PSO и не может меняться в проходе. Движок вынужден конвертировать буфер в свое внутреннее представление, что убивает саму идею формата — прямую загрузку данных в видеопамять без препроцессинга.
- Поддержка моделей без некоторых атрибутов — например, без текстурных координат или нормалей. В игровом движке не может быть такого, что нормали отсутствуют, для PBR-пайплайна их все равно нужно предоставить шейдеру. Поэтому отсутствующие атрибуты приходится генерировать — а как это делать, glTF не регламентирует, приходится изобретать свою логику. Это, опять-таки, противоречит идее «эффективного GPU-формата».
- Кости для скелетной анимации определены как обычные ноды сцены, хотя они логически принадлежат не сцене, а отдельному виду объектов — собственно скелету. Скелет не вставляется в граф сцены, а используется как источник данных для построения pose-матриц для каждой индивидуально анимированной модели в сцене. То есть, с точки зрения архитектуры движка, glTF-подход к скелетке очень далек от эффективного решения несложной в принципе задачи.
- Поддержка отрицательного масштаба. Для рендеринга это зло, потому что меняет winding и влияет на отбор видимости. Иногда при рендеринге это используется целенаправленно, но в форматах 3D-моделей практически всегда приводит к проблемам совместимости.
- Использование web-first форматов текстур — PNG и JPEG. В AAA-движках эти форматы практически не используются из-за того, что они требуют декодирования на стороне CPU. Кроме того, для игр с большими мирами жизненно важно блочное сжатие текстур, это индустриальный стандарт. А с glTF получается, что нужен отдельный этап декодирования и сжатия изображений в GPU-френдли форматы. Это может и не быть большой проблемой, если движок поддерживает такой процесс, но все равно возникает много сложностей. Основной вопрос — в какой формат сжимать? Если это base color, то, конечно, достаточно BC1/BC3 (в зависимости от наличия альфа-канала), но для карт нормалей уже желателен BC7, а для черно-белых изображений эффективнее BC4. glTF никак не регламентирует семантику текстур, она определяется на уровне материалов, что в общем случае не позволяет реализовать полностью автоматический алгоритм выбора формата.
В общем, для Dagon 2.0 я обязательно буду пилить собственный формат ассетов, учитывая весь этот опыт. Поддержка glTF сохранится благодаря библиотеке Assimp, но формат больше не будет позиционироваться как основной.