Ombres et reflets

       La principale application du stencil buffer, ce sont les ombres et les reflets. Ce sont 2 trucs assez sympas, pas trop durs à réaliser si on accepte certaines limitations.

1.   Reflets

1.1.    Principe

       Les reflets sur un plan sont très simples : ce n’est qu’un double d’un objet, recouvert d’une surface légèrement transparente pour donner l’illusion qu’il s’agit d’un reflet. En plus, ce qui simplifie bien les choses, il existe la fonction :

void glScalef( GLfloat x, GLfloat y, GLfloat z )

       qui multiplie la matrice courante par une matrice multipliant toutes les coordonnées entrées par les valeurs spécifiées, c’est-à-dire une matrice diagonale (x, y, z, 1). Dans notre cas, pour effectuer une symétrie par rapport au plan y = 0, il suffit d’entrer glScalef(1.0f, -1.0f, 1.0f).
       Seul problème : le reflet peut sortir de la surface miroir ! C’est là que le stencil buffer intervient. La surface réfléchissante est d’abord « virtuellement » dessinée, en fait elle est dessinée normalement sauf que pour empêcher l’affichage on utilise :

void glColorMask( GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha )

       avec tous les paramètres à GL_FALSE. Cela permet de changer la valeur du stencil là où la surface va être dessinée.

       Les étapes du reflets sont finalement :

·  Limitation du reflet à la surface réfléchissante : 

  glDisable(GL_DEPTH_TEST);        
  glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);          
  glEnable(GL_STENCIL_TEST);     
  glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);     
  glStencilFunc(GL_ALWAYS, 1, 0xffffffff);  
  Dessin de la surface réfléchissante
  glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);      
  glEnable(GL_DEPTH_TEST);

· Dessin du reflet :         

  glStencilFunc(GL_EQUAL, 1, 0xffffffff); /* draw if ==1 */  
  glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);    
  glPushMatrix();         
  glScalef(1.0, -1.0, 1.0);        
  respositionnement des lumières     
  dessin de l’objet reflété      
  glPopMatrix();          
  respositionnement des lumières     
  glDisable(GL_STENCIL_TEST);

·  Dessin de la surface réfléchissante :     

  glEnable(GL_BLEND);        
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);      
  Dessin de la surface réfléchissante
 
glDisable(GL_BLEND);
·        Dessin de l’objet

1.2.  Exemple

            Voici le reflet d'un cube sur le sol :

void CALLBACK Main_part(void)
{
            GLfloat Rouge[] = {0.8f, 0.0f, 0.0f, 1.0f};
            GLfloat Vert[] = {0.0f, 0.8f, 0.0f, 1.0f};
            GLfloat Bleu[] = {0.0f, 0.0f, 0.8f, 1.0f};

            glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
            glDisable(GL_DEPTH_TEST);
                        //on ne va pas dessiner dans le buffer-écran, mais dans les autres buffers
            glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
            glEnable(GL_STENCIL_TEST);
            glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
            glStencilFunc(GL_ALWAYS, 1, 0xffffffff);
                        //Dessin de la surface réfléchissante dans le stencil-buffer
            drawFloor();
                        //on reactive le dessin à l'ecran
            glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
            glEnable(GL_DEPTH_TEST);
                        //cela va dessiner uniquement dans les endroits du stencil-buffer qui sont à 1
                        //c.a.d. dans le miroir
            glStencilFunc(GL_EQUAL, 1, 0xffffffff);
            glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
            glCullFace(GL_BACK);
            glPushMatrix();
                        //on fait la symétrie par rapport à l'axe des Z
            glScalef(1.0, 1.0, -1.0);
                        //les lumières aussi subissent la symétrie
            SetLight();
                        //on dessine la scène qui va être reflétée
            glTranslatef(0.0f, 0.0f, 5.0f);
            glRotatef((80.0f), -0.5f, 0.3f, -0.8f);
            glRotatef((10.0f), 0.5f, -0.3f, 0.8f);
            SetLight();
            glCallList(cube);
            glPopMatrix();
                        //fin de la symétrie
                        //on remet les lumières et tout le reste
            SetLight();
            glCullFace(GL_FRONT);
            glDisable(GL_STENCIL_TEST);
                        //on dessine le sol transparent
            glEnable(GL_BLEND);
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
            glDisable(GL_LIGHTING);
            drawFloor();
            glEnable(GL_LIGHTING);
            glDisable(GL_BLEND);
                        //puis le reste de la scène
            glPushMatrix();
            glTranslatef(0.0f, 0.0f, 5.0f);
            glRotatef((80.0f), -0.5f, 0.3f, -0.8f);
            glRotatef((10.0f), 0.5f, -0.3f, 0.8f);
            glCallList(cube);
            glPopMatrix();
            auxSwapBuffers();
}

