Point Cloud Morphing FX на Unreal Engine 4
Как создать эффект 3d морфинга на движке Unreal Engine 4.
Всем привет!
В статье опишу алгоритм и процесс создания своей версии Point Clouds Morphing VFX для Unreal Engine 4.
Что из себя представляет Point Clouds Morphing?
Это эффект трансформации (плавного перехода) между 3d моделями, которые представлены в виде particles (частиц) в трехмерном пространстве:
Прежде чем начать работу над VFX, я сформулировал требования, которым итоговая реализация должна удовлетворять:
Полностью на BluePrint без использования C++.
- Не должно быть сложных и тяжелых расчётов на CPU.
- Лимит по количеству частиц позволяющий показать среднюю по детализации 3d модель.
- Морфинг любого количество исходных частиц в любое целевое в пределах лимита.
- Поддержка трансформации вращения и перемещения.
- Возможность кастомизации по цвету и другим параметрам.
- Возможность масштабирования лимита для C++ версии.
Итак, у нас есть две 3d модели и нам нужно трансформировать одну модель в другую.
Как мы знаем, 3d модель содержит в себе информацию о vertex (вершинная координата). На основе вертексов формируются треугольники, которые затем видеокарта отрисовывает.
Каждый вертекс содержит всю необходимую информацию для рендеринга 3d модели, но нам в данном случае для создания эффекта нужны только координаты вертекса, описывающие его положение в пространстве, т.е. XYZ:
Разумеется, пересчитывать координаты, трансформации и т.д. на CPU было бы крайне неэффективно, поэтому весь расчет производится на GPU.
После некоторого резёрча и процесса подумать, стало очевидно - самый простой и эффективный способ хранить данные и оперировать с ними при помощи GPU, это запечь (bake) координаты вертексов в текстуру. На выходе мы и получим тот самый Point Cloud. Фактически - текстуру в которой хранятся координаты вертексов модели в специальном формате.
Упаковываем координаты в текстуру
Следующий вопрос - как запечь координаты в текстуру средствами Unreal Engine 4 при этом только используя BluePrint?
Для этого я использовал RenderTarget и отрисовку данных непосредственно в него. Фактически мы рисуем в текстуру с помощью доступных в UE4 средств смещаясь по текстуре (Pixel Coordinate).
Для рисования используется DrawLine функция с параметрами линии шириной в 1 пискел и длиной в 1 пиксел.
Т.е. мы поточечно рисуем всю текстуру:
Т.о. мы берём из 3d модели все координаты её вертексов и отрисовываем их значения. На выходе получаем вот такую Point Cloud текстуру:
Здесь нужно более подробно рассказать о формате в котором хранятся данные в текстуре.
Дело в том, что изначально координаты в 3d модели представлены в виде float значений. А каждый такой float занимает 4 байта или 32 бита. Соответственно, чтобы нам сохранить данные о координате всего лишь одного вертекса потребуется 12 байт.
Это достаточно расточительно и потребовалось бы значительно увеличивать размер текстуры. Поэтому я ограничил размер модели, которая может быть использована для эффекта в диапазоне от -32767 до +32767.
Фактически под один компонент координаты вертекса (X, Y или Z) отводится 16 бит integer значения. Т.е. это не число с плавающей точкой.
16й бит это знак + или - и остальные 15 битов непосредственно для задания координаты.
Т.о. чтобы упаковать все три компоненты вертекса в Point Cloud текстуру нам нужно 6 байтов вместо 12.
Разумеется можно было бы упаковать и полноценный float, но сам по себе эффект используется локально и очень маловероятно что потребуется использовать большие диапазоны.
Второй момент который нужно было учитывать - это процесс распаковки (декодирования) координаты из текстуры в назначения на GPU.
16 bit значение состоит из Hi Byte и Low Byte:
Мы округляем значение координаты до Integer. Если в модели координата имеет значение 345,783, то алгоритм упаковки выполнит округление до 346.
Дробная часть нам не нужна, т.к. мы не работаем с полигонам, а лишь задаем координаты для центра частиц близко к оригинальным значениям из 3d модели.
Например, число 12345 будет представлено в виде двух байтов:
HiByte = 48
LowByte = 57
48 * 256 + 57 = 12345Далее, полученные два байта конвертируется в два компонента linear цвета для точки, которым мы и рисуем в текстуре.
Третий компонент RGB цвета отвечает за видимость частицы - это visibility флаг
На выходе мы получаем цвет точки которую и рисуем в RenderTarget текстуру, показанную в видео выше.
Компоненты X,Y и Z рисуются в текстуру с определенным смещением в RGB каналы текстуры:
X компонента со смещением 0 по горизонтали и вертикале.
Y компонента со смещением 128 по горизонтали и 0 по вертикале.
Z компонента со смещением 0 по горизонтали и 128 по вертикале.
В четвертую область текстуры я записываю значение видимости - т.е. Visibility flag.
Point Cloud Morphing всегда отрисовывает заданное количество частиц.
Но т.к. разные модели содержат разное количество вертексов, и если количество точек в модели меньше чем заданное, то несуществующие я помечаю, как невидимые.
На картинке выше красным цветом - это те точки которые существуют в исходной модели. Координаты же дублируются до максимального возможного значения 16384.
Отрисовка на GPU
Имея запеченные в текстуры координаты вертексов и флаг их видимости можно отрендерить частицы.
Для того, чтобы процесс отрисовки не отнимал очень много ресурсов и выполнялся за 2 draw calls я использовал одну из features UE4 - Instanced Static Mesh Component.
Данная технология позволяет массово отрисовывать одинаковые статические модели без существенной нагрузки на CPU и GPU.
Вся отрисовка происходит внутри материала (shader). В качестве параметров в материал подается две текстуры, а так же все другие необходимые данные, например степень морфинга (0...1), цвет частиц и т.д.
Первая текстура - текущая модель.
Вторая текстура - модель в которую будет происходить морфирование.
Сам материал достаточно комплексный и выглядит так:
Внутри выполняются следующие действия:
- Декодирование координаты для вертекса из HiByte и LowByte в значение для обеих Cloud текстур. Это и будет центр частицы.
- Поворот полученной точки вокруг центра эффекта на требуемые углы и смещение если требуется.
- Применение цвета, свечения и других пользовательских настроек.
- Если частица невидима (Visibility flag равен 0), то частица рисуется размером 0.
- Интерполяция между координатами, цветом и др. параметрами текущей модели и целевой.
В зависимости от смещения (0,128) материал декодирует требуемую компоненту для её сборки в координату.
Если же целевая модель имеет больше точек чем исходная то в процессе морфинга идет увеличение размера частицы от 0 до 1.0, ровно так же и и обратную сторону - интерполяция значения от 1.0 до 0.
Т.к. координаты продублированы в Point Cloud текстуре - создается эффект того, что частицы создаются из существующих и формируют новую структуру.
Если же в целевой модели меньше частиц, частицы из исходной будут стремиться в координаты существующих точек у целевой модели.
Добавляется дополнительный пользовательский distortion, а также custom значения для scale.
Определенный хак пришлось сделать, чтобы определять какой частице соответствует индекс в текстуре.
К сожалению нельзя получить внутри материала индекс Instanced Mesh Component'а, поэтому изначально все инстансы создаются со смещением.
Разница в смещении инстанса (world position) и есть индекс по которому материал выбирает нужную координату из Point Cloud текстуры и рассчитывает частицу.
Частица представлена в виде двух треугольников, которые всегда обращены к камере.
Результат геометрического расчёта отправляется на WPO (World Position Offset) вход материала.
В финале получаем результат:
Подводя итог:
Эффект почти полностью рассчитывается на GPU.
На CPU выполняется лишь логика смены текстур и передача некоторых параметров в материал - цвет, настройки distortion, степень морфинга и т.д.
16384 частиц (с возможностью расширения в С++ версии).
Полностью на UE4 BluePrint.
PointCloud текстура 256x256 точек, RGB. Не требует много места.
Рендер происходит за 2 draw calls.
Возможностью запечь любую модель в пределах 16384 точек и размером от -32767 до +32767.
Эффект протестирован на PC (SM5), PS4 и Nintendo Switch.
Время на разработку ... undetermined =)
На этом всё, благодарю за внимание!