Làm thế nào để lập trình lại mục tiêu hoạt hình từ bộ xương này sang bộ xương khác?


23

Tôi đang cố gắng viết mã để chuyển hình ảnh động được thiết kế cho một bộ xương để trông chính xác trên bộ xương khác. Các hình động nguồn chỉ bao gồm các phép quay ngoại trừ các bản dịch trên thư mục gốc (chúng là các hình ảnh động mocap từ cơ sở dữ liệu chụp chuyển động CMU ). Nhiều ứng dụng 3D (ví dụ Maya) có sẵn tiện ích này, nhưng tôi đang cố gắng viết phiên bản (rất đơn giản) cho trò chơi của mình.

Tôi đã thực hiện một số công việc về lập bản đồ xương và vì các bộ xương tương tự về thứ bậc (hai chân), tôi có thể thực hiện lập bản đồ xương 1: 1 cho mọi thứ trừ cột sống (có thể hoạt động trên đó sau). Tuy nhiên, vấn đề là bộ xương cơ sở / tư thế liên kết là khác nhau và xương có quy mô khác nhau (ngắn hơn / dài hơn), vì vậy nếu tôi chỉ sao chép xoay thẳng qua nó trông rất lạ.

Tôi đã thử một số thứ tương tự như giải pháp của lorancou dưới đây nhưng không có kết quả (tức là nhân từng khung trong hoạt hình bằng một số nhân cụ thể của xương). Nếu bất cứ ai có bất kỳ tài nguyên nào về những thứ như thế này (giấy tờ, mã nguồn, v.v.), điều đó sẽ thực sự hữu ích.


Làm thế nào để bạn mong đợi tôi bỏ qua đuôi và điều giữa hai chân? : P
kaoD

2
@kaoD Nếu bạn phải hỏi, bộ xương bắt nguồn từ (0,0) nên có xương giả ở đó. Về phần đuôi ... mọi người đều biết cuộc sống sẽ tốt hơn nếu bạn có một cái đuôi. Tôi đã luôn nghĩ rằng nó sẽ hiệu quả cho những việc như mang cốc cà phê và giữ thăng bằng trên cành cây.
Robert Fraser

Tôi đã thấy một bản demo thời gian thực về điều này trong đó một động vật được sử dụng để làm động một mô hình được hiển thị trong xna. Hãy nghĩ rằng mã là trên một trang web nguồn mở. Sẽ tìm kiếm ...
George Duckett


Tôi nghi ngờ rằng vấn đề của bạn là nhiều hơn với các tư thế liên kết riêng biệt hơn là với tỷ lệ xương, bạn có thể thử cách ly điều đó. Ví dụ, bắt đầu từ bộ xương ban đầu, chia tỷ lệ một số xương trên nó để tạo bộ xương mới và xem liệu thuật toán của bạn có bị hỏng với bộ xương này không. Nếu không, sau đó khởi động lại từ bộ xương ban đầu nhưng lần này không thu nhỏ xương, chỉ cần xoay chúng và xem thuật toán của bạn có bị hỏng không. Nếu đúng như vậy, thì có lẽ có một biến đổi bổ sung để thực hiện ở đâu đó.
Laurent Couvidou

Câu trả lời:


8

Vấn đề là một trong những ổn định số. Khoảng 30 giờ làm việc này trong suốt 2 tháng, chỉ để nhận ra tôi đã làm nó ngay từ khi bắt đầu. Khi tôi chuẩn hóa các ma trận xoay trước khi cắm chúng vào mã nhắm mục tiêu lại, giải pháp đơn giản là nhân nguồn * nghịch đảo (đích) đã hoạt động hoàn hảo. Tất nhiên, có nhiều thứ để nhắm mục tiêu hơn thế (đặc biệt, có tính đến các hình dạng khác nhau của bộ xương, tức là chiều rộng vai, v.v.). Đây là mã tôi đang sử dụng cho cách tiếp cận đơn giản, đơn giản, nếu có ai tò mò:

    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);
    }

mã trên trang này đã được cập nhật kể từ khi nó được viết chưa? Có một thời gian khó khăn để cố gắng để hiểu mà không có bối cảnh của động cơ sử dụng nó. Tôi cũng đang cố gắng thực hiện nhắm mục tiêu lại hoạt hình. Sẽ thật tuyệt khi có một số mã giả của các bước về cách xử lý nhắm mục tiêu lại.
Phác thảo

