Инверсная кинематика: простой и быстрый алгоритм. Скелетная анимация, прямая и инверсная кинематика


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

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

Если в общем, то скелетная анимация - это анимирование 3D-фигуры посредством относительно небольшого количества управляющих элементов, и внешне и по принципу работы, напоминающие скелет - или строение марионетки.

Как он «изготавливается», мы рассматривали в статье «Риггинг» . От того, насколько разумно и правильно риггинг был выполнен, зависит и результаты первых попыток анимировать что-либо. Если иерархия костей сделана как надо, то и поведение цепочки, равно как и привязанных к ней вершин, будет «жизнеподобным». Ошибки же могут привести к совершенно нелепым последствиям: ноги могут «уехать» за голову, например.

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

Существуют два основных типа планирования движения «скелета» - это прямая кинематика (Forward Kinematics - FK) и инверсная, или обратная кинематика (Inverse Kinematics - IK). Их также выбирают на этапе риггинга, - впрочем, этот процесс от скелетной анимации вообще неотделим.

Итак: создадим отдельно взятую цепочку «костей» - без добавления мяса пока что. Все операции осуществляются в пакете Blender3D, скриншоты, стало быть, тоже оттуда.



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

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



Повернули кость 2 , вместе с ней повернулись (но остались на одной прямой) кости 3 и 4 .

Это прямая кинематика: перемещение старших по иерархии костей приводят к тому, что перемещаются и младшие.

При использовании обратной кинематики алгоритм получается ровно противоположный:




Задав режим автоматического выбора инверсной кинематики (Blender делает вид, что подбирает оптимальный вариант, и иногда вполне успешно), сдвигаем самую младшую в иерархии кость 4 ; вся цепочка послушно изогнулась.

Звено цепочки, изменение положения которого приводит к изменению положения других звеньев, называется effector (существует диковинный перевод «влиятель», но как на самом деле это слово переводить?..).



Подвигали кость 3 . Звенья 1-2 поменяли своё положение, а 4 - осталось на одной прямой со звеном 3 . Кость 3 теперь является effector"ом, и всё, что находится ниже его по иерархии подчиняется алгоритмам прямой кинематики.

Инверсная кинематика применяется главным образом там, где требуется точное расположение конечного звена в нужной точке (например, чтобы при ходьбе ноги персонажа не «проскальзывали» по поверхности или не утопали в полигонах, изображающих твердь земную).

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

Далее начинается процесс автоматизации движений - тут всё делается так же, как и при обычной анимации. Задаются ключевые кадры для отдельных управляющих элементов, и они тащат за собой все остальные. При этом анимационные пакеты вполне могут регистрировать ключевые положения только для индивидуальных костей, для всех звеньев разом или для отдельных их групп. При этом генерируются кривые движения/вращения/масштаба для каждого элемента, участвующего в анимации. Современные пакеты, разумеется, предоставляют в избытке средства, позволяющие экономить на усилиях - например, «глобализовать» управление сразу множеством элементов, группируя их более-менее удобным способом. На скриншоте ниже представлено всё рабочее окно Blender с активированными Action Editor и Timeline Editor.

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

Ну, и напоследок, добавим, что если целью стоит сделать не «реалистичную» анимацию, а стилизацию под анимацию рисованную, то необходимо иметь в виду пресловутые 12 принципов, заложенные

Здесь мы будем создавать анимацию для персонажа, которого мы изготовили в Главе 4. На сей раз обойтись отдельными сферами и чайниками не удастся, поскольку нам нужен не подопытный объект, а иерархически-связанная система объектов.

Прямая кинематика (FK)

Прямая кинематика (FK - Forward Kinematics) представляет собой движение дочерних объектов относительно родительских. Здесь нет ничего хитрого. Создадим прямую кинематику для рук нашего персонажа. Для этого откроем файл с персонажем. Нажмём кнопку Auto Key.

Пускай персонаж помашет нам рукой.

Сместим бегунок в какой-нибудь кадр, например в 10-й, поднимем руку вверх. Делать это мы будем при помощи вращения, причём, для придания нужного положения, скорей всего повернуть придётся обе части руки.

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

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

Чтобы избавиться от этого эффекта, надо в последнем взмахе рукой создать ключ, в котором предплечие находится в правильном положении. Создать этот ключ желательно в том же кадре, в котором стоит ключ для последнего взмаха плечом (предпоследний ключ для плеча). Выделите плечо, чтобы отображались его анимационные ключи, и переместите бегунок в кадр, где находится его предпоследний ключ. Теперь выделите предплечие и поверните его в нужное положение. Проиграйте анимацию снова. Теперь персонаж машет рукой более естественно. Выключим кнопку Auto Key. Эту кнопку вообще лучше не держать зря включённой, поскольку в этом случае можно создать нежелательную анимацию во время редактирования и размещения объектов. При выключенной кнопке Auto Key анимация по-прежнему проигрывается, но теперь её нельзя создавать. Трансформация объектов приводит к тому, что сдвигается сама анимация. Если мы сейчас в нулевом кадре повернём руку персонажа так, чтобы она была вытянута вперёд и проиграем анимацию, то увидем, что рука движется в другом положении. Нажмём Undo, чтобы восстановить первоначальную анимацию.

Подводим итоги: мы создали много анимационных ключей для плеча и всего четыре ключа для предплечия. Между вторым и третим ключами предплечие неподвижно относительно своего родительского объекта - плеча.

Здесь файл с анимацией руки (формат 3ds MAX 6).

