Les filtres et l'éclairage

Jeff Molofee (NeHe)

Traducteur

Mickaël Le Trocquer

Dans ce tutoriel, je vais vous apprendre comment utiliser trois différents filtres. Je vais vous expliquer comment bouger un objet en utilisant les touches du clavier et je vais aussi vous montrer comment rajouter de l'éclairage dans votre scène OpenGL. Beaucoup de choses seront vues dans ce tutoriel, donc si le précédent vous a causé des soucis, je vous conseille de l'étudier plus en profondeur. Il est important de bien comprendre les bases avant d'étudier le code qui suit. ♪

- Titre : Les filtres et l'éclairage

- Auteur : Jeff Molofee (NeHe)

- Traducteur : Mickaël Le Trocquer

- Parution : 23 mars 2007

- Mise à jour : 19 décembre 2012

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2007 Nehe Gamedev.net. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Contributions

- II. Tutoriel

- II-A. Les inclusions et déclarations

- II-B. La fonction LoadBMP

- II-C. La fonction LoadGLTextures

- II-D. La fonction InitGL

- II-E. La fonction DrawGLScene

- II-F. La fonction WinMain

- II-G. Conclusion

- III. Téléchargements

- IV. Remerciements

- V. Liens

I - Contributions

Nous recherchons toujours des traducteurs pour finir ce projet de traduction des articles de NeHe. Votre aide serait appréciée ! Vous êtes intéressé ? Alors contactez-nous à l'adresse suivante : nehe@redaction-developpez.com.

II - Tutoriel

II-A - Les inclusions et déclarations

Nous allons modifier le code à partir de la première leçon une fois encore. Comme d'habitude, s'il y a des gros changements, je réécrirais la partie entière du code qui sera modifié. Nous allons commencer en rajoutant quelques nouvelles variables au programme.

Inclusion des bibliothèques et déclaration des variables globales

#include <windows.h>   // Fichier en-tete pour Windows
#include <stdio.h>     // Fichier en-tete pour les entrees/sorties standards ( nouveau )
#include <gl\gl.h>     // Fichier en-tete pour la bibliotheque OpenGL32
#include <gl\glu.h>    // Fichier en-tete pour la bibliotheque GLu32
#include <gl\glaux.h>  // Fichier en-tete pour la bibliotheque GLaux

HDC       hDC=NULL;        // Contexte de peripherique GDI prive
HGLRC     hRC=NULL;        // Contexte de rendu permanent
HWND      hWnd=NULL;       // Contient l'identificateur de la fenetre
HINSTANCE hInstance;       // Contient l'identificateur de l'instance de l'application
bool      keys[256];       // Tableau utilise pour la gestion du clavier
bool      active=TRUE;     // Indicateur pour savoir si la fenetre est active
bool      fullscreen=TRUE; // Indicateur pour savoir si la fenetre est en plein ecran

Les lignes suivantes sont nouvelles. Nous allons rajouter trois variables booléennes. BOOL signifie que la variable pourra avoir seulement avoir comme valeur TRUE (vrai) ou FALSE (faux). Nous créons une variable appelée light (lumière) pour savoir si l'éclairage est actif ou non. Les variables lp et fp sont utilisées pour savoir si les touches 'L' ou 'F' ont été appuyées. Je vous expliquerai, plus tard dans le code, l'intérêt de ces variables. Pour l'instant, retenez juste qu'elles sont nécessaires.

Déclaration des drapeaux

BOOL light;  // Éclairage OUI / NON
BOOL lp;     // L appuyee?
BOOL fp;     // F appuyee?

Maintenant, nous allons déclarer cinq variables qui stockeront l'angle sur l'axe des X (xrot), l'angle sur l'axe des Y (yrot), la vitesse de rotation de la boîte sur l'axe des X (xspeed), et la vitesse de rotation de la boîte sur l'axe des Y (yspeed). Nous allons également créer une variable nommée z qui contiendra la profondeur d'écran de la boîte sur l'axe des Z.

Déclaration des variables pour la rotation du cube

GLfloat xrot;     // Rotation sur l'axe des X
GLfloat yrot;     // Rotation sur l'axe des Y
GLfloat xspeed;   // Vitesse de rotation sur l'axe des X
GLfloat yspeed;   // Vitesse de rotation sur l'axe des Y
GLfloat z=-5.0f;  // Profondeur dans l'écran

Maintenant nous allons définir un tableau qui servira pour créer la lumière. Nous allons utiliser deux types différents d'éclairage. Le premier type d'éclairage est appelé éclairage ambiant. L'éclairage ambiant est la lumière qui ne provient pas d'une direction en particulier. Tous les objets de votre scène seront ainsi éclairés par l'éclairage ambiant. Le second type de lumière est appelé lumière diffuse. La lumière diffuse provient d'une source lumineuse et va venir se réfléchir sur la surface d'un objet de votre scène. Toutes les surfaces d'un objet sur lesquelles la lumière viendra directement seront très lumineuses, et les zones où la lumière se fera rare seront plus sombres. Ceci permet de créer un effet sympa d'ombre sur les côtés de votre boîte.

