Animation de squelette en OpenGL (1/2)

Prérequis : connaissance des outils mathématiques utilisés en 3D : vecteurs, matrices, quaternions. Connaissance d’OpenGL et GLSL pour la partie GPU. Le chargement des modèles animés est ici effectué avec la libraire assimp

J’ai été amené, il y a quelques temps, à développer dans mon moteur un gestionnaire d’animation par squelette. Et force est de constater que ce n’est pas si évident que cela, et que si il existe bien des documentations sur le web sur le sujet, elles sont rares et pas toujours exhaustives, même si elles m’ont grandement aidé. Je vais essayer ici d’expliquer en détail la philosophie des « skeletal animation« , aussi appelé « Skinning« , et les opérations à effectuer pour y parvenir. Je vais tenter également de séparer la logique algorithmique du « GPU Skinning », qui permet de transmettre une partie des calculs à la carte graphique (ici illustré en OpenGL/GLSL).

skeleton

Un modèle avec son squelette d’animation.

 

Tout d’abord, il faut comprendre le processus qui permet classiquement d’obtenir une animation de personnage en 3D.

1) Le Rigging

En premier lieu, à partir d’un modeleur 3D (3DS Max, Blender…), les graphistes dessinent puis animent un personnage en lui affectant un squelette, puis en ajoutant des clés d’animation à celui-ci afin qu’il décrive un mouvement particulier (marcher, courir, sauter…). Ce procédé s’appelle le Rigging. Le squelette est composé de bones qui affectent la position des vertex du mesh. A chaque bone est donc affecté plusieurs vertex, et un vertex peut être affecté par plusieurs bones. Quand un vertex est affecté à un bone, un poids est définit, qui déterminera l’influence du bone sur ce vertex. Nous ne détaillerons pas ici les algorithmes qui sont utilisés lors de la phase de rigging.

2) Le skinning

Ensuite l’animation est lue depuis un moteur d’animation. Depuis le mouvement du squelette, des clefs d’animation qui affectent les bones, et des poids qui s’appliquent sur les vertex, on détermine la position finale des vertex à un instant t. Cette étape se nomme le skinning, et c’est celle-ci que j’ai choisis de détailler dans ce tutoriel.

 

Ainsi les clefs d’animation déterminent la position des bones à un instant t. Entre deux clefs, il est nécessaire que le moteur interpole les positions et rotations afin que le mouvement reste fluide.

Pour lire les modèles et récupérer les informations d’animation, j’utilise la librairie assimp.
Elle a l’avantage de charger une grande variété de format, dont entre autre les .blend, .3DS, .x, .dae, .obj…

Vous pouvez télécharger ici un exemple de fichier .x contenant une animation:  dwarf

La hiérarchie

Un modèle lu par assimp est présenté sous forme d’arbre : un nœud racine (le root) possédant un certains nombres d’enfants, qui peuvent posséder également des enfants… mais qui ne possèdent qu’un seul parent. L’ensemble de cette hiérarchie étant communément appelée le graphe de scène. A chacun de ces nœuds peuvent être associé divers éléments 3D de la scène, comme des meshs, mais aussi des lumières, caméras, effets graphiques…

Les transformations (translation, rotation, agrandissement..) appliquées à un nœud s’appliquent alors à tout ses enfants, ainsi que les entités 3D qui y sont associés. On peut grâce à ce système d’arbre, réaliser des scènes avec des mouvements qui peuvent paraître complexe, comme par exemple un système solaire.

La matrice de transformation des éléments d’un nœud pourra être déterminée facilement:

NodeTransf

Dans Assimp, la classe représentant un nœud se nomme aiNode. Le nœud root est accessible via la variable mRootNode de aiScene. Les entités 3D associés aux nœuds sont référencées par un index.

Les animations

Il existe plusieurs type d’animations. Les animations qui s’appliquent aux meshs dans leur globalité, et les animations qui s’appliquent à un nœud en particulier. C’est les secondes qui sont utilisées dans la cadre des squelettes d’animation. En effet, une animation de squelette est décrite par un ensemble d’animations de nœud.

Les « node animation » sont composées de la manière suivante:

- Un champ permettant de référencer le noeud auquel s’applique l’animation (index ou nom du nœud par exemple) .

- Des clefs de transformation, souvent décomposées en fonction du type de transformation (translation, rotation, scale). Aux clefs sont associés un temps.

- Un indicateur qui va déterminer le type d’interpolation à effectuer (constant, linéaire, cubique…).

Dans Assimp, les animations sont référencées par aiNodeAnim. Une animation complète (par exemple marcher, sauter…) est une aiAnimation qui contient l’ensemble des aiNodeAnim qui composent le mouvement.

En associant les node animation aux nœuds, on est déjà en mesure de décrire le mouvement du squelette. Il suffit pour un instant t de récupérer les clefs les plus proche de ce temps, et d’appliquer les transformations des clefs au nœud associé. Mais un soucis va subvenir : sans interpolation, l’animation sera saccadée.

Les interpolateurs

Dans le fichier anim.h de assimp, on peut trouver des interpolateurs linéaire pour les types de donnée qui nous intéressent, à savoir : les entiers, réels, les vecteurs et les quaternions. Ils ont globalement la structure suivante:

interpolator

a: Valeur correspondant à la première clef.

b: Valeur correspondant à la deuxième clef.

d: temps normalisé entre a et b. Si par exemple la clef a correspond à l’instant t = 4s et la clé b à l’instant t = 8s, au bout de 6s on aura d=0.5. A 7s on aura d=0.75.

out: résultat interpolé.

Ils vont nous permettre de calculer les valeurs intermédiaires de translation, rotation et scale.

On a désormais l’animation du squelette et sans saccade. Mais il nous manque le plus important: la peau !

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Vous pouvez utiliser ces balises et attributs HTML : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>