OpenGL:Reflet d'eau avance
Un article de Games Creators Network.
Dans cet article qui fait suite à "Réaliser un reflet dans de l'eau", nous verrons comment utiliser le stencil buffer et l'extension FrameBufferObject d'OpenGl pour réaliser un rendu plus réaliste de l'eau, comme on peut le voir sur l'image ci-dessous.
Nous ne verrons cependant l'implémentation complète du code, mais juste ce qui nous intéresse.
Sommaire |
[modifier] Au commencement...
Nous supposons ici que vous avez chargé les images telles que:
- GLuint idtextwater = id de la texture de l'eau
- RenderTransformation = fonction avec les transformations nécessaires pour la caméra
- RenderScene = fonction de rendu de la scène
- RenderSkyBox = fonction de rendu du skybox (au cas où...)
De plus, vous aurez besoin de la librairie [1], qui nous évitera de devoir trop s'occuper des extensions. Vous devrez l'initialiser dans la fonction d'initialisation à l'aide de la fonction glewInit()
//Headers divers... #include <math.h> //... #include <GL/glew.h> #include <GL/glu.h> //Fonction d'initialisation... bool Init(void){ //Initialisation de SDL, ou GLUT, etc... //Initialisation de GLEW glewInit(); //Initialisation d'OpenGl //(on peut notamment checker les extensions) return true; }
À noter que l'on ne doit pas include gl.h et glext.h, sinon le compilateur générera une erreur (vous pouvez toujours examiner l'header de glew).
Pour les extensions, vous pouvez toujours vous renseigner sur le d'OpenGl, ou vous trouverez leur liste complète avec des explications pour chacune.
[modifier] La technique que l'on va utiliser
Elle est toute simple: vous vous souvenez certainement que dans le précédent article sur les reflets d'eau, on rendait l'eau à l'aide du stencil buffer? Ici, on va faire un peu plus casse-tête; nous allons rendre notre scène inversée (sans oublier le plan de clipping, pour éviter de mauvaises surprises), puis on va récupérer le résultat du rendu dans un texture. Et c'est là que ce tutoriel va devenir intéressant, parce que l'on va utiliser la formidable puissance du stencil buffer: on va plaquer la texture de notre rendu sur tout l'écran, mais en activant le stencil buffer...
[modifier] FrameBuffer, ou comment rendre autrement qu'à l'écran
L'extension FrameBuffer (FBO) permet (comme indiquer dans le titre de la section) de rendre notre scène autrement qu'à l'écran. En fait, comme vous l'aurez certainement déjà compris, le rendu s'effectue dans un buffer, lequel on va copier dans une texture pour le rendu de la réflexion... Pour nous simplifier la vie, on va d'abord écrire une petite structure pour gérer les framebuffers...
struct FrameBuffer_st{ GLuint idframe, //id du frame-buffer idrenderdepth, //id du render-buffer nécessaire pour le tampon de profondeur idtext; //id de la texture dans laquelle rendre notre buffer FrameBuffer_st(GLuint _idtext, size_t width, size_t height){ idtext = _idtext; //Génération des buffers glGenFramebuffersEXT(1, &idframe); glGenRenderbuffersEXT(1, &idrenderdepth); //On s'occupe de paramétrer le FBO glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, idframe); glBindTexture(GL_TEXTURE_2D, idtext); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, idtext, 0); // Depth-Buffering du FBO glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, idrenderdepth); glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24, width, height); glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, idrenderdepth); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); } ~FrameBuffer_st(){ //Suppression des frames... glDeleteFramebuffersEXT(1, &idframe); glDeleteRenderbuffersEXT(1, &idframe); } //Activation/Désactivation du FBO void Bind(){ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, idframe); } void UnBind(){ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); } // Copie du rendu dans la texture void RenderToTexture(){ glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, idtext, 0); } };
[modifier] Rendu de l'eau
La fonction de rendu de l'eau n'a pas vraiment changé, nous ferons juste une animation de la texture pour améliorer l'effet...
void RenderWater(){ static int frame = 90; static bool sens = true; if(frame > 359) sens = false; //changement du sens de l'animation... if(frame < 1) sens = true; glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, watertext.id); //Modification de la matrice de texture... glMatrixMode(GL_TEXTURE); glPushMatrix(); //On sauvegarde au préalable l'ancienne... float transl = sin(frame); float transl2= cos(frame); float intensity = 0.5; glRotated(anglerot, 0.0, 1.0, 0.0); glTranslatef(transl*intensity, transl2*intensity, 0); //Retour à la matrice de visualisation glMatrixMode( GL_MODELVIEW ); //Dessin de l'eau... glMatrixMode(GL_TEXTURE); glPopMatrix(); //restauration de l'ancienne matrice de texture glMatrixMode(GL_MODELVIEW); //augmentation de la frame if(sens) frame++; else frame--; }
Ce sera tout pour le rendu de notre eau...
[modifier] Rendu final de l'eau
Le rendu s'effectue en plusieurs passes; Tout d'abord il s'agira de rendre notre scène à l'envers dans le FBO, en activant le clipping. Ensuite, nous devrons nous occuper de la texture du rendu, pour créer l'effet du FBO, et au final, nous pourrons (enfin!) rendre l'eau et la scène...
[modifier] Passe 1: Rendu de la scène inversée
C'est ici que commence donc notre fonction...
void RenderWaterFinal(float y){ //paramètres: y = ordonnée du plan d'eau GLdouble equation[4] = {0.0, -1.0, 0.0, 0.0}; //équation du clipping frmbuffer->Bind(); glClearColor(0.2, 0.4, 0.7, 1.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glEnable(GL_CLIP_PLANE0); glMatrixMode(GL_MODELVIEW); glPushMatrix(); // On sauvegarde la matrice courante glLoadIdentity(); glViewport(0, 0, 1024, 1024); //les deux derniers paramètres sont les dimensions de la textures RenderTransformation(); glTranslatef(0.0, y, 0.0); glClipPlane(GL_CLIP_PLANE0, equation); glScalef( 1.0, -1.0, 1.0 ); // On inverse les Y glTranslatef(0.0, -y, 0.0); //Rendu de la scène (ne pas rendre le skybox, sinon... // caca -à vous de tester ;) RenderScene(); glDisable(GL_CLIP_PLANE0); glMatrixMode(GL_MODELVIEW); glPopMatrix(); // On recharge la matrice frmbuffer->RenderToTexture(); //rendu dans la texture frmbuffer->UnBind(); //FBO par défaut (c'est-à-dire l'écran) //...
Voilà voilou pour la passe 1!
[modifier] Passe 2: Rendu de la scène
Nous nous occupons maintenant d'un rendu "normal" de la scène:
//(donc on est à la suite de la fonction) //nettoyage de la scène... glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glClearColor(0.0, 0.0, 0.0, 0.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); RenderSkyBox(); //rendu du skybox RenderScene(); //rendu de la scène //fin de la passe 2!
[modifier] Passe 3: Rendu (tant attendu) de l'eau
Cette passe est la plus importante pour le rendu de l'eau, et celle que l'on attend le plus...
On s'occupe tout d'abord de la texture:
//On binde la texture, puis on utilise un shader pour l'effet de diffraction //[A faire]
//Activation du stencil buffer //On remarque que ce n'est pas la même chose que // lors du précédent tutoriel, mais le principe // reste le même... glEnable(GL_STENCIL_TEST); glStencilFunc(GL_ALWAYS, 0x0, 0xff); glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); //Rendu de l'eau... RenderWater(); // On s'occupe du masque du stencil (quand différent de 0) glStencilFunc(GL_NOTEQUAL, 0, ~0); //Réinitialisation de la matrice glMatrixMode(GL_MODELVIEW); glLoadIdentity(); //Réinitialisation de la matrice de projection... glMatrixMode(GL_PROJECTION); glPushMatrix(); //... que l'on sauvegarde auparavant glLoadIdentity(); //On va donc binder la texture de rendu du FBO ici //On peut également faire du multi-texturing pour ajouter un reflet // du skybox glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, idwatertextenv); glDisable(GL_DEPTH_TEST); //désactivation du tampon de profondeur //Et on dessine finalement notre jolie petite eau! glBegin(GL_QUADS); glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0, -1.0, -0.5); glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0, 1.0, -0.5); glTexCoord2f(1.0f, 1.0f); glVertex3f(1.0, 1.0, -0.5); glTexCoord2f(1.0f, 0.0f); glVertex3f(1.0f, -1.0f, -0.5); glEnd(); glDisable(GL_STENCIL_TEST); //désactivation du stencil buffer glEnable(GL_DEPTH_TEST); //réactivation du depth buffer glMatrixMode(GL_PROJECTION); //restauration de la matrice de projection glPopMatrix(); } //fin de la fonction
Et voilà! Finalement, ce n'était pas si dur, non? ;-)
[modifier] Conclusion
Il vous ne reste plus qu'à implémenter l'ensemble dans un moteur 3D fait par vous-même!
Pour ce tutoriel, je me suis (largement) inspiré du site suivant. Faites-y un petit tour si vous êtes intéressé par un rendu plus complexe encore...


