Привет, Хабр! Я Михаил Солуянов, ведущий разработчик в МТС Авто — занимаюсь генерацией синтетических изображений для обучения нейросетей. В рабочее время я Unity-разработчик, а в свободное — инди-аниматор мультфильмов. Сегодня расскажу о том, как сделать складки на одежде без симуляции ткани в Blender.
В 2020 году я сделал мультфильм «Мышиный Новый год» (ru, en), который попал на фестиваль Giffoni-50 — один из самых крутых фестивалей, специализирующихся на детской анимации. В мультфильме у меня были герои — антропоморфные мыши в миниатюрных одеждах. И мне захотелось добавить им складки на одежде, которые правдоподобно реагировали бы на их движения. Расскажу о том, как использовал Tension Map и почему перешёл в итоге на Geometry Nodes. Поехали!
Мышонок со складками на кофте
В первую очередь я подумал о физической симуляции ткани. Но у этого способа есть несколько минусов:
Ресурсоёмко и долго. На просчёт физических симуляций в каждом кадре для всех героев нужно время. Как и на настройку, подбор параметров веса, силы трения, сопротивления к сгибу и прочих нюансов. Это не то, чем ты располагаешь, делая мультфильм фактически в одиночку. Иначе говоря, если вы не можете выделить отдельного человека из команды на эту задачу, ничего не получится.
К тому же физическая симуляция ещё и плохо контролируется. Я хотел получить то, что часто называют artistic control — возможность настроить именно так, как ты хочешь, а не как получилось. Чтобы складка шла именно так, как я хочу, а не иначе, а результат был похож на референс.
Это подводит нас к другому вопросу: складки в основном не гуляют по одежде (кроме, разве что, очень объёмной одежды, длинных юбок и платьев). У ткани есть «эффект памяти»: она запоминает, каким образом согнулась, и в следующий раз загнётся так же, пока её не разгладишь утюгом. Некоторые складки образуются на участках сгибов, другие — при растяжении, натяжении ткани. И как их имитировать?
В графике давно используется способ компенсации формы путём добавления корректирующих ключей формы (Corrective Shape Keys).
Работает это так: мы создаём дополнительные ключи формы (Shape keys/Blend Shapes). Они определяют форму объекта с возможностью интерполяции между разными состояниями.
Далее двигаем форму модели как нам нужно, например, образуя складки отдельно для каждого сустава. Затем мы ставим зависимость каждого ключа формы от положения, поворота определённых костей. Согнули руку в локте — соответствующий ключ формы со складками на руке стал более видимым.
Этот метод хоть и даёт полный artistic control, но требует много времени и труда. Да, можно сделать это для одного персонажа, но для нескольких? Большой команде этот метод подойдёт, а для персонального проекта я искал более простое и быстрое решение.
Поиск простого решения для аниматоров-одиночек
Это привело меня к аддону Tension map. Есть небольшое видео, объясняющее принцип его работы.
Суть такова: при деформации объекта изменяются размеры его составных частей — рёбер, полигонов. Скрипт сохраняет размеры до и после деформации, сравнивает изменения в размере. Далее мы берём эти данные и используем для визуализации складок. Чем сильнее уменьшается размер, тем заметнее складка. Но самое главное — мы можем нарисовать складки для всей модели целиком, а не для каждого участка отдельно. Используя данные от аддона, мы из целой карты складок активируем только те участки, которые нам нужны.
Теперь о самой «карте складок». Есть несколько способов имитации неровности поверхности:
Карта нормалей. Этот способ не меняет геометрию объекта, а лишь имитирует неровности. Эффект пропадает, если неровности слишком большие или должны влиять на контур объекта. Поэтому его можно использовать для небольших складок типа морщин на лице, но не для одежды, тем более такой миниатюрной, как у моих персонажей.
Карта высот. На уровне модификатора или шейдера поверхность геометрии отклоняется, формируя настоящие складки. Для этого способа нужна более детализированная модель, ведь чем больше точек, тем детальнее складки.
Итак, я стал делать складки с помощью карты высот. И, казалось бы, решение найдено. Но до финального рендера дело так и не дошло.
Всё сложнее, чем кажется
Почему? На то есть несколько причин:
Tension map — это аддон, написанный на Python. И если персонаж подключён в сцену из внешнего файла, а не скопирован напрямую, то код аддона просто отказывался работать в этой сцене.
Но почему нельзя просто копировать персонажа из сцены в сцену? В процессе создания мультфильма персонаж может дорабатываться. Нужно, чтобы изменения были учтены во всех сценах, где этот персонаж используется. Поэтому он подключён из одного внешнего файла как библиотека, а все изменения, связанные с ним, отображаются во всех сценах.
Рендер-фермы не любят аддоны. Если анимация зависит от включённых аддонов, то не все рендер-фермы позволят вам установить и включить нужный аддон. А я рендерил именно на ферме, поскольку своих вычислительных мощностей не хватало.
Ещё один минус — нужно «запечь», зафиксировать первоначальный размер полигонов. Если в персонажа вносятся какие-либо правки на уровне полигонов, нужно не забыть заново «запечь» данные для Tension map.
В результате аддон я так и не задействовал, просто сделал складки видимыми всегда. Это выглядело не сильно плохо, на мешковатых фигурках смотрелось приемлемо.
Как релиз Geometry Nodes вернул к идее со складками
Но вот с появлением Geometry Nodes я вернулся к мысли сделать складки в своём следующем мультфильме. Вместо медленного и неудобного в использовании аддона у нас появился модификатор Geometry Nodes, который:
быстрый, т. к. не использует Python
работает во внешней сцене, т. к. оформлен в виде модификатора. Модификаторы не подключаются извне, они — часть программы, поэтому работают быстрее и надёжнее
Деформация геометрии по текстуре с помощью GN тоже не вызывала проблем, тут всё понятно. Осталось решить только один вопрос — как получить карту натяжения
Как получить карту натяжения
Есть несколько вариантов, сейчас расскажу о них.
Первый способ — использование копии модели без деформации, как это предлагают Blender Studio. То есть мы храним где-то в скрытом виде копию объекта и сравниваем размеры рёбер с деформированной геометрией.
Попадался мне и другой способ: в его основе карта «запекалась» в текстуру, из которой в дальнейшем брались данные.
Оба варианта не особо удобны — надо что-то «запекать» или держать на скрытом слое. Мне же хотелось, чтобы всё было легко — включил и забыл.
Я обратил внимание на атрибут Rest position, который ввели для новой системы анимации волос на Geometry Nodes. Он содержит положение недеформированных точек и используется для перемещения волос нодой Deform curves on surface. Она позволяет волосам «не отлипать» при анимации, так как берёт положение вершин меша до деформации из этого атрибута и перемещает волосы к деформированному мешу.
Используя этот атрибут, мы можем определить, где сетка модели становится плотнее, а где она более разрежена. В этом помогает модификатор Geometry Nodes:
GN определяет расстояние между точками: текущее и первоначальное, записанное в атрибуте Rest Position. Так мы получаем разницу в длинах рёбер.
Затем записываем эту разницу в вершины меша, используя ноду Evaluate on domain.
Результат сохраняем в новый атрибут (я его назвал FaceArea), который будет отвечать за размер складок. На скриншоте ниже можно посмотреть результат вычислений: синим обозначены зоны, где меш сжат, красным — где растянут.
Я не деформирую точки сразу по двум причинам: во-первых, я стараюсь ограничивать группу нодов по функциональности, чтобы он не перерастал в огромную кашу, в которой будет сложно потом разобраться. Вторая причина — я использую стандартный модификатор subdivision subsurf, который разбивает меш на мелкие элементы и делает его более детальным.
На следующем скрине — сетап (node tree) для добавления складок на поверхность модели. Я использую ранее сохранённый атрибут FaceArea и специально созданный атрибут Folds, которым я могу дополнительно контролировать степень видимости складок. Полученное значение перемещает вершины меша в зависимости от нарисованной текстуры складок.
Стоит заметить, не мне первому пришла в голову идея использовать ноды для создания такого типа складок. Вот, например, статья Blender Studio о создании морщин описанным способом.
Мне морщины не нужны, поскольку мои герои покрыты мехом и перьями. Поэтому в сетапе есть некоторые отличия:
Направление сжатия. Это важно для морщин, например, на лбу. Я им пренебрёг. Потому как в принципе на одежде это не наблюдается.
Используется копия объекта вместо атрибута Rest Position. Возможно, чтобы получить данные по рёбрам или просто более детальную карту после подразделения. Вероятно, потому, что этот сетап делался до создания атрибута Rest Position — точно сказать не могу.
Хорош ли этот сетап? Мне кажется, для моих целей его достаточно. Да, конечно, он не подойдёт для имитации лёгкой одежды и юбок, но, как и другие методы, его нужно применять там, где он уместен.
Готов ответить на вопросы в комментариях, а также жду вас в моём сообществе!