4

Tôi tin rằng tùy chọn dễ nhất của bạn chỉ đơn giản là khớp với tư thế liên kết ban đầu với bộ xương mới của bạn nếu bạn có khả năng (nếu bộ xương mới của bạn chưa được lột da).

Nếu bạn không thể làm điều đó, đây là thứ bạn có thể thử. Đây chỉ là trực giác, tôi có thể nhìn ra nhiều thứ, nhưng nó có thể giúp bạn tìm thấy ánh sáng. Đối với mỗi xương:

  • Trong tư thế ràng buộc "cũ" của bạn, bạn đã có một bộ tứ mô tả sự xoay tương đối của xương này so với xương cha mẹ của nó . Đây là một gợi ý cho làm thế nào để tìm thấy nó. Hãy gọi nó là q_old.

  • Ibid. cho tư thế ràng buộc "mới" của bạn, hãy gọi nó q_new.

  • Bạn có thể tìm thấy vòng xoay tương đối từ tư thế liên kết "mới" sang tư thế bin "cũ", như được mô tả ở đây . Đó là q_new_to_old = inverse(q_new) * q_old.

  • Sau đó, trong một khóa hoạt hình, bạn đã có một bộ tứ biến đổi xương đó từ tư thế liên kết "cũ" thành tư thế hoạt hình. Hãy gọi nó là cái này q_anim.

Thay vì sử dụng q_animtrực tiếp, hãy thử sử dụng q_new_to_old * q_anim. Điều này sẽ "hủy bỏ" sự khác biệt định hướng giữa các tư thế liên kết, trước khi áp dụng hình ảnh động.

Nó có thể làm điều khó khăn.

CHỈNH SỬA

Mã của bạn ở trên dường như tuân theo logic tôi đang mô tả ở đây, nhưng có gì đó bị đảo ngược. Thay vì làm điều này:

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

Bạn có thể thử điều đó:

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

Tôi nghĩ rằng bạn cần chuyển đổi từ mục tiêu sang nguồn của mình trước khi áp dụng hoạt hình nguồn, để có được định hướng cuối cùng.


Các tư thế ràng buộc của cả nguồn và mục tiêu sẽ thay đổi, đó là lý do tại sao tôi thực hiện điều này :-). Thật vậy, nhân với nghịch đảo của vòng quay mục tiêu là điều đầu tiên tôi thử. Tôi đã thử tính toán lại các chuyển động xoay xương, theo đề nghị của bạn, nhưng kết quả là như nhau. Đây là video về những gì đang xảy ra: youtube.com/watch?v=H6Qq37TM4Pg
Robert Fraser

Bạn có chắc chắn rằng bạn luôn thể hiện sự xoay vòng của mình tương đối với xương cha mẹ? Xem video của bạn, có vẻ như bạn đang sử dụng xoay vòng tuyệt đối / thế giới ở một nơi nào đó, nơi bạn nên sử dụng xoay vòng tương đối so với cha mẹ thay thế.
Laurent Couvidou

Có, tôi khá chắc chắn rằng tôi đang sử dụng các biến đổi tương đối ở đây (Tôi đã thử với các tuyệt đối, nó trông lạ hơn nhiều). Tôi đã cập nhật OP với mã tôi đang sử dụng cho video này. Thay vì cố gắng gỡ lỗi theo cách này, tôi muốn xem một số mã nguồn hoặc hướng dẫn nơi nó được thực hiện thành công, sau đó tôi có thể tìm ra những gì tôi đang làm sai.
Robert Fraser

Chắc chắn, nhưng có lẽ không có hướng dẫn để làm chính xác điều đó :) Tôi nghĩ rằng bạn đã đảo ngược một cái gì đó trong mã của bạn ở trên, tôi sẽ chỉnh sửa câu trả lời của tôi.
Laurent Couvidou

Tôi đã thử nhiều cách, và không có cách nào hiệu quả. Tôi sẽ cố gắng thực sự tính toán các vòng quay toàn cầu trên cơ sở từng khung hình để xem điều gì đang xảy ra. Cám ơn sự giúp đở cuả bạn; Tôi sẽ cung cấp cho bạn 100 đại diện.
Robert Fraser
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.