glTF: скелетная анимация

В следующей версии Dagon наконец-то дебютирует поддержка анимации в сценах glTF. Поддерживается GPU-скиннинг, причем универсальный — для любого объекта можно передать в рендер-пайплайн набор матриц костей (за это отвечает объект Pose) и необходимые вершинные атрибуты (индексы матриц костей и веса).

Поскольку система низкоуровневая, скиннинг требует подготовительной работы — в частности, под каждый уникальный анимированный меш нужен объект позы (Pose), который анимирует кости и заполняет массив матриц. Позу нужно применить к сущности (Entity), и тогда в вершинном шейдере меш этой сущности будет трансформирован матрицами, хранящимися в позе. Преимущество в том, что можно расширять систему скиннинга практически без ограничений. Если вы понимаете, как устроен формат glTF, вы можете написать кастомную альтернативу стандартному классу GLTFPose с любыми фичами: процедурная анимация, обратная кинематика — все, что угодно.

auto characterNode = characterModel.node("character");
characterPose = New!GLTFPose(characterNode.skin, assetManager);
characterPose.animation = characterModel.animation("walk");

Entity character = characterNode.entity;
character.pose = characterPose;
characterPose.play();

Можно прикреплять объекты к костям при помощи родительской связи:

Entity weapon = addEntity(characterModel.node("arm_right").entity);
weapon.drawable = someWeaponMesh;

Const-корректность в D

Русский перевод моей статьи «Const-correctness in D». Оригинал опубликован на Medium.

Ключевое слово const знакомо каждому программисту — оно присутствует во многих современных C-подобных языках и используется в самых разных контекстах. В общем смысле оно означает, что переменная неизменяема и может быть инициализирована только один раз. Чаще всего такой упрощенный смысл const встречается в динамических языках, однако в мире статической типизации, в том числе и в D, существует несколько видов неизменяемости, и понимание этих различий крайне важно для написания надежного ПО.

(далее…)

Dagon 0.21.0

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

  • Переход на SDL 2.32. Под Linux движок теперь предоставляет готовую библиотеку libSDL2-2.0.so, которая копируется в папку с приложением — чтобы ее использовать вместо системной, нужно собирать с флагом линкера "lflags-linux": ["-rpath=$$ORIGIN"] (либо перед запуском задавать рабочую папку в LD_LIBRARY_PATH, что, на мой взгляд, намного менее удобно)
  • Текстуры теперь загружаются через библиотеку SDL2_Image, если она присутствует. Благодаря этому появилась поддержка прогрессивных JPEG, WebP, AVIF, SVG и множества других форматов. Необходимые библиотеки также копируются в проект при сборке (для 64-битных Windows и Linux). Также можно реализовать и подключить к AssetManager кастомный загрузчик изображений
  • Поддержка текстур формата KTX. Полноценная поддержка KTX1 и KTX2. Текстуры, сжатые в Basis Universal, транскодируются в S3TC/RGTC/BPTC или распаковываются в RGBA8, в зависимости от заданной пользователем настройки. Поскольку для этого требуется дополнительная библиотека, за загрузку KTX отвечает расширение dagon:ktx
  • Расширение dagon:physfs, которое позволяет примонтировать в AssetManager виртуальную файловую систему PhysFS и загружать ассеты из архивов
  • Упрощенные тени в SimpleRenderer: дефолтный шейдер затемняет пиксели в зависимости от удаленности от указанной точки в плоскости XZ, создавая кружочек тени под объектом. Радиус затемнения можно контролировать, чтобы сделать либо мягкий кружок, либо резкий. Центр тени можно привязать, например, к позиции персонажа
  • 4-байтное выравнивание в текстурах, которые загружаются из файла вместе с mip-уровнями
  • Функция isExtensionSupported для проверки поддержки расширений OpenGL. Также maxTextureUnits и maxTextureSize — для опрашивания максимального количества текстурных блоков и максимального размера текстуры.

Полный список изменений читайте на странице релиза.

Обновления

bindbc-wgpu 0.24.0

Обновил биндинг к WebGPU, обеспечив поддержку wgpu-native 24.0. Очень рад внедрению WGPUStringView вместо null-terminated строк — теперь строки в API передаются в виде пары указатель + длина. Это дает прямую совместимость со строками D — не нужно вызывать toStringz, что всегда меня дико раздражало. В моем биндинге WGPUStringView в большинстве случаев неявно создается из string благодаря конвертирующему конструктору this(string).

https://github.com/DLangGamedev/bindbc-wgpu

bindbc-physfs

Написал биндинг к PhysFS, популярной библиотеке для абстрактного доступа к файловой системе и содержимому архивов.

https://github.com/DLangGamedev/bindbc-physfs

SDL_Image и загрузчики текстур

Вслед за KTX я решил улучшить в движке ситуацию с поддержкой стандартных форматов изображений. Отныне Dagon загружает текстуры с помощью SDL_Image, если библиотека присутствует, системно или локально. В противном случае используется старый загрузчик на основе dlib.image. Преимуществом такого подхода является гарантированная под Windows поддержка огромного числа форматов, включая современные WebP и AVIF; также автоматически решается застарелая проблема с декодированием прогрессивных JPEG.

