TD4 - Passage à la 3D

1. Introduction

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. Comme vu en cours, cette transformation peut être représentée par une matrice 4x4 (en coordonnées homogènes). Pour la manipulation de vecteurs et matrices en C++, nous utiliserons à nouveau l'implémentation fournie par la bibliothèque Eigen dont voici un petit aide-mémoire et les équivalences GLSL (pour vos shaders).

GLSLC++ (Eigen)
vecteur 2Dvec2Vector2f
vecteur 3Dvec3Vector3f
vecteur 4Dvec4Vector4f
matrice 2x2mat2Matrix2f
matrice 3x3mat3Matrix3f
matrice 4x4mat4Matrix4f
matrice 3x4mat3x4Matrix<float,3,4>
accèsv.xv.x(), v[0], v(0)
v.yv.y(), v[1], v(1)
v.zv.z(), v[2], v(2)
v.wv.w(), v[3], v(3)
(i=ligne,j=colonne)M[j][i]M(i,j)
colonne d'une matriceM[j]M.col(j)
ligne d'une matriceM.row(i)
sous-matriceM.block(i,j,rows,cols)
GLSLC++ (Eigen)
produitsM * vM * v
   matricielsM1 * M2M1 * M2
transposetranspose(M)M.transpose()
produit scalairedot(v1,v2)v1.dot(v2)
normelength(v)v.norm()
produit vectorielcross(v1,v2)v1.cross(v2)
translation d'un vecteur vTranslation3f(v)
rotation d'un angle theta autour de vAngleAxisf(theta,v)
mise à l'échelle d'un facteur sScaling(s)
transformation affineAffine3f A;
  partie linéaire 3x3A.linear()
  translationA.translation()
  matrice 4x4A.matrix()

Exemple d'utilisation :

Affine3f A;
A = Translation3f(p) * AngleAxisf(theta,v) * Translation3f(-p);
v2 = A * v1;

2. Transformations dans le plan X-Y

Nous allons commencer par de petits exercices de transformations 2D, c'est à dire dans le plan X-Y, en laissant les coordonnées Z inchangées. Pour cela, nous allons utiliser le maillage chair.off. Afin de préserver votre travail précédent, ajouter une nouvelle méthode drawScene2D() et l'appeler à partir de updateAndDrawScene(). La fonction doit ressembler à :

void Viewer::drawScene2D()
{
    glViewport(0, 0, _winWidth, _winHeight);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    _shader.activate();
    _mesh.draw(_shader);
    _shader.deactivate();
}

1. Ajoutez une variable uniforme de type mat4 au vertex shader représentant la matrice de transformation à appliquer aux sommets, et supprimez/commentez les autres variables uniformes de transformations du TP précédent. Pour spécifier les valeurs de cette matrice, vous pouvez vous inspirer du code suivant :

Matrix4f M;
M <<  1, 0, 0, 0,
      0, 1, 0, 0,
      0, 0, 1, 0,
      0, 0, 0, 1;
glUniformMatrix4fv(_shader.getUniformLocation("obj_mat"), 1, GL_FALSE, M.data());
qui construit et passe une matrice identité. (Vous pouvez aussi utiliser M.setIdentity().)

2. Faites en sorte que les effets de zoom et de translation du TP précédent (avec contrôle au clavier) fonctionnent à nouveaux via cette matrice de transformation.

3. Pour cette petite série d'exercices, modifiez les valeurs de la matrices à la main (dans le code C++, commentez le contrôle au clavier).

chairs
  1. Réduisez la dimension de la chaise par 2 et la poser sur le sol et légèrement à gauche.
  2. Rajoutez une chaise en face.
  3. Rajoutez une troisième chaise dont la seule transformation sera une rotation contrôlée au clavier.
  4. Faites en sorte que la chaise tourne autour de son centre de gravité en (0,0.5,0). Vous pourrez construire plusieurs transformations que vous combinerez dans le bon ordre.