Инверсная кинематика (IK)

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

Мы рассмотрим средства инверсной кинематики, которые были введены в 3ds МAX 4 и сохранились до последних версий (3ds MAX 7). В более ранних версиях инверсная кинематика была устроена несколько иначе.

Инверсная кинематика создаётся при помощи так называемых цепей инверсной кинематики (IK Chain). Цепь создаётся между последовательно привязанными друг к другу объектами. Для того, чтобы создать такую цепь, иерархическая последовательность должна оканчиваться неким вспомогательным дочерним объектом. Мы проследим создание цепи инверсной кинематики на ногах нашего персонажа, при этом последним вспомогательным объектом цепи будет ступня.

Выделим корневой объект нашей цепи. Имейте ввиду, что корневой объект цепи - это не корневой объект всей иерархии в целом, то есть не body, а бедро (thigh). Выделим бедро и войдём в пункт главного меню Animation . Откроем в Animation пункт IK Solver (решатель инверсной кинематики). Там можно видеть:

HI Solver (History Independent Solver) - исторически-независимый решатель

HD Solver (History Dependent Solver) - исторически-зависимый решатель (лучший решатель для коротких анимационных последовательностей, но даёт плохие результаты при длительной анимации)

IK Limb Solver - решатель для анимации конечностей, расчитан на два объекта иерархической цепи

Spline IK Solver - сплайновый решатель

Мы будем использовать HI Solver. IK Limb Solver хотя и разработан для анимации конечностей человека, работает он плохо, при его использовании у персонажа всегда одно колено выгибается назад, а один локоть - вперёд.

Если бедро у нас по-прежнему выделено, выберем HI Solver и кликнем мышью на ступню той же самой ноги, бедро которой выделено. У нас должна появиться петля, связывающая обе части ноги, и синий крест в опорной точке ступни. Цепь инверсной кинематики является вспомогательным объектом, который имеет цвет и имя. Можете для удобства переназвать созданную цепь. Создадим точно такую же цепь для второй ноги.

Синий крестик - так называемый целевой объект (IK Goal ). Если мы сейчас попробуем его подвигать, то увидем, что нога сгибается и поворачивается таким образом, чтобы опорная точка ступни всегда находилась в центре крестика (при том, что сама ступня не перемещается относительно голени). Настройки для наследования вращения, которые мы сделали для ступни, теперь не работают, поскольку они осуществлялись для прямой кинематики.

Теперь стало понятно, зачем нужен вспомогательный дочерний объект: его опорная точка является ориентиром для цепи обратной кинематики. Чтобы ступня двигалась естественно, надо к ней привязать ещё какой-нибудь вспомогательный объект, и сделать ещё одну цепь инверсной кинематики, но мы сейчас этим заниматься не будем, а попробуем создать анимацию ходьбы.

Если не нажата кнопка Auto Key, нажмём её. Переместим бегунок в какой-нибудь кадр, например 10. Начнём ходьбу с правой ноги. Поднимем её (при помощи IK Goal) и немного занесём. Туловище тоже немного подвинем вперёд, так, как оно подвинется на полушаге. Переместим бегунок снова. Поставим ступню на землю, а туловище ещё немного пододвинем. Заметим, что мы создавали анимационные ключи не для вращения бедра и голени, а для перемещения IK Goal.

Теперь надо создать анимационный ключ для IK Goal в том же кадре, в котором правая ступня вновь коснулась земли. Вообще, ключ создастся по малейшему перемещению нашего IK Goal для левой ноги, но чтобы создать ключ не перемещая ногу, надо нажать на большую кнопку с изображением ключа (Set Key - поставить ключ). Либо, можно войти во вкладку Motion и создать анимационный ключ при помощи кнопки Position в группе Create Key.

Теперь проделаем анимацию левой ноги точно так же, как мы это делали для правой, не забывая при этом перемещать туловище. Отмотаем анимацию назад и проиграем. Если мы всё сделали правильно, персонаж делает два шага, при чём пока одна нога шагает, вторая не проскальзывает. На странное движение ступней пока не будем обращать внимание, в данном случае они у нас играют роль впомогательных объектов.

Здесь готовая сцена , где персонаж делает два шага и машет рукой.

Итак, мы познакомились с прямой и обратной кинематикой, теперь можно переходить к более серьёзным вещам: знакомству с системами костей и созданию скелета для анимации более сложного и детализированного персонажа.

Что такое «Инверсная кинематика»?

Задачей инверсной кинематики является поиск такого набора конфигураций сочленений, который обеспечил бы максимально мягкое, быстрое и точное движение к заданным точкам. Однако, множество существующих ныне методов страдают от таких недостатков как высокая вычислительная сложность и неестественность результирующих поз. В этой статье описан новый (вероятно, на момент написания статьи - 2010 г. ) эвристический метод под названием «Метод прямого и обратного следования» (Forward and Backward Reaching Inverse Kinematics , далее просто FABRIK),
FABRIK избегает использования вращений и матриц в пользу непосредственного получения точки на прямой. Благораря этому, дело обходится всего несколькими итерациями, имеет низкую стоимость вычислений и визуально естественную позу в результате. FABRIK так-же без проблем справляется с наложением ограничений а так-же использованием нескольких цепей и/или конечных точек. Именно об этом методе этот пост.

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

1. Искусственная модель тела