А еще одно важное нововведение — механизм расширения этой системы. Теперь, если нужно добавить новый формат текстур, вместо кастомного ассета достаточно написать и зарегистрировать в AssetManager‘е кастомный загрузчик текстур — реализацию абстрактного класса TextureLoader. Он работает почти аналогично ассету, но предназначен для прямого декодирования данных в TextureBuffer.

(далее…)

Chillwave Drive

Значительно обновлена демка физики автомобиля на Dagon, которая отныне называется Chillwave Drive. Трение колес теперь моделируется на чистых силах вместо встроенных джоинтов Newton, что сделало симуляцию более точной и стабильной. При движении используется динамическое трение (продольное и поперечное) на основе формул Pacejka ’94, при остановке — статическое, препятствующее боковому скольжению машины на склоне. Улучшено управление с контроллера, также добавлена новая модель машины и шейдер неба с облаками и сменой дня и ночи.

Скачать готовую сборку демки под Windows можно тут.

Dagon 0.20.0

Вышло небольшое, но важное обновление Dagon. Добавлена поддержка кастомных шейдеров для текстурирующих слоев материала ландшафта — это делает систему невероятно гибкой, так как вы можете рисовать на ландшафте все, что захотите. В качестве примера в движок добавлен шейдер луж (dagon.extra.puddle) с эффектом кругов от дождевых капель. Также появилась поддержка Matcap для фейковых отражений (особенно полезная фича в в SimpleRenderer, где нет честных отражений), поддержка glTF-расширения KHR_materials_emissive_strength, доступ к материалам glTF по именам. Обновлена библиотека Newton под Linux.

В репозитории проекта открыты обсуждения: https://github.com/gecko0307/dagon/discussions

KTX в Dagon

Наконец-то реализовал давнюю идею поддержки текстур в формате KTX (Khronos Texture). Это контейнер, специально созданный для OpenGL и Vulkan и поддерживающий большое количество форматов текстур, включая сжатые. Особенно интересен KTX2, который позволяет хранить текстуры в формате Basis Universal — он хорош тем, что позволяет при создании игровых ресурсов не волноваться, что сжатие не будет поддерживаться на системах каких-то пользователей. Формат сжатия выбирается движком игры на основе информации от видеодрайвера, а затем текстура на лету транскодируется в этот формат. Эта фича нужна главным образом на мобильных платформах, но и в десктопном движке не помешает — Basis Universal сжимает очень эффективно и транскодируется в S3TC или BPTC за считанные мгновения.

Текстура KTX1/KTX2 загружается в объект KTXAsset, при его создании необходимо указать приоритет транскодирования. Если это TranscodePriority.Size, то загрузчик отдает предпочтение S3TC, если TranscodePriority.Quality — BPTC (при наличии поддержки). Также можно использовать TranscodePriority.Uncompressed, чтобы получить наилучшее качество — текстура будет распакована в RGBA8.

Пример:

TextureAsset aTextureBox;
TextureAsset aTextureEnvmap;

// В конструкторе сцены:

registerKTXLoader(assetManager);

// На стадии запроса ассетов:

aTextureBox = addTextureAsset("data/box.ktx2");
aTextureBox.conversion.hint = TranscodeHint.Size;

aTextureEnvmap = addTextureAsset("data/cubemap.ktx2");
aTextureEnvmap.conversion.hint = TranscodeHint.Quality;

// При создании материалов:

material.baseColorTexture = aTextureBox.texture;
environment.ambientMap = aTextureEnvmap.texture;

Обновления

dlib 1.3.1

Вышло небольшое обновление dlib 1.3, в котором исправлена совместимость с отличными от x86 платформами.

BindBC-SPIRVCross

Написал D-биндинг к SPIRV-Cross, тулкиту для работы со SPIR-V. Теперь потенциально можно создать продвинутый фреймвок компиляции шейдеров — например, систему автоматической адаптации шейдеров к разным графическим API в одном приложении. Пригодится, когда я буду переходить на WebGPU.

dcore

Библиотека dcore, на основе которой я собираюсь писать вторую версию dlib, будет развиваться в качестве самостоятельного проекта: https://github.com/DLangGamedev/dcore. В связи с этим я решил создать организацию DLangGamedev, в которую перевел все BindBC-биндинги библиотек для разработки игр:

LDC поддерживает SPIR-V!

Не секрет, что компилятор LDC, благодаря бэкенду на LLVM, поддерживает множество разных целевых платформ — и в их числе, оказывается, есть SPIR-V. Мне удалось собрать вот такое минимальное вычислительное ядро (которое, правда, ничего не делает):

module main;

import ldc.attributes;

@callingConvention("spir_kernel")
@llvmAttr("hlsl.shader", "compute")
extern(C) void compute_main() nothrow @nogc
{
    //
}
ldc2 -betterC -vgc -c --output-o -of=main.spv --mtriple=spirv-unknown-vulkan -mattr=+spirv1.0 source/main.d

Если вывести ассемблерный листинг, получается следующее:

OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %3 "compute_main"
OpSource Unknown 0
OpName %3 "compute_main"
%1 = OpTypeVoid
%2 = OpTypeFunction %1
%3 = OpFunction %1 None %2
%4 = OpLabel
OpReturn
OpFunctionEnd

Как видно, можно использовать этот SPIR-V модуль как вычислительный шейдер в Vulkan и OpenGL 4.6. Поддержки других видов шейдеров пока нет, но кто знает — может быть в один прекрасный день получится использовать D в качестве полноценного шейдерного языка! Я лично в это верю.