La lumière est créée de la même manière qu'une couleur. Si le premier nombre est 1.0f, et que les deux suivants sont égaux à 0.0f, nous obtiendrons une lumière rouge. Si le troisième nombre est 1.0f, et que les deux premiers sont égaux à 0.0f, nous allons avoir une lumière bleue. Le dernier nombre correspond à la valeur alpha de la couleur. Pour l'instant, nous le laisserons à 1.0f.

Donc, dans la ligne ci-dessous, nous allons mettre une couleur blanche pour l'éclairage ambiant avec une intensité moyenne (0.5f). Comme tous les nombres sont égaux à 0.5f, nous obtiendrons une lumière qui est à mi-chemin entre le noir complet, et l'éclairage total. Rouge, bleu et vert mélangés avec la même valeur permettent de créer une ombre depuis le noir (0.0f) jusqu'au blanc (1.0f). Sans éclairage ambiant, les zones sans lumière diffuse, apparaîtront très foncés.

Valeurs de l'éclairage ambiant

// Valeurs de l'eclairage ambient (nouveau)
GLfloat LightAmbient[]= { 0.5f, 0.5f, 0.5f, 1.0f };

Dans la ligne de code suivante, nous allons enregistrer la valeur pour une superbe lumière. Toutes les valeurs sont fixées à 1.0f. Ce qui veut dire que la lumière éclairera de la manière la plus forte possible. Une lumière diffuse comme celle-ci éclairera le devant de votre boîte joliment.

Valeurs pour la lumière diffuse

// Valeurs pour la lumiere diffuse (nouveau)
GLfloat LightDiffuse[]= { 1.0f, 1.0f, 1.0f, 1.0f };

Enfin, nous enregistrons la position de notre lumière. Les trois premiers nombres sont les mêmes que ceux utilisés dans la fonction glTranslate. Le premier nombre correspond aux mouvements de gauche à droite sur le plan des X, le second nombre pour bouger de haut en bas sur le plan des Y, et le troisième nombre est pour bouger d'avant en arrière sur le plan des Z. Comme nous voulons que notre lumière éclaire directement le devant de notre boîte, nous n'avons pas à bouger ni à gauche, ni à droite, donc la première valeur est égale à 0.0f (pas de mouvement sur les X), nous n'avons pas à bouger ni en haut, ni en bas, donc la seconde valeur est égale à 0.0f. Pour la troisième valeur, nous voulons être surs que la lumière sera toujours devant la boîte. Donc nous allons positionner la lumière hors de l'écran, derrière l'observateur. Si on imagine que la vitre de votre écran d'ordinateur correspond à 0.0f sur le plan des Z. On met la lumière à 2.0f sur le plan des Z. Si nous pouvions voir la lumière, elle serait en train de flotter juste devant la vitre de votre écran. En faisant ceci, la seule façon pour que la lumière soit derrière la boîte c'est que la boîte soit aussi devant votre écran. Et bien sûr, si votre boîte n'est pas derrière la vitre de l'écran, vous ne la verriez pas, et donc la position de la lumière aurait pu être quelconque. Non ?

Il n'y a pas de façon simple d'expliquer le troisième paramètre. Vous devez juste savoir que -2.0f est plus proche de vous que -5.0f. Et que -100.0f sera très loin dans l'écran. Une fois que vous êtes arrivé à 0.0f, l'image sera très grande, elle remplira entièrement l'écran. Une fois que vous partez dans les valeurs positives, l'image n'apparaîtra plus à l'écran, en effet elle sera derrière l'écran. C'est que ce que je veux dire par en dehors de l'écran. L'objet est toujours là, mais simplement vous ne pouvez plus le voir.

Laissez le dernier nombre à 1.0f. Cela indique à OpenGL que la lumière sera de type ponctuel (spot). Vous en saurez plus dans un prochain tutoriel.

Position de la lumière

GLfloat LightPosition[]= { 0.0f, 0.0f, 2.0f, 1.0f };  // Position de la lumière ( nouveau )

La variable filter ci-dessous sert à mémoriser l'indice de la texture que l'on souhaite afficher. La première texture (texture 0) est faite en utilisant GL_NEAREST (sans adoucissement). La seconde texture (texture 1) utilise un filtre de type GL_LINEAR qui lisse un peu l'image de sortie. La troisième texture (texture 2) utilise des textures « mipmap », qui permet de créer de jolie texture. La variable filter sera égale à 0, 1 ou 2 selon la texture que l'on veut utiliser. Nous commencerons avec la première texture.

