Як запрограмувати анімацію з одного скелета на інший?


23

Я намагаюся написати код для передачі анімації, розробленої для того, щоб один скелет виглядав правильно на інший скелет. Анімаційні джерела складаються лише з обертів, за винятком перекладів у корені (вони є анімацією mocap з бази даних захоплення руху КМУ ). У багатьох 3D-додатках (наприклад, Майя) вбудований цей об'єкт, але я намагаюся написати (дуже просту) версію його для своєї гри.

Я провів деяку роботу з картографування кісток, і оскільки скелети є ієрархічно схожими (двоногі), я можу зробити картування кісток 1: 1 для всього, окрім хребта (над цим можна працювати пізніше). Проблема, однак, полягає в тому, що основи скелета / зв'язування є різними, а кістки - різними масштабами (коротше / довше), тож якщо я просто скопіюю обертання прямо, це виглядає дуже дивно.

Нижче я спробував декілька речей, подібних до рішення lorancou, безрезультатно (тобто помножуючи кожен кадр в анімації на кістковий множник). Якщо у когось є ресурси на подібні речі (папери, вихідний код тощо), це було б дуже корисно.


Як ти очікуєш, що я проігнорую хвіст і річ між ніг? : P
якD

2
@kaoD Якщо вам потрібно запитати, скелет вкорінюється на (0,0), тому там є підроблена кістка. Що стосується хвоста ... всі знають, що життя краще, якщо у вас є хвіст. Я завжди думав, що це буде ефективно для таких речей, як носіння чашок кави та балансування на кінцівках дерева.
Роберт Фрейзер

Я бачив демонстрацію цього в реальному часі, коли для анімації моделі, відображеної в xna, використовувався кінект. Подумайте, що код був на сайті з відкритим кодом. Буде шукати ...
Джордж Дакетт


Я підозрюю, що ваша проблема пов'язана швидше з чіткими позими зв’язування, ніж зі збільшенням кісток, ви можете спробувати це виокремити. Наприклад, почніть з оригінального скелета, нанесіть на нього кілька кісток, щоб створити новий скелет, і подивіться, чи ваш алгоритм порушений з цим. Якщо цього не відбувається, перезапустіть оригінальний скелет, але на цей раз не масштабуйте кістки, просто оберніть їх і подивіться, чи зламається ваш алгоритм. Якщо це так, то так, мабуть, є додаткова трансформація для виконання десь.
Лоран Кувіду

Відповіді:


8

Проблема полягала в чисельній стабільності. Приблизно 30 годин роботи над цим протягом 2 місяців, тільки щоб зрозуміти, що я робив це правильно з самого початку. Коли я орто-нормалізував матриці обертання перед тим, як підключити їх до коду перешкоди, просте рішення множення джерела *, обернене (цільове), спрацювало чудово. Звичайно, для ретаргетингу є набагато більше, ніж це (зокрема, беручи до уваги різні форми скелета, тобто ширину плечей тощо). Ось код, який я використовую для простого, наївного підходу, якщо хтось цікавий:

    public static SkeletalAnimation retarget(SkeletalAnimation animation, Skeleton target, string boneMapFilePath)
    {
        if(animation == null) throw new ArgumentNullException("animation");
        if(target == null) throw new ArgumentNullException("target");

        Skeleton source = animation.skeleton;
        if(source == target) return animation;

        int nSourceBones = source.count;
        int nTargetBones = target.count;
        int nFrames = animation.nFrames; 
        AnimationData[] sourceData = animation.data;
        Matrix[] sourceTransforms = new Matrix[nSourceBones];
        Matrix[] targetTransforms = new Matrix[nTargetBones];
        AnimationData[] temp = new AnimationData[nSourceBones];
        AnimationData[] targetData = new AnimationData[nTargetBones * nFrames];

        // Get a map where map[iTargetBone] = iSourceBone or -1 if no such bone
        int[] map = parseBoneMap(source, target, boneMapFilePath);

        for(int iFrame = 0; iFrame < nFrames; iFrame++)
        {
            int sourceBase = iFrame * nSourceBones;
            int targetBase = iFrame * nTargetBones;

            // Copy the root translation and rotation directly over
            AnimationData rootData = targetData[targetBase] = sourceData[sourceBase];

            // Get the source pose for this frame
            Array.Copy(sourceData, sourceBase, temp, 0, nSourceBones);
            source.getAbsoluteTransforms(temp, sourceTransforms);

            // Rotate target bones to face that direction
            Matrix m;
            AnimationData.toMatrix(ref rootData, out m);
            Matrix.Multiply(ref m, ref target.relatives[0], out targetTransforms[0]);
            for(int iTargetBone = 1; iTargetBone < nTargetBones; iTargetBone++)
            {
                int targetIndex = targetBase + iTargetBone;
                int iTargetParent = target.hierarchy[iTargetBone];
                int iSourceBone = map[iTargetBone];
                if(iSourceBone <= 0)
                {
                    targetData[targetIndex].rotation = Quaternion.Identity;
                    Matrix.Multiply(ref target.relatives[iTargetBone], ref targetTransforms[iTargetParent], out targetTransforms[iTargetBone]);
                }
                else
                {
                    Matrix currentTransform, inverseCurrent, sourceTransform, final, m2;
                    Quaternion rot;

                    // Get the "current" transformation (transform that would be applied if rot is Quaternion.Identity)
                    Matrix.Multiply(ref target.relatives[iTargetBone], ref targetTransforms[iTargetParent], out currentTransform);
                    Math2.orthoNormalize(ref currentTransform);
                    Matrix.Invert(ref currentTransform, out inverseCurrent);
                    Math2.orthoNormalize(ref inverseCurrent);

                    // Get the final rotation
                    Math2.orthoNormalize(ref sourceTransforms[iSourceBone], out sourceTransform);
                    Matrix.Multiply(ref sourceTransform, ref inverseCurrent, out final);
                    Math2.orthoNormalize(ref final);
                    Quaternion.RotationMatrix(ref final, out rot);

                    // Calculate this bone's absolute position to use as next bone's parent
                    targetData[targetIndex].rotation = rot;
                    Matrix.RotationQuaternion(ref rot, out m);
                    Matrix.Multiply(ref m, ref target.relatives[iTargetBone], out m2);
                    Matrix.Multiply(ref m2, ref targetTransforms[iTargetParent], out targetTransforms[iTargetBone]);
                }
            }
        }

        return new SkeletalAnimation(target, targetData, animation.fps, nFrames);
    }

