วิธีการตั้งเป้าหมายใหม่ภาพเคลื่อนไหวโดยอัตโนมัติจากโครงกระดูกหนึ่งไปยังอีกโครงกระดูก?


23

ฉันพยายามเขียนโค้ดเพื่อถ่ายโอนอนิเมชั่นที่ถูกออกแบบมาเพื่อโครงกระดูกหนึ่งเพื่อให้ถูกต้องในโครงกระดูกอีกอัน ภาพเคลื่อนไหวต้นฉบับประกอบด้วยการหมุนยกเว้นการแปลบนรูท (เป็นภาพเคลื่อนไหว mocap จากฐานข้อมูลจับการเคลื่อนไหว CMU ) แอปพลิเคชั่น 3D จำนวนมาก (เช่น Maya) มีสิ่งอำนวยความสะดวกในตัว แต่ฉันพยายามเขียนเวอร์ชัน (ง่ายมาก) สำหรับเกมของฉัน

ฉันได้ทำงานเกี่ยวกับการทำแผนที่กระดูกและเนื่องจากโครงกระดูกมีความคล้ายคลึงกันตามลำดับขั้น (bipeds) ฉันสามารถทำแผนที่กระดูก 1: 1 สำหรับทุกสิ่งยกเว้นกระดูกสันหลัง (สามารถทำงานในภายหลัง) อย่างไรก็ตามปัญหาคือโครงกระดูกพื้นฐาน / การผูกโพสท่านั้นแตกต่างกันและกระดูกเป็นสเกลที่แตกต่างกัน (สั้นกว่า / ยาวกว่า) ดังนั้นหากฉันคัดลอกการหมุนตรงๆมันดูแปลกมาก

ฉันได้ลองทำหลายสิ่งคล้ายกับวิธีแก้ปัญหาของ lorancou ด้านล่างเพื่อไม่เกิดประโยชน์ (เช่นการคูณแต่ละเฟรมในภาพเคลื่อนไหวโดยตัวคูณกระดูกเฉพาะ) หากใครมีทรัพยากรในสิ่งเช่นนี้ (เอกสารซอร์สโค้ด ฯลฯ ) นั่นจะเป็นประโยชน์จริง ๆ


คุณคาดหวังให้ฉันไม่สนใจหางและสิ่งของระหว่างขาได้อย่างไร : P
kaoD

2
@ kaoD หากคุณต้องถามโครงกระดูกนั้นหยั่งรากที่ (0,0) ดังนั้นจึงมีกระดูกปลอมอยู่ที่นั่น ส่วนหาง ... ทุกคนรู้ว่าชีวิตดีกว่าถ้าคุณมีหาง ฉันคิดเสมอว่ามันจะมีประสิทธิภาพสำหรับสิ่งต่าง ๆ เช่นถือถ้วยกาแฟและปรับสมดุลให้กับกิ่งไม้
Robert Fraser

ฉันได้เห็นการสาธิตแบบเรียลไทม์ของที่ซึ่ง kinect ถูกใช้ในการเคลื่อนไหวโมเดลที่แสดงใน xna คิดว่ารหัสนั้นอยู่บนไซต์โอเพนซอร์ซ จะค้นหา ...
George Duckett


ฉันสงสัยว่าปัญหาของคุณจะเกิดจากการผูกที่แตกต่างกันมากกว่าการปรับขนาดกระดูกคุณสามารถลองแยกมันออกมา ตัวอย่างเช่นเริ่มต้นจากโครงกระดูกดั้งเดิมปรับขนาดกระดูกสองสามตัวเพื่อสร้างโครงกระดูกใหม่และดูว่าอัลกอริทึมของคุณแตกหักด้วยโครงร่างนี้หรือไม่ หากไม่เป็นเช่นนั้นให้รีสตาร์ทจากโครงกระดูกดั้งเดิม แต่คราวนี้ไม่ขยายขนาดกระดูกเพียงหมุนพวกเขาและดูว่าอัลกอริทึมของคุณแตกหรือไม่ ถ้าเป็นเช่นนั้นก็ใช่ว่าอาจมีการแปลงเพิ่มเติมเพื่อแสดงที่ไหนสักแห่ง
Laurent Couvidou