Sources et éxécutable

1.3 Tout n'est pas parfait !!!

            Ben, oui, il faut le reconnaître, cet algorithme est pas mal, mais il ne gère pas un truc. Quand un objet se trouve juste derrière le sol sur lequel la scène se reflète, il apparaît dans le miroir alors qu'il devrait être caché. ceci est dû à la symétrie : lors de cette dernière, l'objet passe devant le miroir. vient alors le "Stencil-Test". L'image est alors découpée autour du miroir. Mais commel l'objet EST devant le miroir, il apparait !!!!

            "Mais, il suffit de tenir compte du "Depth_Test" lors du "Stencil-test" ", me direz-vous. Ben, j'ai essayé et ca n'a pas marché. Ou plutot si, ca marchait, mais j'avais alors d'autre problèmes : objets cachés qui apparaissaient dans le miroir, miroir ne faisant apparaitre QUE l'objet en question .Bref, je n'ai pas réussi. Vous avez une idée??? Mailez-moi !!!

2.   Ombres

2.1.   Principe

            Le stencil buffer joue toujours son rôle de délimitation, pour l’ombre cette fois. Reste à dessiner l’ombre elle-même, or qu’est-ce qu’une ombre ? C’est la projection de tout un objet sur un plan suivant la direction de la lumière. Cela fait (devrait faire ?) immédiatement  penser à « matrice de projection ». Effectivement il suffit de multiplier la matrice ModelView par une matrice de projection pour avoir le résultat voulu.

            Quelques détails tout de même :

-         l’ombre n’est pas le dessin de l’objet lui-même mais une surface noire (à moitié transparente). Bien que dans la réalité, ce n’est pas exactement cela : c’est tout le reste de la scène qui est plus éclairée. Cependant il est bien plus rapide d’assombrir là où est l’ombre que d’éclaircir tout le reste. S’il n’y a aucune lumière ambiante, l’ombre n’est pas transparente.
-         avec cette méthode, il faut faire attention à ne pas assombrir deux fois le même point : ce n’est pas parce que deux surfaces cachent la lumière que l’ombre est deux fois plus sombre ! L’astuce consiste ici à incrémenter le stencil lorsque le stencil test et le depth test passent (dès qu’un point est assombri, il ne fait plus « partie » de la surface).           
Cependant, il existe un cas où il faut volontairement oublier ce fait : lorsque l’objet projeté est en partie transparent. Il est alors possible d’avoir un assombrissement proportionnel au nombre de surfaces qui cachent la lumière. Sauf qu’il ne faut pas trop en abuser, le temps de calcul s’allongeant encore.
-         le problème de la matrice de projection : elle n’est pas dans glu ! Voici un moyen de la calculer :
void MakeShadowMatrix(GLfloat points_plan[3][3], GLfloat lightPos[4], GLfloat destMat[4][4])
{
GLfloat planeCoeff[4];
GLfloat dot;

//on calcule un vecteur normal à ce plan
Normale(points_plan,planeCoeff);

// le dernier coefficient se calcule par substitution
planeCoeff[3] = - ( (planeCoeff[0]*points_plan[2][0]) + (planeCoeff[1]*points_plan[2][1]) + (planeCoeff[2]*points_plan[2][2]));
dot = planeCoeff[0] * lightPos[0] + planeCoeff[1] * lightPos[1] + planeCoeff[2] * lightPos[2] + planeCoeff[3] * lightPos[3];

// Now do the projection
// 1ère colonne
destMat[0][0] = dot - lightPos[0] * planeCoeff[0];
destMat[1][0] = 0.0f - lightPos[0] * planeCoeff[1];
destMat[2][0] = 0.0f - lightPos[0] * planeCoeff[2];
destMat[3][0] = 0.0f - lightPos[0] * planeCoeff[3];

// 2ème colonne
destMat[0][1] = 0.0f - lightPos[1] * planeCoeff[0];
destMat[1][1] = dot - lightPos[1] * planeCoeff[1];
destMat[2][1] = 0.0f - lightPos[1] * planeCoeff[2];
destMat[3][1] = 0.0f - lightPos[1] * planeCoeff[3];

// 3ème colonne destMat[0][2] = 0.0f - lightPos[2] * planeCoeff[0];
destMat[1][2] = 0.0f - lightPos[2] * planeCoeff[1];
destMat[2][2] = dot - lightPos[2] * planeCoeff[2];
destMat[3][2] = 0.0f - lightPos[2] * planeCoeff[3];

// 4ème colonne destMat[0][3] = 0.0f - lightPos[3] * planeCoeff[0];
destMat[1][3] = 0.0f - lightPos[3] * planeCoeff[1];
destMat[2][3] = 0.0f - lightPos[3] * planeCoeff[2];
destMat[3][3] = dot - lightPos[3] * planeCoeff[3];
}

            Les étapes de la création de l’ombre sont alors :

