Обновления

Dagon 1.3.0

Выпустил новую версию движка, до этого также вышло несколько багфиков для 1.2. Метод Application.takeScreenshot теперь совершает полную отрисовку кадра, чтобы все изменения в рендере отразились на скриншоте. Добавлены методы Material.copyTo и Material.copyFrom для копирования свойств одного материала в другой. Добавлено свойство Entity.blurOnlyRotation, предназначенное для ограничения motion-векторов угловой скоростью, игнорируя изменение позиции — это важно для объектов, которые вращаются, но не меняют своего положения в экранном пространстве, таких, как колеса машины.

Улучшен префильтр яркости для эффекта glow: добавлен верхний порог яркости, а нижний теперь не срезает пиксели жестко, используя технику soft-knee для плавной градации. Разница заметна, например, при свечении неба, которое имеет неравномерную яркость. Соответственно, добавлены новые опции render.conf: glow.softKnee, glow.maxEnergy.

Исправлены баги рендеринга декалей. Я использую диффузные декали для размытых теней под днищем машин, но это приводило к тому, что тень некрасиво наползала на вертикальную стенку, когда машина приближалась к ней вплотную. Это вызвано вырожденным случаем при расчете текстурных координат проекции. Проблема исправлена тем, что прозрачность декали зависит от ее копланарности поверхности, на которую она проецируется. Если поверхность перпендикулярна, то прозрачность равна нулю.

dlib 1.5.0

Вышло небольшое обновление dlib. Добавлена функция trsMatrix для создания матрицы трансформации из смещения/поворота/масштаба за один шаг, без перемножений. Появилась функция lerpAngle для линейной интерполяции углов по кратчайшей дуге, а также shortestAngleDelta для вычисления угловой дельты по кратчайшей дуге. Добавлена новая изинг-функция easeOutElastic. Функция wrapAngle теперь работает с любыми угловыми единицами при помощи опционального параметра периода (по умолчанию период равен 360, что означает градусы).

Chillwave Drive

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

Dagon 1.2.0

Выпустил новую версию Dagon. Основным поводом к этому релизу стало исправление серьезного бага, связанного с горячим подключением геймпадов (в качестве индексов для GameInputDevice использовались неправильные ID). Кроме того, игра заметно подтормаживала при вызове InputManager.getAxis из-за использования SDL_GameControllerGetAxis — эта проблема была решена использованием значений осей из событий SDL_CONTROLLERAXISMOTION. Соответственно, GameInputDevice теперь имеет поле axisValues.

Исправлена утечка памяти в методе Application.takeScreenshot.

Добавлена поддержка суперсэмплинга. Он управляется при помощи опции ss.sampleRatio в render.conf. Если выставить в нее 2, то игра будет рендериться в двухкратном разрешении.

Шейдер FXAA заменен на более качественную версию 3.11. Добавлена поддержка независимого от разрешения радиуса размытия для фильтра свечения (glow). Если в render.conf присутствует опция glow.normalizedRadius, то обычный glow.radius не используется — радиус вычисляется динамически как доля высоты экрана. Например, если выставить значение 0.005, то при 1080p радиус свечения будет 6 пикселей.

Обновления по итогам июня

Вышел Dagon 1.1.1. Добавлено свойство GLTFMeshPrimitive.materialIndex, индекс материала в файле glTF. Этот индекс записывается в структуру Triangle при обходе по граней с помощью GLTFAsset.opApply, что позволяет передавать данные о материалах в различные обработчики мешей. Основное применение этой фичи — учет материалов при обнаружении столкновений и рейкастинге. Соответственно, в dagon:newton добавлена поддержка индексов материалов при создании NewtonMeshShape. В интерфейсный метод NewtonRaycaster.onRayHit добавлен параметр long id, через который коллбек получает искомый индекс материала при попадании луча в меш.

Пошла серьезная работа над Chillwave Drive. Я уже рассказывал про ИИ, но, помимо этого, на днях улучшил модель колеса и наконец-то добавил поддержку материалов, чтобы машина учитывала свойства покрытия. Например, когда машина вылетает с трассы, то трава на обочине сильно ее замедляет и не дает разгоняться. Также добавил HUD-тахометр, обновил текстуры тестовой трассы, реализовал эффекты погоды — дождь и динамически появляющееся и исчезающее за облаками солнце. У игры появилась страничка на GameJolt, где я регулярно выкладываю альфа-версии.

Привел в порядок блог, объединил старую версию и новую, подкорректировал неактуальную информацию.

Соперники в Chillwave Drive

