Animation von 3D-Modellen Teil 06: Vertex Skinning

Alle notwendigen Vorbereitungen sind abgeschlossen, so dass wir nun mit der Animation unseres 3D-Modells beginnen können. Hierbei gilt es zunächst die Gesamttransformationsmatrizen für alle Gelenke des Animations-Skeletts zu berechnen. Während des Renderings müssen diese Matrizen dann dem Vertex Shader SkeletalAnimatedObjectNM.vert übergeben werden, wo schließlich die Transformation der den Gelenken zugeordneten Vertices in die gewünschte Animationspose durchgeführt wird (Vertex Skinning).


Schritt 1: Berechnung der Gesamttransformationsmatrizen

Wie bereits im Artikel über Kinematik besprochen wurde (siehe hierzu Animation von 3D-Modellen Teil 03), müssen vor der Berechnung der Gesamttransformationsmatrizen zuerst die Rotations- und Translationsmatrizen aller Gelenke aufgestellt werden.

Die Rotationsmatrizen werden hierbei aus den Gelenkwinkeln berechnet, welche die Drehungen der Gelenke um die x-, y- und z-Achse beschreiben.

Beim Aufstellen der Translationsmatrizen beginnt man mit demjenigen Gelenk, dass in der Hierarchie an höchster Stelle steht. Für dieses Gelenk beschreibt die Translationsmatrix die Verschiebung relativ zum Modellkoordinatenursprung.

Hinweis:
Für das Gelenk, das in der Hierarchie an höchster Stelle steht, entspricht die Translationsmatrix gleichsam der Gesamttransformationsmatrix.

Im Anschluss daran werden für alle Bewegungsgruppen angefangen beim zweiten Gelenk die Translationsmatrizen berechnet, die die Verschiebungen relativ zu den vorangehenden Gelenken (diese Gelenke stehen in der Hierarchie eine Stufe höher) beschreiben.
Als Teil einer zweiten Bewegungsgruppe mit höherer Gruppenhierarchie beschreibt die Translationsmatrix des ersten Gelenks einer Bewegungsgruppe die Verschiebung relativ zum vorangehenden Gelenk in der zweiten Bewegungsgruppe (Beispiel: das erste Gelenk in der Bewegungsgruppe rechter Arm ist ein Wirbelgelenk und damit zugleich Teil der Bewegungsgruppe Wirbelsäule, die in der Gruppenhierarchie höher steht. Die Translationsmatrix dieses Wirbelgelenks beschreibt die Verschiebung relativ zum vorangehenden Gelenk in der Wirbelsäule).

Nachdem die Rotations- und Translationsmatrizen für alle Gelenke berechnet worden sind, können im zweiten Schritt die Gesamttransformationsmatrizen (TotalTransformationMatrix) aufgestellt werden, mit deren Hilfe sich die animierten Gelenkpositionen im Modellkoordinatensystem ermitteln lassen. Zusätzlich dazu wird für jedes Gelenk die Gesamtrotationsmatrix (TotalRotationMatrix) berechnet, die die Drehungen aller in der Hierarchie höher stehenden Gelenke sowie die Drehung des Gelenks selbst beschreibt. Die Berechnungen erfolgen für jede Bewegungsgruppe separat, angefangen beim jeweils zweiten Gelenk (vorwärts Kinematik).