·        Limitation de l’ombre à la surface :      
              glEnable(GL_STENCIL_TEST);     
              glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);     
              glStencilFunc(GL_ALWAYS, 1, 0xffffffff);  
              Dessin de la surface
              glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);      
              glEnable(GL_DEPTH_TEST);
              glDisable(GL_STENCIL_TEST);

·        Dessin de l’objet

·        Calcul de la matrice de projection (avec vfloor des points de la surface) :       
              GLfloat floorPlane[4];          
              GLfloat floorShadow[4][4];  
              findPlane(floorPlane, vfloor[1], vfloor[2], vfloor[3]); 
              shadowMatrix(floorShadow, floorPlane, lightZeroPosition);

·        Changement des paramètres pour dessiner une surface noire transparente :     
              glStencilFunc(GL_EQUAL, 1, 0xffffffff); /* draw if ==1 */  
              glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);    
              glEnable(GL_BLEND);        
              glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);      
              glDisable(GL_LIGHTING); 
              glDisable(GL_TEXTURE_2D);       
              glDisable(GL_DEPTH_TEST);        
              glColor4f(0.0, 0.0, 0.0, 0.5);

·        Dessin de l’ombre :     
              glPushMatrix();         
              glMultMatrixf((GLfloat *) floorShadow);      
              dessin de l’objet      
              glPopMatrix();          

2.2.    Exemple

Voici le même cube, mais avec son ombre sur le sol. J'ai copié ci-dessous tout ce qui a changé par rapport à la dernière fois.
            //On a besoin de 3 points sur le sol
            //Remarquez que j'ai un peu triché : les points ont une valeur de 0.05f pour leur hauteur au sol.
            //Ceci a pour but d'éviter ce que l'on appelle le "Z-figthing",
            // c'est à dire les problèmes d'approximation qui, par endroit, amène l'ombre a passer sous le sol !!!
            //Elle est alors invisible. Cela peut se régler avec le Stencil buffer, mais ce n'est pas le but ici.

GLfloat sol[3][3] = {{0.0f,0.0f,0.05f}, {4.0f,0.0f,0.05f}, {0.0f,4.0f,0.05f}};
GLfloat ombre[4][4];

