Липкое это дело, товарищи!

Меня зовут Загоруйко Александр.
В данный момент я работаю сетапером и разработчиком скриптов в воронежской студии Б.С.Т.
Более подробно я о себе рассказываю на сайте alexanderzagoruyko.ru.

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

Итак, тема данного урока - липкие губ и "липкий" рот. Под липким ртом подразумевается деформация рта с сомкнутыми губами.
Решение данной проблемы мы будем рассматривать на примере слегка доработанного бесплатного рига AndyRig.
Почему доработанного? Объясню далее.

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

Прежде чем приступать к самой задаче, определимся с входными данными. Что нам нужно?
Базовый меш (меш = геометрия)
Бленды, управляющие ртом.
В нашем примере у нас есть еще skinCluster, управляющий базовым мешем.

Взгляните на рисунок. Выделена базовая сетка, справа показаны инпуты (скин, бленды и твик).



Суть описываемого метода stickyLips / stickyMouth (липкие губы, липкий рот) сводится к созданию управляемых wire-кривых на каждую губу. В идеале, нам необходимо, чтобы все деформации на рот осуществлялись через узел blendShape, а не костями в базовом скин-кластере. У AndyRig, к сожалению, это не так (кость из базового скина управляет открытием рта, а все остальные деформации лица осуществляются через blendShape) и чтобы избежать циклов пришлось кое-как его переделать (я создал дополнительный меш andy_FinalMesh и перенес на него блендом andy_Mesh).

Итак, шаг первый - создание кривой для каждой губы. Кривые должны быть созданы на геометрии с блендами.
В нашем случае лучший способ создать их - выделить ребра верхней и нижней губы и щелкнуть modify -> Convert -> Polygon Edges to Curve (в Maya 2011). Кривую сделайте линейной (degree=1).



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

На рисунке выделена кривая верхней губы. Соответственно, кривая для нижней губы делается из тех ребер, что остались не выделенными в лупе. Среднее сечение ограничивает кривые.



Теперь необходимо сонаправить обе кривые и привести их к одной топологии.
Делается это следующим образом: выделите обе кривые и посмотрите где у них начинаются первые главные вершины (они изображены квадратиками). Если направления совпадают - кривые имеют вершину c индексом 0 в одной точке, если нет - выполните для одной из кривых команду Edit Curves -> Reverse Curve Direction. Топологии приводятся к одному виду посредством команды Edit Curves -> Rebuild Curve. Кривые делаем
кубическими с количеством разбиений в 6 (number of spans = 6).

На этом этапе мы имеем две топологически одинаковые кривые, деформируемые мешем с блендами.
Порядок вычисления таков: сперва срабатывают бленды, потом перестраивается геометрия в соответствии с ними, далее эта перестроенная геометрия передается деформерам polyEdgeToCurve, которые строят кривые по ребрам, а в конце rebuildCurve придает кривым нужную топологию.

blends -> mesh -> polyEdgeToCurve -> rebuildCurve -> curve (полученная кривая на губы)

Теперь нам нужно сделать среднюю кривую, к которой будут сводиться обе губы при слипании.
Это можно сделать как нодой avgCurves, так и обычными блендшейпами. Воспользуемся вторым способом.
Делаем дубликат одной из кривых (например, upLip_curve), переименовываем его в avg_curve и кидаем на него блендшейп от кривых upLip_curve и downLip_curve.
Ставим атрибуты upLip_curve и downLip_curve в 0.5 как на рисунке. Выделенная кривая и есть avg_curve.



Три созданные нами кривые - постоянные и управляющие. Создадим кривые для каждой губы, которые будут использоваться деформатором wire.
Сделаем дубликат одной из кривых (без разницы какой, ведь они топологически идентичны), переименуем его в upLip_wire_curve.
Эта кривая должна "бегать" между upLip_curve и avg_curve.
Если функция "липкие губы" включена, то кривая двигается за avg_curve, если нет - за upLip_curve.

Соединим upLip_curve.local с upLip_wire_curve.create. Теперь деформации upLip_curve передаются на кривую upLip_wire_curve.
Добавим avg_curve как блендшейп на upLip_wire_curve. Что это даст? Меняя атрибут avg_curve на блендшейпе мы можем приводить кривую в два нужных нам состояния: слипание - 1, размыкание - 0.

upLip_curve -> blendShape (avg_curve) -> upLip_wire_curve. История wire-кривой на верхнюю губу.

Проделайте то же самое для кривой нижней губы. Должна получится еще одна кривая downLip_wire_curve, управляемая downLip_curve ( по коннекту .local -> .create ) и блендшейпом avg_curve.



