Textures

 

    Les faces unies c'est bien pour voir comment ça fonctionne, mais les textures c'est plus realiste, surtout que les textures eclairees et transparentes sont faciles a implementer.

  1. Dans un programme j'ai trouve une classe CTexture remarquablement bien faite, alors autant l'utiliser (incluse dans le code source en bas de cette page). Il y a pas mal de methodes, la principale etant ReadFile. Attention : une texture doit etre de dimension 2^n x 2^m, et sur 3DFX au maximum 256x256 (autrement 2048x2048).

    Commencer par ajouter un attribut m_Texture a la classe CCube, et l'initialiser ainsi :

        m_Texture.ReadFile("Marbre.bmp");
        m_Texture.BGRtoRGB();

        if (!m_changeable)
        {
            m_num_list = glGenLists(1);
            glNewList(m_num_list, GL_COMPILE_AND_EXECUTE);
                if(m_Texture.GetData() != NULL)
                    glTexImage2D(GL_TEXTURE_2D,0,3, m_Texture.GetWidth(), m_Texture.GetHeight(), 0, GL_RGB, GL_UNSIGNED_BYTE, m_Texture.GetData());
                glBegin(GL_POLYGON);
                    glNormal3d(0.0,0.0,1.0);
                    glTexCoord2f(0.0, 1.0); glVertex3d(-m_r, m_r, m_r);
                    glTexCoord2f(1.0, 1.0); glVertex3d(-m_r, -m_r, m_r);
                    glTexCoord2f(1.0, 0.0); glVertex3d( m_r, -m_r, m_r);
                    glTexCoord2f(0.0, 0.0); glVertex3d( m_r, m_r, m_r);
                glEnd();

                glBegin(GL_POLYGON);
                    glNormal3d(0.0,0.0,-1.0);
                    glTexCoord2f(0.0, 1.0); glVertex3d( m_r, m_r, -m_r);
                    glTexCoord2f(0.0, 0.0); glVertex3d( m_r, -m_r, -m_r);
                    glTexCoord2f(1.0, 0.0); glVertex3d( -m_r, -m_r, -m_r);
                    glTexCoord2f(1.0, 1.0); glVertex3d( -m_r, m_r, -m_r);
                glEnd();

                glBegin(GL_POLYGON);
                    glNormal3d(1.0,0.0,0.0);
                    glTexCoord2f(0.0, 0.0); glVertex3d( m_r, m_r, m_r);
                    glTexCoord2f(1.0, 0.0); glVertex3d( m_r, -m_r, m_r);
                    glTexCoord2f(1.0, 1.0); glVertex3d( m_r, -m_r, -m_r);
                    glTexCoord2f(0.0, 1.0); glVertex3d( m_r, m_r, -m_r);
                glEnd();

                glBegin(GL_POLYGON);
                    glNormal3d(-1.0,0.0,0.0);
                    glTexCoord2f(0.0, 0.0); glVertex3d( -m_r, m_r, m_r);
                    glTexCoord2f(1.0, 0.0); glVertex3d( -m_r, m_r, -m_r);
                    glTexCoord2f(1.0, 1.0); glVertex3d( -m_r, -m_r, -m_r);
                    glTexCoord2f(0.0, 1.0); glVertex3d( -m_r, -m_r, m_r);
                glEnd();

                glBegin(GL_POLYGON);
                    glNormal3d(0.0,-1.0,0.0);
                    glTexCoord2f(0.0, 0.0); glVertex3d( -m_r, -m_r, m_r);
                    glTexCoord2f(1.0, 0.0); glVertex3d( -m_r, -m_r, -m_r);
                    glTexCoord2f(1.0, 1.0); glVertex3d( m_r, -m_r, -m_r);
                    glTexCoord2f(0.0, 1.0); glVertex3d( m_r, -m_r, m_r);
                glEnd();

                glBegin(GL_POLYGON);
                    glNormal3d(0.0,1.0,0.0);
                    glTexCoord2f(0.0, 0.0); glVertex3d( -m_r, m_r, m_r);
                    glTexCoord2f(1.0, 0.0); glVertex3d( m_r, m_r, m_r);
                    glTexCoord2f(1.0, 1.0); glVertex3d( m_r, m_r, -m_r);
                    glTexCoord2f(0.0, 1.0); glVertex3d( -m_r, m_r, -m_r);
                glEnd();

            glEndList();
        }

    Rien de tres complique, mais tout est indispendable :
    - ReadFile et BGRtoRGB : special a la classe CTexture, aller voir Texture.cpp
    - glTexImage2D : specifie quelle texture doit etre utilisee (taille, nombre d'octete par point, type, emplacement memoire...)
    - glTextCoord : indique la position du prochain point sur la texture, toutes les valeurs sont autorisees, mais pour remplir un carre avec un seul exemplaire de la texture, il suffit d'associer (0,0) au coin en haut a gauche, (1,1) en bas a droite, pareil pour les autres. Si de valeurs sont plus grandes que 1, il y a deux possibilites : la texture est repetee en mosaique, ou la derniere ligne/colonne est repetee. Parametrer GL_TEXTURE_WRAP pour cela (voir man page de glTexParameter).

    Il manque un appel a glColor, par defaut (1.0, 1.0, 1.0, 1.0), car maintenant la couleur courante module la couleur de la texture (multiplie composante par composante).
  2. Comme pour la lumiere, il faut rajouter quelques petites choses dans OnSize :

        glEnable(GL_LIGHT0);
        glEnable(GL_LIGHTING);

        glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, ambientProperties);

        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glEnable(GL_TEXTURE_2D);


    Lancer l'application, et voila un cube texture eclaire en jaune. Enlever glEnable(GL_LIGHTING) ou mettre une lumiere blanche pour voir la texture avec les couleurs reelles : automatiquement la texture est eclairee !

    Quant a GL_TEXTURE_MAG_FILTER et GL_TEXTURE_MIN_FILTER, cela sert a indiquer de quelle facon la texture est agrandie ou reduite : GL_NEAREST est le calcul le plus rapide, le point choisi est le plus proche (suffisant pour une reduction), GL_LINEAR fait une sorte de moyenne des points alentours. Les autres modes utilisent les mipmaps.
  3. Pour economiser des calculs, il est possibles d'utiliser la technique des mipmaps. Cela consiste a avoir la meme texture de tailles differentes, celle qui a la taille la plus proche de la surface a dessiner est choisie. Il est possible de prendre 2 mipmap : l'une de taille au-dessus, l'autre en dessous, est de moyenner le tout.

    Il faut d'abord changer le mode d'application des textures :

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    Ensuite il faut fournir les bitmaps pour chaque taille. C'est fastidieux, et la fonction gluBuild2DMipmaps fait ca tres bien toute seule. Changement dans le constructeur de CCube :

        if(m_Texture->GetData())
        {
            glGenTextures(1, &m_Texture_num);
            glBindTexture(GL_TEXTURE_2D, m_Texture_num);
            if (m_transparent)
            {
                m_Texture->AddAlphaLayer(alpha);
                gluBuild2DMipmaps(GL_TEXTURE_2D, 4, m_Texture->GetWidth(), m_Texture->GetHeight(), GL_RGBA, GL_UNSIGNED_BYTE, m_Texture->GetData());
            }
            else
                gluBuild2DMipmaps(GL_TEXTURE_2D, 3, m_Texture->GetWidth(), m_Texture->GetHeight(), GL_RGB, GL_UNSIGNED_BYTE, m_Texture->GetData());
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);

            delete m_Texture;
            m_Texture = NULL;
        }
  4. Detail important : a chaque fois que glTexImage2D est appelee, la texture est envoyee a la carte 3D. Meme si les bus PCI / AGP sont rapides, il faut eviter d'appeler la fonction pour chaque face. Une solution est apportee par la version 1.1 d'OpenGL : glBindTexture, qui permet d'associer une texture a un numero. Gain de temps considerable si les textures sont dans la RAM de la carte 3D (s'il y a une carte avec 16 Mo ou 32 Mo avec un driver OpenGL 1.1, autant s'en servir).
    Malheureusement je n'ai pas trouve la page man de cette fonction, alors il va falloir que j'explique un peu plus (d'apres ce que j'ai cru comprendre dans des exemples). L'aide sur ces fonctions est par contre dans Visual C++ 5 (c'est de là que viennent les prototypes des fonctions).

    Première chose a faire, reserver n numero de textures, c'est ce que fait glGenTextures (avec textures declare GLuint textures[n]).

        void glGenTextures( GLsizei n, GLuint * textures );

    Ensuite il faut appeler :

        void glBindTexture( GLenum target, GLuint texture );

    avec target = GL_TEXTURE_1D ou plus probablement GL_TEXTURE_2D, et texture le numero prealablement reserve par glGenTextures.

    C'est apres que je ne sais pas exactement ce qu'il mettre, mais ce qui suit fonctionne :

        glTexImage2D(GL_TEXTURE_2D,0,3, m_Texture.GetWidth(), m_Texture.GetHeight(), 0, GL_RGB, GL_UNSIGNED_BYTE, m_Texture.GetData());
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

    glTexImage2D peut etre remplacee par gluBuild2DMipmap.

    Ensuite, pour utiliser la texture, au lieu d'appeler glTexImage2D, rappeler glBindTexture suivit d'un glBegin.
    Pour liberer une texture (dans le destructeur de CCube), utiliser :

        void glDeleteTextures(GLsizei n, const GLuint * textures );

    Dans la classe CCube, m_Texture est devenu un pointeur car apres etre passe une fois dans le constructeur, elle ne sert plus a rien (glBind en a fait une copie).

    Tutorial5.jpg (10304 bytes)

    Voila deja de quoi bien s'amuser, avec pourtant un programme tres simple. L'executable fait moins de 30 Ko, et pourtant il affiche un cube texture, eclaire et qui tourne... Pour ceux qui ont essaye de programmer la meme chose alors que les cartes 3D n'existait pas, rappelez vous votre programme de 30 Ko, ou souvent plus, a 90 % code en assembleur pour atteindre 30 fps en ModeX : les temps ont bien change.

    Code source : Tutorial5.zip

    Etape precedente     Etape suivante