TD2 : Passage à la 3D
1 - Transformations
Redimensionner, déplacer, faire tourner une forme en modifiant individuellement et manuellement ses sommets est laborieux, alors qu'une transformation géométrique globale pourrait être utilisée. Cette transformation peut être représentée par une matrice 4x4 (en coordonnées homogènes) ; nous utiliserons dans ce TD l'implémentation fournie par la librairie Eigen dont voici un aide-mémoire.
GLSL | C++ (Eigen) | |
---|---|---|
vecteur 2D | vec2 | Vector2f |
vecteur 3D | vec3 | Vector3f |
vecteur 4D | vec4 | Vector4f |
matrice 2x2 | mat2 | Matrix2f |
matrice 3x3 | mat3 | Matrix3f |
matrice 4x4 | mat4 | Matrix4f |
matrice 3x4 | mat3x4 | Matrix<float,3,4> |
accès | v.x | v.x(), v[0], v(0) |
v.y | v.y(), v[1], v(1) | |
v.z | v.z(), v[2], v(2) | |
v.w | v.w(), v[3], v(3) | |
(i=ligne,j=colonne) | M[j][i] | M(i,j) |
colonne d'une matrice | M[j] | M.col(j) |
ligne d'une matrice | M.row(i) | |
sous-matrice | M.block(i,j,rows,cols) |
GLSL | C++ (Eigen) | |
---|---|---|
produits | M * v | M * v |
M1 * M2 | M1 * M2 | |
transpose | transpose(M) | M.transpose() |
produit scalaire | dot(v1,v2) | v1.dot(v2) |
norme | length(v) | v.norm() |
produit vectoriel | cross(v1,v2) | v1.cross(v2) |
translation d'un vecteur v | Translation3f(v) | |
rotation d'un angle theta autour de v | AngleAxisf(theta,v) | |
mise à l'échelle d'un facteur s | Scaling(s) | |
transformation affine | Affine3f A; | |
partie linéaire 3x3 | A.linear() | |
translation | A.translation() | |
matrice 4x4 | A.matrix() |
Exemple d'utilisation :
Affine3f A;
A = Translation3f(p) * AngleAxisf(theta,v) * Translation3f(-p);
v2 = A * v1;
1. Ajoutez une variable uniforme de type mat4
au vertex shader et passez lui une homothétie de facteur 1/2 dans Viewer::display
(à l'aide des fonctions glUniformMatrix4fv
et glGetUniformLocation
). Transformez la position du vertex par cette matrice. Côté C++, vous pouvez utiliser une variable de type Eigen::Affine3f
pour stocker cette transformation ; il faudra alors faire appel aux méthodes matrix()
et data()
pour obtenir le pointeur sur les données à passer au shader. Utilisez le clavier pour contrôler le facteur de zoom.
2. Expérimentez et combinez d'autres transformations planaires (rotations, translations). Notez l'importance de l'ordre de ces transformations en inversant translation et rotation. Attention, pour l'instant la zone visible sur chaque axe va de -1 à 1.
3. Intégrez à votre code les classes Shape
et Cube
ci-jointe (ajoutez les au CMakeLists.txt). Ajoutez une instance de Cube
au Viewer
, initialisez ses VAO/VBO après avoir créé les shaders en appelant sa méthode Cube::init
, puis affichez le cube dans la méthode display
. Une seule face du cube est actuellement visible.
2 - Caméra

1. Pour passer d'un espace 2D à 3D, il nous faut définir une matrice de projection. Intégrez la classe Camera
ci-jointe à votre code, et créez une matrice de projection perspective avec sa méthode :
Matrix4f Camera::perspective(float fovy, float aspect, float zNear, float zFar);
2. Il est pratique de contrôler la caméra en lui indiquant une position d'observation position
, une cible à viser target
et une orientation verticale up
. Implémentez la fonction générant ainsi la matrice de vue, avec ce prototype :
Matrix4f Camera::lookAt(const Vector3f& position, const Vector3f& target, const Vector3f& up);
Indices : vous devez construire une base orthonormale 3D dont le premier axe est celui de visé et le deuxième axe est aussi proche que possible de l'orientation verticale tout en étant orthogonal au premier. Le troisième axe du repère peut finalement être obtenu par produit vectoriel. Assemblez alors la matrice 4x4 de changement de base à partir de ces vecteurs en n'oubliant pas que le repère doit être centré sur la position d'observation.
3. Il est courant de séparer la transformation des objets de celle fixant le point de vue de la caméra dans la scène. Déclarez une nouvelle matrice en attribut de la classe Shape
où vous y stockerez la transformation de chaque objet. Modifiez le
4. Permettez le contrôle de la caméra à l'aide des touches du clavier : rotation horizontale et verticale autour du point visé, et translation dans la direction de visée.
3 - Maillage
1. Téléchargez les classes Mesh
et MeshLoader
ci-jointes permettant
de charger un maillage triangulaire depuis un fichier OBJ ou OFF. Intégrez ces classes à votre code.
Chargez par exemple ce maillage de lemming (haute ou basse résolution) – méthodes load
et init
– et affichez le à la place du rectangle (dans la méthode display
).
2. Vous constaterez que l'affichage est étrange : l'élimination des parties cachées n'est pas correcte. Activez le depth buffer avant la boucle de rendu (état GL_DEPTH_TEST
), et videz le à chaque appel de Viewer::display
avec glClear(GL_DEPTH_BUFFER_BIT)
.

3. Plus le nombre de triangles affichés augmente, plus les performances décroissent. Un des grands enjeux de l’informatique graphique est de minimiser le nombre de triangles affichés tout en conservant un maximum de détails. Différentes techniques ont été mises au point dans ce domaine.
Une des approches les plus simples, directement implémentée dans OpenGL, consiste à n’afficher que les triangles dont on voit la face avant ou arrière pour économiser des calculs (opération de culling). Pour mettre en œuvre cette optimisation, il suffit de l’activer à l’aide de glEnable(GL_CULL_FACE)
. Il faut aussi spécifier la face qu’on ne souhaite pas afficher :
glCullFace(GL_BACK)
si on veut ne rendre que les triangles dont on voit la face avant ;glCullFace(GL_FRONT)
si on ne veut rendre que ceux dont on voit la face arrière.
GL_FRONT
et GL_BACK
).
Remarque : par défaut, une face est considérée "avant" si ses sommets sont décrits dans le sens inverse des aiguilles d’une montre. Ce comportement est modifiable par l'appel à glFrontFace()
.