Удивлю вас, сказав, что для stickyMouth (липкого рта) практически все готово! Осталось назначить wire, раскрасить на нем веса и сделать парочку корректирующих блендшейпов. Если вы делаете липкий рот, то атрибут avg_curve на блендшейпах должен управляться каким-нибудь атрибутом, включающим/отключающим stickyMouth (1 - привести кривые к середине, 0 - к губе ). Для липких губ установите avg_curve в 1 на двух блендшейпах - включение/выключение здесь будет осуществляться путем редактирования весов деформера для каждой вершины кривой.

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

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



У обычных блендшейпов есть одно замечательное свойство: если удалить исходный меш, то деформер использует сохраненную копию исходного меша в качестве смещения! Иначе говоря, бленшейп деформер не перезаписывает деформации объекта как обычно, а добавляет смещение к тому, что есть по истории.
Чтобы было понятнее, попробуйте сделать кубик, прискиньте его к какой-нибудь кости, сделайте дубликат кубика и сдвиньте пару вершин.
Далее добавьте второй кубик как блендшейп к первому, включите его и поместите созданный блендшейп в самый верх по порядку деформации (или создайте блендшейп с deformation order = default). Теперь скин не работает (он перезаписывается блендшейпом).
А теперь удалите второй кубик. Магия?

Приведем две управляющие кривые к средней кривой. Сделайте дубликат avg_curve и назовите полученную кривую middle_curve.
Добавьте middle_curve как блендшейп на upLip_curve и на downLip_curve. Включите атрибут middle_curve на созданных блендшейпах. Удалите middle_curve (я обычно делаю дубликат этой кривой для сохранности).


Пришла пора wire-деформеров. Их нужно добавить так, чтобы они рассчитывались после блендов, но до скина - так скорость просчета будет выше.
В любом случае wire должен срабатывать после открытия рта, иначе ему нечего будет корректировать (смыкать губы). В моем случае я кидаю два деформера upLip_wire и downLip_wire на меш andy_FinalMesh. Если бы я кинул деформер на меш, откуда рассчитываются кривые на губы (andy_Mesh), то неизбежно получил бы цикл - кривые управляются мешем и в то же время влияют на него. Здесь нужно быть очень внимательным и заранее предвидеть возможные циклы.
В лучше случае иерархия для данной системы липких губ/рта будет такая:
Бленды с лицевыми морфемами подключаются к stickyMesh, на который вешаются wire-деформеры, а stickyMesh подключается блендом к базовой геометрии basicMesh со скином.



Итак, назначаем два wire-деформера:
1. Меш andy_FinalMesh, кривая upLip_wire_curve (верхняя губа)
2. Меш andy_FinalMesh, кривая downLip_wire_curve (нижняя губа)
У обоих деформеров атрибут rotation ставим в 0.
После срабатывания команды wire для каждой кривой создалась базовая кривая, используемая при просчете деформера (так работают деформеры вроде wrap или wire).
Кинем блендшейпом кривую upLip_curve на базовую wire-кривую верхней губы. У меня эта кривая называется upLip_wire_curveBaseWire.
То же самое нужно сделать для базовой wire-кривой нижней губы. Смысл всего этого состоит в том, что по дефолту базовая wire-кривая и сама кривая должны совпадать, иначе возникнут смещения. Блендшейп можно не добавлять в том случае, если у вас блендшейпы на рот находятся на геометрии, которая не перемещается и не деформируется никакими другими способами. У AndyRig, как было сказано, бленды и скин связаны в одном меше (рот открывается костью из базового скин-кластера). Так лучше не делать.

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



Итак, основную работу мы уже сделали. Если вы делаете stickyMouth, то все уже почти готово - осталось навесить все это дело на атрибут и радоваться. Создайте локатор, добавьте к нему атрибут stickyMouth и свяжите его с включением блендшейпов на усреднение wire-кривых (avg_curve атрибут, помните?).

Для stickyLips придумаем кое-что другое. Создадим локатор locator1 и добавим к нему атрибут stickyLips типа float от 0 до 1.
При
stickyLips = 0, наша система не работает.
stickyLips = 1, губы полностью сомкнуты..
Все промежуточные значения задают плавный переход между этими двумя состояниями. Стоит сказать, что губы начинают разлипаться начиная с середины и заканчивая уголками рта. Хотелось бы, чтобы атрибут stickyLips учитывал данное условие. Я предлагаю сделать это с помощью ноды setRange.
Создайте ноду setRange командой createNode setRange. Переименуйте ее в sticky_1_setRange. Данный узел будет отвечать за включение/отключение уголков рта.
Кто не знает, setRange переводит один диапазон в другой. Скажем, диапазон 0 до 1 привести в 0 до 180.

