OpenGL:Reflet d'eau

Effet d'eau avec OpenGL
Dans cet article, nous allons traiter d'un effet assez classique en OpenGL : créer une eau mouvante, qui reflete la scène courante.

Pour ce faire, nous utiliserons l'extension GLU, l'API Win32, ainsi que le compilateur Dev-C++. Néanmoins, cet exemple pourra être utilisé avec n'importe quel autre gestionnaire de fenêtre, ou compilateur, à vous de faire les modifications nécessaires.

Le template de base de notre application est extrait du site NeHe. Pour plus d'explications sur ce template d'application OpenGL avec Win32, consultez [NeHe.gamedev.net NeHe.gamedev.net]

Nous utiliserons aussi la librarie de chargement de fichier Targa de Nate Miller (vandals1@home.com) pour nos textures.

Sommaire

[masquer]

[modifier] Avant toute chose

Commençons directement par étudier les fichiers d'en-tête de notre projet. Nous incluons tga.h pour nos textures

/*****************************************************
*               FICHIERS EN-TETE                     *
******************************************************/
#include <windows.h>		// Windows
#include <GL/gl.h>			// OpenGL
#include <GL/glu.h>			// OpenGL Utility
#include "tga.h"

Nous déclarons ensuite 2 variables de type float, la première nous servira à faire le décalage de la texture de l'eau, afin de lui donner un effet "mouvant", la deuxième nous servira à faire tourner notre cube.

/*****************************************************
*             DECLARATIONS DES VARIABLES             *
******************************************************/
float UVdecal = 0;          // Décalage des coordonnées de l'eau
float Rot = 0;              // Rotation du cube

[modifier] Initialisation d'OpenGL

Nous arrivons à la fonction d'initialisation d'OpenGL. La seule différence avec le template est que nous chargeons en mémoire 2 textures, avec les identifiants respectifs 1 (pour l'eau) et 2 (pour le cube).

int InitGL(GLvoid) {
	glShadeModel(GL_SMOOTH);
	glClearColor(0.0f, 0.0f, 0.0f, 0.5f);
	glClearDepth(1.0f);
	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LEQUAL);
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
 
	loadTGA("water.tga",1);   // Chargement de la texture d'eau
	loadTGA("GCN.tga",2);     // Chargement de la texture du cube
 
	return TRUE;
}

[modifier] Rendu de l'eau

Nous allons maintenant définir une simple fonction de dessin de notre eau.

void RenderWater( void ) {
     UVdecal+=0.0001;                    // On incrémente le décalage de texture pour l'eau
 
     glEnable( GL_BLEND );               // On active la transparence
     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);  // On définit une fonction de
                                                         // transparence adéquat
 
     glEnable(GL_TEXTURE_2D);
     glBindTexture(GL_TEXTURE_2D, 1);    // On sélectionne la texture de l'eau
     glBegin( GL_QUADS );
              glColor4f(1.,1.,1.,.8);    // On ajoute une petite transparence (0.8)
 
              glTexCoord2d(0-UVdecal,0); // Enfin on affiche notre eau
              glVertex3f(0,0,0);         // qui est en fait un simple
                                         // carré texturé, et donc les
              glTexCoord2d(1-UVdecal,0); // coordonnées de texture sont
              glVertex3f(100,0,0);       // modifiées par UVdecal
 
              glTexCoord2d(1-UVdecal,1);
              glVertex3f(100,0,100);
 
              glTexCoord2d(0-UVdecal,1);
              glVertex3f(0,0,100);
     glEnd();
 
     glDisable(GL_TEXTURE_2D);           // On desactive la selection de texture
 
     glDisable( GL_BLEND );              // On desactive la transparence
}

[modifier] Rendu du cube

Maintenant nous allons nous attaquer à la fonction d'affichage du cube. Nous affichons un simple cube texturé, qui tourne sur lui même.