Система из множества твёрдых тел состоит из набора твёрдых тел, называемых узлами, соединёнными вместе рёбрами. Все рёбра являются компонентами concerned с движением: они ограничивают перемещения в пределах некоторого угла относительно соседних рёбер. Моделирование виртуального тела важно для вычисления позы человека. Модель с правильно расставленными ограничениями позволит получить набор правильных поз, что даст возможность получить более реалистичное движение. Большинство моделей подразумевают твёрдость частей тела, хотя это просто примерное приближение к реальности.
Скелет обычно смоделирован в виде иерархии твёрдых сегментов соединённых рёбрами, каждое из которых задано такими свойствами как длинна, форма, обьём и масса. Манипулятор, на манер робо-руки или анимированного персонажа, смоделиован как цепь, собранная из твёрдых узлов, сопряженных друг с другом рёбрами. Каждое перемещение и/или вращение кости с индексом i влияет на все последующие элементы цепи. Цепь можно формализовать следующим образом: всякий узел без дочерних элементов следует величать конечной точкой; для каждой конечной точки цепь может быть сформирована движением обратно по скелету, от родителя к родителю до тех пор, пока не будет встречен корневой узел цепи (начало цепи). По определению, в задаче IK предполагается статичность корневого узла. Однако, методы обычно справляются с перемещением корня.

Алгоритм полного цикла алгоритма FABRIK (псевдокод, первый элемент массива под индексом 1)

Исходные данные: массив позиций узлов p[i] с i = 1...n, целевая позиция t и значения дистанций между сопряжёнными узлами. d[i] = | p -t | for i = 1, ... , n-1 Выходные данные: Новые позиции p[i], i = 1...n //Дистанция между корнем и целью dist = | p - t | //Проверяем достижимость цели if dist > d + d + ... + d { //цель недостижима for i = 1, ..., n-1 do { //Найдем дистанцию r[i] между целью t и узлом p[i] r[i] = | t - p[i] | lambda[i] = d[i] / r[i] //Находим новую позицию узла p[i] p = (1 - lambda[i]) * p[i] + lambda[i] * t } } else { //Дель достижима; т.о. b будет новой позицией узла p b = p //Проверяем, не выше ли дистанция между конечным узлом p[n] и //целевой позицией t значения терпимости (tolerance) DIFa = | p[n] - t | while DIFa > tol do { //Этап 1: прямое следование //Устанавливаем конечный узел p[n] в качестве цели (вероятно, имелось ввиду "ставим на позицию цели" - прим. перев.) p[n] = t for i=n -1 , ..., 1 do { //Получаем расстояние r[i] между узлом p[i] и новой позицией p r[i] = | p - p[i] | lambda[i] = d[i] / r[i] //Вычисляем новую позицию узла p[i] p[i] = (1 - lambda[i]) * p + lambda[i] * p[i] } //Этап 2: обратное следование //Устанавливаем корневому элементу p начальную позицию p[i] = b for i=1 ,..., n - 1 do { //Получаем дистанцию r[i] между узлом p и позицией p[i] r[i] = | p - p[i] | lambda[i] = d[i] / r[i] //Получаем новую позицию p[i] p = (1-lambda[i]) * p[i] + lambda[i] * p } DIFa = | p[n] - t | } }

2.FABRIK - новое эвристическое решение задачи IK

В этой части предоставляется суть метода FABRIK. Он использует позиции, уже рассчитанные в режимах прямого и обратного следования. FABRIK достигает минимизации ошибки путём единоразовой подстройки угла каждого узла. Т.е. происходит обход всей цепи, начиная с последнего узла, с подстройкой угла каждого обойдённого узла, после чего, происходит обход цепи уже в обратном направлении. Этот метод, в отличие от преобразования вращений, обращает задачу поиска позиции узла в задачу поиска точки на прямой; следовательно, можно сэкономить время и уменьшить количество вычислений. Предположим что множество p,…, p[n] является множеством позиций узлов манипулятора. Также, предположим что p является корневым узлом и p[n] является конечным узлом, т.о. для простоты оставим один конечный узел. Цель представлена позицией t и начальной базовой позицией b . Метод FABRIK представлен в листинге выше и графической интерпретации полного цикла на рисунке слева, с одной целевой точкой и четырьмя узлами в цепи. Рассмотрим полный цикл алгоритма на рисунке:

  • a - Начальные позиции манипулятора и цели.
  • b - Двигаем конечный узел p к цели.
  • c - Обнаруживаем позицию p" p" и p , на дистанции d от точки p" .
  • d - Повторяем для всех узлов.
  • e - Вторая стадия алгоритма: передвигаем крневой элемент с позиции p" на его начальную позицию.
  • f - Повторяем для всех узлов, но на этот раз начинаем с базы и двигаемся к конечному узлу. Алгоритм повторяется до тех пор, пока позиция конечного элемента не приблизится к цели на достаточное расстояние.