GLuint texture[3] crée un tableau pour stocker nos trois différentes textures. Les textures seront mises dans texture[0], texture[1] et texture[2].

Stockage des textures

GLuint  filter;         // Quel filtre on utilise
GLuint  texture[3];     // Tableau des 3 textures
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);  // Declaration de WndProc

Maintenant nous allons charger une image, et créer trois textures à partir d'elle. Ce tutoriel utilise la bibliothèque glaux pour charger l'image, donc assurez vous bien d'avoir cette bibliothèque d'incluse avant d'essayer de compiler le code. Je sais que Delphi et Visual C++ possèdent cette librairie. Je ne suis pas sûr pour les autres langages. Je vais juste vous expliquer ce que font les nouvelles lignes de code, si vous voyez une ligne non commentée, et que vous ne voyez pas ce qu'elle fait, regardez dans le tutoriel #06. Il explique le chargement et la construction de texture depuis une image Bitmap d'une manière plus détaillée.

II-B - La fonction LoadBMP

Juste après le précédent code, et avant la fonction ReSizeGLScene(), nous allons rajouter la portion de code suivante. C'est le même code que celui utilisé dans la leçon 6 pour charger des fichiers Bitmap. Rien n'a changé. Si vous n'êtes pas sûr de ce que font ces lignes, relisez le tutoriel 6. Il explique ce code en détail.

Chargement d'une image

AUX_RGBImageRec *LoadBMP(char *Filename)  // Charger une image Bitmap
{
FILE *File=NULL;                       // Descripteur de fichier

// On s'assure qu'un nom de fichier a bien ete passe
if (!Filename)
{
return NULL;            // Sinon on retourne NULL
}
File=fopen(Filename,"r");  // On verifie que le fichier existe
if (File)                  // Il existe ?
{
fclose(File);           // On ferme le descripteur
return auxDIBImageLoad(Filename); // On charge l'image, et on retourne un pointeur
}
return NULL;               // Si le chargement a echoue, on retourne NULL
}

II-C - La fonction LoadGLTextures

Ici c'est la portion de code qui charge l'image (en appelant la fonction précédente) et qui la convertit en trois textures. La variable Status est utilisée pour savoir à tout moment si la texture s'est correctement chargée et créée.

Convertir les images en textures

int LoadGLTextures()           // Charge les images et les convertit en textures
{
int Status=FALSE;          // Indicateur pour le bon déroulement
AUX_RGBImageRec *TextureImage[1];         // Pour stocker notre texture
memset(TextureImage,0,sizeof(void *)*1);  // On initialise le pointeur a NULL

Maintenant on charge l'image et on la convertit en texture. TextureImage[0] = LoadBMP(« Data/Crate.bmp ») va appeler notre fonction LoadBMP(). Le fichier nommé Crate.bmp dans le répertoire Data va être chargé. Si tout va bien, l'image va être stockée dans TextureImage[0], et la variable Status sera positionnée à TRUE, et on commencera à construire notre texture.

L'image Crate.bmp

Lancement de la fonction précédente

// Charge l'image, verifie qu'il n'y a pas d'erreur,
// si l'image n'est pas trouvee, on quitte
if (TextureImage[0]=LoadBMP("Data/Crate.bmp"))
{
Status=TRUE;    // On positionne la variable Status à TRUE

Maintenant que nous avons chargé le contenu de l'image dans TextureImage[0], nous allons utiliser ces données pour construire nos trois textures. La ligne suivante indique à OpenGL que nous voulons créer trois textures, qui seront stockées respectivement dans texture[0], texture[1] et texture[2].

Création des trois textures

glGenTextures(3, &texture[0]);  // Creation des trois textures.

Dans le tutoriel six, nous avons utilisé des textures filtrées linéairement. Cela requiert une grosse puissance de calcul, mais le résultat est vraiment joli. Le premier type de texture que l'on va créer dans ce tutoriel va utiliser GL_NEAREST. Typiquement ce type de texture ne dispose d'aucun filtre. Il ne consomme qu'un tout petit peu de ressource, et ne rend pas réellement bien. Si vous avez déjà joué à un jeu où les textures sont plutôt pixellisées, c'est probablement parce qu'il utilise ce type de texture. Le seul bénéfice à utiliser ce type de texture dans un projet réside dans le fait qu'il fonctionnera relativement bien sur des petites configurations.

Vous noterez que nous utilisons GL_NEAREST pour le MIN et le MAG. Vous pouvez additionner GL_NEAREST avec GL_LINEAR, et la texture sera un peu mieux, mais nous sommes intéressés par la vitesse d'exécution, donc nous allons utiliser une basse qualité pour les deux. Le MIN_FILTER est le filtre utilisé quand une image est dessinée d'une façon réduite par rapport à sa taille d'origine. Le MAG_FILTER est le filtre utilisé quand une image est dessinée d'une façon agrandie par rapport à sa taille d'origine.

Création de filtres de texture de proximité

// Creation des filtres de texture de proximite
glBindTexture(GL_TEXTURE_2D, texture[0]);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST); // ( Nouveau )
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); // ( Nouveau )
glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0,
GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);

