OpenGL:Reflet d'eau
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 + l'exécutable
--NewbiZ 24 août 2005 à 23:38 (CEST)