Более подробно:
Сначала считаются позиции между узлами (массив d ), после чего идёт проверка, достижима ли целевая точка; считается расстояние между корневым узлом и целью (dist ), и если эта дистанция меньше общей суммы дистанций между узлами, то цель достижима, иначе нет. Если цель достижима, полный цикл ограничивается двумя этапами. На первом этапе, алгоритм оценивает начальную позицию каждого узла, начиная с конечного элемента p[n] двигаясь к базе манипулятора p . Таким образом, позволим целевой позиции быть позицией конечного узла, p"[n] = t . Получим прямую l , лежащую на точках p и p"[n] . Новая позиция узла с индексом n-1 , p" , лежит на этой линии на дистанции d от p"[n] . Аналогично, новая позиция узла с индексом n-2 , p" , может быть вычислена используя прямую l , лежащую на точках p и p" на дистанции d от p" . Алгоритм повторяется до тех пор, пока все не будут посчитаны новые позиции для всех узлов, включая конечный. В случаях, когда корневой элемент перемещается на необходимую позицию, FABRIK срабатывает как было описано, тем лишь отличием, что новая позиция p"" корневого узла будет желаемой позицией, а не начальной.
После одной полной итерации, почти во всех случаях (по наблюдениям) конечный узел приблизится к цели. Процедура повторется необходимое количество раз, до тех пор, пока конечный узел не ляжет на позицию цели или не приблизится к ней на допустимую дистанцию. Реализация метода FABRIK без введения ограничителей сойдётся на любой целевой точке/цепи, если цель достижима. Однако, если цель находится дальше достанции, на которую может вытянуться цепь, необходимо прерывающее условие, которое сравнит прошлую и текущую позицию конечного узла, и которое прекратит выполнение алгоритма если смещение конечного узла будет меньше некоторого значения (эпсилона). Так-же, в особых случаях, алгоритм прерывается по истечению некоторого числа итераций (впрочем, пока-что такая ситуация не была встречена).
Для более быстрого результата и решения в несколько итераций, возможна оптимизация с применением Конформной Геометрической Алгебры (Conformal Geometric Algebra, далее CGA); CGA имеет преимущество на базовых фигурах, такие как сферы, прямые, плоскости и окружности, достаточно просто отображаемые алгебраическими обьектами. Поэтому, поиск позиции узла, находящегося между двумя известными узлами, может быть выражен пересечением двух сфер с центрами на соответствующих этим узлам позициям, и радиусом, равным расстоянию между позициям искомого узла и имеющимися; новая позиция узла будет лежать на ближайшей точке окружности, сформированной пересечением двух сфер. Другая простая оптимизация заключается в прямом построении прямой в направлении цели, когда последняя недоступна.

3. Модель с множеством конечных узлов


Как и в случае с одним конечным узлом, алгоритм разбивается на два этапа:

  • Первый этап точно такой-же, только на этот раз начинается от каждого конечного узла с движением внутрь по цепи от этого узла, вплоть до суб-базы (вероятно, суб-базой является узел с несколькими примыкающими к нему рёбрами - прим. перев.). Таким образом, получим столько разных позиций для суб-базы, сколько соединённых с ней конечных узлов у нас имеется. Итоговая же позиция может быть взята как центроид (тобиш, просто среднее арифметическое - прим. перев.) из этих позиций. После этого, выполнение алгоритма продолжается в нормальном режиме, двигаясь от суб-базы до корня. Если суб-базы имеют собственные суб-базы, то по отношению к ним производятся аналогичные действия - так-же составляется список возможных позиций, после чего эта суб-база устанавливается на центроид из всего списка позиций.
  • На втором этапе, обычный алгоритм применяется к каждому узлу, двигаясь всё дальше от корневого узла. При этом, каждая цепь должна быть обработана отдельно вплоть до конечного узла: чем больше суб-баз, тем больше повторений для каждой из них. Процесс повторяется до тех пор, пока конечные узлы не достигнут своей цели или пока не сработает условие прерывания.

4. Ограничители

Ну и наконец, самая вкусная часть этой статьи - рассчёты с применением ограничителей. Нужны они, как уже стоило догадаться, для большего сходства с реальными организмами. Сам же узел, обычно, характеризуется тремя степенями свободы. Вращение узла может быть характеризовано как «простое вращение» (2 степени свободы), которое отражает его конечную позицию, и вращение вокруг собственной оси (1 степень свободы). Таким образом, разделив передвижение узла на две такие фазы, и применив к ним ограничители, можно управлять положением узла. Сами же ограничения можно наложить подобным образом: т.к. алгоритм итеративный, можно применять ограничения вращений на каждой итерации алгоритма. Ограничители, при этом, не повлияют на сходимость алгоритма. Основная же идея применения ограничителей заключается в репозициионировании и переориентации узлов в пределах ограничений.

  • a - Начальные конфигурации манипулятора и цели.
  • b - Двигаем конечный узел p к цели и ориентируем его на неё.
  • c - Обнаруживаем позицию p" , лежащую на линии между позициями p" и p , на дистанции d от точки p" .
  • d - Переориентируем узел на позиции p" таким образом, чтобы он смотрел вдоль ребра, соединяющего p" и p" .
  • e - Вычисление ограничивающего эллипса: разрешённые позиции находятся в затенённом участке. Ни одна из вершин на этом этапе никуда не двигается.
  • f - Узел p перемещается на позицию p^ , который является ближайшей позицией на затенённом эллипсе, удостоверяясь таким образом в том, что новая позиция p^ будет лежать в допустимых пределах.
  • g - Двигаем узел p^ на точку p" , чтобы сохранить длинну ребра.
  • h - Переориентируем p" , чтобы удовлетворить ограниение ориентации.

Эта процедура повторяется для всех узлов, в прямом и обратном порядке, аналогично тому, как в варианте без ограничений осуществлялись перемещения. При этом, ограничение «эллипс», вероятно, является характеристикой ребра, а не узла, т.о. на второй фазе перемещаться на эллипс должен узел p - прим. перев.

Градиентный спуск

