Wie wende ich eine Skelettanimation aus einer .x-Datei (Direct X) an?

8

Mit dem .x-Format zum Exportieren eines Modells aus Blender kann ich ein Netz, einen Anker und eine Animation laden. Ich habe keine Probleme beim Generieren des Netzes und beim Anzeigen von Modellen im Spiel. Zusätzlich habe ich Animationen und den Anker richtig in entsprechende Datenstrukturen geladen.

Mein Problem besteht darin, die Animation ordnungsgemäß auf die Modelle anzuwenden. Ich habe den Rahmen für die Anwendung der Modelle und den Code für die Auswahl von Animationen und das Durchlaufen von Frames.

Soweit ich weiß, liefert das AnimationKeys im Inneren die AnimationSetTransformationen, um die Bindungspose in die Pose im animierten Frame umzuwandeln. Als kleines Beispiel:

Animation {
  {Armature_001_Bone}
  AnimationKey { 
    2; //Position
    121; //number of frames
    0;3;     0.000000, 0.000000, 0.000000;;,
    1;3;     0.000000, 0.000000, 0.005524;;,
    2;3;     0.000000, 0.000000, 0.022217;;,
    ...
  }
  AnimationKey { 
    0; //Quaternion Rotation 
    121;
    0;4;   -0.707107, 0.707107, 0.000000, 0.000000;;,
    1;4;   -0.697332, 0.697332, 0.015710, 0.015710;;,
    2;4;   -0.684805, 0.684805, 0.035442, 0.035442;;,
    ...
  }
  AnimationKey { 
    1; //Scale
    121;
    0;3;     1.000000, 1.000000, 1.000000;;,
    1;3;     1.000000, 1.000000, 1.000000;;,
    2;3;     1.000000, 1.000000, 1.000000;;,
    ...
  }
}

Um Frame 2 anzuwenden, würde ich die Position, Drehung und Skalierung von Frame 2 übernehmen, daraus eine Transformationsmatrix erstellen (nennen Transform_A) und diese Matrix auf die Scheitelpunkte anwenden, die von Armature_001_Boneihren Gewichten gesteuert werden. Also würde ich TransformAin meinen Shader stopfen und den Scheitelpunkt transformieren. Etwas wie:

vertexPos = vertexPos * bones[ int(bfs_BoneIndices.x) ] * bfs_BoneWeights.x;

Wo bfs_BoneIndicesund bfs_BoneWeightssind Werte, die für den aktuellen Scheitelpunkt spezifisch sind.

Beim Laden in die Netzscheitelpunkte transformiere ich sie durch das rootTransformund das meshTransform. Dies stellt sicher, dass sie für die Anzeige der Bindungspose richtig ausgerichtet und skaliert sind.

Das Problem ist, dass beim Erstellen dieser Transformationsmatrix (unter Verwendung der Position, Drehung und Skalierung aus der Animation) der Scheitelpunkt nicht richtig transformiert wird. Es geht wahrscheinlich um mehr als nur um die Verwendung der Animationsdaten. Ich habe auch versucht, die Knochentransformationshierarchien anzuwenden, immer noch keine Würfel. Grundsätzlich habe ich einige verdrehte Modelle. Es sollte auch beachtet werden, dass ich in openGL arbeite, daher sollten alle Matrixtransponierungen, die möglicherweise angewendet werden müssen, berücksichtigt werden.

Welche Daten benötige ich und wie kombiniere ich sie, um .x-Animationen auf Modelle anzuwenden?

Ich habe einige Modelle gemacht, wie das aussieht, falls das nützlich ist.

Zuerst wollte ich nur die Übersetzung testen, das ist ein schwankender Kopf, wie es in Blender aussieht:

http://i.stack.imgur.com/NAc4B.gif

Und wie es im Spiel aussieht (egal die Farben):

http://i.stack.imgur.com/nj2du.gif

Für eine einfache Drehung war die Animation der Kopf, der sich um 360 Grad um die vertikale Achse drehte. So sieht das im Spiel aus:

