Нажмите ESC для закрытия

Интервью10 июня 2026 г.

6000 агентов на одном игровом потоке:

Йонджу «Kaley» Чо показывает нам, как создать целый стадион людей с помощью системы для управления толпой на базе GPU, используя Houdini и Unreal Engine, и объясняет логику, текстурирование и анимацию, лежащие в основе проекта.

−85%

РАСПРОДАЖА В SKILLS UP

Цены 2020 года

КурсыВидеоуроки
Ещё →
6000 агентов на одном игровом потоке:
6 000 агентов в одном игровом потоке: — изображение 1

Введение

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

Заполнение огромного боксёрского стадиона более чем 6 000 реагирующих зрителей обычно доводит системы скелетных сеток, привязанных к процессору, до предела. Перенося деформацию персонажей на GPU с помощью Vertex Animation Textures (VAT), я могу поддерживать высокую частоту кадров, сохраняя при этом высокое качество движения. В этом обзоре рассматривается полный конвейер: от процедурных настроек Houdini до запуска анимации на основе шейдеров, с акцентом на основных принципах технического искусства для современной разработки игр.

Эта статья описывает полный процесс от необработанной геометрии стадиона до живой, дышащей толпы.

Готовая система: 6 072 зрителя анимируются в реальном времени, управляемые через интерактивный пользовательский интерфейс. Время на GPU: 17,51 мс. Вызовы отрисовки: 276.

Часть 1: создание основы в Houdini

Шаг 1: извлечение позиций сидений

Первая проблема проста в описании и сложна в решении вручную: где сидит каждый зритель и в каком направлении он смотрит? Разместить 6 000 преобразований вручную невозможно, поэтому вместо этого работает геометрия сидений самого стадиона.

FBX стадиона импортируется в Houdini, геометрия стула выделяется, и цикл For Each перебирает все соединённые части. Внутри цикла VEX wrangle использует getbbox() для поиска ограничивающей рамки каждого сиденья и помещает одну точку в её центре с помощью addpoint(). В результате получается чистое облако точек без лишней геометрии.

6 000 агентов в одном игровом потоке: — изображение 2

Unreal Engine использует систему координат Z-up, в то время как Houdini по умолчанию использует Y-up. Чтобы убедиться, что ваши позиции VAT совпадают со статическими экземплярами сетки в движке, поменяйте местами атрибуты Y и Z и отрицайте новый Y.

// Houdini Z → Unreal Front/Back
// Houdini X → Unreal Left/Right
// Houdini Y → Unreal Up/Down
@P = set(@P.z, -@P.x, @P.y);

// Применяем то же преобразование к нормалям
@N = set(@N.z, -@N.x, @N.y);

Конечный узел в цепочке — Labs CSV Exporter. Экспортируются только четыре атрибута, которые нужны Unreal: RowName, Index, P (позиция, разделённая на Px/Py/Pz) и N (нормаль, разделённая на Nx/Ny/Nz). В результате получается облегчённая электронная таблица, которая становится единственным источником истины для всех преобразований более чем 6 000 сидений.

6 000 агентов в одном игровом потоке: — изображение 3

Внутри Unreal этот CSV импортируется как Data Table с помощью пользовательской структуры S_CrowdTable. В результате получается идеально организованная таблица всех сидений на арене.

6 000 агентов в одном игровом потоке: — изображение 4

Часть 2: запекание VAT в Houdini

Конвейер персонажей

Каждый персонаж-зритель сначала проходит подготовительный этап. Сетка очищается, поли-уменьшается до соответствующего LOD для трибун, и назначаются группы материалов (Body, Shirt, Hair), чтобы материал Unreal мог применять рандомизированные цвета для каждой группы.

6 000 агентов в одном игровом потоке: — изображение 5
6 000 агентов в одном игровом потоке: — изображение 6

Выпечка нескольких анимационных состояний

Толпе нужно больше одной анимации. Ей нужны: Idle (сидячее положение, едва заметное движение), Clap, Yell и Stand. Каждая анимация — это отдельная выпечка VAT.

Сеть использует общий OUT_DeformMesh, который подаётся на четыре параллельных узла ProcessCharacter, по одному на каждое анимационное состояние. Каждая ветвь деформирует сетку через анимационные кадры и выводит в свой собственный OUT_ null, который затем считывает Labs VAT ROP.

6,000 Agents on One Game Thread: - изображение 7

Настройки VAT ROP

