Animasyonları bir iskeletten diğerine programlı olarak nasıl yeniden hedefleyebilirim?


23

Bir iskeletin başka bir iskelette doğru görünmesi için tasarlanan animasyonları aktarmak için kod yazmaya çalışıyorum. Kaynak animasyonlar, yalnızca root'taki çeviriler haricindeki rotasyonlardan oluşur ( CMU hareket yakalama veritabanındaki mocap animasyonlarıdır ). Birçok 3D uygulamasında (örneğin Maya) bu tesis yerleşik olarak bulunur, ancak oyunum için bunun (çok basit) bir sürümünü yazmaya çalışıyorum.

Kemik haritalama konusunda bazı çalışmalar yaptım ve iskeletler hiyerarşik olarak benzer olduğundan (iki ayaklı), omurga dışındaki her şey için 1: 1 kemik haritalaması yapabilirim (daha sonra üzerinde çalışabilirim). Bununla birlikte sorun, temel iskelet / bağlama pozlarının farklı olması ve kemiklerin farklı ölçekler (daha kısa / daha uzun) olmasıdır; bu nedenle, sadece dönüşü düz bir şekilde kopyalarsam çok garip görünüyor.

Lorancou'nun çözümüne benzer bir çok şeyi boşuna boşuna yapmaya çalıştım (örn. Animasyondaki her kareyi kemiğe özgü bir çarpan ile çarpmak). Birisi böyle şeyler hakkında herhangi bir kaynağa sahipse (bildiri, kaynak kodu, vb.), Bu gerçekten yardımcı olacaktır.


Kuyruğu ve bacakların arasındaki şeyi görmezden gelmemi nasıl beklersin? : P
kaoD

2
@kaoD Sormanız gerekiyorsa, iskelet (0,0) 'a dayanır, bu yüzden orada sahte bir kemik vardır. Kuyruğa gelince ... herkes kuyruğun varsa hayatın daha iyi olduğunu bilir. Kahve fincanı taşımak ve ağaç uzuvlarını dengelemek gibi şeyler için her zaman etkili olacağını düşündüm.
Robert Fraser,

Bunun, xna'da gösterilen bir modeli canlandırmak için bir kinectin kullanıldığı gerçek zamanlı bir demo gördüm. Kodun açık kaynak kodlu bir sitede olduğunu düşünün. Arama
George Duckett


Sorunun, kemiklerin ölçeklendirilmesinden ziyade, belirgin cilt pozlarıyla daha fazla olduğundan şüpheleniyorum, bunu izole etmeye çalışabilirsiniz. Örneğin, orijinal iskeletten başlayın, yeni bir iskelet oluşturmak için üzerine birkaç kemik ölçekleyin ve algoritmanızın buna uyup uymadığını kontrol edin. Olmazsa, orijinal iskeletten yeniden başlayın ama bu sefer kemikleri ölçeklendirmeyin, sadece döndürün ve algoritmanızın kırılıp kırılmadığını görün. Eğer öyleyse, evet, muhtemelen bir yerde gerçekleştirmek için ek bir dönüşüm var.
Laurent Couvidou

Yanıtlar:


8

Sorun sayısal kararlılıktan biriydi. Bu konuda yaklaşık 30 saatlik çalışma 2 ay boyunca, sadece en başından beri doğru yaptığımı bulmak için. Döndürme matrikslerini yeniden geciktirme koduna bağlamadan önce orto-normalize ettiğimde, basitçe çarpan kaynak * ters (hedef) çözümü mükemmel sonuç verdi. Tabii ki, yeniden hedeflemekten daha fazlası var (özellikle iskeletin farklı şekillerini, yani omuz genişliğini vb. Dikkate alarak). Meraklı biri varsa, basit, naieve yaklaşım için kullanıyorum kodu İşte:

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