Простейший способ разобраться в работе градиентного спуска - представить холмистый рельеф. Мы находимся в случайном месте и хотим достигнуть самой низкой точки. Назовём её минимумом рельефа. На каждом шаге градиентный спуск говорит нам двигаться в направлении, снижающем нашу высоту. Если геометрия рельефа относительно проста , то такой подход приведёт нас к самому низу долины.

На графике ниже показан стандартный случай, в котором градиентный спуск будет успешным. В этом простейшем примере у нас есть функция. Она получает один параметр (ось X) и возвращает значение ошибки (ось Y). Мы начинаем со случайной точки на оси X (синяя и зелёная точки). Градиентный спуск должен заставить нас двигаться в направлении минимума (синяя и зелёная стрелки).

Если мы будем смотреть на функцию в целом, то направление движения очевидно. К сожалению, градиентный спуск не имеет заранее информации о том, где находится минимум. Лучшей догадкой, которую может допустить алгоритм, станет движение по направлению к склону, также называемом градиентом функции. Если вы находитесь на горе, отпустите мяч, и он сам достигнет долины. На графике ниже показан градиент функции ошибок в двух разных точках.

Насколько хорошо выглядит рельеф?

Чтобы градиентный спуск был эффективным, минимизируемая нами функция должна удовлетворять определённым требованиям. Если рельеф функции относительно плавный, то вероятность успешного применения градиентного спуска выше. Если у функции есть разрывы или множество максимумов, то это особенно сложно, потому что для достижения дна долины потребуется гораздо более долгое путешествие.

Вот как может выглядеть рельеф для робота-манипулятора с двумя соединениями (управляемыми и ):


Оценка градиента

Если вы изучали математический анализ, то, наверно, знаете, что градиент функции непосредственно связан с её производной . Однако для вычисления производной функции необходимо, чтобы она удовлетворяла определённым математическим свойствам, и выполнение этого требования для любой проблемы в общем случае гарантировать невозможно. Более того, для аналитического взятия производной нужно, чтобы функция ошибок была представлена в аналитическом виде. И у нас не всегда есть аналитический вид функции, которую нужно минимизировать.

Во всех таких случаях невозможно найти истинную производную функции. Решением является приблизительная оценка её значения. На графике ниже показано, как она находится в одном измерении. Дискретной выборкой близлежащих точек можно приблизительно получить локальный градиент функции. Если функция ошибок меньше слева, то мы двигаемся влево, если справа - то вправо.

В чём разница между градиентом и производной?

Понятия градиента и произвольной тесно связаны.

Градиент или косая производная функции - это вектор, указывающий в направлении наискорейшего подъёма. В случае одномерных функций (как на наших графиках) градиент равен или , если функция идёт вверх, или , если функция идёт вниз. Если функция определена через две переменные (например, робот-манипулятор с двумя соединениями), то градиент является «стрелкой» (единичным вектором ) двух элементов, направленным в сторону наискорейшего подъёма.

Производная функции, в отличие от градиента - это просто число, определяющее скорость подъёма функции при движении в направлении градиента.

В этой статье мы не будем стремиться вычислить настоящий градиент функции. Вместо этого мы создадим оценку. Наш приблизительный градиент - это вектор, который, как мы надеемся, указывает в направлении наискорейшего подъёма. Как мы увидим, это необязательно будет единичный вектор.


Насколько важно создание выборки?

Оно очень важно. Выборка близлежащих точек требует оценки функции на определённом расстоянии от текущего положения. Это расстояние критически важно.

Посмотрите на график:

Это расстояние выборки, использованное для оценки градиента, слишком велико. Градиентный спуск ошибочно «предполагает», что правая сторона выше, чем левая. В результате алгоритм будет двигаться в неправильном направлении.

Снижение расстояния выборки позволяет уменьшить эту проблему, но избавиться от неё никогда не удастся. Более того, меньшее расстояние выборки приводит ко всё более медленному приближению к решению.

Эту проблему можно решить с помощью более сложных вариаций градиентных спусков.


Что если у функции есть несколько локальных минимумов?

В общем случае такой «жадный» подход не даёт нам никаких гарантий, что мы на самом деле достигнем самой низкой точки долины. Если есть другие долины, то мы можем застрять в одной из них и не сможем достичь нашей истинной цели.

С учётом изложенного выше наивного описания градиентного спуска мы можем заметить, что именно это произойдёт с функцией на графике выше. У этой функции есть три локальных минимума , создающих три отдельные долины. Если мы инициализируем градиентный спуск в точке из зелёной области, то он закончится на дне зелёной долины. То же самое относится к красной и синей областям.

Все эти проблемы тоже можно решить с помощью усложнённых вариаций градиентного спуска.

Математика

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

С математической точки зрения производная функции называется . Её значение в точке равно , и оно показывает, насколько быстро растёт функция. Согласно ей:

Идея заключается в том, чтобы использовать оценку для вычисления градиента, обозначаемого . Математически определяется как:

На графике ниже показано, что это значит:

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

Подведём итог. Для вычисления истинной производной функции необходимо использовать предел. Наш градиент является приблизительной оценкой производной, созданной с помощью достаточно небольшого расстояния выборки:

В следующем разделе мы увидим, чем эти два понятия отличаются при нескольких переменных.

Найдя приблизительную производную, нам нужно двигаться в противоположном направлении, чтобы спуститься по функции вниз. Это значит, что нужно обновить параметр следующим образом:

Константу часто называют learning rate . Она определяет, как быстро мы будем двигаться по градиенту. Чем больше значения, тем быстрее найдётся решение, но тем больше вероятность пропустить его.

