OpenGL

Transformations

Alexander Overvoorde

Traducteur

Alexandre Laurent

- Titre : OpenGL

- Auteur : Alexander Overvoorde

- Traducteur : Alexandre Laurent

- Parution : 8 février 2017

- Licence : Le contenu de cet article est rédigé par Alexander Overvoorde et est mis à disposition selon les termes de la Licence Creative Commons Attribution - Partage dans les Mêmes Conditions 3.0 non transposé. Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright ® 2013 Developpez.com.

Sommaire

- Navigation

- Matrices

- Opérations de base

- Addition et soustraction

- Produit scalaire

- Produit matrice-vecteur

- Translation

- Redimensionnement

- Rotation

- Produit matrice-matrice

- Assembler les transformations

- Les transformations en OpenGL

- Matrice de modèle

- Matrice de vue

- Matrice de projection

- Assembler le tout

- Utiliser les transformations pour la 3D

- Une transformation simple

- Passer à la 3D

- Exercices

- Remerciements

- Navigation

Navigation

Le contenu dynamique ne peut pas être affiché dans ce support, veuillez consulter la page en ligne.

Tutoriel précédent : textures

Sommaire

Tutoriel suivant : tampons de profondeur et de pochoir

Matrices

Sachant que ceci est un guide sur la programmation graphique, ce chapitre ne sera pas exhaustif sur la théorie qui traite les matrices. Seule la théorie s'appliquant aux graphismes sera décrite et sera expliquée du point de vue du programmeur. Si vous souhaitez en apprendre plus sur ces sujets, ces vidéos de la Khan Academy sont vraiment une bonne introduction.

Une matrice est un tableau rectangulaire d'expressions mathématiques, très proche d'un tableau à deux dimensions. Ci-dessous un exemple de matrice écrite entre crochets :

Les valeurs des matrices ont pour indice (i, j) où i est la ligne et j la colonne. C'est pourquoi la matrice ci-dessus est appelée une matrice 3 sur 2. Pour discuter d'une valeur particulière, par exemple 5, la notation est utilisée.

Opérations de base

Afin de se familiariser avec le concept d'un tableau de nombres, voyons quelques opérations de base.

Addition et soustraction

Tout comme pour les nombres habituels, les opérateurs d'addition et de soustraction existent pour les matrices. La seule condition est que deux opérandes doivent avoir exactement le même nombre de lignes et de colonnes.

Les valeurs dans la matrice sont ajoutées ou soustraites individuellement les unes avec les autres.

Produit scalaire

Le produit d'un nombre scalaire avec une matrice est aussi direct que l'addition et la soustraction.

Les valeurs dans la matrice sont multipliées une par une avec le scalaire.

Produit matrice-vecteur

Le produit d'une matrice avec une autre matrice est un peu plus compliqué et est souvent mal compris, donc, pour une raison de simplicité, je ne vais mentionner que les cas spécifiques qui s'appliquent à la programmation graphique. Pour voir comment les matrices sont couramment utilisées pour transformer des vecteurs, nous allons débuter par la multiplication d'une matrice par un vecteur.

Pour calculer le produit d'une matrice par un vecteur, le vecteur est écrit comme une matrice de dimension 4 par 1. Les expressions à droite du signe égal montrent comment les valeurs x, y, z sont calculées après transformation du vecteur. Pour ceux parmi vous qui ne sont pas très calés en maths, le point est un signe de multiplication.

Je vais mentionner chacune des transformations de vecteur classiques dans cette section et montrer comment une matrice peut être utilisée pour les effectuer. Pour être complet, prenons une transformation qui ne fait absolument rien.

La matrice est appelée une matrice d'identité, car, tout comme le nombre 1, elle retournera toujours la valeur avec laquelle elle a été multipliée.

Voyons maintenant la transformation la plus courante et déduisons comment une matrice peut être créée à partir de celle-ci.

Translation

Pour voir pourquoi nous travaillons avec des vecteurs de dimension 4 sur 1 et ensuite des matrices de transformation de taille 4 par 4, voyons comment se constitue une matrice de translation. Une translation déplace un vecteur à une certaine distance dans une certaine direction.

