Как создать динамическую симуляцию щупальцев и верёвок по мотивам игры Returnal
Адияр Айдарбеков рассказал о своём симуляторе щупальцев GPU и верёвок, вдохновлённом игрой Returnal. Он показал процесс работы с Niagara в Unreal Engine и объяснил настройку столкновений.

Динамические щупальца и верёвки
Вдохновлённый презентацией Housemarque на GDC о Returnal, где использовалась специальная система на базе GPU для снарядов, щупальцев и листвы, я захотел проверить, можно ли создать нечто подобное полностью в рамках движка Unreal Engine с помощью системы Niagara, не прибегая к пользовательской архитектуре C++.

Система Niagara и её Scratch Pad больше не просто инструменты для взрывов и магии — это полностью настраиваемая среда вычислений HLSL с множеством пресетов и примеров от разработчиков Epic Games, которые вы можете легко открыть и посмотреть, что происходит внутри каждого модуля, от ColorScaler до логики столкновений GPU и RayTracing.
Начальная настройка
Когда я начинал, я понятия не имел, что такое HLSL и с чего начать. Поскольку не было много руководств или даже документации о Niagara Scratch Pads, мне пришлось следовать продвинутым примерам Unreal Engine и реверсировать их, чтобы научиться.
В Niagara есть Attribute Reader, который позволяет излучателям и частицам извлекать данные из других частиц и присваивать себе переменные на основе выходных данных.

Итак, чтобы сделать симуляцию ограничений верёвки, мне нужно было реализовать логику ограничений дочернего элемента к родительскому. Это создаёт иерархию в массиве частиц в порядке цепочки.
Управляя нативными атрибутами Niagara, такими как UniqueID, RibbonLinkOrder и RibbonID, вы можете индексировать и оценивать частицы одну за другой, позволяя Attribute Reader сопоставлять данные по всей цепочке.
Наконец, мне удалось сделать так, чтобы это работало как одностороннее поведение ограничения: если мы отсортировали порядок и прочитали позицию родителя с помощью Attribute Reader, мы можем изменить положение частиц и привязать их к родителю.

HLSL проверяет, переместилась ли дочерняя частица слишком далеко от родительской (определяется maxDistance), и если да, то возвращает дочернюю частицу обратно к закреплённой позиции родителя.
Столкновения с полем расстояний окружения
Модуль столкновений по умолчанию уже предлагает многое, но у него есть опции «безопасности», чтобы уничтожать или отсеивать частицы, если они проникают в поверхность слишком глубоко. Поэтому мне нужна была простая, но надёжная логика «Не обрезай и не умирай». Особенно если одна частица умирает вдоль верёвки, это нарушает порядок соседей и саму логику ограничений.
Чтобы обойти это, я написал собственный упрощённый скрипт столкновения, используя то же поле Signed Distance Field (SDF), что и в исходном модуле столкновения.

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

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

Двустороннее ограничение и PBD
Одностороннее ограничение работало хорошо, но было недостаточно реалистичным, чтобы вести себя как настоящая верёвка.
Чтобы реализовать стабильное двустороннее ограничение, которое ведёт себя естественно и может быть натянуто/закреплено с обоих концов, я создал собственный решатель на основе динамики положения (PBD) с использованием интеграции Верле.
Вот информативная статья о логике верёвки Верле и PBD:
Код и излучатель немного длинны, чтобы объяснять их в этой статье, но я покажу основные моменты:


В основном это работает так же, как одностороннее ограничение, но в этом случае оно также считывает параметры дочерних элементов:

И вместо того, чтобы привязывать частицу обратно к родительскому ограничению, она привязывается к среднему значению между родительской коррекцией и коррекцией дочерней:

Кроме того, существует модуль ограничения концов, который по сути фиксирует нулевые и последние частицы ленты в указанном месте с помощью нормализованного RibbonLinkOrder (определяет, является ли это нулевой/последней частицей в цепочке):

Дополнительно существует одно жёсткое правило: не вычисляйте скорость в PBD. Она рассчитывается после всех преобразований положения с помощью CalculateAccurateVelocity, используя дельту положения — которая является основой решателя PBD — беря предыдущее положение и текущее положение и вычисляя скорость из их дельты.

В итоге я создал не просто пакет VFX, а инструмент, который можно использовать разными способами: для создания не только щупальцев, но и растительности, лоз, травы, верёвок, цепей и многого другого.
Вот результаты и возможные варианты использования:
Пакеты ресурсов
Я упаковал все эти исследования в два отдельных набора инструментов, доступных на FAB под FXology. Оба пакета будут участвовать в летней распродаже Fab (21–29 июля); добавьте их в свой список пожеланий сейчас, чтобы воспользоваться скидкой:
- Динамические щупальца и верёвки (Advanced Niagara VFX)
Включает полный двухсторонний решатель PBD, Grid3D self-col. isions и 48 аннотированных примеров настроек (UE 5.5+). - Динамические щупальца (Niagara VFX)
В оригинальном рабочем процессе используется более лёгкий, односторонний механизм ограничений (UE 5.1 +).

Если у вас есть вопросы, вы можете найти меня на FAB, X, YouTube, Discord и LinkedIn.
Адияр Айдарбеков, старший VFX-артист
Автор: Adiyar Aidarbekov