한 골격에서 다른 골격으로 애니메이션을 프로그래밍 방식으로 재 타겟팅하는 방법은 무엇입니까?


23

한 골격이 다른 골격에서 올바르게 보이도록 설계된 애니메이션을 전송하는 코드를 작성하려고합니다. 소스 애니메이션은 루트에서의 변환을 제외하고 회전으로 만 구성됩니다 ( CMU 모션 캡처 데이터베이스 의 mocap 애니메이션 ). 많은 3D 응용 프로그램 (예 : Maya)에는이 기능이 내장되어 있지만 게임용으로 (아주 간단한) 버전을 작성하려고합니다.

뼈 매핑에 대한 몇 가지 작업을 수행했으며 골격이 계층 적으로 유사하기 때문에 (biped) 척추 이외의 모든 것에 대해 1 : 1 뼈 매핑을 수행 할 수 있습니다 (나중에 작동 할 수 있음). 그러나 문제는 기본 스켈레톤 / 바인드 포즈가 다르고 뼈가 다른 스케일 (더 짧거나 길다)이므로 회전을 똑바로 복사하면 매우 이상하게 보입니다.

나는 lorancou의 솔루션과 비슷한 많은 것들을 시도하지 않았다 (즉, 애니메이션의 각 프레임에 뼈 특정 승수를 곱하는 것). 누구든지 이와 같은 자료 (종이, 소스 코드 등)에 대한 자료가 있다면 실제로 도움이 될 것입니다.


꼬리와 다리 사이의 일을 어떻게 무시하겠습니까? : P
kaoD

2
@kaoD 만약 당신이 물어봐야한다면, 골격은 (0,0)에 뿌리를두고 있으므로 가짜 뼈가 있습니다. 꼬리는 ... 꼬리가 있으면 삶이 더 좋다는 것을 모두가 알고 있습니다. 나는 항상 커피 잔을 들고 나무 가지에 균형을 맞추는 것과 같은 것이 효율적이라고 생각했습니다.
Robert Fraser

키네틱을 사용하여 xna에 표시된 모델에 애니메이션을 적용하는 실시간 데모를 보았습니다. 코드가 오픈 소스 사이트에 있다고 생각하십시오. 검색 것입니다 ...
조지 Duckett


나는 당신의 문제가 뼈 스케일링보다 뚜렷한 바인드 포즈와 관련이 있다고 생각합니다. 예를 들어 원래 골격에서 시작하여 골격을 몇 개 확장하여 새 골격을 만들고 알고리즘이이 골격과 충돌하는지 확인하십시오. 그렇지 않은 경우 원래 골격에서 다시 시작하지만 이번에는 뼈의 크기를 조절하지 않고 회전시켜 알고리즘이 깨지는 지 확인하십시오. 그렇다면 어딘가에서 수행 할 추가 변환이있을 것입니다.
Laurent Couvidou

답변:


8

문제는 수치 적 안정성 중 하나였습니다. 2 개월 동안이 작업에 대해 약 30 시간의 작업이있었습니다. 회전 행렬을 대상 코드에 꽂기 전에 직교 정규화하면 소스 곱하기의 간단한 솔루션 * 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.

  • 여기에 설명 된대로 "새"바인드 포즈에서 "오래된"빈 포즈까지의 상대 회전을 찾을 수 있습니다 . 그렇습니다 q_new_to_old = inverse(q_new) * q_old.

  • 그런 다음 하나의 애니메이션 키에 해당 뼈를 "이전"바인드 포즈에서 애니메이션 포즈로 변환하는 하나의 쿼터니언이 있습니다. 이것을 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?v=H6Qq37TM4Pg
Robert Fraser

항상 부모 뼈를 기준으로 회전을 표현하고 있습니까? 비디오를 보면 어딘가에서 부모 회전을 사용해야하는 절대 / 세계 회전을 사용하는 것처럼 보입니다.
Laurent Couvidou

예, 여기에 상대 변환을 사용하고 있다고 확신합니다 (절대적으로 시도했지만 훨씬 낯선 것처럼 보입니다).이 비디오에 사용했던 코드로 OP를 업데이트했습니다. 이 방법으로 디버깅하려고 시도하는 대신 소스 코드 또는 자습서가 성공적으로 완료된 것을보고 싶습니다. 그러면 내가 뭘 잘못하고 있는지 파악할 수 있습니다.
Robert Fraser

물론, 그러나 정확히 그렇게 할 수있는 튜토리얼이 없을 수도 있습니다.
Laurent Couvidou

나는 많은 방법을 시도했지만 아무도 효과가 없었습니다. 실제로 무엇이 잘못되었는지 확인하기 위해 프레임 단위로 전역 회전을 실제로 계산하려고합니다. 그래도 도움을 주셔서 감사합니다. 100 명의 담당자를 드리겠습니다.
Robert Fraser
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.