http://i.stack.imgur.com/gVyUW.gif

Beachten Sie, dass es keine Neigung geben sollte, sondern nur eine Drehung wie ein Karussell.

Aktualisieren

Ich habe den Übersetzungsteil der Animation in Arbeit. Aber es fühlt sich irgendwie hackig an und ich sehe nicht, wie ich es auf die Rotation anwenden soll.

Die Übersetzung funktioniert folgendermaßen:

  1. Nehmen Sie die Position aus dem Animationsrahmen und tauschen Sie die Werte yund zaus
  2. Übersetzen Sie die Transformationsmatrix um die geänderte Position
  3. Transponieren Sie die Transformationsmatrix
  4. Wenden Sie die Transformationsmatrix auf die Scheitelpunkte an

So kann es also funktionieren, aber wie soll es allgemein für Position, Skalierung und Rotation funktionieren?

MichaelHouse
quelle
2
Haben Sie den Scheitelpunkt vor dem Anwenden der Hautbildung mit der inversen Bindungsposenmatrix transformiert?
r2d2rigo
Ich habe, es hat eine Wirkung, aber es ist keine erwünschte. Es wirkt sich anscheinend auf die visuelle Darstellung des Modells aus (invertiert alle Normalen, sieht aus wie), hat jedoch keinen Einfluss auf die Bewegung / Drehung / Skalierung.
MichaelHouse
Könnten Sie einen Screenshot veröffentlichen, der das Problem veranschaulicht?
r2d2rigo
@ r2d2rigo Ich habe mit animierten Gifs aktualisiert.
MichaelHouse
1
Ich mache hier hauptsächlich Brainstorming, aber wenn sich die Vektorreihenfolge (xyz, zxy) zwischen .x und opengl unterscheidet, würden Sie einige lustige Effekte erzielen. Die Tatsache, dass das Transponieren hilft, bedeutet, dass für die Position ein Spalten- (oder Zeilen-) Vektor verwendet wird und nicht umgekehrt. en.wikipedia.org/wiki/… enthält eine Beschreibung der Rotationen für 2D. Ich weiß allerdings nicht genau, wie ich eine Quaternion ändern soll. Oder der Unterschied zwischen .x und openGL-Formaten
Daniel Carlsson

Antworten:

3

Es sollte auch beachtet werden, dass ich in openGL arbeite, daher sollten alle Matrixtransponierungen, die möglicherweise angewendet werden müssen, berücksichtigt werden.

Vielleicht ist das der Schlüssel. Haben Sie versucht, einige Z-Koordinaten zu negieren, um sie von DirectX-Koordinaten für Linkshänder in OpenGL-Koordinaten für Rechtshänder umzuwandeln?

Sie können alle Zs negieren, bevor Sie Ihre Transformationen anwenden. Wenn Sie fertig sind, können Sie erneut negieren, um festzustellen, ob die Ausgabe besser wird.

BEARBEITEN

Das Vertauschen der Y- und Z-Koordinaten ist eine weitere Möglichkeit, die Händigkeit zu ändern. Sie müssen also Ihre Transformationen anpassen. Weitere Informationen finden Sie in diesem Dokument .

Die Tatsache, dass Sie Ihre Matrix transponieren müssen, bedeutet auch, dass Sie von Zeilenmajor zu Spaltenmajor oder umgekehrt wechseln. Dies sollte weniger kompliziert zu handhaben sein, Sie müssen wahrscheinlich nur den richtigen "Ort" finden, um dies in Ihrer Pipeline zu tun. Ich rate nur, aber es bei Quaternionen zu tun, könnte einfach bedeuten, sie in Matrizen zu ändern, Quaternionen zu transponieren und zurück zu gehen, wenn Ihre Math-Bibliothek diese Option anbietet.

