Ce cours est visible gratuitement en ligne.

Got it!
Développez vos applications 3D avec OpenGL 3.3
Last updated on Friday, June 21, 2013
  • Moyen

Les matrices

Alors là, nous allons aborder un chapitre assez compliqué. Nous allons découvrir ce que sont les matrices avec OpenGL, leur utilité, etc... C'est un chapitre assez technique et surtout rempli de mathématiques :-° . Je vous conseille de bien le relire plusieurs fois jusqu'à que vous ayez tout compris, car les matrices sont indispensables avec OpenGL. ;)

Introduction

Lançons nous tout de suite dans le monde des mathématiques. Je vous rappelle que si vous voulez développer des jeux-vidéo vous ne pouvez pas éviter les maths, elles sont indispensables au bon fonctionnement du jeu. Les premières notions que nous allons apprendre sont les transformations.

Qu'est-ce qu'une transformation vous allez me dire ? Au lieu d'une définition barbante que nous n'allons point retenir ( :p ) je vais vous donner des exemples de transformations et vous comprendrez tout de suite ce que c'est :

  • Une translation : consiste à faire "glisser" un ensemble de points sur un "rail" (un vecteur).

  • Une rotation : consiste à faire pivoter un ensemble de points d'un angle Thêta par rapport à un point.

  • Une homothétie : consiste simplement à agrandir ou à réduire une forme géométrique.

Une transformation est donc grosso-modo une modification apportée à un ensemble de points ou repère dans un espace donné (soit 2D, soit 3D, etc ...). Les 3 transformations que vous voyez là sont les principales transformations utilisées dans OpenGL, en particulier la translation.

Ok, on sait ce qu'est une transformation maintenant, mais à quoi ça sert ?

Elles vont nous servir à placer tous nos objets (personnages, arbres, maisons, ...) dans notre monde 3D. A chaque fois que nous voudrons placer un objet nous utiliserons les transformations. Elles seront essentiellement utilisées sur le repère.

Les matrices sous OpenGL 2.1

Avant de parler du fonctionnement des matrices dans la version 3, je vais vous parler de celui des anciennes versions, ce sera plus facile à comprendre. De plus le principe ne change absolument pas d'une version à l'autre.
Tout d'abord, qu'est-ce qu'une matrice ? C'est un tableau de nombres ordonnés en lignes et en colonnes entourés par des parenthèses. Sa syntaxe est semblable à celle d'un vecteur mais avec plus de nombres :

Image utilisateur

