[C++/OpenGL] Génération d'un terrain 3D à partir d'un bitmap
* Fonctionnement des textures sous OpenGL *
Nous allons voir rapidement voir comment fonctionne une texture. Une texture est une image qui est appliquée à des vertex puis peinte. On peut partir de plusieurs formats (tga, bmp) mais pour plus de facilté, nous allons utiliser des images de type bitmap étant donné que nous venons de voir comment on les parcourt.
Que demande OpenGL ? Il lui faut le tableau de pixels, la largeur et la hauteur de l'image. On peut récupérer tout cela il me semble non ? Et bien allons y alors ! Une fois qu'on lui a donné, il nous demande un pointeur vers un entier pour lui assigner une texture et nous permettre par la suite de lui appliquer.
On prendre donc un tableau de textures que l'on va déclarer de cette sorte :
GLuint texture[2];
Notre première texture sera donc texture[0] puis texture[1] ...
* Fabrication des textures *
BITMAPFILEHEADER bfh; BITMAPINFOHEADER bih; unsigned char * tmp; fread (&bfh, sizeof (BITMAPFILEHEADER), 1, File); fread (&bih, sizeof (BITMAPINFOHEADER), 1, File); // Allocation mémoire tmp = new unsigned char [bih.biSizeImage]; // on stocke les données fread(tmp, 1, bih.biSizeImage, File); |
Si vous avez suivi l'étape de récupération des hauteurs, ce bout de code est exactement le même, on va donc passer à la suite.
glGenTextures(1, &texture[index]); glBindTexture(GL_TEXTURE_2D, texture[index]); glTexImage2D(GL_TEXTURE_2D, 0, 3, bih.biWidth, bih.biHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, tmp); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); delete(tmp); |
Cette étape est classique, on passe notre identifiant de texture à glGenTextures en lui précisant que l'on veut qu'une seule texture. Ensuite, on créé la texture à proprement parlé avec glTexImage2D en lui passant notemment la taille de l'image ainsi que le tableau de pixels.
Le reste étant des paramètres définissant la qualité de la texture. On libère ensuite la mémoire.
Maintenant si on teste notre texture, on va remarquer une drôle de chose, et oui les couleurs ne sont pas correctes ! Pourquoi ?
Et bien parce que les pixels de couleurs ne sont pas RVB sur un bitmap mais BVR ... Il faut donc rajouter une petite étape d'inversion des deux couleurs afin d'avoir nos images de base avec leurs vraies couleurs.
unsigned char tmpR; for (int c = 0; c < bih.biSizeImage; c +=3) { tmpR = tmp[c+2]; tmp[c+2] = tmp[c]; tmp[c] = tmp; } |
Notre fonction est maintenant prête à créer de belles textures pour notre terrain.
* Application des textures *
Il faut donc revenir dans le code d'affichage du terrain et y rajouter le mapage. Avant d'accrocher la texture aux vertex, il faut dire à OpenGL qu'on va utiliser des textures, et il faut les charger :
LoadGLTextures("water.bmp",1); LoadGLTextures("land.bmp",0); if (CONFIG_filaire) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glDisable(GL_TEXTURE_2D); } else { glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glEnable(GL_TEXTURE_2D); } |
On donne donc le numéro "0" au terrain, et le numéro "1" à l'eau. Ensuite, on teste si nous sommes en mode texturé ou pas, si c'est le cas on active la texture, et on précide le mode d'affichage des polygones (filaire ou linéaire).
Maintenant attaquons nous au placement de la texture. Une texture se place selon 2 points (U et V) que l'on associe à un vertex. Ce chiffre définit en fait la position à laquelle nous nous placons sur la texture, si ce chiffre est supérieur à 1.00, la texture est alors répétée ou plus affichée (ce qui dépend du mode de texturage appliqué, par défaut elle est répétée). De plus, les axes sont inversés, c'est à dire que le U correspond au Y et le V au X. Pour notre terrain, on ne veut évidemment pas la répéter, on va donc choisir une valeur comprise entre 0 et 1 (en utilisant le même principe que lors de la conversion des hauteurs)
Code d'affichage :
// premier triangle glTexCoord2f( (float)j / CONFIG_max_points, (float)i/CONFIG_max_points); glVertex3d(posi,posj,map[i][j]); glTexCoord2f( ((float)j+1)/CONFIG_max_points,(float)i/CONFIG_max_points); glVertex3d(posi,posj+ajout,map[i][j+1]); glTexCoord2f( (float)j/CONFIG_max_points, ((float)i+1)/CONFIG_max_points); glVertex3d(posi+ajout,posj,map[i+1][j]); // second triangle glTexCoord2f( ((float)j+1)/CONFIG_max_points,((float)i+1)/CONFIG_max_points); glVertex3d(posi+ajout,posj+ajout,map[i+1][j+1]); |
Ce qui donne :
Evidemment ce n'est pas très réaliste, mais cela donne un bon aperçu du fonctionnement des textures .. Ci-dessous la même chose mais en mode "QUADS" :
On a donc une réalisation plus nette en triangle strip, même si ce n'est pas évident au premier coup d'oeil et avec cette résolution, mais vous ferez le test, on voit quand même des textures plus hâchées sur cette dernière.
Note : si vous appliquez avant une couleur avec glColor3f, un produit de cette matrice par celle de la texture sera alors effectuée et évidemment les couleurs seront étranges .. Je vous conseille donc de mettre un :
glColor3f(1.0f,1.0f,1.0f);
Le produit donnera alors le même résultat.
* Rajout de l'eau *
L'eau c'est quoi ?
Et bien, on ne va pas passer par 4 chemins, on va tout simplement rajouter un carré d'un polygone à une hauteur fixée par nous même. La profondeur étant gérée par OpenGL, tout ce qui est au-dessus de cette hauteur ne sera pas caché par l'eau.
glColor3f(1.0f,1.0f,1.0f); glBindTexture(GL_TEXTURE_2D, texture[1]); glBegin(GL_QUADS); glTexCoord2f(0,0); glVertex3d(0,0,hauteurEau); glTexCoord2f(2,0); glVertex3d(0, ajout * CONFIG_max_points,hauteurEau); glTexCoord2f(2,2); glVertex3d(ajout * CONFIG_max_points,ajout * CONFIG_max_points,hauteurEau); glTexCoord2f(0,2); glVertex3d(ajout * CONFIG_max_points,0,hauteurEau); glEnd(); |
Comme vous pouvez le remarquer, j'utilise la répétition des textures, en effet en précisant .. (2, 0), je dis que la texture sera répétée 2 fois, vous pouvez augmenter ce chiffre si vous voulez un peu plus de réalisme.
Allez testons maintenant ce petit bout de code :
Voilà une belle plaque d'eau ! Mais on va tenter de rajouter un peu de réalisme, comme une transparence non ? Il suffit d'activer l'option de BLENDING, de donner la valeur d'alpha (à l'aide de glColor4f (R, V, B, A)) et des configurations des calculs de transparence. Voilà le petit bout de code :
glColor4f(1.0f,1.0f,1.0f,0.55f); // alpha 55% // on active la transparence glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA,GL_ONE); glBindTexture(GL_TEXTURE_2D, texture[1]); glBegin(GL_QUADS); (...) glEnd(); // désactive transparence glDisable(GL_BLEND); |
Ce qui nous donne le résultat final :
Voilà, ceci termine l'explication du code ... j'espère qu'il vous avoir été utile =)