void CSkeletalAnimatedBody::Set_AnimationPose(
     CSkeletalAnimationPose* pAnimationPose,
     D3DXVECTOR3* pWorldSpacePosition, D3DXMATRIXA16* pOrientationMatrix)
{
WorldSpacePosition = *pWorldSpacePosition;

long i, j;

D3DXMATRIXA16 tempMatrix1, tempMatrix2, tempMatrix3;

// Gelenk-Rotationsmatrizen berechnen:

for(i = 0; i < NumJoints; i++)
{
    JointDesc[i].JointWorldSpacePosLastFrame =
    JointDesc[i].ActualJointWorldSpacePos;

    CalcRotXMatrix(&tempMatrix1, pAnimationPose->JointRotationAngleX[i]);
    CalcRotYMatrix(&tempMatrix2, pAnimationPose->JointRotationAngleY[i]);
    CalcRotZMatrix(&tempMatrix3, pAnimationPose->JointRotationAngleZ[i]);

    JointDesc[i].JointRotationMatrix = tempMatrix1*tempMatrix2*tempMatrix3;
}

// Translations- und Rotationsmatrix für das Gelenk berechnen,
// das in der Hierarchie an höchster Stelle steht:

// Hinweis:
// Die einzelnen Bewegungsgruppen werden durch JointHierarchy-Instanzen
// repräsentiert!!

long tempLong = JointHierarchy[0].HierarchyElement[0];

D3DXVECTOR3 tempVektor3 = JointDesc[tempLong].NonAnimatedJointModelSpacePos;

JointDesc[tempLong].JointRotationMatrix *= *pOrientationMatrix;

CreateTranslationMatrix(
&JointDesc[tempLong].TranslationMatrixRelToPrevJoint, &tempVektor3);

JointDesc[tempLong].AnimatedJointModelSpacePos =
JointDesc[tempLong].NonAnimatedJointModelSpacePos;

JointDesc[tempLong].TotalTransformationMatrix =
JointDesc[tempLong].TranslationMatrixRelToPrevJoint;

JointDesc[tempLong].TotalRotationMatrix = g_identityMatrix;

// Translationsmatrizen für alle weiteren Gelenke berechnen, angefangen
// beim jeweils zweiten Gelenk einer Bewegungsgruppe:

for(j = 0; j < NumJointHierarchies; j++)
{
tempLong = JointHierarchy[j].NumHierarchyElements;

for(i = 1; i < tempLong; i++)
{
    tempVektor3 = JointDesc[JointHierarchy[j].HierarchyElement[i]].
                  NonAnimatedJointModelSpacePos -
                  JointDesc[JointHierarchy[j].HierarchyElement[i-1]].
                  NonAnimatedJointModelSpacePos;

    CreateTranslationMatrix(
    &JointDesc[JointHierarchy[j].HierarchyElement[i]].
    TranslationMatrixRelToPrevJoint, &tempVektor3);
}}

// Gesamttransformations- und Rotationsmatrizen berechnen:

long temp1Long, temp2Long;

for(j = 0; j < NumJointHierarchies; j++)
{
// Gesamttransformationsmatrix für das erste Gelenk einer
// Bewegungsgruppe:
tempMatrix1 = JointDesc[JointHierarchy[j].HierarchyElement[0]].
              TotalTransformationMatrix;

// Gesamtrotationsmatrix für das erste Gelenk einer Bewegungsgruppe:
tempMatrix2 = JointDesc[JointHierarchy[j].HierarchyElement[0]].
              TotalRotationMatrix;

tempLong = JointHierarchy[j].NumHierarchyElements;

for(i = 1; i < tempLong; i++)
{
temp1Long = JointHierarchy[j].HierarchyElement[i];
temp2Long = JointHierarchy[j].HierarchyElement[i-1];

// Gesamttransformationsmatrix für das i-te Gelenk einer
// Bewegungsgruppe:
tempMatrix1 = JointDesc[temp1Long].
              TranslationMatrixRelToPrevJoint*
              JointDesc[temp2Long].JointRotationMatrix*tempMatrix1;

// Gesamtrotationsmatrix für das i-te Gelenk einer Bewegungsgruppe:
tempMatrix2 = JointDesc[temp2Long].JointRotationMatrix*tempMatrix2;

JointDesc[temp1Long].TotalTransformationMatrix = tempMatrix1;

JointDesc[temp1Long].TotalRotationMatrix       = tempMatrix2;
}
}

// Positionen (sowohl im Modell- als auch im Weltkoordinatensystem)
// der animierten Gelenke berechnen:

// Hinweis:
// die Weltkoordinatenpositionen können im Rahmen von Treffer- und
// Kollisionstests verwendet werden!!


for(i = 0; i < NumJoints; i++)
{
Multiply3DVectorWithMatrix(&JointDesc[i].AnimatedJointModelSpacePos,
                           &g_NullVector,
                           &JointDesc[i].TotalTransformationMatrix);

JointDesc[i].ActualJointWorldSpacePos =
JointDesc[i].AnimatedJointModelSpacePos + WorldSpacePosition;

// Zwischenspeichern der Gesamttransformations- und -rotationsmatrizen für
// die spätere Übergabe an den Vertex Shader des 3D-Modells:
JointTotalRotationMatrix[i]       = JointDesc[i].TotalRotationMatrix;
JointTotalTransformationMatrix[i] = JointDesc[i].TotalTransformationMatrix;
}}


Schritt 2: Vertex Skinning

Nachdem die Gelenk-Gesamttransformationsmatrizen der aktuellen Animationspose berechnet worden sind, kann nun auch das eigentliche 3D-Modell animiert werden. Die notwendigen Berechnungen im Rahmen des Vertex Skinnings werden hierbei ausschließlich im Vertex Shader durchgeführt:

// Transformationsmatrix:
uniform mat4 matWorldViewProjection;
uniform mat4 JointTotalTransformationMatrixArray[45];

void main()
{
    // Index der zu verwendenden JointTotalTransformationMatrix
    // für die Transformation der Vertx-Position:
    int Index1 = int(gs_MultiTexCoord0.p);

    // Index der zu verwendenden JointTotalRotationMatrix
    // für die Transformation der Vertex-Normale:
    int Index2 = int(gs_MultiTexCoord0.q);

    // Animierte Vertex-Position im Modellkoordinatensystem:
    vec4 AnimatedPos = JointTotalTransformationMatrixArray[Index1] *
                       gs_Vertex;

    gl_Position      = matWorldViewProjection * AnimatedPos;

    gs_TexCoord[0]   = gs_MultiTexCoord0;

    mat3 JointTotalRotationMatrix;

    // Extrahieren der JointTotalRotationMatrix aus der
    // JointTotalTransformationMatrix:

    JointTotalRotationMatrix[0] =
    vec3(JointTotalTransformationMatrixArray[Index2][0][0],
         JointTotalTransformationMatrixArray[Index2][0][1],
         JointTotalTransformationMatrixArray[Index2][0][2]);

    JointTotalRotationMatrix[1] =
    vec3(JointTotalTransformationMatrixArray[Index2][1][0],
         JointTotalTransformationMatrixArray[Index2][1][1],
         JointTotalTransformationMatrixArray[Index2][1][2]);

    JointTotalRotationMatrix[2] =
    vec3(JointTotalTransformationMatrixArray[Index2][2][0],
         JointTotalTransformationMatrixArray[Index2][2][1],
         JointTotalTransformationMatrixArray[Index2][2][2]);

    // Normal:
    gs_TexCoord[1].xyz = JointTotalRotationMatrix * gs_Normal;

    // Perpendicular1:
    gs_TexCoord[2].xyz = JointTotalRotationMatrix * gs_MultiTexCoord1.xyz;

    // Perpendicular2:
    gs_TexCoord[3].xyz = JointTotalRotationMatrix * gs_MultiTexCoord2.xyz;
}