Une matrice n'est pas forcément un tableau de 9 cases, elle peut en contenir jusqu'à l'infini. Cependant ce n'est pas qu'un simple tableau, c'est une sorte de super-vecteur qui permet de faire pas mal de choses intéressantes. Un vecteur est en général utilisé en 3D pour gérer les points, les directions, les normales... Les matrices permettent de faire bien plus que ça. Elles servent principalement à convertir des données géométriques en données numériques. Il est plus facile de travailler avec des nombres qu'avec des compas et des équerres placés sur notre écran. :p Voyons d'ailleurs pourquoi OpenGL a besoin des matrices :

  • Pour la projection : grâce à cela nous allons pourvoir "transformer" un monde 3D en un monde 2D (jusqu'à nouvel ordre, un écran ne dispose que de deux dimensions).

  • Pour les transformations : regroupant les transformations que je vous ai énumérées à l'instant.

Ce sont les besoins fondamentaux d'OpenGL pour utiliser la 3D. Dans les anciennes versions, les matrices étaient gérées automatiquement, on n'utilisait que quelques fonctions pour les créer et les utiliser. Depuis la version 3.1, ces fonctions sont supprimées, car l'approche des jeux d'aujourd'hui est différente. Heureusement pour nous, nous avons en notre possession une librairie mathématique du nom de GLM qui permet de gérer les matrices (et bien plus), mais avant de commencer à les manipuler nous allons faire un peu de théorie pour bien comprendre ce que nous faisons.

En définitif, nous aurons besoin de deux matrices pour coder avec OpenGL :

  • La matrice de projection : qui sert à transformer notre monde 3D en un monde 2D affichable sur l'écran.

  • La matrice modelview (ou visualisation de modèle): qui sera la matrice principale, c'est sur elle que nous allons appliquer nos transformations.

Chaque vertex sera multiplié par ces deux matrices pour pouvoir être affiché sur notre écran, le tout en nous donnant une impression de 3D (magique n'est-ce pas :magicien: ).

Partie théorique

Dans cette partie nous allons apprendre ce que sont les matrices et comment les utiliser pour nos programmes. Pour simplifier cet apprentissage, je ne vais vous montrer que la multiplication de matrices car c'est une notion que nous retrouverons assez souvent dans ce tuto. De plus, cela vous donnera une bonne idée des calculs matriciels sans pour autant voir les choses les plus compliquées (inversion, déterminant, etc.). :p

Matrice carrée

On commence tout de suite cette partie par les matrices carrées.

Une matrice carrée est une matrice ayant le même nombres de colonnes et de lignes :

Image utilisateur

Il y a 3 rangées de colonnes et 3 rangées de lignes, c'est donc ce que l'on appelle une matrice carrée. Son nom complet est d'ailleurs : matrice carrée d'ordre 3. Le chiffre à la fin permet de spécifier la taille. Si on avait mis le chiffre 4, alors la matrice aurait eu 4 colonnes et 4 lignes. Nous n'utiliserons que les matrices carrées dans OpenGL. Et tant mieux, car ça simplifie grandement les choses :-° .

Matrice d'identité

Sous ce nom barbare se cache en réalité la matrice la plus simple qu'il soit. Elle a la particularité d'avoir toutes ses valeurs égales à 0 hormis les valeurs de sa diagonale qui, elles, sont égales à 1. Quelle que soit la taille de la matrice, on retrouvera toujours cette particularité :

Image utilisateur
Image utilisateur

Si nous multiplions un vecteur par une telle matrice, le vecteur ne sera absolument pas changé :p :

Image utilisateur

Nous allons étudier la multiplication matricielle dans un instant. Mais vous pouvez déjà retenir ce principe : si un vecteur est multiplié par une matrice d'identité, alors il ne sera pas modifié.

Multiplication d'un vecteur par une matrice

Partie 1 : la vérification

Dans ce premier exemple, je vais prendre une matrice carrée d'ordre 3 pour simplifier les choses. Sachez cependant que le principe est le même quelle que soit la taille de la matrice.

Image utilisateur

Voyons comment arriver à ce résultat.
Premièrement, il faut vérifier que le nombre de colonnes de la matrice soit égal au nombre de coordonnées du vecteur. Si ce n'est pas le cas alors la multiplication est impossible.

Image utilisateur

La matrice possède 3 colonnes et le vecteur possède 3 coordonnées. La multiplication est donc possible.

Prenons un autre exemple :

Image utilisateur

La matrice possède 2 colonnes et le vecteur 3 coordonnées. La multiplication est impossible.
Vous avez compris le principe ? Bien, passons à la suite.

Partie 2 : La multiplication

Attaquons la partie la plus intéressante :diable: . La multiplication d'une matrice et d'un vecteur s'effectue comme ceci : pour une seule coordonnée (x, y, ou z) du vecteur résultat, nous allons faire la somme des multiplications de chaque nombre d'une ligne de la matrice par chaque nombre correspondant du vecteur. Un peu barbant comme explication, rien ne vaut un bon exemple :

Commençons déjà par la première ligne, vous devriez comprendre le principe une fois cet exemple fini :

Image utilisateur

Il faut multiplier les nombres de même couleur sur le schéma, puis additionner les différents résultats. Une ligne de la matrice ne donnera qu'une seule coordonnée du vecteur résultat, ici la coordonnée x. Passons à la deuxième ligne :

Image utilisateur

Vous comprenez le principe ? La première ligne nous a donné la première coordonnée, la deuxième ligne nous donne la deuxième coordonnée. Nous multiplions chaque ligne de la matrice par toutes les valeurs du vecteur. Passons à la troisième ligne :

Image utilisateur

La troisième ligne de la matrice nous donne la troisième coordonnée. Si la matrice avait eu 4 lignes (et donc 4 colonnes) alors il y aurait eu une quatrième opération du même type pour la quatrième coordonnée. Si on récapitule tout ça, on a :

Image utilisateur

Vous avez compris ? Je vous donne un autre exemple pour comparer les différents résultats :

Image utilisateur

N'hésitez pas à bien relire et essayez de comprendre ces deux exemples. C'est la base de la multiplication matricielle.

Passons à un pseudo-exemple, vous n'avez pas oublié ce qu'est la matrice d'identité j'espère :p , on sait qu'un vecteur multiplié par cette matrice ne changera pas. Maintenant que l'on connait un peu la multiplication on va voir pourquoi le vecteur n'est pas modifié :

Image utilisateur

Oh magie, le résultat ne change pas. Vous comprenez pourquoi cette matrice est la plus simple à utiliser. ;)
Je vais maintenant vous donner un exemple de multiplication avec une matrice carrée d'ordre 4. Le principe ne change absolument pas, nous allons voir cet exemple étape par étape pour bien comprendre. Commençons :

Image utilisateur

La matrice possède 4 colonnes et le vecteur 4 lignes, la multiplication est possible. Maintenant on passe à la première étape :

Image utilisateur

Vous ne voyez qu'une seule différence : on ajoute à la somme une multiplication supplémentaire. En effet, n'oubliez pas que l'on multiplie les nombres de la matrice avec les nombres correspondants du vecteur. Pour la deuxième ligne c'est pareil :

Image utilisateur

Pour la troisième ligne ça ne change pas :

Image utilisateur

Ah, une quatrième ligne, eh bien oui c'est plus long :p . Mais ça ne change toujours pas :

Image utilisateur

Ce qui nous donne au final :

Image utilisateur

Vous voyez, le principe de la multiplication ne change absolument pas.

Piouf, cette partie était un peu difficile à comprendre. Je vais vous donner quelques exercices pour vous entrainer à la multiplication. Faites-les sérieusement, vous avez besoin de comprendre ce que l'on vient de faire pour comprendre la suite. Sinon vous serez complétement largués :( .

Exercices de multiplications

Exercice 1 :

Image utilisateur

Exercice 2 :

Image utilisateur

Exercice 3 :

Image utilisateur
Solutions

Exercice 1 :

Image utilisateur

Exercice 2 :

La multiplication est impossible, la matrice possède 3 colonnes alors que le vecteur possède 4 coordonnées.

Exercice 3 :

Image utilisateur

Multiplication de deux matrices

La multiplication de deux matrices peut sembler plus compliquée à première vue mais si vous avez compris ce que l'on a fait avant, alors vous savez déjà multiplier deux matrices.

Partie 1

Avant tout, pour pouvoir multiplier deux matrices carrées entre-elles il faut absolument que les deux matrices soient de la même taille (donc du même ordre), c'est-à-dire qu'elles doivent avoir le même nombre de lignes et le même nombre de colonnes.

Image utilisateur

Les deux matrices ont la même taille, on peut donc les multiplier.
Prenons un autre exemple :

Image utilisateur

Ici, les deux matrices n'ont pas la même taille nous ne pouvons pas les multiplier.
Il ne devrait y avoir rien de compliqué pour le moment. :)

