Programmation Graphique : TP séance 05
- Rendu du TP sur Ametice :
- Suivez les instructions pour le rendu du TP en mettant le bon numéro de séance dans le nom du répertoire.
- N'oubliez pas de rendre votre travail à la fin de la séance comme indiqué, même si vous n'avez pas fini la planche de TP, puis de rendre la version finale (sans changer le nom du fichier) avant le début de la séance suivante.
Exercice 1. Roue dentée avec lumière diffuse
On se propose de reprendre le dessin de la roue dentée du TP1, cette fois-ci en OpenGL core. Toutes les facettes auront le même couleur, et seront rendues visibles par une lumière ambiante et diffuse. Le trou aura un lissage de Phong.
Dans votre nouveau répertoire pgra-TP05-NOM1-NOM2
, récupérer les
fichiers suivants :
le Makefile
du TP 04 (qui compile avec stb_image
),
les fichiers de GLAD glad.c
, glad.h
,
khrplatform.h
,
le module de calcul matriciel vmath.h
,
le petit complément vmath-et.h
,
la librairie tout-en-un stb_image.h
et le petit
module C associé stb_image.c
.
Enfin, récupérer l'exemple de départ fw51-light.cpp
.
Vérifiez que vous pouvez compiler et exécuter l'exemple (au besoin, voir 3.1. Installation et compilation dans le CM 01).
On peut afficher les shaders par défaut avec l'option -ps
(print shader), et
modifier les shaders avec les options -vs vs_file
et -fs fs_file
.
La touche U
recharge les shaders sans quitter le programme.
La touche a
déclenche une animation, la touche p
change le type de
projection, h
affiche l'aide pour les réglages de la projection.
Les éléments utiles en OpenGL core pour réaliser ce TP sont expliqués dans le CM 05, en particulier dans la section 9.3. Exemple du Kite, pour le calcul des normales.
1.1. Paramètres d'une roue
En vous inspirant de la classe Kite
présente dans l'exemple, déclarer
une classe RoueNor
avec les données membres suivantes (identiques au TP 01),
en rajoutant le préfixe m_
:
- le nombre de dents
nb_dents
(dans l'exemple il y en a 10) ; - le rayon intérieur
r_trou
(la distance \(OA\) dans la figure ci-dessous) ; - le rayon de la roue
r_roue
(le rayon du grand cercle jaune qui passe entre \(C\) et \(D\)) ; - la hauteur des dents
h_dent
(la différence de rayons entre le cercle rose qui passe par \(D\) et \(E\), et le cercle rose qui passe par \(B\), \(C\), \(F\)) ; - la couleur
(coul_r, coul_v, coul_b)
; - l'épaisseur
ep_roue
(en \(z\)).
1.2. Mémorisation des faces
Le but est de tout mémoriser dans un seul VBO, avec pour chaque sommet : sa position, sa couleur et la normale au triangle dont il fait partie.
Dans le VBO il y aura de quoi dessiner les GL_TRIANGLE_STRIP
suivants :
- la face de devant,
- la face de derrière,
- le pourtour du trou avec lissage de Phong,
- le pourtour des dents (1 triangle strip par plan à cause des normales).
Le dessin du côté d'une dent avec un triangle strip comme indiqué sur la figure nécessite l'introduction des sommets I = (A+H)/2 et J = (H+G)/2 (cliquer sur la figure pour l'agrandir).
Pour mémoriser la face de devant, on mémorise dans le VBO les sommets dans l'ordre :
- pour la première dent : A,B,I,C,H,D,J,E ;
- puis pour chaque dent suivante, les sommets dans le même ordre avec une rotation (des sommets de la première dent) ;
- et à la fin on rajoute encore A,B pour "fermer" le triangle strip.
On ne mémorise donc pas les sommets G,F d'une dent car ils correspondent aux
sommets A,B de la dent suivante.
Tous ces sommets auront la même couleur et la même normale (ils sont coplanaires
sur le plan z = +ep_roue/2
).
Astuce : comme la face est dans le plan \(Oxy\), on peut directement prendre comme
normale \((0,0,1)\).
L'important pour que les triangles soient bien orientés (la face avant vers
l'extérieur et la face arrière vers l'intérieur de la roue) est que le premier
triangle dans le triangle strip soit anti-clockwise, c'est-à-dire dans
l'ordre trigonométrique ; c'est bien le cas ici. L'orientation de la face permet
de tester gl_FrontFacing
dans les fragment shader, et de correctement
calculer la normale au triangle pour l'éclairage.
Pour la face à l'arrière de la roue, attention il faudra que les triangles (et les normales) soient eux aussi bien orientés, de manière à ce que si l'on retourne la roue (avec une rotation de la roue de 180° autour de l'axe des \(y\)), cette face soit correctement éclairée. Le plus simple est de faire une symétrie centrale, c'est-à-dire de reprendre la boucle complète de mémorisation de la première face en inversant les signes des coordonnées des positions et des normales.
Avant de vous lancer dans le code, lisez la section suivante !
1.3. Un peu d'aide
Pour faciliter l'écriture voici quelques suggestions ;
dans la classe Roue
déclarer :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
Avec get_sommet_pos
vous pourrez directement obtenir les coordonnées d'un
sommet à partir de son numéro num_som
et du numéro de sa dent num_dent
.
Le calcul du rayon de I
et de J
est obtenu avec un peu de trigonométrie,
complétez les lignes 10,11 par :
10 11 |
|
1.4. Mémorisation des pourtours
Le pourtour du trou sera rendu avec un lissage de Phong ; cela permet de mémoriser tous les sommets en un seul triangle strip. Pour rajouter les sommets, couleur et normales du pourtour du trou dans le VBO, il suffit de faire comme dans la section précédente :
- pour la première dent, on insère : A',A,H',H ;
- puis pour chaque dent suivante, on insère les sommets dans le même ordre avec une rotation (des sommets de la première dent) ;
- et à la fin on rajoute encore A',A pour "fermer" le triangle strip.
Les normales doivent "pointer" vers l'axe Oz, il suffit donc pour un sommet \((x,y,z)\) de prendre comme normale \((-x,-y,0)\).
Enfin pour le pourtour des dents, il va falloir insérer 4 triangle strip par dent, chacun avec la normale correspondant à son plan : BB'CC', CC'DD', DD'EE', EE'FF'. En effet le pourtour des dents ne doit pas être lissé.
1.5. Affichage
Pour afficher la roue dans la méthode draw
, il faut appeler
glDrawArrays (GL_TRIANGLE_STRIP, start_index, vertex_number)
pour chaque triangle strip, ce qui implique de bien calculer
les paramètres start_index
et vertex_number
en fonction de
nb_dents
.
Rajouter un paramètre flag_fill
à la méthode draw
de manière à pouvoir
choisir entre des faces remplies et un dessin "fil de fer" (touche L
)
grâce à la fonction glPolygonMode
(voir TP 02).
Dans l'application, incrémenter un angle m_alpha
avec la touche espace, et
tourner la scène dans displayGL
avec une rotation autour de l'axe des \(z\).
Exercice 2. Sphère avec lumière spéculaire
On se propose de générer récursivement une sphère triangulée, avec ou sans lissage de Phong, avec lumière ambiante et diffuse, avec ou sans lumière spéculaire.
Dans le même répertoire que pour l'exercice 1, récupérer
l'exemple de départ fw53-specular.cpp
.
2.1. Paramètres d'une sphère
En vous inspirant de la classe Kite
présente dans l'exemple, déclarer
une classe Sphere
avec les données membres suivantes,
en rajoutant le préfixe m_
:
- le rayon
rayon
de la sphère, par défaut0.5
; - le nombre d'étapes
nb_etapes
du découpage récursif ; - la couleur
(coul_r, coul_v, coul_b)
; - le booléen
flag_lissage
.
2.2. Découpage récursif
On partage au départ la sphère en 8 grands triangles (sphériques), comme dans la
figure, en veillant à les orienter dans le sens trigonométrique (le devant vers
l'extérieur de la sphère). Sur chacun des 8 triangles on appellera la fonction
de découpage récursive decouper_triangle
:
La fonction decouper_triangle
prend en entrée 3 sommets \(A\), \(B\), \(C\),
un niveau de récursion n
(0 au premier appel),
et par référence le vector
de GLfloat
qu'on passera à la fin au VBO.
La fonction calcule les points \(D\), \(E\), \(F\) comme sur la figure, en utilisant le fait que
où \(R\) = m_rayon
; on fait de même pour \(E\) et \(F\).
La fonction effectue ensuite 4 appels récursifs pour le niveau n+1
,
sur les sommets dans l'ordre : \(A,D,F\) ; \(D,B,E\) ; \(E,C,F\) ; \(D,E,F\).
Au début de la fonction on place le cas terminal : si n
vaut nb_etapes
alors on mémorise pour les 3 sommets ; leur position, leur couleur et leur normale,
tout ceci dans le vector
de GLfloat
, puis on revient.
(Remarque : il ne faut surtout pas mémoriser les sommets dans les étapes intermédiaires).
On donnera la même couleur à tous les sommets. Quant aux normales :
-
si le lissage est désactivé (
m_flag_lissage
estfalse
), on mémorisera pour chaque sommet d'un triangle, la normale calculée du triangle (produit vectoriel de deux vecteurs) ; -
si le lissage est activé, on donne en chaque sommet \(P\) sa normale à la surface de la sphère, qui est par définition \(\vec{OP}\) (à un facteur d'échelle près, qui n'a pas d'importance puisque le fragment shader normalisera les coordonnées).
2.3. Affichage
Pour l'affichage des triangles dans la méthode draw
, appeler glDrawArrays
avec GL_TRIANGLES
, en lui fournissant le nombre de sommets générés.
Remarque : à l'étape 0 il y a 8 triangles, à l'étape 1 il y a \(8*4^1\) triangles, etc, donc à l'étape \(k\) il y a \(8*4^k\) triangles.
Rajouter un paramètre flag_fill
à la méthode draw
de manière à pouvoir
choisir entre des faces remplies et un dessin "fil de fer" (touche L
)
grâce à la fonction glPolygonMode
(voir TP 02).
Dans MyApp
, instancier 2 sphères, l'une lissée et l'autre non, de manière
à passer de l'une à l'autre à l'aide de la touche o
comme dans l'exemple
de départ.
Pour pouvoir activer ou désactiver la lumière spéculaire, gérer un flag avec
la touche s
, passer le flag en uniform
au fragment shader par défaut,
et modifier la somme des composantes de lumières en conséquence.
Tests supplémentaires : dans le fragment shader, utiliser uniform vec4 mousePos
pour déplacer la source de lumière
(voir 7.3.2. Position de la souris).