Радость от деформаций корректами
Всем привет. В данный момент я работаю инструктором по риггингу в онлайн школе CGTarian, поэтому если хотите более подробно разобраться в этой и других темах – милости просим на курсы!
Темой нашего сегодняшнего урока будет использование корректирующих блендшейпов в сложных ситуациях, таких как плечи или бедра. Ввиду специфики данного урока, предполагаю более или менее знающих читателей, разбирающихся в таких понятиях как деформер или блендшейп.
Я рассматриваю только стандартные возможности Maya + дополнительные скрипты и не умаляю достоинств таких интересных инструментов для работы с деформациями как poseDeformer. В конце концов, не все имеют возможность использовать сторонние плагины в своих сценах (например, если над сценой работают много разных людей вне студии или сценку нужно будет передать кому-то неквалифицированному).
Как вы, надеюсь, знаете, основная проблема настройки корректирующих блендшейпов (коррект блендов) сводится к определению веса нужного бленда, то есть нужно четко знать когда он включается, а когда нет.
С локтем, скажем, все более или менее понятно – мы привязываем вес бленда к углу предплечья (локтя), а так как оно у нас вращается всего по одной оси, то и проблем с весом у нас нет – driven key решит все проблемы.
А как быть с такими областями как плечи или бедра, ведь они вращаются по всем трем осям? Да, здесь в лоб проблему не решить, так как нельзя привязываться к конкретным осям из-за специфики вычисления вращений – будут возникать скачки в значениях.
Но я придумал замечательный и очень гибкий способ настройки весов в таких случаях. Основная идея – мы привязываемся не к конкретным значениям, а к областям в пространстве – позам.
Давайте рассмотрим такого вот персонажа.
В качестве примера разберем создание корректирующих блендшейпов на бедра. Нам понадобится набор замоделенных поз в виде коррект блендов (для этого используем скрипт BSspiritCorrectiveShape).
На данный момент мы имеем персонажа и набор поз со сгенерированными корректирующими вариантами.
Я остановился на таких позициях ноги: поднята в сторону, поднята вперед/назад и среднее положение между «поднята в сторону» и «поднята вперед». Выбранные позы позволят нам получить адекватные деформации в не слишком критических положениях. Если же персонаж по сюжету должен выполнять какие-либо акробатические трюки, то тут, конечно, без поз на критические положения не обойтись. В общем я вам расскажу суть, а как и где применять данную технологию решать вам.
Итак, наша система построена не на привязке к углам, а на анализе расстояния между позами и некой точкой – осью.
Каждая поза идентифицирует положение оси в пространстве и на основании расстояния до оси формирует весовой коэффициент.
Целиком система poseCorrector состоит из оси и набора поз, которые между собой смешиваются. Интепроляция в пределах системы нормализирована, то есть не позволяет вершинам принимать вес > 1 от всех поз (нет наложений блендов).
В сцене система выглядит так.
Общая группа poseCorrector позиционируется на бедренную кость и через orientConstraint (с maintain offset) управляется тазовой костью.
poseCorrector_axis_group через orientConstraint (без maintain offset) управляется костью ноги.
poseCorrector_axis – локатор, который находится в группе и слегка сдвинут в направлении ноги по оси X. Таким образом мы как бы формируем вектор ноги.
Локаторы poseCorrector_side_pose, poseCorrector_forward_pose, poseCorrector_back_pose, poseCorrector_diagonal_pose – собственно позы, которые делались дубликатом poseCorrector_axis в тех положениях ноги, где делались корректирующие бленды.
Каждая поза имеет атрибут radius, который будет управлять областью ее влияния.
Что у нас получилась за ерунда? На самом деле все очень просто. Проще, чем кажется.
Начинаю вращать ногой – poseCorrector_axis точно направлен по бедру и не меняет своего положения при вращении ноги вокруг своей оси.
То есть положение оси точно идентифицирует ногу в данном положении и нам не важно как именно она повернута по каналам rotate.
Теперь нужно получить расстояния от оси до всех поз для построения весовых коэффициентов.
Делается это через ноду distanceBetween: createNode distanceBetween;
Нам нужны расстояния до каждой позы, поэтому дистансБитвинов столько, сколько и поз (в нашем случае 4).
Коннекты такие: point1 – poseCorrector_axis.worldPosition (шейп у локатора смотрите), point2 – worldPosition от позы.
Помимо этого нам понадобятся ноды setRange для каждой позы (тоже 4) для превращения расстояния в весовой коэффициент: createNode setRange;
Соединяем расстояние от оси до позы (distanceBetween.distance) в setRange.valueX.
Также коннектим радиус позы (pose.radius) в setRange.oldMaxX.
Остальные значения такие: oldMinX = 0, minX = 1, maxX = 0.
Что мы получили? Теперь нода setRange для каждой позы возвращает число от 0 до 1 в зависимости от радиуса влияния позы и положения оси (преобразовали диапазон от 0 до radius в диапазон от 0 до 1). Что ж – неплохо.
Не поверите, почти все!!!
Теперь в общем-то можно коннектить setRange.outValueX каждой позы в соответствующий канал корректирующего бленда и деформации будут достаточно контролируемы, но мы пойдем дальше.
Хоть все и работает, но без нормализации очень трудно подобрать радиусы влияния для каждого бленда – корректы налазят друг на друга в промежуточных позициях и вследствие этого возникают сильные и непонятные движения вершин. В идеале должно быть так, чтобы вес всех поз на вершину был не больше 1. Это называется нормализацией.
Данная функция тоже выполняется достаточно просто: суммируем веса всех поз и делим вес нужной позы на эту сумму.
Скажем, у нас такие веса: 0.3, 0.5, 0.8. Если их сложить, то получится 1.6.
Нормализируем: 0.3 / 1.6 = 0.1875, 0.5 / 1.6 = 0.3125, 0.8 / 1.6 = 0.5.
Вот, новые веса, а точнее коэффициенты весов, нам и нужны.
Суммируем веса, к примеру, нодой blendWeighted: createNode blendWeighted;
Коннетим вес каждой позы в свободный инпут blendWeighted ноды:
connectAttr setRange.outValueX blendWeighted.i[0,1 и т.д.]; // Теперь у нас есть сумма весов всех поз.
А что делать, если нога в таком положении, где ни одна из поз не действует? Тогда у нас будет деление на 0. Чтобы этого избежать мы включим проверку на отсутствие включенных блендов.
Делаем через ноду condition: createNode condition;
Операцию у кондишна ставим «greater then» ( > ). Коннектим сумму весов (blendWeighted.output) в каналы firstTerm и colorIfTrueR.
Нода условия теперь возвращает сумму весов, если хотя бы одна из поз включена или 1 (1 – значение по умолчанию в атрибуте colorIfFalseR).
Далее, для каждой ноды необходимо теперь делением получить коэффициент:createNode multiplyDivide;
Операцию ставим «/» (деление). В input1X коннектим вес позы (setRange нода), а в input2X – сумму весов от кондишна (condition.outColorR).
Теперь нода multiplyDivide для каждой позы возвращает весовой коэффициент, но не сам вес. Для получения веса позы умножим этот коэффициент на начальный вес позы (setRange): createNode multDoubleLinear;
В input1 коннектим вес позы (нода setRange), в input2 – весовой коэффициент (multiplyDivide.outputX). Отлично!
Все, коннектим multDoubleLinear.output каждой позы в соответствующий вес корректирующего бленда в узле blendShape.
Подправляем радиусы и радуемся хорошим деформациям.
Очень надеюсь, что вы разобрались с технологией и уже начали использовать корректирующие блендшейпы везде где только возможно