La texture suivante que nous allons construire est du même type que la texture que nous avons utilisée dans le tutoriel6. Filtrée linéairement. La seule chose qui change est que nous la stockons dans texture[1] au lieu de texture[0] puisque c'est devenu notre seconde texture. Si nous la mettions dans texture[0] comme avant, cela écraserait la texture GL_NEAREST (la première texture).

Création de la texture filtrée linéairement

// Creer la texture filtree lineairement
glBindTexture(GL_TEXTURE_2D, texture[1]);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0,
GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);

Maintenant nous allons voir une nouvelle manière de faire une texture. Le Mipmapping ! Vous avez dû remarquer que quand vous affichez une image très petite sur l'écran, beaucoup de détails disparaissent. Les modèles qui sont utilisés pour afficher des belles images commencent à montrer des faiblesses. Quand vous indiquez à OpenGL de construire une texture mipmappée, OpenGL essaye de construire des textures de différentes tailles de haute qualité. Quand vous dessinez une texture mipmappée à l'écran, OpenGL va sélectionner la meilleure texture dans celles qu'il a construites (texture avec le plus de détails) et la dessiner à l'écran, au lieu de redimensionner l'image originale (ce qui entraînerait la perte de détails).

J'ai dit dans la leçon six qu'il y avait un moyen de contourner la limite des 64, 128, 256, etc. que OpenGL met aux hauteur et largeur des textures. gluBuild2DMipmaps est ce moyen. D'après ce que j'ai compris, vous pouvez utiliser les images que vous voulez (n'importe quelle hauteur ou largeur) quand vous construisez des textures mipmappées. OpenGL va automatiquement les redimensionner avec une hauteur et largeur correctes.

Comme c'est la texture numéro trois, nous allons la stocker dans texture[2]. Donc maintenant nous avons texture[0] qui n'a aucun filtrage, texture[1] qui utilise le filtrage linéaire, et texture[2] qui utilise les textures mipmappées. Nous avons donc fini de construire les textures de ce tutoriel.

Création de la texture mipmappée

// Création de la texture mipmappée
glBindTexture(GL_TEXTURE_2D, texture[2]);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

// Nouveau : Utiliser GL_LINEAR_MIPMAP_NEAREST
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);

La ligne suivante permet de construire la texture mipmappée. Nous créons ainsi une texture 2D qui utilise trois couleurs (rouge, verte, bleue). TextureImage[0]->sizeX est la largeur de l'image, TextureImage[0]->sizeY est la hauteur de l'image, GL_RGB signifie que nous utilisons les couleurs rouge, vert et bleu dans cet ordre. GL_UNSIGNED_BYTE signifie que les données qui composent la texture sont à base d'octets, et TextureImage[0]->data pointe sur les données de l'image que nous avons construite à partir de la texture.

Création de la texture mipmappée

gluBuild2DMipmaps(GL_TEXTURE_2D, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY,
GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data); // ( Nouveau )
}

Maintenant nous allons libérer toute la mémoire que nous avons pu utiliser pour stocker l'image. Nous vérifions d'abord qu'il y a des données correspondant à l'image stockée dans TextureImage[0]. Si tel est le cas, nous vérifions qu'il y a des données stockées. S'il y en a, nous les effaçons. Ensuite nous libérons la structure décrivant l'image pour être surs que toute la mémoire est bien libérée.

Libération de l'image

if (TextureImage[0])                // Est-ce que la texture existe ?
{
if (TextureImage[0]->data)       // Si le contenu de l'image est en memoire
{
free(TextureImage[0]->data);  // On libere la memoire du contenu de l'image
}
free(TextureImage[0]);           // On libere la structure decrivant l'image
}

Enfin, on retourne l'état. Si tout c'est bien passé, la variable Status contiendra TRUE. Si quelque chose s'est mal déroulé, Status contiendra FALSE.

Retourne le status

return Status;  // Retourne la variable Status
}

II-D - La fonction InitGL

Maintenant nous allons charger les textures, et initialiser les options d'OpenGL. La première ligne de InitGL charge les textures en utilisant le code précédent. Après que les textures aient été créées, nous allons activer le mapping pour les textures 2D par l'intermédiaire de l'appel à glEnable(GL_TEXTURE2D). Les ombres seront des ombres douces, et la couleur d'arrière-plan est fixée à noire, nous allons activer le test de profondeur et la mise en place de la perspective.