Pouvez-vous deviner à partir de la multiplication ce à quoi la matrice devrait ressembler pour déplacer un vecteur de (X, Y, Z) ?

Si la quatrième colonne et ligne n'avait pas été 1, la translation n'aurait pas été possible.

Redimensionnement

Une transformation de mise à l'échelle redimensionne chacun des composants du vecteur avec un scalaire (différent). C'est une opération communément utilisée pour réduire ou agrandir un vecteur, comme présenté ci-dessous.

Si vous avez compris comment la matrice précédente a été créée, il ne devrait pas être difficile de trouver la matrice qui redimensionne un vecteur donné par (SX, SY, SZ).

Si vous prenez un moment pour y penser, vous pouvez voir que le redimensionnement aurait aussi été possible avec une simple matrice de dimension 3 par 3.

Rotation

Une rotation tourne le vecteur autour de son origine (0, 0, 0) sur un axe et un angle donnés. Pour comprendre comment l'axe et l'angle contrôlent la rotation, faisons une petite expérience.

Dirigez votre pouce vers votre écran et essayez de tourner votre main autour. L'objet, votre main, tourne autour de votre pouce : l'axe de rotation. Plus vous tournez votre main, plus l'angle de rotation sera grand.

De la même manière, l'axe de rotation peut être imaginé comme une flèche autour de laquelle l'objet tourne. Si vous imaginez votre écran comme une surface 2D, l'axe de rotation (votre pouce) pointe dans la direction Z.

Les objets peuvent être tournés autour d'un axe quelconque, mais pour le moment, seuls les axes X, Y et Z sont importants. Vous allez voir plus loin dans ce chapitre que n'importe quel axe de rotation peut être établi en tournant autour des axes X, Y et Z simultanément.

Les matrices pour tourner autour des trois axes sont indiquées ici. L'angle de rotation est indiqué par le theta ().

Rotation autour de l'axe X :

Rotation autour de l'axe Y :

Rotation autour de l'axe Z :

Ne vous préoccupez pas de comprendre la géométrie sous-jacente, et son explication sort du contexte de ce guide. Ce qui compte est d'avoir une idée claire de comment une rotation est définie par un angle et un axe de rotation et que vous sachiez à quoi ressemble une matrice de rotation.

Produit matrice-matrice

Dans la section précédente, vous avez vu que les matrices de transformation peuvent être utilisées pour appliquer des transformations aux vecteurs, mais en soi, ce n'est pas très utile. Il est clair qu'il est plus facile de faire une translation et un redimensionnement à la main sans toutes ces fichues matrices.

Maintenant, que se passe-t-il si je vous dis qu'il est possible de combiner autant de transformations que vous le souhaitez dans une seule matrice, simplement en les multipliant ? Vous pouvez même appliquer les transformations les plus complexes d'un vecteur par une simple multiplication.

En suivant le style de la section précédente, voici comment le produit de deux matrices de dimension 4 par 4 est réalisé :

L'opération ci-dessus est couramment reconnue, par les mathématiciens, comme un désordre indéchiffrable. Pour avoir une meilleure idée de ce qui se passe, prenons des matrices de dimension 2 par 2 à la place.

Avec l'aide des couleurs, essayez de voir le motif. Les facteurs (1, 2 et 3, 4) à gauche du signe de multiplication sont les valeurs de la ligne de la première matrice. Les facteurs sur la droite sont les valeurs de la ligne de la seconde matrice et ainsi de suite. Il n'est pas utile de se rappeler comment cela fonctionne, mais il est bon de voir comment l'effectuer au moins une fois.

Assembler les transformations

Pour expliquer la multiplication de deux matrices, essayez de redimensionner un vecteur donné par (2, 2, 2) et de le déplacer par (1, 2, 3). Avec les matrices de translation et de redimensionnement vues précédemment, le produit est calculé comme suit :

Notez que nous souhaitons redimensionner le vecteur en premier, mais que la transformation de redimensionnement arrive en dernier dans la multiplication. Prenez garde à cela lorsque vous assemblez des transformations, sinon vous obtiendrez le contraire de ce que vous souhaitez.