lim?

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

Рассмотрим наш условный пример. Чем меньше расстояние выборки , тем лучше мы можем оценить истинный градиент функции. Однако мы не можем задать , потому что деление на ноль не разрешено. Пределы позволяют нам обойти эту проблему. Мы не можем делить на ноль, но с помощью пределов мы можем задать число, условно близкое к нулю, но на самом деле ему не равное.

Несколько переменных

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

Функция с одним параметром соответствует роботу-манипулятору с единственным сочленением. Если мы хотим выполнить градиентный спуск для более сложных манипуляторов, то необходимо задать градиент функций с несколькими переменными. Например, если у нашего робота-манипулятора есть три сочленения, то функция будет больше похожа на . В таком случае градиент - это вектор, состоящий из трёх чисел, показывающих локальное поведение в трёхмерном пространстве его параметров.

Мы можем ввести понятие частных производных , которые, в сущности, являются «традиционными» производными, вычисляемыми для каждой из переменных:

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

Для нашего градиентного спуска мы будем в качестве градиента использовать вектор, содержащий в себе все три результата:

Это не единичный вектор!

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

Хотя это и может выглядеть как насилие над математикой (а возможно, так оно и есть!), но при этом оно не обязательно станет проблемой для нашего алгоритма. Нам нужен вектор, указывающий в направлении наискорейшего подъёма. Использование приблизительных значений частных производных в качестве элементов такого вектора удовлетворяет нашим ограничениям. Если нам нужно, чтобы это был единичный вектор, то можно просто нормализовать его, поделив на его длину.

Использование единичного вектора даёт нам преимущество определения максимальной скорости, с которой мы движемся по поверхности. Эта скорость является learning rate . Использование ненормализованного вектора означает, что мы будем быстрее или медленнее, в зависимости от наклона . Это не хорошо и не плохо, это просто ещё один подход к решению нашей проблемы.

Часть 5. Инверсная кинематика для робота-манипулятора

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

Введение

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

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

Например, если у робота-манипулятора есть три сочленения, то у нас будет функция , получающая три параметра: , и . Тогда наш градиент задаётся как:

А , и - достаточно малые значения.

Мы получили приблизительный градиент . Если мы хотим минимизировать , то необходимо двигаться в противоположном направлении. Это означает обновление , и следующим образом:

Где - это learning rate , положительный параметр, управляющий скоростью удаления от поднимающегося градиента.

Реализация

Теперь у нас есть все знания, необходимые для реализации простого градиентного спуска на C#. Давайте начнём с функции, вычисляющей приблизительное значение частного градиента i -того сочленения. Как говорилось выше, для этого нам нужно создать выборку функции (которая является нашей функцией ошибок DistanceFromTarget , описанной во «Введении в градиентный спуск») в двух точках:

