Динамическое разрушение в игре Radio Viscera

Динамическое разрушение в игре Radio Viscera
Разрушение стены, обломки и кровь

Это обзор динамической системы, создающей разрушение в стенах для игры Radio Viscera.

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

Я старался вставить как можно больше гифок, скриншотов и звуков, чтобы текст не был сухим. Несколько последних разделов должны быть легче для понимания, потому что посвящены (менее техническим) вторичным системам и эффектам, которые работают вместе с разрушением.

Этот проект был создан на самописном движке на основе С++, который использует рендерер OpenGL и Bullet Physics SDK.

1. Обзор

Этапы и инструменты, которыми рисуется разрушение в стене
Рис. 1. Основные концепции, стоящие за системой

Система разрушения зависит от совместной работы трех элементов, которые делают иллюзию пробитой в стене дыры:

Параметрическая геометрия

Используется для рендера разрушенных стен.

Физический движок

Управляет формами столкновений (collision shapes), которые делают стену твердой, выполняет запросы raycast и помогает генерировать триангулированные формы столкновения при повреждении стены.

Текстуры для рендера

Хранит для каждой стены данные разрушений, которые потом скармливаются геометрическому шейдеру и также используются как источник для генерации форм столкновения.

2. Начальная генерация поверхностей

Каждая поверхность строится из трех частей. Первая часть — это отображаемая сетка (display mesh), которая представляет собой параметрически сгенерированную плоскость с данными о положении, нормали и UV-координатах. Она используется для рендеринга поверхности во время игры.

Вторая часть — это форма столкновения (collision shape). Это статическое сталкивающееся твердое тело, добавленное в динамический мир физического моделирования. Форма идентична отображаемой сетке и лежит в точности поверх нее. В дополнение к созданию столкновения, которое должно быть у твердой стены, она также обнаруживает запросы на разрушение raycast (см. параграф Обнаружение raycast).

Начальная поверхность (разрушение отсутствует)
Рис. 2. Начальные компоненты неразрушенной поверхности (отображаемая сетка, форма столкновений и буфер повреждений)

Третья часть — это буфер повреждений (damage buffer). Он представляет собой текстуру 2D-рендеринга, в которой хранится состояние повреждения поверхности. Размеры этой текстуры рассчитываются так, чтобы соответствовать размеру поверхности в мировом пространстве, умноженному на коэффициент «пикселей на метр», который определяет разрешение, при котором будут сохранены повреждения. В моем случае я использую 48 пикселей на метр, поэтому стена 4 x 2 метра в мировом пространстве будет иметь буфер повреждений размером 192 x 96 пикселей. Эта текстура очищена до RGBA [0,0,0,0].

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

3. Обнаружение raycast и применение разрушений

Разрушение применяется с использованием raycast. Во время выстрела из оружия тест raycast выполняется в отношении всех тел столкновения в динамическом мире, при этом луч исходит из дула оружия и продолжается в направлении текущей цели. Если raycast пересекается с фигурой столкновения на разрушаемой поверхности, столкновение регистрируется. Мировое положение и нормальное направление столкновения записываются и направляются в объект разрушаемой поверхности, чтобы он мог записать столкновение в буфер повреждений.

Проверка raycast
Рис. 3. Нормаль к поверхности столкновения используется для эффектов, связанных с направлением

Чтобы действительно применить разрушение, спрайт нужно нарисовать в буфере разрушения. Черный пиксель в буфере разрушения показывает, что на данном участке разрушения нет, а полностью белый пиксель показывает полное разрушение участка. Перед тем, как спрайт можно будет нарисовать, мне нужно вычислить, где поместить рисунок в буфере разрушения, чтобы он совпадал с местом столкновения в мировом пространстве. Для этого процесса будут использованы барицентрические координаты, поэтому сначала мне нужно выяснить, какой из двух треугольников (которые составляют форму столкновения неповрежденной плоскости) получил удар.

Разрушение происходит в одном из треугольников плоскости
Рис. 4. Шаги, необходимые для рисования повреждений в буфере

Примечание: барицентрический метод — это пережиток того времени, когда система была разработана для работы с произвольными треугольными сетками. Если вы имеете дело только с плоскими прямоугольными гранями, это преобразование можно упростить.

