如何以编程方式将动画从一个骨架重定位到另一个骨架?


23

我正在尝试编写代码,以传输为一个骨架设计的动画,以便在另一个骨架上看起来正确。源动画仅由旋转组成,除了根部上的平移(它们是CMU运动捕获数据库中的mocap动画)。许多3D应用程序(例如Maya)都内置了此功能,但是我正在尝试为我的游戏编写一个(非常简单的)版本。

我已经完成了一些有关骨骼映射的工作,并且由于骨骼在层次结构上是相似的(两足动物),因此我可以对除脊柱以外的所有对象进行1:1骨骼映射(以后可以使用)。但是,问题在于基本骨骼/绑定姿势不同,骨骼的比例不同(较短/较长),因此,如果我直接复制旋转,则看起来很奇怪。

我尝试了许多类似于lorancou的以下解决方案的方法,但无济于事(即,将动画中的每个帧乘以特定于骨骼的乘法器)。如果任何人在此类内容(如文件,源代码等)上有任何资源,那将非常有帮助。


您如何期望我忽略尾巴和两腿之间的东西?:P
kaoD

2
@kaoD如果需要询问,骨骼的根系是(0,0),因此那里是假骨头。至于尾巴……每个人都知道,如果你有一条尾巴,生活会更好。我一直认为,这对于携带咖啡杯和在树枝上保持平衡是有效的。
罗伯特·弗雷泽

我已经看到了一个实时演示,其中使用kinect动画化了xna中显示的模型。认为代码在开源站点上。将会搜索...
George Duckett 2012年


我怀疑您的问题更多是与独特的绑定姿势有关,而不是与骨骼缩放有关,您可以尝试将其隔离。例如,从原始骨骼开始,在其上缩放几根骨骼以创建新骨骼,然后查看您的算法是否与此骨骼相符。如果不是,则从原始骨架重新启动,但是这次不缩放骨骼,只需旋转它们,看看算法是否中断。如果可以,那么是的,可能还有其他转换要在某个地方执行。
Laurent Couvidou 2012年

Answers:


8

问题是数值稳定性之一。在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

我相信,如果可能的话(如果新蒙皮尚未蒙皮),最简单的选择就是将原始绑定姿势与新蒙皮相匹配。

如果您不能这样做,可以尝试以下方法。这只是直觉,我可能忽略了很多事情,但这可能会帮助您找到光明。对于每个骨骼:

  • 在您的“旧”绑定姿势中,您有一个四元数来描述此骨骼与其父骨骼相比的相对旋转。这是有关如何找到它的提示。叫它。q_old

  • 同上 为您的“新”装订姿势,我们称之为q_new

  • 您可以找到从“新”绑定姿势到“旧” bin姿势的相对旋转,如此处所述。那是q_new_to_old = inverse(q_new) * q_old

  • 然后在一个动画关键帧中,您获得了一个四元数,该四元数将该骨骼从“旧”绑定姿势转换为动画姿势。我们称这个为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?v=H6Qq37TM4Pg
罗伯特·弗雷泽

您确定总是相对于父骨骼来表达旋转吗?观看视频时,您似乎在某处使用了绝对/世界旋转,而应该在其中使用相对父旋转。
Laurent Couvidou 2012年

是的,我相当确定我在这里使用了相对转换(我已经尝试过使用绝对值,这看起来很陌生)。我用这段视频中使用的代码更新了OP。与其尝试以这种方式调试它,不如看一些成功完成它的源代码或教程,然后我可以弄清楚我做错了什么。
罗伯特·弗雷泽

当然,但是也许没有教程可以做到这一点:)我想您在上面的代码中反转了一些内容,我将编辑我的答案。
Laurent Couvidou

我已经尝试了许多方法,但都没有成功。我将尝试实际基于每帧计算全局旋转,以查看出了什么问题。不过,谢谢您的帮助;我给你100代表。
罗伯特·弗雷泽
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.