Chargement des textures et initialisation d'OpenGL

int InitGL(GLvoid)          // Tous les reglages pour OpenGL sont ici
{
if (!LoadGLTextures())   // On appelle la fonction pour charger les textures
{
return FALSE;         // Si une texture s'est mal chargee, on retourne FALSE
}
glEnable(GL_TEXTURE_2D);               // On active le texture mapping
glShadeModel(GL_SMOOTH);               // On active les ombres douces
glClearColor(0.0f, 0.0f, 0.0f, 0.5f);  // Fond noir
glClearDepth(1.0f);                    // Initialisation du tampon de profondeur
glEnable(GL_DEPTH_TEST);               // On active le test de profondeur
glDepthFunc(GL_LEQUAL);                // Le type de test de profondeur a effectuer
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);  // Le calcul des perspectives jolies

Maintenant nous allons mettre en place l'éclairage. La ligne suivante va fixer la quantité de lumière ambiante que light1 va fournir. Au début de ce tutoriel, nous avons stocké la quantité de lumière ambiante dans LightAmbient. Les valeurs que nous avons mises dans le tableau vont être utilisées (une intensité moyenne d'éclairage ambiant).

Éclairage ambiant

glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient);  // Installe l'eclairage ambiant

Ensuite on fixe la quantité de lumière diffuse émise par la lumière numéro un. Nous avons stocké cette quantité de lumière diffuse dans LightDiffuse. Les valeurs que nous avons stockées dans ce tableau vont être utilisées ici (lumière blanche à pleine intensité).

Éclairage diffus

glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse);  // Installe l'eclairage diffus

Maintenant nous allons positionner la lumière. Nous avons stocké la position dans LightPosition. Les valeurs que nous avons mises dans ce tableau vont être utilisées ici. Au centre juste devant la face avant, 0.0f en x, 0.0f en y et 2 unités vers la caméra (en direction de l'extérieur de l'écran) sur le plan des Z.

Positionne la lumière

glLightfv(GL_LIGHT1, GL_POSITION,LightPosition);  // Position de la lumiere

Enfin, on active la lumière numéro un. Nous n'avons pas encore activé GL_LIGHTING, donc vous ne verrez pas encore de lumière dans votre scène. La lumière est installée, positionnée et activée, mais tant que GL_LIGHTING n'est pas activé, l'éclairage ne fonctionnera pas.

Activation de GL_LIGHT1

glEnable(GL_LIGHT1);  // Active la lumiere  numero un
return TRUE;          // L'initialisation s'est bien passee
}

II-E - La fonction DrawGLScene

Dans la partie de code suivante, nous allons dessiner la texture mappée sur le cube. Je vais commenter un peu les lignes seulement parce qu'elles sont nouvelles. Si vous n'êtes pas sûr de ce que font les lignes non commentées, reportez-vous au tutoriel 6.

Début de la fonction DrawGLScene

int DrawGLScene(GLvoid)  // C'est ici que tous les dessins sont faits
{
// Efface l'ecran et le tampon de profondeur
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();  // Reinitialise la vue

Les trois lignes suivantes permettent de positionner et tourner le cube avec la texture mappée. glTranslatef(0.0f,0.0f,z) bouge le cube de la valeur de z sur l'axe des Z ( celui qui vient de loin et s'approche de la caméra). glRotatef(xrot, 1.0f, 0.0f, 0.0f) utilise la variable xrot pour tourner le cube autour de l'axe des X. glRotatef(yrot, 0.0f, 1.0f, 0.0f) utilise la variable yrot pour tourner le cube autour l'axe des Y.

Bouge et tourne le cube

// Translation vers l'interieur ou l'exterieur de l'ecran de z unites
glTranslatef(0.0f,0.0f,z);
glRotatef(xrot,1.0f,0.0f,0.0f);  // Tourne autour l'axe des X de xrot
glRotatef(yrot,0.0f,1.0f,0.0f);  // Tourne autour l'axe des Y de yrot

Les lignes suivantes sont similaires à celles utilisées dans le tutoriel six, mais au lieu d'utiliser texture[0], nous allons utiliser texture[filter]. Chaque fois que la touche 'F' sera pressée, la valeur de filter sera incrémentée. Si cette valeur est plus grande que deux, alors la variable filter sera remise à zéro. Quand le programme va s'exécuter, la variable filter sera initialisée à zéro. Cela revient au même que glBindTexture(GL_TEXTURE_2D, texture[0]). Si on appuis sur 'F' une fois, la variable filter sera égale à un, ce qui est la même chose que de mettre glBindTexture(GL_TEXTURE_2D, texture[1]). En utilisant la variable filter, nous pouvons sélectionner chacune des trois textures que nous avons fabriquées.

