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


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.