Экспорт фактически осуществляет Labs Vertex Animation Textures ROP. Здесь несколько настроек имеют решающее значение:

  • Method: Soft (Constant Topology) — топология сетки никогда не меняется между кадрами, что требуется для корректной работы VAT.
  • Raster Depth: 16-Bit Floating Point — это не обсуждается. 8-битные текстуры не имеют достаточной точности для хранения смещений положения в мировом пространстве без видимого дрожания вершин, особенно когда камера находится близко к трибунам.
  • Pack Normals into Position Alpha — упаковывает данные о нормали в альфа-канал текстуры положения, сохраняя слот текстуры.
  • Engine: Unreal Engine — устанавливает правильные выходные соглашения координат для UE.
6,000 Agents on One Game Thread: - изображение 8

Часть 3: Шейдер Unreal Engine

Материал (M_VAT_HISM) — это то, где GPU выполняет всю свою работу. Он считывает текстуры VAT, прокручивает их во времени, смешивает между анимационными состояниями, рандомизирует цвета и обрабатывает волновой эффект, и всё это без единой инструкции CPU для каждого экземпляра во время выполнения.

UV: Прокрутка по текстуре анимации

Основная механика VAT заключается в том, что каждый анимационный кадр — это горизонтальная строка в текстуре. Чтобы воспроизвести анимацию, шейдер прокручивает координату V вниз с течением времени. Раздел UV обрабатывает это, создавая временную петлю на основе параметров FPS и количества кадров, затем используя PerInstanceRandom для смещения времени начала каждого экземпляра, чтобы они не анимировались синхронно. Без этого 6 000 символов будут считываться как одна плитка текстуры, а не как независимые агенты.

6,000 Agents on One Game Thread: - изображение 9

WPO: Динамическое смешивание анимации

Раздел World Position Offset одновременно считывает пять анимационных текстур (Anim1–Anim4 для четырёх базовых состояний, плюс AnimWave). RGB каждой текстуры хранит сжатые смещения положения, а параметры min/max ограничивающей рамки используются для их распаковки обратно в значения в мировом пространстве. Цепочка узлов Lerp затем смешивает между активными состояниями на основе альфа-значений, управляемых ползунками пользовательского интерфейса.

6,000 Agents on One Game Thread: - изображение 10

Нормаль: распаковка сжатых нормалей

Нормали распаковываются с помощью пользовательской функции материала MF_VAT_UnpackNormal, которая восстанавливает компонент Z из упакованных значений X и Y, затем синхронизируется с помощью Lerp с смешиванием WPO. Здесь зеркально отражена та же цепочка Lerp из раздела WPO, чтобы сохранить нормали согласованными с анимированными позициями.

6,000 Agents on One Game Thread: - изображение 11

Волна: волна на стадионе, управляемая GPU

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

Коллекция материальных параметров MPC_VAT содержит глобальные переменные: WaveStartTime (когда была запущена волна) и WaveSweepDuration (сколько времени должно занять полное перемещение по стадиону). Каждый экземпляр хранит свой уникальный GlobalID в PerInstanceCustomData[6]. Раздел Wave шейдера использует этот ID для расчёта персонализированного времени срабатывания:

WaveTriggerTime = (GlobalID × WaveSweepDuration) + WaveStartTime

Когда узел Time материала превышает WaveTriggerTime, WaveAnimAlpha интерполируется от 0 до 1, плавно переводя экземпляр из текущей анимации в строку VAT Wave.

6 000 агентов на одном игровом потоке: изображение 12
6 000 агентов на одном игровом потоке: изображение 13

Основной цвет

Рандомизация основного цвета гарантирует, что никакие два соседних зрителя не одеты в один и тот же цвет. PerInstanceRandom управляет сдвигом оттенка на диффузной текстуре, а отдельный слот PerInstanceCustomData[3,4,5] хранит цвет рубашки, установленный в Blueprint при появлении, что позволяет процессору устанавливать цвета один раз при появлении, а не пересчитывать их каждый кадр.

6 000 агентов на одном игровом потоке: изображение 14

Часть 4: Создание экземпляров в Blueprint и логика пользовательского интерфейса

Blueprint CrowdManager

Один актёр BP_CrowdManager считывает таблицу данных и заполняет HISM. Функция создания экземпляров (AddAgentInstances) принимает целевое количество агентов, вычисляет, сколько экземпляров нужно добавить или удалить на основе текущего состояния, и выполняет итерацию по (перетасованному) списку создания экземпляров.

«Перетасованный список создания экземпляров» является ключом к органичному заполнению. Вместо заполнения мест ряд за рядом строки таблицы данных сортируются в перемешанном порядке при инициализации. По мере увеличения ползунка Fill % толпа растёт случайным, рассеянным образом по всему стадиону.

6 000 агентов на одном игровом потоке: изображение 15
6 000 агентов на одном игровом потоке: изображение 16
6 000 агентов на одном игровом потоке: изображение 17
6 000 агентов на одном игровом потоке: изображение 18

Привязка пользовательского интерфейса