Laurent Couvidou
quelle
Erzählen Sie mir, was ich hier bereits weiß ...
MichaelHouse
1
Nun, Sie haben in Ihrer Frage die Händigkeit nicht erwähnt, und ich glaube, das ist zumindest ein Teil Ihres Problems. Ich kann nicht davon ausgehen, dass Sie wissen oder nicht wissen, dass DirectX und OpenGL in diesem Punkt unterschiedliche Konventionen befolgen (dies ist tatsächlich etwas komplexer ). Beachten Sie, dass dies anderen Personen, die diese Frage lesen, helfen kann , sie erwähnt zu sehen. Dies ist der Sinn dieser gesamten Website.
Laurent Couvidou
Fair genug, du hast recht. Ich habe nur angedeutet, dass ich über die Konvertierungen Bescheid wusste, die stattfinden mussten, als ich die Verwendung von OpenGL erwähnte, und über die Transponierungen, die stattfinden mussten.
MichaelHouse
2

OK, ich habe etwas mehr Zeit dafür aufgewendet und es für mehrere Knochen zum Laufen gebracht und deutlich weniger hackartig (wenn auch immer noch ein wenig). Das System, das ich ursprünglich hatte, war fast korrekt. Die Hauptprobleme waren jedoch:

  1. Blender speichert Quaternionen im W-, X-, Y- und Z- Format. Ich habe sie X, Y, Z, W geladen .
  2. Mein Code zum Konvertieren eines Quaternions in eine Matrix war falsch. Ich bin mir eigentlich immer noch nicht sicher, warum dies falsch ist, aber ich weiß, dass der folgende Code als Teil meiner Quaternion-Klasse funktioniert:

    public Matrix4f toMatrix() {
        Matrix4f tmpMatrix = new Matrix4f();
        if (this.length() > 0)
            this.normalise();
    
        float xx = this.x * this.x;
        float xy = this.x * this.y;
        float xz = this.x * this.z;
        float wx = this.x * this.w;
    
        float yy = this.y * this.y;
        float yz = this.y * this.z;
        float wy = this.y * this.w;
    
        float zz = this.z * this.z;
        float wz = this.z * this.w;
        //note the "* -1"s
        //I needed those to make the same matrices Blender was expecting
        tmpMatrix.m00 = (1.0f - 2.0f * (yy + zz)) * -1;
        tmpMatrix.m01 = (       2.0f * (xy - wz)) * -1;
        tmpMatrix.m02 =        2.0f * (xz + wy);
        tmpMatrix.m03 = 0;
    
        tmpMatrix.m10 =        2.0f * (xy + wz);
        tmpMatrix.m11 = 1.0f - 2.0f * (xx + zz);
        tmpMatrix.m12 = (       2.0f * (yz - wx)) * -1;
        tmpMatrix.m13 = 0;
    
        tmpMatrix.m20 =        2.0f * (xz - wy);
        tmpMatrix.m21 =        2.0f * (yz + wx);
        tmpMatrix.m22 = (1.0f - 2.0f * (xx + yy)) * -1;
        tmpMatrix.m23 = 0;
        return tmpMatrix;
    }

Davon abgesehen ist es nur:

finalMatrix = boneMatrix * skinMatrix * invertedBindMatrix;

Notiere dass der boneMatrix animierten Matrizen für jeden Knochen anstelle der Bindematrizen verwendet werden sollten. Es sollte auch alle übergeordneten Matrizen wie folgt erhalten:

private Matrix4f getBoneMatrix() {
        if (parent == null) {
            return Matrix4f.mul(Matrix4f.mul(parentArmature.getBindMatrix(), parentArmature.getArmatureRootMatrix(), null), boneMatrix, null);
        } else {
            return Matrix4f.mul(parent.getBoneMatrix(), boneMatrix, null);
        }
    }

Und es funktioniert. Hinterlasse einen Kommentar, wenn du Klarstellung brauchst.

MichaelHouse
quelle
Ich habe in meinem Blog einige allgemeinere Informationen darüber geschrieben, wie dies funktioniert: byte56.com/2012/12/the-blender-connection.html
MichaelHouse