После релиза Dagon 1.1 я решил вернуться к демке автосимулятора Chillwave Drive — собираюсь превратить ее в полноценную гоночную игру. Этому препятствовало отсутствие ИИ для соперников, и на днях наконец-то эта задача была решена — я написал алгоритм следования траектории на базе Pure Pursiut, популярного метода из робототехники (но почему-то практически не известного в среде инди-разработчиков). В ближайшее время будет подробная статья на эту тему, а пока вы можете скачать свежий билд Chillwave Drive и увидеть ИИ в действии, либо просто посмотреть видео:

Обновления

Выпустил первое крупное обновление Dagon после стабильного релиза — 1.1.0. В этой версии исправлены некоторые баги, добавлен модуль проверки столкновений в 2D и в целом несколько упрощена работа с 2D-играми. Во всех встроенных шейдерах сабрутины GLSL заменены на ветвления для лучшей совместимости со старыми видеокартами. Стартовала работа над редактором сцен для движка.

К этому релизу я приурочил выпуск переиздания моей старой игры 2048×2 — версии популярной головоломки 2048 для двух игроков. Игра портирована на Dagon, что обеспечило поддержку x86_64 и улучшило качество рендеринга шрифтов. Также я добавил переводы игры на немецкий, французский, испанский, португальский и другие языки. Графический стиль и механика не изменились.

Сборку для Windows можно скачать тут, исходники и linux-версия будут на днях.

Формат DAF

В предыдущем посте я уже писал о том, почему glTF нельзя использовать в качестве полноценного формата игровых ассетов, и вот альтернатива, которую я специально проектирую для Dagon 2.0 — Dagon Asset Format, сокращенно DAF.

DAF хранит, в первую очередь, вершинные буферы для прямой передачи в VRAM. Формат бинарный, архитектурно соответствующий API Dagon 2.0 и расширяемый: в нем могут быть объявлены любые дополнительные структуры данных и даже динамические свойства, не ломая обратную совместимость.

Основные задачи формата:

  • Хранение данных в форме, подходящей для прямой загрузки в видеопамять без накладных расходов на обработку (zero overhead). В отличие от glTF, в DAF вершинные буферы имеют фиксированный формат, согласованный с пайплайном движка, не требуя конверсии;
  • Максимальная эффективность десериализации. glTF требует парсинга JSON и динамического построения довольно сложных объектов в памяти (списков, словарей), а загрузка DAF – это просто реинтерпретация слайсов байтового буфера в массивы POD-структур. DAF экономит память и сокращает риск утечек, поскольку не требует множества аллокаций;
  • Частичная десериализация. Декодер может читать из DAF только те данные, которые ему нужны, не разбирая остальные.
  • Формат «все в одном». Файл DAF может хранить как отдельный меш, так и целую сцену. Все структуры формата поддерживают пользовательские свойства, что позволяет хранить в DAF метаданные редактора. Фактически, DAF может быть использован как простая NoSQL база данных для различных целей.
  • Поддержка семантики данных. Все объекты имеют список классов, что позволяет движку группировать их для задач игровой логики. Все текстуры помечаются как baseColor, normal, height, roughness-metallic, emission, для того, чтобы движок мог выбрать оптимальный BCn формат сжатия.
  • Поддержка данных для физики и проверки столкновений (в разработке).

Спецификация DAF находится здесь.

Недостатки формата 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, но формат больше не будет позиционироваться как основной.

Прогресс по Dagon 2

За последние недели значительно продвинулся с портом: реализовал в Dagon 2 загрузку glTF (через библиотеку Assimp), обновил шейдер антиалиасинга, добавил цветокоррекцию и поддержку 3D-текстур, проделал множество мелких улучшений в рендере.

Модель — Datsun 280Z от Martin Trafas (бесплатная).

Новые скриншоты Dagon 2

Еще несколько тестов SSLR с различными поверхностями пола и картами окружения:

Также я завел отдельную страничку, посвященную Dagon 2.

SSLR и темпоральная аккумуляция

Как и планировал, сумел победить зашумленность отражений при помощи накопления данных с предыдущих кадров (с репроекцией по motion-векторам для компенсации медленного движения камеры):

Это решение также неплохо сглаживает шум, вызванный неоднородностью нормалей:

При резких движениях, конечно, репроекция не спасает, приходится сбрасывать историю, и шум возвращается. И, конечно, экранная трассировка — это аппроксимация, у нее всегда будут видимые артефакты в виде пустот там, где сэмплинг невозможен ввиду недостатка информации в буферах. Но результат вполне, как говорится, shippable — мне кажется, у меня получилось даже лучше, чем во многих коммерческих играх. Во многом, это благодаря использованию GGX importance sampling: распределение лучей очень близко к тому, что делается для префильтрации кубических карт под PBR.