чи оновлено код на цій сторінці з моменту написання? Нелегко намагаються зрозуміти без контексту двигуна, який його використовує. Я також намагаюся зробити ретаргетинг анімації. Було б чудово мати псевдокод кроків, як впоратися з ретаргетингом.
SketchpunkLabs

4

Я вважаю, що ваш найпростіший варіант - просто зіставити оригінальну пов'язуючу позу з вашим новим скелетом, якщо у вас є можливість (якщо ваш новий скелет ще не знятий).

Якщо ви не можете цього зробити, ось щось ви могли б спробувати. Це просто інтуїція, я, мабуть, пропускаю багато речей, але це може допомогти вам знайти світло. Для кожної кістки:

  • У вашій "старій" позі прив'язки у вас є один кватерніон, який описує відносне обертання цієї кістки порівняно з її батьківською кісткою. Ось підказка, як його знайти. Давайте назвемо це q_old.

  • Там само. для вашої "нової" прив'язки пози, давайте назвемо це q_new.

  • Ви можете знайти відносне обертання від "нової" позиції прив'язки до "старої" пози бункера, як описано тут . Ось так q_new_to_old = inverse(q_new) * q_old.

  • Потім в одному анімаційному ключі ви отримали свій один кватерніон, який перетворює цю кістку зі "старої" прив'язки в анімаційну позу. Давайте назвемо це q_anim.

Замість використання q_animбезпосередньо спробуйте використовувати q_new_to_old * q_anim. Це повинно "скасувати" різницю орієнтації між позиціями зв'язування, перш ніж застосовувати анімацію.

Це може зробити трюк.

EDIT

Ваш код вище, здається, відповідає логіці, яку я тут описую, але щось перевернуто. Замість цього:

multipliers[iSourceBone] = Quaternion.Invert(sourceBoneRot) * targetBoneRot;

Ви можете спробувати це:

multipliers[iSourceBone] = Quaternion.Invert(targetBoneRot) * sourceBoneRot;

Я думаю, що перед тим, як застосувати анімацію джерела, вам потрібно перетворитись від цілі до джерела, щоб отримати таку саму кінцеву орієнтацію.


Позиції прив'язки як джерел, так і цілей будуть різними, тому я реалізую це :-). Дійсно, множення на обертання цільового обертання було першим, що я спробував. Я спробував перерахувати обертання кісток відповідно до ваших пропозицій, але результат був таким же. Ось відео про те, що відбувається не так: youtube.com/watch?v=H6Qq37TM4Pg
Роберт Фрейзер

Ви впевнені, що ви завжди виражаєте свої обертання відносно батьківської кістки? Переглядаючи ваше відео, схоже, що ви десь використовуєте абсолютний / світовий обертання, де замість цього слід використовувати поворот відносно батьків.
Лоран Кувіду

Так, я впевнений, що тут я використовую відносні перетворення (я намагався з абсолютами, це виглядає набагато дивніше). Я оновив ОП з кодом, який я використовував для цього відео. Замість того, щоб намагатися налагодити це таким чином, я скоріше побачу якийсь вихідний код або підручники, де це було зроблено успішно, тоді я можу зрозуміти, що я роблю не так.
Роберт Фрейзер

Звичайно, але, можливо, немає підручника, щоб зробити саме це :) Я думаю, що ви перевернули щось у своєму коді вище, я відредагую свою відповідь.
Лоран Кувіду

Я перепробував багато способів, і жоден з них не працював. Я спробую насправді обчислити глобальні обертання на основі кадру, щоб побачити, що відбувається не так. Дякую за вашу допомогу; Я дам вам 100 повторів.
Роберт Фрейзер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.