Maintenant, essayez de transformer un vecteur et voyons si cela fonctionne :

Parfait ! Le vecteur est premièrement redimensionné par deux, puis déplacé de (1, 2, 3).

Les transformations en OpenGL

Vous avez vu dans les sections précédentes comment les transformations pouvaient s'appliquer aux vecteurs pour les déplacer dans le monde. Le travail de transformation des points 3D en coordonnées 2D pour notre écran se réalise aussi à travers les matrices de transformation. Tout comme le pipeline graphique, la transformation d'un vecteur est effectuée étape par étape. Bien qu'OpenGL vous permette de décider de ces étapes par vous-même, toutes les applications graphiques 3D utilisent une variation du processus décrit ici.

Chaque transformation transforme un vecteur dans un nouveau système de coordonnées, donc le passe à l'étape suivante. Ces transformations et systèmes de coordonnées seront détaillés plus tard.

Matrice de modèle

La matrice de modèle transforme une position dans un modèle en position dans le monde. Cette position est impactée par la position, échelle et rotation du modèle qui est dessiné. C'est généralement une combinaison des transformations simples vues précédemment. Si vous avez déjà spécifié vos sommets dans les coordonnées du monde (courant lors du rendu d'une simple scène de test), alors la matrice peut être simplement une matrice d'identité.

Matrice de vue

Dans la vie vous êtes habitué à déplacer la caméra pour modifier la vue d'une certaine scène. Dans OpenGL, c'est le contraire. La caméra dans OpenGL ne peut se déplacer et est définie à la position (0, 0, 0) et regarde en direction de l'axe Z. Cela veut dire qu'au lieu de déplacer et tourner la caméra, le monde est déplacé et tourne autour de la caméra pour reproduire la vue souhaitée.

Les anciennes versions d'OpenGL forçaient l'utilisation des transformations modèle-vue et projection. La matrice modèle-vue rassemblait les transformations du modèle et de la vue. Personnellement, je trouve plus simple de séparer les deux, permettant ainsi de modifier la vue indépendamment de la matrice de modèle.

Cela signifie que pour simuler la transformation de la caméra, vous devez transformer le monde avec l'inverse de cette transformation. Par exemple, si vous souhaitez déplacer la caméra vers le haut, vous devez déplacer le monde vers le bas.

Matrice de projection

Après avoir aligné le monde avec votre caméra grâce à la transformation de la vue, la projection peut être appliquée, pour obtenir des coordonnées dans la zone de découpage (« clip coordinates »). Si vous effectuez une perspective, ces coordonnées de découpage ne sont pas encore prêtes à être utilisées comme coordonnées normalisées pour le périphérique.

Pour transformer les coordonnées de découpage en coordonnées normalisées pour le périphérique, une division de perspective doit être effectuée. Une coordonnée de découpage provenant d'une perspective a un nombre différent de 1 dans la quatrième ligne, aussi connu comme w. Ce nombre reflète directement l'effet rendant les objets au loin plus petits.

Les coordonnées x et y seront maintenant dans l'échelle -1 et 1, ce qu'OpenGL peut transformer en coordonnées de fenêtre. Le z est connu comme la profondeur et va jouer un rôle important au prochain chapitre.

Les coordonnées résultant de la projection sont appelées coordonnées de découpage, car la valeur de w est utilisée pour déterminer si l'objet est trop proche, ou derrière la caméra, ou trop loin, pour être affiché. La matrice de projection est créée avec ces limites, donc vous pouvez les spécifier vous-même.

Assembler le tout

Pour récapituler, la transformation finale d'un sommet est le produit des matrices du modèle, de la vue et de la projection.

Cette opération est typiquement effectuée dans le vertex shader et le résultat est affecté à la variable gl_Position dont la valeur doit être donnée en coordonnées de découpage. OpenGL effectuera la division de perspective et la transformation en coordonnées de fenêtre. C'est important de connaître ces étapes, car vous devez les faire vous-même lorsque vous travaillez sur des techniques comme l'application d'ombres.