void RenderCube( void ) {
     glPushMatrix();                   // On sauvegarde la matrice courante
 
     glRotatef(Rot,1,1,1);             // On applique notre rotation
     glTranslatef(30,30,30);
     glScalef(10,10,10);               // On grossit légèrement le cube
 
     glEnable(GL_TEXTURE_2D);
     glBindTexture(GL_TEXTURE_2D, 2);  // Sélection de la texture du cube
 
     glBegin( GL_QUADS );
              glColor4f(1.,1.,1.,1.);  // Aucune transparence ni effet de couleur
              //FACE 1
              glTexCoord2d(1,1);
              glVertex3i(1,1,1);
              glTexCoord2d(1,0);
              glVertex3i(1,-1,1);
              glTexCoord2d(0,0);
              glVertex3i(-1,-1,1);
              glTexCoord2d(0,1);
              glVertex3i(-1,1,1);
              //FACE 2
              glTexCoord2d(0,0);
              glVertex3i(1,1,-1);
              glTexCoord2d(1,0);
              glVertex3i(1,-1,-1);
              glTexCoord2d(1,1);
              glVertex3i(-1,-1,-1);
              glTexCoord2d(0,1);
              glVertex3i(-1,1,-1);
              //FACE 3
              glTexCoord2d(0,0);
              glVertex3i(1,1,1);
              glTexCoord2d(1,0);
              glVertex3i(1,-1,1);
              glTexCoord2d(1,1);
              glVertex3i(1,-1,-1);
              glTexCoord2d(0,1);
              glVertex3i(1,1,-1);
              glTexCoord2d(1,1);
              //FACE 4
              glTexCoord2d(0,0);
              glVertex3i(-1,1,1);
              glTexCoord2d(0,1);
              glVertex3i(-1,-1,1);
              glTexCoord2d(1,1);
              glVertex3i(-1,-1,-1);
              glTexCoord2d(1,0);
              glVertex3i(-1,1,-1);
              //FACE 5
              glTexCoord2d(0,0);
              glVertex3i(-1,1,-1);
              glTexCoord2d(1,0);
              glVertex3i(-1,1,1);
              glTexCoord2d(1,1);
              glVertex3i(1,1,1);
              glTexCoord2d(0,1);
              glVertex3i(1,1,-1);
              //FACE 6
              glTexCoord2d(0,0);
              glVertex3i(-1,-1,-1);
              glTexCoord2d(1,0);
              glVertex3i(-1,-1,1);
              glTexCoord2d(1,1);
              glVertex3i(1,-1,1);
              glTexCoord2d(0,1);
              glVertex3i(1,-1,-1);
     glEnd();
     glDisable(GL_TEXTURE_2D);         // On desactive la texture
 
     glPopMatrix();                    // On recharge l'ancienne matrice
 
     Rot += 0.1;                       // On augmente l'angle de rotation
}

Maintenant que nous avons nos fonction d'affichage du cube et de l'eau, nous allons nous attaquer à la fonction de reflet proprement dite. C'est là tout l'intéret de l'article.

[modifier] Rendu du reflet

La technique utilisée est simple. Le but est de redessiner la scène, avec un Y inversé. Mais ceci ne résout pas le problème, le reflet va s'afficher partout ! Nous allons donc utiliser le Stencil Buffer. Le principe est le suivant : Nous effacons le stencil buffer ( en fait on le remplit de "0" ). Ensuite nous dessinons notre eau dans ce tampon en remplacant les "0" par des "1". Nous afficherons ensuite le reflet uniquement aux endroits ou le tampon de stencil est à "1" ! C'est aussi simple que cela.

void RenderReflet( void ) {
       glColorMask(0, 0, 0, 0);       
       glEnable(GL_STENCIL_TEST);                // Activation du Stencil Buffer
       glStencilFunc(GL_ALWAYS, 1, 1);           // Fonction pour remplacer le contenu du buffer
       glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
       glDisable(GL_DEPTH_TEST);                 // on desactive le test de profondeur (inutile)
 
       RenderWater(  );                          // On dessine notre eau
 
       glEnable(GL_DEPTH_TEST);                  // On peut réactiver le test de profondeur
       glColorMask(1, 1, 1, 1); 
       glStencilFunc(GL_EQUAL, 1, 1);            // On teste si le stencil est égal à 1
       glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 
 
       glPushMatrix();                           // On sauvegarde la matrice courante
          glScalef( 1.0, -1.0, 1.0 );            // On inverse les Y
          RenderCube(  );                        // On dessine notre cube
       glPopMatrix();                            // On recharge la matrice
 
       glDisable(GL_STENCIL_TEST);               // On desactive le tampon de Stencil
 
       RenderWater(  );                          // On peut dessiner notre eau
}

[modifier] Afficher le résultat

Le reflet est fait. Il ne nous reste plus qu'à afficher le résultat, et ne pas oublier d'effacer le tampon de stencil !

int DrawGLScene(GLvoid) {
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT '''| GL_STENCIL_BUFFER_BIT''');
	glLoadIdentity();
	gluLookAt(150,75,150,0,0,0,0,1,0);
    RenderCube(  );
    RenderReflet(  );
	return TRUE;
}

[modifier] Un dernier détail ... qui a son importance

Il nous reste un détail à régler : allouer un espace mémoire pour le tampon de stencil, ceci n'est pas fait par défaut dans le template de NeHe. Nous allons donc faire les modifications suivantes :

static	PIXELFORMATDESCRIPTOR pfd=
	{
		sizeof(PIXELFORMATDESCRIPTOR),
		1,
		PFD_DRAW_TO_WINDOW |
		PFD_SUPPORT_OPENGL |
		PFD_DOUBLEBUFFER,
		PFD_TYPE_RGBA,
		bits,
		0, 0, 0, 0, 0, 0,
		0,
		0,
		0,
		0, 0, 0, 0,
		16,
		1,
		0,
		PFD_MAIN_PLANE,
		0,
		0, 0, 0
	};

Voilà, vous avez toutes les clefs en main pour faire un bel effet de reflet dans votre eau. La prochaine étape sera l'ajout d'un plan de clipping, pour éviter que les objets qui entrent dans l'eau, voient leur reflet sortir de l'eau !

[modifier] Téléchargements

Télécharger les sources

Télécharger l'exécutable

Télécharger les sources + l'exécutable


--NewbiZ 24 août 2005 à 23:38 (CEST)