Travaux Pratiques OpenGL
TP9
-
Le but de ce TP est de créer un objet d'extrusion et de le texturer. Ceci va vous permettre de vous sensibiliser aux difficultés de modélisation d'objets 3D complexes ainsi que d'expérimenter le texturage et d'observer les problèmes de distortions. Pour créer l'objet, nous allons nous appuyer sur une cubique d'Hermite et le repère de Frénet. A la fin de ce TP, vous obtiendrez le résultat suivant (à gauche, l'extrusion d'un cercle et à droite l'extrusion d'une étoile) :
-
Ce TP va être effectué en deux temps. Tout d'abord, il va falloir modéliser un objet d'extrusion, puis après avoir défini une paramétrisation, il va falloir plaquer une texture.
-
Modélisation d'un objet d'extrusion le long d'une courbe :
Rappelez le principe général de construction d'un objet d'extrusion à partir d'un profil 2D extrudé le long d'une courbe directrice.
Pour le TP, ceci va être réalisé en trois temps : (1) Tout d'abord le profil va être déplacé dans le repère local du premier point de la courbe (en u=0). (2) Puis pour un pas de variation fixé du paramètre u, le profil va être déplacé en chaque point de la courbe (on se contentera de visualiser la liste des points de l'objet pour vérifier que le résultat est correct). (3) Enfin, il faudra construire un tableau d'index correct pour visualiser l'objet d'extrusion avec des QUADS.
-
Soit le profil suivant :
static float _profil [] = {
1.,0.,0., //sommet numero 0
0.25,0.25,0., //sommet numero 1
0.,1.,0., // ...
-0.25,0.25,0.,
-1.,0.,0.,
-0.25,-0.25,0.,
0.,-1.,0.,
0.25,-0.25,0.};
et la matrice de contrôle de la cubique d'Hermite suivante :
static Matrix _controle = {
4. , -3., 3. , 1., //P0
-4. , 5., 3. , 1., //P1
-30., 0. , -40. , 0., //T0
-30., 0. , 40. , 0. }; //T1
-
Dessinez ce profil dans un repère orthonormé. Dans quel plan se situe le profil ?
-
Ecrivez la fonction int extrudeHermite (Matrix controle, float *profil, int nbPointProfil, float *sommets) qui à partir des paramètres de contrôle d'une cibique d'Hermite controle, d'un profil profil et de son nombre de sommets nbPointProfil, positionne dans le tableau sommets les points du profil dans le repère de Frénet pour u=0 uniquement. La fonction retourne le nombre de valeurs insérées dans le tableau sommets.
Visualisez avec un Vertex Array le tableau de sommets _sommets, déclaré en variable globale static float *_sommets, calculé avec la fonction extrudeHermite (...). Construisez le tableau d'index static GLuint *_indexProfil correspondant pour visualiser _sommets sous la forme d'une GL_LINE_LOOP. Vous devez obtenir le résultat suivant :
-
Faites un schéma d'une courbe paramétrique avec son repère de Frénet (TNB) au premier point (en u=0). Si le profil est tracé dans ce repère en pernant X=T, Y=N, Z=B (comme nous venons de le faire), comment va-t-il être orienté ? Représentez le sur votre schéma. Est-ce le résultat que l'on souhaite obtenir ? Quelle correspondance faut-il faire entre les axes (XYZ) et les axes (TNB) pour que le profil soit correctement orienté ?
Ecrivez la fonction void adaptFrenet(Matrix frenet, Matrix transf) {...} qui réordonne les axes dans la matrice de transformation amenant dans le repère de Frénet afin que le profil soit orienté correctement par rapport à la courbe. Appelez cette fonction dans la fonction extrudeHermite (...) pour obtenir un profil correctement positionné et orienté en u=0 (comme illustré ci-dessous).
-
Maintenant qu'un profil est correctement positionné en u=0, nous allons choisir un pas en u pour parcourir la cubique static float _pasUExtrude = 0.1 (par exemple), et amener le profil en chacun des repères de Frénet correspondant.
-
Modifiez la fonction extrudeHermite (...) pour que le tableau _sommets contienne les sommets du profil en ses différentes positions le long de la courbe.
-
Modifiez le tableau d'index _indexProfil pour visualiser ces points avec un Vertex Array sous la forme de points GL_POINTS. La taille des points à l'écran peut être augmentée en utilisant la fonction glPointSize(float size). Avec des points de taille 6, voici le résultat que vous devez obtenir :
-
Zoomez sur un point. Comment un point est-il affiché en OpenGL ? Déplacez votre caméra : se déforme-t-il ? Qu'en déduisez-vous sur la façon dont le point est projeté à l'écran ?
-
Nous venons de construire l'ensemble des sommets de l'objet d'extrusion. Nous avons donc fini de construire sa Géométrie. Il nous faut maintenant relier les sommets par des arêtes pour pouvoir visualiser des facettes. Il nous faut donc créer sa Topologie. Pour ce TP, nous choisissons de construire une topologie avec des facettes quadrilatérales. Il aurait aussi été possible de construire des facettes triangulaires, ou encore des bandes de triangles, ou ...
-
Le tableau de sommets _sommets est déjà correct. Ecrivez la fonction void quadExtrude (int nbPointProfil, int nbProfil, GLuint *index) {...} qui construit le tableau d'index index correspondant à une visualisation du tableau _sommets avec un Vertex Array sous la forme de GL_QUADS. La variable d'entrée nbPointProfil contient le nombre de sommets du profil et la variable d'entrée nbProfil contient le nombre de répétition du profil le long de la cubique d'Hermite. Avec un tableau d'index calculé avec la fonction quadExtrude (...) et un rendu avec des primitives GL_QUADS, vous devez obtenir le résultat suivant :
Voici le résultat obtenu si on visualise l'objet plein avec un éclairage (lumière LIGHT0 allumée) :
Pourquoi l'éclairage est-il incorrect ?
-
Proposez une méthode de calcul des normales en chaque sommet du profil (dans son plan (XY) de définition). Attention, les normales doivent toutes pointer vers l'extérieur du profil.
-
Ecrivez la fonction void normalProfil (float *profil, int nbPointProfil, int pos, Vector n) {...} qui pour un profil profil composé de nbPointProfil sommets, calcule la normale n au sommet numéro pos du profil.
-
Modifiez la fonction int extrudeHermite (Matrix controle, float *profil, int nbPointProfil, float pasU, float *sommets, float *normales) {...} en ajoutant le vecteur des normales float *normales dans ses paramètres de sortie. Il va donc falloir à chaque fois qu'un sommet est ajouté au tableau sommets, transformer aussi sa normale pour qu'elle soit correctement orientée par rapport au repère local de la courbe et l'ajouter au tableau normales.
Les normales sont stockées dans le tableau static float *_sommets et elles sont prises en compte lors du tracé du Vertex Array en ajoutant les fonctions glEnableClientState (GL_NORMAL_ARRAY), glNormalPointer (GL_FLOAT, 0, _normales) et glDisableClientState (GL_NORMAL_ARRAY). Si vous n'arrivez pas à placer correctement ces fonctions dans votre programme, regardez votre cours. Prenez un pas en u plus fin (par exemple _pasUExtrude = 0.01) et en éclairant, vous pourrez obtenir le résultant suivant :
-
Essayez votre extrusion avec le profil suivant :
static float _profil [] = {
1.,0.,0.,
0.866, 0.5, 0.,
0.70711,0.70711,0.,
0.5, 0.866, 0.,
0.,1.,0.,
-0.5, 0.866, 0.,
-0.70711,0.70711,0.,
-0.866, 0.5, 0.,
-1.,0.,0.,
-0.866, -0.5, 0.,
-0.70711,-0.70711,0.,
-0.5, -0.866, 0.,
0.,-1.,0.,
0.5, -0.866, 0.,
0.70711,-0.70711,0.,
0.866, -0.5, 0.,};
-
Sachant que le paramètre u de la cubique d'Hermite varie entre 0 et 1, modifiez très simplement votre code pour que le profil change d'échelle linéairement le long de la courbe. Ceci doit donner le résultat suivant :
Puis créez votre propre profil et modifiez la forme de la cubique.
-
Deuxième partie : Placage de texture.
-
Qu'est-ce qu'une texture ? Que fait-on lorsque l'on paramétrise une surface ? Rappelez le principe de placage d'une texture 2D sur une surface.
-
Soit une texture de taille 2×2, dont la couleur de chaque pixel est codée en RGBA sur 4×8 bits (1 octet pour le rouge, 1 octet pour le vert, 1 octet pour le bleu, 1 octet pour la transparence) avec les valeurs suivantes :
static GLubyte _texture[] = {
255,0,0,0,0xFF,0777,0xFF,0,
0xFF,255,0xFF,0,0777,0,0,0 };
Sachant qu'un nombre commançant par 0 est codé en octal et qu'un nombre commançant par Ox est codé en hexadécimal, dessinez cette texture et donnez ses couleurs.
-
Le texturage de l'objet va être effectué en trois temps : (1) chargement de la texture, (2) paramétrisation de la surface, (3) placage de la texture sur l'objet.
-
Il est possible de charger directement une texture de taille quelconque avec la librairie GLU en utilisant la fonction gluBuild2DMipmaps (GL_TEXTURE_2D,4,2,2,GL_RGBA,GL_UNSIGNED_BYTE, _texture). Décrivez ce que fait cette fonction et donnez la signification de chacun de ses paramètres.
La texture que l'on plaque sur l'objet fait 2×2 pixels alors que la surface d'objet d'extrusion est bien plus grande. Il faut donc choisir la façon dont la couleur de la texture va être affectée aux différents points de la surface. Pour le moment nous choisissons de mettre la couleur du pixel de la texture le plus proche. Ceci est fait avec les fonctions suivantes :
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
A l'aide de la documentation OpenGL, expliquez sur un schéma ce que font ces fonctions avec ces valeurs de paramétres.
-
Voici la partie la plus embêtante. Soit v et w les deux paramètres de textures sur la surface d'extrusion, v variant de O à 1 le long de la cubique d'Hermite et w variant de 0 à 1 autour de la cubique. Faites un schéma pour illustrer la variation de ces deux paramètres sur la surface d'extrusion.
-
Que ce passe-t-il pour les points où w=0 et ceux où w=1 ?
-
Peut-on donner deux coordonnées de texture à un même sommet du maillage ?
-
Pourquoi est-il nécessaire de modifier la modélisation de l'objet d'extrusion (en ajoutant un sommet au profil) pour que le dernier sommet du profil soit à la même position que le premier ?
-
Faites cette modification au niveau du profil _profil, et modifiez le calcul de _sommets, _normales et _indexProfil en concéquence.
Maintenant vous disposez d'une géomtrie avec des sommets où w va valloir 0 et d'autres sommets, situés au même endroit, mais où w va valloir 1. La texture va donc pouvoir être plaquée correctement. Il reste tout de méme à affecter les coordonnées de texture aux différents sommets du maillage de l'objet d'extrusion.
Modifiez la fonction int extrudeHermite (Matrix controle, float *profil, int nbPointProfil, float pasU, float *sommets, float *normales, float *texCoord) {...} en ajoutant le tableau float *texCoord pour quelle retourne aussi en sortie le tableau des coordonnées texture texCoord associées à chaque sommet du maillage de l'objet d'extrusion. A l'appel de la fonction extrudeHermite (...), ces coordonnées seront stockées dans le tableau static float *_texCoord. Quelle va être la taille de ce tableau ?
-
Maintenant, il faut activer l'utilisation des textures avant le tracé de l'objet d'extrusion avec la fonction glEnable(GL_TEXTURE_2D), et les désactiver après le tracé. Ensuite, de la même façon que l'on a fait pointer le Vertex Array OpenGL vers le tableau de sommets _sommets et Normal Array vers le tableau de normales _nomales, nous allons faire pointer le Texture Coord Array vers le tableau _texCoord avec les fonctions glEnableClientState (GL_TEXTURE_COORD_ARRAY), glTexCoordPointer(2,GL_FLOAT, 0, _texCoord) et glDisableClientState (GL_TEXTURE_COORD_ARRAY). Placez correctement toutes ces fonctions dans votre boucle de rendu pour obtenir le texturage de l'objet d'extrusion.
ATTENTION : Le placage de la texture se fait après la rasterisation dans le pipeline de rendu OpenGL. Ceci implique que la couleur en chaque sommet a déjà été calculée en fonction du matériau et de l'éclairage, alors que la couleur de la texture n'a toujours pas été appliquée. Quand elle est appliquée, la couleur de la texture se mélange avec la couleur calculée lors de la rasterization. Ainsi, pour que la couleur de la texture soit respectée, le matériau doit être blanc. Par exemple, vous pouvez utiliser le matériau suivant pour votre objet d'extrusion :
static float blanc_ambient [] = {0.7,0.7,0.7,1.0};
static float blanc_diffuse [] = {0.5,0.5,0.5,1.0};
static float blanc_specular [] = {0.5,0.5,0.5,1.0};
static float blanc_shininess = 30.;
Pour que ce matériau soit correctement pris en compte, entourez le tracé l'objet d'extrusion et la définition de son matériau par glDisable (GL_COLOR_MATERIAL) et glEnable (GL_COLOR_MATERIAL), afin que le mode GL_COLOR_MATERIAL soit actif pour le tracé du pantin, mais pas pour celui du tracé de l'objet d'extrusion.
Ouf! Avec tout ceci et si vous n'avez pas de bugs, voila le résultat que vous devez obtenir :
Pour bien voir le modèle et les effets de lumière, la lampe suivante (GL_LIGHT3) a été ajoutée (fixe dans la scène, comme la lampe GL_LIGHT0) :
static float light3_ambient []= {0.2,0.2,0.2,1.0};
static float light3_diffuse []= {1.,1.,1.,1.0};
static float light3_specular []= {1.,1.,1.,1.0};
static float light3_position []= {5.,3.,-7.5,1.0};
-
Le plus dur est fait, maintenant, il ne s'agit que d'expérimenter quelques fonctionalités des textures :
-
Matériau : Comme cela a été spécifié ci-dessus, la couleur de la texture se mélange avec la couleur calculée après la prise en compte de l'éclairage et du matériau en chaque sommet du maillage et l'interpolation de la couleur par la rasterisation, ainsi, les trois composantes ambiantes, diffuses et spéculaires vont prendre la couleur de la texture : ceci correspond à un matériau conducteur comme un métal (voir TP4). Si l'on souhaite pouvoir contrôler la couleur de la composante spéculaire du matériau pour faire un matériau isolant comme un plastique, il faut éviter le mélange de la couleur de la texture avec la composante spéculaire. Ceci peut être fait en différant le calcul de la composante spéculaire de l'éclairage après le placage de la texture en utilisant la fonction glLightModeli (GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR). Ceci vous donnera l'effet suivant au niveau spéculaire : le reflet blanc donné par la composante spéculaire blanche du matériau ne se mélange pas avec la couleur rouge de la texture (comme illustré ci-dessous).
-
Répétition de la texture : Par défaut, la texture est plaquée dans son intégralité quand v et w varient entre 0 et 1. Elle est replaquée dans son intégralité pour v variant entre 0 et 1 et w variant entre 1 et 2. En fait, il y a une occurence de la texture pour chaque variation de v et w entre deux entiers successifs. Adaptez votre programme en quelques simples modifications pour pouvoir paramétrer le nombre d'occurence de la texture en v (static float _repetitionTexV) et en w (static float _repetitionTexW). Ci-dessous le résultat avec _repetitionTexV = 20 et _repetitionTexW = 4.
-
Lissage de la texture : Comme on peut le constater dans l'image ci-dessus, un pixel de la texture (qui fait 2 pixels par 2 pixels) se projette souvent sur plusieurs pixels de l'image. La question qui se pose devient la suivante : comment la couleur de la texture doit-elle être plaquée ? Pour un pixel de l'image, on prend la couleur de texture la plus proche (en coordonnées v, w), ou on fait une interpolation entre les différentes couleurs voisines ? Jusqu'à maintenant, nous avons pris la couleur la plus proche. Observez le résultat si on interpole les couleurs. Ceci est fait en remplaçant GL_NEAREST par GL_LINEAR dans les 2 fonctions glTexParameteri().
-
Charger une texture depuis un fichier .raw : Un fichier .raw est un fichier "brut". C'est à dire qu'il n'y a que les couleurs de l'image sotckées pixel par pixel. Il n'y a pas d'information sur le codage de la couleur (3 octets en RGB ou 1 octet en dégradé de gris = LUMINANCE ou 4 octets en RGBA, ... ?) et sur la taille. Ces informations doivent donc être connues par avance.
Voici une image de taille 300×300 dont les couleurs sont codées sur 3 octets en RGB :
texture 1.
Ce fichier peut être chargé dans la texture _texture (dans laquelle il y a pour l'instant la texture 2×2 rouge et blanche) avec le code suivant :
FILE *f;
f = fopen("Textures/twister1_300_300.raw", "rb"); // Ouverture du fichier en lecture
_largTex = 300; // Largeur de l'image
_hautTex = 300; // Hauteur de l'image
_texture = (GLubyte *)malloc(sizeof(GLubyte)*_largTex*_hautTex*3); // Allocation memoire de la texture avec des couleurs codees sur 3 octets
fread(_texture, 1, _largTex*_hautTex*3, f); // Copie de l'image dans _texture
Modifiez les paramètres de la fonction gluBuild2DMipmaps() pour plaquer cette texture. Voici le résultat avec _repetitionTexV = 5 et _repetitionTexW = 1 :
Voici trois autres images .raw que vous pouvez plaquer en texture :
-
Une image de taille 640×480 dont les couleurs sont codées sur 3 octets en RGB : texture 2.
-
Une image de taille 474×372 dont les couleurs sont codées sur 3 octets en RGB : texture 3.
-
Une image de taille 320×240 dont les couleurs sont codées sur 1 octet en LUMINANCE : texture 4.
-
Plaquez les différentes textures avec différentes répétitions sur différents profils. Observez les distortions dans l'image plaquée et la différence entre le lissage GL_LINEAR (interpolation linéaire) et la sélection de la couleur la plus proche GL_NEAREST (cette différence est très claire quand on s'approche de l'objet).
Fin du TP9