Напишем небольшой MEL-скрипт, выполняющие нужные нам действия ( скриптом некоторые вещи делать более наглядно ).

connectAttr locator1.stickyLips sticky_1_setRange.valueX; // соединим атрибут включения липких губ и входной параметр ноды
setaAttr sticky_1_setRange.minX 0; // новый диапазон
setaAttr sticky_1_setRange.maxX 1; // ---
setaAttr sticky_1_setRange.oldMinX 0; // старый диапазон
setaAttr sticky_1_setRange.oldMaxX 0.25; // --

Что мы получили? Смотрите, наш атрибут stickyLips изменяется от 0 до 1. Мы используем первые значения этого диапазона от 0 до 0.25 для слипания уголков рта. То есть при значении 0, нода выдает 0, а при значении входящего атрибута в 0.25, на выходе мы получаем 1. Понятно? А почему 0.25? Дело в том, что кривая с numberOfSpans = 6 имеет 4 вершины, которыми нам нужно управлять. Стало быть 1/4 = 0.25. Дальше мы создадим еще 3 setRange для разных частей губ.
Теперь нам нужно соединить выходной атрибут нашего setRange с весами на блендшейпе, чтобы иметь возможность включать/отключать его влияние на выбранные
вершины.
Веса деформера blendShape хранятся в атрибутах, но по умолчанию к ним нет доступа - они скрыты в Connection Editor. Чтобы их отобразить нам нужно выделить все вершины у кривой и открыть Component Editor на вкладке BlendShape deformers. Необходимо сперва сбросить веса в 0 у выбранного шейпа, а потом установить их в 1, тогда атрибуты появятся в списке доступных атрибутов в Connection Editor. Если вы не поняли, что нужно сделать не отчаивайтесь - к атрибутам можно получить доступ с помощью MEL-команд setAttr/connectAttr.



Открывайте Connection Editor. Загрузите с одной стороны blendShape на кривой upLip_wire_curve, а с другой - ноду setRange.
Нужно соединить sticky_1_setRange.outValueX с индексом вершины в массиве весов блендшейпа (вот как сложно получается).



Скриптом это делается так (даже если атрибуты не видны в Connection Editor):
connectAttr sticky_1_setRange.outValueX blendShape3.inputTarget[0].inputTargetGroup[0].targetWeights[индекс_вершины];
Уголки рта - это предпоследние вершины с обеих сторон кривой (индексы 1 и 7).



Теперь при изменении атрибута stickyLips от 0 до 0.25 у нас полностью слипаются уголки рта. Что, собственно, нам и нужно.
Далее по аналогии делаем еще 3 setRange. Называем их sticky_2_setRange, sticky_3_setRange, sticky_4_setRange (средняя вершина на губах).
Уловили хоть немного что мы делали с первым setRange? Объясняю по пунктам:
1. Коннект атрибута stickyLips с входным атрибутом valueX на каждом setRange.
2. Новый диапазон (minX,maxX) у всех setRange будет 0, 1.
3. Старый дипазаон (oldMinX,oldMaxX) будет таким:
sticky_2_setRange = 0,25, 0.5. Включается после уголков.
sticky_3_setRange = 0,5, 0.75. Затем это включается.
sticky_4_setRange = 0,75, 1. И, наконец, середина.
4. Коннект выходного атрибута каждого setRange с весами нужных вершин в блендшейпе.
На рисунке показаны какие вершины принадлежат какому setRange (по номеру).
Таким образом настраивается последовательное слипание каждой части губ.



Вот примерный результат моего труда. Не надо, бывает и хуже :-).



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

Мне бы хотелось, чтобы вы вынесли из этого урока не столько саму систему для создания липких губ, сколько подход к риггингу, ведь задач существует великое множество, а хороших уроков всего два :-)

804 0 850 17
7
2011-04-15
отлично. покопаю как время будет.
2011-04-15
очень интересно и грамотно
2011-04-15
Прекрасный урок и гениальный автор!!! [smile=13]
2011-04-15
[quote=Wiellarifayeretielliy v] :-) да, Володя, это так. [/quote]
2011-04-15
Наконец-то что-то по майке появилось! Интересно, надо попрактиковаться))
2013-03-07
как где соединить у кривых .local -> .create ?
2013-03-07
[quote=syracusy] как где соединить у кривых .local -> .create ? [/quote] Шейп кривой смотри (curveShape)
RENDER.RU