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.
- 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).
- 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.
- 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;
}
- 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).
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