Виджет пользовательского интерфейса использует два типа ползунков: ползунок Spawn #, который напрямую управляет общим количеством агентов, и ползунок Fill %, который контролирует, какой процент созданных агентов отображается. Ползунки состояния анимации (Clap, Yell, Stand) управляют каналами PerInstanceCustomData для смешивания альф.

6 000 агентов на одном игровом потоке: изображение 19

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

Часть 5: Результаты производительности

Демонстрация в действии

Прежде чем перейти к цифрам: вот как система выглядит на самом деле во время работы.

Система толпы в действии с созданием экземпляров в реальном времени и смешиванием анимации. По мере настройки ползунков Spawn # и Fill % зрители появляются на стадионе органично, а не заполняя места ряд за рядом. Ползунки анимации управляют каналами PerInstanceCustomData для каждого экземпляра, переводя 6 072 зрителя между состояниями Idle, Clap, Yell, Stand и Wave без затрат на анимацию процессора.

VAT против скелетной сетки: реальная стоимость

Чтобы доказать, что подход VAT действительно того стоит, тот же стадион был заселён сравнимым количеством стандартных актёров скелетной сетки, уменьшенных до почти идентичного бюджета полигонов, как у статической сетки VAT, так что сравнение является справедливым. Всё профилирование проводилось на RTX 3070 при 1080p с 90% процентом экрана.

Бюджет сетки для обоих подходов почти идентичен. Статическая сетка VAT имеет 14 130 треугольников/10 063 вершины, а скелетная сетка — 14 073 треугольника/12 292 вершины.

Метрика

Скелетная сетка (6 072 агента)

VAT + HISM (6 072 агента)

Улучшение

FPS

28,37

57,31

в 2 раза быстрее

Время игры

34,15 мс

0,66 мс

в 51,7 раз быстрее

Время отрисовки

16,22 мс

0,74 мс

в 21,8 раз быстрее

Время GPU

32,00 мс

17,51 мс

в 1,8 раз быстрее

Вызовы отрисовки

21 369

276

на 77,4 % меньше

Устранение узкого места в игровом потоке: переход от 34,15 мс к 0,66 мс в игровом потоке фактически «разблокировал» движок. Процессор так усердно работал над вычислением преобразований костей для скелетных сеток, что у него не оставалось ресурсов для логики геймплея, физики или любых других игровых систем.

Эффективность вызовов отрисовки: сокращение количества вызовов отрисовки с 21 369 до 276 — это снижение нагрузки на RHI на 98,7 %. Объединяя 6 072 агента в иерархические экземпляры статических сеток (HISMs), движок отправляет на GPU лишь несколько массивных инструкций вместо тысяч мелких.

Преимущества для GPU через инстансинг: несмотря на то, что текстуры анимации вершин добавляют вычислений в вершинный шейдер, время работы GPU сократилось почти наполовину (с 32 мс до 17,5 мс). Это доказывает, что накладные расходы на управление 6 000 отдельными вызовами отрисовки скелетных сеток фактически замедляли сам GPU, а конвейер инстансинга HISMs значительно более эффективен для обработки аппаратурой.

6 000 агентов на одном игровом потоке: — изображение 20
6 000 агентов на одном игровом потоке: — изображение 21

Сложность шейдера

В Unreal представление сложности шейдера визуализирует стоимость инструкций на пиксель по цвету от зелёного (дешёвые) до розового (дорогие). Когда 6 0072 агента VAT заполняют кадр, вся толпа отображается в зелёной полосе.

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

Ловушка точности текстур 8-бит против 16-бит

Одно решение, которое легко упустить из виду в настройках Houdini VAT ROP, имеет заметные последствия: Растровая глубина. Установка этого параметра в 8-бит вместо 16-битной плавающей запятой приводит к ошибке квантования в сохранённых смещениях позиций, что проявляется в виде дрожания вершин. Всегда используйте 16-бит. Накладные расходы на память текстуры невелики, а визуальная разница очевидна.

Заключение

Переход от скелетных сеток, управляемых процессором, к экземплярам VAT, управляемым GPU, меняет фундаментальный вопрос систем толпы. Он перестаёт быть вопросом о том, сколько персонажей можно себе позволить, и становится вопросом о том, как сделать их реалистичными. Самым удовлетворяющим моментом в этом проекте было наблюдение за тем, как волна впервые распространяется по всему стадиону, когда 6 072 фигуры поднимаются последовательно, управляемые двумя числами, отправленными с процессора, и ничем иным. В этом и заключается обещание технического искусства: превращение холодной математики в коллективные эмоции.

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

В эпоху растущей визуальной сложности самая мощная оптимизация — это не только сохранение кадров. Это создание пространства для жизни.

Ёнжу Чо, технический художник

Автор: Yeonju Cho

Рефпаки
Стать автором