Нахождение нужного треугольника выполняется с помощью теста на пересечение лучей и треугольников с использованием данных из попадания raycast. Так как треугольников всего два, это происходит быстро. Мировое положение разрушения затем преобразуется в локальное пространство относительно поверхности путем умножения его на обратное преобразование объекта стены. Расстояния между точкой удара и вершинами треугольника вычисляются и используются для генерации UV-координаты, которая совпадает с положением повреждения на стене.

Спрайт дыры в стене
Рис. 5. Увеличенная и осветленная версия спрайта повреждений

Последний шаг здесь — нарисовать спрайт повреждений в предназначенную для рендера текстуру буфера повреждений как штамп. Облик спрайта является результатом множества проб и ошибок, я искал, какая форма хорошо подойдет и произведет желаемый эффект (как визуально, так и физически). Вращение спрайта произвольно, а его масштаб и яркость зависят от величины повреждения. Это позволяет более мощным ударам проделывать отверстия побольше. Спрайт рисуется с аддитивным смешиванием, так что последовательные спрайты повреждений могут «накладываться» друг на друга, если они применяются в одной и той же области.

4. Рисование поврежденной стены

Тесселяция поверхности перед изменением ее формы
Рис. 6. Каркас меша после тесселяции. Никакие повреждения еще не применялись.

Когда нетронутая поверхность повреждается впервые, происходит несколько вещей. Сначала тесселируется отображаемая сетка, которая до этого момента представляла собой плоскость с двумя треугольниками. Подобно тому, как рассчитывается разрешение буфера повреждений, существует коэффициент плотности сетки, который описывает «количество граней на метр». Я использую значение 6, поэтому моя стена 4 x 2 м дает сетку размером 24 x 12 квадратных секций (576 треугольников). Синий канал цвета вершины используется как логическое значение, чтобы отмечать, какие вершины находятся на самых краях плоской сетки. Поэтому я могу выдавливать эти вершины в геометрическом шейдере, чтобы создать толщину стены, которую вы видите вдоль верхнего края.

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

UV для объектов, относящихся к стене
Рис. 7. Визуальное представление двух наборов UV (ниже) с текстурами, которые они сэмплируют (выше)

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

В следующем фрагменте я пытаюсь описать шейдер с помощью слов.

Если вы предпочитаете просто читать псевдокод, прокрутите вниз.

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

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

Последний вариант является наиболее сложным. В этом случае одна или несколько из трех вершин имеют значение повреждения больше 0, но ниже порога «полностью поврежден». Положение поврежденной вершины трансформируется, поэтому чем больше повреждений у вершины, тем больше она будет выталкиваться внутрь, как в стену, так и по направлению к соседним вершинам. Это преобразование применяется к исходным вершинам, а также к перевернутым вершинам «внутренней стены». В результате получается многоугольный край, который сжимается и утончается по мере того, как наносится больше повреждений, делая геометрию стены все тоньше и тоньше, пока значение повреждения не достигнет порогового значения, а геометрия полностью не будет отброшена.

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

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

Псевдокод геометрического шейдера

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

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

Вид дыры в игре, с каркасом и в пространстве нормалей
Рис. 8. Поврежденная стена как она выглядит в игре, наложение каркаса и нормали

5. Столкновение

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

Рис. 9. Рисование в буфер повреждений и его стирание с использованием инструментов редактора. Форма столкновения в белом цвете.

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

Получение буфера повреждений из VRAM без остановки конвейера

Теперь необходимо проанализировать пиксели буфера повреждений, чтобы выделить контур поврежденной области. Алгоритм маршевых квадратов используется для сканирования пикселей и поиска краев любых ярких форм, указывающих на поврежденную область. Он включает в себя несколько магических пороговых значений и значений эпсилон, которые я подбирал методом проб и ошибок, чтобы гарантировать достоверный результат. Возвращается набор 2D-форм, края которых получены из координат пикселей. Затем формы упрощаются, чтобы удалить посторонний шум и детали.

Обработка информации после столкновения
Рис. 10. Генерация новой формы столкновения из битмапа

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

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

6. Навигация

Навигация для NPC в Radio Viscera использует решатель A* на основе узлов. Объекты узлов пути размещаются по всему уровню, а затем автоматически связываются вместе, с использованием проверок видимости и доступности от узла к узлу. Если NPC необходимо перебраться в новое место, он запустит решение пути между узлом, ближайшим к месту назначения, и узлом, ближайшим к текущему местоположению NPC.

