TD6 : Textures
Pour démarrer ce TD dans de bonnes conditions, repartez de cette base de code :
pghp_td6.zip
Vous pourrez désormais contrôler la caméra à l'aide de la souris (classe Trackball
) et charger un maillage (classe Mesh
) aux formats OBJ et OFF.
1 - Introduction
Voilà les étapes nécessaires à la création d'une texture en OpenGL :
- obtention d'un identifiant de texture (
glGenTextures
) - lien avec cette texture (
glBindTexture
) - spécification des paramètres (
glTexParameter
), notamment de filtrage et de répétition/pavage - chargement de l'image depuis un fichier
- envoie des dimensions et des texels (
glTexImage2D
) - génération des niveaux de mipmaps (
glGenerateMipmap
)
En pratique, vous utiliserez la bibliothèque SOIL2 (déjà intégrée au projet) qui permet de réaliser ces opérations en un appel de fonction (SOIL_load_OGL_texture
).
À l'affichage, il faut spécifier sur quelle unité de texture on veut attacher la texture (glActiveTexture
) en la liant avec l'identifiant de texture correspondant (glBindTexture
). Dans les shaders, il est alors possible de manipuler les unités de textures auxquelles on a associées un nom par le passage d'un uniform (uniform sampler2D Tex2D
prend la valeur 0 pour accéder à l'unité 0, par exemple).
Bien sûr, il faut également fournir les coordonnées de texture de l'objet comme attributs pour pouvoir accéder au sampler
par la fonction GLSL texture
. Pour la première partie de ce TD, vous utiliserez une sphère paramétrique (classe Sphere
) pour laquelle il est simple de calculer des coordonnées de texture (latitude et longitude).
2 - Placage de texture
1. Dans Viewer::init
, Chargez "earth1.png" (depuis le répertoire "data/textures") dans une texture OpenGL à l'aide de SOIL. Créez un shader affichant cette texture à la surface de la sphère. Pour cela, vous devrez passer les coordonnées de texture in vec2 vtx_texcoord
du vertex au fragment shader, puis les utiliser pour accéder à un sampler2D
(déclaré comme variable uniforme) avec la fonction GLSL texture
.

2. Pour simuler l'éclairage par le soleil, vous allez ajouter la contribution d'une source lumineuse directionnelle, de direction $\vx{l}$. Pour pouvoir calculer l'éclairage, vous avez besoin de connaître l'orientation de la surface. Cette information est représentée par les normales (vecteurs 3D) que nous noterons $\vx{n}$. Les normales sont transmises par les classes filles de Shape
aux shaders en tant qu'attribut vec3 vtx_normal
.
Modifiez le vertex shader pour qu'il récupère l'attribut vtx_normal
en entrée et qu'il le transmette au fragment shader. Comme dans le TD2, calculez alors le terme en cosinus : $\cos(\theta) = \max(\vx{n} \cdot \vx{l}, 0)$. Utilisez-le pour faire varier la couleur finale entre celle de la texture et le noir.
Remarque : vous pouvez spécifier la direction de la lumière "en dur" dans votre shader, ou permettre à l'utilisateur de la définir interactivement et la passer à votre shader dans une variable uniforme.
3. Chargez la deuxième texture "earth2.png" – correspondant à la terre vue de nuit – dans une deuxième unité de texture. Modifiez le code de votre fragment shader pour remplacer le noir par cette texture (vous pouvez utiliser la fonction GLSL mix
).
Bonus : appliquez à chaque astre du système solaire développé lors du précédent TD une texture (cherchez sur internet des images avec comme mots clés "moon", "earth", "sun" $+$ "texture"), voire plusieurs textures afin de donner l'impression d'une atmosphère.
3 - Filtrage
1. Ajoutez les attributs suivant à la classeViewer
:
GLuint _samplerId;
GLenum _minFilter = GL_NEAREST;
GLenum _magFilter = GL_NEAREST;
Dans la fonction Viewer::init
, créez un sampler object (cf. glGenSamplers) et initialisez ses paramètres de filtrage à _minFilter
et _magFilter
. Lors de l'affichage, activez ce sampler et redéfinissez systématiquement ses paramètres de filtrage : ainsi, si ceux-ci sont modifiés, ils seront automatiquement mis à jour. Les paramètre du sampler sont utilisés à la place de ceux des textures lorsqu'un sampler est lié, par conséquent toutes vos textures seront filtrées avec les paramètres _minFilter
et _magFilter
.