Utiliser les transformations pour la 3D

Maintenant que vous connaissez trois importantes transformations, il est temps de les implémenter dans le code d'une scène 3D. Vous pouvez utiliser n'importe quel programme développé au cours des deux derniers chapitres, mais je vais utiliser l'exemple de mélange de texture du chapitre précédent.

Pour intégrer les matrices dans le code, nous pouvons utiliser la bibliothèque GLM (OpenGL Math). Cette bibliothèque fournit les classes de vecteurs et de matrices et se chargera efficacement des mathématiques sans même que vous ayez à vous en préoccuper. C'est une bibliothèque implémentée intégralement dans un fichier en-tête ce qui signifie que vous n'avez rien à lier.

Pour l'utiliser, ajoutez le répertoire racine de GLM dans les chemins d'inclusion de votre projet et incluez ces trois en-têtes :

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

Le deuxième en-tête inclut les fonctions pour faciliter le calcul des matrices de vue et de projection. Le troisième en-tête ajoute les fonctionnalités pour convertir un objet matrice en tableau de nombres à virgule flottante utile à OpenGL.

Une transformation simple

Avant de s'enfoncer directement dans la 3D, essayons d'abord une simple rotation 2D.

glm::mat4 trans;
trans = glm::rotate(trans, glm::radians(180.0f), glm::vec3(0.0f, 0.0f, 1.0f));

La première ligne crée une matrice de dimension 4 par 4, qui est une matrice d'identité par défaut. La fonction glm::rotate multiplie cette matrice par une rotation de 180 degrés autour de l'axe Z. Rappelez-vous que si l'écran est un plan XY, l'axe Z est l'axe autour duquel vous tournez les points.

Pour voir si cela fonctionne, essayons de tourner un vecteur avec cette transformation :

glm::vec4 result = trans * glm::vec4(1.0f, 0.0f, 0.0f, 1.0f);
printf("%f, %f, %f\n", result.x, result.y, result.z);

Comme prévu, la sortie est (-1, 0, 0). Une rotation dans le sens inverse des aiguilles d'une montre de 180 degrés d'un vecteur pointant vers la droite donne un vecteur pointant vers la gauche. Notez que la rotation aurait été dans le sens des aiguilles d'une montre si nous avions utilisé un axe (0, 0, -1).

La prochaine étape est d'appliquer cette transformation dans le vertex shader pour tourner tous les sommets dessinés. GLSL possède un type spécial mat4 pour stocker les matrices et nous pouvons l'utiliser pour envoyer la transformation au GPU à travers une variable uniforme.

GLint uniTrans = glGetUniformLocation(shaderProgram, "trans");
glUniformMatrix4fv(uniTrans, 1, GL_FALSE, glm::value_ptr(trans));

Le deuxième paramètre de la fonction glUniformMatrix4fv (doc) spécifie combien de matrices sont envoyées, car vous pouvez avoir un tableau de matrices en GLSL. Le troisième paramètre indique si la matrice doit être transposée avant utilisation. C'est en rapport avec le fait que les matrices sont stockées comme des tableaux de nombres à virgule flottante en mémoire : vous n'avez pas à vous en préoccuper. Le dernier paramètre indique la matrice à envoyer, la fonction glm::value_ptr convertit la matrice en tableau de 16 (4x4) nombres à virgule flottante.

Tout ce qui reste à faire est de mettre à jour le vertex shader pour inclure cette variable uniforme et l'utiliser pour transformer chaque sommet :

#version 150

in vec2 position;
in vec3 color;
in vec2 texcoord;

out vec3 Color;
out vec2 Texcoord;

uniform mat4 trans;

void main() {
Color = color;
Texcoord = texcoord;
gl_Position = trans * vec4(position, 0.0, 1.0);
}

Les primitives de notre scène vont être retournées.

Pour pimenter les choses, vous pouvez modifier la rotation au fil du temps :

auto t_start = std::chrono::high_resolution_clock::now();

...

