あるスケルトンから別のスケルトンにプログラムでアニメーションをリターゲットする方法は?


23

私は、あるスケルトンが別のスケルトンで正しく見えるように設計されたアニメーションを転送するコードを作成しようとしています。ソースアニメーションは、ルート上の変換(CMUモーションキャプチャデータベースのモーションキャプチャアニメーション)を除き、回転のみで構成されます。多くの3Dアプリケーション(Mayaなど)にはこの機能が組み込まれていますが、ゲーム用に(非常に単純な)バージョンを作成しようとしています。

ボーンマッピングでいくつかの作業を行いました。スケルトンが階層的に類似しているため(二足歩行)、脊椎以外のすべてに対して1:1のボーンマッピングを実行できます(後で作業できます)。ただし、問題は、ベースのスケルトン/バインドポーズが異なり、ボーンのスケールが異なる(短い/長い)ため、回転をそのままコピーすると、非常に奇妙に見えます。

私は以下のロランクーの解決策に似たいくつかのことを試みました(すなわち、アニメーションの各フレームにボーン固有の乗数を掛けます)。このようなもの(ペーパー、ソースコードなど)にリソースがある場合、それは非常に役立ちます。


尾と足の間のものを無視することをどのように期待しますか?:P
kaoD

2
@kaoD質問する必要がある場合、スケルトンのルートは(0,0)なので、そこに偽のボーンがあります。尾については...誰もがあなたが尾を持っていると人生が良くなることを知っています。私はいつも、コーヒーカップを運んだり、木の枝でバランスをとったりするのに効率的だと思っていました。
ロバートフレイザー

kinectを使用してxnaに表示されたモデルをアニメーション化する、このリアルタイムデモを見ました。コードはオープンソースのサイトにあったと思います。...検索します
ジョージ・ダケット


あなたの問題は、骨のスケーリングよりも明確なバインドポーズにあると思われるので、それを分離してみてください。たとえば、元のスケルトンから開始し、その上にいくつかのボーンをスケーリングして新しいスケルトンを作成し、アルゴリズムがこのスケルトンで壊れるかどうかを確認します。そうでない場合は、元のスケルトンから再起動しますが、今回はボーンをスケーリングせず、単にボーンを回転させて、アルゴリズムが壊れるかどうかを確認します。もしそうなら、おそらくどこかで実行する追加の変換があります。
ローランCouvidou

回答:


8

問題は数値安定性の1つでした。この作業には2か月間で約30時間かかりましたが、最初からそれを行っていたことがわかりました。回転行列をリターゲットコードに接続する前にオルソ正規化すると、source * inverse(target)を乗算するという単純なソリューションが完全に機能しました。もちろん、リターゲッティングにはそれ以上のものがあります(特に、スケルトンのさまざまな形状、つまり肩幅などを考慮に入れる)。誰かが興味を持っている場合、私はシンプルで素朴なアプローチに使用しているコードは次のとおりです。

    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

あなたの最も簡単なオプションは、可能性がある場合は、元のバインドポーズを新しいスケルトンと単に一致させることだと思います(新しいスケルトンがまだスキンされていない場合)。

それができない場合、ここで試してみてください。これは単なる直観であり、おそらく多くのことを見落としているでしょうが、それはあなたが光を見つけるのに役立つかもしれません。各骨について:

  • 「古い」バインドポーズには、ボーンと比較したこのボーンの相対的な回転を表すクォータニオンが1つあります。これを見つける方法のヒント次に示します。それを呼び出しましょう。q_old

  • 同上。「新しい」バインドポーズの場合、それを呼び出しましょうq_new

  • ここで説明するように、「新しい」バインドポーズから「古い」ビンポーズへの相対的な回転を見つけることができます。それがありますq_new_to_old = inverse(q_new) * q_old

  • 次に、1つのアニメーションキーに、その骨を「古い」バインドポーズからアニメーションポーズに変換する1つのクォータニオンがあります。これを1つとしましょうq_anim

q_anim直接使用する代わりに、を使用してみてくださいq_new_to_old * q_anim。これは、アニメーションを適用する前に、バインドポーズ間の方向の違いを「キャンセル」する必要があります。

それはトリックをするかもしれません。

編集

上記のコードは、ここで説明しているロジックに従っているようですが、逆になっています。これを行う代わりに:

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

あなたはそれを試すことができます:

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

ソースアニメーションを適用する前に、ターゲットからソースに変換して、同じ最終的な向きを取得する必要があると思います。


ソースとターゲットの両方のバインドポーズは変化するため、これを実装しているのはこのためです:-)。実際、最初に試したのは、ターゲットの回転の逆数を掛けることです。あなたの提案に従って、骨の回転を再計算しようとしましたが、結果は同じでした。問題の動画を次に示します:youtube.com/watch
Robert Fraser

親のボーンに対して相対的に回転を常に表現していると確信していますか?ビデオを見ると、どこかで絶対/世界の回転を使用しているように見えますが、代わりに親からの相対回転を使用する必要があります。
ローランCouvidou

はい、私はここで相対変換を使用していると確信しています(絶対値で試しましたが、見知らぬ人に見えます)。このビデオに使用していたコードでOPを更新しました。この方法でデバッグしようとするのではなく、正常に実行されたソースコードまたはチュートリアルを参照して、間違っていることを把握できます。
ロバートフレイザー

確かに、しかしそれを正確に行うためのチュートリアルはないかもしれません:)上記のコードで何かを反転させたと思うので、答えを編集します。
ローランCouvidou

私は多くの方法を試しましたが、どれもうまくいきませんでした。実際に、フレームごとにグローバルローテーションを計算して、何が問題なのかを確認します。ただし、ご協力いただきありがとうございます。100人の担当者をお渡しします。
ロバートフレイザー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.