Dans Viewer::keyPressed
, assignez des touches pour modifier les variables _minFilter
et _magFilter
de façon à pouvoir tester toutes les combinaisons de filtres. La magnification accepte les paramètres GL_NEAREST
et GL_LINEAR
, et la mingnification accepte en plus les 4 valeurs GL_*_MIPMAP_*
, où *
est remplacé par GL_NEAREST
ou GL_LINEAR
.
En jouant sur le zoom, observer la qualité obtenue avec les différents modes de filtrage. Testez avec la sphère et le maillage "cow.obj". Vous pouvez aussi tester avec un plan (classe Quad
), ainsi qu'avec d'autres textures, comme "cow.jpg" et "checkerboard.png".
2. Utilisez le code suivant dans Viewer::init
pour générer procéduralement une texture et sa mip-map :
int w = 256;
int h = 256;
unsigned char colors[6][3] = { {255, 0 , 0}, {0, 255, 0 }, { 0, 0, 255},
{255, 255, 0}, {0, 255, 255}, {255, 0, 255} };
unsigned char data[w*h*3];
glGenTextures(1,&mTexID);
glBindTexture(GL_TEXTURE_2D, mTexID);
for(int l=0; ; ++l) {
for(int i=0; i<w*h; ++i){
data[3*i ] = colors[l][0];
data[3*i+1] = colors[l][1];
data[3*i+2] = colors[l][2];
}
glTexImage2D(GL_TEXTURE_2D, l, (GLint)GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
if(w==1 && h==1)
break;
w = std::max(1, w/2);
h = std::max(1, h/2);
}
Ce code charge dans chaque niveau de mip-map une image de couleur unie, alternativement rouge, vert, bleu, jaune, cyan et magenta. Prenez un peu de temps pour comprendre le fonctionnement de ce code.
Maintenant, re-testez les différents modes de filtrage sur plusieurs modèles avec cette texture. Vous pouvez éventuellement désigner une touche pour passer d'une texture standard à cette textures spéciale. Qu'en concluez-vous sur le fonctionnement du mip-mapping ?
Important : les niveaux de mip-map étant générés à la main, il ne faut pas appeler glGenerateMipmap()
ou utiliser le flag SOIL_FLAG_MIPMAPS
pour que cet exercice fonctionne.

4 - Motifs procéduraux (bonus)
1. En utilisant la position des sommets du maillage dans le repère du modèle, écrire un shader permettant d'obtenir une grille procédurale à la surface de l'objet.
L'idée est d'associer une couleur (ici, blanche ou noire) à chaque point de la surface de l'objet. Pour une grille, il suffit de répéter la procédure pour créer des hachures selon les deux directions et de mélanger le résultat. Pour générer des hachures, il faut donc définir une fonction $F$ qui à la position $t$ associe une couleur $c$. Cette fonction à la forme :

Dans un premier temps, elle peut être obtenue en utilisant la fonction GLSL step(x,t)
qui renvoie 0 si $t < x$ et 1 si $t \geq x$. De cette façon, on obtient une seule marche. Pour obtenir plusieurs bandes, il faut moduler la fréquence en utilisant la fonction fract
.
2. Les lignes obtenues sont cependant très crénelées. Pour en obtenir des plus douces, vous pouvez utiliser un cosinus au lieu de fract
et la fonction mix
pour mélanger les couleurs.