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

我试图编写代码来传输为一个框架devise的animation,以便在另一个框架上看起来正确。 源animation仅包含旋转,除了在根上的翻译(它们是来自CMU动作捕捉数据库的animationanimation)。 许多3D应用程序(例如Maya)都内置了这个设备,但是我正在尝试为我的游戏编写一个(非常简单的)版本。

我已经做了一些关于骨骼映射的工作,并且由于骨架是分层相似的(两足动物),所以我可以对脊椎(除此之外)的所有东西都进行1:1的骨骼映射。 但是,问题是基本的骨架/绑定姿势是不同的,而骨骼是不同的尺度(更短/更长),所以如果我直接复制旋转,看起来很奇怪。

我尝试了一些类似于下面的lorancou的解决scheme的东西无济于事(即animation中的每个帧乘以一个骨骼特定的乘数)。 如果有人有这样的东西(论文,源代码等)的任何资源,这将是非常有益的。

Solutions Collecting From Web of "如何以编程方式将animation从一个骨架重新定位到另一个?"

问题是数值稳定性问题之一。 在这2个月的时间里,大约需要30个小时的工作,只是从一开始就搞清楚了。 在将旋转matrix插入到重定位代码之前,我对其进行了正规化,乘法源* 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); } 

如果你有可能的话(如果你的新骨架还没有皮肤),我相信你最简单的select就是简单地将原始的绑定姿势和你的新骨架相匹配。

如果你不能这样做,这是你可以尝试的东西。 这只是直觉,我可能忽略了很多东西,但它可能会帮助你find光明。 对于每个骨骼:

  • 在你的“旧”绑定姿势中,你有一个四元数描述了这个骨骼相对于它的骨骼的相对旋转。 这里有一个提示如何find它。 我们称之为q_old

  • 同上。 为你的“新”绑定姿势,让我们称之为q_new

  • 如下所述,您可以find从“新”绑定姿势到“旧”仓姿势的相对旋转。 这是q_new_to_old = inverse(q_new) * q_old

  • 然后在一个animation的关键,你有你的一个四元数,将骨骼从“旧”绑定姿势转换为animation姿势。 我们称之为q_anim

不要直接使用q_anim ,请尝试使用q_new_to_old * q_anim 。 在应用animation之前,这应该“取消”绑定姿势之间的方向差异。

它可能会诀窍。

编辑

你上面的代码似乎遵循我在这里描述的逻辑,但有些东西倒过来。 而不是这样做:

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

你可以试试这个:

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

我认为在应用源animation之前,您需要将目标转换为源代码,以获得相同的最终方向。

看到这个链接他们采取Kinect骨架,并映射到自定义模型显示在XNA。 它是开源的。 有关骨架映射,另请参见文档的这一部分 。