Selectionne la texture à appliquer

// Selectionne la texture correspondante a filter
glBindTexture(GL_TEXTURE_2D, texture[filter]);
glBegin(GL_QUADS);  // Commence a dessiner les quadrilateres

L'utilisation de glNormal3f est nouvelle dans mes tutoriels. Une normale est une ligne qui ressort d'un polygone en son milieu avec un angle de 90 degrés. Quand vous utilisez des lumières, vous devez spécifier les normales. Les normales indiquent à OpenGL dans quelle direction le polygone fait face…dans quel sens est le haut. Si vous ne spécifiez pas de normales, plein de bizarreries peuvent se produire. Les faces qui ne devaient pas être éclairées vont l'être, le mauvais côté d'un polygone va s'éclairer, etc. La normale doit pointer en dehors du polygone.

Quand vous regardez la face de devant, vous remarquez que la normale est positive sur l'axe des z. Ceci signifie qu'elle pointe vers l'observateur. Exactement la direction que nous voulions. Sur la face du fond, la normale pointe vers le fond de la scène, dans l'écran. Ici encore, c'est ce que nous voulions. Si le cube tourne de 180 degrés sur l'axe des x ou des y, la face de devant va être celle du fond, et le fond devenir la face de devant. Ainsi, n'importe quelle face qui sera devant l'observateur, sa normale pointera également vers lui. Comme la lumière est derrière l'observateur, chaque fois que la normale pointe vers l'observateur, elle va également pointer vers la lumière. Quand cela se produit, la face s'éclaire. Plus une normale pointe vers une lumière, plus le polygone correspondant sera éclairé. Si vous bougez à l'intérieur du cube, vous remarquez que c'est sombre. Toutes les normales pointent vers l'extérieur, donc il n'y a aucune lumière dans la boîte, exactement comme nous le voulions.

Dessin du cube

// Face de devant
glNormal3f( 0.0f, 0.0f, 1.0f);   // La normale pointe vers l'observateur
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);   // Point 1 (Devant)
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);   // Point 2 (Devant)
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);    // Point 3 (Devant)
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);    // Point 4 (Devant)

// Face de derrière
glNormal3f( 0.0f, 0.0f,-1.0f);  // La normale pointe vers le fond de l'écran
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);  // Point 1 (Fond)
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);   // Point 2 (Fond)
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);   // Point 3 (Fond)
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);  // Point 4 (Fond)

// Face du haut
glNormal3f( 0.0f, 1.0f, 0.0f);  // La normale pointe vers le haut
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);   // Point 1 (Haut)
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f);    // Point 2 (Haut)
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f);    // Point 3 (Haut)
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);   // Point 4 (Haut)

// Face d'en bas
glNormal3f( 0.0f,-1.0f, 0.0f);  // La normale pointe vers le bas
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);  // Point 1 (Bas)
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);  // Point 2 (Bas)
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);   // Point 3 (Bas)
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);   // Point 4 (Bas)

// Face de droite
glNormal3f( 1.0f, 0.0f, 0.0f);  // La normale pointe vers la droite
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);  // Point 1 (Droite)
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);   // Point 2 (Droite)
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);    // Point 3 (Droite)
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);   // Point 4 (Droite)

// Face de gauche
glNormal3f(-1.0f, 0.0f, 0.0f);  // La normale pointe vers la gauche
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);  // Point 1 (Gauche)
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);   // Point 2 (Gauche)
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);    // Point 3 (Gauche)
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);   // Point 4 (Gauche)
glEnd();  // Fin du dessin du cube

Les deux lignes suivantes incrémentent xrot et yrot de la valeur contenue dans xspeed et yspeed. Si la valeur dans xspeed ou yspeed est grande, xrot et yrot vont augmenter très rapidement. Plus xrot (ou yrot) augmentera rapidement, plus le cube tournera vite sur cet axe.

Modifie la prochaine rotation

xrot+=xspeed;  // Ajoute xspeed à xrot
yrot+=yspeed;  // Ajoute yspeed à yrot
return TRUE;   // On retourne TRUE
}

II-F - La fonction WinMain

Maintenant on arrive à la fonction WinMain(). Nous allons rajouter du code pour activer ou non l'éclairage, tourner le cube, changer le filtre, et bouger le cube d'avant en arrière de l'écran. Juste en bas de WinMain(), vous devez trouver l'instruction SwapBuffers(hDC). Juste après cette ligne, on rajoute le code suivant.

Ce code vérifie si la touche 'L' a été pressée sur le clavier. La première ligne vérifie que 'L' a été pressée. Si 'L' a été pressée, mais que lp n'est pas FALSE (ce qui signifie que 'L' est déjà pressée ou qu'elle est maintenue) rien ne va se passer.