Partie 2

Pour pouvoir multiplier deux matrices carrées, nous allons utiliser une petite combine. Nous allons couper la deuxième matrice en 3 vecteurs (ou 4 selon la taille), puis nous appliquerons la multiplication que nous avons vue à l'instant pour chacun de ces vecteurs. Prenons deux matrices cobayes :

Image utilisateur

Bien, coupons la seconde matrice en 3 vecteurs :

Image utilisateur

Maintenant il nous suffit d'appliquer la multiplication d'une matrice et d'un vecteur pour chaque vecteur que nous venons de créer. Voici ce que ça donne pour le premier :

Image utilisateur

Au tour du deuxième :

Image utilisateur

Quand il y a un "0" dans une matrice ça nous facilite grandement le calcul :lol: . Bref, on fait la même chose avec le dernier vecteur :

Image utilisateur

Nous obtenons au final 3 vecteurs fraichement calculés. Il nous suffit ensuite de les assembler dans l'ordre de la division de la matrice !

Image utilisateur

Au final, nous obtenons :

Image utilisateur

Vous voyez, une fois que vous avez compris la première multiplication, vous savez déjà multiplier deux matrices.
Évidemment, la taille de la matrice ne change toujours pas le principe, voyons ensemble un exemple de multiplication de deux matrices carrées d'ordre 4.

