TD6 - Textures
Pour démarrer ce TD dans de bonnes conditions, repartez de cette correction partielle des TD précédents :
mds3d_td6.zip
Vous pouvez désormais contrôler la caméra à l'aide de la souris (classe Trackball
) et faire tourner le maillage en pressant la touche 'a'. L'éclairage diffus est calculé dans le fragment shader.
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 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 (samler) on veut attacher la texture à l'aide de glActiveTexture
en la liant avec l'identifiant de texture correspondant en appelant 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. Par exemple, pour indiquer que la variable GLSL uniform sampler2D tex2D
doit accéder à l'unité de texture i, on appellera glUniform1i(_shader.getUniformLocation("tex2D"), i)
.
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
. Les maillages earth.obj, cow.obj et plane.obj fournis possèdent déjà des coordonnées de texture, il ne vous reste plus qu'à mettre à jour la fonction Mesh::draw
pour les transmettre aux shaders (comme vu aux TD4 et 5 pour les attributs de couleurs et de normales). Pour vérifier que c'est bien le cas, passez les coordonnées de texture du vertex au fragment shader et affichez-les comme une couleur.

2. Placage de texture
1. Dans Viewer::init
, chargez "earth.jpg" (depuis le répertoire "data/textures") à l'aide de SOIL_load_OGL_texture
(cf. exemple d'utilisation). Créez un shader affichant cette texture à la surface de la sphère. Pour cela, utilisez les coordonnées de texture interpolées dans le fragment shader 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}$ (variable uniforme lightDir
). Comme précédemment, utilisez le terme en cosinus : $\cos(\theta) = \max(\vx{n} \cdot \vx{l}, 0)$ où $\vx{n}$ est la normale au fragment, pour faire varier la couleur finale entre celle de la texture et du noir.
3. Pour donner l'impression d'une atmosphère, chargez la texture "earth_clouds.jpg"dans une deuxième unité de texture. Ajoutez un second sampler2D
au fragment shader et mélangez la couleur lue dans cette texture avec celle de la terre. Utiliser la fonction GLSL mix
pour obtenir un fondu continu.
4. Chargez enfin la texture "earth_night.jpg" – correspondant à la terre vue de nuit – dans une troisième unité de texture. Modifiez le fragment shader pour que les zones non-éclairées utilisent cette texture. Pour cela, effectuez à nouveau un mix
avec $\cos(\theta)$ comme poids de mélange.
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). Lors de l'affichage, activez ce sampler et redéfinissez systématiquement ses paramètres de filtrage à _minFilter
et _magFilter
avec la fonction glSamplerParameteri
(vous devrez forcer le cast à GLint
). Ainsi, si ceux-ci sont modifiés, ils seront automatiquement mis à jour. Les paramètres du sampler sont utilisés à la place de ceux de la texture à laquelle il est lié (avec glBindSampler
). Faites en sorte de filtrer toutes vos textures 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 minification 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" et la texture "cow.jpg" associée. Vous pouvez aussi tester avec un plan ("plane.obj") et la texture "checkerboard.png".
2. Utilisez le code suivant dans Viewer::init
pour générer procéduralement une texture et sa mip-map :
int texW = 256;
int texH = 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[texW*texH*3];
glGenTextures(1,&_texID1);
glBindTexture(GL_TEXTURE_2D, _texID1);
for(int l=0; ; ++l) {
for(int i=0; i<texW*texH; ++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, texW, texH, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
if(texW==1 && texH==1)
break;
texW = std::max(1, texW/2);
texH = std::max(1, texH/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.