Gestion du clavier

SwapBuffers(hDC);      // Echange les buffers (Double Buffering)
if (keys['L'] && !lp)  // 'L' est pressee et c'est la premiere fois qu'on vient ici ?
{

Si lp est FALSE, cela signifie que 'L' n'a pas encore été pressée, ou qu'elle a été relâchée, lp sera égale à TRUE. Cela force l'utilisateur à relâcher la touche 'L' pour pouvoir réexécuter cette portion de code. Si on ne vérifiait pas que la touche vient d'être pressée, l'éclairage s'activerait et se désactiverait sans arrêt, car le programme penserait vous êtes en train d'appuyer sur la touche 'L' à chaque fois qu'il arrive à ce bout de code.

Une fois que lp est mise à TRUE, le programme considère que 'L' est appuyée, et donc on active ou non l'éclairage. La variable light peut avoir uniquement les valeurs TRUE ou FALSE. Donc si on écrit light = !light, cela signifie que light sera égale à NON light. Ce qui donne en français, si light est égale à TRUE, la nouvelle valeur de light sera égale à NON TRUE (donc à FALSE), et si light est égale à FALSE, la nouvelle valeur de light sera égale à NON FALSE (donc à TRUE). Donc si light était égale à TRUE, light sera égale à FALSE, et si elle était égale à FALSE, elle devient TRUE.

Changement du mode d'éclairage

lp=TRUE;       // lp se positionne à TRUE
light=!light;  // On met light à TRUE ou FALSE

Maintenant on va vérifier l'état de light. La première ligne traduite en français signifie : si light est égale à FALSE. Si on fait de même, les lignes suivantes donnent : si light égale à FALSE, on désactive l'éclairage. La commande 'else' est traduite en : si light n'est pas égale à FALSE. Donc si light n'est pas égale à FALSE (donc light est égale à TRUE), alors on active l'éclairage.

Activation ou non de la lumière

if (!light)                 // Si Light est egale a FALSE
{
glDisable(GL_LIGHTING);  // On desactive l'eclairage
}
else                        // Sinon
{
glEnable(GL_LIGHTING);   // On active l'eclairage
}
}

Les lignes suivantes vérifient si la touche 'L' a été relâchée. Si c'est le cas, la variable lp prend la valeur FALSE, qui signifie que 'L' n'est plus pressée. Si on ne vérifie pas si la touche est relâchée, on ne pourra pas réactiver la lumière, car le programme pensera que la touche 'L' est toujours pressée, et donc il ne nous laissera pas désactiver la lumière.

'L' est relâchée

if (!keys['L'])  // Est-ce que 'L' est relachee?
{
lp=FALSE;      // Si oui, lp sera egale a FALSE
}

Maintenant nous allons faire quelque chose de similaire avec la touche 'F'. Si la touche est en train d'être pressée, et qu'elle n'est pas restée appuyée ou qu'elle n'a jamais été appuyée avant, cela va affecter à fp la valeur TRUE. Ce qui signifie que la touche est actuellement pressée. Ensuite cela va incrémenter la variable filter. Si filter contient une valeur supérieure à 2 (ce qui correspondrait à texture[3], elle n'existe pas), on réinitialise la variable en la remettant à zéro.

Changement de la texture

if (keys['F'] && !fp)  // Est-ce que 'F' est pressee?
{
fp=TRUE;              // On affecte TRUE à fp
filter+=1;            // On incremente la variable fp de un
if (filter>2)         // Est-ce que la valeur de filter est plus grande que 2 ?
{
filter=0;         // Si oui, on affecte 0 a filter
}
}
if (!keys['F'])        // Est-ce que 'F' a ete relachee ?
{
fp=FALSE;            // Si oui, on affecte FALSE a fp
}

Les quatre lignes suivantes vérifient si la touche 'Page Up' est pressée. Si c'est le cas, on décrémente la variable z. Si la valeur de z diminue, le cube va bouger vers l'intérieur, car on utilise glTranslatef(0.0f, 0.0f, z) dans notre fonction DrawGLScene.

'Page Up' éloigne le cube

if (keys[VK_PRIOR])  // Est-ce que 'Page Up' est pressee?
{
z-=0.02f;          // Si oui, on bouge vers l'interieur de l'ecran
}

Ces quatre lignes vérifient si la touche 'Page Down' est pressée. Si c'est le cas, on incrémente la variable z ce qui fera bouger le cube vers l'observateur, car la commande glTranslatef(0.0f, 0.0f, z) est utilisée dans la fonction DrawGLScene.

'Page Down' rapproche le cube

if (keys[VK_NEXT])   // Est-ce que 'Page Down' est pressee ?
{
z+=0.02f;          // Si oui, on bouge vers l'observateur
}