Image utilisateur

Coupons la seconde matrice en 4 vecteurs (et oui car c'est une matrice carrée d'ordre 4 et pas 3) :

Image utilisateur

Il nous suffit maintenant d'appliquer la multiplication "Matrice - Vecteur" sur les 4 vecteurs que nous venons de créer. Voici le premier résultat :

Image utilisateur

Puis le second :

Image utilisateur

Le troisième :

Image utilisateur

Et enfin le quatrième et dernier vecteur :

Image utilisateur

Maintenant on réunit les vecteurs résultats dans le bon ordre :

Image utilisateur

Voici donc le résultat final :

Image utilisateur

Vous remarquez que la multiplication se passe exactement de la même façon que l'on soit en présence de matrices à 3 colonnes ou à 4 colonnes ou même à 1000 colonnes :p . Passons maintenant à quelques exercices pour vous entrainer. C'est important je le répète, essayez de faire ces exercices sérieusement.

Exercices de multiplications

Exercice 1 :

Image utilisateur

Exercice 2 :

Image utilisateur

Exercice 3 :

Image utilisateur
Solutions

Exercice 1 :

La multiplication est impossible, la première matrice possède 4 lignes et 4 colonnes alors que la seconde matrice ne possède que 3 lignes et 3 colonnes.

Exercice 2 :

Image utilisateur

Exercice 3 :

Image utilisateur

Et voilà, la partie la plus compliquée de ce chapitre est enfin terminée ! La multiplication matricielle est une notion difficile à comprendre (même s'il en existe bien d'autres plus complexes) mais vous êtes maintenant capables de la maitriser. :D

Transformations au niveau matricielle

Le but de la partie précédente était d'apprendre à multiplier deux matrices. J'ai volontairement inclus des exercices avec des matrices carrées d'ordre 4 car OpenGL aura besoin la plupart du temps de ce genre de matrices (et heureusement !). Dans cette partie, nous allons faire passer les transformations de la géométrie à l'algèbre (les nombres).

Comme vous le savez les transformations sont des outils géométriques, pour calculer une rotation par exemple nous avons besoin d'un rapporteur. Or c'est un peu compliqué de poser notre rapporteur sur l'écran, surtout si l'angle se trouve derrière le dos d'un personnage :lol: . Pour pouvoir utiliser les transformations numériquement nous devons utiliser... les matrices. ;)

La translation

La translation permet de déplacer un ensemble de points ou une forme dans un espace donné. En gros, on prend un vecteur qui servira de "rail" puis on fera glisser nos points sur ce rail. La forme finale ne sera pas modifiée, elle sera juste déplacée. :)

Une translation en 3 dimensions se traduit par la matrice suivante :

Image utilisateur
  • X : C'est la coordonnée x du vecteur de translation.

  • Y : C'est la coordonnée y du vecteur de translation.

  • Z : C'est la coordonnée z du vecteur de translation.

Vous remarquerez que cette matrice ressemble beaucoup à la matrice d'identité, il n'y a que les coordonnées du vecteur en plus.