Bu sayfadaki kod yazıldıktan sonra güncellendi mi? Onu kullanan motorun bağlamı olmadan anlamaya çalışırken çok zorlanıyor. Ben de animasyon yeniden hedefleme yapmaya çalışıyorum. Yeniden hedeflemenin nasıl uygulanacağına dair adımların sahte koduna sahip olmak harika olurdu.
SketchpunkLabs

4

En kolay seçeneğinizin, basit bir olasılıkla (yeni iskelet henüz tenli değilse) orijinal bağ pozunu yeni iskeletinizle eşleştirmek olduğuna inanıyorum.

Bunu yapamazsan, işte deneyebileceğin bir şey. Bu sadece sezgi, muhtemelen pek çok şeye göz atıyorum, ancak ışığı bulmanıza yardımcı olabilir. Her kemik için:

  • "Eski" cilt pozunuzda, bu kemiğin ana kemiğine kıyasla göreceli rotasyonunu tanımlayan bir kuaterniyonunuz var . İşte onu bulmak için bir ipucu . Hadi diyelim .q_old

  • Ibid. "yeni" cilt pozun için diyelim q_new.

  • Göreceli rotasyonu burada "yeni" bağlama pozundan "eski" kutu pozuna kadar bulabilirsiniz . Bu q_new_to_old = inverse(q_new) * q_old.

  • Sonra bir animasyon anahtarında, bu kemiği "eski" bağlama pozundan animasyonlu bir poza dönüştüren bir kuaterniyonunuz olur. Buna bir diyelim q_anim.

q_animDoğrudan kullanmak yerine kullanmayı deneyin q_new_to_old * q_anim. Bu, animasyonu uygulamadan önce bağlama pozları arasındaki yön farklılıklarını "iptal etmelidir".

Hile yapabilir.

DÜZENLE

Yukarıdaki kodunuz burada tanımladığım mantığı izliyor gibi görünüyor, ancak bir şey tersine çevrildi. Bunu yapmak yerine:

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

Bunu deneyebilirsin:

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

Aynı son yönlendirmeyi elde etmek için kaynak animasyonu uygulamadan önce hedefinizden kaynağınıza dönüştürmeniz gerektiğini düşünüyorum.


Hem kaynakların hem de hedeflerin ciltleri değişecektir, bu yüzden bunu uyguladım :-). Aslında, hedef rotasyonun tersi ile çarpmak denedim ilk şeydi. Önerinize göre, kemik rotasyonlarını yeniden hesaplamayı denedim, ancak sonuç aynıydı. Neyin yanlış gittiğinin bir videosu: youtube.com/watch?v=H6Qq37TM4Pg
Robert Fraser

Rotasyonlarınızı her zaman bir ana kemiğe göre ifade ettiğinizden emin misiniz? Videonuzu gördüğünüzde, görece ebeveyn dönüşünü kullanmanız gereken bir yerde mutlak / dünya dönüşü kullanıyormuşsunuz gibi görünüyor.
Laurent Couvidou,

Evet, burada göreceli dönüşümleri kullandığımdan eminim (mutlakları denedim, çok garip görünüyor). OP'yi bu video için kullandığım kodla güncelledim. Bu şekilde hata ayıklamaya çalışmak yerine, başarılı bir şekilde yapıldığı bazı kaynak kodları veya dersleri görmeyi tercih ederim, o zaman yanlış yaptığımı anlayabilirim.
Robert Fraser

Tabii, ama belki de tam olarak bunu yapacak bir ders yok :) Yukarıdaki kodunuzda ters bir şey yaptığınızı düşünüyorum, cevabımı düzenleyeceğim.
Laurent Couvidou

Birçok yol denedim ve hiçbiri işe yaramadı. Neyin yanlış gittiğini görmek için aslında küresel rotasyonları kare başına hesaplamayı deneyeceğim. Yine de yardımlarınız için teşekkürler; Sana 100 temsilciyi vereceğim.
Robert Fraser
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.