Article     Discussion     Modifier     Historique     Forums     Salon IRC

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.

Image:Opengl_reflet_avance_000.jpg

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...

 

Rechercher
Installer l'extension de recherche Plus d'informations

 

Comprendre
Tu me dis, j'oublie. Tu m'enseignes, je me souviens. Tu m'impliques, j'apprends. - Benjamin Franklin

 

Partager
La connaissance est la seule chose qui s'accroit lorsqu'on la partage. - Sacha Boudjema

 

Créer
L'imagination est plus importante que la connaissance. - Albert Einstein

 

 

Le wiki en images Le wiki en images Image du mois: «Snowball: un prototype de jeu développé avec NeL.