Public float PartialGradient (Vector3 target, float angles, int i) { // Сохраняет угол, // который будет восстановлен позже float angle = angles[i]; // Градиент: / h float f_x = DistanceFromTarget(target, angles); angles[i] += SamplingDistance; float f_x_plus_d = DistanceFromTarget(target, angles); float gradient = (f_x_plus_d - f_x) / SamplingDistance; // Восстановление angles[i] = angle; return gradient; }
При вызове этой функции она возвращает одно число, определяющее, как изменяется расстояние от цели как функция от поворота сочленения.

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

Public void InverseKinematics (Vector3 target, float angles) { for (int i = 0; i < Joints.Length; i ++) { // Градиентный спуск // Обновление: Solution -= LearningRate * Gradient float gradient = PartialGradient(target, angles, i); angles[i] -= LearningRate * gradient; } }
Многократный вызов InverseKinematics перемещает робот-манипулятор ближе к целевой точке.

Преждевременное завершение

Одна из основных проблем инверсной кинематики, реализованной этим наивным подходом - малая вероятность окончательного схождения градиента. В зависимости от выбранных для LearningRate и SamplingDistance значений очень возможно, что манипулятор будет «качаться» рядом с истинным решением.

Так происходит потому, что мы обновляем углы слишком часто, и это приводит к «перелёту» через истинную точку. Правильным решением этой проблемы будет использование адаптивного learning rate, изменяющегося в зависимости от близости к решению. Более дешёвая альтернатива - останавливать алгоритм оптимизации, если мы ближе, чем определённое пороговое значение:

< DistanceThreshold) return; for (int i = Joints.Length -1; i >= 0; i --) { // Градиентный спуск // Обновление: Solution -= LearningRate * Gradient float gradient = PartialGradient(target, angles, i); angles[i] -= LearningRate * gradient; // Преждевременное завершение if (DistanceFromTarget(target, angles) < DistanceThreshold) return; } }
Если мы будем повторять эту проверку после каждого поворота сочленения, мы выполним минимальное количество требуемых движений.

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

Ограничения

Одна из характеристик реальных сочленений заключается в том, что у них обычно есть ограниченный диапазон возможных углов поворота. Не все сочленения могут вращаться на 360 градусов вокруг своей оси. Пока мы не наложили никаких ограничений на наш алгоритм оптимизации. Это значит, что мы, скорее всего, получим вот такое поведение:

Решение достаточно очевидно. Мы добавим в класс RobotJoint минимальные и максимальные углы:

Using UnityEngine; public class RobotJoint: MonoBehaviour { public Vector3 Axis; public Vector3 StartOffset; public float MinAngle; public float MaxAngle; void Awake () { StartOffset = transform.localPosition; } }
затем нужно убедиться, что мы ограничиваем углы нужным диапазоном:

Public void InverseKinematics (Vector3 target, float angles) { if (DistanceFromTarget(target, angles) < DistanceThreshold) return; for (int i = Joints.Length -1; i >= 0; i --) { // Градиентный спуск // Обновление: Solution -= LearningRate * Gradient float gradient = PartialGradient(target, angles, i); angles[i] -= LearningRate * gradient; // Ограничение angles[i] = Mathf.Clamp(angles[i], Joints[i].MinAngle, Joints[i].MaxAngle); // Преждевременное завершение if (DistanceFromTarget(target, angles) < DistanceThreshold) return; } }

Проблемы

Даже при наличии ограничений углов и преждевременного завершения использованный нами алгоритм очень прост. Слишком прост. С этим решением может возникнуть множество проблем, и большинство из них связано с градиентным спуском. Как написано во «Введении в градиентный спуск», алгоритм может застревать в локальных минимумах . Они являются субоптимальными решениями : неестественными или нежелательными способами достижения цели.

Посмотрите на анимацию:


Рука манипулятора ушла слишком далеко, и при возврате в исходное положение она перевернулась. Наилучшим способом избежать этого будет использование функции комфорта . Если мы достигли требуемой точки, то нужно попытаться изменить ориентацию манипулятора на более удобное, естественное положение. Следует заметить, что это не всегда бывает возможно. Изменение ориентации манипулятора может заставить алгоритм увеличить расстояние до цели, что может противоречить его параметрам.

Часть 6. Инверсная кинематика щупалец

Введение

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

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

Мы начнём с примера из предыдущей части и постепенно придём к поведению, которое для наших целей окажется достаточно реалистичным.

Риггинг щупальца

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

Компонент Unity, позволяющий реализовать эту функцию, называется Skinned Mesh Renderer :


К сожалению, Unity не предоставляет возможности создания рендерера сеток со скиннингом в редакторе. Необходим редактор 3D-моделей, например, Blender . На изображении ниже показана модель щупальца, которое мы будем использовать в этой части. Внутри видно несколько костей , соединённых друг с другом. Это объекты, позволяющие нам изгибать модель.


В этом туториале мы не будем изучать добавление костей к моделям, также называемое риггингом . Хорошее введение в предмет можно прочитать в статье Blender 3D: Noob to Pro/Bones .

Кости и сочленения

Следующий этап реализации инверсной кинематики щупальца - прикрепление к каждой кости скрипта RobotJoint . Благодаря этому мы даём нашему алгоритму инверсной кинематики возможность сгибать щупальце.

У обычного осьминога каждое сочленение может свободно поворачиваться по всем трём осям. К сожалению, код написанный для робота-манипулятора, позволяет вращать сочленения только по одной оси. Если попытаться изменить это, то мы добавим нашему коду новый уровень сложности. Вместо этого мы можем циклично менять ось сочленений, чтобы сочленение 0 поворачивалось по X, сочленение 1 - по Y, сочленение 2 - по Z, и так далее. Это может привести к неестественному поведению, но такая проблема у вас может никогда не возникнуть, если кости достаточно малы.

В скачиваемом проекте Unity, продаваемом с этим туториалом, скрипт SetRobotJointWeights автоматически инициализирует параметры всех сочленений щупальца. Вы можете делать это вручную, чтобы иметь более точный контроль над движением каждой кости.

Функция комфорта

На представленной ниже анимации показано два щупальца. Щупальце слева тянется к красной сфере с помощью алгоритма из «Инверсной кинематики для робота-манипулятора». Правое щупальце добавляет совершенно новый уровень реализма, закручиваясь спирально, в более органическом стиле. Этого примера должно быть достаточно, чтобы понять, почему для щупалец нужен свой собственный туториал.


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

Щупальце справа минимизирует другую функцию. Функция DistanceFromTarget , использованная для манипулятора, заменена на новую, более сложную функцию. Мы можем заставить эту новую функцию ErrorFunction учитывать и другие параметры, которые нам важны. Показанные в этом туториале щупальца минимизируют три различные функции:

  • Расстояние до цели : уже готова
  • Поворот конечного звена : конец щупальца пытается соответствовать повороту объекта, к которому мы хотим приблизиться. Такое поведение можно заметить в анимации выше, когда правое щупальце спирально загибается вокруг сферы. Поскольку каждое сочленение имеет ограниченный диапазон движений, это создаёт пульсации, распространяющиеся вниз по кинетической цепи костей. Мы можем заставить щупальце соответствовать повороту объекта, к которому она стремится. Для этого мы можем измерить угол между поворотом конечного звена и поворотом цели. В Unity есть для этого удобная функция - Quaternion.Angle:

    Float rotationPenalty = Mathf.Abs (Quaternion.Angle(EndEffector.rotation, Destination.rotation) / 180f);
    Такое приведение к соответствию с локальным поворотом не всегда подходит. В зависимости от ситуации можно выравнивать щупальце иначе.

  • Кручение : держать части тела неестественным образом некомфортно. Этот параметр «штрафует» криволинейные движения, заставляя инверсную кинематику выполнять более линейный, простой поворот. Для вычисления штрафа за кручение нам нужно сначала определить, что же такое «кручение» в данном контексте. Проще всего определить его как среднее углов для всех сочленений. Такой штраф стремится к тому, чтобы щупальце было расслабленным, и «наказывает» решения, требующие большого количества изгибов.

    Float torsionPenalty = 0; for (int i = 0; i < solution.Length; i++) torsionPenalty += Mathf.Abs(solution[i]); torsionPenalty /= solution.Length;

Эти три ограничения приводят к более реалистичному способу движения щупалец. Более сложная версия может обеспечить колебания, даже когда условия удовлетворяют всем остальным ограничениям.

Используем разные единицы измерения?

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

В идеале их нужно нормализовать между и . После этого можно будет использовать коэффициенты для указания их относительной важности:

Public float ErrorFunction (Vector3 target, float angles) { return NormalisedDistance(target, angles) * DistanceWeight + NormalisedRotation(target, angles) * RotationWeight + NormalisedTorsion (target, angles) * TorsionWeight ; }
Такой подход ещё и обеспечивает более точный контроль за поведением щупальца. В любой момент можно изменить коэффициенты, чтобы менять способ движения в зависимости от ситуации. Например, можно увеличить TorsionWeight , чтобы распутывать запутавшиеся щупальца.


У нас нет аналитического определения!

Традиционную проблему прямой кинематики можно смоделировать аналитически. Это значит, что мы можем вывести уравнение, которое её решает. Мы уже упоминали о существовании аналитических методов решения двойственной проблемы инверсной кинематики.

На данный момент у нас есть функция, которую, возможно, не получится описать аналитически. Если бы мы выбрали традиционный, аналитический подход к решению инверсной кинематики, то нам не удалось бы добавить такие нюансы поведения щупальца. Использование градиентного спуска означает, что теперь мы можем минимизировать (почти!) любую произвольную функцию, есть у неё уравнение или нет.

Усовершенствования

Практически нет никаких ограничений для внесения улучшений в нашу модель. Повысить реализм щупалец определённо поможет функция замедления. Щупальца должны двигаться медленнее, когда приближаются к цели.

Кроме того, щупальца не должны взаимно пересекаться. Чтобы избежать этого, следует использовать коллайдеры для каждого сочленения. Однако это может привести к причудливому поведению. В нашем коде коллизии игнорируются и он может приблизиться к решению, в котором возникают самопересечения. Решение заключается в изменении функции пригодности, чтобы решения с самопересечением имели высокие штрафы.

[Готовый проект Unity со скриптами и 3D-моделями можно приобрести за 10 долларов на странице Patreon автора оригинала статьи.]

Root Motion - как оно работает

Инверсная кинематика (только для Pro версий)

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

Однако, часто полезным является взгляд на задачу позиционирования соединений с другой стороны - со стороны выбранной позиции в пространстве. Работая в обратном направлении, найдите допустимый способ расположения соединений такой, чтобы конечная точка совпала с заданной ранее позицией Это может быть полезно, когда вы хотите, чтобы персонаж коснулся объекта в точке, заданной пользователем, или корректно позиционировал свои ступни на неровной поверхности. Этот подход называется инверсной кинематикой(Inverse Kinematics ) (IK) и поддерживается Mecanim для любого персонажа гуманоида с правильно настроенным аватаром

Для того, чтобы настроить IK для персонажа, вы, как правило, имеете объекты, расположенные на сцене, с которыми взаимодействует персонаж, и затем вы настраиваете IK с помощью скрипта, в частности, с помощью функций Animator, таких как SetIKPositionWeight , SetIKRotationWeight , SetIKPosition , SetIKRotation , SetLookAtPosition , bodyPosition , bodyRotation

На иллюстрации выше мы показываем персонажа, поднимающего цилиндрический объект. Как мы сделали это?

We start out with a character that has a valid Avatar.

Next create an Animator Controller with containing at least one animation for the character. Then in the Layers pane of the Animator window, click the cog settings icon of the Layer and and check the IK Pass checkbox in the menu which pops up.


Make sure the Animator Controller is assigned to the character’s Animator Component:

Next, attach to it a script that actually takes care of the IK, let’s call it IKControl . This script sets the IK target for the character’s right hand, and its look position to make it look at the object it is holding:

Using UnityEngine; using System; using System.Collections; public class IKControl: MonoBehaviour { protected Animator animator; public bool ikActive = false; public Transform rightHandObj = null; public Transform lookObj = null; void Start () { animator = GetComponent(); } //a callback for calculating IK void OnAnimatorIK() { if(animator) { //if the IK is active, set the position and rotation directly to the goal. if(ikActive) { // Set the look target position, if one has been assigned if(lookObj != null) { animator.SetLookAtWeight(1); animator.SetLookAtPosition(lookObj.position); } // Set the right hand target position and rotation, if one has been assigned if(rightHandObj != null) { animator.SetIKPositionWeight(AvatarIKGoal.RightHand,1); animator.SetIKRotationWeight(AvatarIKGoal.RightHand,1); animator.SetIKPosition(AvatarIKGoal.RightHand,rightHandObj.position); animator.SetIKRotation(AvatarIKGoal.RightHand,rightHandObj.rotation); } } //if the IK is not active, set the position and rotation of the hand and head back to the original position else { animator.SetIKPositionWeight(AvatarIKGoal.RightHand,0); animator.SetIKRotationWeight(AvatarIKGoal.RightHand,0); animator.SetLookAtWeight(0); } } } }

Поскольку мы не хотим, чтобы персонаж захватывал весь объект своей рукой, мы располагаем сферу в месте, в котором рука должна держать цилиндр и, соответственно, поворачиваем его.