Astuce : côté C++, vous pouvez aussi utiliser des variables de type Affine3f pour stocker des transformations ; il faudra alors faire appel aux méthodes matrix() et data() pour obtenir le pointeur sur les données à passer au shader.

4. Revenez sur votre fonction drawScene() précédente, chargez un des personnages 3D (ex., lemming ou monkey).

  1. Appliquez-lui une rotation 3D d'un angle contrôlé au clavier, et autour d'un axe quelconque via Eigen::AngleAxisf(theta,vec).
  2. Calculez et appliquez une rotation de telle sorte que l'axe vertical du personnage (0,1,0) soit aligné avec le vecteur (1,1,1) du repère monde.

3. Caméra

1. Pour passer d'un espace 2D à une "vraie" vue 3D, il nous faut tout d'abord définir une matrice de projection et l'exploiter dans nos shaders. Pour cela, utilisez la classe Camera dont une instance est déjà associée au viewer. Vous pouvez contrôler la pyramide de vision avec la méthode Camera::setPerspective(...).

2. La seconde étape consiste à contrôler la position et orientation de la caméra via une matrice de vue. Pour cela implémentez la méthode Camera::lookAt(...) dont le but est de définir la matrice mViewMatrix de transformation du repère monde au repère de la caméra à partir d'une position d'observation position, une cible à viser target et une orientation verticale up. Pensez à mettre à jour vos shaders pour exploiter cette matrice supplémentaire (vos shaders doivent maintenant prendre en compte 3 matrices : la matrice objet, la matrice de vue, et la matrice de perspective). Testez en créant une vue de 3/4.

Indices : vous devez construire une base orthonormale 3D dont le premier axe $\vx{z}$ est l'opposé du vecteur de visée. Le deuxième axe $\vx{y}$ correspond au vecteur dans le plan défini par l'orientation verticale up et la direction de visée qui est orthogonal au premier axe. Le dernier axe $\vx{x}$ du repère peut finalement être obtenu par produit vectoriel entre les deux vecteurs précédent. 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. En principe, il est déjà possible de zoomer à l'aide de la molette de la souris. Voir la fonction Camera::zoom() qui est appelée par la fonction Viewer::mouseScroll. La dernière étape consiste donc à mettre en œuvre un contrôle de l'orientation/position de la caméra à la souris via la classe Trackball fournie. Pour cela, vous n'avez qu'à implémenter la méthode Camera::rotateAroundTarget(float angle, Vector3f axis) qui doit mettre à jour la matrice de vue de telle sorte que la caméra tourne d'un angle angle autour du point cible mTarget et de l'axe axis. Testez en cliquant-déplaçant le curseur dans la fenêtre.

4. Système solaire

Pour appliquer les concepts précédents vus en section 2 à un cas concret, vous allez mettre en œuvre un mini système solaire animé. Il inclura au moins la terre, qui devra tourner sur son axe et être en révolution autour du soleil. Vous ajouterez ensuite notre lune en révolution autour de la terre.

1. Commencez par un système solaire minimaliste avec une sphère (sphere.obj) au centre de la scène représentant le soleil et une seconde sphère, plus petite, tournant autour du soleil à une distance fixe.

Conseil : afin de mieux visualiser les rotations, utilisez le mode de rendu en fils de fer vu au TP précédant.

2. Faites en sorte que la terre tourne sur elle-même autour d'un second axe. Modifiez l'axe de la terre afin que son axe de rotation soit incliné (d'environ 23 degrés) comme c'est le cas en réalité. Rappel : vous pouvez utiliser la classe Eigen::AngleAxisf pour construire une rotation autour d'un axe arbitraire.

3. Sur le même principe, ajoutez une lune tournant autour de la terre.

Earth-Moon.svg
Source : Wikipedia

Vous pouvez continuer en ajoutant d'autres planètes et leurs lunes pour former le système solaire complet.