Уроки Аушвица: VR-проект с Volumetric съемкой. Часть 4
27 января был опубликован видео-арт проект, над которым мы работали 2,5 месяца. В этом цикле статей мы расскажем про важные смысловые и технологические аспекты этого проекта и о том, почему не нужно бояться неизвестного.
Часть 1 \ Часть 2 \ Часть 3 \ Часть 4
Вступление
В предыдущих статьях мы описали многие нюансы, связанные с ресерчами, концептуальной составляющей, съемками, работой с техникой (камерами, VR-шлемами).
Следующим этапом была подготовка пайплайна для разработки CG-контента на основе отснятого материала и ряда заготовленных эффектов в VFX Graph на Unity.
Мы разделили контент, который предстояло обработать, на три части по сценарию:
Первая часть — Intro, где реализовывалась “оцифровка” лиц детей, отснятых крупным планом одновременно с обычной камеры и с камеры глубины.
Вторая часть — Main, где дети, отснятые в volumetric (камера глубины), с наложенными на них эффектами, рисуют в VR.
Третья часть — World, пролёт по сцене, где расставлены получившиеся VR-рисунки каждого ребенка. Было важно соблюсти логику связи каждой части.
Мы сформировали общий пайплайн разработки с учетом технологических и концептуальных особенностей каждой части — о нем будет подробно рассказано в этой статье.
Экспорт из Depthkit
Экспорт отснятого контента из depthkit и импорт его в Unity — не такая простая задача. По каждому ребенку у нас имелся отдельный проект в Depthkit, который содержал дубли с его участием. Всего у нас было десять проектов — девять художников и музыкант Петр Термен. Чтобы не запутаться в отснятом материале везде была продумана строгая иерархия.
Итак, начнём с экспорта из Depthkit. После выбора дубля, с которым предстояло работать, создаем для него ч/б маску в After Effects и применяем её как REFINEMENT MASK в Depthkit проекте, это позволит улучшить результат. К слову, на официальном сайте depthkit даже есть инструкция, как сделать такие маски на разном видео-софте. После этого выбираем необходимый диапазон по времени в дубле и экспортируем как видео и как obj-sequence. (подробнее об этом тут)
Подготовка для импорта в Unity
После экспорта у нас получился массив материалов в одной папке: mp4-видео, txt-файл с данными о съемке с камеры глубины, набор obj-моделей и текстур для них, preview-картинка. Чтобы подготовить все к нашей составленной в Unity иерархии, нужно было проделать следующее:
- Сконвертировать mp4-видео в mov с кодеком hap.
- Удалить теперь уже ненужный mp4.
- Удалить preview-картинку.
- Убрать в названиях длинные ненужные суффиксы, появившиеся после экспорта.
- Создать папки с определенными названиями и раскидать все файлы по ним.
Для этих монотонных операций мы написали скрипт на powershell, который по одному нажатию делал всё за нас.
После этого мы помещали вышеперечисленные подготовленные файлы с четкой иерархией в определенную папку Unity-проекта. У нас она называлась DepthkitSources и была записана в git-ignore, то есть система контроля версий git игнорировала данную папку (так как исходники получились уж очень тяжеловесными). Это было необходимо сделать в целях оптимизации ресурсов. В противном случае, притягивание каждого нового изменения занимало бы много времени и места на диске. К тому же, внутри git-системы очень тяжело корректно работать с тяжеловесными проектами. Unity проект весил в итоге свыше 100 Гб.
Импорт и настройка в Unity
Ключевой фигурой в графике проекта являлось volumetric-видео (видео глубины) с наложенными на него эффектами. Именно для этого мы использовали 3D-движок Unity и инструмент в нём - VFX Graph. Для связки volumetric-видео с VFX Graph мы опирались на готовую механику от Kejiro.
Суть её заключается в том, что мы имеем два компонента в сцене — Hap Player и Converter. Первый воспроизводит указанное видео — то, что мы экспортировали из Depthkit, и конвертировали в mov c кодеком hap. Второй компонент — Converter. Ссылаясь на указанный HapPlayer, он берёт изображение цвета и глубины из текущего кадра в видео, которое мы указали в HapPlayer. Эти изображения он сохраняет в заранее созданные Render-текстуры в Unity. И каждый эффект для volumetric, созданный в VFX Graph, основывается на этих двух текстурах. То есть первая render-текстура Depth дает информацию частицам в VFX Graph о их положении в пространстве, а вторая текстура Color — какой цвет будет иметь частица.
И важный момент, что в расчет входит Metadata — информация из текстового файла с данными о записи, который мы получили при экспорте из Depthkit. На деле это что-то вроде json-файла со множеством параметров, которые необходимо учесть, дабы корректно интерпретировать информацию о глубине.
Таким образом, нам нужно было подготовить универсальную для наших нужд сцену, чтобы использовать её как шаблон. Такая сцена должна содержать вышеперечисленные настроенные компоненты со всеми ссылками. Но помимо того, что нам нужно было просто дублировать подготовленный шаблон, также был необходимо подготовить сцену для конкретного дубля, с которым мы хотели работать. В это входило: прописать путь к mov-видеофайлу в HapPlayer, дублировать шаблон метадаты и парсить в него текстовый файл с параметрами, получаемый при экспорте из depthkit, а после указать эту новую metadata в компоненте Converter.
Особенно важно было соблюдать иерархию папок для дублей, сцен, метадат и остальных сопутствующих файлов для контента.
Поэтому здесь точно так же прибегли к автоматизации всех повторяющихся процессов. В конечном итоге лишь по нажатию кнопки, выбрав папку нужного нам дубля, создавалась соответствующая иерархия в папках, куда помещалась сцена со всеми настройками и заданными параметрами под данный дубль.
Volumetric-проигрыватель
Большая сложность предстояла с реализацией имитации VR-рисования детей. Для удобства анимации был разработан собственный инструмент, в котором мы реализовали внутри редактора Unity основные функции проигрывателя. Поначалу это был проигрыватель obj-секвенций, тем самым удобно было подстраивать анимации под детей в volumetric. При этом, к полю экспериментов с визуалом добавляется меш obj-секвенций, на который можно писать шейдеры и комбинировать с частицами. Так же мы связали наш проигрыватель и HapPlayer в одном таймлайне, что добавило удобства и облегчило создание контента.
Дополнительно была реализована работа с аудио-дорожками внутри нашего проигрывателя, что изначально отсутствует в Unity. Это позволило сразу подстраивать происходящее под композицию будущего видеоролика.
Данный инструмент сыграл важную роль в Main-части грядущего видеоролика.
Задаем траектории
Еще один вспомогательный инструмент, реализованный в процессе — это работа с безье кривыми (Bezier curves). Он обеспечивал различные анимации, где требовалось задавать определенную траекторию, например пролет камеры. В большей степени это было необходимо для траекторий, так называемых в Unity трейлов (Trail), и их анимации. Забегая вперед, это был один из способов имитации VR-рисования.
Переход
Ключевым моментом в разработке стал переход из Intro-части в Main часть — из физического в цифровой мир, сопровождающийся удивительной композицией на терменвоксе. Помимо того, что переход отражает часть концепции проекта, именно на нём была первая проба пера всех предстоящих механик: начальный эффект “оцифровки” детей, volumetric-эффект, связь с Терменом и его музыкой, аудио-визуализация, имитация VR-рисования.
И главная техническая особенность, помимо volumetric-эффектов, была в том, что девочка должна была проводить линию, на которой визуализировался аудио-спектр в момент начала музыки терменвокса. Безье кривыми была задана траектория для построения линии, с помощью volumetric-проигрывателя это было синхронизировано и анимировано под движения девочки.
А визуализация аудио-спектра реализовывалась кодом через еще одну дополнительную логику. Вкратце, в скрипте создавался массив заданной размерности. И в методе Update() (то есть каждый кадр) в данный массив записывалась спектр-дата момента аудио-трека, проигрываемого в компоненте AudioSource. Делалось это с помощью метода GetSpectrumData(float[] samples, int channel, FFT window) класса AudioSource. После чего это интерпретировалось в численные значения, как смещение по вертикали для каждой точки построенной нами кривой.
...private void Update()
{ _audioSrc.GetSpectrumData(this.samples,0,FFTWindow.BlackmanHarris); for(int i = 0; i < _samples.Length; i++) { _samplePos.Set( BezierCurve((float)i / _samples.Length).x, BezierCurve((float)i / _samples.Length).y +
Mathf.Clamp(_samples[i] * (50 + i * i), 0, 50) * _power, BezierCurve((float)i / _samples.Length).z
);
_lineRenderer.SetPosition(i, _samplePos); }}...
Имитация VR-рисования в Unity
Всего у нас было 4 разных способа имитации VR-рисования детей:
1. Trail Renderer
Это встроенный в Unity компонент. Служит для создания шлейфов за движущимися в сцене объектами. Выставив в данном компоненте параметр Time (время жизни трейла) на какое-нибудь высокое значение мы получали эффект, схожий с рисованием в TiltBrush. Помимо этого, на трейл можно было навесить материал кисти из TiltBrush. Мы брали объект, применяли к нему данный компонент, и анимировали его вдоль безье кривых (траекторий), которые мы строили на основе мазков импортированных TiltBrush-моделей, сопоставляя это с движениями детей.
2. Texture Offset
Некоторые кисти в TiliBrush устроены следующим образом. Если 3D-мазком TiltBrush-кисти называть то, что получается с момента, как была зажата кнопка, до момента, когда она была отпущена, то во многих кистях один мазок имеет такую развертку, что вся его длина раскладывается вдоль всего UV-пространства, то есть от начала (0) до конца (1) по U-координате.
Таким образом мы можем смещать текстуру на UV, и тем самым перемещать её равномерно вдоль всей длины 3D-мазка.
И если это анимировать, синхронно с движениями детей, то получается результат, похожий на очень достоверную имитацию процесса рисования в Tiltbrush.
3. Object Scale
В некоторых случаях мы вычленяли определенные 3D-мазки и соединяли их в один объект. Затем анимировали их размер. Но такое подходило для случаев с прямыми линиями без изгибов, либо же для совокупности мазков - таких, как огненные глаза.
4. Shaders
И было два частных случая, под которые были написаны специальные шейдеры.
- В первом случае шейдер был для проявления погибших душ, который основывался на вертикальном градиенте с прозрачностью. Это комбинировалось с анимацией свечения и tilling текстуры мазков.
- Во втором случае реализовывалось проявление большого количества мазков по кругу. Чтобы не вычленять их вручную в 3D редакторе в отдельный объект и анимировать, было решено всё сделать одним шейдером. В шейдере строятся две сферические маски, одна больше, другая меньше. Вычитая меньшую из большей мы получаем некоторую область. Именно ту, по которой будет проявляется нарисованный слой. Так как мальчик рисовал по кругу, нужно было добавить еще радиальное проявление через арктангенс.
Из Tilt Brush в Unity
VR-рисунки из Tilt Brush являлись важной частью повествования. Особенно в World-части клипа. Трудность интеграции их в Unity заключалась в том, что мы работали на HDRP, и большая часть шейдеров кистей из Tilt Brush SDK не воспроизводилась корректно.
Некоторые из нерабочих кистей мы заменяли на Lit/Unlit встроенные Unity -шейдера. Но не всегда такое получалось. Например, были кисти по типу бегающий неон — в таких ситуациях приходилось реализовывать самим данную механику в шейдерах.
Заключение
Это был интересный опыт — создавать подобного рода VFX и пост-продакшн прямо в 3D-движке в real-time.
- Помимо того, что все эффекты у нас были real-time на Unity, так и весь композинг, цветокоррекцию для удобства мы делали там же.
- Также присутствовал непривычный момент в разработке. Обычно финальный результат - это build приложения, в нашем случае это было видео. Для этого мы использовали Unity Recorder.
Конечно же, были и неудобства в связи с тем, что движок больше заточен под другие задачи. Но в некоторых ситуациях движок, как некая песочница, позволяет соорудить всё что угодно под свои нужды, если подойти к этому с умом, вооружившись кодом.