L'homothétie

Une homothétie permet d'agrandir ou de réduire une forme géométrique, voici la matrice correspondante :

Image utilisateur
  • X : C'est le facteur multiplicatif de l'axe x.

  • Y : C'est le facteur multiplicatif de l'axe y.

  • Z : C'est le facteur multiplicatif de l'axe z.

La rotation

Attention ! La matrice que vous allez voir est certainement la matrice la plus compliquée de tout le tutoriel :p . Cependant, il est inutile de la retenir, elle est trop complexe nous la verrons jamais directement :

Image utilisateur

Je vous avais dit que cette matrice était... particulière (pour vous dire, je ne la connais pas par cœur moi-même). Grâce à elle, nous pouvons faire pivoter en ensemble de points d'un angle thêta autour d'un axe défini par les coordonnées (x, y, z). Nul besoin de retenir cette matrice, je le répète. ;)

Transformations au niveau géométrique

Durant l'introduction, je vous ai brièvement parlé de deux matrices : la projection (qui permet de convertir un monde 3D en un monde 2D affichable sur notre écran) et la modelview (qui permet de placer nos objets dans ce même monde 3D).

C'est sur cette dernière matrice que l'on effectuera toutes les transformations que l'on a vues, à savoir : la translation, la rotation et l'homothétie. Voyons d'ailleurs comment placer un objet dans un monde 3D :

  • On crée la matrice modelview (une seule fois pour tout le programme, pas une seule fois par objet).

  • Puis on effectue une transformation sur cette matrice (par exemple une rotation de 90° sur l'axe Z).

  • Enfin on dessine ce que l'on veut dessiner et vu que l'on a effectué une rotation de 90°, alors notre objet sera penché de 90°.

Voyons ce que cela donne si je fais pivoter un triangle de 90°...

Voici un triangle avant la rotation :

Image utilisateur

Et le revoilà après :

Image utilisateur

Vous voyez ce qui c'est passé ? Le triangle a pivoté de 90° grâce à une transformation.

Les Transformations sous OpenGL

Aaahh... on commence à relier OpenGL et les mathématiques (enfin !). Il est temps de voir le comportement des transformations dans un programme.

La première chose à savoir est que chaque transformation (rotation, ...) sera effectuée non pas sur un objet mais sur le REPÈRE du monde, c'est-à-dire que si l'on veut faire pivoter un objet de 120° alors il faudra faire pivoter non pas l'objet en lui-même mais son REPÈRE.

Par exemple, si vous voulez afficher un objet à 300 mètres d'un autre, vous n'allez pas ajouter 300 unités de longueur à chaque vertex de l'objet, autant garder les vertices déjà définis.

Hein ??? J'ai rien compris. o_O

Bon, mettons que vous ayez un méchant soldat ennemi de 500 vertices. Vous n'allez pas modifier ses 500 vertices pour afficher un autre ennemi deux mètres plus loin. Ça serait trop couteux en ressources, surtout si vous affichez une armée de 200 soldats...

A la place, on modifiera la position du repère (qui est en fait la matrice modelview) pour chaque objet que l'on veut dessiner. Un personnage gardera ses 500 vertices intactes quelque soit sa position.

Vous voyez la différence entre modifier toute une scène de dizaines de milliers de vertices et modifier une matrice de 16 valeurs.

Illustrations

Je vais vous donner quelques exemples de transformations effectuées sur un repère, ce sera plus simple à comprendre si vous voyez ce que donne chaque transformation.

Le repère de base

Le repère de base est en fait un repère qui n'a pas encore été modifié, c'est sur celui-ci que l'on effectuera notre première transformation. Graphiquement, on représente un repère comme ceci :

Image utilisateur

Au niveau des matrices, ce repère correspond simplement à une matrice d'identité d'ordre 4 :

Image utilisateur
La translation

La translation est la transformation la plus simple :p . N'oubliez pas que c'est le REPÈRE qui est modifié. Ici, on translate le repère par rapport au vecteur V(2, 1), soit deux unités de longueur sur l'axe X et 1 unité sur l'axe Y :

Image utilisateur

Avec les matrices on multiplierait la matrice modelview par la matrice de translation suivante :

Image utilisateur
La rotation

Le principe ne change pas pour la rotation, on effectue la transformation sur le repère. Voici un exemple d'une rotation de 45° sur l'axe Z :

Image utilisateur

Pour la rotation, on multipliera la matrice modelview par la matrice de rotation (toujours aussi effrayante :diable: ) :

Image utilisateur
L'homothétie

En temps normal, avec une homothétie on modifie la taille d'une forme géométrique. Sauf qu'ici on modifie la taille du repère, donc on modifie la taille de chaque unité de longueur. Voici ce que donne une homothétie de coordonnées (2, 2) sur le repère :

Image utilisateur

Vous voyez que la taille du repère à changé, désormais si on fait une translation par un vecteur(2, 1) alors la translation sera plus grande et le repère se retrouvera plus loin.

Pour ce qui est de la matrice modelview, il suffira de la multiplier par la matrice suivante :

Image utilisateur

Les matrices et la boucle principale

Vous n'êtes pas sans savoir qu'un jeu se passe en grande partie dans ce que l'on appelle la boucle principale, c'est une boucle qui va se répéter indéfiniment jusqu'à ce que le joueur arrête de jouer (enfin pas vraiment, mais partons de ce principe).

Il faudra à chaque tour de boucle réinitialiser la matrice modelview pour ne pas qu'elle garde les traces de transformations du tour précédent. Si c'était le cas, le jeu ne se redessinerait jamais au même endroit et serait totalement difforme.

Voici ce qui se passera à chaque tour de boucle :

  • On replacera notre repère à sa position initiale, grâce à la fonction loadIdentity.

  • On effectuera une ou des transformation(s) pour afficher un objet grâce aux fonctions translate, ...

  • On répètera la deuxième étape jusqu'à que tous les objets du niveau soient affichés.

Toutes ces étapes se répèteront indéfiniment dans notre programme. En général, on affiche tous les objets d'un niveau 50 à 60 fois par seconde, imaginez le fourbi que l'ordinateur doit calculer. :p

Accumulation de transformations

Attention, de même que l'ordre des matrices dans la multiplication, l'ordre des transformations a une importance capitale ! Si vous effectuez une translation puis une rotation, vous n'obtiendrez pas la même chose que si vous faisiez une rotation et une translation.

Comme d'habitude prenons un petit exemple. Dans un premier temps, je vais faire une translation du repère par rapport au vecteur V(2, 1), puis une rotation de 90° sur l'axe Z (l'axe Z est en fait pointé vers nous, c'est pour ça que nous ne le voyons pas) :

Image utilisateur

Maintenant je vais l'inverse, une rotation de 90° puis une translation de vecteur (2, 1) :

Image utilisateur

Oh tiens ! Les repères ne sont pas les mêmes. Et oui, les repères sont différents car l'ordre des transformations est important. Faites donc bien attention à ce que vous voulez faire et à l'ordre dans lequel effectuer ces transformations. ;)

Enfin ce chapitre est terminé, il nous aura fallu du temps mais n’oubliez pas que tout ce que je vous enseigne est nécessaire pour comprendre et utiliser OpenGL. Récapitulons ce que nous savons faire :

  • On sait afficher des vertices pour former des formes géométriques.

  • On sait utiliser les shaders pour leur donner un peu de couleurs (même si l’on peut faire bien plus avec eux ;) ).

  • On sait utiliser des matrices pour effectuer des transformations.

Bonne nouvelle ! Nous connaissons tout ce qui est nécessaire pour ajouter une troisième dimension à nos programmes. Il est d'ailleurs temps mes amis, attaquons-nous à cette nouvelle dimension qui s'offre à nous ! :pirate:

Example of certificate of achievement
Example of certificate of achievement