// Calcul de la transformation
auto t_now = std::chrono::high_resolution_clock::now();
float time = std::chrono::duration_cast<std::chrono::duration<float>>(t_now - t_start).count();

glm::mat4 trans;
trans = glm::rotate(
trans,
time * glm::radians(180.0f),
glm::vec3(0.0f, 0.0f, 1.0f)
);
glUniformMatrix4fv(uniTrans, 1, GL_FALSE, glm::value_ptr(trans));

// Draw a rectangle from the 2 triangles using 6 indices
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

...

Le résultat sera quelque chose comme :

Le contenu dynamique ne peut pas être affiché dans ce support, veuillez consulter la page en ligne.

Si vous avez des soucis, vous pouvez trouver le code complet ici.

Passer à la 3D

La rotation ci-dessus peut être considérée comme une transformation du modèle, car elle transforme les sommets de l'espace objet dans l'espace monde en utilisant une rotation.

glm::mat4 view = glm::lookAt(
glm::vec3(1.2f, 1.2f, 1.2f),
glm::vec3(0.0f, 0.0f, 0.0f),
glm::vec3(0.0f, 0.0f, 1.0f)
);
GLint uniView = glGetUniformLocation(shaderProgram, "view");
glUniformMatrix4fv(uniView, 1, GL_FALSE, glm::value_ptr(view));

Pour créer une transformation de vue, GLM fournit l'utile fonction glm::lookAt, qui simule une caméra en déplacement. Le premier paramètre indique la position de la caméra, le second, l'endroit sur lequel la caméra pointe et le troisième, l'axe « up ». Ici, « up » est équivalent à l'axe Z, ce qui implique que le plan XY est le « sol ».

glm::mat4 proj = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 1.0f, 10.0f);
GLint uniProj = glGetUniformLocation(shaderProgram, "proj");
glUniformMatrix4fv(uniProj, 1, GL_FALSE, glm::value_ptr(proj));

De même, GLM fournit une fonction glm::perspective pour créer une matrice de projection en perspective. Le premier paramètre est le champ de vue vertical, le second paramètre indique le ratio de l'écran et les deux derniers sont pour les plans proche (« near ») et lointain (« far »).

Champ de vision

Le champ de vision définit l'angle entre le haut et le bas de la surface 2D sur laquelle le monde sera projeté. Dans les jeux, le zoom est souvent implémenté en diminuant cet angle au lieu de rapprocher la caméra, car c'est plus proche de la réalité.

En diminuant l'angle, vous pouvez imaginer que le « rayon » provenant de la caméra se dispersera moins et couvrira donc une plus petite zone de la scène.

Les plans proche et lointain sont connus sous le nom de plans de découpage. N'importe quel sommet plus proche de la caméra que le plan de découpage proche et n'importe quel sommet plus éloigné que le plan de découpage lointain sont éliminés, car ils impactent la valeur w.

Maintenant rassemblons les morceaux, le vertex shader ressemblera à cela :

#version 150

in vec2 position;
in vec3 color;
in vec2 texcoord;

out vec3 Color;
out vec2 Texcoord;

uniform mat4 model;
uniform mat4 view;
uniform mat4 proj;

void main() {
Color = color;
Texcoord = texcoord;
gl_Position = proj * view * model * vec4(position, 0.0, 1.0);
}

Notez que j'ai renommé en model la matrice anciennement connue sous le nom de trans et qu'elle est toujours mise à jour à chaque image.

Le contenu dynamique ne peut pas être affiché dans ce support, veuillez consulter la page en ligne.

Bravo ! Si vous êtes bloqué, vous pouvez trouver le code complet ici.

Exercices

- Faites en sorte que le rectangle avec le mélange d'images grossisse et rétrécisse avec un sinus. (Solution) ;

- Faites en sorte que le rectangle tourne autour de l'axe X après l'appui sur la barre espace et s'arrête lentement. (Solution)

Remerciements

Cet article est une traduction autorisée dont le texte original peut être trouvé sur open.gl.

Navigation

Tutoriel précédent : textures

Sommaire

Tutoriel suivant : tampons de profondeur et de pochoir