Multitextring

    Cette page (tiree de http://reality.sgi.com/jamesb) est en anglais mais il y a plein d'images. L'exemple est impressionnant (voir la page pour les touches) : Technique/reality.sgi.com/jamesb/multi.exe.zip

    Le multitexturing consiste comme son nom l'indique a faire plusieurs passages par face. La fonction la plus importante est glBlendFunc, deja vue pour la transparence. Mais si elle s'appelle ainsi et pas glTransparencyFunc, ce n'est pas pour rien !
    Blend signifie ici melanger les couleurs, et il se trouve que la transparence c'est la meme chose, mais il est possible de melanger les couleurs de plusieurs facons. Maintenant c'est un autre aspect d'OpenGL qui apparait : des fonctions puissantes, mais le moindre parametre oublie et l'effet n'est pas du tout ce qu'il etait prevu.

    glBlendFunc prend deux parametres : le 1er pour indiquer la maniere dont un nouveau point est transforme, le 2eme pour le point deja dans le buffer video. Ensuite les deux valeurs sont ajoutees (composante par composante, si >1 alors = 1).

    Voici la nouvelle fonction Draw de CCube :

void CCube::Draw(float x, float y, float z, bool multitexturing)
{
    glPushMatrix();   
        glTranslated(m_x - x, m_y - y, m_z - z);
        glRotated(m_rx, 1.0, 0.0, 0.0);
        glRotated(m_ry, 0.0, 1.0, 0.0);
        glRotated(m_rz, 0.0, 0.0, 1.0);
        if (multitexturing)
        {
            GLfloat amplitude = 1.0f;

            glColor3f(amplitude, amplitude, amplitude);

            glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
            glEnable(GL_BLEND);
            glDepthFunc(GL_LEQUAL);
            glDepthMask(GL_TRUE);
            glDisable(GL_LIGHTING);
            glDisable(GL_DITHER);
            glShadeModel(GL_FLAT);
            glEnable(GL_TEXTURE_2D);
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
            if (m_changeable)
                DrawCube();
            else
                glCallList(m_num_list);

            glEnable(GL_LIGHTING);
            glDisable(GL_TEXTURE_2D);
            glDepthFunc(GL_EQUAL);
            glDepthMask(GL_FALSE);
            glEnable(GL_DITHER);
            glBlendFunc(GL_ONE, GL_ONE);
            if (m_changeable)
                DrawCube();
            else
                glCallList(m_num_list);
        }
        else
            if (m_changeable)
                DrawCube();
            else
                glCallList(m_num_list);
    glPopMatrix();
}

    Changement de lampes dans OnSize :

    GLfloat lightZeroPosition[] = {5.0f, 0.0f, -15.0f, 1.0f};
    GLfloat lightZeroColor[] = {0.8f, 1.0f, 0.8f, 1.0f}; /* green-tinted */
    GLfloat lightOnePosition[] = {-10.0f, 0.0f, -10.0f, 0.0f};
    GLfloat lightOneColor[] = {0.6f, 0.3f, 0.2f, 1.0f}; /* red-tinted */

    glLightfv(GL_LIGHT0, GL_POSITION, lightZeroPosition);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, lightZeroColor);
    glLightfv(GL_LIGHT0, GL_SPECULAR, lightZeroColor);
    glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.05f);
    glLightfv(GL_LIGHT1, GL_POSITION, lightOnePosition);
    glLightfv(GL_LIGHT1, GL_DIFFUSE, lightOneColor);
    glLightfv(GL_LIGHT1, GL_SPECULAR, lightOneColor);
    glLightf(GL_LIGHT1, GL_LINEAR_ATTENUATION, 0.05f);
    glEnable(GL_LIGHT0);
    glEnable(GL_LIGHT1);

    GLfloat white[] = {1.0f, 1.0f, 1.0f, 1.0f};

    glMaterialfv(GL_FRONT, GL_DIFFUSE, white);
    glMaterialfv(GL_FRONT, GL_SPECULAR, white);
    glMaterialf(GL_FRONT, GL_SHININESS, 20.0f);


    D'abord, ce qu'il aurait fallu rajouter dans l'exemple de texture : couleur a (1.0, 1.0, 1.0, 1.0) et cette valeur sert a multiplier (GL_MODULATE) la couleur de la texture (ce sont les valeurs par defaut).
    Ensuite activer le blending. Puis mettre comme fonction de test de profondeur GL_LEQUAL, et autoriser l'ecriture dans le Z-Buffer (une fois encore, ce sont les valeurs par defaut, mais elles sont changees au deuxieme passage).
    L'eclairage est desactive. Ce n'est pas obligatoire, mais le resultat est meilleur, surtout qu'ici il n'y a plus de lumiere ambiente, on ne voit pas grand chose.
    L'application de texture est activee, et la fonction de blending est celle deja vue pour la transparence, mais quelques explications :
        - GL_SRC_ALPHA : la couleur a appliquer est multipliee par son facteur alpha (1.0 par defaut)
        - GL_ONE_MINUS_ALPHA : signifie "1 moins le facteur alpha", la couleur destination est multipliee par 1 - le facteur alpha de la couleur a appliquer. Cela donne bien un effet de transparence : si un point opaque (alpha = 1.0) vient par dessus, la couleur du dessous est effacee, ce qui revient a la multiplier par 0. Au contraire si le point est parfaitement transparent (alpha = 0.0), il n'apparait pas (est multiplie par 0) et celui du dessous reste tel quel (multiplie par 1).
    Il y a d'autres possibilites, mais ce sont les plus courantes.

    Deuxieme passage : cette fois on met la lumiere, enleve la texture. Cependant le cube a deja ete dessine. Pour gagner du temps, il suffit de ne dessiner que les points qui apparaissent, c'est-a-dire ceux qui ont la meme profondeur que le point precedement affiche (GL_EQUAL). Il ne faut pas modifier le Z-Buffer.
    Pour superposer directement le nouveau rendu du cube (cette fois juste de faces unies), on utilise GL_ONE pour les nouveaux points et les anciens (GL_ONE signife multiplier par 1).

    Tutorial8.jpg (13989 bytes)

    Pour les lumieres, ce qui est important c'est qu'elle ait un parametre "specular", c'est un spot lumineux, c'est lui qui donne les reflets. L'attenuation lineaire diminue l'intensite avec la distance. Pour plus de realite utliliser GL_QUADRATIC_ATTENUATION, mais tres vite la lumiere n'eclaire plus.
    Le spot ne se voit pas bien car l'intensite lumineuse n'est calculee qu'en chaque vertex : pour voir le spot, il faudrait decouper chaque face du cube en plusieurs carres (ou triangles...). Un autre moyen est de changer pour une sphere, il y a d'ailleurs une fonction gluSphere. Pour tester, changer CCube::DrawCube :

    GLUquadricObj *obj = gluNewQuadric();
    gluSphere(obj, 7, 20, 20);
    gluDeleteQuadric(obj);

    Pour qu'elles soient plus belles, passer en glShadeModel(GL_SMOOTH). Reduire si besoin le nombre de spheres (dans TutorialView.h). Augmenter le nombre de divisions des spheres.

   Un defaut apparait dans la transparence : les spheres sont visibles a l'interieur de celle de droite mais pas celle de gauche. Ceci est "normal" : le fait que cela soit la sphere de droite ou de gauche depend de l'ordre d'affichage, mais la disparation d'une partie des points est due au "depth-test", qui est a GL_LEQUAL. Lorsque l'on dessine une deuxieme sphere, la premiere est en partie recouverte, la il n'y a pas de probleme (elle apparait sous la nouvelle), mais lorsque la seconde est sous la premiere, les points ne passent pas le "depth-test", puisqu'ils sont plus loin (et donc pas less or equal) : ils disparaissent. Il est possible d'enlever le test (ou le mettre a GL_ALWAYS), mais alors la fonction de blending n'est plus la bonne. En mettant GL_ONE en second parametre de glBlendFunc tout est apparent, mais l'effet de transparence n'est pas terrible, et rien n'est attenue, cela sature (tout est blanc). Seule solution : afficher les faces dans le bon ordre pour qu'elles se recouvrent correctement (c'est la seule que je connaisse).

    Tutorial9.jpg (10466 bytes)

    Code source : Tutorial8.zip

    Etape precedente     Etape suivante