Определение пути персонажа в игре
Рис. 11. Пурпурные линии — это граф навигации, красная линия — прямой маршрут к пункту назначения NPC, а зеленая линия — запланированный маршрут

Чтобы пройти через разрушенную стену, NPC должны знать, могут ли они пройти в этом месте. Навигационную сеть необходимо обновлять в ответ на события повреждения.

Новый узел графа чтобы проложить путь через разрушение, вид сверху
Рис. 12. Вид сверху на граф навигации после повреждения

Когда происходит событие повреждения, в сети узлов пути выполняется поиск, чтобы собрать все узлы в пределах определенного радиуса от попадания. Из этого набора выбираются два ближайших узла по обе стороны от поврежденной грани, и между ними выполняется проверка видимости. Если два узла имеют прямую видимость друг друга, это означает, что поврежденная область достаточно велика, чтобы персонаж мог пройти. Соединение этих двух узлов напрямую иногда приводит к непрямым путям, которые с большой вероятностью приведут к застреванию персонажей. Как решение, новый временный узел пути вставляется в граф на земле непосредственно под поврежденной областью, чтобы создать более ортогональный маршрут. Два исходных узла соединены новым временным узлом, и теперь NPC могут безопасно проходить через стену, как если бы ее там никогда не было. Эти узлы помечены как «временные», потому что они никогда не сохраняются и стираются при сбросе уровня.

Рис. 13. Для демонстрации эффекта приседание показано на половинной скорости

Размеры и форма тел столкновения персонажей имеют такую особенность, что персонажи часто застревают при попытке пройти сквозь отверстие, которое «выглядит» достаточно большим для этого. Чтобы решить эту проблему, персонажи постоянно выполняют серию raycast, чтобы проверить, не сталкиваются ли со стеной их головы и средние части тел. Если предположение верно, персонаж знает, что он будет биться головой только о верхний край стены, и чтобы пройти сквозь нее, ему нужно пригнуться. Когда эти условия выполнены, тело столкновения персонажа масштабируется вниз по оси Y, чтобы оно могло протиснуться через зазор. Как только персонаж пройдет сквозь стену, лучи больше не будут обнаруживать ничего, и тело столкновения вернется к своей нормальной форме. Во время этого процесса модель персонажа также уменьшается в масштабе синхронно с телом столкновения, чтобы имитировать, будто проходя через дыру он на короткое время наклоняется.

Рис. 14. NPC по имени Bully даже не подозревает о существовании стены

Одним из особых случаев в отношении навигации персонажей является этот большой атакующий NPC (по прозвищу Bully), который может пробивать стены своим телом и подбрасывать персонажа игрока в воздух. В начале я пытался проделать работу обычным способом, когда NPC врезался в стену, создавая дыру, а затем перемещался через только что созданное отверстие. Но с таким подходом не получалось, чтобы NPC разбивал стену на полной скорости, не останавливаясь, не меняя направление или не застревая. В результате я отключил столкновение между этим конкретным типом NPC и повреждаемыми стенами, в то же время обнаруживая события столкновения и нанося урон стене. Это создает иллюзию того, что стена была разрушена NPC, и NPC может поддерживать свою скорость на протяжении всей атаки. Кроме того, логика навигации для этого NPC была изменена, чтобы игнорировать повреждаемые стены при проверке видимости. Это позволяет NPC перемещаться к месту назначения по прямой, не беспокоясь о «правильном» пути.

7. Эффекты

Рис.15. Показывается на 0.1х обычной скорости

Чтобы усилить воздействие от повреждения стены, активируется несколько вторичных визуальных и звуковых эффектов.

Рис. 16. Эффект взрыва

Первый из них — это взрывной эффект частиц. Он отвечает за внезапную вспышку и выброс очень ярких фрагментов в сторону от места удара по нормали повреждения. Частицам придается высокая стартовая скорость с большим коэффициентом сопротивления, поэтому они быстро вырываются, а затем сразу же начинают замедляться. Меш для каждой частицы представляет собой общий кусок геометрии, такой же меш используется для обломков, падающих на землю. Цвет каждой частицы начинается с RGB [1600, 1600, 1600] и использует экспоненциальную функцию ослабления для постепенного уменьшения до [255, 255, 255], поскольку его преобразование масштабируется до нуля. Время жизни каждой частицы колеблется в пределах 0.5-4.0 секунд.

