Анимация мимики по принципу "Blended Morph"
Об авторе:
Зовут меня Поклонов Максим, родился живу и тружусь в городе-герое Усть-Каменогорске, том, что в Казахстане. Графикой занимаюсь где-то года с 99, естественно персонажкой занялся много позже, пройдя множество трудностей и поборов кучу глюков, наработал кладезь ценного материала, коим спешу поделиться с вами.
Введение
Привет, друзья. Это снова я, и я хочу порадовать вас, очередным уроком. Я расскажу о довольно мало известном, но весьма интересном приеме анимации мимики при помощи морфинга. В этом уроке я буду использовать уже известного персонажа для мульт заставок игры Air Xonix, изготовленного мной для компании Axysoft. Правда, для демонстрации возможностей данного приема анимации, лицо персонажа пришлось немного модифицировать.
Идея данного способа, скрипты и некоторые специальные наработки были почерпнуты мной в четвертом DVD Пола Нила (Paul Neale) «Facial Rigging Techniques». Эта идея была полностью переработана и настроена под меня, так что на DVD Пола Нила все будет по другому. В этом уроке я покажу только сам способ анимации, ну а если вам этого будет мало, и вы захотите научиться сохранять мимические позы, или научиться делать костную анимацию на профессиональном уровне, и так далее, то ищите DVD Пола Нила (Paul Neale) «Facial Rigging Techniques», он будет для вас невероятно полезен.
Любая анимация мимики, это не просто, поэтому приготовьтесь к большому и очень сложному уроку. Наберитесь терпения и будьте крайне внимательны. Поехали.
Сразу определимся со сторонами лево-право. Все что касается лица персонажа, надо понимать с его точки зрения, то есть, что для него лево, то для нас право. Все что не касается непосредственно модели, следует рассматривать с нашей позиции, то есть где лево - там лево, и где право, там соответственно право.
Подготовка модели к анимации
По мере написания урока мне пришлось столкнуться с одной досадной ошибкой. Когда я полностью настроил правую половину лица персонажа, и принялся копировать и зеркалить морф-цели, то выяснилось, что модель содержит скрытые ошибки, не склеенные точки, и даже Pivot немного смещен. В результате мне не удалось правильно отзеркалить цели. И мне пришлось править модель и все начинать по новой. Чтобы избежать подобной ошибки, в самом начале давайте подготовим модель. Для начала преобразуйте ее в Editable Poly (если она таковой не является). Первым делом надо склеить разлепленные точки, их может быть не видно во вьюпорте, но они могут доставить массу неожиданных сюрпризов. Для этого войдите в подобъект «Vertex», выделите все точки модели и примените к ним операцию «Weld». Ее можно вызвать как в панели инструментов Editable Poly, так и Quad Menu (на мышке).
Обратите внимание, что операция «Weld» вызывается нажатием не на саму надпись, а на изображение окошка, рядом с ней. Это дает нам возможность настроить параметры в окошке с настройками.
Включите во вьюпорте отображение сетки, и внимательно смотрите, какие точки склеиваются при увеличении параметра Weld Threshold. Этот параметр должен быть минимальным, чтобы склеить только незаметные для глаза точки. В секции Numbers of Vertices можно видеть, что даже при минимальном значении параметра Weld Threshold количество точек стало существенно меньше. После того как точки склеены, визуально проверяем модель, и исправляем все ошибочки, если найдем.
Затем нужно обнулить Pivot. Для этого выделяем модель головы, заходим в панель Hierarchy, выравниваем Pivot по центру модели (Center to Object) и сбрасываем трансформации вращения (Align to World).
После этого, применяем к модели модификатор Symmetry, делаем Reset XForm (это в панели Utilites) и конвертируем в Editable Poly. Последние операции с Pivot, я заменяю скриптом «Collapse Selected To Poly At Origin» из бесплатного пакета TIM Scripts. Для этого я помещаю модель центром в нулевую точку и применяю скрипт. Он сбрасывает все скрытые трансформации и преобразует модель в чистенький и готовый к анимации Editable Poly и помечает Pivot в кулевую точку, за один клик.
Кости и скин
Способ то конечно морфинговый, да не совсем. Челюсть будет управляться костью. Поэтому начнем работу с создания костей и скина. Вот и делаем. Создаем кости для шеи, головы и челюсти. Челюстную кость линкуем к кости головы. Сустав между шейной и головной костью должен находиться примерно в месте последнего шейного позвонка. Сустав челюсти должен быть в районе уха. В моем случае, сустав челюсти расположен прямо в месте шейного позвонка.
Теперь применяем модификатор «Skin» и скиним нижнюю челюсть. Помещаем в скин три кости, шейную головную и челюстную. Включаем «Edit Envelopes». Чтобы была возможность работать с точками, в настройках модификатора включаем галку «Vertices». Выбираем енвелоп головы и выделяем все точки. Устанавливаем вес точек на 100%, то есть 1.
Чтобы удобнее было работать с нижней челюстью, сделайте анимацию кадров на 5, это позволит открывать челюсть, не выходя из редактирования енвелопов. Теперь выбираем енвелоп челюсти и выделяем точки, которые будем ему назначать. Это удобнее делать выделением типа Lasso. Грубо выделяем точки района нижней челючти. Так же выделяем точки полости рта внизу и устанавливаем вес =1. Теперь точки нижней челюсти назначены на кость.
При выделении мы захватили точки верхней губы, нужно исключить их из выделения. Для этого выделяем точку посередине верхней губы и увеличиваем область выделения кнопкой Grow. Устанавливаем вес на 0 и точки возвращаются под влияние головной кости.
(Копка Grow, как и ряд других нововведений, появилась в скине сравнительно недавно, и в более ранних версиях макса ее не было. Чтобы, все же иметь возможность увеличивать и уменьшать выделение точек в скине, можно воспользоваться скриптами из пакета TIM Scripts, в нем есть эти инструменты).
А теперь самое интересное. Просто включаем инструмент «Paint Weights» и смягчаем вес точек вокруг рта. (Обратите внимание, галка «Paint Blend Weights» под кнопкой «Paint Weights» должна быть включена).
С косметическими процедурами закончили, теперь, нужно проделать то же самое в полости рта.
После этакого «лифтинга» наш «пациент» выглядит уже намного лучше. Если есть необходимость, можно поднастроить некоторые точки индивидуально. Не забудьте удалить ключевые кадры с кости, когда мы привяжем ее к управляющему объекту, они будут только мешать. Все, с челюстью закончили.
Управляющий объект
Для управления мимикой будем использовать некую внешнюю контрольную панель состоящую из отдельных управляющих объектов. Управляющий объект представляет собой сплайновые объекты, прямоугольник с кругом внутри.
Перемещения круга ограничены стенками прямоугольника. Сделано это при помощи скрипт контроллеров на позиции круга. Управляющий объект, (я буду называть его «Контрол») взят как есть, с DVD Пола Нила (Paul Neale) «Facial Rigging Techniques». Мы не будем строить его, а просто воспользуемся готовым скриптом Пола:
rec=Rectangle length:10 width:10 name:(uniqueName "FaceControl") wireColor:[0,0,200] displayRenderMesh:false
cir=circle radius:1 name:(uniqueName "FaceControlHandle") wireColor:[200,0,0] displayRenderMesh:false
cir.parent=rec
setTransformLockFlags cir #{3..9}
conName=text text:"Control" size:2 wireColor:[0,150,0] pos:[0,5.5,0] alignment:2 displayRenderMesh:false
conName.parent=rec
addModifier conName (meshSelect())
setTransformLockFlags conName #{1..9}
rec.rotation.x_rotation=90
listCon=cir.pos.controller=position_list()
scCon=listCon.available.controller=position_script()
def=attributes clamp
(
parameters clampP
(
l type:#float
w type:#float
x type:#float
y type:#float
)
)
custAttributes.add scCon def
scCon.l.controller=rec.length.controller=bezier_float()
scCon.l=10
scCon.w.controller=rec.width.controller=bezier_float()
scCon.w=10
scCon.x.controller=listCon[1].x_position.controller=bezier_float()
scCon.y.controller=listCon[1].y_position.controller=bezier_float()
script="
x=0
y=0
w=this.w/2
l=this.l/2
if this.x>w then (x=-this.x+w;this.x=w)
if this.x<-w then (x=-this.x-w;this.x=-w)
if this.y>l then (y=-this.y+l;this.y=l)
if this.y<-l then (y=-this.y-l;this.y=-l)
[x, y, 0]
"
scCon.script=script
Запустив этот скрипт, вы как раз получите готовый контрол, который будет создан в нулевой точке координат.
Обнуление контроллера челюстной кости
Привязку будем осуществлять через Wire Parameters. Но перед тем как делать привязку, кость нужно сначала подготовить. Дело в том, что при привязке кость может получить существенное смещение (кто пользовался Wire Parameters, поймет, о чем я). Для этого мы применим хитрый прием, и условно назовем его обнуление контролера. Не знаю, получится ли у меня объяснить, но смещение кости при привязке на контрол через Wire Parameters будет происходить потому, что кость, в данное время, имеет свое собственное, определенное значение ориентации, при привязке на контрол, это значение стирается, и кости просто передается новое значение которое дает контрол. Чтобы при привязке смещения не происходило нужно применить List контроллер. List контроллер позволяет добавить в список несколько контроллеров, и каждый последующий контроллер в списке будет потомком по отношению к предыдущему. Получается самая что ни на есть обычная иерархия прямо как у костей. Соответственно на первом контроллере у нас останется текущее положение кости, второй контроллер, по отношению к первому будет иметь нулевые координаты, как потомок, к этому контроллеру мы и будем привязываться. То есть текущее значение ориентации кости у нас ни куда не стирается, а остается на первом в списке контроллере, а влияние контрола будет распространяться только на второй контроллер, и значение первого контроллера будет суммироваться со значением контроллера потомка и таким образом мы избегаем непредсказуемой перемены значения при привязке кости на контрол.
Если не понятно, не страшно, перейдем к практике и просто сделаем все то же самое по шагам. Итак, выделяем челюстную кость, и открываем Curve Editor и на Rotation челюстной кости делаем «Assign Controller» и назначаем контроллер Rotation List.
Так у нас получилось, что на Rotation челюстной кости, назначен List Контроллер. Первым и единственным в списке идет контроллер Euler XYZ. Чтобы добавить в список еще один контроллер, выделяем параметр «Available» и назначаем на него еще один Euler XYZ.
Таким образом у нас в списке получилось два контроллера Euler XYZ. Теперь давайте зайдем в панель Motion и для удобства дадим имена этим контроллерам. Первый назовем Zero, а второй Animation.
Если посмотреть ниже, то можно увидеть как раз то, что я пытался объяснить сначала, то есть родительский контролер (Zero) по оси Z имеет значение -101,79, а потомок (Animation), равен 0.
Теперь, если активировать контроллер Animation и задать на него анимацию, то она просто добавится к той, что «зашита» на первом контроллере. (Кстати таким образом можно делать анимацию для различных объектов как бы по слоям, то есть каждый контроллер в списке может иметь абсолютно свой набор ключей анимации).
Ну все, оставляем активным контроллер Animation и переходим непосредственно к привязке.
Привязка челюстной кости к контролу
Наша задача, теперь заключается в том, чтобы заставить челюстную кость перемещаться под влиянием контрола. При перемещении контрола вверх-вниз, то есть по локальному Y, кость должна вращаться по ее локальному Z. Вот и выделяем контрол, заходим в Wire Parameters и находим там параметр позиции контрола по Y.
И привязываем этот параметр к Z вращению кости, вот того самого второго контроллера-потомка «Animation».
При прямой привязке, кость реагирует на перемещения контрола слишком интенсивно, поэтому привязку осуществляем через понижающий множитель, то есть умножаем параметр Y_Position на 0.1
Теперь не закрывая этого окна привязываем X_Position контрола к Y_Rotation кости с тем же множителем.
Нижняя челюсть готова.
Создание и привязка морф-целей для губ
Теперь можно смело приступать к созданию морф-целей. На данном этапе нам нужно создать четыре цели, для управления губами правой половины лица. Для этого просто копируем голову, удаляем с нее все модификаторы. Понадобятся цели с губами «в трубочку», с сильно растянутыми губами, губами выгнутыми вниз и губами выгнутыми вверх.
Первые цели готовы, можно приступать непосредственно к морфингу. Итак, добавляем модификатор Morpher. Но Morpher должен быть ниже модификатора Skin в стеке, то есть, добавляем его сразу после Editable Poly. И тут же заводим туда первые морф-цели.
Дам маленький совет, при настройке морф-целей, нужно постоянно перегружать их в морфере, чтобы увидеть изменения. Чтобы миновать это неудобство, включите галку Automatically Reload Targets. Таким образом в риалтайме вы сможете видеть на модели все изменения производимые с морф-целью.
Сразу же копируем и располагаем контролы возле модели и даем им удобные названия.
Немножко поясню, как работает контрол. В дефолтном положении контрол находится в нулевой точке по локальной оси. Если его смещать вверх, (по Y) то он уйдет в положительную сторону, если вниз, то в отрицательную. Так же, если смещать контрол вправо (по X), он уйдет в полюс, влево, в минус. Соответственно у нас есть четыре морф-цели, которые будут привязываться ко всем трансформациям контрола.
Первым делом привяжем ту цель, которая сводит губы «в трубочку». Выделяем контрол для правой половины губ и идем в Wire Paramrters и находим параметр X позиции.
Тащим связь на модель и привязываемся к первой морф-цели.
Влияние контрола получается очень слабым, поэтому параметр X_Position подключаем через множитель. В данном случае этот множитель 20. В принципе могу объяснить почему. Дело в том, что при создании контрола, его размеры указаны так, что сам управляющий объект – круг, можно смещать на 5 единиц от центра в каждую сторону. Пределы работы морф-цели от 0 до 100, оттуда и этот множитель, то есть 5 единиц умножть на множитель 20, получаем как раз 100 единиц на управление морф-целью.
Не закрывая этого окна привязываем остальные цели. На этот же X_Position привяжем цель которая растягивает губы. То же самое, только учтем, что в этом случае контрол смещается в минус, соответственно знак меняется на отрицательный.
Теперь, точно так же привязываем цель с губами согнутыми вниз. Привязку делаем по оси Y в отрицательную сторону.
И цель с губами вверх, в положительную сторону.
С губами закончили, должно получиться что-то вроде этого.
Создание и привязка морф-целей для глаз
Дальше, проделываем, то же самое, что и в прошлый раз. Копируем модель головы, и создаем морф-цели для глаз. Поясню, как работают морфы для глаз. Как работает морфинг? Он перемещает точки модели из положения одной цели, в положение другой цели, но перемещение происходит по прямой линии, это значит, если сделать две цели на открытый глаз и на закрытый, и сделать морфинг, то веко пройдет сквозь глазное яблоко.
Но нам-то нужно чтобы веко закрывалось по окружности, ну или по крайней мере, чтобы оно огибало выпуклый глаз персонажа. Специально для этого сделаем, морф-цель, полузакрытого века, на самой выпуклой точке. А теперь перечислю какие морфы понадобятся (на картинке с низу вверх): полузакрытое веко (для огибания глазного яблока), закрытое веко, сильно открытое веко, закрытое нижнее веко, сильно открытое нижнее веко и еще две дополнительные цели – смещение век вправо и смещение век влево. Эти две дополнительные цели позволят сделать анимацию глаз более живой, используя движение контрола в стороны.
Назначаем цели морферу и начинаем привязывать их к контролу правого глаза. Тут, пожалуйста повнимательнее, из-за огибания веком глаза нам придется поработать с целью полузакрытого века. По ходу повествования, я обращу на это внимание. Итак выделяем глазной контрол и идем в Wire Paramers.
Так же, как и в прошлые разы, привязываем Y_Position контрола к первой цели, с полузакрытым веком.
Эту цель пропишем позже, а пока просто устанавливаем ее на 100% и пишем Y_Position=100
И связываем остальные цели. На Y_Position вторую цель с закрытым веком.
На Y_Position третью цель с сильно открытым веком.
Вот сейчас внимание, займемся первой целью, с полузакрытым веком. Если сейчас подвигать контрол по Y, то видно, что морфинг работает не правильно. Это потому, что мы установили первую морф-цель (с полузакрытым веком) на 100% и она добавляется к остальным целям. Вот и займемся ею. Вернемся к первой цели и пропишем туда вместо «Y_Position=100» такое выраженьице «if Y_Position >= 0 then (100-Y_Position*20) else (100+Y_Position*20)»
Выраженьице это значит, что если положение контрола по Y больше или равно 0, то значение, которое будет передано на морф цель будет 100% минус положение контрола (от 0 до 5) помноженное на коэфициент 20, иначе (если контрол уйдет вниз), к 100% прибавится значение положения контрола (отрицательное естественно, а значит оно отнимется от 100) помноженное на 20. Например при контроле задранном вверх первая цель будет убрана на 0, а выражение можно понять так: «100%-(5*20)» то есть 0. Или же при контроле опущенном вниз первая цель будет так же убрана на 0, а выражение можно понять так: «100%+(-5*20)» то есть опять же 0. Теперь наши цели будут работать правильно, и глаз будет закрываться корректно, не проходя сквозь глазное яблоко.
Все, заводим остальные цели. Нижнее веко, при опускании контрола, поднимается (коэффициент я поставил не 20, а 15, чтобы веко не поднималось слишком сильно):
Нижнее веко сильно открывается (опускается) при поднятии контрола вверх:
Движение век вправо, при движении контрола вправо (в плюс по X)
Движение век влево при движении контрола влево (в минус по X)
Веки готовы.
Создание и привязка морф-целей для бровей
Ну а теперь самый последний и самый сложный этап работы. Брови. Принцип работы бровей будет немного другой, потому что привязка будет не к одной оси, а к двум, поэтому сначала рассмотрим как это работает. При поднятии контрола вверх, поднимается центральная часть брови, при движении поднятого конторла в лево, центральная часть брови опускается, но поднимается левый край брови. При движении поднятого конрола вправо, так же опускаются центр и левый край брови, но поднимается правый край. То же, и при опущенном контроле. Так же, если подвигать в стороны контрол находящийся в нулевой точке, то есть в центре, бровь будет смещаться влево-вправо.
Таким образом, достигается очень гибкое управление бровью.
Теперь рассмотрим один из узлов более детально. Для примера возьмем цель, которая поднимает центр века вверх. Суть данного приема в следующем: при движении контрола (из нулевой точки) вверх, добавляется морф-цель с поднятой серединой века. Но при движении поднятого контрола в стороны, вес этой цели сходит на ноль. Следовательно, каждая морф-цель века реагирует как на положение контрола по Y, так и по X. Для того, чтобы реализовать привязку к двум осям, вместо одной, нам нужно будет немного изменить контроллеры управляющие каждой целью. Сейчас начнем это делать и походу, я поясню, что конкретно нам дает смена контроллера.
Но сначала, надо сделать сами морфы. Нам нужны следующие цели: (с низу вверх) поднятый центр брови, поднятый носовой край брови, поднятый височный край брови, опущенный центр брови, опущенный носовой край брови, опущенный височный край брови, бровь смещенная к носу, бровь смещенная к виску.
Теперь добавляем эти цели в Morpher. Постарайтесь сделать последовательность как у меня, целей много, можно легко запутаться в них. Можно править контроллеры. Как я уже говорил, нам нужно сделать управление одной целью, зависимой от двух параметров, X_Position и Y_Position контрола. Сейчас пока начнем делать, а там я растолкую, что к чему. Итак, выделяем модель головы и идем в Curve Editor и находим там наш Morpher. Выделите все цели бровей, кроме двух последних (бровь влево и бровь вправо) и назначьте им контроллер Float List.
Рассмотрим, что это дает. Как видно на рисунке ниже, у нас в списке есть контроллер Bezier Float. Однако есть и еще один параметр Weight: Bezier Float. Что накое Bezier Float, надеюсь понятно, это собственно контроллер который будет управлять добавлением морф-цели, а вот Weight: Bezier Float ничто иное как вес контроллера в списке. То есть даже если морф-цель в контроллере выведена на 100%, то с помощью веса все равно можно свести ее на нет.
Если все понятно, то поехали делать связи. (Если не понятно, то все равно поехали, по ходу прояснится). Итак, закрываем Curve Editor, выделяем контрол для правой брови, идем в Were Parameters и проделываем все как в прошлые разы, только привязываем Y_position контрола к Bezier Float первой бровной цели (поднятая середина брови).
Привязываем Y_Position к контроллеру Float Wire через множитель, как обычно 20.
А теперь, новый прием, внимание! Тут же, X_Position контрола, привязываем к весу контроллера морф-цели Weight: Float Wire. Выражение для управления весом пмшем:
«if X_Position <=0 then (1+(X_Position*.2)) else (1-(X_Position*.2))»
Это выражение означает, что если контрол по X (по горизронтали) ушел в минусовую сторону, то значение которому будет равен вес контроллера будет 1 (это вес контроллера от 0 до 1) плюс положение контрола по X (может быть от 0 до -5) которое помножено на понижающий коэффициент 0.2, иначе, если контрол ушел в плюс, то значение будет 1 (максимальное значение веса) минус положение контрола по X (возможно от 0 до 5) пониженное через множитель на 0.2 Например если контрол поднять вверх, и сдвинуть его влево (в минус), то морф-цель будет равняться 100%, но вес всего контроллера дудет равен 0, и значит и влияние морф-цели будет равно 0, а выражение следует понимать как «1+(-5*0.2)» в итоге 0. Или если контрол поднять вверх и сдвинуть вправо (в плюс), то морф-цель выведется на 100%, но вес, так же будет 0, а выражение можно понять как «1-(5*0.2)» в итоге опять же 0.
Таким образом, мы привязали управление морф-целью к двум параметрам, к положению контрола по Y и по X.
Выражения для краев бровей, будут немного отличаться, потому, что нам нужно, чтобы край брови поднимался, только при движении контрола в какую-то одну сторону. Поэтому, не закрывая окна привязываем следующую цель, с поднятым краем брови со стороны носа. Выбираем обратно Y_Position, и вяжем его на Float Wire следующей цели с множителем 20.
И так же переходим к X_Position и так же, вяжем его на Weight: Float Wire. Только выражение пишем: «if X_Position <=0 then X_Position=0 else X_Position*.2» Надеюсь, к этому времени вы уже стали великими программерами и выражения больше «разжевывать» не придется.
И опять переходим обратно к Y_Position и вяжем его с Float Wire следующей цели (поднятый височный край брови) с множителем 20.
И опять вяжем X_Position на Weight: Float Wire и выражение пишем: «if X_Position <=0 then -X_Position*0.2 else X_Position=0»
С поднятиями брови закончили.
Опускание брови делается точно так же. Я пошагово объясню что делать но уже обойдемся без картинок.
Итак, разворачиваем в списке следующую цель, опускание центра брови, выбираем Y_Position и вяжем его к Float Wire. Выражение пишем: «-Y_Position*20»
Открываем Weihgts и вяжем X_Position на Weight: Float Wire. Выражение пишем: «if X_Position <=0 then (1+(X_Position*.2)) else (1-(X_Position*.2))»
Цель опускание носового края брови. Выбираем Y_Position и вяжем его к Float Wire. Выражение пишем: «-Y_Position*20»
Открываем Weihgts и вяжем X_Position на Weight: Float Wire. Выражение пишем: «if X_Position <=0 then X_Position=0 else X_Position*0.2»
Цель опускание височного края брови. Выбираем Y_Position и вяжем его к Float Wire. Выражение пишем: «-Y_Position*20»
Открываем Weihgts и вяжем X_Position на Weight: Float Wire. Выражение пишем: «if X_Position <=0 then -X_Position*.2 else X_Position=0»
Готово.
Привязываем оставшиеся две цели. Они смещают бровь влево-вправо. Тут все просто:
Цель смещение брови к носу. Выбираем X_Position и вяжем его к цели. Выражение пишем: «Y_Position*20»
Цель смещение брови к виску. Выбираем X_Position и вяжем его к цели. Выражение пишем: «-Y_Position*20»
Тут можно так же, как и на предыдущих целях, сделать, чтобы от движения по Y, влияние двух последних целей угасало, Но мне это показалось лишним, и я этого делать не стал.
Ура, половину победили! Теперь займемся второй половиной.
Зеркальное копирование морф целей
Ну, что ж, самое трудное позади. Половина лица у нас готова и правильно работает. Осталось сделать все то же самое на другую половину. Для начала нужно подготовить морф-цели, для левой половины лица, но тут есть одно «но». Дело в том, что если просто отзеркалить модель, то ничего не изменится, потому, что точки модели так же зеркалятся, сохраняя значение на позиции. То есть все точки имеют то же имя, и ту же позицию только в зеркальном отображении. Поэтому обычное зеркальное копирование тут не получится. Вот для этого мы и подготавливали модель в самом начале урока.
Для зеркального копирования морф целей, возьмем еще один скрипт Пола Нила:
rightAr=#()
leftAr=#()
centerAr=#()
tempAr=#()
fn getMirrorData threshold:0.05=
(
rightAr=#()
leftAr=#()
centerAr=#()
tempAr=#()
baseObj=$.baseObject
vertSel=(polyOp.getVertSelection baseObj)as array
for i = 1 to vertSel.count do
(
pos=polyOp.getVert baseObj vertSel[i]
if pos.x < -threshold then
(
append rightAr vertSel[i]
)
if pos.x < threshold and pos.x > -threshold then
(
append centerAr vertSel[i]
)
if pos.x > threshold then
(
append tempAr vertSel[i]
)
)
for i in rightAr do
(
pos=polyOp.getVert baseObj i
found=false
for c = 1 to tempAr.count do
(
cPos=(polyOp.getVert baseObj tempAr[c])*[-1,1,1]
dist=distance cPos pos
if dist< threshold then
(
append leftAr tempAr[c]
deleteItem tempAr c
found=true
exit
)
)
if found==false then append leftAr undefined
)
#(rightAr.count,leftAr.count,centerAr.count)
)
fn mirrorMorph symmetry:false=
(
for i = 1 to rightAr.count do
(
if leftAr[i]!=undefined do
(
Rpos=polyOp.getVert $ rightAr[i]
if symmetry==false then Lpos=polyOp.getVert $ leftAr[i]
polyOp.setVert $ leftAr[i] (((Rpos-$.pos)*[-1,1,1])+$.pos)
if symmetry==false then polyOp.setVert $ rightAr[i] (((Lpos-$.pos)*[-1,1,1])+$.pos)
)
)
if symmetry==false then
(
for i = 1 to centerAr.count do
(
Cpos=polyOp.getVert $ centerAr[i]
polyOp.setVert $ centerAr[i] (((Cpos-$.pos)*[-1,1,1])+$.pos)
)
)else
(
for i = 1 to centerAr.count do
(
Cpos=polyOp.getVert $ centerAr[i]
polyOp.setVert $ centerAr[i] (((Cpos-$.pos)*[0,1,1])+$.pos)
)
)
)
Суть работы скрипта в следующем: сначала мы показываем ему нашу модель (которая должна быть симметричной), и он запоминает ее. Причем запоминает он ее не просто всю, а делит ее пополам (относительно Pivot, примерно как модификатор Symmetry), находит и запоминает противоположные (зеркальные) точки. Для поиска зеркальных точек в самом начале скрипта имеется параметр «threshold:0.01». 0.01 означает, что модель должна быть практически идеально симметричной, в противном случае можно немного увеличить threshold. Потом мы берем любую морф-цель, копируем (обычным способом) и применяем скрипт. Скрипт на основе данных об исходной модели, зеркально перекидывает данные о положении точек с левой половины на правую, а с правой на левую. Таким образом, точки модели остаются в том же порядке, по позиция их зеркально меняется. Вот и получается, что цели зеркалятся корректно.
Теперь объясню, как пользоваться.
- 1. Сохраните этот скрипт как «PEN_mirrorVerts.ms» (на самом деле имя не имеет значения). Потом запустите его, через MaxScript->Run Script.
2. Выделите модель головы, перейдите в стеке модификаторов в самое начало к Editable Poly (модель обязательно должна быть в Editable Poly) и перейдите на уровень Vertex. Выделите точки модели (точки не обязательно выделять все, главное, чтобы были выделены точки затронутые морфингом). В данном случае лучше выделить все точки.
3. Откройте MaxScript Listener, (MaxScript-> MaxScript Listener, по умолчанию F11) и напишите в нем команду которая запускает функцию запоминающую модель: «getMirrorData()» (без кавычек), нажмите ввод. Выделенные точки модели будут запомнены. Если все верно, то Listener покажет что-то типа этого: «#(384, 384, 78)». Это значит, что 384 точек справа соответствует 384 точкам слева, и 78 точек по центру. Выйдете из подобъекта Vertex.
4. Теперь возьмите любую морф-цель. Скопируйте ее и разместите рядышком. Она (цель для отзеркаливания) должна выть выделена. Идем в Listener и пишем «mirrorMorph()»(без кавычек), ввод. Оп, модель отзеркалилась!
5. Если что-то пошло не так и скрипт не сработал, значит либо что-то не так с моделью, либо модель не очень симметричная. Попробуйте запустить скрипт немного увеличив параметр threshold и проделайте все сначала.
Теперь два слова о том, как я пользуюсь этим скриптом. Я сохранил скрипт с именем «PEN_mirrorVerts.ms» и разместил его в 3dsmax root/scripts/startup, написал макроскрипт под названием «PEN_mirrorVerts.mcr»:
macroScript GetMirrorData
buttontext:"GetMirrorData"
category:"Morph Mirror Targets"
internalCategory:"Morph Mirror Targets"
tooltip:"GetMirrorData"
(
getMirrorData()
)
macroScript MirrorMorph
buttontext:"MirrorMorph"
category:"Morph Mirror Targets"
internalCategory:"Morph Mirror Targets"
tooltip:"MirrorMorph"
(
mirrorMorph()
)
и разместил его в 3dsmax root/UI/MacroScripts. После чего добавил его себе в Quad Menu.
И теперь, этот инструмент у меня теперь всегда наготове, не надо ничего запускать и ничего писать.
Зеркальное копирования морф-целей
Но вернемся к нашим «зайцам». Выделите модель головы, войдите в подобъект Vertex и выделите все точки. Откройте Listener и напишите в нем getMirrorData(), нажмите ввод.
Теперь скопируйте морф-цель (можете скопировать сразу все морф-цели), и выделяя их по очереди пишем в Listener mirrorMorph(), нажимаем ввод.
Таким образом нужно подготовить все морф-цели.
Настройка второй половины лица
Когда все зеркальные цели готовы и удобным образом названы (!), добавляем их по порядку в Morpher и начинаем привязывать. Делается это точно так же как и при настройке первой половины. Поэтому растолковывать тут больше ничего не буду. Просто дам параметры для всех остальных целей.
Губы (контрол для левой половины губ)
X_Position –> цель с губами «в трубочку», выражение: -X_Position*20
X_Position –> цель с губами растянутыми в сторону, выражение: X_Position*20
Y_Position –> цель с губами выгнутыми вниз, выражение: -Y_Position*20
Y_Position –> цель с губами выгнутыми вверх, выражение: Y_Position*20
Глаз (контрол для левого глаза)
Y_Position –> цель с полузакрытым верхним веком, выражение: if Y_Position >= 0 then (100-Y_Position*20) else (100+Y_Position*20)
Y_Position –> цель с закрытым верхним веком, выражение: -Y_Position *20
Y_Position –> цель с открытым верхним веком, выражение: Y_Position *20
Y_Position –> цель с закрытым нижним веком, выражение: -Y_Position *15
Y_Position –> цель с открытым нижним веком, выражение: Y_Position *20
X_Position –> цель с веком смещенным в сторону носа, выражение: -X_Position *20
X_Position –> цель с веком смещенным в сторону виска, выражение: X_Position *20
Бровь (контрол для левой брови) Не забываем назначать List контроллеры.
Y_Position –> цель с поднятым центром брови, выражение: Y_Position *20
X_Position –> цель с поднятым центром брови, выражение: if X_Position <=0 then (1+(X_Position*.2)) else (1-(X_Position*.2))
Y_Position –> цель с поднятым краем брови со стороны носа, выражение: Y_Position *20
X_Position –> цель с поднятым краем брови со стороны носа, выражение: if X_Position <=0 then -X_Position*.2 else X_Position=0
Y_Position –> цель с поднятым краем брови со стороны виска, выражение: Y_Position *20
X_Position –> цель с поднятым краем брови со стороны виска, выражение: if X_Position <=0 then X_Position=0 else X_Position*.2
Y_Position –> цель с опущенным центром брови, выражение: -Y_Position *20
X_Position –> цель с опущенным центром брови, выражение: if X_Position <=0 then (1+(X_Position*.2)) else (1-(X_Position*.2))
Y_Position –> цель с опущенным краем брови со стороны носа, выражение: -Y_Position *20
X_Position –> цель с опущенным краем брови со стороны носа, выражение: if X_Position <=0 then -X_Position*.2 else X_Position=0
Y_Position –> цель с опущенным краем брови со стороны виска, выражение: -Y_Position *20
X_Position –> цель с опущенным краем брови со стороны виска, выражение: if X_Position <=0 then X_Position=0 else X_Position*.2
X_Position –> цель с бровью смещенной в сторону носа, выражение: -X_Position *20
X_Position –> цель с бровью смещенной в сторону виска, выражение: X_Position*20
Ну вот, наше лицо полностью готово готово! Не забывайте, что вы можете добавлять сюда какие угодно цели, на свое усмотрение, "губы в дудку" и другие специальные цели для реализации фонемных особенностей, морщины и даже язык, можно сделать подобным образом. Так что, тут вам все карты, как говорится.
Если после этого урока у вас не пропало желание работать в 3dsmax, то могу вас поздравить, в ваших жилах течет крепкий бульон! Удачных работ и профессионального роста вам. Пока!