Maintenant nous allons vérifier l'état des touches directionnelles. En appuyant sur les touches 'gauche' ou 'droite', xspeed va augmenter ou diminuer. En appuyant sur 'haut' ou 'bas', yspeed va augmenter ou diminuer. Rappelez-vous qu'un peu plus haut dans ce tutoriel, je vous ai dit que si la valeur contenue dans xspeed ou yspeed était grande, le cube tournerait vite. Plus longtemps vous resterez appuyé sur une touche de direction, plus le cube tournera vite dans cette direction.

Accélération ou non de la vitesse de rotation

if (keys[VK_UP])           // Est-ce que 'Fleche Haut' est pressee?
{
xspeed-=0.01f;            // Si oui, on decremente xspeed
}
if (keys[VK_DOWN])         // Est-ce que 'Fleche Bas' est pressee?
{
xspeed+=0.01f;            // Si oui, on incremente xspeed
}
if (keys[VK_RIGHT])        // Est-ce que 'Fleche Droite' est pressee?
{
yspeed+=0.01f;            // Si oui, on incremente yspeed
}
if (keys[VK_LEFT])         // Est-ce que 'Fleche Gauche' est pressee?
{
yspeed-=0.01f;            // Si oui, on decremente yspeed
}

Comme dans les tutoriels précédents, assurez-vous que le titre de la fenêtre est correct.

Changement du titre

if (keys[VK_F1])              // Est-ce que F1 est pressee?
{
keys[VK_F1]=FALSE;         // Si oui, on met FALSE pour cette touche
KillGLWindow();            // On detruit notre fenetre OpenGL
fullscreen=!fullscreen;    // On passe du mode plein ecran a fenetre (ou inversement)

// Recreation de notre fenetre OpenGL
if (!CreateGLWindow("Tutoriel de Nehe sur les Textures, les lumières et le clavier",
640, 480, 16, fullscreen))
{
return 0;               // On quitte si la fenetre ne s'est pas creee
}
}
}
}
}

// Fermeture
KillGLWindow();                  // On detruit notre fenetre OpenGL
return (msg.wParam);             // On sort du programme
}

II-G - Conclusion

À la fin de ce tutoriel, vous devez être capable de créer et de manipuler des textures de hautes qualités, réalistes ou mappées sur des objets à base de cube. Vous devez avoir compris les avantages des trois filtres utilisés dans ce tutoriel. En appuyant sur une touche spécifique du clavier, vous devez être capable d'interagir avec des objets à l'écran, et finalement, vous devez maintenant savoir comment appliquer un simple éclairage sur votre scène pour la rendre un peu plus réelle.

Jeff Molofee ( NeHe )

III - Téléchargements

Compte tenu du nombre de versions de codes sources pour les tutoriels nehe, nous les laissons en anglais. En principe, si vous avez compris le code présenté dans ce tutoriel (et les tutoriels antérieurs), vous n'aurez pas de mal à le comprendre :

- Borland C++ Builder 6(Conversion par Christian Kindahl)

- C#(Conversion par Brian Holley)

- Code Warrior 5.3(Conversion par Scott Lupton)

- CygWin(Conversion par Stephan Ferraro)

- D(Conversion par Familia Pineda Garcia)

- Delphi(Conversion par Michal Tucek)

- Dev C++(Conversion par Dan)

- Euphoria(Conversion par Evan Marshall)

- Game GLUT(Conversion par Milikas Anastasios)

- GLUT(Conversion par Andy Restad)

- Irix(Conversion par Lakmal Gunasekara)

- Java(Conversion par Jeff Kirby)

- Jedi-SDL(Conversion par Dominique Louis)

- JOGL(Conversion par Kevin J. Duling)

- LCC Win32(Conversion par Robert Wishlaw)

- Linux(Conversion par Richard Campbell)

- Linux GLX(Conversion par Mihael Vrbanec)

- Linux SDL(Conversion par Ti Leggett)

- LWJGL(Conversion par Mark Bernard)

- Mac OS(Conversion par Anthony Parker)

- Mac OS X/Cocoa(Conversion par Bryan Blackburn)

- MASM(Conversion par Nico (Scalp))

- VC++/OpenIL(Conversion par Denton Woods)

- Pelles C(Conversion par Pelle Orinius)

- Power Basic(Conversion par Angus Law)

- Scheme(Conversion par Brendan Burns)

- Solaris(Conversion par Lakmal Gunasekara)

- Visual Basic(Conversion par Peter de Tagyos)

- Visual Fortran(Conversion par Jean-Philippe Perois)

- Visual Studio

- Visual Studio NET(Conversion par Grant James)

IV - Remerciements

Merci à fearyourself et à jc_cornic pour leur relecture.

V - Liens

Sommaire

Tutoriel précédent : Textures

Tutoriel prochain : La transparence