คำตอบ:


8

ปัญหาคือหนึ่งในเสถียรภาพเชิงตัวเลข ประมาณ 30 ชั่วโมงในการทำงานกับสิ่งนี้ในช่วง 2 เดือนเท่านั้นที่จะคิดออกว่าฉันทำมันถูกต้องตั้งแต่เริ่มต้น เมื่อฉันปรับการฝึกอบรมให้เป็นออร์โธไนซ์ก่อนที่จะเสียบเข้ากับรหัส retarget คำตอบง่ายๆของการคูณแหล่งที่มา * อินเวอร์ส (เป้าหมาย) ทำงานได้อย่างสมบูรณ์ แน่นอนว่ามีการกำหนดเป้าหมายใหม่มากกว่านั้น (โดยเฉพาะอย่างยิ่งโดยคำนึงถึงรูปร่างที่แตกต่างกันของโครงกระดูกเช่นความกว้างของไหล่ ฯลฯ ) นี่คือรหัสที่ฉันใช้สำหรับวิธีที่ง่ายและไร้เดียงสาหากใครอยากรู้:

    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

ฉันเชื่อว่าตัวเลือกที่ง่ายที่สุดของคุณคือการจับคู่การผูกแบบดั้งเดิมกับโครงกระดูกใหม่ของคุณหากคุณมีความเป็นไปได้

หากคุณไม่สามารถทำสิ่งต่อไปนี้เป็นสิ่งที่คุณสามารถลองได้ นี่เป็นเพียงสัญชาตญาณฉันอาจจะมองเห็นสิ่งต่าง ๆ มากมาย แต่มันอาจช่วยให้คุณค้นพบแสงสว่าง สำหรับแต่ละกระดูก:

  • ในท่าผูก "เก่า" คุณมี quaternion หนึ่งตัวที่อธิบายการหมุนสัมพัทธ์ของกระดูกนี้เมื่อเปรียบเทียบกับกระดูกแม่ นี่คือคำแนะนำสำหรับวิธีการค้นหา q_oldขอเรียกว่า

  • อ้าง สำหรับ "ใหม่" q_newผูกก่อให้เกิดของคุณขอเรียกว่า

  • คุณสามารถค้นหาญาติหมุนจาก "ใหม่" ผูกก่อให้ "เก่า" ถังท่า, ตามที่อธิบายไว้ที่นี่ 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
Robert Fraser

คุณแน่ใจหรือไม่ว่าคุณแสดงการหมุนของคุณกับกระดูกของผู้ปกครองอยู่เสมอ ดูวิดีโอของคุณดูเหมือนว่าคุณกำลังใช้การหมุนแบบสัมบูรณ์ / โลกบางแห่งซึ่งคุณควรใช้การหมุนแบบสัมพัทธ์กับผู้ปกครองแทน
Laurent Couvidou

ใช่ฉันค่อนข้างแน่ใจว่าฉันใช้การแปลงสัมพัทธ์ที่นี่ (ฉันได้ลองด้วยสมบูรณาญมันดูแปลก ๆ ) ฉันอัพเดท OP ด้วยรหัสที่ฉันใช้สำหรับวิดีโอนี้ แทนที่จะพยายามดีบั๊กด้วยวิธีนี้ฉันอยากเห็นซอร์สโค้ดหรือแบบฝึกหัดที่ทำสำเร็จแล้วจากนั้นฉันก็สามารถเข้าใจได้ว่าฉันทำอะไรผิด
Robert Fraser

แน่นอน แต่อาจจะไม่มีการสอนที่จะทำอย่างนั้น :) ฉันคิดว่าคุณกลับบางสิ่งบางอย่างในรหัสของคุณด้านบนฉันจะแก้ไขคำตอบของฉัน
Laurent Couvidou

ฉันลองมาหลายวิธีแล้วก็ไม่ได้ผลเลย ฉันจะลองคำนวณการหมุนรอบตัวจริงแบบต่อเฟรมเพื่อดูว่าเกิดอะไรขึ้น ขอบคุณสำหรับความช่วยเหลือของคุณ ฉันจะให้ 100 ตัวแทน
Robert Fraser
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.