void cube(int ombre)
{
            GLfloat Rouge[] = {0.8f, 0.0f, 0.0f, 1.0f};
            GLfloat Vert[] = {0.0f, 0.8f, 0.0f, 1.0f};
            GLfloat Bleu[] = {0.0f, 0.0f, 0.8f, 1.0f};
            GLfloat Jaune[] = {0.8f, 0.8f, 0.0f, 1.0f};
            GLfloat Rose[] = {0.8f, 0.0f, 0.8f, 1.0f};
            GLfloat Cyan[] = {0.0f, 0.8f, 0.8f, 1.0f};

            glBegin(GL_QUADS);
                                    //Si il s'agit de l'ombre,
                                    //on dessine le cube noir légèrement transparent
                        if (!ombre) glColor4fv(Rouge); else glColor4f(0.0f, 0.0f, 0.0f, 0.25f);
                        glNormal3d(0.0,0.0,1.0);
                        glVertex3d(-1, 1, 1);
                        glVertex3d(-1, -1, 1);
                        glVertex3d( 1, -1, 1);
                        glVertex3d( 1, 1, 1);
                        if (!ombre) glColor4fv(Jaune);
                        glNormal3d(-1.0,0.0,0.0);
                        glVertex3d( -1, 1, 1);
                        glVertex3d( -1, 1, -1);
                        glVertex3d( -1, -1, -1);
                        glVertex3d( -1, -1, 1);
                        if (!ombre) glColor4fv( Vert);
                        glNormal3d(1.0,0.0,0.0);
                        glVertex3d( 1, 1, 1);
                        glVertex3d( 1, -1, 1);
                        glVertex3d( 1, -1, -1);
                        glVertex3d( 1, 1, -1);
                        if (!ombre) glColor4fv( Rose);
                        glNormal3d(0.0,1.0,0.0);
                        glVertex3d( -1, 1, 1);
                        glVertex3d( 1, 1, 1);
                        glVertex3d( 1, 1, -1);
                        glVertex3d( -1, 1, -1);
                        if (!ombre) glColor4fv( Bleu);
                        glNormal3d(0.0,-1.0,0.0);
                        glVertex3d( -1, -1, 1);
                        glVertex3d( -1, -1, -1);
                        glVertex3d( 1, -1, -1);
                        glVertex3d( 1, -1, 1);
                        if (!ombre) glColor4fv( Cyan);
                        glNormal3d(0.0,0.0,-1.0);
                        glVertex3d( 1, 1, -1);
                        glVertex3d( 1, -1, -1);
                        glVertex3d( -1, -1, -1);
                        glVertex3d( -1, 1, -1);
            glEnd();
}

void drawFloor()
{
            GLfloat Gris[] = {0.25f, 0.25f, 0.25f, 1.0f};

            glColor4fv(Gris);
            glBegin(GL_QUADS);
                        glVertex3f(-4.0, 4.0, 0.0);
                        glVertex3f( 4.0, 4.0, 0.0);
                        glVertex3f( 4.0, -4.0, 0.0);
                        glVertex3f(-4.0, -4.0, 0.0);
            glEnd();
}

void SetLight()
{
            GLfloat ambientProperties[] = {0.7f, 0.7f, 0.7f, 1.0f};
            GLfloat diffuseProperties[] = {1.0f, 1.0f, 1.0f, 1.0f};

            glLightfv( GL_LIGHT0, GL_AMBIENT, ambientProperties);
            glLightfv( GL_LIGHT0, GL_DIFFUSE, diffuseProperties);
            glLightfv( GL_LIGHT0, GL_POSITION, light_pos);
            glEnable(GL_LIGHT0);
            glEnable(GL_LIGHTING);
            glEnable(GL_COLOR_MATERIAL);
}

void CALLBACK Main_part(void)
{
            glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
            SetLight();
                        //on dessine la scène normalement
            drawFloor();
            glPushMatrix();
                        glTranslatef(0.0f, 0.0f, 5.0f);
                        glRotatef((80.0f), -0.5f, 0.3f, -0.8f);
                        glRotatef((10.0f), 0.5f, -0.3f, 0.8f);
                                    //dessin du cube en couleur normale
                        cube(0);
            glPopMatrix();
                        //on initialise la transparence
            glEnable(GL_BLEND);
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
                        //Calcul de la matrice de projection sur le sol
            MakeShadowMatrix(sol,light_pos,ombre);
            glPushMatrix();
                        glMultMatrixf((GLfloat *) ombre);
                                    //dessin de l'objet
                        glTranslatef(0.0f, 0.0f, 5.0f);
                        glRotatef((80.0f), -0.5f, 0.3f, -0.8f);
                        glRotatef((10.0f), 0.5f, -0.3f, 0.8f);
                                    //on dessine le cube en noir transparent
                        cube(1);
            glPopMatrix();
            glDisable(GL_BLEND);
            auxSwapBuffers();
}

Sources et éxécutable

 

Page précédente Page suivante