TD10 - Cinématique inverse et skinning

Archive : mds3d_td10.zip.

L'objectif de ce TP est de tracer, contrôler et déformer un bras articulé composé de N articulations à 2 degrés de liberté (phi/theta) et de N segments de longueurs fixes. La première articulation coïncide avec le pied du bras qui est positionné en (0,0,0). L'axe vertical est l'axe Z=(0,0,1). Les angles de rotations phi et theta sont stockées dans la matrice Viewer::_jointAngles qui est initialisée dans la méthode Viewer::init(). Pour chaque articulation, le premier angle phi correspond à la rotation autour de l'axe du segment (Z), et le second angle défini la rotation autour de l'axe Y après rotation par phi. Ces angles sont définis relativement par rapport au segment (et donc transformations) précédentes. Les longueurs des segments sont définies dans la variable Viewer::_lengths. Les articulations seront tracé via le maillage _jointMesh et les segments via _segmentMesh. La scène contient également un relief qui nous servira à définir nos cibles.

1. Rendu du bras articulé

La première étape consiste à afficher correctement le bras articulé. Cela ce passe dans la fonction Viewer::drawArticulatedArm() que vous devez corriger pour que les différents éléments soient tracés au bon endroit avec la bonne taille. Vous devez obtenir :

arm0

Conseils : les fonctions de Eigen suivantes vous seront utiles pour cette étapes et les autres :

Affine3f M;
M.setIdentity();
M = other_transformation * M;

M = M * other_transformation;
// où other_transformation peut être:
AngleAxisf(float theta, Vector3f axis); // axis est un Vector3f unitaire, par exemple Vector3f::UnitX(), Vector3f::UnitY(), ...
Scaling(float s);                       // scaling uniforme
Scaling(float sx, float sy, float sz);  // scaling non-uniforme
Translation3f(Vector3f t);              // translation du vecteur t

// Acccès à M:
M.linear()                              // block 3x3 supérieur gauche
M.translation()                         // block 3x1 supérieur droit
M.matrix()                              // matrice 4x4 équivalente (Matrix4f)

Matrix?f A;
A.col(j)                                // j-ème colonne
A.row(i)                                // i-ème ligne

2. Inverse Kinematic (IK)

La seconde étape consiste à calculer automatiquement les rotations des articulations de manière à ce que l'extrémité du bras coïncide avec une cible définie via un ctrl-click sur le maillage bleu (voir la fonction mousePressed pour les curieux). La cible est matérialisée par une sphère verte. Pour cette étape vous avez uniquement à modifier la fonction Viewer::updateAndDrawScene() où vous réaliserez un (ou plusieurs) pas de descente de gradient par pas de temps ce qui permettra d'avoir un retour interactif du processus d'optimisation. Il y a trois étapes :

  1. Calculer la position courante de l'extrémité en espace monde.
  2. Calculer la matrice Jacobienne (3 x 2N). Rappel, pour chaque degré de liberté la colonne i de $J$ correspondante est obtenue par $w_i \times r_i$ où $w_i$ est l'axe de rotation et $r_i$ le vecteur allant du centre de rotation à l’extrémité courante. Tous deux doivent être exprimés dans le repère monde.
  3. En déduire le gradient $G = J^T \Delta P$ (direction de minimisation de l'énergie) et mettre à jour les 2N rotations :
    $Q = Q + \Delta t \cdot G$
    avec $\Delta t$ un pas de temps donné (par ex. $0.01 \pi$) .

Conseils :

  • Reprenez ce que vous avez fait à l'étape précédente pour le tracer du bras.
  • Vous pouvez voir une matrice 2 x N comme un vecteur de taille 2N avec Eigen::Map :
    VectorXf::Map(_jointAngles.data(), 2*n)
  • Pour avoir une animation plus réaliste, bornez la vitesse de rotation et éventuellement ajouter des bornes sur les angles de rotations, notamment pour les angles theta.

arm1

3. Skinning

L'objectif est désormais d'appliquer une "peau" au bras articulé et de la déformer par la méthode du skinning linéaire.

1. Intégrer vos shaders du TD8 permettant de créer et d'afficher un cylindre paramétrique à partir d'une grille régulière. Créez un cylindre verticale (le long de l'axe Z) d'origine (0,0,0), de rayon 0.5 et de la même longueur que le bras articulé :

arm2

2. Dans drawArticulatedArm, calculez les matrices $B_j$ de passage du repère du segment $j$ au repos vers le repère monde (translations selon l'axe Z). Stockez dans un tableau les N matrices $M_j = T_j B_j^{-1}$, où $T_j$ est la matrice de transformation courante du segment $j$. Envoyez ce tableau au vertex shader de dessin du cylindre en appelant glUniformMatrix4fv. GLSL n'autorisant pas les tableaux de taille dynamique pour les variables uniforme, fixez le nombre de segments "en dure" dans le shader. Transmettez-lui également le tableau _lengths. À partir de ces informations et de la coordonnée de texture $v$ du sommet, déterminez son plus proche segment et donc quelle matrice de transformation utiliser pour le déformer. Vous obtenez un skinning rigide :

arm3

N'oubliez pas de transformer également les normales pour que le calcul de l'éclairage soit (plus) correct.

3. Enfin, pour obtenir un skinning linéaire, les sommets du cylindre à proximité d'une articulation doivent être influencés par les transformations des deux segments adjacents. Pour cela, il vous faut calculer des poids de mélange par sommet. Par simplicité, vous utiliserez la distance à l'articulation la plus proche le long de l'axe Z dans un certain voisinage, ici de longueur 0.2 :

arm4