Рис. 17. Эффект пыльного облака

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

Графическое представление содержимого буфера смещения в виде сферы
Рис. 18. Вид сцены с соответствующим буфером смещения

Смещение экранного пространства используется для кратковременного искажения области вокруг удара и усиления ощущения, что это большие, разрушительные, энергичные воздействия. Во время рендеринга сцены низкополигональная сфера рисуется в отдельный кадровый буфер (рисунок 18) специально для объектов смещения. После завершения рендеринга основной сцены применяется стек постфильтров, который включает такие вещи, как окклюзия окружающей среды и глубина резкости. В верхней части стека есть специальный постфильтр смещения экранного пространства, который сэмплирует буфер кадра смещения для искажения пикселей финальной сцены. Сфера начинается в полном масштабе, а затем быстро сжимается, пытаясь имитировать эффекты отрицательного давления, наблюдаемые после большой взрывной волны. Масштабирование применяется в течение 0.5 секунды и использует функцию экспоненциального замедления, чтобы все произошло быстро и резко на вид.

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

Звуковой эффект удара, представляет собой смесь падения нескольких кусков песка и камня с басовым барабаном. Это обеспечивает приятный низкий «бум», который для эффектов пыли и мусора затем переходит в более высокие частоты.

Звуковой эффект осыпающегося камня

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

Звук оружия игрока
Использованное подключение устройств в звуковом редакторе Reason
Рис. 19. Креативное подключение в редакторе Reason заставляет все инструменты срабатывать одновременно

8. Двери

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

Проем двери съехал в сторону из-за выбранного для объекта невысокого разрешения
Рис. 20. Ранние проблемы с дверями

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

Рис. 21. Добавление двери в редакторе

В процессе разработки я решил не использовать отпечаток доступных для повреждения стен в виде прямоугольных блоков и перешел на системы в стиле заборных столбов для определения 2D границ комнаты в 3D пространстве (рисунок 21, выделено зеленым цветом). Одной из особенностей этого изменения стала возможность отключать секции стены, чтобы не создавать повреждаемую поверхность вдоль этого края. Я добавил инструменты, которые позволяют мне «штамповать» дверь в стене путем ее разделения и вставки дополнительной секции с отключенным созданием стены, т.е. создавая пустой промежуток. В этот недавно добавленный промежуток помещается дверной проем, а над рамой добавляется поддельная сетка фрамуги. Сетка фрамуги специально создана, чтобы поместиться в этот небольшой промежуток и не имеет столкновений, что значительно упрощает выбрасывание NPC через двери.

Рис. 22. Выбросите отсюда этот мусор

Дверные панели представляют собой динамические тела столкновения с шарнирным ограничением (hinge constraint), которое позволяет им свободно поворачиваться в определенном диапазоне. Шарниры также предоставляют API мотора для возврата двери в закрытое положение. Когда дверная панель получила достаточное повреждение, ограничение шарниров отключается, и дверь вылетает из рамы.

9. Заметки

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

Рис. 23. Один из ранних тестов

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

Каркасы фигур столкновения
Рис. 24. Совмещение форм столкновений с буфером повреждений было непростой задачей

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

Геометрический шейдер также имеет высокую вычислительную интенсивность от кадра к кадру из-за математики для восстановления положений и нормалей поврежденных краев.

Эффективнее было бы использовать вычислительные шейдеры для сканирования буфера повреждений и перестройки сетки только при касании буфера повреждений. Однако этот проект был разработан для работы в контексте OpenGL 3.3, где вычислительные шейдеры не поддерживаются.

Рис. 25. Как все смотрится в игре

Спасибо за чтение. Если вам понравилась статья, поиграйте в игру.

Подпишитесь на @owendeery, чтобы получить гифки от разработчиков игр и материалы по разработке движков.

Дополнительные технические описания и руководства доступны здесь.

Оригинальный текст: http://fire-face.com/destruction/

Понравилась статья? Поделиться с друзьями:
Автор natalya
Переводит для Вас самые интересные статьи про разработку игр. По образованию физик-программист. Техническими переводами начала подрабатывать еще на старших курсах и постепенно это переросло в основное занятие. Интересуется гуманитарными технологиями, пробует себя в журналистике.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *