Создание следов от выстрелов на персонажах в Unreal Engine 4
Как сэкономить ресурсы системы.
Том Луман из Epic Games в своём блоге рассказал, как сделать так, чтобы на персонажах в Unreal Engine 4 оставались следы от выстрелов. Метод накладывает некоторые ограничения, зато не использует много ресурсов системы. Мы выбрали главное из руководства.
В нашей игре есть несколько типов декалей вроде пулевых выстрелов и кровавых брызг, которые остаются на стенах позади раненых персонажей. Эти декали привязаны к компонентам, однако если вы пытались применить их к анимированному мешу, то могли заметить, что декали скользят по его поверхности, что выглядит не очень хорошо.
Я заметил это скольжение в PlayerUnknown’s Battlerounds, где на персонажах использовались традиционные декали. Однако для игр с видом от третьего лица предпочтительно использование более стабильного решения.
Мне захотелось решить эту проблему для персонажей в нашей игре. Я вдохновился демо Райана Брука с GDC.
Его подход не укладывался в наш бюджет. Он требовал две целевые точки рендера (для каждого персонажа на сцене), меш с уникальной UV («манекен» из UE4, например, не имеет уникальной UV и требует дополнительных модификаций за пределами движка), а также сказывался на производительности из-за двух запросов.
Первый рендер происходит тогда, когда вам надо показать брызги на персонаже. С помощью SphereMask мы ищем пиксель, который был «поражён» выстрелом. Однако материал «не знает», где находится персонаж относительно позиции «поражения», поэтому каждому пикселю анимированного персонажа задают позицию в мире (world position) во вторую целевую точку рендера.
Проблема в том, что позиция пикселей в мире меняется с каждым кадром. Это значит, что после каждого выстрела нужно сперва перерендерить вторую целевую точку рендера, чтобы обновить позиции в мире, а только потом проводить финальный рендер с брызгами крови. Это было слишком дорого для нас, поэтому нам пришлось найти более дешёвое решение.
Есть способ оптимизировать технику, используя узел pre-skinned local position. Он заменяет позицию в мире, которую мы запекли в целевую точку рендера на заранее созданную локальную позицию. Я сделал быстрый чертёж и модифицированную версию материала Райана, «захватил» сцену и перевёл RT в статичную текстуру. Таким образом, нужда в повторном рендере отпадает. Каждый «выстрел» мы трансформируем локацию удара в pre-skinned local-space меша, до того, как она будет применена к персонажу.
Оптимизация сделала разработку дешевле, но меня всё равно не устраивало то, что при каждом ударе использовалась требовательная операция DrawMaterialToRenderTarget.
Я «открепил» все целевые точки рендера, чтобы попробовать использовать лишь SphereMasks для создания эффекта. Благодаря ему, количество «ударов» будет лимитировано. Я решил, что 3-5 ударов будет достаточно, так как следующий выстрел гарантировано убьёт противника (если речь идёт не о боссах).
Как и в случае с RT-эффектами, для работы маски с анимированным мешем нам нужна была эталонная поза (reference pose), на которую бы и накладывалась маска. Когда персонаж получает удар, мы трансформируем его позицию в мире в позицию позы (с помощью информации о BoneName). Вы можете сделать это инвертировав трансформацию локации удара с текущего преобразования кости, а затем преобразовав эту локацию с помощью reference pose transform той же кости.
Здесь можно увидеть визуализацию преобразования, принятого к локации удара (отмечено зелёным цветом). Синие линии отмечают трансформации эталонной позы. Фиолетовые позволяют увидеть разницу.
В примере с эталонной позой, локация удара уже находится на нужном месте, поэтому вы можете заметить, что синие и зелёные линии перекрывают друг друга, потому что они находится в одном и том же офсете. Фиолетовых линий нет вовсе, так как разницы между преобразовании нет.
Я преобразовал оригинальные локации ударов в локацию эталонной позы. Я получил постоянную позицию, которая не анимировалась. Эту локацию мы включили в шейдер, который не использовал pre-skinned позиции для маски. Для поддержки нескольких ударов мы изменяли имя параметра с каждым новым ударом: например, HitLocation_1, HitLocation_2 и так далее.
Ещё многое можно улучшить, но основная техника работает хорошо. В своих примерах я добавил HightLerp, чтобы получить более интересный визуальный эффект, чем от простой SphereMask.
Это техника намного менее ресурсозатратна, по сравнению с оригинальной, однако у неё есть ряд ограничений. Нам не нужно заниматься рендером целевой точки для каждого врага и мы используем лишь несколько параметров материалов, чтобы воспроизвести эффект.